different return order, do [err,val]

different return order, do [err,val]

also make "curried" version for convenience in checkers

--- src/checkers.iced
+++ src/checkers.iced
@@ -4,106 +4,122 @@

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

-exports.strip = strip = (input) ->
+strip = (input) ->
   if not input? then null
   else if (m = input.match /^\S*(.*?)\S*$/)? then m[1]
   else ''

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

-exports.in_range = in_range = (x, config) -> (x >= config.min) and (x <= config.max)
+in_range = (x, config) -> (x >= config.min) and (x <= config.max)
+
+E = (s) -> new Error s

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

-exports.check_bool = check_bool = (b) ->
-  if b      in [0,"0","false",false] then return [false, null]
-  else if b in [1,"1","true", true]  then return [true, null]
-  else return [null, 'bad boolean value']
+checkers = {}

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

-exports.is_empty = is_empty = (m) -> not(m?) or not(m.length) or m.match(/^\s+$/)
+checkers.check_bool = check_bool = (b) ->
+  if b      in [0,"0","false",false] then return [null, false]
+  else if b in [1,"1","true", true]  then return [null, true ]
+  else return [ E('bad boolean value'), null ]

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

-exports.check_pgp_message = check_pgp_message = (text, type) ->
-  [err, msg] = kbpgp.armor.decode text
-  if err? then [ null, "Error parsing PGP data: #{err.message}" ]
-  else if not is_empty(msg.post) then [ null, "found bogus trailing data" ]
-  else if not is_empty(msg.pre)  then [ null, "found bogus prefix data" ]
-  else if msg.type isnt type then     [ null, "wrong PGP message type" ]
-  else [ msg.raw(), null ]
+is_empty = (m) -> not(m?) or not(m.length) or m.match(/^\s+$/)

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

-exports.check_base64u = check_base64u = (s) ->
-  x = /^[0-9a-z-_]+$/i
-  if not s? then [ null, "unspecified" ]
-  else if s.match x then [s, null ]
-  else [ null, "not a Base64u encoded string"]
+checkers.check_pgp_message = check_pgp_message = (text, type) ->
+  [err, msg] = kbpgp.armor.decode text
+  if err? then [ new Error("Error parsing PGP data: #{err.message}"), null ]
+  else if not is_empty(msg.post) then [ E("found bogus trailing data"), null ]
+  else if not is_empty(msg.pre)  then [ E("found bogus prefix data"), null ]
+  else if msg.type isnt type then     [ E("wrong PGP message type"), null ]
+  else [ null, msg.raw() ]

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

-exports.check_hex = check_hex = (s, len) ->
-  x = /^[0-9a-f]+$/i
-  if not (s = strip s)? then [ null, 'unspecified' ]
-  else if not s.match x then [ null, 'need an id']
-  else if len? and s.length isnt len then [ null, "needed a hex string of length #{len}" ]
-  else [ s.toLowerCase(), null ]
+checkers.check_base64u = check_base64u = (s) ->
+  x = /^[0-9a-z-_]+$/i
+  if not s? then [ E("unspecified"), null ]
+  else if s.match x then [ null, s ]
+  else [ E("not a Base64u encoded string"), null ]

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

-exports.check_base64 = check_base64 = (s) ->
+checkers.check_hex = check_hex = (s, len) ->
+  x = /^[0-9a-f]+$/i
+  if not (s = strip s)? then [ E('unspecified'), null ]
+  else if not s.match x then [ E('need an id'), null]
+  else if len? and s.length isnt len then [ E("needed a hex string of length #{len}"), null ]
+  else [ null, s.toLowerCase() ]
+
+##-----------------------------------------------------------------------
+
+checkers.check_base64 = check_base64 = (s) ->
   if not s?
-    return [null, 'unspecified']
+    return [ E('unspecified'), null ]
   else
     x = /^[0-9a-z\/\+]+[=]{0,2}$/i
     s = s.replace /\s/g, ''
     if not s.match x
-      return [ null, 'not base64']
+      return [ E('not base64'), null ]
     else
-      return [ s, null]
+      return [ null, s ]

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

-exports.check_string = check_string = (s, min, max) ->
-  if not (s = strip s)? then [ null, "unspecified" ]
-  else if (min? and s.length < min) then [ null, "Must be at least #{min} long"]
-  else if (max? and s.length > max) then [ null, "Must be at least #{max} long"]
-  else [ s, null]
+checkers.check_string = check_string = (s, min, max) ->
+  if not (s = strip s)? then [ E("unspecified"), null ]
+  else if (min? and s.length < min) then [ E("Must be at least #{min} long"), null]
+  else if (max? and s.length > max) then [ E("Must be at least #{max} long"), null]
+  else [ null, s]

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

-exports.check_int = check_int = (s, min, max) ->
+checkers.check_int = check_int = (s, min, max) ->
   x = /^-?[0-9]+$/
-  if not (s = strip s)? then [ null, "Unspecified" ]
-  else if not s.match x then [ null, "need an integer" ]
-  else if isNaN(i = parseInt s) then [ null, "Could not parse integer #{s}" ]
-  else if (min? and i < min) or (max? and i > max) then [ null, "Must be in range #{min}-#{max}"]
-  else [ i, null ]
+  if not (s = strip s)? then [ E("Unspecified"), null ]
+  else if not s.match x then [ E("need an integer"), null ]
+  else if isNaN(i = parseInt s) then [ E("Could not parse integer #{s}"), null ]
+  else if (min? and i < min) or (max? and i > max) then [ E("Must be in range #{min}-#{max}"), null]
+  else [ null, i ]

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

-exports.check_id = check_id = (x, config, required = true) ->
+checkers.check_id = check_id = (x, config, required = true) ->
   empty = not x? or x.length is 0
-  if empty and required then [ null, "no ID specified" ]
+  if empty and required then [ E("no ID specified"), null ]
   else if empty and not required then [ null, null ]
-  else if x.length is 2*config.byte_length then [ x, null ]
-  else [ null, "ID has wrong length"]
+  else if x.length is 2*config.byte_length then [ null, x ]
+  else [ E("ID has wrong length"), null]

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

-exports.check_multi = check_multi = (x,fn) ->
-  if not x? or x.length is 0 then [out,err] = [ null, "no keys given" ]
+checkers.check_multi = check_multi = (x,fn) ->
+  if not x? or x.length is 0 then [err,out] = [ E("no keys given"), null ]
   else
     v = x.split /,/
     err = null
     out = []
     for e in v when not err?
-      [val,err] = fn e
+      [err, val] = fn e
       out.push val
     out = null if err?
-  return [ out, err ]
+  return [ err, out ]

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

+exports.raw = checkers
+exports.curried = curried = {}
+
+for k,v in checkers
+  ((name, fn) ->
+    curried[name] = (args...) -> (s) -> fn s, args...
+  )(k,v)
+
+##-----------------------------------------------------------------------
+


Use callback mechanism

Use callback mechanism

--- src/client.coffee
+++ src/client.coffee
@@ -225,7 +225,7 @@
             if @users[u].email == email
                 return @users[u]

-    getUserDirectMessageChannel: (userID) ->
+    getUserDirectMessageChannel: (userID, callback) ->
         # check if channel already exists
         channel = @self.id + "__" + userID
         channel = @findChannelByName(channel)
@@ -237,8 +237,8 @@
                 # channel obviously doesn't exist, let's create it
                 channel = @createDirectChannel(userID)
                 if !channel
-                    return null
-        return channel
+                    if callback? then callback(null)
+        if callback? then callback(channel)

     getAllChannels: ->
         @channels
@@ -256,7 +256,7 @@
             user_id: userID
         @_apiCall 'POST', @channelRoute('/create_direct'), postData, (data, headers) =>
             @logger.debug 'Created Direct Channel.'
-            return data.id
+            return data

     findChannelByName: (name) ->
         for c of @channel_details


Bugfix: node.textContent

Bugfix: node.textContent

--- overrides.coffee
+++ overrides.coffee
@@ -121,7 +121,7 @@
 ms.on 'marking-surface:add-tool', (tool) ->
   {label} = decisionTree.currentTask.getChoice() ? ''
   legend = tool.controls.el.querySelector 'legend'
-  legend.innerText = label if legend?
+  legend.textContent = label if legend?

 page_zoom.addEventListener 'change', (e) ->
   return unless current_tool?


Re render step view if received same step again

--- app/assets/javascripts/langtrainer_frontend_backbone/models/step.js.coffee
+++ app/assets/javascripts/langtrainer_frontend_backbone/models/step.js.coffee
@@ -84,6 +84,7 @@
       success: (response) ->
         if response
           that.set response
+          that.trigger('change', that)
           that.trigger('verify:right')
         else
           that.trigger('verify:wrong')


adding in getHosts so that we can see hosts with no services in the getServiceDetailsByHost fn

--- lib/Ndoutils.coffee
+++ lib/Ndoutils.coffee
@@ -6,36 +6,55 @@

   connect: ()->
     @db.connect()
+    return @

   end: ()->
     @db.end()
+    return @

   getServiceDetailsByHost: (cb)->
-    @getServiceDetails (err, rows, fields)->
+    self = this
+    @getHosts (err, rows, fields)->
       if err
         console.log err
         return

-      if rows.length <= 0
-        cb(null, {})
-        return
+      hosts = {}

-      hosts = {}
-      for id of rows
-        if hosts[rows[id].host_display_name]
-          hosts[rows[id].host_display_name].services.push rows[id]
-        else
-          hosts[rows[id].host_display_name] = {}
-          hosts[rows[id].host_display_name].services = []
+      rows.forEach (row)->
+        hosts[row.display_name] = {}
+        hosts[row.display_name].current_state = row.current_state
+        hosts[row.display_name].services = []
+
+      self.getServiceDetails (err, rows, fields)->
+        if err
+          console.log err
+          return
+
+        if rows.length <= 0
+          cb(null, {})
+          return
+
+        for id of rows
           hosts[rows[id].host_display_name].services.push rows[id]

-      cb(null, hosts)
+        cb(null, hosts)

     return @

   getHosts: (cb)->
-    @db.connection.query "SELECT * FROM nagios_hosts", (err, rows, fields)->
+    query =  ""
+    query += "SELECT h.display_name, hs.current_state "
+
+    query += "FROM nagios_hosts h "
+    query += "  left join nagios_hoststatus hs "
+
+    query += "ON h.host_object_id = hs.host_object_id"
+
+    @db.connection.query query, (err, rows, fields)->
       cb(err, rows, fields)
+
+    return @

   getServices: (cb)->
     query = ""


resin ssh: disable ControlMaster ssh option

This change was necessary because our ssh gateway does not
support ControlMaster mode.

Closes #366

--- lib/actions/ssh.coffee
+++ lib/actions/ssh.coffee
@@ -94,7 +94,11 @@
 			.then ({ username, uuid, containerId }) ->
 				throw new Error('Did not find running application container') if not containerId?
 				Promise.try ->
-					command = "ssh #{verbose} -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
+					command = "ssh #{verbose} -t \
+						-o LogLevel=ERROR \
+						-o StrictHostKeyChecking=no \
+						-o UserKnownHostsFile=/dev/null \
+						-o ControlMaster=no \
 						-p #{options.port} #{username}@ssh.#{settings.get('proxyUrl')} enter #{uuid} #{containerId}"

 					subShellCommand = getSubShellCommand(command)


fixed positioning of the activity header: issue was: height was not updated correctly

--- client/app/Applications/Activity.kdapplication/AppView.coffee
+++ client/app/Applications/Activity.kdapplication/AppView.coffee
@@ -43,8 +43,7 @@
     @_windowDidResize()

   setFixed:->
-    {groupEntryPoint:entryPoint} = KD.config
-    pre = if @getScrollTop() > headerHeight and not entryPoint then "set" else "unset"
+    pre = if @getScrollTop() > headerHeight = @header.getHeight() then "set" else "unset"
     @["#{pre}Class"] "fixed"

   navigateHome:(itemData)->


client: environment: minor typo

client: environment: minor typo

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -498,7 +498,7 @@
                                            # for unknown routes.

     stackBasedStates =
-      NotInitialized : 'is not build yet.'
+      NotInitialized : 'is not built yet.'

     stackText = stateTexts[@state]


minor whitespace

minor whitespace

--- client/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
+++ client/app/Applications/Groups.kdapplication/Views/permissionsmodal.coffee
@@ -152,8 +152,8 @@
     cascadeData[current]= {
       name
       cssClass
-      itemClass: KDCheckBox
-      defaultValue: isChecked ? no
+      itemClass    : KDCheckBox
+      defaultValue : isChecked ? no
     }

     if current in ['admin','owner']


ace: Updated editor shortcuts modal for new shortcuts.

--- client/landing/app/MainApp/filetree/bottomlist/finderbottomlistitem.coffee
+++ client/landing/app/MainApp/filetree/bottomlist/finderbottomlistitem.coffee
@@ -57,8 +57,11 @@
       { keySet : "cmd+s",          title : "Save File" }
       { keySet : "cmd+shift+s",    title : "Save as..." }
       { keySet : "cmd+f",          title : "Find" }
-      { keySet : "cmd+g",          title : "Find next" }
-      { keySet : "cmd+shift+g",    title : "Find previous" }
+      { keySet : "cmd+shift+f",    title : "Find and replace" }
+      { keySet : "cmd+shift+c",    title : "Compile and run" }
+      { keySet : "cmd+shift+p",    title : "Preview" }
+      # { keySet : "cmd+g",          title : "Find next" }
+      # { keySet : "cmd+shift+g",    title : "Find previous" }
       # { keySet : "cmd+b",          title : "Compile code" }
       # { keySet : "cmd+e",          title : "Run code" }
       # { keySet : "esc",            title : "Autocomplete" }


Add cookie/session middleware

Add cookie/session middleware

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

--- lib/application.coffee
+++ lib/application.coffee
@@ -14,6 +14,10 @@

 # Parse bodies - required for forms
 x.use express.bodyParser()
+
+# Use sessions
+x.use express.cookieParser()
+x.use express.session secret: 'i dont think it matters if my secret is on github'

 # Set up static files
 x.use express.static __dirname + '/../public'


code review cleanup

code review cleanup

--- common/static/coffee/src/discussion/views/thread_response_show_view.coffee
+++ common/static/coffee/src/discussion/views/thread_response_show_view.coffee
@@ -52,11 +52,11 @@
         @$(".vote-btn span.sr").html("votes (click to remove your vote)")
       else
         @unvote()
-        @$(".vote-btn span.sr").html("votes (click vote)")
+        @$(".vote-btn span.sr").html("votes (click to vote)")

     vote: ->
       url = @model.urlFor("upvote")
-      @$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) + 1 ) + '')
+      @$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) + 1) + '')
       DiscussionUtil.safeAjax
         $elem: @$(".discussion-vote")
         url: url
@@ -67,7 +67,7 @@

     unvote: ->
       url = @model.urlFor("unvote")
-      @$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) - 1 )+'')
+      @$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) - 1)+'')
       DiscussionUtil.safeAjax
         $elem: @$(".discussion-vote")
         url: url
@@ -75,7 +75,6 @@
         success: (response, textStatus) =>
           if textStatus == 'success'
             @model.set(response)
-

     edit: (event) ->


Remove transaction from onDidStopChanging as it re-fires.

--- lib/auto-indent.coffee
+++ lib/auto-indent.coffee
@@ -112,13 +112,10 @@
     return unless @autoJsx
     selectedRange = @editor.getSelectedBufferRange()
     highestRow = Math.max selectedRange.start.row, selectedRange.end.row
-    if highestRow isnt @highestSelectedRow
-      @highestSelectedRow = highestRow
-      if @jsxInScope(highestRow)
-        endPointOfJsx = new Point highestRow,0
-        startPointOfJsx =  autoCompleteJSX.getStartOfJSX @editor, endPointOfJsx
-        @editor.transact 300, =>
-          @indentJSX new Range(startPointOfJsx, endPointOfJsx)
+    if @jsxInScope(highestRow)
+      endPointOfJsx = new Point highestRow,0
+      startPointOfJsx =  autoCompleteJSX.getStartOfJSX @editor, endPointOfJsx
+      @indentJSX new Range(startPointOfJsx, endPointOfJsx)

   # is the jsx on this line in scope
   jsxInScope: (bufferRow) ->


Extracted closeDiscussion method

Extracted closeDiscussion method

--- core/app/backbone/modules/discussion_modal_on_frontend.coffee
+++ core/app/backbone/modules/discussion_modal_on_frontend.coffee
@@ -31,8 +31,7 @@
       DiscussionModalOnFrontend.setBackgroundPageUrl fragment

       if FactlinkApp.discussionModalRegion.currentView?
-        FactlinkApp.discussionModalRegion.close()
-        FactlinkApp.ModalWindowContainer.close()
+        DiscussionModalOnFrontend.closeDiscussion()
         return if already_on_the_background_page

       old_loadUrl.apply @, arguments
@@ -45,6 +44,10 @@
     FactlinkApp.discussionModalRegion.show newClientModal
     newClientModal.mainRegion.show new NDPDiscussionView model: fact

+  DiscussionModalOnFrontend.closeDiscussion = ->
+    FactlinkApp.discussionModalRegion.close()
+    FactlinkApp.ModalWindowContainer.close()
+
   DiscussionModalOnFrontend.setBackgroundPageUrl = (fragment) ->
     sanitized_fragment = Backbone.history.getFragment(fragment)
     unless FactlinkApp.showFactRegex.test sanitized_fragment


regexes for julia and python that support high Unicode code points

--- lib/autocomplete-provider.coffee
+++ lib/autocomplete-provider.coffee
@@ -9,17 +9,17 @@
         selector: selector
         disableForSelector: '.comment'

-        defaultRegex: /([\w0-9_-][\.:\$]?)+$/
+        defaultRegex: /([\w-][\.:\$]?)+$/
         regexes:

             # pretty dodgy, adapted from http://stackoverflow.com/questions/8396577/check-if-character-value-is-a-valid-r-object-name/8396658#8396658
             r: /([^\d\W]|[.])[\w.$]*$/

             # this is NOT correct. using the python one until I get an alternative
-            julia: /[^\d\W][\w.]*$/
+            julia: /([^\d\W]|[\u00A0-\uFFFF])[\w.!\u00A0-\uFFFF]*$/

             # adapted from http://stackoverflow.com/questions/5474008/regular-expression-to-confirm-whether-a-string-is-a-valid-identifier-in-python
-            python: /[^\d\W][\w.]*$/
+            python: /([^\d\W]|[\u00A0-\uFFFF])[\w.\u00A0-\uFFFF]*$/

             # taken from https://github.com/n-riesco/nel/blob/master/lib/nel.js#L713
             javascript: /[_$a-zA-Z][_$a-zA-Z0-9]*$/


JSAlertView update

JSAlertView update

--- JSKit/03_JSAlertView.coffee
+++ JSKit/03_JSAlertView.coffee
@@ -8,7 +8,7 @@
 		super()
 		@_bgColor = JSColor("clearColor")
 		@_style = "JSAlertViewStyleDefault"
-		@delegate = null
+		@delegate = @
 		@cancel = false

 	setAlertViewStyle:(@_style)->


use version specific editor.scan signature

--- lib/project/results-model.coffee
+++ lib/project/results-model.coffee
@@ -247,10 +247,14 @@
     return unless editor.getPath()

     matches = []
-    leadingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountBefore')
-    trailingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountAfter')
-    editor.scan @regex, {leadingContextLineCount, trailingContextLineCount}, (match) ->
-      matches.push(match)
+    if parseFloat(atom.getVersion()) >= 1.17
+      leadingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountBefore')
+      trailingContextLineCount = atom.config.get('find-and-replace.searchContextLineCountAfter')
+      editor.scan @regex, {leadingContextLineCount, trailingContextLineCount}, (match) ->
+        matches.push(match)
+    else
+      editor.scan @regex, (match) ->
+        matches.push(match)

     result = Result.create({filePath: editor.getPath(), matches})
     @setResult(editor.getPath(), result)


Initialize alert-history with 5 blank lines

--- views/components/info/alert.cjsx
+++ views/components/info/alert.cjsx
@@ -10,11 +10,15 @@
 # Alert info
 PoiAlert = React.createClass
   getInitialState: ->
+    history = []
+    for i in [0..4]
+      history.push <div key={keyCount++} className='alert alert-default alert-history-contents'>　</div>
+    @lastMessage = '　'
     message: __ 'Waiting for response...'
     type: 'default'
     overflow: false
     messagewidth: 0
-    history: []
+    history: history
     showHistory: false

   updateAlert: (e, overflow, alertChanged) ->


A bit of cleanup

A bit of cleanup

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -173,7 +173,7 @@
         mine: {}

       for own mode, _items of byMode
-        for own filter, modes of byFilter
+        for own _filter, modes of byFilter
           modes[mode] = {}

@@ -206,7 +206,7 @@
           filters.push 'deleted'
         else if d.message_type == 'praise'
           filters.push 'praises'
-        else
+        else if d.can_be_resolved
           if d.resolved
             filters.push 'resolved'
           else


Fixes #68. Fix error when beautifying file that is "untitled".

--- lib/beautify.coffee
+++ lib/beautify.coffee
@@ -229,34 +229,38 @@
   # and how path.dirname(DIRECTORY) returns the parent directory of DIRECTORY
   homeOptions = getConfig(path.join(userHome,"FAKEFILENAME"), false)

-  # Handle EditorConfig options
-  # http://editorconfig.org/
-  editorconfig ?= require('editorconfig');
-  editorConfigOptions = editorconfig.parse(editedFilePath);
-  # Transform EditorConfig to Atom Beautify's config structure and naming
-  if editorConfigOptions.indent_style is 'space'
-    editorConfigOptions.indent_char = " "
-    # if (editorConfigOptions.indent_size)
-    #   editorConfigOptions.indent_size = config.indent_size
-  else if editorConfigOptions.indent_style is 'tab'
-    editorConfigOptions.indent_char = "\t"
-    editorConfigOptions.indent_with_tabs = true
-    if (editorConfigOptions.tab_width)
-        editorConfigOptions.indent_size = config.tab_width
-
-  # Get all options in configuration files from this directory upwards to root
-  projectOptions = []
-  p = path.dirname(editedFilePath)
-  # Check if p is root (top directory)
-  while p isnt "/"
-    # Get config for p
-    pf = path.join(p, "FAKEFILENAME")
-    pc = getConfig(pf, false)
-    # Add config for p to project's config options
-    projectOptions.push(pc)
-    # console.log p, pc
-    # Move upwards
-    p = path.resolve(p,"../")
+  if editedFilePath?
+    # Handle EditorConfig options
+    # http://editorconfig.org/
+    editorconfig ?= require('editorconfig');
+    editorConfigOptions = editorconfig.parse(editedFilePath);
+    # Transform EditorConfig to Atom Beautify's config structure and naming
+    if editorConfigOptions.indent_style is 'space'
+      editorConfigOptions.indent_char = " "
+      # if (editorConfigOptions.indent_size)
+      #   editorConfigOptions.indent_size = config.indent_size
+    else if editorConfigOptions.indent_style is 'tab'
+      editorConfigOptions.indent_char = "\t"
+      editorConfigOptions.indent_with_tabs = true
+      if (editorConfigOptions.tab_width)
+          editorConfigOptions.indent_size = config.tab_width
+
+    # Get all options in configuration files from this directory upwards to root
+    projectOptions = []
+    p = path.dirname(editedFilePath)
+    # Check if p is root (top directory)
+    while p isnt "/"
+      # Get config for p
+      pf = path.join(p, "FAKEFILENAME")
+      pc = getConfig(pf, false)
+      # Add config for p to project's config options
+      projectOptions.push(pc)
+      # console.log p, pc
+      # Move upwards
+      p = path.resolve(p,"../")
+  else
+    editorConfigOptions = {}
+    projectOptions = []

   # Combine all options together
   allOptions = [


only redirect to first ecosystem if not present

--- tutor/src/components/qa/index.cjsx
+++ tutor/src/components/qa/index.cjsx
@@ -18,9 +18,10 @@

   componentWillMount: ->
-    unless EcosystemsStore.isLoading()
+    unless EcosystemsStore.isLoaded() or EcosystemsStore.isLoading()
       EcosystemsActions.load()
-      EcosystemsStore.once('loaded', @redirectToFirst)
+      unless @props.params?.ecosystemId?
+        EcosystemsStore.once('loaded', @redirectToFirst)

   redirectToFirst: ->
     ecosystemId = EcosystemsStore.first()?.id


Display current graphs as active in the menu when loaded

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -83,17 +83,6 @@
     return unless $.isNumeric(id)
     addGraphToPage(id)

-enableGraphLinks = (e) ->
-  e.find('a[data-graph-id]').on 'click', (e) ->
-    graph_id = $(this).attr('data-graph-id')
-    i = graphs.indexOf(graph_id)
-    if i == -1
-      addGraphToPage(graph_id)
-    else
-      removeGraphFromPage(graph_id)
-    return false
-
-
 @graphs = new Array()

 $ ->
@@ -109,7 +98,21 @@
       icon.attr('class', 'icon-refresh icon-spin')
       $.get("/entities/#{entity.data('entity-id')}/menu_metrics", (data) ->
         n = entity.append(data)
-        enableGraphLinks(n)
+        n.find('a[data-graph-id]').each (i,e) ->
+          # Show/Hide graph on click
+          $(e).on('click', ->
+            graph_id = $(this).data('graph-id').toString()
+            if graphs.indexOf(graph_id) == -1
+              addGraphToPage(graph_id)
+            else
+              removeGraphFromPage(graph_id)
+            return false
+          )
+
+          # Mark as active if the graph is open
+          if graphs.indexOf($(e).data('graph-id').toString()) != -1
+            $(e).parent('li').addClass('active')
+
         # Call click again, to display it
         link.click()
       )


Fixed site location image in summary

Fixed site location image in summary

--- app/annotation/summary.cjsx
+++ app/annotation/summary.cjsx
@@ -7,7 +7,7 @@
 steps = require '../lib/steps'

 sites =
-  siteA: ["Guinea_FoutahDjalon", "Guinea_Sangaredi", "GuineaBissau_Boe_Part1", "GuineaBissau_Boe_Part2", "IvoryCoast_Azagny", "IvoryCoast_Djouroutou", "IvoryCoast_TaiE", "IvoryCoast_TaiR", "Liberia_EastNimba", "Liberia_Grebo_Sala_part1", "Liberia_Grebo_Sala_part2", "Liberia_Grebo_Tasla", "Liberia_Sapo", "Senegal_Kayan"]
+  siteA: ["Guinea_FoutahDjalon", "Guinea_Sangaredi", "GuineaBissau_Boe_Part1", "GuineaBissau_Boe_Part2", "IvoryCoast_Azagny", "IvoryCoast_Djouroutou", "IvoryCoast_TaiE", "IvoryCoast_TaiR", "Liberia_EastNimba", "Liberia_Grebo_Sala_part1", "Liberia_Grebo_Sala_part2", "Liberia_Grebo_Tasla", "Liberia_Sapo", "Senegal_Kayan", "Liberia_Grebo_Part1"]
   siteB: ["Nigeria_Gashaka"]
   siteC: ["EquatorialGuinea_Nationwide"]
   siteD: ["DRC_BiliUere", "Uganda_Budongo", "Uganda_Bwindi", "Uganda_Ngogo", "Tanzania_Ugalla"]


use ActivityLogger in merge

use ActivityLogger in merge

--- lib/views/merge-list-view.coffee
+++ lib/views/merge-list-view.coffee
@@ -1,8 +1,7 @@
 fs = require 'fs-plus'
 {$$, SelectListView} = require 'atom-space-pen-views'
-git = require '../git'
-OutputViewManager = require '../output-view-manager'
-notifier = require '../notifier'
+git = require('../git-es').default
+ActivityLogger = require('../activity-logger').default

 module.exports =
 class ListView extends SelectListView
@@ -49,11 +48,9 @@

   merge: (branch) ->
     mergeArg = ['merge'].concat(@args).concat [branch]
-    git.cmd(mergeArg, cwd: @repo.getWorkingDirectory(), {color: true})
-    .then (data) =>
-      OutputViewManager.getView().showContent(data)
+    git(mergeArg, cwd: @repo.getWorkingDirectory(), color: true)
+    .then (result) =>
+      ActivityLogger.record(Object.assign({message: "Merge branch '#{branch}'"}, result))
       atom.workspace.getTextEditors().forEach (editor) ->
         fs.exists editor.getPath(), (exist) -> editor.destroy() if not exist
       git.refresh @repo
-    .catch (msg) ->
-      notifier.addError msg


Randomize storm order

Randomize storm order

--- app/controllers/home.coffee
+++ app/controllers/home.coffee
@@ -27,7 +27,7 @@

     @notificationPreferenceContainer.append contactCheckbox.el

-    for storm in featuredStorms
+    for storm in featuredStorms.sort(-> Math.random() - Math.random())
       (new StormStatus storm).el.appendTo @featuredStormsContainer

     StormStatus.on 'select', =>


Getting rid of jquery.

Getting rid of jquery.

Slowly getting rid of jQuery.

--- app/assets/js/shared.coffee
+++ app/assets/js/shared.coffee
@@ -1,6 +1,30 @@
-$ ->
-  # Display javascript only tags
-  $('.javascript-only').removeClass('javascript-only')
+#$ ->
+#  # Display javascript only tags
+#  $('.javascript-only').removeClass('javascript-only')
+#
+#  # Remove noscript tags for the lulz
+#  $('noscript').remove()

-  # Remove noscript tags for the lulz
-  $('noscript').remove()+#(function(d){
+#	var a = d.getElementsByClassName('javascript-only');
+#	for(var i=0; i<a.length; ++i){
+#		a[i].className = a[i].className.replace('javascript-only', '');
+#	}
+#	a = document.getElementsByTagName('noscript');
+#	for(i=0; i<a.length; ++i){
+#		(a[i].parentNode || a[i].parentElement).removeChild(a[i]);
+#	}
+#})(document);
+((d) ->
+  a = d.getElementsByClassName('javascript-only')
+  i = 0
+  while i < a.length
+    a[i].className = a[i].className.replace('javascript-only', '')
+    ++i
+  a = document.getElementsByTagName('noscript')
+  i = 0
+  while i < a.length
+    (a[i].parentNode or a[i].parentElement).removeChild a[i]
+    ++i
+  return
+) document


Fix being unable to drag+drop after changing pages

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -60,7 +60,7 @@
     )).then ->
       window.location = "/"

-
+document.addEventListener('turbolinks:load', () ->
   $("#dropzone").on "dragenter", (evt) ->
     evt.preventDefault()
     evt.stopPropagation()
@@ -93,3 +93,4 @@

   $(document).keyup (evt) ->
     $("#dropzone-overlay").fadeOut 125  if not window.isUploading and evt.keyCode is 27
+)


Add clone() method to model.

Add clone() method to model.

--- app/coffee/modules/base/model.coffee
+++ app/coffee/modules/base/model.coffee
@@ -27,6 +27,12 @@

         @.setAttrs(data)
         @.initialize()
+
+    clone: ->
+        instance = new Model(@._name, @._attrs, @._dataTypes)
+        instance._modifiedAttrs = @._modifiedAttrs
+        instance._isModified = @._isModified
+        return instance

     applyCasts: ->
         for attrName, castName of @._dataTypes


Support 'use strict'

Support 'use strict'

--- app/js/jasmine-given.coffee
+++ app/js/jasmine-given.coffee
@@ -2,7 +2,7 @@

   mostRecentlyUsed = null

-  root = @
+  root = `(1, eval)('this')`

   currentSpec = null
   beforeEach ->


remove canvasXCoord, use only dom and data coords

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -72,12 +72,10 @@
     @ctx.translate(0,@canvas.height)
     @ctx.scale(1,-1)

-  toCanvasXCoord: (dataPoint) -> ((dataPoint - @smallestX) / (@largestX - @smallestX)) * @canvas.width
-
-  toDataXCoord: (canvasPoint) -> (canvasPoint / @canvas.width) * (@largestX - @smallestX)
+  toDataXCoord: (domCoord) -> ((domCoord - @canvas.getBoundingClientRect().left)/ @canvas.width) * (@largestX - @smallestX)

   # TODO: fix the math on this one....
-  toDomXCoord: (dataPoint) -> ((dataPoint / @canvas.width) * (@largestX - @smallestX) * @canvas.width) + @canvas.getBoundingClientRect().left
+  toDomXCoord: (dataPoint) -> ((dataPoint) * (@largestX - @smallestX)) + @canvas.getBoundingClientRect().left

 class Marks
   constructor: -> @all = []
@@ -101,7 +99,6 @@
   MAX_WIDTH = 150

   constructor: (e, @canvasGraph) ->
-    #TODO: make an active state
     @canvas = @canvasGraph.canvas

     @element = document.createElement('div')
@@ -126,7 +123,6 @@
     @element.addEventListener 'click', (e) => @canvasGraph.marks.remove(@) if e.layerY < 15

   draw: (e) ->
-    console.log "E", e
     markLeftX = Math.min @startingPoint, e.pageX
     markRightX = Math.max @startingPoint, e.pageX

@@ -147,13 +143,9 @@
     @domXMin = markLeftX
     @domXMax = markRightX

-    #canvas coords
-    @canvasXMin = markLeftX - @canvasGraph.canvas.getBoundingClientRect().left
-    @canvasXMax = markRightX - @canvasGraph.canvas.getBoundingClientRect().left
-
     #data coords
-    @dataXMin = @canvasGraph.toDataXCoord(@canvasXMin)
-    @dataXMax = @canvasGraph.toDataXCoord(@canvasXMax)
+    @dataXMin = @canvasGraph.toDataXCoord(@domXMin)
+    @dataXMax = @canvasGraph.toDataXCoord(@domXMax)

   updateCursor: (e) ->
     if e.layerY < 15


canvasGraph: remove unused debug code

canvasGraph: remove unused debug code

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -1,9 +1,6 @@
 Mark = require './mark'

 class CanvasGraph
-
-  DEBUG = false
-
   constructor: (@canvas, @data) ->
     @leftPadding = 60
     @showAxes    = true


Fix isSilhouettedItem

Fix isSilhouettedItem

This fixes #3264. Checks were failing in cases where no gold amount was
set and tier was 0. Checks are now using existential operator to ensure
null/undefined.

--- app/models/ThangType.coffee
+++ app/models/ThangType.coffee
@@ -473,7 +473,7 @@
     name: name, display: display, matchedShortName: matchedShortName

   isSilhouettedItem: ->
-    return console.error "Trying to determine whether #{@get('name')} should be a silhouetted item, but it has no gem cost." unless @get('gems') or @get('tier')
+    return console.error "Trying to determine whether #{@get('name')} should be a silhouetted item, but it has no gem cost." unless @get('gems')? or @get('tier')?
     console.info "Add (or make sure you have fetched) a tier for #{@get('name')} to more accurately determine whether it is silhouetted." unless @get('tier')?
     tier = @get 'tier'
     if tier?


Remove Redundant jQuery Require

Remove Redundant jQuery Require

--- lib/dispatch.coffee
+++ lib/dispatch.coffee
@@ -5,7 +5,6 @@
 Gopath = require './gopath'
 Gobuild = require './gobuild'
 _ = require 'underscore-plus'
-$ = require('atom').$
 {MessagePanelView, LineMessageView, PlainMessageView} = require 'atom-message-panel'

 module.exports =


fix a nasty typo

fix a nasty typo

--- library/membrane/document.coffee
+++ library/membrane/document.coffee
@@ -173,7 +173,7 @@
         noVersion = "version should be a string"
         assert _.isString(version), noVersion
         @emit.call this, "version", arguments...
-        return @$version = vesrion.toString()
+        return @$version = version.toString()

     # Either get or set the outputs of the method that is being
     # described by this document. If you do not supply outputs


minor wording

minor wording

--- library/membrane/document.coffee
+++ library/membrane/document.coffee
@@ -149,7 +149,7 @@
     # is being described by this document. If you do no supply any
     # arguments this method will return already described failures.
     # The markings should be an object of `marking: level` values.
-    # The supplied markings will be concatenated with a previous.
+    # All supplied markings will be concatenated with a previous.
     markings: (markings) ->
         return @$markings or {} if arguments.length is 0
         external = "the method signature expects an object"


fix autofocus across different transcribe tools (resolves #158)

--- app/assets/javascripts/components/transcribe/tools/text-tool/index.cjsx
+++ app/assets/javascripts/components/transcribe/tools/text-tool/index.cjsx
@@ -25,12 +25,6 @@

     dx = e.pageX - @state.x #Click# - window.scrollX
     dy = e.pageY - @state.y #Click # + window.scrollY
-
-    console.log """
-      state.xClick = #{@state.xClick}
-      e.pageX = #{e.pageX}
-      delta = #{delta.x}
-    """

     @setState
       x: dx
@@ -67,13 +61,14 @@
     focus: true

   componentWillReceiveProps: ->
-    console.log 'PROPS: ', @props
-    @refs[@props.ref || 'input0'].getDOMNode().focus() if @props.focus
-
     {x,y} = @getPosition @props.subject.data
     @setState
       x: x
       y: y, => @forceUpdate() # updates component position on new subject
+
+  componentWillUpdate: ->
+    # autofocus text input element
+    @refs[@props.ref || 'input0'].getDOMNode().focus() if @props.focus

   componentWillUnmount: ->
     tool_config = @toolConfig()
@@ -85,7 +80,8 @@
     @props.tool_config ? @props.task.tool_config

   componentDidMount: ->
-    # @updatePosition()
+    # autofocus text input element (first time)
+    @refs[@props.ref || 'input0'].getDOMNode().focus() if @props.focus

     tool_config = @toolConfig()
     if tool_config.suggest == 'common'


tmp fix for ember shipment params

tmp fix for ember shipment params

--- app/assets/javascripts/trade/controllers/shipments_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/shipments_controller.js.coffee
@@ -83,13 +83,11 @@
         # TODO: implement this in a cleaner way!
         obj = self.get(prop.name)
         p = [] unless obj
-        if obj?.mapBy
+        if obj?.mapBy('id') and obj.mapBy('id')[0]
           p = obj.mapBy('id')
-        else if obj?.get
+        else if obj?.get('id')
           p = obj.get('id')
         else
           p = obj
         params[prop.param] = p
-      @openShipmentsPage params
-
-
+      @openShipmentsPage params


src/apm/coffee: Split VS path check by version

Older versions have only one path to check,
whereas starting with Visual Studio/C++ Build Tools 2017,
multiple distinct install directories exist (one per edition).

i.e. Community, Professional, Enterprise, Build Tools, and Express.

Conditioning these path checks by version
can reduce the filesystem reads (in the worst-case scenario) by 22.

--- src/apm.coffee
+++ src/apm.coffee
@@ -93,7 +93,10 @@
     return '2010' if @visualStudioIsInstalled("10.0")

   visualStudioIsInstalled: (version) ->
-    fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio #{version}", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "BuildTools", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "Community", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "Enterprise", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "Professional", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "WDExpress", "Common7", "IDE"))
+    if version < 2017
+      fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio #{version}", "Common7", "IDE"))
+    else
+      fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "BuildTools", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "Community", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "Enterprise", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "Professional", "Common7", "IDE")) or fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio", "#{version}", "WDExpress", "Common7", "IDE"))

   loadNpm: (callback) ->
     npmOptions =


add null check on notifications handler

add null check on notifications handler

--- services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
+++ services/web/app/coffee/Features/Notifications/NotificationsHandler.coffee
@@ -5,7 +5,7 @@
 oneSecond = 1000

 makeRequest = (opts, callback)->
-	if !settings.apis.notifications.url?
+	if !settings.apis.notifications?.url?
 		return callback()
 	else
 		request(opts, callback)


push correct path data to history

push correct path data to history

do not truncate the path data when no query param is present.

--- app/talk/lib/paginator.cjsx
+++ app/talk/lib/paginator.cjsx
@@ -2,7 +2,10 @@
 {History} = require 'react-router'

 updatePageQueryParam = (page) ->
-  pathname = location.href.slice(document.baseURI.length - 1, location.href.indexOf('?'))
+  base_uri_length = document.baseURI.length
+  has_params = !!window.location.search
+  end_path_index = if has_params then location.href.indexOf('?') else location.href.length
+  pathname = location.href.slice(base_uri_length - 1, end_path_index)
   @history.pushState(null, pathname, {page})

 module?.exports = React.createClass


rm coffee version

rm coffee version

--- lib/friebyrd.coffee
+++ lib/friebyrd.coffee
@@ -1,125 +0,0 @@
-((root) ->
-  root = this
-  _ = root._ || require 'underscore'
-  F = {}
-
-  # Non-determinism
-  # ---------------
-
-  F.succeed = (result) -> [result]
-  F.fail = _.always []
-
-  disjunction = (l, r) ->
-    (x) -> _.cat(l(x), r(x))
-  conjunction = (l, r) ->
-    (x) -> _.mapcat(l(x), r)
-
-  F.disj = () ->
-    return F.fail if _.isEmpty(arguments)
-    disjunction(_.first(arguments), F.disj.apply(this, _.rest(arguments)))
-
-  F.conj = () ->
-    clauses = _.toArray(arguments)
-    return F.succeed if _.isEmpty(clauses)
-    return _.first(clauses) if _.size(clauses) is 1
-    conjunction(_.first(clauses),
-                (s) -> F.conj.apply(null, _.rest(clauses))(s))
-
-  # Knowledge representation
-  # ------------------------
-
-  class LVar
-    constructor: (@name) ->
-
-  F.lvar = (name) -> new LVar(name)
-  F.isLVar = (v) -> (v instanceof LVar)
-
-  find = (v, bindings) ->
-    lvar = bindings.lookup(v)
-    return lvar if F.isLVar(v)
-    if _.isArray(lvar)
-      if _.isEmpty(lvar)
-        return lvar
-      else
-        return _.cons(find(_.first(lvar), bindings), find(_.rest(lvar), bindings))
-    lvar
-
-  class Bindings
-    constructor: (seed = {}) ->
-      @binds = _.merge({}, seed)
-    extend: (lvar, value) ->
-      o = {}
-      o[lvar.name] = value
-      new Bindings(_.merge(@binds, o))
-    has: (lvar) ->
-      @binds.hasOwnProperty(lvar.name)
-    lookup: (lvar) ->
-      return lvar if !F.isLVar(lvar)
-      return this.lookup(@binds[lvar.name]) if this.has(lvar)
-      lvar
-
-  F.ignorance = new Bindings()
-
-  F.$x = F.lvar("x")
-  F.$y = F.lvar("y")
-
-  # Unification
-  # -----------
-
-  F.unify = (l, r, bindings) ->
-    t1 = bindings.lookup(l)
-    t2 = bindings.lookup(r)
-
-    if _.isEqual(t1, t2)
-      return bindings
-    if F.isLVar(t1)
-      return bindings.extend(t1, t2)
-    if F.isLVar(t2)
-      return bindings.extend(t2, t1)
-    if _.isArray(t1) && _.isArray(t2)
-      bindings = F.unify(_.first(t1), _.first(t2), bindings)
-      bindings = if (bindings isnt null) then F.unify(_.rest(t1), _.rest(t2), bindings) else bindings
-      return bindings
-    return null
-
-  # Operational logic
-  # -----------------
-
-  F.goal = (l, r) ->
-    (bindings) ->
-      result = F.unify(l, r, bindings)
-      return F.succeed(result) if result isnt null
-      return F.fail(bindings)
-
-  F.run = (goal) ->
-    goal(F.ignorance)
-
-  # Logico
-  # ------
-
-  F.choice = ($v, list) ->
-    return F.fail if _.isEmpty(list)
-
-    F.disj(F.goal($v, _.first(list)),
-                  F.choice($v, _.rest(list)))
-
-  F.membero = F.choice
-
-  F.commono = (l, r) ->
-    $x = F.lvar("x")
-    F.conj(F.choice($x, l),
-           F.choice($x, r))
-
-  F.conso = ($a, $b, list) -> F.goal(_.cons($a, $b), list)
-
-  F.joino = ($a, $b, list) -> F.goal([$a, $b], list)
-
-  # Exports and sundries
-  # --------------------
-
-  if module?
-    module.exports = F
-  else
-    root.F = F
-
-)(this)


Only reorder when updated_at is changed

Only reorder when updated_at is changed

--- app/js/views/threads.coffee
+++ app/js/views/threads.coffee
@@ -7,7 +7,7 @@
     'click #mark-all-read': 'read'

   initialize: ->
-    @listenTo @collection, 'add change', @add
+    @listenTo @collection, 'add change:updated_at', @add
     @listenTo @collection, 'reset', @addAll

     @listenToOnce @collection, 'sync', =>


Added additional waiting.

Added additional waiting.

--- tests.coffee
+++ tests.coffee
@@ -3871,6 +3871,9 @@
       expect (error, res) =>
         test.isFalse error, error?.toString?() or error
         test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), WAIT_TIME
 ,
   (test, expect) ->
     @person1 = Person.documents.findOne @person1Id,


"Added more syntaxes to editor"

"Added more syntaxes to editor"

--- assets/js/models/edit_session.coffee
+++ assets/js/models/edit_session.coffee
@@ -1,11 +1,30 @@
 mappings = [
+  ["c_cpp", "C/C++", ["c", "cpp", "cxx", "h", "hpp"]]
+  ["clojure", "Clojure", ["clj"]]
   ["coffee", "CoffeeScript", ["coffee"]]
+  ["coldfusion", "ColdFusion", ["cfm"]]
+  ["csharp", "C#", ["cs"]]
   ["css", "CSS", ["css"]]
+  ["groovy", "Groovy", ["groovy"]]
+  ["haxe", "haXe", ["hx"]]
   ["html", "HTML", ["html", "htm"]]
+  ["java", "Java", ["java"]]
   ["javascript", "JavaScript", ["js"]]
   ["json", "JSON", ["json"]]
+  ["latex", "LaTeX", ["tex"]]
+  ["lua", "Lua", ["lua"]]
   ["markdown", "Markdown", ["md", "markdown"]]
+  ["ocaml", "OCaml", ["ml", "mli"]]
+  ["perl", "Perl", ["pl", "pm"]]
+  ["pgsql", "pgSQL", ["pgsql", "sql"]]
+  ["php", "PHP", ["php"]]
+  ["powershell", "Powershell", ["ps1"]]
+  ["python", "Python", ["py"]]
+  ["scala", "Scala", ["scala"]]
   ["scss", "SCSS", ["scss"]]
+  ["ruby", "Ruby", ["rb"]]
+  ["sql", "SQL", ["sql"]]
+  ["svg", "SVG", ["svg"]]
   ["textile", "Textile", ["textile"]]
   ["xml", "XML", ["xml"]]
 ]


simplify node splitting, fixes issue where external nodes are moved incorrectly

--- src/dom.coffee
+++ src/dom.coffee
@@ -27,10 +27,6 @@

   isExternal: (node) ->
     return node?.classList?.contains(Scribe.Constants.SPECIAL_CLASSES.EXTERNAL)
-
-  isTextNodeParent: (node) ->
-    childNodes = Scribe.DOM.filterUneditable(node.childNodes)
-    return childNodes.length == 1 && childNodes[0].nodeType == node.TEXT_NODE

   mergeNodes: (node1, node2) ->
     return node2 if !node1?
@@ -68,20 +64,13 @@
     left = node
     right = node.cloneNode(false)
     node.parentNode.insertBefore(right, left.nextSibling)
-    if Scribe.DOM.isTextNodeParent(node)
-      right.textContent = node.textContent.substring(offset)
-      [leftText, textOffset] = Scribe.DOM.findDeepestNode(left, offset)
-      leftText.textContent = leftText.textContent.substring(0, textOffset)
-      return [left, right, true]
-    else
-      # Node split
-      [child, offset] = Scribe.Utils.getChildAtOffset(node, offset)
-      [childLeft, childRight] = Scribe.DOM.splitNode(child, offset)
-      while childRight != null
-        nextRight = childRight.nextSibling
-        right.appendChild(childRight)
-        childRight = nextRight
-      return [left, right, true]
+    [child, offset] = Scribe.Utils.getChildAtOffset(node, offset)
+    [childLeft, childRight] = Scribe.DOM.splitNode(child, offset)
+    while childRight != null
+      nextRight = childRight.nextSibling
+      right.appendChild(childRight)
+      childRight = nextRight
+    return [left, right, true]

   switchTag: (node, newTag) ->
     return if node.tagName == newTag


Minor: variable name

Minor: variable name

--- src/resources/forms/orbeon/builder/resources/section/section-editor.coffee
+++ src/resources/forms/orbeon/builder/resources/section/section-editor.coffee
@@ -121,8 +121,8 @@
         updateHightlight = (updateClass, clickInterceptor) ->
             offset = f$.offset clickInterceptor
             section = findSection offset.top
-            sectionLabel = f$.find '.fr-section-title:first', section.element
-            updateClass 'hover', sectionLabel
+            sectionTitle = f$.find '.fr-section-title:first', section.element
+            updateClass 'hover', sectionTitle

         do setupLabelClickInterceptor = ->


Set data and draw it in separate methods.

--- assets/js/lightcurve.coffee
+++ assets/js/lightcurve.coffee
@@ -45,9 +45,11 @@
             else
                 0.0

-        drawData: (data) ->
-            @xScale.domain d3.extent data, (d) -> d.jd
-            @yScale.domain d3.extent data, (d) -> d.magnitude
+        setData: (@data) ->
+
+        drawChart: ->
+            @xScale.domain d3.extent @data, (d) -> d.jd
+            @yScale.domain d3.extent @data, (d) -> d.magnitude
             @xAxis.scale(@xScale)
             @yAxis.scale(@yScale)
             @svg.select('.x.axis')
@@ -55,7 +57,7 @@
             @svg.select('.y.axis')
                 .call(@yAxis)
             @svg.selectAll('circle')
-                .data(data)
+                .data(@data)
                 .enter()
                 .append('circle')
                 .attr
@@ -78,6 +80,7 @@
             phase: lc.getPhase +d.jd
         ),
         ((error, data) ->
-            lc.drawData data
+            lc.setData data
+            lc.drawChart()
         )
     )


Change creation time for tool object

Change creation time for tool object

--- app/views/ubret_tool.coffee
+++ app/views/ubret_tool.coffee
@@ -16,6 +16,9 @@

     @$el.addClass @model.get('type')
     @$el.attr 'id', @id
+    @tool = new Ubret[@model.get('type')]
+      selector: '#' + @id
+      el: @$el

   render: =>
     if (not @model.dataSource.data?) or (@model.dataSource.data.length is 0)
@@ -34,13 +37,12 @@
         filters: @model.filters.models
         selectedElements: @model.get('selectedElements')?.slice()
         selectedKey: @model.get('selectedKey')
-        el: @$el
-        selector: '#' + @id
         selectElementsCb: @selectElements
         selectKeyCb: @selectKey

       _.extend opts, @model.settings.toJSON()
-      @tool = new Ubret[@model.get('type')](opts)
+
+      @tool.setOpts opts

       # This is Horrifically ugly
       if $("##{@id}").length isnt 0


Drop enemy line when capture

Drop enemy line when capture

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -118,6 +118,15 @@
         city.popular = 1
         effects[Player.Neutral.id] = 0
         newCamp = maxIndex(effects)
+        @lines = _.filter @lines, (line) ->
+          if line.start == city
+            console.log(line.end.camp, newCamp)
+            line.end.camp.id == newCamp
+          else if line.end == city
+            console.log(line.start.camp, newCamp)
+            line.start.camp.id == newCamp
+          else
+            true
         city.camp = _.find @players, (p) -> p.id == newCamp
       city.refresh()


Simplify

Simplify

--- src/coffee/rest.coffee
+++ src/coffee/rest.coffee
@@ -26,8 +26,23 @@
   @

 exports.Rest.prototype.GET = (resource, callback)->
-  options = @_options
-  _get = ->
+  params =
+    resource: resource
+    method: "GET"
+  exports.preRequest(@_options, params, callback)
+
+exports.Rest.prototype.POST = (resource, payload, callback)->
+  params =
+    resource: resource
+    method: "POST"
+    body: payload
+  exports.preRequest(@_options, params, callback)
+
+exports.Rest.prototype.PUT = -> #noop
+exports.Rest.prototype.DELETE = -> #noop
+
+exports.preRequest = (options, params, callback)->
+  _req = ->
     unless options.access_token
       exports.doAuth options.config, (data)->
         access_token = data.access_token
@@ -36,26 +51,16 @@
           headers:
             "Authorization": "Bearer #{access_token}"
         # call itself again (this time with the access_token)
-        _get()
+        _req()

     request_options = _.clone(options.request)
     _.extend request_options,
-      uri: "#{request_options.uri}#{resource}"
-      method: "GET"
+      uri: "#{request_options.uri}#{params.resource}"
+      method: params.method
+    if params.body
+      request_options.body = params.body
     exports.doRequest(request_options, callback)
-  _get()
-
-exports.Rest.prototype.POST = (resource, payload, callback)->
-  options = _.clone(@_options.request)
-  _.extend options,
-    uri: "#{options.uri}#{resource}"
-    method: "POST"
-    body: payload
-
-  exports.doRequest(options, callback)
-
-exports.Rest.prototype.PUT = -> #noop
-exports.Rest.prototype.DELETE = -> #noop
+  _req()

 exports.doRequest = (options, callback)->
   request options, (error, response, body)->


Update icons.coffee

Update icons.coffee

--- src/tasks/build/after/local/ios/icons.coffee
+++ src/tasks/build/after/local/ios/icons.coffee
@@ -39,6 +39,9 @@

     if icons?.ios?.icon60x2
       grunt.file.copy icons.ios.icon60x2, path.join(res, 'icon-60@2x.png'), encoding: null
+
+    if icons?.ios?.icon60x3
+      grunt.file.copy icons.ios.icon60x3, path.join(res, 'icon-60@3x.png'), encoding: null

     if icons?.ios?.icon72
       grunt.file.copy icons.ios.icon72, path.join(res, 'icon-72.png'), encoding: null


fix tabbed literate code

fix tabbed literate code

--- lexer.coffee
+++ lexer.coffee
@@ -65,15 +65,15 @@

   # Preprocess the code to remove leading and trailing whitespace, carriage
   # returns, etc. If we're lexing literate CoffeeScript, strip external Markdown
-  # by removing all lines that aren't indented by at least four spaces.
+  # by removing all lines that aren't indented by at least four spaces or a tab.
   clean: (code) ->
     code = code.slice(1) if code.charCodeAt(0) is BOM
     code = "\n#{code}" if WHITESPACE.test code
     code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
     if @literate
       lines = for line in code.split('\n')
-        if LITERATE.test line
-          line.substr(4)
+        if match = LITERATE.exec line
+          line[match[0].length..]
         else
           '# ' + line
       code = lines.join '\n'


𝗣𝗮𝗿𝘁 𝗜𝗜: Fix that some keyboard commands would insert text

Follow up to ae613e1e46290be1fa0e8840572e2e62fe394783, which wasn't quite right. Fixes that key combos like control+E and control+A would insert "e" / "a" in Firefox.

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -156,7 +156,8 @@

     keypress: (event) ->
       return if @inputSummary.eventName?
-      return if keyEventIsKeyboardCommand(event)
+      return if event.metaKey
+      return if event.ctrlKey and not event.altKey

       if string = stringFromKeyEvent(event)
         @delegate?.inputControllerWillPerformTyping()


Re-work error checking

Re-work error checking

--- app/coffee/ImageOptimiser.coffee
+++ app/coffee/ImageOptimiser.coffee
@@ -11,11 +11,12 @@
 			timeout: 30 * 1000
 			killSignal: "SIGKILL"
 		exec args, opts,(err, stdout, stderr)->
-			if err? and err?.signal == 'SIGKILL'
-				logger.warn {err: err, stderr: stderr, localPath: localPath}, "optimiser timeout reached"
-				err = null
 			if err?
-				logger.err err:err, stderr:stderr, localPath:localPath, "something went wrong converting compressPng"
+				if err.signal == 'SIGKILL'
+					logger.warn {err: err, stderr: stderr, localPath: localPath}, "optimiser timeout reached"
+					err = null
+				else
+					logger.err err:err, stderr:stderr, localPath:localPath, "something went wrong converting compressPng"
 			else
 				logger.log  localPath:localPath, "finished compressPng file"
 			callback(err)


Resubscribe on reconnect

Resubscribe on reconnect

--- app/coffee/modules/events.coffee
+++ app/coffee/modules/events.coffee
@@ -62,6 +62,7 @@
             path = _.trimStart(url, "/")
             url = "#{scheme}//#{loc.host}/#{path}"

+        @.error = false
         @.ws = new @win.WebSocket(url)
         @.ws.addEventListener("open", @.onOpen)
         @.ws.addEventListener("message", @.onMessage)


build($fuSessionModule): Add fuSessionModule to build task

--- lib/grunt/utils.coffee
+++ lib/grunt/utils.coffee
@@ -87,6 +87,7 @@
         fuEditFilenameModule: false
         fuImagePreviewModule: false
         fuImageValidationModule: false
+        fuSessionModule: false
         fuSrcJquery: false
         fuSrcS3Jquery: false
         cryptoJs: false


Dollards to cents.

Dollards to cents.

--- client/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -181,11 +181,11 @@
             cssClass      : "allocation"
             change        : @bound "allocationChanged"
             selectOptions : [
-              { title : "$ 10",  value : "10" }
-              { title : "$ 20",  value : "20" }
-              { title : "$ 30",  value : "30" }
-              { title : "$ 50",  value : "50" }
-              { title : "$ 100", value : "100" }
+              { title : "$ 10",  value : "1000" }
+              { title : "$ 20",  value : "2000" }
+              { title : "$ 30",  value : "3000" }
+              { title : "$ 50",  value : "5000" }
+              { title : "$ 100", value : "10000" }
             ]
           desc            :
             itemClass     : KDCustomHTMLView


corresponding key event handlers for zoom buttons

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -32,12 +32,16 @@
     zoomingTimeoutId: null

   componentDidMount: ->
-    addEventListener "keydown", @frameKeyPan
-    addEventListener "mousewheel", @frameKeyPan
+    if @props.project? && 'pan and zoom' in @props.project?.experimental_tools
+      # these events enable a user to navigate an image using arrows, +, and - keys,
+      # while the user is in pan and zoom mode.
+      addEventListener "keydown", @frameKeyPan
+      addEventListener "mousewheel", @frameKeyPan

   componentWillUnmount: ->
-    removeEventListener "keydown", @frameKeyPan
-    removeEventListener "mousewheel", @frameKeyPan
+    if @props.project? && 'pan and zoom' in @props.project?.experimental_tools
+      removeEventListener "keydown", @frameKeyPan
+      removeEventListener "mousewheel", @frameKeyPan

   render: () ->
     subject = @props.subject
@@ -78,13 +82,27 @@

-              <button title={"zoom out"} className={"zoom-out fa fa-minus" + if @cannotZoomOut() then " disabled" else "" } onMouseDown={ @continuousZoom.bind(this, 1.1 ) } onMouseUp={@stopZoom} />
+              <button
+                title={ "zoom out" }
+                className={"zoom-out fa fa-minus" + if @cannotZoomOut() then " disabled" else "" }
+                onMouseDown={ @continuousZoom.bind(this, 1.1 ) }
+                onMouseUp={@stopZoom}
+                onKeyDown={@keyDownZoomButton.bind(this,1.1)}
+                onKeyUp={@stopZoom}
+              />

-              <button title={"zoom in"} className="zoom-in fa fa-plus" onMouseDown={@continuousZoom.bind(this, .9)} onMouseUp={@stopZoom} />
+              <button
+                title={ "zoom in" }
+                className={ "zoom-in fa fa-plus" }
+                onMouseDown={@continuousZoom.bind(this, .9)}
+                onMouseUp={@stopZoom}
+                onKeyDown={@keyDownZoomButton.bind(this,.9)}
+                onKeyUp={@stopZoom}
+              />

-              <button title={"rest zoom levels"} className={"reset fa fa-refresh" + if @cannotZoomOut() then " disabled" else ""} onClick={ this.zoomReset } ></button>
+              <button title={"reset zoom levels"} className={"reset fa fa-refresh" + if @cannotZoomOut() then " disabled" else ""} onClick={ this.zoomReset } ></button>

}

@@ -118,17 +136,22 @@
     return if change == 0
     @setState zooming: true, =>
       zoomNow = =>
-        # if !@state.zooming, we don't want to continuously call setTimeout
+        # if !@state.zooming, we don't want to continuously call setTimeout.
+        # !@state.zooming will be the case after a user creates a mouseup event.
         return if !@state.zooming
         @zoom(change)
-        clearTimeout @state.zoomingTimeoutId
+        @clearZoomingTimeout()
         @setState zoomingTimeoutId: setTimeout(zoomNow, 200)

       zoomNow()

+  clearZoomingTimeout: ->
+    if @state.zoomingTimeoutId
+      clearTimeout @state.zoomingTimeoutId
+
   zoom: (change) ->
     return if !@state.zooming
-    clearTimeout @state.zoomingTimeoutId
+    @clearZoomingTimeout()
     newNaturalWidth = @state.viewBoxDimensions.width * change
     newNaturalHeight = @state.viewBoxDimensions.height * change

@@ -146,6 +169,12 @@
           x: newNaturalX,
           y: newNaturalY

+  keyDownZoomButton: (change, e) ->
+    # only zoom if a user presses enter on the zoom button.
+    if e.which == 13
+      @setState zooming: true, =>
+        @zoom(change)
+
   stopZoom: (e) ->
     e.stopPropagation()
     @setState zooming: false
@@ -189,7 +218,7 @@
         width: @state.viewBoxDimensions.width
         height: @state.viewBoxDimensions.height

-  frameKeyPan: (e)->
+  frameKeyPan: (e) ->
     return unless @state.panEnabled
     keypress = e.which
     switch keypress


applied suggestions in Sinan's comments

applied suggestions in Sinan's comments

--- client/landing/app/Applications/Home.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Home.kdapplication/AppController.coffee
@@ -73,9 +73,9 @@
   createContentDisplayWithOptions:(options, callback)->
     {model, route, query} = options

+    controller = @getSingleton 'contentDisplayController'
     switch route
       when 'About'
         contentDisplay = new AboutView
-        controller = @getSingleton 'contentDisplayController'
         controller.emit 'ContentDisplayWantsToBeShown', contentDisplay
         callback contentDisplay


reverted this to @ - fixed in another branch - lets not clash

--- client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -18,7 +18,7 @@
     treeOptions.maxRecentFolders  = options.maxRecentFolders  or= 10
     treeOptions.useStorage        = options.useStorage         ?= no
     treeOptions.loadFilesOnInit   = options.loadFilesOnInit    ?= no
-    treeOptions.delegate          = this
+    treeOptions.delegate          = @

     super options, data


log: couldn't fetch vms

log: couldn't fetch vms

--- client/Terminal/terminalstarttab.coffee
+++ client/Terminal/terminalstarttab.coffee
@@ -27,6 +27,7 @@

     vmController.fetchVMs force, (err, vms)=>
       if err
+        ErrorLog.create "terminal: Couldn't fetch vms", reason:err
         return new KDNotificationView title : "Couldn't fetch your VMs"

       vms.sort (a,b)-> a.hostnameAlias > b.hostnameAlias


activity/flux: fix property retrieval

activity/flux: fix property retrieval

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -61,7 +61,7 @@
   selectedChannel
   (threads, channel) ->
     return null  unless channel
-    thread = threads.get channel.id
+    thread = threads.get channel.get('id')
     return thread.set 'channel', channel
 ]


Add locksmithdon@gmail.com as a bcc to all new user registrations.

--- client/setupNewUser/setupNewUser.coffee
+++ client/setupNewUser/setupNewUser.coffee
@@ -19,6 +19,7 @@
 						from: 'All-Court Registrations <postmaster@allcourt.co.nz>',
 						to: [Meteor.user().profile.email, 'Tennis Auckland <volunteers@tennisauckland.co.nz>'],
 						replyTo: [Meteor.user().profile.email, 'Tennis Auckland <volunteers@tennisauckland.co.nz>'],
+						bcc: 'locksmithdon@gmail.com',
 						subject: 'New user registration on allcourt.co.nz',
 						text: "#{Meteor.user().profile.firstName}, thank you for your #{Meteor.user().profile.type} registration at All-Court.\n\nIf you have any questions, just reply to this message and someone from Tennis Auckland will get back to you as soon as they can.\n\nAll-Court is still under development, but in the near future (before the tournaments begin) you'll be able to use it to help with your involvement in the tournaments.\n\nWarm regards,\nTennis Auckland"


Hacky solution for offering to merge if checks are restarted

--- app/scripts/contentscript.coffee
+++ app/scripts/contentscript.coffee
@@ -118,10 +118,10 @@
       store.refresh callback
   , 400

-waitUntilVisible = (selector, callback) ->
+waitUntilVisible = (selector, timeout, callback) ->
   count = $(selector).length
   if count is 0
-    setTimeout (-> waitUntilVisible selector, callback), 50
+    setTimeout (-> waitUntilVisible selector, timeout, callback), timeout
   else
     callback()

@@ -129,7 +129,12 @@
   # console.debug("page changed", document.URL)
   Renderer.renderState(Renderer.states.vanilla)
   if isPullRequestPage()
-    waitUntilVisible ".gh-header-title", updateSite
+    waitUntilVisible ".gh-header-title", 50, ->
+      updateSite()
+
+waitUntilVisible ".status-description.text-pending", 3000, ->
+  console.log 'Branch status changed, updating'
+  updateSite()

 store.watch ->
   console.log "Store changed, updating!"


Revert "If unit display name is non-empty string, change it to a defaul (STUD-1284)"

This reverts commit b8da1c5bb78a0f3219f0d93a946402938b801b84.

--- cms/static/coffee/src/views/unit.coffee
+++ cms/static/coffee/src/views/unit.coffee
@@ -271,10 +271,7 @@
       @$spinner = $('');

     render: =>
-      display_name = @model.get('metadata').display_name
-      if not display_name:
-        display_name = gettext '[Untitled]'
-      @$('.unit-display-name-input').val(display_name)
+      @$('.unit-display-name-input').val(@model.get('metadata').display_name)

     setEnabled: =>
       disabled = @model.get('state') == 'public'
@@ -288,9 +285,6 @@
       metadata = $.extend({}, @model.get('metadata'))
       metadata.display_name = @$('.unit-display-name-input').val()
       @model.save(metadata: metadata)
-      display_name = metadata.display_name
-      if not display_name:
-        display_name = gettext '[Untitled]'
       # Update name shown in the right-hand side location summary.
       $('.unit-location .editing .unit-name').html(metadata.display_name)
       analytics.track "Edited Unit Name",


babel polyfill is in webivew

babel polyfill is in webivew

--- coach/configs/webpack.production.coffee
+++ coach/configs/webpack.production.coffee
@@ -4,7 +4,7 @@
 module.exports =
   entry:
     main: ['resources/styles/main.less']
-    'full-build': [ "babel-polyfill", 'full-build']
+    'full-build': ['full-build']
   output:
     filename: '[name].min.js'
   plugins: [


add chrome store

add chrome store

--- app/src/tasks/ticker_task.coffee
+++ app/src/tasks/ticker_task.coffee
@@ -3,13 +3,18 @@
 ###
 class CurrencyCache

-  _cache: {}
+  _cache = {}
+  chromeStore = new ledger.storage.ChromeStore('ticker')
+  chromeStore.get 'ticker_cache', (r) =>
+    _cache = r.ticker_cache?
+

   get: () ->
-    return @_cache
+    return _cache

   set: (currencies) ->
-    @_cache = currencies
+    _cache = currencies
+    chromeStore.set {ticker_cache: currencies}

 ###
@@ -34,13 +39,11 @@

   updateTicker: () ->
     return unless @isRunning()
-    @_currenciesRestClient.getAllCurrencies (currencies, error) =>
+    @_currenciesRestClient.getAllCurrencies (currencies) =>
       @_cache.set(currencies)
       #10 minutes * 60 seconds * 1000 milliseconds = 600000ms
       setTimeout((() => @updateTicker()), 600000)
-      #l(@_cache.get())
-      return
-
+      #l @_cache.get()

   getCache: () ->
     @_cache.get()


Rename "Archive" to "Delete" since that's what it actually does

--- app/talk/inbox-conversation.cjsx
+++ app/talk/inbox-conversation.cjsx
@@ -109,7 +109,7 @@
               }
           </div>
           }
-        <SingleSubmitButton className="delete-conversation" onClick={@handleDelete}>Archive this conversation</SingleSubmitButton>
+        <SingleSubmitButton className="delete-conversation" onClick={@handleDelete}>Delete this conversation</SingleSubmitButton>
         <div>{@state.messages.map (message) -> <Message data={message} key={message.id} />}</div>
         <CommentBox
           header={"Send a message..."}


publish isnt working from the page to the js file for some reason. reverting to method call, will investigate

--- app/coffee/context_panel.coffee
+++ app/coffee/context_panel.coffee
@@ -1,7 +1,7 @@
-contextController = null
+window.contextController = null

 $(document).ready ->
-  contextController = new ContextController('#contextPanel')
+  window.contextController = new ContextController('#contextPanel')

 class ContextController
   constructor: (containerSelector) ->
@@ -18,6 +18,7 @@
     $(containerSelector).find('.context').click (event) =>
       @toggleProfile()

+    # why isnt this working?
     $.subscribe 'profile:expand', (data)=>
       @toggleProfile()


Use class instead of element

Use class instead of element

--- shared/specs/components/change-student-id-form.spec.coffee
+++ shared/specs/components/change-student-id-form.spec.coffee
@@ -13,7 +13,7 @@

   it 'renders values from props', ->
     Testing.renderComponent( ChangeStudentIdForm, props: @props ).then ({dom}) =>
-      expect(dom.querySelector('h3').textContent).to.equal(@props.title)
+      expect(dom.querySelector('.title').textContent).to.equal(@props.title)
       expect(dom.querySelector('.control-label').textContent).to.equal(@props.label)
       expect(dom.querySelector('.btn').textContent).to.equal(@props.saveButtonLabel)


Small improvement, use api now.

Small improvement, use api now.

--- app/assets/javascripts/app/controllers/widget/translation_support.js.coffee
+++ app/assets/javascripts/app/controllers/widget/translation_support.js.coffee
@@ -5,7 +5,7 @@
     check = =>

       # to not translate en
-      return if App.i18n.get().substr(0,2) is 'en'
+      return if App.i18n.notTranslatedFeatureEnabled(App.i18n.get())

       # only show for admins
       return if !@isRole('Admin')


isLadder is not same as isType('ladder') (#6446)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/lib/LevelLoader.coco.coffee
+++ app/lib/LevelLoader.coco.coffee
@@ -541,7 +541,9 @@
         delete patch[key]
     unless _.isEmpty patch
       if @level.isLadder() and @session.get('team')
-        patch.team = 'humans'  # Save the team in case we just assigned it in PlayLevelView, since sometimes that wasn't getting saved and we don't want to save ogres team in ladder
+        patch.team = @session.get('team')
+        if @level.isType('ladder')
+          patch.team = 'humans'  # Save the team in case we just assigned it in PlayLevelView, since sometimes that wasn't getting saved and we don't want to save ogres team in ladder
       @session.set key, value for key, value of patch
       tempSession = new LevelSession _id: @session.id
       tempSession.save(patch, {patch: true, type: 'PUT'})


Update project lab to use new pages resource

--- app/pages/lab/science-case.cjsx
+++ app/pages/lab/science-case.cjsx
@@ -1,6 +1,8 @@
 React = require 'react'
 AutoSave = require '../../components/auto-save'
 handleInputChange = require '../../lib/handle-input-change'
+PromiseRenderer = require '../../components/promise-renderer'
+apiClient = require '../../api/client'

 module.exports = React.createClass
   displayName: 'EditProjectScienceCase'
@@ -8,14 +10,26 @@
   getDefaultProps: ->
     project: {}

+  fetchOrCreate: ->
+    new Promise (resolve, reject) =>
+      @props.project.get('pages', url_key: 'science_case').then ([scienceCase]) =>
+        if scienceCase?
+          resolve(scienceCase)
+        else
+          params = project_pages: { url_key: "science_case", title: "Science", language: @props.project.primary_langauge }
+          apiClient.post(@props.project._getURL("project_pages"), params).then ([scienceCase]) =>
+            resolve(scienceCase)
+
   render: ->

This page is for you to describe your research motivations and goals to the volunteers. Feel free to add detail, but try to avoid jargon. This page renders markdown, so you can format it and add images via the Media Library and links. The site will show your team members with their profile pictures and roles to the side of the text.

-        <AutoSave resource={@props.project}>
-          Science case
-

-          <textarea className="standard-input full" name="science_case" value={@props.project.science_case} rows="20" onChange={handleInputChange.bind @props.project} />
-        </AutoSave>
+        <PromiseRenderer promise={@fetchOrCreate()}>{ (scienceCase) ->
+          <AutoSave resource={scienceCase}>
+            Science case
+

+            <textarea className="standard-input full" name="science_case" value={@props.project.science_case} rows="20" onChange={handleInputChange.bind @props.project} />
+          </AutoSave>
+        }</PromiseRenderer>


I guess I just removed a comment

I guess I just removed a comment

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -128,10 +128,8 @@
         workflow_id: workflow.id
         sort: 'cellect' unless SKIP_CELLECT

-      # TODO: If something goes wrong (Cellect is down, etc.), pull a list of subject from somewhere else.
-      fetchSubjects = apiClient.type('subjects').get subjectQuery
-        .then (subjects) ->
-          upcomingSubjects.forWorkflow[workflow.id].push subjects...
+      fetchSubjects = apiClient.type('subjects').get(subjectQuery).then (subjects) ->
+        upcomingSubjects.forWorkflow[workflow.id].push subjects...

       # If we're filling this list for the first time, we won't have a subject selected, so try again.
       subject ?= fetchSubjects.then ->


feat: implement css for logout

feat: implement css for logout

--- app/routes/authenticated.coffee
+++ app/routes/authenticated.coffee
@@ -50,6 +50,7 @@
       userId = user.get "id"
       accountId = user.get("email").split("@").pop().trim();
       analytics.login(userId,accountId)
+      analytics.logout()
     catch error


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -152,6 +152,7 @@
             if value is Module
               moduleName = key

+        if
         unless I.included_modules.include moduleName
           I.included_modules.push moduleName
           self.extend Module(I, self)


Workspace: Check same origin policy for iframe and display a message if it fails.

--- client/landing/CollaborativeWorkspace/panes/collaborativepreviewpane.coffee
+++ client/landing/CollaborativeWorkspace/panes/collaborativepreviewpane.coffee
@@ -18,6 +18,7 @@
     @previewer.on "ViewerLocationChanged", =>
       @saveUrl()
       @previewPane.secureInfo?.destroy()
+      @sameOriginMessage?.destroy()

     @previewer.on "ViewerRefreshed",       => @saveUrl yes

@@ -29,7 +30,9 @@
     if value?.url
       @recreateIframe()
       @previewer.openPath value.url
+      @checkSameOrigin value.url
       @previewPane.secureInfo?.destroy()
+      @sameOriginMessage?.destroy()

   openUrl: (url) ->
     @recreateIframe()
@@ -45,9 +48,34 @@
     url    = unless force then path.replace(/\?.*/, "") else "#{path}?#{Date.now()}"

     @workspaceRef.child("url").set url
+    @checkSameOrigin url
     @workspace.addToHistory
       message: "$0 opened #{url}"
       by     : KD.nick()
+
+  checkSameOrigin: (url) ->
+    $.ajax
+      type    : "GET"
+      url     : "https://ssl.koding.com/#{url}"
+      success : (responseCode) =>
+        @createSameOriginMessage url  if responseCode.trim() is "0"
+
+  createSameOriginMessage: (url) ->
+    @sameOriginMessage?.destroy()
+    @sameOriginMessage = new KDCustomHTMLView
+      partial   : "<span>Unfortunately, the site you're trying to preview doesn't allow us to show its content here, </span>"
+      cssClass  : "tw-browser-splash"
+
+    @sameOriginMessage.addSubView new KDCustomHTMLView
+      tagName   : "a"
+      partial   : "click here"
+      click     : -> window.open url.replace /\?.*/, ""
+
+    @sameOriginMessage.addSubView new KDCustomHTMLView
+      tagName   : "span"
+      partial   : " to open in a new browser tab"
+
+    @previewPane.container.addSubView @sameOriginMessage

   viewAppended: ->
     super


Revert "fix following facets, remove some of old cache stuff"

This reverts commit c77588c24dfef1dbf44f547d2f1c870cae429168.

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -108,19 +108,17 @@
     KD.time "Activity fetch took - "
     options = to : options.to or Date.now()

-    @fetchActivity options, (err, teasers)=>
+    @fetchCachedActivity options, (err, cache)=>
       @isLoading = no
-      @listController.hideLazyLoader()
-      KD.timeEnd "Activity fetch took"
-
-      if err or teasers.length is 0
-        warn "An error occured:", err  if err
+      if err or cache.length is 0
+        warn err  if err
+        @listController.hideLazyLoader()
         @listController.showNoItemWidget()
       else
-        @extractTeasersTimeStamps(teasers)
-        @listController.listActivities teasers
-
-      callback? err, teasers
+        @extractCacheTimeStamps cache
+        @sanitizeCache cache, (err, cache)=>
+          @listController.hideLazyLoader()
+          @listController.listActivitiesFromCache cache

   fetchActivitiesFromCache:(options = {})->
     @fetchCachedActivity options, (err, cache)=>
@@ -216,17 +214,38 @@
         createdAt : -1

     {CActivity}   = KD.remote.api
-    options.feedType = @feedType
-
-    if options.facets is activityTypes
-      options.facets = ['Everything']
-
-    if @feedType is "Public"
-      options.groupId = KD.getSingleton("groupsController").getCurrentGroup().getId()
-
-      @fetchActivitiesFromCache options, callback
+    if KD.config.useNeo4j
+      options.feedType = @feedType
+
+      if options.facets is activityTypes
+        options.facets = ['Everything']
+
+      if @feedType is "Public"
+        options.groupId = KD.getSingleton("groupsController").getCurrentGroup().getId()
+        CActivity.fetchPublicContents options, callback
+      else
+        CActivity.fetchFolloweeContents options, callback
+
     else
-      CActivity.fetchFolloweeContents options, callback
+      @isExempt (exempt)->
+        options.lowQuality = exempt
+        CActivity.fetchFacets options, (err, activities)->
+          if err
+            callback err
+          else if not exempt
+            KD.remote.reviveFromSnapshots clearQuotes(activities), callback
+          else
+            # trolls and admins in show troll mode will load data on request
+            # as the snapshots do not include troll comments
+            stack = activities.map (activity)-> (cb)->
+              activity.fetchTeaser (err, teaser)->
+                if err then console.warn 'could not fetch teaser'
+                else cb err, teaser
+                stack.fin()
+              , yes
+
+            dash stack, (err, res)->
+              callback null, res

   # Fetches activities that occured after the first entry in user feed,
   # used for minor disruptions.
@@ -274,13 +293,23 @@
         log "fetchSomeActivities timeout reached"

   fetchCachedActivity:(options = {}, callback)->
-    options.timestamp or= options.to
-    options.groupName   = KD.getSingleton("groupsController").getCurrentGroup()?.slug or "koding"
-    options.facets      = @getFilter()
-
-    KD.remote.api.CStatusActivity.fetchPublicActivityFeed options, (err, result)=>
-      result.overview.reverse() if result?.overview
-      return callback err, result
+    if KD.config.useNeo4j
+      options.timestamp or= options.to
+      options.groupName   = KD.getSingleton("groupsController").getCurrentGroup()?.slug or "koding"
+      options.facets      = @getFilter()
+
+      KD.remote.api.CStatusActivity.fetchPublicActivityFeed options, (err, result)=>
+        result.overview.reverse() if result?.overview
+        return callback err, result
+
+    else
+      $.ajax
+        url     : "/-/cache/#{options.slug or 'latest'}"
+        cache   : no
+        error   : (err)->   callback? err
+        success : (cache)->
+          cache.overview.reverse()  if cache?.overview
+          callback null, cache

   continueLoadingTeasers:->
     # fix me


handle load errors

handle load errors

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -228,13 +228,17 @@
     if @state.loading || @state.paging.loading || !@state.paging.more
       return

-    pagingState = _.extend {}, @state.paging
-    pagingState.loading = true
-
-    @setState paging: pagingState
-
-    @fetchNewState().then (newState) =>
+    @setState
+      paging: _.extend {}, @state.paging, loading: true
+
+    @fetchNewState()
+    .then (newState) =>
       @setState newState
+
+    .catch (error) =>
+      @setState
+        error: error
+        paging: _.extend {}, @state.paging, loading: false

   saveState: =>
@@ -250,11 +254,18 @@
     @setState loading: true
     @backToTop.current.reset()

-    @fetchNewState(true).then (newState) =>
+    @fetchNewState(true)
+    .then (newState) =>
       cutoff = @backToTopAnchor.current.getBoundingClientRect().top
       window.scrollTo window.pageXOffset, window.pageYOffset + cutoff if cutoff < 0

       @setState newState
+
+    .catch (error) =>
+      @setState
+        error: error
+        loading: false
+        paging: {}

   sorting: =>


[fix,cleanup] syntax bugs

[fix,cleanup] syntax bugs

--- src/el-borracho.coffee
+++ src/el-borracho.coffee
@@ -88,7 +88,7 @@
     {queue, state} = req.params

     await @store.jobs queue, state, ideally defer jobs
-    await @store.stateCounts        ideally defer counts
+    await @store.stateCounts queue, ideally defer counts
     res.json {jobs, counts}

   deleteByState: (req, res, next) =>
@@ -116,8 +116,9 @@
     return next new Error "job required" unless job = req.body
     ideally = errify next

-    job = [job] unless Array.isArray job
+    jobs = [job] unless Array.isArray job
     await @bull.createJobInQueue req.param.queue, job, ideally defer() for job in jobs
+    res.json message: "Sucessfully created jobs"

   queues: (req, res, next) =>
     ideally = errify next


no need to parse exif, ha-ha

no need to parse exif, ha-ha

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -24,9 +24,9 @@
     op = ns.readJpegChunks(file)
     op.progress (marker, view) ->
       if marker == 0xe1
-        exifTags = ns.parseExifTags(view)
-        if exifTags?
-          exif = view.buffer
+        if view.byteLength >= 14
+          if view.getUint32(0) == 0x45786966 and view.getUint16(4) == 0
+            exif = view.buffer
     op.always ->
       # start = new Date()
       img = new Image()
@@ -117,70 +117,3 @@
       readNext()

     df.promise()
-
-
-  ns.parseExifTags = (view) ->
-    if view.byteLength < 14
-      return
-    if view.getUint32(0) != 0x45786966 or view.getUint16(4) != 0
-      return
-
-    tiffOffset = 6
-    if view.getUint16(tiffOffset) == 0x4949
-      littleEnd = true
-    else if view.getUint16(tiffOffset) == 0x4d4d
-      littleEnd = false
-    else
-      return
-
-    if view.getUint16(tiffOffset + 2, littleEnd) != 0x002a
-      return
-
-    entryOffset = view.getUint32(tiffOffset + 4, littleEnd)
-    if entryOffset < 8
-      return
-    entryOffset += tiffOffset + 2
-
-    entries = view.getUint16(entryOffset - 2, littleEnd)
-    tags = {}
-
-    for i in [0...entries]
-      tag = view.getUint16(entryOffset, littleEnd)
-      value = readTagValue(view, entryOffset + 2, littleEnd)
-      if value?
-        tags[tag] = value
-      entryOffset += 12
-    tags
-
-
-  readTagValue = (view, entryOffset, littleEnd) ->
-    type = view.getUint16(entryOffset, littleEnd)
-    numValues = view.getUint32(entryOffset += 2, littleEnd)
-    valueOffset = view.getUint32(entryOffset += 4, littleEnd) + 6
-
-    if type == 2  # ascii, 8-bit byte
-      offset = if numValues > 4 then valueOffset else entryOffset
-      buffer = view.buffer.slice(offset, offset + numValues - 1)
-      return String.fromCharCode.apply(null, new Uint8Array(buffer))
-
-    if numValues == 1
-      switch type
-        # byte, 8-bit unsigned int
-        # undefined, 8-bit byte, value depending on field
-        when 1, 7
-          return view.getUint8(entryOffset)
-
-        when 3  # short, 16 bit int
-          return view.getUint16(entryOffset, littleEnd)
-
-        when 4  # long, 32 bit int
-          return view.getUint32(entryOffset, littleEnd)
-
-        when 5  # rational = two long values, first is numerator, second is denominator
-          return view.getUint32(valueOffset, littleEnd) / view.getUint32(valueOffset + 4, littleEnd)
-
-        when 9  # slong, 32 bit signed int
-          return view.getInt32(entryOffset, littleEnd)
-
-        when 10  # signed rational, two slongs, first is numerator, second is denominator
-          return view.getInt32(valueOffset, littleEnd) / view.getInt32(valueOffset + 4, littleEnd)


See #713. Change key from _general to general for settings

--- src/config.coffee
+++ src/config.coffee
@@ -1,8 +1,9 @@
 module.exports = {
-  _general:
+  general:
     title: 'General'
     type: 'object'
     collapsed: true
+    order: -1
     description: 'General options for Atom Beautify'
     properties:
       analytics :


add logging

add logging

--- services/real-time/config/settings.defaults.coffee
+++ services/real-time/config/settings.defaults.coffee
@@ -51,6 +51,7 @@
 	forceDrainMsDelay: process.env['FORCE_DRAIN_MS_DELAY'] or false

 console.log process.env['REDIS_CLUSTER_HOSTS'], process.env['REDIS_CLUSTER_NATMAP']
+console.log typeof(process.env['REDIS_CLUSTER_HOSTS'])
 if process.env['REDIS_CLUSTER_HOSTS']? or process.env['REDIS_CLUSTER_NATMAP']?

 	for redisKey in Object.keys(settings.redis)


commandstream: Expose commands

commandstream: Expose commands

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -648,3 +648,4 @@
   initialGraphMessages: initialGraphMessages
   toCommandStreamBuffer: toCommandStreamBuffer
   fromCommand: fromCommand
+  commands: commands


angular debug info disabled by default

angular debug info disabled by default

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -234,7 +234,7 @@
         linewidth: "The subject must have a maximum size of %s"
     })

-    $compileProvider.debugInfoEnabled(window.taigaConfig.debugInfo)
+    $compileProvider.debugInfoEnabled(window.taigaConfig.debugInfo || false)

 init = ($log, $i18n, $config, $rootscope, $auth, $events, $analytics) ->
     $i18n.initialize($config.get("defaultLanguage"))


Load problems no longer redirects to mimick the behavior of the load deployment option

--- web/coffee/management-shell.coffee
+++ web/coffee/management-shell.coffee
@@ -146,7 +146,8 @@
   pushData: ->
     apiCall "POST", "/api/problems/load_problems", {competition_data: @state.publishedJSON}
     .done ((data) ->
-      apiNotify data, "/management"
+      apiNotify data
+      @clearPublishedJSON()
     ).bind this

   clearPublishedJSON: ->


Added watch task

Added watch task

--- gulp.coffee
+++ gulp.coffee
@@ -33,7 +33,8 @@
 #
 onError = (err) ->
     gutil.beep()
-    console.log err
+    gutil.log   gutil.colors.cyan('Plumber') + gutil.colors.red(' found unhandled error:\n'),
+                err.toString()

 # Build not-minified version, with sourcemaps.
@@ -63,6 +64,12 @@
         .pipe header banner, pkg: config
         .pipe gulp.dest paths.dist

+# Build all versions
+#
+gulp.task 'build', [ 'build:prod', 'build:dev' ]
+gulp.task 'default', [ 'build' ]
+
+
 # Test dist version
 #
 gulp.task 'test:dist', (done) ->
@@ -78,4 +85,12 @@
             configFile: "#{__dirname}/config/karma.min.conf.coffee",
             singleRun: yes
         , done)
+
+# Run all tests for deployed version
+gulp.task 'test', [ 'test:dist', 'test:min' ]
+
+# Watcher
+#
+gulp.task 'watch', ->
+    gulp.watch paths.src, -> gulp.start [ 'build:dev', 'build:prod' ]


refactor debug/basedir to their own functions

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -33,16 +33,10 @@
   # CompileStep has the absolute path to the file in `fullInputPath`
   # CompileStep has the name of the file in `inputPath`
   # basedir is fullInputPath with inputPath replaced with '.npm/package'
-  basedir = step.fullInputPath.slice(0, -(step.inputPath.length)) + '.npm/package'
+  basedir = getBasedir(step)

   # debug is true unless we are doing a bundle or build, excpet with --debug set
-  debug = true
-  # check args used
-  for key in process.argv
-    # if 'meteor bundle file' or 'meteor build file'
-    if key is 'bundle' or key is 'build'
-      debug = '--debug' in process.argv
-      break;
+  debug = getDebug(step)

   # create a browserify instance passing our readable stream as input,
   # and options with debug set to true for a dev build, and the basedir
@@ -78,3 +72,21 @@

 # add our function as the handler for files ending in 'browserify.js'
 Plugin.registerSourceHandler 'browserify.js', processFile
+
+getBasedir = (step) ->
+  # CompileStep has the absolute path to the file in `fullInputPath`
+  # CompileStep has the name of the file in `inputPath`
+  # basedir is fullInputPath with inputPath replaced with '.npm/package'
+  basedir = step.fullInputPath.slice(0, -(step.inputPath.length)) + '.npm/package'
+
+getDebug = (step) ->
+  debug = true
+
+  # check args used
+  for key in process.argv
+    # if 'meteor bundle file' or 'meteor build file'
+    if key is 'bundle' or key is 'build'
+      debug = '--debug' in process.argv
+      break;
+
+  return debug


use basedir option to find local node_modules

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -33,22 +33,37 @@
 #   2. otherwise the root is where the referenced packages are, which can be anything

 # helps getNpmDir by searching even deeper into the InputFile's properties
-getNpmDirForPackage = (isopackCache, name) ->
+getNpmDirForPackage = (isopackCache, name, basedirOption) ->
   pkg = isopackCache._packageMap._map[name]
   if pkg.kind is 'local' then pkg.packageSource.npmCacheDirectory

   else # it's a versioned package... doesn't have a packageSource object.
     builds = isopackCache._isopacks[name].unibuilds
+
     for build in builds when build.arch is 'os' # the 'os' one has nodeModulesPath
+      # if user specified a basedir option
+      if basedirOption?
+        # we need the home directory to find ~/.meteor/packages
+        home = process.env.HOME ? process.env.USERPROFILE
+        meteorPackages = home + '/.meteor/packages/'
+        # package name uses a colon, file system uses an underscore
+        packageName = name.replace ':', '_'
+        # the build info has the package's version
+        version = '/' + build.pkg.version
+        # combine this all into a directory and convert it to OS specific path
+        dir = Plugin.convertToOSPath meteorPackages + packageName + version + '/web.browser/' + basedirOption
+        # lastly, return this directory only if it has `node_modules` in it
+        return dir if fs.existsSync path.join dir, 'node_modules'
+      # if we didn't return above, then, return this:
       return build.nodeModulesPath[...-12]

-getNpmDir = (file) ->
+getNpmDir = (file, basedirOption) ->
   packageName = file.getPackageName()
   # way deep down there is some useful properties to locate the npm directory
   isopackCache = file._resourceSlot.packageSourceBatch.processor.isopackCache

   # package file, get npm dir from the package's properties
-  if packageName? then getNpmDirForPackage isopackCache, packageName
+  if packageName? then getNpmDirForPackage isopackCache, packageName, basedirOption

   # app file, try meteorhacks:npm's npm-container package...
   else if isopackCache._packageMap._map?['npm-container']?
@@ -208,10 +223,12 @@
     if option?.input?
       userOptions = JSON.parse option.input.getContentsAsString()

-    # sane defaults for options; most important is the baseDir
+    # check for `basedir` from options file or package.js file options
+    basedirOption = userOptions.basedir ? option?.package?.basedir
+
+    # sane defaults for options
+    # NOTE: no longer putting basedir right here
     defaultOptions =
-      # Browserify will look here for npm modules
-      basedir: Plugin.convertToOSPath getNpmDir file

       # must be true to produce source map which we extract via exorcist and
       # provide to CompileStep
@@ -224,6 +241,15 @@
           NODE_ENV: if @getDebug() then 'development' else 'production'
           _:'purge'

+    # Browserify will look in `basedir` for npm modules
+    # 1. place the result into `userOptions` so it's last in the hierarchy
+    #    and isn't overridden
+    # 2. check for cached result to avoid the work in rebuilds
+    # 3. include `basedirOption` no matter what, it may be undefined
+    # 4. store the result on the `file` for #2
+    userOptions.basedir = file.__basedir ? Plugin.convertToOSPath getNpmDir file, basedirOption
+    file.__basedir = userOptions.basedir
+
     # merge user options with defaults (option.package is file.getFileOptions())
     _.defaults userOptions, option.package, defaultOptions


:new: Complete lint of editor-linter

:new: Complete lint of editor-linter

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -27,15 +27,39 @@
     scopes = @editor.scopeDescriptorForBufferPosition(@editor.getCursorBufferPosition()).scopes
     scopes.push '*' # To allow global linters

-    Promise.all(@_lint()).then =>
+    Promise.all(@_lint(wasTriggeredOnChange, scopes)).then =>
       @_lock(wasTriggeredOnChange, false)

   # This method returns an array of promises to be used in _lint
-  _lint: ->
+  _lint: (wasTriggeredOnChange, scopes)->
+    return @linter.linters.map (linter)=>
+      return if wasTriggeredOnChange and not linter.lintOnFly
+      return if (not wasTriggeredOnChange) and linter.lintOnFly
+      return unless (scopes.filter (entry) -> linter.scopes.indexOf(entry) isnt -1 ).length
+
+      new Promise((resolve)=>
+        resolve(linter.lint(@editor))
+      ).then(EditorLinter.validateResults).catch((error)->
+        atom.notifications.addError error, {detail: error.stack, dismissible: true}
+        []
+      ).then =>
+        if linter.scope is 'project' then @linter.messagesProject.set linter, results
+        else @messages.set linter, results
+
+        @emitter.emit 'did-update'
+        @linter.view.render() if @editor is @linter.activeEditor

   # This method sets or gets the lock status of given type
   _lock: (wasTriggeredOnChange, value)->
     key = wasTriggeredOnChange ? 'inProgressFly' : 'inProgress'
     return @[key] if typeof value is 'undefined'
     @[key] = value
+
+  @validateResults: (results)->
+    if (not results) or results.constructor.name isnt 'Array'
+      throw new Error "Got invalid response from Linter, Type: #{typeof results}"
+    results.forEach (Result)->
+      unless Result.type
+        throw new Error "Missing type field on Linter Response, Got: #{Object.keys(results)}"
+    results
 module.exports = EditorLinter


code typo

code typo

--- trust.coffee
+++ trust.coffee
@@ -31,7 +31,7 @@

 fingerprint_for_cert_path = (path, callback) ->
-  exec "/usr/bin/openssl x509 -noout -in '#{path}' -fingerprint", args, (e, out, err) ->
+  exec "/usr/bin/openssl x509 -noout -in '#{path}' -fingerprint", (e, out, err) ->
     return callback e if e
     m = out.match /SHA1 Fingerprint=([A-F0-9:]+)/
     if not m


Client: fix bug where topic realtime updates were appended, not prepended

--- client/Social/Activity/views/topicmessagepane.coffee
+++ client/Social/Activity/views/topicmessagepane.coffee
@@ -5,11 +5,11 @@
     super options, data

     KD.singletons.socialapi
-      .on 'MessageAdded',   @bound 'prependMessage'
+      .on 'MessageAdded',   @bound 'addMessage'
       .on 'MessageRemoved', @bound 'removeMessage'

-  prependMessage: (message) ->
+  addMessage: (message) ->

     {name} = @getData()
     return  unless message.body.match ///##{name}///


Age module

Age module

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -158,7 +158,7 @@
       I.active = false
   }

-  defaultModules = [ABindable, Bounded, Clampable, Cooldown, Drawable, Expirable, Follow, Metered, Movable, Rotatable, TimedEvents, Tween]
+  defaultModules = [Ageable, Bindable, Bounded, Clampable, Cooldown, Drawable, Expirable, Follow, Metered, Movable, Rotatable, TimedEvents, Tween]
   modules = defaultModules.concat(I.includedModules.invoke('constantize'))
   modules = modules.without(I.excludedModules.invoke('constantize'))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -161,7 +161,7 @@
       I.active = false
   }

-  defaultModules = [Bindable, Bounded, Clampable, Cooldown, Drawable, Durable, Metered, TimedEvents, Tween]
+  defaultModules = [Bindable, Bounded, Clampable, Cooldown, Drawable, Durable, Follow, Metered, TimedEvents, Tween]
   modules = defaultModules.concat(I.includedModules.invoke('constantize'))
   modules = modules.without(I.excludedModules.invoke('constantize'))


Stack credential: do not show __rawContent field on edit custom vars form

--- client/app/lib/providers/computecontroller.ui.coffee
+++ client/app/lib/providers/computecontroller.ui.coffee
@@ -51,6 +51,7 @@
       credentialFields = {}

       for field in requiredFields
+        continue  if field is '__rawContent'
         name = field.name ? field
         type = field.type ? 'text'
         { values } = field


Only post button for general

Only post button for general

--- resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
@@ -75,7 +75,14 @@

           div
             className: "#{bn}__footer-content #{bn}__footer-content--right"
-            ['praise', 'suggestion', 'problem'].map @messageTypeSelection
+            if @props.mode == 'timeline'
+              ['praise', 'suggestion', 'problem'].map @submitButton
+            else
+              button
+                className: 'btn-osu-big btn-osu-big--beatmap-discussion'
+                disabled: !@validPost()
+                onClick: @post
+                osu.trans('common.buttons.post')

   setTimestamp: (e) ->
@@ -123,7 +130,7 @@
     .always LoadingOverlay.hide

-  messageTypeSelection: (type) ->
+  submitButton: (type) ->
     button
       key: type
       className: 'btn-osu-big btn-osu-big--beatmap-discussion'


Stabilize up.motion spec in IE11

Stabilize up.motion spec in IE11

--- spec_app/spec/javascripts/up/motion_spec.js.coffee
+++ spec_app/spec/javascripts/up/motion_spec.js.coffee
@@ -427,7 +427,8 @@
         $child = $('

').css(margin: '40px').appendTo($element)
         up.motion.prependCopy($element)
         $clonedChild = $('.up-ghost .child')
-        expect($clonedChild.offset()).toEqual($child.offset())
+        expect($clonedChild.offset().top).toBeAround($child.offset().top, 0.5)
+        expect($clonedChild.offset().left).toBeAround($child.offset().left, 0.5)

       it 'correctly positions the ghost over an element within a scrolled body', ->
         $body = $('body')