donot check 'appInfo' when received command 'join'

Signed-off-by: Manson Li <f2a598d7717cc6757bef28b84a9ab02e5efc6048@matchstick.tv>

--- lib/dial/handler/FlingAppControlHandler.coffee
+++ lib/dial/handler/FlingAppControlHandler.coffee
@@ -98,79 +98,84 @@
             type = message?.type
             appInfo = message?.app_info

-            if ((type is "launch") or (type is "relaunch") or (type is "join")) and appInfo
-                # already has a token
-                token = req.headers["authorization"]
-                if token
-                    SessionManager.getInstance().touchSession token
-                else
-                    session = SessionManager.getInstance().createSenderSession appId
-                    token = session.getToken()
-
-                if appInfo and appInfo.url
-                    hostIp = req.connection.remoteAddress or
-                        req.socket.remoteAddress or
-                        req.connection.socket.remoteAddress
-                    appInfo.url = appInfo.url.replace "${REMOTE_ADDRESS}", hostIp
-
-                app = ApplicationManager.getInstance().getAliveApplication()
-                switch type
-                    when "launch"
-                        if app
-                            if app.getAppId() isnt appId
-                                Log.w "#{app.getAppId()} is interrupted, #{appId} will be launched"
-                                app.stop()
-                                @_doLaunch appId, appInfo
-                                statusCode = 201
-                            else
-                                Log.w "#{appId} is running, ignore launch"
-                                statusCode = 200
-                        else
+            if (type isnt "launch") and (type isnt "relaunch") and (type isnt "join")
+                Log.e "bad post request: type is #{type}"
+                @respondBadRequest req, res, "unsupport type"
+                return
+            if (type is "launch") or (type is "relaunch")
+                if not appInfo
+                    Log.e "bad post request: appInfo is null"
+                    @respondBadRequest req, res, "empty appInfo"
+                    return
+
+            token = req.headers["authorization"]
+            if token
+                SessionManager.getInstance().touchSession token
+            else
+                session = SessionManager.getInstance().createSenderSession appId
+                token = session.getToken()
+
+            if appInfo and appInfo.url
+                hostIp = req.connection.remoteAddress or
+                    req.socket.remoteAddress or
+                    req.connection.socket.remoteAddress
+                appInfo.url = appInfo.url.replace "${REMOTE_ADDRESS}", hostIp
+
+            app = ApplicationManager.getInstance().getAliveApplication()
+            switch type
+                when "launch"
+                    if app
+                        if app.getAppId() isnt appId
+                            Log.w "#{app.getAppId()} is interrupted, #{appId} will be launched"
+                            app.stop()
                             @_doLaunch appId, appInfo
                             statusCode = 201
-                    when "relaunch"
-                        if app
-                            if app.getAppId() is appId
-                                Log.w "#{appId} is interrupted, it will be relaunched"
-                                app.stop()
-                                @_doLaunch appId, appInfo
-                                statusCode = 201
-                            else
-                                Log.e "running app is #{app.getAppId()}, request appid is #{appId}, they are not matched!!!"
-                                @respondBadRequest req, res, "relaunch failed"
-                                return
                         else
-                            Log.e "no running app, cannot be relaunched!!!"
+                            Log.w "#{appId} is running, ignore launch"
+                            statusCode = 200
+                    else
+                        @_doLaunch appId, appInfo
+                        statusCode = 201
+                when "relaunch"
+                    if app
+                        if app.getAppId() is appId
+                            Log.w "#{appId} is interrupted, it will be relaunched"
+                            app.stop()
+                            @_doLaunch appId, appInfo
+                            statusCode = 201
+                        else
+                            Log.e "running app is #{app.getAppId()}, request appid is #{appId}, they are not matched!!!"
                             @respondBadRequest req, res, "relaunch failed"
                             return
-                    when "join"
-                        if app
-                            if app.getAppId() is appId
-                                statusCode = 200
-                            else
-                                Log.e "running app is #{app.getAppId()}, request appid is #{appId}, they are not matched!!!"
-                                @respondBadRequest req, res, "join failed"
-                                return
+                    else
+                        Log.e "no running app, cannot be relaunched!!!"
+                        @respondBadRequest req, res, "relaunch failed"
+                        return
+                when "join"
+                    if app
+                        if app.getAppId() is appId
+                            statusCode = 200
                         else
-                            Log.e "no running app, cannot join!!!"
+                            Log.e "running app is #{app.getAppId()}, request appid is #{appId}, they are not matched!!!"
                             @respondBadRequest req, res, "join failed"
                             return
-
-                body =
-                    token: token
-                    interval: Config.SENDER_SESSION_PP_INTERVAL
-                bodyContent = JSON.stringify body
-                Log.d "response body ->\n#{bodyContent}"
-                headers =
-                    "Connection": "keep-alive"
-                    "Cache-Control": "no-cache, must-revalidate, no-store"
-                    "Content-Type": "application/json"
-                    "Content-Length": bodyContent.length
-                @respond req, res, statusCode, headers, bodyContent
-                SessionManager.getInstance().sessionConnected session
-            else
-                Log.e "bad post request: type is #{type}, appInfo is #{JSON.stringify appInfo}"
-                @respondBadRequest req, res, "unsupport control"
+                    else
+                        Log.e "no running app, cannot join!!!"
+                        @respondBadRequest req, res, "join failed"
+                        return
+
+            body =
+                token: token
+                interval: Config.SENDER_SESSION_PP_INTERVAL
+            bodyContent = JSON.stringify body
+            Log.d "response body ->\n#{bodyContent}"
+            headers =
+                "Connection": "keep-alive"
+                "Cache-Control": "no-cache, must-revalidate, no-store"
+                "Content-Type": "application/json"
+                "Content-Length": bodyContent.length
+            @respond req, res, statusCode, headers, bodyContent
+            SessionManager.getInstance().sessionConnected session

     _onDelete: (req, res, appId) ->
         token = req.headers["authorization"]


FIX: Select first task that applies to filter

--- src/app/tasks/task-inbox-list/task-inbox-list.coffee
+++ src/app/tasks/task-inbox-list/task-inbox-list.coffee
@@ -64,14 +64,20 @@
       $scope.taskData.source.query { id: newUnitId, task_def_id: $scope.filters.taskDefinitionIdSelected },
         (response) ->
           $scope.tasks = $scope.unit.incorporateTasks(response)
-          # Load initial set task, either the first in the list of tasks or if
-          # provided (URL) selected task id then load actual task in now
-          task = findTaskForId($scope.taskData.temporaryTaskId) || _.first($scope.tasks)
-          $scope.setSelectedTask(task)
           # If loading via task definitions, fill
           if $scope.taskData == Unit.tasksForDefinition
             unstartedTasks = $scope.unit.fillWithUnStartedTasks($scope.tasks, $scope.selectedDefinition)
             _.extend($scope.tasks, unstartedTasks)
+          # Load initial set task, either the one provided (by the URL)
+          # then load actual task in now or the first task that applies
+          # to the given set of filters.
+          task = findTaskForId($scope.taskData.temporaryTaskId)
+          unless task?
+            filteredTasks = $filter('tasksOfTaskDefinition')($scope.tasks, $scope.filters.taskDefinition)
+            filteredTasks = $filter('tasksInTutorials')(filteredTasks, $scope.filters.tutorials)
+            filteredTasks = $filter('tasksWithStudentName')(filteredTasks, $scope.filters.studentName)
+            task = _.first(filteredTasks)
+          $scope.setSelectedTask(task)
           # For when URL has been manually changed, set the selected task
           # using new array of tasks loaded from the new temporaryTaskId
           listeners.push $scope.$watch 'taskData.temporaryTaskId', (newId, oldId) ->


Using All Mail folder as archive in Gmail

Summary: Archive folder does not exist in Gmail. Instead, use the all mail folder

Test Plan: manual

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://phab.nylas.com/D1891

--- src/flux/tasks/archive-thread-helper.coffee
+++ src/flux/tasks/archive-thread-helper.coffee
@@ -56,7 +56,7 @@
       else if direction is "unarchive"
         params.labelsToAdd = [currentLabel]

-      archiveLabel = CategoryStore.getStandardCategory("archive")
+      archiveLabel = CategoryStore.getStandardCategory("all")
       if archiveLabel
         if direction is "archive"
           params.labelsToAdd = [archiveLabel]


JSession::activeSession: query by clientId only

--- workers/social/lib/social/models/session.coffee
+++ workers/social/lib/social/models/session.coffee
@@ -62,15 +62,7 @@

   @activeSession = secure (client, callback) ->
-
-    username = client.connection?.delegate?.profile?.nickname
-    clientId = client.sessionToken
-
-    if not username or not clientId
-      return callback new KodingError 'Access denied'
-
-    @one { username, clientId }, callback
-
+    @one { clientId: client.sessionToken }, callback

   # TODO not sure why we are creating session only for guest user


landing: load font over HTTPS

landing: load font over HTTPS

--- workers/social/lib/social/render/landing.coffee
+++ workers/social/lib/social/render/landing.coffee
@@ -13,7 +13,7 @@
       <title>Koding</title>
       #{getStyles()}
       <link rel="stylesheet" href="/css/landingapp.#{KONFIG.version}.css" />
-      <link href="http://fonts.googleapis.com/css?family=Raleway:100,700" rel="stylesheet" type="text/css">
+      <link href="https://fonts.googleapis.com/css?family=Raleway:100,700" rel="stylesheet" type="text/css">
       #{getGraphMeta()}
     </head>
     <body class='koding landing'>


@props.src for key

@props.src for key

--- app/assets/javascripts/components/svg-image.cjsx
+++ app/assets/javascripts/components/svg-image.cjsx
@@ -12,11 +12,6 @@
   getInitialState: ->
     key: 0

-  componentWillReceiveProps: (new_props)->
-    newKey = @state.key + 1
-    @setState
-      key: newKey
-
   getDefaultProps: ->
     src: ''
     width: 0
@@ -24,4 +19,4 @@

   render: ->
     imageHTML = "<image xlink:href='#{@props.src}' width='#{@props.width}' height='#{@props.height}' />"
-    <g key={@state.key} className="svg-image-container" dangerouslySetInnerHTML={__html: imageHTML} {...@props} />
+    <g key={@props.src} className="svg-image-container" dangerouslySetInnerHTML={__html: imageHTML} {...@props} />


Add delay with selector example

Add delay with selector example

--- example_observables.coffee
+++ example_observables.coffee
@@ -80,3 +80,26 @@
           ]
       }
     ]
+  delayWithSelector:
+    description: "Every value takes progressively longer to process"
+    root:
+      type: 'interval', args: '500'
+    operators: [
+      {
+        type: 'delayWithSelector'
+        args: 'function(input) { return '
+        observable:
+          root:
+            type: 'timer', args: 'input * input * 500'
+          operators: [
+            {
+              type: 'timeout'
+              args: '3000'
+            }
+          ]
+      }
+      {
+        type: 'takeUntilWithTime'
+        args: '6000'
+      }
+    ]


Remove debug statement

Remove debug statement

--- src/nodes/class.coffee
+++ src/nodes/class.coffee
@@ -240,7 +240,6 @@
       if !@options.private && method.doc.status == "Private"
         return false
       else if !@options.internal && method.doc.status == "Internal"
-        console.log method.name
         return false
       else
         return true


tweak offset

tweak offset

--- src/components/task-plan/homework/add-exercises.cjsx
+++ src/components/task-plan/homework/add-exercises.cjsx
@@ -107,7 +107,7 @@
       else
         <ExerciseCards
           {...sharedProps}
-          topScrollOffset={120}
+          topScrollOffset={110}
           onShowDetailsViewClick={@onShowDetailsViewClick}
         />


put conditional contextModel logic back into controllers/routing

--- scripts/controllers/routing.coffee
+++ scripts/controllers/routing.coffee
@@ -86,9 +86,10 @@
             model = decodeURIComponent(model)
             model = allContent.get(model)

-            # Un-escape the `model.id` because a piece of content may have `/` in it (github uses these)
-            contextModel = decodeURIComponent(contextModel)
-            contextModel = allContent.get(contextModel)
+            if contextModel
+              # Un-escape the `model.id` because a piece of content may have `/` in it (github uses these)
+              contextModel = decodeURIComponent(contextModel)
+              contextModel = allContent.get(contextModel)

           # Redirect to workspace if model does not exist
           if not model
@@ -103,7 +104,7 @@
             @_currentContext = contextModel

             # this is needed right now to render the workspace
-            contextModel.set('selected', true)
+            contextModel?.set('selected', true)

             # Always show the workspace pane
             @_showWorkspacePane(SidebarView)
@@ -111,12 +112,23 @@
             # set more granular file selected flags to be used in ToC
             model.set('selected', true)

-            if !@layout.sidebar.currentView or @layout.sidebar.currentView.model != contextModel
-              contextView = new SidebarView
-                model: contextModel
+            # Force the sidebar if a contextModel is passed in
+            if contextModel
+              # Only change the view if there is nothing there or if the model differs
+              if !@layout.sidebar.currentView or @layout.sidebar.currentView.model != contextModel
+                contextView = new SidebarView
+                  model: contextModel

-              @layout.sidebar.show(contextView)
-              contextView.maximize()
+                @layout.sidebar.show(contextView)
+                contextView.maximize()
+            # Some models do not change the sidebar because they cannot contain children (like Module)
+            else if model.getChildren
+              # Only change the view if there is nothing there or if the model differs
+              if !@layout.sidebar.currentView or @layout.sidebar.currentView.model != model
+                modelView = new SidebarView
+                  model: model
+                @layout.sidebar.show(modelView)
+                modelView.maximize()

             model.contentView((view) => if view then @layout.content.show(view)) if model.contentView


rewritten in redux

rewritten in redux

--- imports/ui/pages/meteor/NewInstancePage.coffee
+++ imports/ui/pages/meteor/NewInstancePage.coffee
@@ -1,29 +1,67 @@
-{ createContainer } = require 'meteor/react-meteor-data'
+_           = require 'lodash'
+{ connect } = require 'react-redux'

-module.exports = createContainer (props) ->
-  App = props.route.App
-  query = props.location.query
-  StorageBuckets = App.collections.StorageBuckets
-
-  console.log props
-
-  App.subscribe.allApps()
-  App.subscribe.allStorageBuckets()
-
-  selectedAppName = props.params?.name or null
-  selectedAppVersion = props.params?.version or null
-
-  apps: (App.collections.Apps.find({}, sort: {name: -1, version: -1}).map (a) -> _id: a._id, name: a.name, version: a.version) or []
-  buckets: StorageBuckets.find({}, sort: name: 1).map (b) -> b.name
+mapStateToProps = (state, { params, location }) ->
+  query = location.query
+  selectedAppName = params.name or null
+  selectedAppVersion = params.version or null
+  apps: state.collections.apps?.map (a) -> _id: a._id, name: a.name, version: a.version or []
+  buckets: state.collections.buckets?.map (b) -> b.name
   name: query?.name
   bucket: query?.bucket
   appsearch: query?.appsearch
-  selectedApp: App.collections.Apps.findOne name: selectedAppName, version: selectedAppVersion
-  selectedAppName: props.params?.name or null
-  selectedAppVersion: props.params?.version or null
-  onAppSelected: (name, version) ->
-    App.emit 'NewInstancePage::state changed', name, version, {}
+  selectedApp: _.find state.collections.apps, (name: selectedAppName, version: selectedAppVersion)
+  selectedAppName: selectedAppName
+  selectedAppVersion: selectedAppVersion
+
+mapDispatchToProps = (dispatch) ->
   onStateChanged: (state) ->
-    App.emit 'NewInstancePage::state changed', selectedAppName, selectedAppVersion, state
+    dispatch Object.assign (type: 'START_APP_REQUEST'), state

-, require '../NewInstancePage.cjsx'
+mergeProps = (stateProps, dispatchProps, ownProps) ->
+  state =
+    app:
+      name: stateProps.selectedAppName
+      version: stateProps.selectedAppVersion
+    params:
+      name: stateProps.name
+      bucket: stateProps.bucket
+      appsearch: stateProps.appsearch
+
+  Object.assign {}, stateProps, dispatchProps, ownProps,
+    onStateChanged: (stateDiff) ->
+      dispatchProps.onStateChanged Object.assign {}, state, params: (Object.assign {}, state.params, stateDiff)
+    onAppSelected: (name, version) ->
+      dispatchProps.onStateChanged Object.assign {}, state, app: {name: name, version:version}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps) require '../NewInstancePage.cjsx'
+
+# { createContainer } = require 'meteor/react-meteor-data'
+#
+# module.exports = createContainer (props) ->
+#   App = props.route.App
+#   query = props.location.query
+#   StorageBuckets = App.collections.StorageBuckets
+#
+#   console.log props
+#
+#   App.subscribe.allApps()
+#   App.subscribe.allStorageBuckets()
+#
+#   selectedAppName = props.params?.name or null
+#   selectedAppVersion = props.params?.version or null
+#
+#   apps: (App.collections.Apps.find({}, sort: {name: -1, version: -1}).map (a) -> _id: a._id, name: a.name, version: a.version) or []
+#   buckets: StorageBuckets.find({}, sort: name: 1).map (b) -> b.name
+#   name: query?.name
+#   bucket: query?.bucket
+#   appsearch: query?.appsearch
+#   selectedApp: App.collections.Apps.findOne name: selectedAppName, version: selectedAppVersion
+#   selectedAppName: props.params?.name or null
+#   selectedAppVersion: props.params?.version or null
+#   onAppSelected: (name, version) ->
+#     App.emit 'NewInstancePage::state changed', name, version, {}
+#   onStateChanged: (state) ->
+#     App.emit 'NewInstancePage::state changed', selectedAppName, selectedAppVersion, state
+#
+# , require '../NewInstancePage.cjsx'


Bug fix in Show all field submit

Bug fix in Show all field submit

--- app/assets/javascripts/employee_search.js.coffee
+++ app/assets/javascripts/employee_search.js.coffee
@@ -2,40 +2,44 @@
   # Autocomplete on employee search
   queryEmployeeFields = ["#query-employee", "#masthead-q-employee"]
   items = 0
+  requestTerm = ""
   for queryEmployeeField in queryEmployeeFields
-    $queryEmployeeField = $(queryEmployeeField)
-    if $queryEmployeeField.length
-      $queryEmployeeField.autocomplete
-        source: (request, response) ->
-          $.ajax
-            url: $queryEmployeeField.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"
-            $queryEmployeeField.closest("form").submit()
-          else
-            document.location = ui.item.path
-      .data("ui-autocomplete")._renderItem = (ul, item) ->
-        # Create item for full search we reached 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)
+    do ->
+      $queryEmployeeField = $(queryEmployeeField)
+      if $queryEmployeeField.length
+        $queryEmployeeField.autocomplete
+          source: (request, response) ->
+            requestTerm = request.term
+            $.ajax
+              url: $queryEmployeeField.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) ->
+                    $.extend(item, { value: item.displayname })
+                    item
+          minLength: 2
+          select: (event, ui) ->
+            if ui.item.path is "full-search"
+              $queryEmployeeField.closest("form").submit()
+            else
+              document.location = ui.item.path
+        .data("ui-autocomplete")._renderItem = (ul, item) ->
+          # Create item for full search we reached the last item
+          $more = ""
+          if items is ul.find("li").length + 1
+            $more = $("
Visa all träffar
")
+              .data("ui-autocomplete-item", { path: "full-search", value: requestTerm})
+          ul.addClass('search_users')
+          $("
")
+            .data("ui-autocomplete-item", item)
+            .append("
+
#{item.displayname}

+                #{item.company_short}

+                #{item.department}

+            ")
+            .appendTo(ul).after($more)


Update test in case of SSH port 22 not open

--- test/NetworkTest.coffee
+++ test/NetworkTest.coffee
@@ -65,9 +65,13 @@
 		next()
 	'Test netListenAddress': (next) ->
 		s = sigar()
-		netListenAddress = s.netListenAddress 22
-		# console.log 'netListenAddress', netListenAddress
-		assert.eql netListenAddress, '0.0.0.0'
+		try
+			netListenAddress = s.netListenAddress 22
+			# console.log 'netListenAddress', netListenAddress
+			assert.eql netListenAddress, '0.0.0.0'
+		catch e
+			# SSH port 22 not open
+			assert.eql e.message, 'sigar_net_listen_address_get error: 2 (No such file or directory)'
 		next()
 	'Test netStat': (next) ->
 		s = sigar()


Stop Atom highlighting `INSTALL` files as "shell"

Seriously?

--- config.cson
+++ config.cson
@@ -3,10 +3,6 @@
 		showSpacingInStatusBar: false
 	core:
 		customFileTypes:
-			"source.ditroff.desc": [
-				"/dev[-a-z0-9]+/[A-Z]+$"
-				"/dev\\w+/(?!DESC|NOTES)[A-Z]+$"
-			]
 			"source.gfm": [
 				"README"
 				"TODO"
@@ -22,6 +18,9 @@
 			]
 			"source.shell": [
 				"profile"
+			]
+			"text.plain": [
+				"install"
 			]
 			"text.roff": [
 				"mm"


Fix analytics mapping.

Fix analytics mapping.

--- server/commons/mapping.coffee
+++ server/commons/mapping.coffee
@@ -34,7 +34,7 @@

 module.exports.handlerUrlOverrides =
   'analytics_log_event': 'analytics_log_event'
-  'analytics_perday': 'analytics.perday'
+  'analytics_perday': 'analytics_perday'
   'analytics_string': 'analytics.string'
   'analytics_stripe_invoice': 'analytics.stripe.invoice'
   'level_component': 'level.component'


if the last person on the team, delete the team. Finishes #59

--- models/person.coffee
+++ models/person.coffee
@@ -87,8 +87,11 @@
   @team (err, team) ->
     return next err if err
     if team
-      team.peopleIds = _.reject team.peopleIds, (id) -> id.equals(myId)
-      team.save next
+      if team.peopleIds.length is 1
+        team.remove next
+      else
+        team.peopleIds = _.reject team.peopleIds, (id) -> id.equals(myId)
+        team.save next
     else
       next()


Rewrote Braintree JS to be more concise.

--- app/assets/javascripts/spree/braintree.js.coffee
+++ app/assets/javascripts/spree/braintree.js.coffee
@@ -1,20 +1,22 @@
-braintreeName = (name) ->
-  "input[data-braintree-name=#{name}]"
+$ = jQuery

-$ ->
-  $('form#checkout_form_payment').submit (event) ->
-    if $(this).data('submitting')
-      true
-    else
-      braintreeClient = new braintree.api.Client clientToken: braintree.clientToken
-      card =
-        number: $(braintreeName "number").val()
-        expirationDate: $(braintreeName "expiration_date").val().replace(/\s/g, "")
-        cvv: $(braintreeName "cvv").val()
-        cardholderName: $(braintreeName "cardholder_name").val()
+cc_fld = (scope, name) -> scope.find "input[data-braintree-name=#{name}]"
+cc_val = (scope, name) -> cc_fld(scope, name).val()

-      braintreeClient.tokenizeCard card, (err, nonce) =>
-        $('input[data-braintree-name=payment_method_nonce]').attr('value', nonce)
-        $(this).submit()
-      $(this).data('submitting', true)
-      false
+$(document).on 'submit', 'form#checkout_form_payment', (event) ->
+  $_ = $ @
+  return if $_.data 'submitting'
+
+  processor = new braintree.api.Client clientToken: braintree.clientToken
+  card =
+    number: cc_val $_, "number"
+    expirationDate: cc_val($_, "expiration_date").replace /\s/g, ""
+    cvv: cc_val $_, "cvv"
+    cardholderName: cc_val $_, "cardholder_name"
+
+  processor.tokenizeCard card, (err, nonce) ->
+    cc_fld($_, 'payment_method_nonce').val nonce
+    $_.submit()
+
+  $_.data 'submitting', true
+  event.preventDefault()


do not show webconsole help if already seen

--- plugins/webconsole/app/assets/javascripts/webconsole/global/webconsole_container.coffee
+++ plugins/webconsole/app/assets/javascripts/webconsole/global/webconsole_container.coffee
@@ -1,4 +1,8 @@
 class @WebconsoleContainer
+  unless Storage is "undefined"
+    # save information from local storage in class variable.
+    webconsoleHelpSeen = localStorage.getItem("webconsoleHelpSeen")
+
   defaults =
     toolbarCssClass:    'toolbar'
     buttonsCssClass:    'main-buttons'
@@ -35,7 +39,6 @@

         # create and add each button to buttons container
         for button, i in settings.buttons
-          console.log button
           $buttons.append("")

     # add webconsole holder to container
@@ -51,7 +54,10 @@

       # create a container div for help text and show it
       $helpContent = $("

").appendTo($helpContainer)
-      $helpContainer.animate({width:'toggle'},'400px')
+
+      # open help container unless already seen
+      $helpContainer.animate({width:'toggle'},'400px') unless webconsoleHelpSeen
+
       # set help button to active
       $('[data-trigger="webconsole:help"]').addClass('active')

@@ -59,6 +65,8 @@
       $("").prependTo($helpContainer).click (e) ->
         $helpContainer.animate({width:'toggle'},'400px')
         $('[data-trigger="webconsole:help"]').toggleClass('active')
+        # save information already seen in local storage
+        localStorage.setItem("webconsoleHelpSeen",true)

       # set height
@@ -109,7 +117,7 @@
     }
     $.ajax( options );

-  @init= (containerSelector, settings={}) ->
+  @init= (containerSelector, settings={}) ->
     @$container = $(containerSelector)
     @settings   = $.extend {}, defaults, @$container.data(), settings
     @$holder    = createDomStructure(@$container, @settings)
@@ -163,20 +171,17 @@

   @open= (callback) ->
-    console.log 'open'
     # Open console container
     @$container.parent().slideDown 'slow', () ->
       WebconsoleContainer.load()
       callback() if callback

   @close= (callback) ->
-    console.log 'close'
     @$container.parent().slideUp 'slow', () ->
       console.log('Webconsole closed')
       callback() if callback

   @toogleFullscreen= (callback) ->
-    console.log 'toogle fullscreen'
     $parentContainer = @$container.parent()

     new_width = $parentContainer.data('width') || $( window ).width()
@@ -191,12 +196,10 @@
     })

   @reload= () ->
-    console.log 'reload'
     @load(true)

   @load= (reload=false) ->
     if @loaded && reload==false
-      console.log 'alredy loaded'
       return

     # bind this to self
@@ -242,6 +245,9 @@
                   $helpContainer.html(context.help_html)

                 $loadingHint.remove()
+
+                # hterm deletes all values in local storage (don't know why). So save it again.
+                localStorage.setItem("webconsoleHelpSeen",true) if (Storage isnt "undefined") and webconsoleHelpSeen

               self.loaded = true
             error: (xhr, bleep, error) -> console.log('error: ' + error)


Things work

Things work

--- core.coffee
+++ core.coffee
@@ -36,13 +36,16 @@
 			, (err, response, body) ->
 				items = JSON.parse(body).items
 				async.each items, (item, callback) ->
-					repo = new db.Repository
-						fullName: item.full_name
-						stars: item.stargazers_count
-						forks: item.forks_count
-					repo.save (err, repo) ->
+					db.Repository.findOneAndUpdate {fullName: item.full_name},
+						$setOnInsert:
+							fullName: item.full_name
+						$set:
+							stars: item.stargazers_count
+							forks: item.forks_count
+					, upsert: true, new: true
+					, (err, repo) ->
 						fetchRepoLanguages repo
-						fetchRepoCommits repo
+						# fetchRepoCommits repo
 						callback()
 				, ->
 					console.log "Saved #{items.length} repositories of Top Repositories from page #{page}"
@@ -81,7 +84,7 @@
 							exports.fetchCommit repo, commit

 getUserOrCreateUser = (username, callback) ->
-	db.User.findOneAndUpdate {username: username}, {$setOnInsert: username: username}, upsert: true, (err, user) ->
+	db.User.findOneAndUpdate {username: username}, {$setOnInsert: username: username}, upsert: true, new: true, (err, user) ->
 		callback err, user

 fetchCommit = (repo, commit) ->
@@ -97,8 +100,11 @@

 exports.startJobs = ->
-	do doTask = ->
+	intervalDescriptor = null
+	doTask = ->
 		console.log "#{searchJobs.length} Search Jobs and #{apiJobs.length} API Jobs in queue"
+		clearInterval intervalDescriptor if searchJobs.length is 0 and apiJobs.length is 0
 		searchJobs.shift()?() for i in [0 ... globals.searchRequestsPerMinute]
 		apiJobs.shift()?() for i in [0 ... globals.apiRequestsPerMinute]
-	setInterval doTask, 1000 * 60 # Runs every minute
+	intervalDescriptor = setInterval doTask, 1000 * 60 # Runs every minute
+	doTask()


Add appserver5 for reprovisionning through ChatOps

--- vagrant/provision/roles/hubot/files/ninjabot/scripts/update.coffee
+++ vagrant/provision/roles/hubot/files/ninjabot/scripts/update.coffee
@@ -23,7 +23,7 @@
           process.exit(0)
         , 5 * 1000

-  robot.respond /update host (monitoring|cinode|cirepo|db|appserver1|appserver2|appserver3|appserver4|)/i, (msg) ->
+  robot.respond /update host (monitoring|cinode|cirepo|db|appserver1|appserver2|appserver3|appserver4|appserver5)/i, (msg) ->
     user = robot.brain.userForId(msg.envelope.user.id, null)
     unless robot.auth.hasRole(user, "admin")
       msg.send ":grin: Access denied. You must have 'admin' role to use this command"
@@ -54,6 +54,9 @@
       when 'appserver4'
         server='app-server-node-4'
         command="sh ./provision.sh --local --limit=#{server} micro_appserver.yml"
+      when 'appserver5'
+        server='app-server-node-5'
+        command="sh ./provision.sh --local --limit=#{server} micro_appserver.yml"

     msg.send "Reprovisioning host with the latest on Github..."


Changing the place where the loader appears

Signed-off-by: Renato Carvalho <3a567acd0cc20231ba7f35c2bad7076ae1d7999b@gmail.com>

--- app/assets/javascripts/_image_uploader.coffee
+++ app/assets/javascripts/_image_uploader.coffee
@@ -1,5 +1,6 @@
 class @ImageUploader
   constructor: (@container) ->
+    @preview = @container.find('[data-provides="fileupload"]')
     @fileInput = @container.find('input[type="file"]')
     @idInput = @container.find('[data-item="id"]')
     @container.find('a.btn.fileupload-exists').bind('ajax:success', @delete)
@@ -11,8 +12,8 @@
       done: @done

   add: (e, data) =>
-    @loader = $('

').hide()
-    @container.append(@loader.fadeIn())
+    @loader = $('

').hide()
+    @preview.prepend(@loader.fadeIn())
     data.submit()

   progress: (e, data) =>


Fixes typo.

Fixes typo.

--- app/assets/javascripts/app/lib/auth.js.coffee
+++ app/assets/javascripts/app/lib/auth.js.coffee
@@ -66,7 +66,7 @@

         # refresh/load default collections
         for key, value of data.default_collections
-          App.Collection.rest( type: key, data: value )
+          App.Collection.reset( type: key, data: value )

         # rebuild navbar with new navbar items
         Spine.trigger 'navrebuild', data.session


Activity: sidebar pinned post avatars made smaller

--- client/Social/Activity/sidebar/pinneditem.coffee
+++ client/Social/Activity/sidebar/pinneditem.coffee
@@ -7,7 +7,7 @@
     {slug}           = data
     options.route    = "Post/#{slug}"
     options.type     = 'member'
-    options.cssClass = 'kdlistitemview-sidebar-item'
+    options.cssClass = 'kdlistitemview-sidebar-item conversation'

     super options, data

@@ -18,7 +18,7 @@
       constructorName : data.account.constructorName

     @avatar = new AvatarStaticView {
-      size       : width : 30, height : 30
+      size       : width : 24, height : 24
       cssClass   : "avatarview"
       showStatus : yes
       origin


Draging network graph is replaced by vertical scrolling.

--- app/assets/javascripts/branch-graph.js.coffee
+++ app/assets/javascripts/branch-graph.js.coffee
@@ -122,22 +122,11 @@
   bindEvents: ->
     drag = {}
     element = @element
-    dragger = (event) ->
-      element.scrollLeft drag.sl - (event.clientX - drag.x)
-      element.scrollTop drag.st - (event.clientY - drag.y)
-
-    element.on mousedown: (event) ->
-      drag =
-        x: event.clientX
-        y: event.clientY
-        st: element.scrollTop()
-        sl: element.scrollLeft()
-      $(window).on "mousemove", dragger
+
+    $(element).scroll (event) =>
+      @renderPartialGraph()

     $(window).on
-      mouseup: =>
-        $(window).off "mousemove", dragger
-        @renderPartialGraph()
       keydown: (event) =>
         # left
         element.scrollLeft element.scrollLeft() - 50  if event.keyCode is 37


Fix Page test broken by new factories

Fix Page test broken by new factories

--- server/test/units/page.coffee
+++ server/test/units/page.coffee
@@ -103,7 +103,7 @@
 test('get "fat" page with no related children by page ID', (done) ->
   Page = require('../../models/page.coffee').model

-  helpers.createSection((err, section) ->
+  helpers.createSection(indicator: undefined, (err, section) ->
     helpers.createPage({sections: [section]}).then(
       (page) ->
         Page.findFatModel(page._id, (err, fatPage) ->


adding class earlier

adding class earlier

--- app/client/views/families/familyPhotos.coffee
+++ app/client/views/families/familyPhotos.coffee
@@ -2,6 +2,7 @@
 familyPhotoUploader = null

 Template.familyPhotos.created = ->
+    $('body').addClass("photoBody")
     if not familyPhotoUploader?
         familyPhotoUploader = new PhotoUploadHandler
             serverUploadMethod: "insertFamilyPhoto"
@@ -19,7 +20,7 @@

 Template.familyPhotos.rendered = ->
     $('body').addClass("photoBody")
-
+
     familyPhotoUploader.setOptions
         serverUploadOptions:
             familyId: Session.get('currentRecordId')


bring back recordsLoaded boolean on bucket page

--- app/components/bucket-page/bucket-page.coffee
+++ app/components/bucket-page/bucket-page.coffee
@@ -18,6 +18,7 @@
         $scope.membership = Records.memberships.find(groupId: $scope.group.id, memberId: CurrentUser().id)[0]
         Records.contributions.fetchByBucketId(bucketId)
         Records.comments.fetchByBucketId(bucketId)
+        $scope.recordsLoaded = true
       else
         console.log('user can not view bucket')


debugs off in sound sync system

debugs off in sound sync system

--- src/javascript/view/systems/sound_sync_system.coffee
+++ src/javascript/view/systems/sound_sync_system.coffee
@@ -28,13 +28,13 @@
         sound.seekMillis(playPosition)
         sound.play()
         sound._sidecar.paused = false
-        console.log "sound_sync_system: seek/play"
+        # console.log "sound_sync_system: seek/play"

     else
       if playPosition > 0 && playPosition == sound._sidecar.playPosition
         sound.pause()
         sound._sidecar.paused = true
-        console.log "sound_sync_system: pause",playPosition, sound._sidecar.playPosition
+        # console.log "sound_sync_system: pause",playPosition, sound._sidecar.playPosition
       else
         sound._sidecar.playPosition = playPosition


Update de-DE.coffee

Update de-DE.coffee

Translated new lines.

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -151,9 +151,9 @@
     campaign_multiplayer: "Mehrspieler-Abenteuer"
     campaign_multiplayer_description: "... in der du Kopf-an-Kopf gegen andere Spieler programmierst."
     brain_pop_done: "Programmierter Sieg über die Oger!"
-#    brain_pop_challenge: "Challenge yourself to play again using a different programming language!"
-#    replay: "Replay"
-#    back_to_classroom: "Back to Classroom"
+    brain_pop_challenge: "Fordere dich selbst heraus und spiele noch einmal mit einer anderen Programmiersprache!"
+    replay: "Neu spielen"
+    back_to_classroom: "Zurück zum Klassenraum"

   code:
     if: "wenn" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -631,9 +631,9 @@
     purchase: "Kauf"
     purchased: "Gekauft"

-#  earn_gems:
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Keep playing to earn more!"
+  earn_gems:
+    prompt_title: "Zuwenig Edelsteine"
+    prompt_body: "Spiele weiter und verdiene mehr!"

   subscribe:
     subscribe_modal_title: "CodeCombat Premium"
@@ -683,7 +683,7 @@
     will_be_cancelled: "Läuft ab am"
     currently_free: "Du hast aktuell ein kostenloses Abonnement"
     currently_free_until: "Du hast aktuell ein kostenloses Abonnement bis zum"
-#    free_subscription: "Free subscription"
+    free_subscription: "Kostenloses Abonnement"
     was_free_until: "Du hattest ein kostenloses Abonnement bis zum"
     managed_subs: "Verwaltete Abonnements"
     subscribing: "Abonniere..."
@@ -1320,7 +1320,7 @@
     update_account_students_can3: "Sich in Arenen mit Mitschülern messen"
     update_account_students_can4: "Zugriff auf aktuelle Scülerfunktionen erhalten, sobald wir sie veröffentlichen."
     update_account_students_warning: "Warnung: Du wirst keine Möglichkeit mehr haben, zuvor von dir erstellte Klassen zu verwalten oder neue zu erstellen."
-#    unsubscribe_warning: "Warning: You will be unsubscribed from your monthly subscription."
+    unsubscribe_warning: "Warnung: Dein Monats-Abonnement wird beendet werden."
     update_account_remain_student: "Schüler bleiben"
     update_account_update_student: "In Schülerkonto umwandeln"
     need_a_class_code: "Du brauchst einen Klassencode für die Klasse, der du beitrittst:"


Create flag for light theme.

Create flag for light theme.

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -156,6 +156,7 @@
 					sourceMapBasepath: "public/stylesheets"
 					globalVars:
 						'is-overleaf': false
+						'is-overleaf-light': false
 						'show-rich-text': Settings.showRichText
 				files:
 					"public/stylesheets/style.css": "public/stylesheets/style.less"
@@ -166,6 +167,7 @@
 					sourceMapBasepath: "public/stylesheets"
 					globalVars:
 						'is-overleaf': true
+						'is-overleaf-light': false
 						'show-rich-text': Settings.showRichText
 				files:
 					"public/stylesheets/ol-style.css": "public/stylesheets/ol-style.less"
@@ -177,6 +179,7 @@
 					sourceMapBasepath: "public/stylesheets"
 					globalVars:
 						'is-overleaf': true
+						'is-overleaf-light': true
 						'show-rich-text': Settings.showRichText
 				files:
 					"public/stylesheets/ol-light-style.css": "public/stylesheets/ol-light-style.less"


Fixed Pt-Br

Fixed Pt-Br

Fixed resolve conflict results and indent error in Pt-Br

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -1629,8 +1629,8 @@
     gd3_curriculum: "Desenvolvimento de Jogos 3 - Guia de Currículo"
     gd3_curriculum_desc: "Planos de aula para desenvolvimento de jogos 3."
     wd1: "Desenvolvimento de Web 1"
-#    wd1_headlines: "Headlines & Headers Activity"
-#    wd1_headlines_example: "View sample solution"
+    wd1_headlines: "Atividade de Título & Cabeçalho"
+    wd1_headlines_example: "Visualizar exemplo"
     wd1_headlines_desc: "Por que as tags de parágrafo e cabeçalho são importantes? Use essa atividade para mostrar como os cabeçalhos bem escolhidos facilitam a leitura das páginas da Web. Existem muitas soluções corretas para isso!"
     wd1_html_syntax: "Guia de Sintaxe HTML"
     wd1_html_syntax_desc: "Referência de página em HTML para os alunos que irão fazer o curso Desenvolvimento de Web 1."
@@ -1643,8 +1643,8 @@
     wd2_quizlet_worksheet_instructions: "Veja instruções & exemplos"
     wd2_quizlet_worksheet_desc: "Antes que seus alunos construam seu projeto de teste de personalidade no final do curso Desenvolviemtno de Web 2, eles devem planejar suas perguntas, resultados e respostas do questionário usando esta planilha. Os professores podem distribuir as instruções e exemplos para os alunos se preferirem."
     student_overview: "Visão Geral"
-#    student_details: "Student Details"
-#    student_name: "Student Name"
+    student_details: "Detalhes do Aluno"
+    student_name: "Nome do Aluno"
     no_name: "Nenhum nome fornecido."
     no_username: "Nenhum nome de usuário fornecido."
     no_email: "aluno não tem endereço de e-mail."
@@ -1664,14 +1664,14 @@
     student_warn_detail: "Este aluno pode precisar de alguma ajuda com novos conceitos que foram introduzidos neste curso."
     student_great: "está indo muito bem em"
     student_great_detail: "Este aluno pode ser um bom candidato para ajudar outros alunos que trabalham neste curso."
-#    full_license: "Full License"
-#    starter_license: "Starter License"
-#    trial: "Trial"
+    full_license: "Licença Completa"
+    starter_license: "Licença de Iniciante"
+    trial: "Teste"
     hoc_welcome: "Feliz Semana da Educação em Ciência da Computação"
     hoc_intro: "Existem três maneiras de sua classe participar da Hora do Código com o CodeCombat"
     hoc_self_led: "Jogo autônomo"
     hoc_self_led_desc: "Os alunos podem jogar através de dois tutoriais Hour of Code CodeCombat por conta própria"
-   hoc_game_dev: "Desenvolvimento de Jogos"
+    hoc_game_dev: "Desenvolvimento de Jogos"
     hoc_and: "e"
     hoc_programming: "Programação JavaScript/Python"
     hoc_teacher_led: "Lições do Professor"


Better error handling

Better error handling

--- emma.coffee
+++ emma.coffee
@@ -35,17 +35,27 @@
       http.get(url.parse(targetURL), (imageRes) ->
         console.log('HEADERS: ' + JSON.stringify(imageRes.headers))

-        res.writeHead(imageRes.statusCode,
-          'Date': imageRes.headers['date'],
-          'Last-Modified': imageRes.headers['last-modified'],
-          'ETag': imageRes.headers['etag'],
-          'Content-Type': 'image/jpeg'
-        )
-
         if imageRes.statusCode == 200
           image = gm(imageRes, fileName)
           nsConfig.processImage(image)
-          image.stream((err, stdout, stderr) -> stdout.pipe(res))
+
+          image.stream((err, stdout, stderr) ->
+            if err
+              console.log("processing error: #{err}")
+              res.writeHead(500, {
+                'Date': new Date().toUTCString(),
+                'Content-Type': 'text/plain'
+              })
+              res.end(err.message)
+
+            else
+              res.writeHead(imageRes.statusCode,
+                'Date': imageRes.headers['date'],
+                'Last-Modified': imageRes.headers['last-modified'],
+                'ETag': imageRes.headers['etag'],
+                'Content-Type': 'image/jpeg'
+              )
+              stdout.pipe(res))

         else
           # res.setEncoding('utf8')
@@ -56,7 +66,10 @@
       ).on('error', (err) ->
         console.log("problem with request: #{err.message}")
         res.writeHead(500)
-        res.end(err.message)
+        res.end(err.message, {
+          'Date': new Date().toUTCString(),
+          'Content-Type': 'text/plain'
+        })
       )

     else


Only open devtools window if DEVTOOLS equals '1'

--- app/metal/window.coffee
+++ app/metal/window.coffee
@@ -36,7 +36,7 @@

   main_window.loadUrl "file://#{__dirname}/../index.html"

-  if process.env.DEVTOOLS
+  if process.env.DEVTOOLS == "1"
     main_window.openDevTools()

 install = ->


Update more meaningful log msg

Update more meaningful log msg

--- app/models/Level.coffee
+++ app/models/Level.coffee
@@ -101,7 +101,7 @@

   checkIfDone: =>
     if @mapLoaded and (@soundsLoaded or !mediator.playWithSounds)
-      console.log "load level completed" if debug
+      console.log "Finished loading #{@manifest.map.file}" if debug
       @loadCompleted = true
       @_callback() if @_callback


revert back to standard passport session removal

--- server/authentication/index.coffee
+++ server/authentication/index.coffee
@@ -184,8 +184,7 @@

   logout = (req, res) ->
     req.logout()
-    req.session.destroy (err) ->
-      res.send 200
+    res.send 200

   #Configure Passport authentication strategies
   users = app.models.users


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -1,16 +1,16 @@
 module "Flickerable"

 context '#flicker', ->
-test 'it should set up defaults', ->
-  player = GameObject()
-
-  player.include Flickerable
+  test 'it should set up defaults', ->
+    player = GameObject()

-  player.flicker()
-
-  equals player.I.flickerAlpha, 0.5
-  equals player.I.flickerFrequency, 3
-  equals player.I.flickerDuration, 30
+    player.include Flickerable
+
+    player.flicker()
+
+    equals player.I.flickerAlpha, 0.5
+    equals player.I.flickerFrequency, 3
+    equals player.I.flickerDuration, 30

 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject


typos

typos

--- samples/bbcshots.coffee
+++ samples/bbcshots.coffee
@@ -27,7 +27,7 @@

 # Building resulting page and image
 buildPage = ->
-    this.echo 'Build result page'
+    @echo 'Build result page'
     fs = require 'fs'
     @viewport 624, 400
     pageHtml = ""
@@ -36,8 +36,8 @@
     pageHtml += ""
     fs.write 'result.html', pageHtml, 'w'
     @thenOpen "file://#{fs.workingDirectory}/result.html", ->
-        this.echo 'Resulting image saved to result.png'
-        this.capture 'result.png'
+        @echo 'Resulting image saved to result.png'
+        @capture 'result.png'

 # Capture carrousel area
 next = ->
@@ -48,10 +48,10 @@
     if currentLink < nbLinks
         @click ".carousel_itemList_li[rel='#{currentLink}']"
         @wait 1000, ->
-            this.then next
+            @then next
             currentLink++
     else
-        this.then buildPage
+        @then buildPage

 casper.then next


Fix prev commit ಠ_ಠ.

Fix prev commit ಠ_ಠ.

--- src/chaplin/lib/route.coffee
+++ src/chaplin/lib/route.coffee
@@ -45,7 +45,7 @@
       # Save parameter name
       @paramNames.push paramName
       # Replace with a character class
-      '([\\w.-%]+)'
+      '([\\w.\-%]+)'

     # Test if the route matches to a path (called by Backbone.History#loadUrl)
     test: (path) ->


Add utils.getAllPrototypeVersions.

Add utils.getAllPrototypeVersions.

--- src/chaplin/lib/utils.coffee
+++ src/chaplin/lib/utils.coffee
@@ -44,6 +44,18 @@
       chain = [object]
       chain.push object while object = object.constructor?.__super__
       chain
+
+    # Get all property versions from object’s prototype chain.
+    # E.g. if object1 & object2 have `prop` and object2 inherits from
+    # object1, it will get [object1prop, object2prop].
+    getAllPropertyVersions: (object, property) ->
+      _(utils.getPrototypeChain object)
+        .chain()
+        .pluck(property)
+        .compact()
+        .uniq()
+        .value()
+        .reverse()

     # Function Helpers
     # ----------------


Add up.macro to the docs

Add up.macro to the docs

--- lib/assets/javascripts/unpoly/syntax.js.coffee
+++ lib/assets/javascripts/unpoly/syntax.js.coffee
@@ -239,6 +239,7 @@

   Examples for built-in macros are [`up-dash`](/up-dash) and [`up-expand`](/up-expand).

+  @function up.macro
   @param {String} selector
     The selector to match.
   @param {Object} options


Yet another regression; fixed. Re: #9

Yet another regression; fixed. Re: #9

--- servers/api/middleware/cors.coffee
+++ servers/api/middleware/cors.coffee
@@ -1,15 +1,15 @@
 nconf = require("nconf")
-wwwUrl = nconf.get('url:www')

 module.exports.middleware = (config = {}) ->

+  valid = [nconf.get('url:www'), nconf.get('url:embed')]

   (req, res, next) ->
     # Just send the headers all the time. That way we won't miss the right request ;-)
     # Other CORS middleware just wouldn't work for me
     # TODO: Minimize these headers to only those needed at the right time

-    res.header("Access-Control-Allow-Origin", wwwUrl)
+    res.header("Access-Control-Allow-Origin", if req.headers.origin in valid then req.headers.origin else "*")
     res.header("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE")
     res.header("Access-Control-Allow-Headers", "Authorization, User-Agent, Referer, X-Requested-With, Proxy-Authorization, Proxy-Connection, Accept-Language, Accept-Encoding, Accept-Charset, Connection, Content-Length, Host, Origin, Pragma, Accept-Charset, Cache-Control, Accept, Content-Type")
     res.header("Access-Control-Expose-Headers", "Link")


remove remember session config

remove remember session config

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -1,13 +1,6 @@
 'global':
   'welcome':
     'showOnStartup': false
-  'remember-session':
-    'x': 0
-    'y': 64
-    'width': 1280
-    'height': 960
-    'path': '/home/danh/code/play/clojure-noob'
-    'treeWidth': 200
   'editor':
     'fontFamily': 'Consolas, Inconsolata, Monaco, Courier New, Courier'
     'fontSize': 14


Add back setting avatar background image to avatar url

--- src/packages/collaboration/lib/participant-view.coffee
+++ src/packages/collaboration/lib/participant-view.coffee
@@ -20,7 +20,9 @@
     @video.click => @toggleClass('large')
     @avatar.click => @toggleClass('large')

-    @video.css('background-image', "url('#{participant.getState().avatar_url}')")
+    avatarUrl = participant.getState().avatar_url
+    @avatar.css('background-image', "url('#{avatarUrl}')")
+    @video.css('background-image', "url('#{avatarUrl}')")

     @removeButton.click @onClickRemove
     @toggleVideoButton.click @onClickToggleVideo
@@ -38,6 +40,7 @@

   onClickRemove: =>
     false
+
   onClickToggleVideo: =>
     @toggleVideoButton.toggleClass('disabled')
     @toggleClass('hide-video')


Use active editor from root view instead of ivar

--- src/packages/jump-to-line/lib/jump-to-line-view.coffee
+++ src/packages/jump-to-line/lib/jump-to-line-view.coffee
@@ -48,5 +48,5 @@
   attach: ->
     @previouslyFocusedElement = $(':focus')
     @rootView.append(this)
-    @message.text("Enter a line number 1-#{@editor.getLineCount()}")
+    @message.text("Enter a line number 1-#{@rootView.getActiveEditor().getLineCount()}")
     @miniEditor.focus()


Fix number passed to Phone obj

Fix number passed to Phone obj

--- src/coffee/vtex-phone.coffee
+++ src/coffee/vtex-phone.coffee
@@ -51,7 +51,7 @@
 			result = countryObj.specialRules(originalNumber, withoutCountryCode,
 												withoutNDC, nationalDestinationCode)

-			phoneNumber = new PhoneNumber(countryCode, nationalDestinationCode, number, originalNumber)
+			phoneNumber = new PhoneNumber(countryCode, nationalDestinationCode, withoutNDC, originalNumber)
 			if result
 				phoneNumber.valid(true)
 				return phoneNumber


Fills in closing an open expression feature

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -108,11 +108,12 @@
     class_mixer(OpenSubExpressionManipulation)

-    class CloseOpenSubExpressionManipulation
-      invoke: (expression)->
-        _ImplicitMultiplication.build().
-          onNumeric(expression).
-          append(comps.expression.build().open())
+    class CloseSubExpressionManipulation
+      invoke: (expression)->
+        last = expression.last()
+        expression.replaceLast(last.close())
+    class_mixer(CloseSubExpressionManipulation)
+

     class LeftParenthesisExpressionManipulation
       invoke: (expression)->
@@ -206,6 +207,7 @@
       subtraction: SubtractionExpressionManipulation
       negate_last: NegationExpressionManipulation
       open_sub_expression: OpenSubExpressionManipulation
+      close_sub_expression: CloseSubExpressionManipulation
       left_parenthesis: LeftParenthesisExpressionManipulation
       right_parenthesis: RightParenthesisExpressionManipulation
       division: DivisionExpressionManipulation


removed unneeded semicolon

removed unneeded semicolon

--- core/app/backbone/collections/subchannels.coffee
+++ core/app/backbone/collections/subchannels.coffee
@@ -4,4 +4,4 @@
   initialize: (models, opts) ->
     @channel = opts.channel

-  url: -> @channel.url() + '/subchannels';
+  url: -> @channel.url() + '/subchannels'


Make bug #xxx also work

Make bug #xxx also work

--- scripts/bugzilla.coffee
+++ scripts/bugzilla.coffee
@@ -22,7 +22,7 @@
   makePath = (bug) ->
     '/bugzilla/show_bug.cgi?id=' + bug

-  robot.respond /bug (\d+)/i, (msg) ->
+  robot.respond /bug #?(\d+)/i, (msg) ->
     bug = msg.match[1]

     params =


Changed search behavior. @mrflix would be cool to combine those somehow and prioritize better matching ones?

--- app/assets/javascripts/app/lib/app_post/searchable_select.js.coffee
+++ app/assets/javascripts/app/lib/app_post/searchable_select.js.coffee
@@ -103,7 +103,7 @@
     @filterByQuery query

   filterByQuery: (query) ->
-    regex = new RegExp(query.split('').join('.*'), 'i')
+    regex = new RegExp(query.split(' ').join('.*'), 'i')

     @values
       .addClass 'is-hidden'


File storage used persistence

File storage used persistence

--- src/persistence.coffee
+++ src/persistence.coffee
@@ -421,16 +421,43 @@
       ).callback callback

-  # Remove comments when deleting teamMedium
+  # Reload team to get "storage" used.
+  wrapMethod sdk, 'uploadTeamMedium', (uploadTeamMedium) ->
+    (teamMedium, progressCallback, callback) ->
+      uploadTeamMedium.call(this, teamMedium, progressCallback).then((result) ->
+        sdk.loadTeam(teamMedium.teamId).then ->
+          result
+      ).callback callback
+
+  # Remove comments when deleting teamMedium, reload team (for file usage quota)
   wrapMethod sdk, 'deleteTeamMedium', (deleteTeamMedium) ->
     (teamMedium, callback) ->
       toRemove = teamMedium.teamMediumComments.slice()

       linking.unlinkItems toRemove, lookup
-      deleteTeamMedium.call(this, teamMedium).fail((err) ->
-        linking.linkItems toRemove, lookup
-        err
-      ).callback callback
+      deleteTeamMedium.call(this, teamMedium).then((result) ->
+        sdk.loadTeam(teamMedium.teamId).then ->
+          result
+      ,(err) ->
+        linking.linkItems toRemove, lookup
+        err
+      ).callback callback
+
+
+  # Note: For the purposes of persistence, this method will accept a teamId,
+  # even though it isn't accepted in the actual method.
+  wrapMethod sdk, 'bulkDeleteTeamMedia', (bulkDeleteTeamMedia) ->
+    (teamMediumIds, teamId, callback) ->
+      if typeof teamId is 'function'
+        callback = teamId
+
+      bulkDeleteTeamMedia.call(this, teamMediumIds, callback).then((result) ->
+        if typeof teamId is 'string' or typeof teamId is 'number'
+          sdk.loadTeam(teamId).then ->
+            result
+        else
+          result
+      )

   # Remove all records belonging to a team when it is deleted


Fix number type

Fix number type

--- view/coffee/creator/create_diagram.coffee
+++ view/coffee/creator/create_diagram.coffee
@@ -60,7 +60,7 @@
         now = _.clone(start)
         times = while (now.isBefore(end) or now.equals(end)) and (now.isAfter(start) or now.equals(start))
           result = now.fourDigit()
-          now.addMinutes(@pattern.period)
+          now.addMinutes(parseInt(@pattern.period))
           result
         @starts += times.join(', ')
       addStop: (idx) ->


Only use XDomain IE transport when ajax requests are made by Firehose

--- lib/assets/javascripts/firehose/long_poll.js.coffee
+++ lib/assets/javascripts/firehose/long_poll.js.coffee
@@ -39,6 +39,7 @@
     #       See: http://api.jquery.com/jQuery.ajax/#jqXHR
     @_lastRequest = $.ajax
       url:          @config.longPoll.url
+      firehose:     true
       crossDomain:  true
       data:         data
       timeout:      @_timeout
@@ -77,6 +78,7 @@
       url:          @config.longPoll.url
       method:       'HEAD'
       crossDomain:  true
+      firehose:     true
       data:         @config.params
       success:      =>
         if @_needToNotifyOfReconnect
@@ -94,3 +96,35 @@
       setTimeout @_ping, @_retryDelay + @_lagTime
       # Reconnect with delay
       setTimeout @_request, @_retryDelay
+
+# Let's try to hack in support for IE8-9 via the XDomainRequest object!
+# This was adapted from code shamelessly stolen from:
+# https://github.com/jaubourg/ajaxHooks/blob/master/src/ajax/xdr.js
+if $?.browser?.msie and parseInt($.browser.version, 10) in [8, 9]
+  jQuery.ajaxTransport (s) ->
+    if s.crossDomain and s.async and s.firehose
+      if s.timeout
+        s.xdrTimeout = s.timeout
+        delete s.timeout
+      xdr = undefined
+      return {
+        send: (_, complete) ->
+          callback = (status, statusText, responses, responseHeaders) ->
+            xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop
+            xdr = undefined
+            complete status, statusText, responses, responseHeaders
+          xdr = new XDomainRequest()
+          xdr.open s.type, s.url
+          xdr.onload = ->
+            headers = "Content-Type: #{xdr.contentType}"
+            callback 200, "OK", {text: xdr.responseText}, headers
+          xdr.onerror = -> callback 404, "Not Found"
+          if s.xdrTimeout?
+            xdr.ontimeout = -> callback 0, "timeout"
+            xdr.timeout   = s.xdrTimeout
+          xdr.send (s.hasContent and s.data) or null
+        abort: ->
+          if xdr?
+            xdr.onerror = jQuery.noop()
+            xdr.abort()
+      }


Fix multiline code when there is text after closing ```

--- packages/rocketchat-highlight/highlight.coffee
+++ packages/rocketchat-highlight/highlight.coffee
@@ -21,11 +21,11 @@
 					message.msg = message.msg + "\n```"

 				# Separate text in code blocks and non code blocks
-				msgParts = message.html.split(/(```\w*[\n\ ]?[\s\S]*?```+?)$/)
+				msgParts = message.html.split(/^\s*(```(?:[a-zA-Z]+)?(?:(?:.|\n)*?)```)(?:\n)?$/gm)

 				for part, index in msgParts
 					# Verify if this part is code
-					codeMatch = part.match(/```(\w*[\n\ ]?)([\s\S]*?)```+?$/)
+					codeMatch = part.match(/^```(\w*[\n\ ]?)([\s\S]*?)```+?$/)
 					if codeMatch?
 						# Process highlight if this part is code
 						singleLine = codeMatch[0].indexOf('\n') is -1


Removed some duplication in unit tests

Removed some duplication in unit tests

--- test/xml2js.test.coffee
+++ test/xml2js.test.coffee
@@ -14,29 +14,23 @@
       x2js.parseString data

 module.exports =
-  'test default parse': (test) ->
-    x2js = new xml2js.Parser()
-    x2js.addListener 'end', (r) ->
-      console.log 'Result object: ' + sys.inspect(r, false, 10)
-      assert.equal r['chartest']['@']['desc'], 'Test for CHARs'
-      assert.equal r['chartest']['#'], 'Character data here!'
-      assert.equal r['cdatatest']['@']['desc'], 'Test for CDATA'
-      assert.equal r['cdatatest']['@']['misc'], 'true'
-      assert.equal r['cdatatest']['#'], 'CDATA here!'
-      assert.equal r['nochartest']['@']['desc'], 'No data'
-      assert.equal r['nochartest']['@']['misc'], 'false'
-      assert.equal r['listtest']['item'][0]['#'], 'This is character data!'
-      assert.equal r['listtest']['item'][0]['subitem'][0], 'Foo(1)'
-      assert.equal r['listtest']['item'][0]['subitem'][1], 'Foo(2)'
-      assert.equal r['listtest']['item'][0]['subitem'][2], 'Foo(3)'
-      assert.equal r['listtest']['item'][0]['subitem'][3], 'Foo(4)'
-      assert.equal r['listtest']['item'][1], 'Qux.'
-      assert.equal r['listtest']['item'][2], 'Quux.'
-      test.finish()
+  'test default parse': skeleton(undefined, (r) ->
+    console.log 'Result object: ' + sys.inspect(r, false, 10)
+    assert.equal r['chartest']['@']['desc'], 'Test for CHARs'
+    assert.equal r['chartest']['#'], 'Character data here!'
+    assert.equal r['cdatatest']['@']['desc'], 'Test for CDATA'
+    assert.equal r['cdatatest']['@']['misc'], 'true'
+    assert.equal r['cdatatest']['#'], 'CDATA here!'
+    assert.equal r['nochartest']['@']['desc'], 'No data'
+    assert.equal r['nochartest']['@']['misc'], 'false'
+    assert.equal r['listtest']['item'][0]['#'], 'This is character data!'
+    assert.equal r['listtest']['item'][0]['subitem'][0], 'Foo(1)'
+    assert.equal r['listtest']['item'][0]['subitem'][1], 'Foo(2)'
+    assert.equal r['listtest']['item'][0]['subitem'][2], 'Foo(3)'
+    assert.equal r['listtest']['item'][0]['subitem'][3], 'Foo(4)'
+    assert.equal r['listtest']['item'][1], 'Qux.'
+    assert.equal r['listtest']['item'][2], 'Quux.')

-    fs.readFile __dirname + '/fixtures/sample.xml', (err, data) ->
-      x2js.parseString data
-
   'test parse EXPLICIT_CHARKEY': (test) ->
     x2js = new xml2js.Parser()
     x2js.EXPLICIT_CHARKEY = true


treat single counters as stacks when dropping (ask how many)

--- dragAndDrop.coffee
+++ dragAndDrop.coffee
@@ -1,7 +1,8 @@
 # XXX: this is kind of hacky, refactor
 CBGA.ui.PanelContainerContoller::doMoveComponent = (component, owner, oldContainer) ->
   console.log 'move component, meteoric way'
-  if component instanceof CBGA.ui.ComponentStack
+  type = @rules.getComponentType component.type
+  if type.isCounter
     IonPopup.prompt
       title: 'Move stack'
       template: 'How many?'
@@ -13,9 +14,9 @@
         else
           count = Number count
         selector = type: component.type
-        if component.stack?
-          selector[component.uiType.stackProperty] = component.stack
-        component.container.find selector, limit: count
+        if type.stackProperty?
+          selector[type.stackProperty] = component[type.stackProperty]
+        component.container().find selector, limit: count
         .forEach (eachComponent) =>
           eachComponent.moveTo @getContainer owner
           oldContainer?.componentRemoved?(eachComponent)


fixup: render style for Object.

fixup: render style for Object.

--- src/prim-object.coffee
+++ src/prim-object.coffee
@@ -10,7 +10,7 @@
 	samples: (specdict) ->
 		repeat dict list map(([k, v]) -> [k, sample v]) enumerate(specdict)
 	htmlInline: (specdict) ->
-		"{...}"
+		"{...}"
 	htmlNode: (specdict) ->
 		lis = map(([k, v]) ->
 			node = htmlNode v


Revert "fs.readFile.bind fs.readFile"

Revert "fs.readFile.bind fs.readFile"

This reverts commit 575c76cc2ad842ea35c4384aa27ffe4428b36506.

--- tests/integrated.coffee
+++ tests/integrated.coffee
@@ -14,17 +14,17 @@
     before (done) ->
       targetPromises = [
         q.nfcall(
-          fs.readFile.bind(fs.readFile),
+          fs.readFile,
           "./tests/data/single/source.css"
         ).then(
           (css) -> right.file = removeMapFile(css.toString "utf-8").trim()
         )
         q.nfcall(
-          fs.readFile.bind fs.readFile,
+          fs.readFile,
           "./tests/data/single/source.css.map"
         ).then(
           (map) ->
-            right.sourcemap = JSON.parse map
+            right.sourcemap = JSON.parse(map.toString "utf-8")
             right.sourcemap.sources = right.sourcemap.sources.map(
               (file) -> path.relative(
                 process.cwd(), path.resolve "./tests/data/single", file
@@ -58,14 +58,8 @@

       compilationDefer.promise.then(
         -> q.all([
-          q.nfcall(
-            fs.readFile.bind fs.readFile,
-            "./tests/results/single/source.css"
-          )
-          q.nfcall(
-            fs.readFile.bind fs.readFile,
-            "./tests/results/single/source.css.map"
-          )
+          q.nfcall fs.readFile, "./tests/results/single/source.css"
+          q.nfcall fs.readFile, "./tests/results/single/source.css.map"
         ]).then(
           (data) ->
             expect(
@@ -86,7 +80,7 @@
       promises = []
       targetFiles.forEach (file) ->
         promises.push q.nfcall(
-          fs.readFile.bind fs.readFile,
+          fs.readFile,
           path.join("./tests/data/multiple", file)
         ).then(
           (data) ->
@@ -98,7 +92,7 @@
             ).trim()
         )
         promises.push q.nfcall(
-          fs.readFile.bind fs.readFile,
+          fs.readFile,
           path.join("./tests/data/multiple", file + ".map")
         ).then(
           (data) ->
@@ -140,7 +134,7 @@
           promises = []
           files.forEach (file) ->
             promises.push q.nfcall(
-              fs.readFile.bind fs.readFile, file
+              fs.readFile, file
             ).then(
               (data) ->
                 expect(
@@ -150,7 +144,7 @@
                 )
             )
             promises.push q.nfcall(
-              fs.readFile.bind fs.readFile, file + ".map"
+              fs.readFile, file + ".map"
             ).then((data) -> expect(JSON.parse data).eql right[file].map)
           q.all(promises).done (-> done()), done
       )
@@ -163,7 +157,7 @@
         result_path = path.join("tests/results/glob", folder, "source.css")
         promises.push(
           q.nfcall(
-            fs.readFile.bind fs.readFile,
+            fs.readFile,
             path.join("./tests/data/glob", folder, "source.css")
           ).then(
             (data) ->
@@ -174,7 +168,7 @@
               ).trim()
           )
           q.nfcall(
-            fs.readFile.bind fs.readFile,
+            fs.readFile,
             path.join("./tests/data/glob", folder, "source.css.map")
           ).then(
             (data) ->
@@ -219,7 +213,7 @@
               "source.css"
             )
             promises.push q.nfcall(
-              fs.readFile.bind fs.readFile,
+              fs.readFile,
               resultPath
             ).then(
               (data) ->
@@ -228,7 +222,7 @@
                 ).equal right[resultPath].content
             )
             promises.push q.nfcall(
-              fs.readFile.bind fs.readFile,
+              fs.readFile,
               resultPath + ".map"
             ).then(
               (map) ->
@@ -245,7 +239,7 @@
     beforeEach (done) ->
       promises = [
         q.nfcall(
-          fs.readFile.bind fs.readFile,
+          fs.readFile,
           "./tests/data/imports/main.css"
         ).then(
           (data) -> right.content = removeMapFile(
@@ -253,7 +247,7 @@
           ).trim()
         )
         q.nfcall(
-          fs.readFile.bind fs.readFile,
+          fs.readFile,
           "./tests/data/imports/main.css.map"
         ).then(
           (data) ->
@@ -285,7 +279,7 @@
         ->
           promises = []
           promises.push q.nfcall(
-            fs.readFile.bind fs.readFile,
+            fs.readFile,
             "./tests/results/imports/main.css"
           ).then(
             (data) -> expect(
@@ -293,7 +287,7 @@
             ).equal right.content
           )
           promises.push q.nfcall(
-            fs.readFile.bind fs.readFile,
+            fs.readFile,
             "./tests/results/imports/main.css.map"
           ).then((data) -> expect(JSON.parse data).eql right.map)
           q.all promises


Show Shabbat netz times on week schedule

--- source/javascripts/index/weekTable.js.coffee
+++ source/javascripts/index/weekTable.js.coffee
@@ -1,5 +1,7 @@
 #= require ./arbit
 #= require ../site/hebrewDateExtensions
+
+linkToShabbatOnScreen = (content) -> "#{content}#{content}"

 class WeekTable
   constructor: (gregorianWeek, hebrewWeek, zmanimWeek, shaharitWeek, minchaWeek, events) ->
@@ -48,10 +50,10 @@
     hodu = (time.format("h:mm") for time in shaharit.hodu())
     switch hodu.length
       when 2
-        korbanot = "#{korbanot[0]} and #{korbanot[1]}"
-        hodu = "#{hodu[0]} and #{hodu[1]}"
+        korbanot = "#{linkToShabbatOnScreen(korbanot[0])} and #{korbanot[1]}"
+        hodu = "#{linkToShabbatOnScreen(hodu[0])} and #{hodu[1]}"
         yishtabach = ""
-        amidah = "#{shaharit.amidah().format("h:mm:ss")}"
+        amidah = "#{linkToShabbatOnScreen(shaharit.amidah().format("h:mm:ss"))}"
       when 1
         korbanot = korbanot[0]
         hodu = hodu[0]


\\\\ should be \\

\\\\ should be \\

--- tests/integrated.coffee
+++ tests/integrated.coffee
@@ -32,7 +32,7 @@
             right.sourcemap.sources = right.sourcemap.sources.map(
               (file) -> path.relative(
                 process.cwd(), path.resolve "./tests/data/single", file
-              ).replace /\\\\/g, "/"
+              ).replace /\\/g, "/"
             )
         )
       ]


originTransform should be based on SVG size instead of parent size

--- framer/SVGBaseLayer.coffee
+++ framer/SVGBaseLayer.coffee
@@ -35,9 +35,9 @@
 		when "originY" then sizeProp = "height"
 	return value unless sizeProp?
 	layerSize = layer[sizeProp]
-	parentSize = layer.parent[sizeProp]
-	return value unless layerSize >= 0 and parentSize > 0
-	return (layerSize / parentSize) * value
+	svgSize = layer._svgSize[sizeProp]
+	return value unless layerSize >= 0 and svgSize > 0
+	return (layerSize / svgSize) * value

 class exports.SVGBaseLayer extends Layer
 	# Overridden Layer properties
@@ -117,6 +117,10 @@
 			@_stylesAppliedToParent = ["webkitTransform", "webkitTransformOrigin"]
 			for prop in ["x", "y", "z", "scaleX", "scaleY", "scaleZ", "scale", "skewX", "skewY", "skew", "rotationX", "rotationY", "rotationZ", "force2d", "originX", "originY"]
 				options[prop] ?= @_parent[prop]
+		svgLayer = @_parent
+		while svgLayer? and not (svgLayer instanceof SVGLayer)
+			svgLayer = svgLayer.parent
+		@_svgSize = svgLayer.size

 		pathProperties = ["fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset", "name", "opacity"]
 		_.defaults options, @constructor.attributesFromElement(pathProperties, element)


Do not generate keys for unencrypted streams

--- rtmp.coffee
+++ rtmp.coffee
@@ -1064,18 +1064,18 @@
         @useEncryption = true
         console.log "[rtmp] using encryption"

-      @clientPublicKey  = keys.clientPublicKey
-      @dh = keys.dh
-      @sharedSecret = @dh.computeSecret @clientPublicKey
-      @keyOut = codecUtils.calcHmac(@dh.getPublicKey(), @sharedSecret)[0..15]
-      @keyIn = codecUtils.calcHmac(@clientPublicKey, @sharedSecret)[0..15]
-
-      @cipherOut = crypto.createCipheriv 'rc4', @keyOut, ''
-      @cipherIn  = crypto.createCipheriv 'rc4', @keyIn, ''
-      zeroBytes = new Buffer 1536
-      zeroBytes.fill 0
-      @encrypt zeroBytes
-      @decrypt zeroBytes
+        @clientPublicKey  = keys.clientPublicKey
+        @dh = keys.dh
+        @sharedSecret = @dh.computeSecret @clientPublicKey
+        @keyOut = codecUtils.calcHmac(@dh.getPublicKey(), @sharedSecret)[0..15]
+        @keyIn = codecUtils.calcHmac(@clientPublicKey, @sharedSecret)[0..15]
+
+        @cipherOut = crypto.createCipheriv 'rc4', @keyOut, ''
+        @cipherIn  = crypto.createCipheriv 'rc4', @keyIn, ''
+        zeroBytes = new Buffer 1536
+        zeroBytes.fill 0
+        @encrypt zeroBytes
+        @decrypt zeroBytes

       callback null, s0s1s2


URL switch for tutorial

URL switch for tutorial

--- scripts/main.coffee
+++ scripts/main.coffee
@@ -55,8 +55,7 @@
 	el: $('section[data-page="classify"]')
 	steps: tutorialSteps

-loggedInUserAlreadyDidTheTutorial = true
-unless loggedInUserAlreadyDidTheTutorial? then tutorial.start()
+unless ~location.search.indexOf 'notut' then tutorial.start()

 {delay} = require 'util'
 delay -> $('html').removeClass 'pre-load'


Offset calculations treat negative offsets as 0

--- src/trix/core/utilities/utf16_string.coffee
+++ src/trix/core/utilities/utf16_string.coffee
@@ -16,10 +16,10 @@
     @ucs2Length = @ucs2String.length

   offsetToUCS2Offset: (offset) ->
-    ucs2encode(@codepoints.slice(0, offset)).length
+    ucs2encode(@codepoints.slice(0, Math.max(0, offset))).length

   offsetFromUCS2Offset: (ucs2Offset) ->
-    ucs2decode(@ucs2String.slice(0, ucs2Offset)).length
+    ucs2decode(@ucs2String.slice(0, Math.max(0, ucs2Offset))).length

   slice: ->
     @constructor.fromCodepoints(@codepoints.slice(arguments...))


:shirt: Fix linter warning

:shirt: Fix linter warning

--- spec/marker-layer-spec.coffee
+++ spec/marker-layer-spec.coffee
@@ -115,7 +115,7 @@
         expect(marker2ChangeCount).toBe(1)

       createdMarker = null
-      layer3.onDidCreateMarker((m) => createdMarker = m)
+      layer3.onDidCreateMarker((m) -> createdMarker = m)
       buffer.undo()

       expect(buffer.getText()).toBe 'foo'


:new: Add specs for main module

:new: Add specs for main module

--- spec/package-deps-spec.coffee
+++ spec/package-deps-spec.coffee
@@ -1,6 +1,6 @@
 describe 'Package-Deps', ->

-  PackageDeps = require('../lib/main')
+  window.__steelbrain_package_deps = new Set()

   describe '::packagesToInstall', ->
     it 'works', ->
@@ -15,7 +15,45 @@
         else
           return null
       )
-      retVal = PackageDeps.packagesToInstall('wow')
+      Helpers = require('../lib/helpers')
+      retVal = Helpers.packagesToInstall('wow')
       expect(retVal.toEnable).toEqual(['linter'])
       expect(retVal.toInstall).toEqual(['atom-hack'])

+  describe '::install', ->
+    it 'works', ->
+      spyOn(atom.packages, 'enablePackage').andCallFake(->)
+      spyOn(atom.packages, 'activatePackage').andCallFake(->)
+
+      # View spies
+      View = require('../lib/view')
+      view = {
+        show: jasmine.createSpy('view.show')
+        advance: jasmine.createSpy('view.advance')
+      }
+      spyOn(View, 'View').andReturn(view)
+
+      # Install spies
+      Helpers = require('../lib/helpers')
+      spyOn(Helpers, 'installPackages').andCallFake (dependencies, successCallback) ->
+        expect(dependencies).toEqual(['linter', 'linter-ruby'])
+        dependencies.forEach (name) ->
+          successCallback(name, true)
+          atom.packages.activatePackage(name)
+        return Promise.resolve()
+      spyOn(Helpers, 'packagesToInstall').andReturn({
+        toEnable: ['pigments'],
+        toInstall: ['linter', 'linter-ruby']
+      })
+
+      # Main spies
+      PackageDeps = require('../lib/main')
+
+      waitsForPromise ->
+        PackageDeps.install('some-package', true).then ->
+          expect(atom.packages.enablePackage).toHaveBeenCalled()
+          expect(atom.packages.enablePackage.callCount).toBe(1)
+          expect(atom.packages.activatePackage).toHaveBeenCalled()
+          expect(atom.packages.activatePackage.callCount).toBe(3)
+          expect(view.show.callCount).toBe(1)
+          expect(view.advance.callCount).toBe(2)


Default to 30 second timeout on promises

--- spec/preview-list-spec.coffee
+++ spec/preview-list-spec.coffee
@@ -1,4 +1,7 @@
 {RootView} = require 'atom'
+
+# Default to 30 second promises
+waitsForPromise = (fn) -> window.waitsForPromise timeout: 30000, fn

 describe 'PreviewList', ->
   [projectFindView, previewList] = []
@@ -15,7 +18,7 @@

   describe "when list is scrollable", ->
     it "adds more operations to the DOM when `scrollBottom` nears the `pixelOverdraw`", ->
-      waitsForPromise timeout: 30000, ->
+      waitsForPromise ->
         projectFindView.findEditor.setText(' ')
         projectFindView.confirm()


Add a waitsFor when checking for visibility

--- spec/symbols-view-spec.coffee
+++ spec/symbols-view-spec.coffee
@@ -359,9 +359,9 @@
       waitsForPromise ->
         activationPromise

-      runs ->
-        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
-        expect(symbolsView.loading).toBeVisible()
+      waitsFor ->
+        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
+        symbolsView?.loading.is(":visible")

       waitsFor ->
         symbolsView.list.children('li').length > 0
@@ -467,8 +467,10 @@
         atom.commands.dispatch(getEditorView(), "symbols-view:toggle-file-symbols")
         atom.workspace.getActiveTextEditor().setGrammar(atom.grammars.grammarForScopeName('source.js'))
         atom.commands.dispatch(getEditorView(), "symbols-view:toggle-file-symbols")
-        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
-        expect(symbolsView.loading).toBeVisible()
+
+      waitsFor ->
+        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
+        symbolsView?.loading.is(":visible")

       waitsFor ->
         symbolsView.list.children('li').length is 1


fix OEmbed afterSaveMessage callback to return the message

--- packages/rocketchat-oembed/server/server.coffee
+++ packages/rocketchat-oembed/server/server.coffee
@@ -168,4 +168,6 @@
 		if changed is true
 			ChatMessage.update {_id: message._id}, { $set: { urls: message.urls } }

+	return message
+
 RocketChat.callbacks.add 'afterSaveMessage', OEmbed.RocketUrlParser, RocketChat.callbacks.priority.LOW


Roles will correctly semicolon-delimit the list of roles if one contains a comma.

--- src/scripts/roles.coffee
+++ src/scripts/roles.coffee
@@ -16,6 +16,7 @@
     "Be more specific, I know #{users.length} people named like that: #{(user.name for user in users).join(", ")}"

   robot.respond /who is @?([\w .-]+)\?*$/i, (msg) ->
+    joiner = ', '
     name = msg.match[1].trim()

     if name is "you"
@@ -28,7 +29,9 @@
         user = users[0]
         user.roles = user.roles or [ ]
         if user.roles.length > 0
-          msg.send "#{name} is #{user.roles.join(", ")}."
+          if user.roles.join('').search(',') > -1
+            joiner = '; '
+          msg.send "#{name} is #{user.roles.join(joiner)}."
         else
           msg.send "#{name} is nothing to me."
       else if users.length > 1


[fix] ##queues naming

[fix] ##queues naming

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -294,7 +294,7 @@
     await @redis.keys "bull:*:id", ideally defer queues
     for queue in queues
       name = queue[5..-4]
-      await @stateCounts name, ideally defer allCounts[queue]
+      await @stateCounts name, ideally defer allCounts[name]

     callback null, allCounts


Handle server-crash error scenario properly

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -4,7 +4,7 @@
       .done (data, status, xhr) ->
         go null, data
       .fail (xhr, status, error) ->
-        message = xhr.responseJSON.errmsg or 'Unknown error'
+        message = xhr.responseJSON?.errmsg or 'Unknown error'
         go new Flow.Error message, new Flow.Error "Error calling GET #{path}"

   doPost = (path, opts, go) ->
@@ -12,8 +12,9 @@
       .done (data, status, xhr) ->
         go null, data
       .fail (xhr, status, error) ->
-        message = xhr.responseJSON.errmsg or 'Unknown error'
+        message = xhr.responseJSON?.errmsg or 'Unknown error'
         go new Flow.Error message, new Flow.Error "Error calling POST #{path} with opts #{JSON.stringify opts}"
+
   mapWithKey = (obj, f) ->
     result = []
     for key, value of obj


Added filter for files listed in index.jade.

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -39,7 +39,7 @@
             throw err if err
             res.render 'index.jade',
                 browsers : browsers.browsers,
-                files : files
+                files : files.filter((file) -> /\.html$/.test(file)).sort()

     server.get '/browsers/:browserid', (req, res) ->
         # TODO: permissions checking and making sure browserid exists would
@@ -67,15 +67,6 @@
             console.log "browsers.create failed"
             console.log e
             send500Error(res)
-
-    server.get '/:source.html', (req, res) ->
-        sessionID = req.sessionID
-        target = URL.parse req.params.source
-        if target.host == undefined
-            target = URL.parse "http://localhost:3001/#{req.params.source}.html"
-        console.log "VirtualBrowser will load: #{target.href}"
-        browsers.create sessionID, target
-        res.render 'base.jade', browserid: sessionID

     send500Error = (res) ->
         res.writeHead 500, {'Content-type': 'text/html'}


Remove spaces

Remove spaces

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -700,7 +700,7 @@
     str = @expectString(args[0]).toLowerCase()
     str.replace PROPER_RE, (m0, m1, m2)->
       return String(m1)+m2.toUpperCase()
-
+
   PERMUT: (args)->
     @checkArgumentSize(args, 2)
     n = @expectInteger(args[0])


debug view methods to behave correctly before vm.event is available

--- src/app/events/eventDetail.controller.coffee
+++ src/app/events/eventDetail.controller.coffee
@@ -66,7 +66,7 @@
     .compact().value()

   vm.lookupByClass = (event, className, idOrArray)->
-    if !event
+    if !(event?.id?) || !idOrArray
       return if _.isArray idOrArray then [] else false
     if _.isArray idOrArray
       check = _.chain( idOrArray )
@@ -80,25 +80,25 @@

   vm.acl = {
     isVisitor: ()->
-      return true if !$rootScope.user
+      return true if _.isEmpty $rootScope.user
       return !vm.acl.isParticipant()

     isParticipant: ()->
-      return false if !$rootScope.user
+      return false if _.isEmpty $rootScope.user
       participantIds = _.pluck vm.lookup['Participations'], 'participantId'
       return true if ~participantIds.indexOf($rootScope.user.id)
       return true if vm.acl.isOwner()
       return false

     isContributor: ()->
-      return false if !$rootScope.user
+      return false if _.isEmpty $rootScope.user
       return true if _.pluck(vm.lookup['Contributions'],'contributorId')
       .indexOf($rootScope.user.id) > -1
       # return vm.event?.contributorIds?.indexOf($rootScope.user.id) > -1
       return false

     isOwner: ()->
-      return false if !$rootScope.user
+      return false if _.isEmpty $rootScope.user
       return vm.event?.ownerId == $rootScope.user.id
   }

@@ -138,6 +138,8 @@

       next = i + 1
       if values[next] == 'contribute'
+        if !vm.event.summary # not ready
+          return vm.on.menuView(next, peek) # skip
         if vm.acl.isParticipant() == false
           return vm.on.menuView(next, peek) # skip
         if vm.event.summary.myParticipation.isFullyParticipating


coffee test

coffee test

--- spec/javascripts/dummy_spec.coffee
+++ spec/javascripts/dummy_spec.coffee
@@ -1,9 +1,50 @@
-describe 'CoffeeScript suite', ->
-  beforeEach ->
-    jasmine.getFixtures().load 'player.html'
-    $('.no-1').foundationPlayer()
-    no1 = $('.no-1').data 'FoundationPlayer'
-
-  it 'it has CoffeeScript support:)', ->
-    expect(true).toBe true
-    expect(false).not.toBe true
+# describe 'CoffeeScript suite', ->
+#   beforeEach ->
+#     jasmine.getFixtures().load 'player.html'
+#     $('.no-1').foundationPlayer()
+#     no1 = $('.no-1').data 'FoundationPlayer'
+#     no1
+#
+#   it 'it has CoffeeScript support:)', ->
+#     expect(true).toBe true
+#     expect(false).not.toBe true
+#
+#   it 'player has default size', ->
+#     expect(no1.currentUISize).toEqual no1.options.size
+#
+#   it 'play() works', ->
+#     player.play()
+#     expect(no1.audio.paused).toEqual false
+#
+#   it 'paused() works', ->
+#     player.pause
+#     expect(no1.audio.paused).toEqual true
+#
+#   it 'playPause() works', ->
+#     no1.pause
+#     no1.playPause
+#     expect(no1.audio.paused).toEqual false
+#     no1.playPause
+#     expect(no1.audio.paused).toEqual true
+#
+#   it 'setVolume() works', ->
+#     no1.setVolume 0.5
+#     expect(no1.audio.volume).toEqual 0.5
+#
+#   it 'setPlayerSize() works', ->
+#     no1.setPlayerSize 'small'
+#     expect(no1.currentUISize).toEqual 'small'
+#
+#   it 'togglePlayerSize() works', ->
+#     no1.setPlayerSize 'small'
+#     no1.togglePlayerSize
+#     expect(no1.currentUISize).toEqual 'normal'
+#     no1.togglePlayerSize
+#     expect(no1.currentUISize).toEqual 'small'
+#
+#   it 'toggleMute() works', ->
+#     no1.audio.muted = false
+#     no1.toggleMute
+#     expect(no1.audio.muted).toEqual true
+#     no1.toggleMute
+#     expect(no1.audio.muted).toEqual false


Reset ready event before each test case

Reset ready event before each test case

--- spec/jquery.turbolinks_spec.coffee
+++ spec/jquery.turbolinks_spec.coffee
@@ -20,6 +20,7 @@
   describe "DOM isn't ready", ->

     beforeEach ->
+      $.setReadyEvent('page:load')
       $.isReady = false

       $(callback1 = sinon.spy())
@@ -42,6 +43,9 @@

     describe '$.setReadyEvent', ->

+      beforeEach ->
+        $.setReadyEvent('page:load')
+
       it 'should unbind default (page:load) event', ->
         $.setReadyEvent('random_event_name')

@@ -63,6 +67,7 @@
   describe "DOM is ready", ->

     beforeEach ->
+      $.setReadyEvent('page:load')
       $.isReady = true

       $(callback1 = sinon.spy())


Update source mapping URL syntax: http://updates.html5rocks.com/2013/06/sourceMappingURL-and-sourceURL-syntax-changed.

--- src/fs_utils/generate.coffee
+++ src/fs_utils/generate.coffee
@@ -164,9 +164,9 @@
     if withMaps
       base = sysPath.basename "#{path}.map"
       if type is 'javascript'
-        data += "\n//@ sourceMappingURL=#{base}"
+        data += "\n//# sourceMappingURL=#{base}"
       else
-        data += "\n/*@ sourceMappingURL=#{base}*/"
+        data += "\n/*# sourceMappingURL=#{base}*/"

     common.writeFile path, data, ->
       if withMaps