Bind preview action to turbolinks:load

Bind preview action to turbolinks:load

Should fix #117

--- app/assets/javascripts/flag_conditions.coffee
+++ app/assets/javascripts/flag_conditions.coffee
@@ -2,7 +2,7 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-$ ->
+$(document).on 'turbolinks:load', ->
   $("input#preview-flag-conditions-button").on 'click', ->
     $.ajax
       url: "/flagging/conditions/preview",


Add clarifying comments to model.

Add clarifying comments to model.

--- app/assets/javascripts/models/paper.js.coffee
+++ app/assets/javascripts/models/paper.js.coffee
@@ -1,8 +1,8 @@
 a = DS.attr
 ETahi.Paper = DS.Model.extend
-  assignees: DS.hasMany('user')
-  editors: DS.hasMany('user')
-  reviewers: DS.hasMany('user')
+  assignees: DS.hasMany('user') # these are admins that have been assigned to the paper.
+  editors: DS.hasMany('user') # these are editors that have been assigned to the paper.
+  reviewers: DS.hasMany('user') # these are reviewers that have been assigned to the paper.
   editor: Ember.computed.alias('editors.firstObject')
   collaborations: DS.hasMany('collaboration')


Moved employee search js to global assets

--- app/assets/javascripts/users_search.js.coffee
+++ app/assets/javascripts/users_search.js.coffee
@@ -1,43 +1,4 @@
 $ ->
-  # Autocomplete on employee search
-  $queryEmployee = $("#query-employee")
-  if $queryEmployee.length
-    items = 0
-    $queryEmployee.autocomplete
-      source: (request, response) ->
-        $.ajax
-          url: $queryEmployee.attr("data-autocomplete-url").replace("http:", location.protocol)
-          data:
-            term: request.term.toLowerCase()
-          dataType: "jsonp"
-          timeout: 5000
-          success: (data) ->
-            if data.length
-              items = data.length
-              response $.map data, (item) ->
-                item
-      minLength: 2
-      select: (event, ui) ->
-        if ui.item.path is "full-search"
-          $queryEmployee.closest("form").submit()
-        else
-          document.location = ui.item.path
-    .data("ui-autocomplete")._renderItem = (ul, item) ->
-      # Create item for full search if it is the last item
-      $more = ""
-      if items is ul.find("li").length + 1
-        $more = $("
Visa fler resultat
")
-          .data("ui-autocomplete-item", { path: "full-search"})
-      ul.addClass('search_users')
-      $("
")
-        .data("ui-autocomplete-item", item)
-        .append("
-
#{item.displayname}

-            #{item.company_short}

-            #{item.department}

-        ")
-        .appendTo(ul).append($more)
-
   # Search results, load more
   $("section.index.users").on "click", ".load-more input", (event) ->
     event.preventDefault()


Add grey ANSI code

Add grey ANSI code

--- src/gral/ansiColors.coffee
+++ src/gral/ansiColors.coffee
@@ -9,6 +9,7 @@
   35: 'color: magenta'
   36: 'color: cyan'
   37: 'color: lightgrey'
+  90: 'color: grey'
   40: 'color: white;background-color: black'
   41: 'color: white;background-color: red'
   42: 'color: white;background-color: green'
@@ -33,6 +34,7 @@
   35: {color: 'magenta'}
   36: {color: 'cyan'}
   37: {color: 'lightgrey'}
+  90: {color: 'grey'}
   40: {color: 'white', backgroundColor: 'black'}
   41: {color: 'white', backgroundColor: 'red'}
   42: {color: 'white', backgroundColor: 'green'}


Add an error notification on missing main file

--- lib/builder.coffee
+++ lib/builder.coffee
@@ -9,9 +9,14 @@
     @latex = latex

   build: ->
-    if !@binCheck()
-      return false
     if !@latex.manager.findMain()
+      atom.notifications.addError(
+        """Cannot find a LaTeX file with the following command to start.""", {
+          detail: '`\\begin{document}`'
+          dismissable: true
+          message: 'Please make sure your LaTeX main file with the above \
+                    command is in the root folder of the opened Atom project.'
+        })
       return false

     @killProcess()
@@ -68,16 +73,9 @@
     @process?.kill()

   binCheck: (binary) ->
-    if binary
-      if hb.sync binary
-        return true
-      return false
-
-    if !hb.sync 'pdflatex'
-      return false
-    if !hb.sync 'bibtex'
-      return false
-    return true
+    if hb.sync binary
+      return true
+    return false

   setCmds: ->
     if atom.config.get('atom-latex.toolchain') == 'auto'


fix indentation of comment

fix indentation of comment

--- tapestry-core/src/main/coffeescript/META-INF/modules/core/pageinit.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/core/pageinit.coffee
@@ -102,8 +102,8 @@
         tracker()

     exports =
-    # Passed a list of initializers, executes each initializer in order. Due to asynchronous loading
-    # of modules, the exact order in which initializer functions are invoked is not predictable.
+      # Passed a list of initializers, executes each initializer in order. Due to asynchronous loading
+      # of modules, the exact order in which initializer functions are invoked is not predictable.
       initialize: (inits = [], callback) ->
         callbackCountdown = inits.length + 1


FIX: Prevent auth timeout cycle for unauthorised state

--- src/app/common/services/redirect-service.coffee
+++ src/app/common/services/redirect-service.coffee
@@ -20,7 +20,7 @@
   redirectService.redirect = (defaultState, defaultParams) ->
     destState = defaultState
     destParams = defaultParams
-    if $stateParams["dest"] && $stateParams["dest"] isnt 'not_found'
+    if $stateParams["dest"] && !_.includes(['not_found', 'unauthorised'], $stateParams["dest"])
       destState = $stateParams["dest"]
       destParams = deserialize $stateParams["params"] if $stateParams["params"]?
     $state.go destState, destParams


Fixes #101: Use `parseFloat` for distance

--- src/infinite-scroll.coffee
+++ src/infinite-scroll.coffee
@@ -110,7 +110,7 @@
     # more when the bottom of the page is less than 3 container heights away,
     # specify a value of 3. Defaults to 0.
     handleInfiniteScrollDistance = (v) ->
-      scrollDistance = parseInt(v, 10) or 0
+      scrollDistance = parseFloat(v) or 0

     scope.$watch 'infiniteScrollDistance', handleInfiniteScrollDistance
     # If I don't explicitly call the handler here, tests fail. Don't know why yet.


bug-fix: identifier expression of 'augment' in 'uses' (#56)

--- src/lang/extensions.coffee
+++ src/lang/extensions.coffee
@@ -89,7 +89,7 @@
             throw @error "'#{@tag}' must be absolute-schema-path to augment within module statement"
           @locate @tag
         when 'uses'
-          if /^\//.test @tag
+          unless /^[_0-9a-zA-Z]/.test @tag
             throw @error "'#{@tag}' must be relative-schema-path to augment within uses statement"
           @parent.state.grouping.locate @tag
       unless target?


Being a little more friendly to demeter.

--- lib/inspect.coffee
+++ lib/inspect.coffee
@@ -23,7 +23,8 @@

 # Finds type name within *Element* node of *property* class.
 findPropertyName = (propNode, variable = true) ->
-  propNode.content.name.literal or (propNode.content.name.variable?.values?[0].literal if variable)
+  nameNode = propNode.content.name
+  nameNode.literal or (nameNode.variable?.values?[0].literal if variable)

 # Lists all defined values.


Remove SpacePen from lines-component

Remove SpacePen from lines-component

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -1,10 +1,13 @@
-{$$} = require 'space-pen'
-
 CursorsComponent = require './cursors-component'
 LinesTileComponent = require './lines-tile-component'
 TiledComponent = require './tiled-component'

-DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
+DummyLineNode = document.createElement('div')
+DummyLineNode.className = 'line'
+DummyLineNode.style.position = 'absolute'
+DummyLineNode.style.visibility = 'hidden'
+DummyLineNode.appendChild(document.createElement('span'))
+DummyLineNode.firstChild.textContent = 'x'

 module.exports =
 class LinesComponent extends TiledComponent


Remove analyze pane if regular query is run

--- lib/pg-hoff.coffee
+++ lib/pg-hoff.coffee
@@ -336,6 +336,9 @@
         index = @hoffPanes.indexOf(pane);
         if index >= 0
           @hoffPanes.splice( index, 1 );
+         @bottomDock.deletePane pane.getId()
+         pane = null
+

     executeQueryWithConnect: ->
         alias = @getAliasForPane()
@@ -440,7 +443,9 @@
                             if queryCount == 1 and result.columns?.length == 1 and result.columns?[0]['name'] == 'QUERY PLAN' and result.query.substring(0, 7) == 'EXPLAIN'
                                 @renderQueryPlan(result.rows)
                             else
-                                console.log 'aksjdljaslk'
+                                if @analyzePane in @hoffPanes
+                                    @removeHoffPane(@analyzePane)
+
                                 @renderResults(result)

                             first = false
@@ -482,7 +487,7 @@
                 div.innerHTML = response;
                 explain = div.querySelector(".result-html");

-                unless @analyzePane
+                unless @analyzePane in @hoffPanes
                     @analyzePane = new AnalyzePaneView()
                     @hoffPanes.push @analyzePane
                     @bottomDock.addPane @analyzePane, 'Analyze', true


runtime: Support getting main graph JSON using component:getsource

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -129,14 +129,16 @@
             "protocol:network"
             "protocol:component"
             "protocol:runtime"
+            "component:getsource"
         ]
         r =
             type: "microflo"
             version: "0.4"
             capabilities: caps
+            namespace: runtime.namespace

         if runtime.graph?.name
-          r.graph = runtime.graph.name
+          r.graph = runtime.namespace + '/' + runtime.graph.name

         runtime.device.open () ->
           connection.send
@@ -157,16 +159,34 @@
     if command is "list"
         listComponents runtime, connection
     else if command is "getsource"
-        runtime.library.getComponentSource payload.name, (err, source) ->
-            r =
-                name: payload.name
-                language: "c++"
-                code: source
-            connection.send
-                protocol: "component"
-                command: "source"
-                payload: r
-            return
+        # Main graph, used in live mode
+        if payload.name == runtime.namespace + '/' + runtime.graph.name
+            graph =
+              properties:
+                name: runtime.graph.name
+                environment:
+                  type: 'microflo'
+              processes: runtime.graph.processes
+              connections: runtime.graph.connections
+              inports: []
+              outports: []
+            resp =
+              code: JSON.stringify graph
+              name: runtime.graph.name
+              library: runtime.namespace
+              language: 'json'
+            connection.send { protocol: 'component', command: 'source', payload: resp }
+        else
+            runtime.library.getComponentSource payload.name, (err, source) ->
+                r =
+                    name: payload.name
+                    language: "c++"
+                    code: source
+                connection.send
+                    protocol: "component"
+                    command: "source"
+                    payload: r
+                return
     else
         console.log "Unknown NoFlo UI command on 'component' protocol:", command, payload
     return
@@ -588,6 +608,7 @@
         @collector = new BracketDataCollector()
         @exportedEdges = []
         @edgesForInspection = []
+        @namespace = 'default'

         # Needed because the runtime on microcontroller only has numerical identifiers
         @graph.nodeMap = {} # "nodeName" -> { id: numericNodeId }


Show fault if invalid vparam return value

--- lib/session.coffee
+++ lib/session.coffee
@@ -397,7 +397,7 @@
   for provision, j in provisions
     counter += 2
     globals = {args: provision.slice(1)}
-    do (j) -> sandbox.run(allVirtualParameters[provision[0]].script, globals,
+    do (provision, j) -> sandbox.run(allVirtualParameters[provision[0]].script, globals,
       sessionContext, startRevision, endRevision,
       (err, _fault, _clear, _declarations, _done, _returnValue) ->
         if err or _fault
@@ -407,7 +407,29 @@
         done &&= _done
         allDeclarations[j] = _declarations or []
         allClear[j] = _clear or []
-        virtualParameterUpdates[j] = JSON.parse(JSON.stringify(_returnValue)) if _done
+        if _done
+          if not _returnValue
+            if counter & 1
+              callback(null, {code: 'script', message: 'Invalid virtual parameter return value'})
+            return counter = 0
+
+          ret = {}
+
+          if _returnValue.writable?
+            ret.writable = +!!_returnValue.writable
+          else if provision[1].writable? or provision[2].writable?
+            if counter & 1
+              callback(null, {code: 'script', message: 'Virtual parameter must provide declared attributes'})
+            return counter = 0
+
+          if _returnValue.value?
+            ret.value = device.sanitizeParameterValue(_returnValue.value)
+          else if provision[1].value? or provision[2].value?
+            if counter & 1
+              callback(null, {code: 'script', message: 'Virtual parameter must provide declared attributes'})
+            return counter = 0
+
+          virtualParameterUpdates[j] = ret

         if (counter -= 2) == 1
           allDeclarations = Array.prototype.concat.apply([], allDeclarations)
@@ -787,9 +809,11 @@
   if not vparams
     return callback()

+  timestamp = sessionContext.timestamp + sessionContext.iteration
   toClear = null
   for vpu, i in ret
-    toClear = commitVirtualParameter(sessionContext, vparams[i], vpu, toClear)
+    vpu[k] = [timestamp + (if vparams[i][2][k]? then 1 else 0), v] for k, v of vpu
+    toClear = device.set(sessionContext.deviceData, ['VirtualParameters', vparams[i][0]], timestamp, vpu, toClear)

   clear(sessionContext, toClear, (err) ->
     return rpcRequest(sessionContext, null, callback)
@@ -1290,25 +1314,6 @@
   )

-commitVirtualParameter = (sessionContext, provision, update, toClear) ->
-  attributes = {}
-
-  timestamp = sessionContext.timestamp + sessionContext.iteration
-  timestamp += 1 if provision[2]?
-
-  if update.writable?
-    attributes.writable = [timestamp, +update.writable]
-  else if provision[1]?.writable? or provision[2]?.writable?
-    throw new Error('Virtual parameter must provide declared attributes')
-
-  if update.value?
-    attributes.value = [timestamp, device.sanitizeParameterValue(update.value)]
-  else if provision[1]?.value? or provision[2]?.value?
-    throw new Error('Virtual parameter must provide declared attributes')
-
-  return device.set(sessionContext.deviceData, ['VirtualParameters', provision[0]], timestamp, attributes, toClear)
-
-
 rpcResponse = (sessionContext, id, rpcRes, callback) ->
   if id != generateRpcId(sessionContext)
     return callback(new Error('Request ID not recognized'))


fix: one more return

fix: one more return

--- src/pagerduty.coffee
+++ src/pagerduty.coffee
@@ -84,7 +84,7 @@
             messages = messages.concat(message)
             if processedConfig['NOTIFICATIONS']
               debug('Sending notifications.')
-              sendNotification processedConfig['NOTIFICATIONS'], message, cb
+              return sendNotification processedConfig['NOTIFICATIONS'], message, cb
           return cb()
       else
         return cb new Error "No schedule to process."


Fixes

Fixes

--- src/privilege.coffee
+++ src/privilege.coffee
@@ -15,9 +15,10 @@
 isIgnored = (username) ->
   table = robot.brain.get(PRIVILEGE_TABLE_KEY) || {}
   ignored = false
-  if username? and user?.id?
+  if username?
     user = robot.brain.userForName username
-    ignored = table[user.id]
+    if user
+      ignored = table[user.id]
   ignored

 module.exports = (robot) ->


Fix broken sorting in Gradebook (SIS ID column)

Register the SIS ID column with the new comprehensive locale-aware
sorting mechanism, so that it doesn't get treated as a regular score
column (which doesn't sort properly because there are no scores).

--- app/coffeescripts/gradebook2/Gradebook.coffee
+++ app/coffeescripts/gradebook2/Gradebook.coffee
@@ -967,11 +967,13 @@
       $('body').on('click', @onGridBlur)

       @grid.onSort.subscribe (event, data) =>
-        if data.sortCol.field == "display_name" || data.sortCol.field == "secondary_identifier"
+        # SFU MOD CANVAS-188 Make SIS ID column sortable
+        if data.sortCol.field == "display_name" || data.sortCol.field == "secondary_identifier" || data.sortCol.field == "sis_id"
+        # END SFU MOD
           sortProp = if data.sortCol.field == "display_name"
             "sortable_name"
           else
-            "secondary_identifier"
+            data.sortCol.field # SFU MOD - CANVAS-188 Make SIS ID column sortable
           @sortRowsBy (a, b) =>
             [b, a] = [a, b] if not data.sortAsc
             @localeSort(a[sortProp], b[sortProp])


removed console logs

removed console logs

--- app/controllers/currentGame_controller.coffee
+++ app/controllers/currentGame_controller.coffee
@@ -18,13 +18,11 @@
       if authPerson.get('id') == homePerson.get('id') or authPerson.get('id') == awayPerson.get('id')
         @set('isMe', true)
     if !rounds?
-      @transitionTo("/games")
+      if(!@get('authPerson').get('id') == "1410921259")
+        @transitionTo("/games")
       return
     currentRound = game.get('currentRound')
     @get('rounds').map((round, index) =>
-      console.log "re calc rounds..."
-      console.log index + 1
-      console.log currentRound
       round:
         homeWon: round.homeScore > round.awayScore
         awayWon: round.homeScore < round.awayScore
@@ -63,7 +61,7 @@
     tweetCompleted.save()

     game.delete()
-    @transitionTo('/')
+    @transitionTo('/games')
   openRound: (round) ->
     @set('confirmOpenRound', false)
     game = @get('model')
@@ -340,7 +338,7 @@
       @set('cancelGameConfirm', false)
       game = @get('model')
       game.delete()
-      @transitionTo("/")
+      @transitionTo("/games")
     cancelGameConfirm: ->
       @set('cancelGameConfirm', true)
     undoCancelGameConfirm: ->


Stop showing 'undefined' in the new explorer

--- gae/scripts/view.coffee
+++ gae/scripts/view.coffee
@@ -822,8 +822,8 @@
             # FIXME: Need to figure out how to bind callRow and then delete _rowForCall.
             controller.BidInterpreter.interpretLastCallInCallsFromBoard calls, board, (calls, interpretation) =>
                 row = @_rowForCall(calls[calls.length - 1])
-                row.cells[1].textContent = interpretation.ruleName
-                row.cells[2].textContent = interpretation.rulePriority
+                row.cells[1].textContent = if interpretation.ruleName then interpretation.ruleName else ""
+                row.cells[2].textContent = if interpretation.rulePriority then interpretation.ruleName else ""
                 row.cells[3].textContent = ""
                 row.cells[3].appendChild StrainView.fragmentReplacingStrainChars(interpretation.constraintsString)


commit

commit

--- app/modules/casting/casting.controller.coffee
+++ app/modules/casting/casting.controller.coffee
@@ -23,9 +23,10 @@

         taiga.defineImmutableProperty(@, "agents", () => @currentUserService.agents.get("all"))

-        currentUser = @currentUserService.getUser().toJS()
-        console.log("current user is")
-        console.log(currentUser)
+        if @auth.getUser()
+            currentUser = @auth.getUser().toJS()
+            console.log("current user is")
+            console.log(currentUser)

         b_scope = @scope
         b_castingService = @castingService
@@ -37,11 +38,10 @@
         .then (response) ->
             b_scope.memberships_agent = response.toJS()
         .then (response) ->
-            if currentUser.is_agent
+            if currentUser and currentUser.is_agent
                 return b_castingService.getMembersListForAgent(currentUser.id)
             else
                 return b_castingService.getCastingMembers()
-
         .then (response) ->
             b_scope.castingMembers = response.toJS()


robot.brain.users() isn't an Array!

robot.brain.users() isn't an Array!

--- src/chalkcircle.coffee
+++ src/chalkcircle.coffee
@@ -20,7 +20,9 @@

     # Access the full user list.
     @allChallengers = =>
-      _.map robot.brain.users, (u) =>
+      ids = Object.keys(robot.brain.users())
+      _.map ids, (uid) =>
+        u = robot.brain.userForId uid
         @getChallenger(u)

     # Load the Move set.


Fix typo

Fix typo

--- project_template/app/config/template_helpers.coffee
+++ project_template/app/config/template_helpers.coffee
@@ -5,7 +5,7 @@
 # These helpers will be exposed in templates under the 'h' namespace.
 #
 # E.g a 'pluralize' helper could be called from a template as
-# h.pluaralize 'something'
+# h.pluralize 'something'
 class AppTemplateHelpers

 Maji.TemplateHelpers.register(new AppTemplateHelpers())


Cleanup: minor

Cleanup: minor

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -60,7 +60,7 @@

     $(window).bind @keyEventsToBeListened.join(' '), @bound "key"

-    $(window).bind "resize",(event)=>
+    $(window).bind "resize", (event)=>
       @setWindowProperties event
       @notifyWindowResizeListeners event

@@ -93,11 +93,10 @@
     layers = @layers

     addListener 'mousedown', (e)=>
-      # $('.twipsy').remove() # temporary for beta
+
       lastLayer = layers.last

       if lastLayer and $(e.target).closest(lastLayer?.$()).length is 0
-        # log lastLayer, "ReceivedClickElsewhere"
         lastLayer.emit 'ReceivedClickElsewhere', e
         @removeLayer lastLayer


added test cases for steamId2ToSteamId3 & steamId3ToSteamId2

--- test/player/steamid-conversion.coffee
+++ test/player/steamid-conversion.coffee
@@ -28,3 +28,22 @@
       expect( numericIdToSteamId('76561198072083907' ) ).to.eq 'STEAM_0:1:55909089'

+  describe '::steamId2ToSteamId3 and ::steamId3ToSteamId2', ->
+
+    it 'should convert correctly', ->
+      # dy/dx
+      expect( Player.steamId2ToSteamId3('STEAM_0:0:17990207') ).to.eq '[U:1:35980414]'
+      expect( Player.steamId3ToSteamId2('[U:1:35980414]'    ) ).to.eq 'STEAM_0:0:17990207'
+
+      # gaben
+      expect( Player.steamId2ToSteamId3('STEAM_0:0:11101') ).to.eq '[U:1:22202]'
+      expect( Player.steamId3ToSteamId2('[U:1:22202]'    ) ).to.eq 'STEAM_0:0:11101'
+
+      expect( Player.steamId2ToSteamId3('STEAM_0:0:60516095') ).to.eq '[U:1:121032190]'
+      expect( Player.steamId3ToSteamId2('[U:1:121032190]'   ) ).to.eq 'STEAM_0:0:60516095'
+
+      expect( Player.steamId2ToSteamId3('STEAM_0:1:55909089') ).to.eq '[U:1:111818179]'
+      expect( Player.steamId3ToSteamId2('[U:1:111818179]'   ) ).to.eq 'STEAM_0:1:55909089'
+
+      expect( Player.steamId2ToSteamId3('STEAM_0:1:99668934') ).to.eq '[U:1:199337869]'
+      expect( Player.steamId3ToSteamId2('[U:1:199337869]'   ) ).to.eq 'STEAM_0:1:99668934'


email: update email freq title

email: update email freq title

--- client/Account/views/emailnotifications.coffee
+++ client/Account/views/emailnotifications.coffee
@@ -9,9 +9,9 @@
       global           :
         title          : 'Send me email notifications'
       daily            :
-        title          : 'Send me a daily digest'
+        title          : 'Send me a daily digest of activity on my posts'
       privateMessage   :
-        title          : 'When someone sends me a chat message'
+        title          : 'When someone sends me a private message'
       comment          :
         title          : 'When my post gets a new comment'
       likeActivities   :
@@ -19,7 +19,7 @@
       mention          :
         title          : 'When someone mentions me'
       marketing        :
-        title          : 'Receive feature updates and special offers'
+        title          : 'When Koding has member updates (like privacy updates, inactive account notices, new offers and campaigns)'

     globalValue = frequency.global


AppDetails: Remove screenshot logic

AppDetails: Remove screenshot logic

--- client/Social/Apps/Views/appdetailsview.coffee
+++ client/Social/Apps/Views/appdetailsview.coffee
@@ -166,15 +166,12 @@
             modal.destroy()
             callback? err

+
   viewAppended: JView::viewAppended

+
   pistachio:->

-    if @app.manifest.screenshots?.length
-      screenshots = """
-        Screenshots
-
{{> @slideShow}}

-      """
     desc = @getData().manifest?.description or ""

     """


Teamwork: Dashboard button is appended to new place.

--- client/Teamwork/Views/teamworkworkspace.coffee
+++ client/Teamwork/Views/teamworkworkspace.coffee
@@ -11,7 +11,7 @@
       @createButtons panel
       @createRunButton panel  if playground
       @getActivePanel().header.setClass "teamwork"
-      panel.headerButtonsContainer.addSubView new KDCustomHTMLView
+      @buttonsContainer.addSubView new KDCustomHTMLView
         cssClass : "tw-db-icon"
         click    : => @getDelegate().dashboard.show()


Check the dir exists before try to compile, if not exists just refresh the apps.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -154,11 +154,6 @@
     appPath = getAppPath manifest
     indexJsPath = "#{appPath}/index.js"
     @kiteController.run "cat #{escapeFilePath indexJsPath}", (err, response)=>
-      if err
-        new KDNotificationView
-          title    : "App list is out-dated, refreshing apps..."
-          duration : 2000
-        @refreshApps noop
       callback err, response

   # #
@@ -471,7 +466,14 @@
     unless @constructor.manifests[name]
       @fetchApps (err, apps)=> kallback apps[name]
     else
-      kallback @constructor.manifests[name]
+      @kiteController.run "stat #{getAppPath @constructor.manifests[name]}", (err)=>
+        if err
+          new KDNotificationView
+            title    : "App list is out-dated, refreshing apps..."
+            duration : 2000
+          @refreshApps noop
+        else
+          kallback @constructor.manifests[name]

   installApp:(app, version='latest', callback)->


Client: Sidebar: Deleted unnecessary onStackRendered prop

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -90,9 +90,7 @@
           previewCount={PREVIEW_COUNT}
           selectedId={@state.selectedThreadId}
           stack={stack}
-          machines={stack.get 'machines'}
-          onStackRendered={@bound 'onStackRendered'}
-          />
+          machines={stack.get 'machines'}/>

     return stackSections


Environment: Prevent event duplication.

Environment: Prevent event duplication.

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -10,7 +10,9 @@
 environmentDataProvider = require 'app/userenvironmentdataprovider'

-_machineEventsCache = {}
+_eventsCache =
+  machine    : {}
+  stack      : no

 _bindMachineEvents = (environmentData) ->
@@ -21,9 +23,9 @@

   machines.map (machine, id) ->

-    return  if _machineEventsCache[id]
-
-    _machineEventsCache[id] = yes
+    return  if _eventsCache.machine[id]
+
+    _eventsCache.machine[id] = yes

     computeController.on "public-#{id}", (event) ->

@@ -41,6 +43,10 @@

 _bindStackEvents = ->

+  return  if _eventsCache.stack is yes
+
+  _eventsCache.stack = yes
+
   { reactor, computeController } = kd.singletons

   computeController.on 'StackRevisionChecked', (stack) ->
@@ -49,7 +55,6 @@

     loadMachines().then ->
       reactor.dispatch actions.STACK_UPDATED, stack
-

 loadMachines = do (isPayloadUsed = no) ->->


video: fix initial activate behavior

video: fix initial activate behavior

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -115,11 +115,11 @@

     if videoActive
       @setActive()
-      @changeActiveParticipant getNick()
-
-      return  unless @isMySession()
-
-      @requestVideoPublishForHost => @changeActiveParticipant getNick()
+
+      if @isMySession()
+        @requestVideoPublishForHost => @changeActiveParticipant getNick()
+      else
+        @changeActiveParticipant getNick()

   ###*


work from home

work from home

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -9,7 +9,6 @@
     @on 'AlreadyHere', ->
       new KDNotificationView title: "You're already here!"

-    # @utils.defer =>
     unless @userRoute
       @handleRoute defaultRoute,
         shouldPushState: yes
@@ -105,6 +104,7 @@
         slug = stripTemplate route, konstructor
         selector[usedAsPath] = slug
         konstructor?.one selector, (err, model)=>
+          error err  if err?
           @openContent name, section, model, route
       else
         @handleNotFound route
@@ -140,7 +140,7 @@
       (sec)=> @createContentDisplayHandler sec
     )

-    nouns = createLinks(
+    section = createLinks(
       'Account Activity Apps Groups Members StartTab Topics'
       (sec)-> ({params:{name}, query})-> @go sec, name, query
     )
@@ -157,22 +157,22 @@
       '/:name?/Join'      : ({params:{name}})-> mainController.doJoin name
       '/:name?/Recover'   : ({params:{name}})-> mainController.doRecover name

-      # nouns
-      '/:name?/Groups'                  : nouns.Groups
-      '/:name?/Activity'                : nouns.Activity
-      '/:name?/Members'                 : nouns.Members
-      '/:name?/Topics'                  : nouns.Topics
-      '/:name?/Develop'                 : nouns.StartTab
-      '/:name?/Apps'                    : nouns.Apps
-      '/:name?/Account'                 : nouns.Account
-
-      # content displays:
+      # section
+      '/:name?/Groups'                  : section.Groups
+      '/:name?/Activity'                : section.Activity
+      '/:name?/Members'                 : section.Members
+      '/:name?/Topics'                  : section.Topics
+      '/:name?/Develop'                 : section.StartTab
+      '/:name?/Apps'                    : section.Apps
+      '/:name?/Account'                 : section.Account
+
+      # content
       '/:name?/Topics/:topicSlug'       : content.Topics
       '/:name?/Activity/:activitySlug'  : content.Activity
       '/:name?/Apps/:appSlug'           : content.Apps

       '/:name?/Recover/:recoveryToken': ({params:{recoveryToken}})->
-        return if recoveryToken is 'Password'
+        return  if recoveryToken is 'Password'
         mainController.appReady =>
           # TODO: DRY this one
           $('body').addClass 'login'


Update tests to fix failing invitations spec

--- lineman/spec-e2e/invitations_spec.coffee
+++ lineman/spec-e2e/invitations_spec.coffee
@@ -61,7 +61,7 @@
     describe 'by email address', ->
       beforeEach ->
         invitationsHelper.openInvitationsModal()
-        invitationsHelper.invite('max@loomio.org')
+        invitationsHelper.invite('max@example.com')
         invitationsHelper.submitInvitationsForm()

       it 'successfully invites the user', ->
@@ -104,7 +104,7 @@
     beforeEach ->
       invitationsHelper.load()

-    it 'successfully invites a contact', ->
+    it 'successfully adds users and invites non-users', ->
       invitationsHelper.openInvitationsModal()
       invitationsHelper.invite('ming')
       invitationsHelper.invite('keanu')


gulp: pass version when building all sites

--- client/landing/gulptasks/task.build.all.coffee
+++ client/landing/gulptasks/task.build.all.coffee
@@ -2,12 +2,11 @@
 gulp    = require 'gulp'
 shell   = require 'gulp-shell'

-
-module.exports = ->
+module.exports = (version) ->

   folders  = (folder for folder in fs.readdirSync('./') when fs.statSync(folder).isDirectory())
   sites    = folders.filter (folder) -> folder.search(/^site\./) is 0 and folder isnt 'site.boilerplate'
-  commands = ("gulp --gulpfile ./#{siteDir}/gulpfile.coffee build --uglify" for siteDir in sites)
+  commands = ("gulp --gulpfile ./#{siteDir}/gulpfile.coffee build --uglify --koding-version=#{version}" for siteDir in sites)

   gulp.src ''
-    .pipe shell commands+    .pipe shell commands


MainController: delete empty lines

MainController: delete empty lines

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -60,8 +60,6 @@
       @emit 'AppIsReady'

       console.timeEnd "Koding.com loaded"
-
-

   accountChanged:(account, firstLoad = no)->
     @userAccount             = account


Fix midnight battle

Fix midnight battle

--- index.cjsx
+++ index.cjsx
@@ -145,8 +145,8 @@

         when '/kcsapi/api_req_battle_midnight/battle'
           flag = true
-          nowFriendHp = afterFriendHp
-          nowEnemyHp = afterEnemyHp
+          nowFriendHp = Object.clone afterFriendHp
+          nowEnemyHp = Object.clone afterEnemyHp
           if body.api_hougeki?
             hougeki = body.api_hougeki
             for damageFrom, i in hougeki.api_at_list


Fix options

Fix options

--- index.cjsx
+++ index.cjsx
@@ -1,6 +1,6 @@
 {join} = require "path-extra"
 fs = require "fs-extra"
-json = fs.readJsonSync join(__dirname, "assets", "quest.json"), "utf8"
+json = fs.readJsonSync join(__dirname, "assets", "quest.json")
 quests = []
 quests[quest.name] = quest for quest in json

@@ -28,4 +28,4 @@
   author: "马里酱"
   link: "https://github.com/malichan"
   version: '0.9.0'
-  show: false+  show: false


A modal title can be defined via data-modal-title attribute closes #370

--- wzk/ui/form/base.coffee
+++ wzk/ui/form/base.coffee
@@ -87,6 +87,9 @@
   url = String(goog.dom.dataset.get(el, 'url'))
   snippet = String(goog.dom.dataset.get(el, 'snippetName'))
   form = new wzk.ui.form.ModalForm(dom, new wzk.resource.Client(xhrFac), url, snippet, register)
+  modalTitle = goog.dom.dataset.get(el, 'modalTitle')
+  if modalTitle
+    form.setTitle(String(modalTitle))
   goog.events.listen el, goog.events.EventType.CLICK, (e) ->
     e.preventDefault()
     form.open()


Parentmodel property for setting an association

--- client/lanes/components/record-finder/RecordFinder.cjsx
+++ client/lanes/components/record-finder/RecordFinder.cjsx
@@ -1,10 +1,11 @@
 class Lanes.Components.RecordFinder extends Lanes.React.Component

     propTypes:
-        query:    Lanes.PropTypes.State.isRequired
-        model:    Lanes.PropTypes.State.isRequired
-        commands: React.PropTypes.object
-        onModelSet: React.PropTypes.func
+        query:       Lanes.PropTypes.State.isRequired
+        model:       Lanes.PropTypes.State
+        parentModel: Lanes.PropTypes.State
+        commands:    React.PropTypes.object
+        onModelSet:  React.PropTypes.func

     contextTypes:
         viewport: Lanes.PropTypes.State.isRequired
@@ -12,6 +13,11 @@
     mixins: [
         Lanes.Components.Form.FieldMixin
     ]
+    modelForAccess: ->
+        if @props.parentModel
+            @props.parentModel[@props.associationName]
+        else
+            @props.model

     showFinder: ->
         @context.viewport.displayModal(
@@ -22,6 +28,8 @@
         ).then(Lanes.emptyFn, Lanes.emptyFn)

     setModel: (model) ->
+        if @props.parentModel
+            @props.parentModel.set(@props.associationName, model)
         @props.commands?.setModel(model)
         @props.onModelSet?(model)

@@ -39,6 +47,7 @@
     renderEdit: ->
         findIcon = <button className="btn btn-primary icon icon-search icon-lg"
             onClick={@showFinder}/>
+
         <LC.Input
             ref="input"
             {...@props}
@@ -46,5 +55,5 @@
             editOnly writable
             name={@props.query.initialField.id}
             onKeyPress={@onKeyPress}
-            model={@props.model}
+            model={@modelForAccess()}
             buttonAfter={findIcon} />


Authentication service class description

--- app/assets/javascripts/darkswarm/services/authentication_service.js.coffee
+++ app/assets/javascripts/darkswarm/services/authentication_service.js.coffee
@@ -1,3 +1,8 @@
+# This class deals with displaying things in the login modal. It chooses
+# the modal tab templates and deals with switching tabs and passing data
+# between the tabs. It has direct access to the instance of the login modal,
+# and provides that access to other controllers as a service.
+
 Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections, Loading)->

   new class AuthenticationService


fixes #42

fixes #42

--- src/selection.coffee
+++ src/selection.coffee
@@ -14,11 +14,15 @@
 # DOM Selection API says offset is child index of container, not number of characters like ScribePosition
 normalizeNativePosition = (node, offset) ->
   if node?.nodeType == ScribeDOM.ELEMENT_NODE
-    if offset == 0
-      node = node.firstChild if node.firstChild?
+    return [node, 0] unless node.firstChild?
+    offset = Math.min(node.childNodes.length, offset)
+    if offset < node.childNodes.length
+      return normalizeNativePosition(node.childNodes[offset], 0)
     else
-      node = node.childNodes[node.childNodes.length-1]
-      offset = ScribeDOM.getText(node).length
+      if node.lastChild.nodeType == ScribeDOM.ELEMENT_NODE
+        return normalizeNativePosition(node.lastChild, node.lastChild.childNodes.length)
+      else
+        return [node.lastChild, ScribeUtils.getNodeLength(node.lastChild)]
   return [node, offset]

 normalizeNativeRange = (nativeRange) ->


Allow pasting white space when `autoIndentOnPaste` is enabled

--- src/selection.coffee
+++ src/selection.coffee
@@ -378,7 +378,7 @@
       indentAdjustment = @editor.indentLevelForLine(precedingText) - options.indentBasis
       @adjustIndent(remainingLines, indentAdjustment)

-    if options.autoIndent and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
+    if options.autoIndent and NonWhitespaceRegExp.test(text) and not NonWhitespaceRegExp.test(precedingText) and remainingLines.length > 0
       autoIndentFirstLine = true
       firstLine = precedingText + firstInsertedLine
       desiredIndentLevel = @editor.languageMode.suggestedIndentForLineAtBufferRow(oldBufferRange.start.row, firstLine)


findercontroller: refactor "mountVms" as a method

--- client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -72,15 +72,15 @@
     else
       @loadVms()

+  mountVms: (vms) ->
+    unless Array.isArray vms
+      return callback? "vmNames should be an Array"
+    @cleanup()
+    @mountVm vm  for vm in vms
+    callback?()
+
   loadVms:(vmNames, callback)->
-    mountVms = (vms)=>
-      unless Array.isArray vms
-        return callback? "vmNames should be an Array"
-      @cleanup()
-      @mountVm vm  for vm in vms
-      callback?()
-
-    if vmNames then mountVms vmNames
+    if vmNames then @mountVms vmNames
     else
       groupSlug  = KD.getSingleton("groupsController").getGroupSlug()
       groupSlug ?= 'koding'
@@ -88,16 +88,16 @@
         vms            or= {}
         vms[groupSlug] or= []
         if vms[groupSlug].length > 0
-          mountVms vms[groupSlug]
+          @mountVms vms[groupSlug]
         else
           KD.remote.api.JVM.fetchVmsByContext {}, (err, vms)=>
             return callback? err  if err
             if not vms or vms.length is 0
               KD.getSingleton('vmController').fetchDefaultVmName (vm)=>
-                if vm then mountVms [vm]
+                if vm then @mountVms [vm]
                 else @noVMFoundWidget.show()
             else
-              mountVms vms
+              @mountVms vms

   getVmNode:(vmName)->
     return null  unless vmName


Support credentials as client token

Support credentials as client token

--- src/octonode/client.coffee
+++ src/octonode/client.coffee
@@ -35,8 +35,10 @@
   # Github api URL builder
   query: (path = '/') ->
     path = '/' + path if path[0] isnt '/'
-    uri = "https://api.github.com#{path}"
-    uri+= if @token then "?access_token=#{@token}" else ''
+    uri = "https://"
+    uri+= if typeof @token == 'object' then "#{@token.username}@#{@token.password}" else ''
+    uri+= "api.github.com#{path}"
+    uri+= if typeof @token == 'string' then "?access_token=#{@token}" else ''

   errorHandle: (res, body, callback) ->
     # TODO More detailed HTTP error message


Autosave only when provider can save

Autosave only when provider can save

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -203,7 +203,7 @@
     if interval > 1000
       interval = Math.round(interval / 1000)
     if interval > 0
-      @_autoSaveInterval = setInterval (=> @save() if @state.dirty and @state.metadata?), (interval * 1000)
+      @_autoSaveInterval = setInterval (=> @save() if @state.dirty and @state.metadata?.provider?.can 'save'), (interval * 1000)

   isAutoSaving: ->
     @_autoSaveInterval > 0


Fix Missing Default Param in choice EDSL

--- src/coffee/edsl.coffee
+++ src/coffee/edsl.coffee
@@ -17,7 +17,7 @@
         {frontFacingChoice} = args
         storylets.register id, title, text, choices, frontFacingChoice
         return
-      choice: (title, text, next, args) ->
+      choice: (title, text, next, args = {}) ->
         {visibleReqs, activeReqs} = args
         visibleReqs ?= {}
         activeReqs ?= {}


Trigger global event to embedded images

Trigger global event to embedded images

--- packages/rocketchat-ui-master/master/main.coffee
+++ packages/rocketchat-ui-master/master/main.coffee
@@ -41,6 +41,12 @@

 			FlowRouter.go(link.pathname + link.search)

+		if $(link).hasClass('swipebox')
+			if RocketChat.Layout.isEmbedded()
+				e.preventDefault()
+				e.stopPropagation()
+				fireGlobalEvent('click-image-link', { href: link.href })
+
 	Tracker.autorun (c) ->
 		w = window
 		d = document


CollaborativeEditorPane: Support to select theme from argument

--- client/landing/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
+++ client/landing/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
@@ -78,16 +78,19 @@
       message: "$0 saved #{@getData().name}"
       by     : KD.nick()

-  setEditorTheme: ->
-    if document.getElementById "codemirror-ambiance-style"
-      return  @codeMirrorEditor.setOption "theme", "ambiance"
-    link       = document.createElement "link"
-    link.rel   = "stylesheet"
-    link.type  = "text/css"
-    link.href  = "#{cdnRoot}/theme/ambiance.css"
-    link.id    = "codemirror-ambiance-style"
-    document.head.appendChild link
-    @codeMirrorEditor.setOption "theme", "ambiance"
+  setEditorTheme: (theme = 'ambiance')->
+
+    styleId = "codemirror-#{theme}-style"
+
+    unless document.getElementById styleId
+      link       = document.createElement "link"
+      link.rel   = "stylesheet"
+      link.type  = "text/css"
+      link.href  = "#{cdnRoot}/theme/#{theme}.css"
+      link.id    = styleId
+      document.head.appendChild link
+
+    @codeMirrorEditor.setOption "theme", theme

   setEditorMode:(fileExtension) ->


[Email Confirmation] Send "EmailConfirmed" notification to user

--- client/landing/app/Applications/Account.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Account.kdapplication/AppController.coffee
@@ -191,4 +191,4 @@
         controller.leftToggler.hide()

   indexOfItem:(item)->
-    @itemsOrdered.indexOf item+    @itemsOrdered.indexOf item


Made naming of helptext functions a bit cleare

--- core/app/backbone/client/facts_new_view.coffee
+++ core/app/backbone/client/facts_new_view.coffee
@@ -38,7 +38,7 @@

   events:
     'click #submit': 'post_factlink',
-    'click .fact-wheel': 'closeWhatIsYourOpinionHelpText'
+    'click .fact-wheel': 'closeOpinionHelptext'

   templateHelpers: ->
     layout: @options.layout
@@ -53,7 +53,7 @@
     @addToCollection = new OwnChannelCollection
     @the_tooltip = new Tooltip($)

-    @openWhatIsYourOpinionHelpText()
+    @openOpinionHelptext()

   onRender: ->
     @renderAddToChannel()
@@ -122,19 +122,19 @@
         fact.set containing_channel_ids: channel_ids
         @trigger 'factCreated', fact

-  openWhatIsYourOpinionHelpText: ->
+  openOpinionHelptext: ->
     if FactlinkApp.guided
       @tooltipAdd '.fact-view',
         "What's your opinion?",
         "Here I can tell you something about colors and opinions. Only not in HTML yet.",
         { side: 'left' }

-  closeWhatIsYourOpinionHelpText: ->
+  closeOpinionHelptext: ->
     if FactlinkApp.guided
-      @openClickHereToFinishHelpText()
       @tooltipRemove('.fact-view')
+      @openFinishHelptext()

-  openClickHereToFinishHelpText: ->
+  openFinishHelptext: ->
     @tooltipAdd '#submit',
       "Great!",
       "Click here to finish.",


put element just needs project folder structure not docs inside

--- services/web/app/coffee/models/Project.coffee
+++ services/web/app/coffee/models/Project.coffee
@@ -80,7 +80,7 @@
 		logger.err project_id:project_id, folder_id:folder_id, element:element, type:type, "failed trying to insert element as it was null"
 		return callback(e)
 	type = sanitizeTypeOfElement type
-	this.findById project_id, (err, project)=>
+	require('../Features/Project/ProjectGetter').getProjectWithOnlyFolders project_id, (err, project)=>
 		if err?
 			callback(err)
 		if !folder_id?


Print python version

Print python version

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -58,6 +58,52 @@
     break
   options

+printVersions = (args) ->
+  apmVersion =  require('../package.json').version ? ''
+  npmVersion = require('npm/package.json').version ? ''
+  nodeVersion = process.versions.node ? ''
+
+  getPythonVersion (pythonVersion) ->
+    if args.json
+      console.log JSON.stringify(apm: apmVersion, npm: npmVersion, node: nodeVersion, python: pythonVersion)
+    else
+      pythonVersion ?= ''
+      console.log """
+        #{'apm'.red}  #{apmVersion.red}
+        #{'npm'.green}  #{npmVersion.green}
+        #{'node'.blue} #{nodeVersion.blue}
+        #{'python'.yellow} #{pythonVersion.yellow}
+      """
+
+getPythonVersion = (callback) ->
+  config = require './config'
+  npm = require 'npm'
+  {spawn} = require 'child_process'
+  semver = require 'semver'
+
+  npmOptions =
+    userconfig: config.getUserConfigPath()
+    globalconfig: config.getGlobalConfigPath()
+  npm.load npmOptions, ->
+    python = npm.config.get('python') ? process.env.PYTHON
+    if config.isWin32() and not python
+      rootDir = process.env.SystemDrive ? 'C:\\'
+      rootDir += '\\' unless rootDir[rootDir.length - 1] is '\\'
+      pythonExe = path.resolve(rootDir, 'Python27', 'python.exe')
+      python = pythonExe if fs.isFileSync(pythonExe)
+
+    python ?= 'python'
+
+    spawned = spawn(python, ['--version'])
+    outputChunks = []
+    spawned.stderr.on 'data', (chunk) -> outputChunks.push(chunk)
+    spawned.stdout.on 'data', (chunk) -> outputChunks.push(chunk)
+    spawned.on 'close', (code) ->
+      if code is 0
+        [name, version] = Buffer.concat(outputChunks).toString().split(' ')
+        version = version?.trim()
+      callback(version)
+
 module.exports =
   run: (args, callback) ->
     options = parseOptions(args)
@@ -95,19 +141,7 @@
     args = options.argv
     command = options.command
     if args.version
-      apmVersion =  require('../package.json').version ? ''
-      npmVersion = require('npm/package.json').version ? ''
-      nodeVersion = process.versions.node ? ''
-
-      if args.json
-        console.log JSON.stringify(apm: apmVersion, npm: npmVersion, node: nodeVersion)
-      else
-        console.log """
-          #{'apm'.red}  #{apmVersion.red}
-          #{'npm'.green}  #{npmVersion.green}
-          #{'node'.blue} #{nodeVersion.blue}
-        """
-
+      printVersions(args)
     else if args.help
       if Command = commands[options.command]
         new Command().showHelp(options.command)


Closes #1981; Message edit mode remains after deleting the message

--- packages/rocketchat-lib/client/MessageAction.coffee
+++ packages/rocketchat-lib/client/MessageAction.coffee
@@ -113,6 +113,8 @@
 					timer: 1000
 					showConfirmButton: false

+				if chatMessages[Session.get('openedRoom')].editing.id is message._id
+					chatMessages[Session.get('openedRoom')].clearEditing(message)
 				chatMessages[Session.get('openedRoom')].deleteMsg(message)
 		validation: (message) ->
 			return RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid ) or RocketChat.settings.get('Message_AllowDeleting') and message.u?._id is Meteor.userId()


fix not display empty line

fix not display empty line

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -20,7 +20,7 @@
         tokens: tokens,
         text: text,
         attributes: { 'class': 'line' },
-        htmlEolInvisibles: ''
+        htmlEolInvisibles: ' '
       }))
     @lines.html(html.join(''))
     html = displayBuffer = screenLines = null


Multiline fix

Multiline fix

--- packages/shrub-form/client/widget/checkboxes.coffee
+++ packages/shrub-form/client/widget/checkboxes.coffee
@@ -18,8 +18,12 @@
             for checkbox in scope.field.checkboxes
               checkbox.childName ?= checkbox.name

-              # ###### TODO: Multiline
-              checkbox.name = "#{scope.field.name}[#{checkbox.childName}]"
+              checkbox.name = "#{
+                scope.field.name
+              }[#{
+                checkbox.childName
+              }]"
+
               checkbox.type = 'checkbox'

             return


Repair the title syncing

Repair the title syncing

Do this by using the new getInitialMetadata config option in the metadata
plugin.

--- scripts/views/workspace/content/content-edit.coffee
+++ scripts/views/workspace/content/content-edit.coffee
@@ -12,6 +12,9 @@
     template: contentEditTemplate

     initialize: () ->
+      # CAVEAT: initialize is called every time a module is opened, so
+      # we're modifying the aloha config on every module open. That is not
+      # problematic, but it can be done more efficiently.
       Aloha.settings.plugins.metadata = Aloha.settings.plugins.metadata || {}
       Aloha.settings.plugins.metadata.supplement = ''

@@ -26,8 +29,10 @@
         metadata

       @listenTo @model, "change:title", (model, value, options) =>
-        Aloha.settings.plugins.metadata.extendMetadata({title: value}) if not options.triggeredByMetadata
-
+        if not options.triggeredByMetadata
+          Aloha.require('metadata/metadata-plugin').extendMetadata
+            title: value
+
       @listenTo @model, "change:head", (model, value, options) =>
         # sometimes there is no head. which is dumb.
         if @model.get('head')
@@ -35,4 +40,8 @@
         else
           Aloha.settings.plugins.metadata.supplement = "<title>" + @model.get("title") + "</title>"

+      # Tell the metadata plugin how to get the initial title
+      Aloha.settings.plugins.metadata.getInitialMetadata = () =>
+        return { title: @model.get('title') }
+
       super()


Added quoteTypes

Added quoteTypes

--- .metalsmith/metal.coffee
+++ .metalsmith/metal.coffee
@@ -58,6 +58,23 @@

     #{text}
   </h#{level}>
+  "
+
+markedRenderer.blockquote = (quote) ->
+  quoteTypes = ["good", "warning", "error"]
+  quoteType = ""
+  for _quoteType in quoteTypes
+    seeking = "
type:#{_quoteType}\n"
+    if quote.indexOf(seeking) == 0
+      # Set the quoteType to the current _quoteType
+      quoteType = _quoteType
+      # Trim the seeking from the quote
+      quote = quote[seeking.length...]
+      break
+  "
+

+    #{quote}
+

   "


0.X4a Experiment

0.X4a Experiment

-Bug fix

--- Eva.widget/index.coffee
+++ Eva.widget/index.coffee
@@ -864,7 +864,9 @@

 #   Processing output, passing values from command line output to variables
     AllOutputs = output.split('\n')
-    Batterievalues  = AllOutputs[0].split(' ')
+    #   If this is a desktop machine, the first line would be empty.
+    if (AllOutputs[0].indexOf("InternalBattery") > -1)
+        window.Batterievalues  = AllOutputs[0].split(' ')
     CPUUsage        = AllOutputs[1].split(' ')
     MemUsage        = AllOutputs[2].split(' ')
     CPUAmount       = AllOutputs[3].split(' ')


Add 'name' arg docs to PackageManager functions

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -162,6 +162,8 @@

   # Public: Enable the package with the given name.
   #
+  # * `name` - The {String} package name.
+  #
   # Returns the {Package} that was enabled or null if it isn't loaded.
   enablePackage: (name) ->
     pack = @loadPackage(name)
@@ -169,6 +171,8 @@
     pack

   # Public: Disable the package with the given name.
+  #
+  # * `name` - The {String} package name.
   #
   # Returns the {Package} that was disabled or null if it isn't loaded.
   disablePackage: (name) ->


Fix console.log wrapper to show correct stack trace

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -1,7 +1,5 @@
 "use strict"
 # Pre dependencies
-Preferences = require("app/preferences")
-sprintf = require("util/sprintf")
 require("util/primitive")

 ###*
@@ -41,20 +39,14 @@
   ###
   @classProperty("version", value: chrome?.runtime?.getManifest?()["version"] or VER_EMULATION)

-  LOG = (type, verbosity, args) ->
-    return if Preferences.logVerbosity < verbosity
-    fn = global.console[type].bind(global.console)
-    return fn(sprintf(args...)) if typeof(args[0]) == "string"
-    return fn(args...)
-
-  @log          = -> return LOG("log", 1, arguments)
-  @log.detail   = -> return LOG("log", 2, arguments)
-  @info         = -> return LOG("info", 1, arguments)
-  @info.detail  = -> return LOG("info", 2, arguments)
-  @warn         = -> return LOG("warn", 1, arguments)
-  @warn.detail  = -> return LOG("warn", 2, arguments)
-  @error        = -> return LOG("error", 1, arguments)
-  @error.detail = -> return LOG("error", 2, arguments)
+  for type in ["log", "info", "warn", "error"]
+    Object.defineProperty(
+      (@[type] = console[type].bind(console))
+      "verbose"
+      get: ->
+        return this if Preferences.logVerbosity >= 1
+        return (-> return)
+    )

   @info("Rubic/%s %s", @version, window?.navigator.userAgent)

@@ -87,4 +79,4 @@
     return false

 # Post dependencies
-# (none)
+Preferences = require("app/preferences")


seem to need to stroke every time, getting rects with border colors wrong. Will investigate using precompiled canvas paths to improve performance

--- bokehjs/src/coffee/renderer/glyph/rect.coffee
+++ bokehjs/src/coffee/renderer/glyph/rect.coffee
@@ -65,10 +65,9 @@
           else
             ctx.rect(sx[i]-sw[i]/2, sy[i]-sh[i]/2, sw[i], sh[i])

-          # only stroke if the line_properties have changed
-          if glyph_props.line_properties.set_vectorize(ctx, i)
-            ctx.stroke()
-            ctx.beginPath()
+          glyph_props.line_properties.set_vectorize(ctx, i)
+          ctx.stroke()
+          ctx.beginPath()

         ctx.stroke()


friendlier error reporting

friendlier error reporting

--- nodeko.coffee
+++ nodeko.coffee
@@ -141,7 +141,7 @@
     res = @res
     team_ids = k for k, v of scores
     save = (error, saved) ->
-      return res.send error.join("\n"), 500 if error?
+      return res.send sys.inspect(error), 500 if error?
       if team_id = team_ids.pop()
         Team.updateAll team_id, { $set: { score: scores[team_id] }}, save
       else


only pass on what seems like a file missing exception, otherwise re-throw

--- nodeko.coffee
+++ nodeko.coffee
@@ -167,6 +167,7 @@
   try
     @render "${file}.html.haml"
   catch e
+    throw e if e.errno != 2
     @pass "/${file}"

 get '/*', (file) ->


Fix #221

Fix #221

IE seems unforgiving of double focuses

--- src/core/editor.coffee
+++ src/core/editor.coffee
@@ -67,8 +67,10 @@
     @selection.update(source)

   focus: ->
-    @selection.setRange(@selection.range)
-    @root.focus()
+    if @selection.range?
+      @selection.setRange(@selection.range)
+    else
+      @root.focus()

   getDelta: ->
     return @delta


dsync -- I have it working so that random "Lost outbound packet" is correctly dealt with now.

--- salvus/dsync.coffee
+++ salvus/dsync.coffee
@@ -36,7 +36,8 @@
         @shadow                = @_copy(@live)
         @backup_shadow         = @_copy(@shadow)
         @shadow_version        = 0
-        @last_version_received = 0
+        @backup_shadow_version = 0
+        @last_version_received = -1
         @edit_stack            = []

     status: () =>
@@ -83,46 +84,54 @@
             console.log("#{@id} -- shadow changes: '#{@shadow}' --> '#{snapshot}'  (version #{@shadow_version+1})")
             @shadow = snapshot
             @shadow_version += 1
-        else
-            # console.log("#{@id} -- push_edits -- (nothing new)")
+
+        if @id=='client' and Math.random() < .5
+            cb?(true)
+            return

         # Push any remaining edits from the stack, *AND* report the last version we have received so far.
-        @server.recv_edits @edit_stack, @last_version_received, cb
+        @server.recv_edits(@edit_stack, @last_version_received, cb)

     # Receive and process the edits from the other end of the sync connection.
     recv_edits: (edit_stack, last_version_ack, cb) =>
-
-        if Math.random() < .5
-            cb?(true)
-            return

         #console.log("#{@id} -- recv_edits -- #{misc.to_json(edit_stack)}")
         # Keep only edits that we still need to send.
         @edit_stack = (edits for edits in @edit_stack when edits.shadow_version > last_version_ack)

-        #console.log("#{@id} -- remaining queued edits for next time: ", @edit_stack)
-        # process the incoming edits
+        if edit_stack.length == 0
+            cb?()
+            return
+
+        # Make a backup, just in case it turns out that our message back to the client that
+        # we applied these changes is lost "Lost return packet."
+        @backup_shadow = @_copy(@shadow)
+        @backup_shadow_version = @shadow_version
+
+        # Process the incoming edits
         for edits in edit_stack
             console.log("#{@id} -- our shadow version = #{@shadow_version} and other shadow version #{edits.shadow_version}")
-            # If edits.shadow_version does not equal @shadow_version, then there was a packet duplication or loss.
-            if edits.shadow_version < @shadow_version
-                console.log("Duplicate Packet: we have no interest in edits we have already processed.")
-                continue
-            else if edits.shadow_version > @shadow_version
-                @restart('shadow_version out of sync')
-            else if edits.shadow_checksum != @_checksum(@shadow)
-                # Data corruption in memory or network -- there should be no other way for this to happen.
-                # In this case, we have to just restart everything from scratch.
-                console.log("#{@id}: shadow = #{@shadow}, version = #{@shadow_version}")
-                @restart("checksum (edit_stack=#{misc.to_json(edit_stack)})")
-            else
-                # Everything looks golden.
+            if edits.shadow_version == @shadow_version
+                if edits.shadow_checksum != @_checksum(@shadow)
+                    # Data corruption in memory or network -- there should be no other way for this to happen.
+                    # In this case, we have to just restart everything from scratch.
+                    @restart("checksum (edit_stack=#{misc.to_json(edit_stack)})")
+                    cb(true)
+                    return
+                console.log("last_version_received:  #{@last_version_received} --> #{edits.shadow_version}")
                 @last_version_received = edits.shadow_version
-                console.log("#{@id} -- shadow changes: '#{@shadow}' --> '#{@_apply_edits(edits.edits, @shadow)}' (version #{@shadow_version+1})")
                 @shadow = @_apply_edits(edits.edits, @shadow)
                 @shadow_version += 1
                 @live = @_apply_edits(edits.edits, @live)
-                @backup_shadow = @_copy(@shadow)
+
+            else
+                # PACKET LOSS / CORRUPTION
+                # If edits.shadow_version does not equal @shadow_version, then there was a packet duplication or loss.
+                if edits.shadow_version < @shadow_version
+                    console.log("Duplicate Packet: we have no interest in edits we have already processed.")
+                    continue
+                else if edits.shadow_version > @shadow_version
+                    @restart('shadow_version out of sync')

         cb?()

@@ -227,10 +236,7 @@

     go()
     client.live += "\nmore stuff"
-    go()
-
-    client.live = 'bar1\n' + client.live
-    server.live = 'bar2\n'
+    server.live = 'bar' + server.live
     status()
     while client.live != server.live
         try


Add guard to password

Add guard to password

--- app/assets/javascripts/users.coffee
+++ app/assets/javascripts/users.coffee
@@ -40,4 +40,4 @@
   addOrgRows $table

 $(document).on "page:change", ->
-  $.guard("#user-password, #user-password-confirmation").using("regex", /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]{8,72}$/).message("Password must contain one uppercase, one lowercase and one number and be atlease 8 characters long.")
+  $.guard("#user-password, #user-password-confirmation").using("regex", /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]{8,72}$/)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -169,4 +169,4 @@

       send: (name, args...) ->
         self[name](args...)
-{+)()


Set default frame of get-subject-location to 0

- Closes #1649

--- app/lib/get-subject-location.coffee
+++ app/lib/get-subject-location.coffee
@@ -2,9 +2,7 @@
   image: ['jpeg', 'png', 'svg+xml', 'gif']
   video: ['mp4']

-module.exports = (subject, frame) ->
-  frame ?= Math.floor Object.keys(subject.locations).length / 2
-
+module.exports = (subject, frame = 0) ->
   for mimeType, src of subject.locations[frame]
     [type, format] = mimeType.split '/'
     if type of READABLE_FORMATS and format in READABLE_FORMATS[type]


small optimisation: send all line item state transitions at once

--- src/coffee/order_state_sync.coffee
+++ src/coffee/order_state_sync.coffee
@@ -52,17 +52,11 @@
       ref = (state) ->
         {typeId: "state", id: state.id}

-      doTransition = (order, ts) ->
-        if _.isEmpty(ts)
-          Q(order)
-        else
-          transition = _.first ts
-          targetSphereService.transitionLineItemState order, targetLineItemId, quantity, ref(transition.from), ref(transition.to), date
-          .then (resOrder) ->
-            doTransition resOrder, _.tail(ts)
+      transitions.then (ts) ->
+        actualTransitions = _.map ts, (transition) ->
+          {quantity: quantity, fromState: ref(transition.from), toState: ref(transition.to), date: date}

-      transitions.then (ts) ->
-        doTransition targetOrder, ts
+        targetSphereService.transitionLineItemStates targetOrder, targetLineItemId, actualTransitions
         .then (resOrder) ->
           {order: resOrder.id, version: resOrder.version, lineItem: targetLineItemId, quantity: quantity, transitions: _.map(ts, (t) -> t.from.key + " -> " + t.to.key)}


fix minor error formatting mistakes

fix minor error formatting mistakes

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -320,8 +320,8 @@
     # Can be overriden to provide more meaningful feedback handlers.
     socketFeedback: external ->
         assert ulocation = @location.green.underline
-        r = "an error raised during socket connection:"
-        p = "an exception happend at the server provider:"
+        r = "an error raised during socket connection: %s"
+        p = "an exception happend at the server provider: %s"
         connected = c = "Established connection at %s".green
         disconnect = "lost socket connection at #{@location}"
         @on "disconnect", -> @booted = false # connection lost


better seen in the console

better seen in the console

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -197,7 +197,7 @@
             assert this isnt constructor, "scoping error"
             assert _.isObject shadow = Object.create this
             assert isolating = "Isolated provider call in %s"
-            logger.debug isolating.grey, sid = socket.id.bold
+            logger.debug isolating.yellow, sid = socket.id.bold
             _.extend shadow, __uis: uis = _.uniqueId(prefix)
             _.extend shadow, __isolated: yes, __origin: this
             _.extend shadow, session: session, binder: binder


Minor syntax fix

Minor syntax fix

--- library/nucleus/routing.coffee
+++ library/nucleus/routing.coffee
@@ -103,5 +103,5 @@
         @emit "recognized", recognized, arguments...
         matching = "Request %s matches %s service"
         logger.debug matching.grey, incoming, identify
-        results =  recognized.process arguments...
+        results = recognized.process arguments...
         return next() unless response.headerSent


Implement the content node abstraction class

--- library/visualize/trees.coffee
+++ library/visualize/trees.coffee
@@ -111,6 +111,24 @@
         @$prefix = prefix.toString()

 # This class represents an implementation of non abstract tree node
+# that implements semantics of the textal content node, equal to
+# XML text content. This node implementation inherits the root ABC
+# and therefore provides no extra capabilies beyond what is does.
+module.exports.Content = class Content extends Abstract
+
+    # Either get or set the attr textual content string for this
+    # element. For the semantics of the name please refer to the
+    # XML and/or SGML specification documents. If the value content
+    # is not supplied, this method will return the current content.
+    content: (content) ->
+        correct = _.isString content
+        return @$content unless content?
+        invalid = "Content is not a string"
+        throw new Error invalid unless correct
+        @emit "content", this, @$content, content
+        @$content = content.toString()
+
+# This class represents an implementation of non abstract tree node
 # that implements semantics of the named, valueable node, equal to
 # XML attrubte. This node implementation inherits the identity ABC
 # and therefore provides the naming and identification capabilities.


Add extra keyframe before delay to keep transform before

--- app/scripts/lib/bounce/index.coffee
+++ app/scripts/lib/bounce/index.coffee
@@ -136,6 +136,9 @@
         componentKeys = component.easingObject.findOptimalKeyPoints().map (key) =>
           (key * component.duration / @duration) + (component.delay / @duration)

+        if component.delay
+          componentKeys.push((component.delay / @duration) - 0.001)
+
         keys = keys.concat componentKeys
     else
       frames = Math.round((@duration / 1000) * Bounce.FPS)


sort team votes by latest on top

sort team votes by latest on top

--- models/team.coffee
+++ models/team.coffee
@@ -204,7 +204,7 @@
 TeamSchema.method 'deploys', (next) ->
   Deploy.find teamId: @id, next
 TeamSchema.method 'votes', (next) ->
-  Vote.find teamId: @id, next
+  Vote.find teamId: @id, {}, { sort: [['updatedAt', -1]] }, next

 # validations


Validate notnull

Validate notnull

--- models/user.coffee
+++ models/user.coffee
@@ -12,10 +12,15 @@
             return next()
         .catch (err) ->
             return next err
+checkNotNull = (value, next) ->
+    if value.length == 0
+        throw new Error('Atention valeur nulle')
+    else
+        next()

-notNull: {
+notNull:
     msg: "Valeur nulle"
-}
+

 # def model
 User = db.orm.define 'User',
@@ -30,7 +35,7 @@
         validate:
             len: [3, 25]
             isUnique: checkUnique
-            notNull: true
+            isNotNull: checkNotNull
     email:
         type: Sequelize.STRING 25
         unique: true
@@ -38,12 +43,12 @@
         validate:
             len: [3, 25]
             isUnique: checkUnique
-            notNull: true
+            isNotNull: checkNotNull
     password:
         type: Sequelize.STRING
         allowNull: false
         validate:
-            notNull: true
+            isNotNull: checkNotNull
             len: [6,100]


Add email+password to user NOT TESTED

Add email+password to user NOT TESTED

--- models/user.coffee
+++ models/user.coffee
@@ -13,6 +13,15 @@
         .catch (err) ->
             return next err

+isLongEnough = (value, next) ->
+    try
+        if val.length < 7
+            throw new Error('Please choose a longer password')
+        else
+            return next()
+    catch err
+        return next err
+
 # def model
 User = db.orm.define 'User',
     id:
@@ -26,6 +35,24 @@
         validate:
             len: [3, 25]
             isUnique: checkUnique
+    email:
+        type: Sequelize.STRING 25
+        unique: true
+        allowNull: false
+        validate:
+            len: [3, 25]
+            isUnique: checkUnique
+    password_hash:
+        type: Sequelize.STRING
+        allowNull: false
+    password:
+        type: Sequelize.VIRTUAL
+        allowNull: false
+        set: (val) ->
+            @setDataValue 'password', val
+            # Remember to set the data value, otherwise it won't be validated
+            @setDataValue 'password_hash', @salt + val
+        validate: isLongEnough

 # def model assocs
 User.belongsTo Team,


Check if axes is defined

Check if axes is defined

--- player.coffee
+++ player.coffee
@@ -131,13 +131,13 @@
     @gl.pixelStorei @gl.UNPACK_FLIP_Y_WEBGL, true

   input_state: (port, device, index, id) =>
-    if id == @core.DEVICE_ID_JOYPAD_LEFT && @inputs[port]?.axes[0] < -.5
-      return true
-    if id == @core.DEVICE_ID_JOYPAD_RIGHT && @inputs[port]?.axes[0] > .5
-      return true
-    if id == @core.DEVICE_ID_JOYPAD_UP && @inputs[port]?.axes[1] < -.5
-      return true
-    if id == @core.DEVICE_ID_JOYPAD_DOWN && @inputs[port]?.axes[1] > .5
+    if id == @core.DEVICE_ID_JOYPAD_LEFT && @inputs[port]?.axes?[0] < -.5
+      return true
+    if id == @core.DEVICE_ID_JOYPAD_RIGHT && @inputs[port]?.axes?[0] > .5
+      return true
+    if id == @core.DEVICE_ID_JOYPAD_UP && @inputs[port]?.axes?[1] < -.5
+      return true
+    if id == @core.DEVICE_ID_JOYPAD_DOWN && @inputs[port]?.axes?[1] > .5
       return true
     @inputs[port]?.buttons[{
       0: 0


Started page parser.

Started page parser.

--- plugin.coffee
+++ plugin.coffee
@@ -10,7 +10,7 @@

 module.exports = (wintersmith, callback) ->

-  isProd = true
+  isProd = false

   preprocessPackage = (type, package) ->
     for name, files of package
@@ -70,7 +70,7 @@
       if @template == 'none'
         # dont render
         return callback null, null
-
+
       async.waterfall [
         (callback) =>
           template = templates[@template]
@@ -84,18 +84,42 @@
             contents: contents
             _: _
             moment: require 'moment'
-          _.extend ctx, locals, assets
+          _.extend ctx, locals, parseAssets contents
           template.render ctx, callback
       ], callback
+
+  class KelvinMarkdownPage extends wintersmith.defaultPlugins.MarkdownPage

-  assets =
-    css:
-      all: '<link rel="text/css" href="test.css" />'
-    js:
-      all: '<script src="test.js"></script>'
+    render: (locals, contents, templates, callback) ->
+      if @template == 'none'
+        # dont render
+        return callback null, null
+
+      async.waterfall [
+        (callback) =>
+          template = templates[@template]
+          if not template?
+            callback new Error "page '#{ @filename }' specifies unknown template '#{ @template }'"
+          else
+            callback null, template
+        (template, callback) =>
+          ctx =
+            page: @
+            contents: contents
+            _: _
+            moment: require 'moment'
+          _.extend ctx, locals, parseAssets contents
+          template.render ctx, callback
+      ], callback
+
+  parseAssets = (contents) ->
+    assets = contents['assets.json']
+    unless assets
+      return
+    {}

   wintersmith.registerContentPlugin 'pages', '**/*.json', KelvinJsonPage
-  #wintersmith.registerContentPlugin 'pages', '**/*.*(markdown|mkd|md)', KelvinMarkdownPage
+  wintersmith.registerContentPlugin 'pages', '**/*.*(markdown|mkd|md)', KelvinMarkdownPage
   wintersmith.registerContentPlugin 'assets', 'assets.json', KelvinAssets
   wintersmith.registerTemplatePlugin '**/*.*(mustache|hogan)', KelvinTemplate
   callback()


Fix graylog script - Hubot should respond, not hear

--- src/scripts/graylog.coffee
+++ src/scripts/graylog.coffee
@@ -25,31 +25,31 @@

 module.exports = (robot) ->

-  robot.hear /graylog streams$/i, (msg) ->
+  robot.respond /graylog streams$/i, (msg) ->
     graylogStreams msg, (what) ->
       msg.send what

-  robot.hear /graylog hosts$/i, (msg) ->
+  robot.respond /graylog hosts$/i, (msg) ->
     graylogHosts msg, (what) ->
       msg.send what

-  robot.hear /graylog$/i, (msg) ->
+  robot.respond /graylog$/i, (msg) ->
     graylogStreamMessages msg, 'all', 5, (what) ->
       msg.send what

-  robot.hear /graylog (\d+)$/i, (msg) ->
+  robot.respond /graylog (\d+)$/i, (msg) ->
     count = parseInt(msg.match[1] || '5')
     graylogMessages msg, 'messages.json', (messages) ->
       sayMessages messages, count, (what) ->
         msg.send what

-  robot.hear /graylog (.+) (\d+)/i, (msg) ->
+  robot.respond /graylog (.+) (\d+)/i, (msg) ->
     stream = msg.match[1]
     count = parseInt(msg.match[2] || '5')
     graylogStreamMessages msg, stream, count, (what) ->
       msg.send what

-  robot.hear /graylog host (.+) (\d+)/i, (msg) ->
+  robot.respond /graylog host (.+) (\d+)/i, (msg) ->
     host = msg.match[1]
     count = parseInt(msg.match[2] || '5')
     graylogHostMessages msg, host, count, (what) ->


More informative error messages

More informative error messages

--- src/scripts/harvest.coffee
+++ src/scripts/harvest.coffee
@@ -151,7 +151,7 @@
         else
           msg.reply "Uh-oh – I just tested your credentials, but they appear to be wrong. Please specify the correct ones."
     catch error
-      msg.reply "Fatal error: #{error}"
+      msg.reply "Unable to test credentials: fatal error: #{error}"

   # Allows a user to delete his credentials.
   robot.respond /forget my harvest account/i, (msg) ->
@@ -182,7 +182,7 @@
               else
                 msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
           else
-            msg.reply "Request failed with status #{status}."
+            msg.reply "Failed to retrieve entry information: request failed with status #{status}."
       else
         harvest.daily msg, (status, body) ->
           if 200 <= status <= 299
@@ -193,9 +193,9 @@
               else
                 msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
           else
-            msg.reply "Request failed with status #{status}."
+            msg.reply "Failed to retrieve entry information: request failed with status #{status}."
     catch error
-      msg.reply("Fatal error: #{error}")
+      msg.reply("Failed to retrieve entry information: fatal error: #{error}")

   # List all project/task combinations that are available to a user.
   robot.respond /list harvest tasks( of (.+))?/i, (msg) ->
@@ -212,9 +212,9 @@
             for task in project.tasks
               msg.reply "  ‣ #{task.name} (#{if task.billable then 'billable' else 'non-billable'})"
         else
-          msg.reply "Request failed with status #{status}."
+          msg.reply "Failed to retrieve project/task list: request failed with status #{status}."
     catch error
-      msg.reply "Fatal error: #{error}"
+      msg.reply "Failed to retrieve project/task list: fatal error: #{error}"

   # Kick off a new timer, stopping the previously running one, if any.
   robot.respond /start harvest at (.+)\/(.+): (.*)/i, (msg) ->
@@ -233,9 +233,9 @@
             msg.reply "Previously running timer stopped at #{body.hours_for_previously_running_timer}h."
           msg.reply "OK, I started tracking you on #{body.project}/#{body.task}."
         else
-          msg.reply "Request failed with status #{status}."
+          msg.reply "Failed to start timer: request failed with status #{status}."
     catch error
-      msg.reply "Fatal error: #{error}"
+      msg.reply "Failed to start timer: fatal error: #{error}"

   # Stops the timer running for a project/task combination,
   # if any. If no combination is given, stops the first
@@ -253,19 +253,19 @@
           if 200 <= status <= 299
             msg.reply "Timer stopped (#{body.hours}h)."
           else
-            msg.reply "Request failed with status #{status}."
+            msg.reply "Failed to stop timer: request failed with status #{status}."
             msg.reply body
       catch error
-        msg.reply("Fatal error: #{error}")
+        msg.reply("Failed to stop timer: fatal error: #{error}")
     else
       try
         harvest.stop_first msg, (status, body) ->
           if 200 <= status <= 299
             msg.reply "Timer stopped (#{body.hours}h)."
           else
-            msg.reply "Request failed with status #{status}."
+            msg.reply "Failed to stop timer: request failed with status #{status}."
       catch error
-        msg.reply("Fatal error: #{error}")
+        msg.reply("Failed to stop timer: fatal error: #{error}")

 # Class encapsulating a user's Harvest credentials; safe to store
 # in Hubot's Redis brain (no methods, this is a data-only construct).


#1168 Added buildWithEmptyParams option

#1168 Added buildWithEmptyParams option

--- src/scripts/jenkins.coffee
+++ src/scripts/jenkins.coffee
@@ -36,12 +36,12 @@
   else
     msg.reply "I couldn't find that job. Try `jenkins list` to get a list."

-jenkinsBuild = (msg) ->
+jenkinsBuild = (msg, buildWithEmptyParameters) ->
     url = process.env.HUBOT_JENKINS_URL
     job = querystring.escape msg.match[1]
     params = msg.match[3]
-
-    path = if params then "#{url}/job/#{job}/buildWithParameters?#{params}" else "#{url}/job/#{job}/build"
+    command = if buildWithEmptyParameters then "buildWithParameters" else "build"
+    path = if params then "#{url}/job/#{job}/buildWithParameters?#{params}" else "#{url}/job/#{job}/#{command}"

     req = msg.http(path)

@@ -55,6 +55,8 @@
           msg.reply "Jenkins says: #{err}"
         else if 200 <= res.statusCode < 400 # Or, not an error code.
           msg.reply "(#{res.statusCode}) Build started for #{job} #{url}/job/#{job}"
+        else if 400 == res.statusCode
+          jenkinsBuild(msg, true)
         else
           msg.reply "Jenkins says: Status #{res.statusCode} #{body}"

@@ -83,10 +85,10 @@

             if content.description
               response += "DESCRIPTION: #{content.description}\n"
-
+
             response += "ENABLED: #{content.buildable}\n"
             response += "STATUS: #{content.color}\n"
-
+
             tmpReport = ""
             if content.healthReport.length > 0
               for report in content.healthReport
@@ -157,7 +159,7 @@
               # Add the job to the jobList
               index = jobList.indexOf(job.name)
               if index == -1
-                jobList.push(job.name)
+                jobList.push(job.name)
                 index = jobList.indexOf(job.name)

               state = if job.color == "red" then "FAIL" else "PASS"
@@ -169,7 +171,7 @@

 module.exports = (robot) ->
   robot.respond /j(?:enkins)? build ([\w\.\-_ ]+)(, (.+))?/i, (msg) ->
-    jenkinsBuild(msg)
+    jenkinsBuild(msg, false)

   robot.respond /j(?:enkins)? b (\d+)/i, (msg) ->
     jenkinsBuildById(msg)


fixed typo

fixed typo

--- src/scripts/manatee.coffee
+++ src/scripts/manatee.coffee
@@ -3,7 +3,7 @@
 #
 # Dependencies:
 #   "htmlparser": "1.7.6"
-#   "soupselect: "0.2.0"
+#   "soupselect": "0.2.0"
 #
 # Configuration:
 #   None


Fix positioned record initialization

Fix positioned record initialization

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -115,7 +115,7 @@
             $scope.form.edit.$setPristine()
       if record
         if isFinite(record.segment.l)
-          $scope.position = track.asset.segment.l
+          $scope.position = record.segment.l

     removed = (track) ->
       return if track.asset || track.file


Make confirmation message for highlights.

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -715,7 +715,7 @@
         if value == undefined || value == ''
           return
         messages.clear(this)
-        if @excerpt.target.permission > slot.volume.permission
+        if !@asset.classification && value > (slot.release || 0)
           confirmation = confirm("The highlight level you have chosen is more open then the volume.  Are you sure you want to proceed?")
         if confirmation
           @excerpt.target.setExcerpt(value)


Fix selection of spaces in spaces/index

Fix selection of spaces in spaces/index

jQuery's `$().on("hover")` is now `$().hover()`.

--- app/assets/javascripts/app/spaces/index.js.coffee
+++ app/assets/javascripts/app/spaces/index.js.coffee
@@ -25,7 +25,7 @@
       $("#space-filter-text").focus()

     # hovering an space shows its description in the sidebar
-    $(".space-item").on "hover", ->
+    $(".space-item").hover ->

       # hide all descriptions and shows the selected
       hovered = "div#" + $(this).attr("name") + "-description"


增加参数appkey，对应新浪微博，qq空间，腾讯微博，开心网的显示来源参数

增加参数appkey，对应新浪微博，qq空间，腾讯微博，开心网的显示来源参数

--- app/assets/javascripts/social-share-button.coffee
+++ app/assets/javascripts/social-share-button.coffee
@@ -5,6 +5,7 @@

   share : (el) ->
     site = $(el).data('site')
+    appkey = $(el).data('appkey') || ''
     $parent = $(el).parent()
     title = encodeURIComponent($parent.data('title') || '')
     img = encodeURIComponent($parent.data("img") || '')
@@ -18,7 +19,7 @@
       when "email"
         location.href = "mailto:?to=&subject=#{title}&body=#{url}"
       when "weibo"
-        SocialShareButton.openUrl("http://service.weibo.com/share/share.php?url=#{url}&type=3&pic=#{img}&title=#{title}")
+        SocialShareButton.openUrl("http://service.weibo.com/share/share.php?url=#{url}&type=3&pic=#{img}&title=#{title}&appkey=#{appkey}")
       when "twitter"
         SocialShareButton.openUrl("https://twitter.com/intent/tweet?url=#{url}&text=#{title}&via=#{via}")
       when "douban"
@@ -26,13 +27,13 @@
       when "facebook"
         SocialShareButton.openUrl("http://www.facebook.com/sharer.php?u=#{url}")
       when "qq"
-        SocialShareButton.openUrl("http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=#{url}&title=#{title}&pics=#{img}&summary=#{desc}")
+        SocialShareButton.openUrl("http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=#{url}&title=#{title}&pics=#{img}&summary=#{desc}&site=#{appkey}")
       when "tqq"
-        SocialShareButton.openUrl("http://share.v.t.qq.com/index.php?c=share&a=index&url=#{url}&title=#{title}&pic=#{img}")
+        SocialShareButton.openUrl("http://share.v.t.qq.com/index.php?c=share&a=index&url=#{url}&title=#{title}&pic=#{img}&appkey=#{appkey}")
       when "baidu"
         SocialShareButton.openUrl("http://hi.baidu.com/pub/show/share?url=#{url}&title=#{title}&content=#{desc}")
       when "kaixin001"
-        SocialShareButton.openUrl("http://www.kaixin001.com/rest/records.php?url=#{url}&content=#{title}&style=11&pic=#{img}")
+        SocialShareButton.openUrl("http://www.kaixin001.com/rest/records.php?url=#{url}&content=#{title}&style=11&pic=#{img}&starid=#{appkey}")
       when "renren"
         SocialShareButton.openUrl("http://widget.renren.com/dialog/share?resourceUrl=#{url}&srcUrl=#{url}&title=#{title}&pic=#{img}&description=#{desc}")
       when "google_plus"


remove deprecated breadcrumb rendering in end panel components, update with prop types

--- src/components/task-step/ends.cjsx
+++ src/components/task-step/ends.cjsx
@@ -9,6 +9,13 @@
 {TaskStepStore} = require '../../flux/task-step'

 PracticeEnd = React.createClass
+  displayName: 'PracticeEnd'
+
+  propTypes:
+    courseId: React.PropTypes.any.isRequired
+    taskId: React.PropTypes.any.isRequired
+    reloadPractice: React.PropTypes.func.isRequired
+
   render: ->
     footer =

@@ -22,7 +29,6 @@

-      {@props.breadcrumbs}
       <BS.Panel bsStyle="default" footer={footer} className='-practice-completed'>

You earned a star!

Great Job!

@@ -30,6 +36,10 @@

 HomeworkEnd = React.createClass
+  displayName: 'HomeworkEnd'
+
+  propTypes:
+    taskId: React.PropTypes.any.isRequired

   _addListener: ->
     TaskStepStore.addChangeListener(@_update)
@@ -90,7 +100,6 @@
     footer = <Router.Link to="dashboard" className="btn btn-primary">Back to Dashboard</Router.Link>

-      {@props.breadcrumbs}
       <BS.Panel bsStyle="default" footer={footer} className='-homework-completed'>
         {congratsMessage}

You have answered {completeSteps} of {totalSteps} questions.

@@ -111,11 +120,12 @@
       @renderBeforeDue taskId

 TaskEnd = React.createClass
+  displayName: 'TaskEnd'
+
   render: ->
     footer = <Router.Link to="dashboard" className="btn btn-primary">Back to Dashboard</Router.Link>

-      {@props.breadcrumbs}
       <BS.Panel bsStyle="default" footer={footer} className="-reading-completed">

You Are Done.

Great Job!


adding collisionBounds method

adding collisionBounds method

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -11,7 +11,7 @@
 @param {Object} self Reference to including object
 ###

-Bounded = (I) ->
+Bounded = (I, self) ->
   I ||= {}

   $.reverseMerge I,
@@ -19,6 +19,7 @@
     y: 0
     width: 8
     height: 8
+    collisionMargin: Point(0, 0)

   ###*
   The position of this game object, the top left point in the local transform.
@@ -31,6 +32,27 @@

   collides: (bounds) ->
     Collision.rectangular(I, bounds)
+
+  ###*
+  This returns a modified bounds based on the collision margin.
+  The area of the bounds is reduced if collision margin is positive
+  and increased if collision margin is negative.
+
+  @name collisionBounds
+  @methodOf Bounded#
+
+  @param {number} xOffset the amount to shift the x position
+  @param {number} yOffset the amount to shift the y position
+  ###
+  collisionBounds: (xOffset, yOffset) ->
+    bounds = self.bounds(xOffset, yOffset)
+
+    bounds.x += I.collisionMargin.x
+    bounds.y += I.collisionMargin.y
+    bounds.width -= 2 * I.collisionMargin.x
+    bounds.height -= 2 * I.collisionMargin.y
+
+    return bounds

   ###*
   The bounds method returns infomation about the location


Remove unused code.

Remove unused code.

--- app/javascripts/router.coffee
+++ app/javascripts/router.coffee
@@ -26,38 +26,6 @@
           cond = paramStrs.join(',')
           # Return the {where: condition} object.
           where: "{#{ cond }}"
-
-        # # Fetches a subject from the given search parameters. This method
-        # # returns a promise which resolves to a subject object as follows:
-        # # * If the search parameters contain a fetch flag which is truthy,
-        # #   then the resolved value is the Subject resource object fetched
-        # #   with the search parameters.
-        # # * Otherwise, the return promise resolves to a new object with
-        # #   the project, collection and subject number copied from the
-        # #   search parameters.
-        # #
-        # # @param params the search parameters
-        # # @returns a promise which resolves to the Subject resource
-        # #   object
-        # getSubject: (params) ->
-        #   # The subject is built from the search parameters.
-        #   subject =
-        #     project: params.project
-        #     collection: _s.capitalize(params.collection)
-        #     number: params.number
-        #   # If the fetch flag is set, then query the Subject resource
-        #   # with the subject as the query condition.
-        #   if params.fetch
-        #     Subject.query(where(subject)).$promise.then (subjects) ->
-        #       if subjects.length > 1
-        #         throw "Subject query returned more than one subject:" +
-        #               " #{ _.pairs(subject) }"
-        #       subjects[0]
-        #   else
-        #     # Make a promise which immediately resolves to the subject.
-        #     deferred = $q.defer()
-        #     deferred.resolve(subject)
-        #     deferred.promise

         # Fetches the subject detail and fixes it up as follows:
         # * converts the session and encounter dates into moments
@@ -110,6 +78,7 @@
                                   " are not yet supported."
                 sess.modeling = sess.modeling[0]

+
               # Fix the encounter dates.
               Helpers.fixDate(enc, 'date') for enc in detail.encounters
               # Copy the detail content into the subject.


fix(macAutocomplete): Fixed menu not positioned correctly Fixed offset getting calculated before animation completes

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -144,12 +144,13 @@
       @name appendMenu
       @description
       Adding menu to DOM
-      ###
-      appendMenu = ->
+      @param {Function} callback Callback after enter animation completes
+      ###
+      appendMenu = (callback) ->
         if inside
-          $animate.enter menuEl, undefined, element
+          $animate.enter menuEl, undefined, element, callback
         else
-          $animate.enter menuEl, angular.element(document.body)
+          $animate.enter menuEl, angular.element(document.body), undefined, callback

         element.bind "blur", clickHandler

@@ -177,17 +178,21 @@
       Calculate the style include position and width for menu
       ###
       positionMenu = ->
-        if $menuScope.items.length > 0
+        return if $menuScope.items.length is 0
+
+        appendMenu ->
+          parentOffset = menuEl.offset()
+
           $menuScope.style          = element.offset()
-          $menuScope.style.top     += element.outerHeight()
+          $menuScope.style.left    -= parentOffset.left
+          $menuScope.style.top     += element.outerHeight() - parentOffset.top
           $menuScope.style.minWidth = element.outerWidth()

+          # Add 'px' to left and top
           angular.forEach $menuScope.style, (value, key) ->
             if not isNaN(+value) and angular.isNumber +value
               value = "#{value}px"
             $menuScope.style[key] = value
-
-          appendMenu()

       ###
       @function


Downloading zaoch submits too

Downloading zaoch submits too

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -1,11 +1,8 @@
 Future = Npm.require('fibers/future');

 class AllSubmitDownloader
-    # Лицей 40
-    baseUrl: (page) ->
-        'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=3696&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=10&with_comment=&page=' + page + '&action=getHTMLTable'
-    # Заоч
-    #tableBaseUrl = 'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=3644&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=10&with_comment=&page=%d&action=getHTMLTable'
+
+    constructor: (@baseUrl, @userList, @limitPages) ->

     AC: 'Зачтено/Принято'
     IG: 'Проигнорировано'
@@ -21,7 +18,7 @@
             outcome = "IG"
         console.log uid, name, pid, runid, prob, date, "'"+outcome+"'"
         Submits.addSubmit(runid, date, uid, pid, outcome)
-        Users.addUser(uid, name, "lic40")
+        Users.addUser(uid, name, @userList)
         res

 #    if date > endDate:  # we do not need this, but continue search
@@ -69,7 +66,8 @@
             if not result
                 break
             page = page + 1
-        updateResults()
+            if page > @limitPages
+                break

 class LastSubmitDownloader extends AllSubmitDownloader
     needContinueFromSubmit: (runid) ->
@@ -93,6 +91,14 @@
                     continue
                 Results.addResult(user._id, problem._id, Submits.problemResult(user._id, problem))

+
+    # Лицей 40
+lic40url = (page) ->
+        'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=3696&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=10&with_comment=&page=' + page + '&action=getHTMLTable'
+    # Заоч
+zaochUrl = (page) ->
+    'http://informatics.mccme.ru/moodle/ajax/ajax.php?problem_id=0&group_id=3644&user_id=0&lang_id=-1&status_id=-1&statement_id=0&objectName=submits&count=10&with_comment=&page=' + page + '&action=getHTMLTable'
+

 SyncedCron.add
     name: 'loadTable-1',
@@ -100,7 +106,9 @@
 #        return parser.text('every 10 seconds');
         return parser.text('every 5 minutes');
     job: ->
-        (new UntilIgnoredSubmitDownloader()).run()
+        (new UntilIgnoredSubmitDownloader(lic40url, 'lic40', 30)).run()
+        (new UntilIgnoredSubmitDownloader(zaochUrl, 'zaoch', 30)).run()
+        updateResults()

 SyncedCron.add
     name: 'loadTable-2',
@@ -108,7 +116,9 @@
 #        return parser.text('every 10 seconds');
         return parser.text('every 1 minutes');
     job: ->
-        (new LastSubmitDownloader()).run()
+        (new LastSubmitDownloader(lic40url, 'lic40', 3)).run()
+        (new LastSubmitDownloader(zaochUrl, 'zaoch', 3)).run()
+        updateResults()

 SyncedCron.add
     name: 'loadTable-3',
@@ -116,7 +126,9 @@
 #        return parser.text('every 10 seconds');
         return parser.text('at 0:00');
     job: ->
-        (new AllSubmitDownloader()).run()
+        (new AllSubmitDownloader(lic40url, 'lic40', 1e9)).run()
+        (new AllSubmitDownloader(zaochUrl, 'zaoch', 1e9)).run()
+        updateResults()

 SyncedCron.start()
 #Meteor.startup ->


add blank lines

add blank lines

--- server/lib/db_remove_helper.coffee
+++ server/lib/db_remove_helper.coffee
@@ -2,12 +2,11 @@
 db = require('../helpers/db_connect_helper').db_connect()

 exports.remove = (doc, callback) =>
-    deletedDoc =
+    deletedDoc =
         "_rev": doc.rev
         "_deleted": true
-    if doc.docType?
+    if doc.docType?
         deletedDoc.docType = doc.docType
-    if doc.binary?.file?
+    if doc.binary?
         deletedDoc.binary = doc.binary
     db.save doc._id, deletedDoc, callback
-


Read messages working

Read messages working

--- server/methods/readMessages.coffee
+++ server/methods/readMessages.coffee
@@ -4,7 +4,7 @@
 		# console.log '[methods] readMessages -> '.green, 'fromId:', fromId, 'room:', room

 		if Meteor.userId()
-			filter = { rid: room, uid: Meteor.userId() }
+			filter = { rid: room, 'u._id': Meteor.userId() }
 		else
 			throw new Meteor.Error 203, '[methods] readMessages -> Invalid user'


flicker before and after fix

flicker before and after fix

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -15,15 +15,12 @@

   originalAlpha = I.alpha

-  before:
-    draw: (canvas) ->
-      I.flickerDuration = I.flickerDuration.approach(0, 1)
+  self.bind 'update', ->
+    I.flickerDuration = I.flickerDuration.approach(0, 1)

-      if (I.age % I.flickerFrequency == 0) && I.flickerDuration > 0
-        I.alpha = I.flickerAlpha
-
-  after:
-    draw: (canvas) ->
+    if (I.age % I.flickerFrequency == 0) && I.flickerDuration > 0
+      I.alpha = I.flickerAlpha
+    else
       I.alpha = originalAlpha

   ###*
@@ -54,3 +51,4 @@
     I.flickerDuration = duration
     I.flickerFrequency = frequency
     I.flickerAlpha = alpha
+


:cloud: fixed helpers.fs reference error

--- app/helpers/user.coffee
+++ app/helpers/user.coffee
@@ -53,7 +53,7 @@
       path = @getPath hash

       if path?
-        return fs.existsSync path
+        return helpers.fs.existsSync path
       else
         return false

@@ -78,7 +78,7 @@
       if @exists userhash
         callback new Error('blacksam.user.create.exists')
       else
-        fs.mkdirp @getPath(userhash), (err) ->
+        helpers.fs.mkdirp @getPath(userhash), (err) ->
           if err
             log.error "User Folder [#{userhash}] creation failed", err
           else
@@ -92,7 +92,7 @@
       if not userpath?
         return 'invalid'

-      for file in fs.readdirSync userpath
+      for file in helpers.fs.readdirSync userpath
         display_name = file.match(/^user\.(.+)\.json$/i)?[1]
         return display_name if display_name?

@@ -107,8 +107,8 @@

       userhash  = @validHash(userhash).from(2)

-      for file in helpers.fs.readdirSync(userpath) when display_name = file.match(/^user\.(.+)\.json$/i)
-        userjson = helpers.fs.readJSONSync "#{userpath}/#{file}"
+      for file in helpers.helpers.fs.readdirSync(userpath) when display_name = file.match(/^user\.(.+)\.json$/i)
+        userjson = helpers.helpers.fs.readJSONSync "#{userpath}/#{file}"

         if not userjson?
           continue


remove step from game object default events

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -129,7 +129,7 @@
     @methodOf GameObject#
     ###
     update: (elapsedTime) ->
-      #TODO Extract this I.active check out into an engine gameO
+      #TODO Extract this I.active check out into an engine gameObject remove processor or something
       if I.active
         self.trigger 'update', elapsedTime


Don't error with Node < 0.10

Don't error with Node < 0.10

--- src/extensions/model_stream.coffee
+++ src/extensions/model_stream.coffee
@@ -1,6 +1,6 @@
 # stream is large so it is optional on the browser
 try stream = require('stream') catch e
-(module.exports = null; return) unless stream
+(module.exports = null; return) unless stream?.Readable

 # @private
 module.exports = class ModelStream extends stream.Readable


Better CLJ triggering

Better CLJ triggering

--- scripts/clojure.coffee
+++ scripts/clojure.coffee
@@ -16,8 +16,8 @@
 ringSessionID = ''

 module.exports = (robot) ->
-  robot.hear /(clojure|clj)\s+(.*)/i, (msg)->
-    script = msg.match[2]
+  robot.hear /(\(.*\))/i, (msg)->
+    script = msg.match[1]

     msg.http("http://www.tryclj.com/eval.json")
       .query(expr: script)


Fix point tool controls offset when scaling image.

--- drawing-tools/pinpoint.coffee
+++ drawing-tools/pinpoint.coffee
@@ -21,8 +21,10 @@
   render: ->
     super

+    offset = @radius / @markingSurface.magnification
+
     @controls.moveTo
-      x: @mark.x + 30
+      x: @mark.x + offset
       y: @mark.y

 module.exports = Pinpoint


the right timeout

the right timeout

--- src/constants.iced
+++ src/constants.iced
@@ -4,7 +4,7 @@
     https : "https://dist.keybase.io/"
     http  : "http://dist.keybase.io/"
   canonical_host : "keybase.io"
-  index_timeout : 60*60*24*100 # 60*5
+  index_timeout : 60*5
   links :
     stable : 'stable'
     current : 'current'