sagews: make split screen editing render properly

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -1114,7 +1114,12 @@
         # starts with a cell or output marker and is not already marked.
         # If not marked, mark it appropriately, and possibly process any
         # changes to that line.
-        cm = @codemirror
+        @_process_sage_updates(@codemirror, start)
+        if @editor._split_view
+            @_process_sage_updates(@editor.codemirror1, start)
+
+    _process_sage_updates: (cm, start) =>
+
         if not start?
             start = 0

@@ -1125,8 +1130,8 @@

             if x[0] == MARKERS.cell
                 marks = cm.findMarksAt({line:line, ch:1})
-                if marks.length == 0
-                    @mark_cell_start(line)
+                if not marks? or marks.length == 0
+                    @mark_cell_start(cm, line)
                 else
                     first = true
                     for mark in marks
@@ -1140,7 +1145,7 @@
                         m = mark.find()
                         if m.from.line != m.to.line
                             mark.clear()
-                            @mark_cell_start(line)
+                            @mark_cell_start(cm, line)
                 flagstring = x.slice(37, x.length-1)
                 mark = cm.findMarksAt({line:line, ch:1})[0]
                 # It's possible mark isn't defined above, in case of some weird file corruption (say
@@ -1190,15 +1195,15 @@
             else if x[0] == MARKERS.output
                 marks = cm.findMarksAt({line:line, ch:1})
                 if marks.length == 0
-                    @mark_output_line(line)
+                    @mark_output_line(cm, line)
                 mark = cm.findMarksAt({line:line, ch:1})[0]
                 uuid = cm.getRange({line:line,ch:1}, {line:line,ch:37})
-                if mark.uuid != uuid # uuid changed -- completely new output
+                if mark? and mark.uuid != uuid # uuid changed -- completely new output
                     #console.log("uuid change: new x = ", x)
                     mark.processed = 38
                     mark.uuid = uuid
                     @elt_at_mark(mark).html('')
-                if mark.processed < x.length
+                if mark? and mark.processed < x.length
                     #console.log("length change; x = ", x)
                     # new output to process
                     t = x.slice(mark.processed, x.length-1)
@@ -1482,11 +1487,10 @@
                     )()

-    mark_cell_start: (line) =>
+    mark_cell_start: (cm, line) =>
         # Assuming the proper text is in the document for a new cell at this line,
         # mark it as such. This hides control codes and places a cell separation
         # element, which may be clicked to create a new cell.
-        cm  = @codemirror
         if line >= cm.lineCount()-1
             # If at bottom, insert blank lines.
             cm.replaceRange("\n\n\n", {line:line+1, ch:0})
@@ -1520,12 +1524,10 @@
         mark.type = MARKERS.cell
         return mark

-    mark_output_line: (line) =>
+    mark_output_line: (cm, line) =>
         # Assuming the proper text is in the document for output to be displayed at this line,
         # mark it as such.  This hides control codes and creates a div into which output will
         # be placed as it appears.
-
-        cm = @codemirror

         # WARNING: Having a max-height that is SMALLER than the containing codemirror editor was *critical*
         # before Codemirror 3.14, due to a bug.
@@ -1864,7 +1866,7 @@
             cm.replaceRange('\n',{line:line+1,ch:0})
         uuid = misc.uuid()
         cm.replaceRange(MARKERS.cell + uuid + MARKERS.cell + '\n', {line:line, ch:0})
-        return {marker:@mark_cell_start(line), created:true}
+        return {marker:@mark_cell_start(cm, line), created:true}

     remove_cell_flags_from_changeObj: (changeObj, flags) =>
         # Remove cell flags from *contiguous* text in the changeObj.


Initialize the HUD for clipboard operations if it hasn't been already

--- content_scripts/hud.coffee
+++ content_scripts/hud.coffee
@@ -90,10 +90,14 @@
   # * we don't want to disrupt the focus in the page, in case the page is listening for focus/blur events.
   # * the HUD shouldn't be active for this frame while any of the copy/paste commands are running.
   copyToClipboard: (text) ->
-    @hudUI.postMessage {name: "copyToClipboard", data: text}
+    DomUtils.documentComplete =>
+      @init()
+      @hudUI?.postMessage {name: "copyToClipboard", data: text}

   pasteFromClipboard: (@pasteListener) ->
-    @hudUI.postMessage {name: "pasteFromClipboard"}
+    DomUtils.documentComplete =>
+      @init()
+      @hudUI?.postMessage {name: "pasteFromClipboard"}

   pasteResponse: ({data}) ->
     @pasteListener data


fix post-receive hook?

fix post-receive hook?

--- controllers/commits.coffee
+++ controllers/commits.coffee
@@ -5,7 +5,6 @@

   (req, res, next) ->
     return next() unless req.method is 'POST' and m = req.url.match /^\/teams\/(.+)\/commits$/
-    console.dir req.body

     # custom error handler (since the default one dies w/o session)
     error = (err) ->
@@ -25,7 +24,7 @@
       util.log "#{'POST-RECEIVE'.magenta} #{team.name} (#{team.id})"

       try
-        $inc = pushes: 1, commits: req.body.repository.commits.length
+        $inc = pushes: 1, commits: req.body.commits.length
       catch e
         return error(e)


add log

add log

--- controllers/deploys.coffee
+++ controllers/deploys.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 m = require './middleware'
+rollbar = require('rollbar')

 module.exports = (app) ->
   Team = app.db.model 'Team'
@@ -7,8 +8,8 @@

   (req, res, next) ->
     return next() unless app.enabled('coding')
-    return next() unless req.method is 'GET' and req._parsedUrl.pathname is '/deploys'
-
+    return next() unless req.method is 'GET' and req._parsedUrl.pathname is '/deploys'
+
     # custom error handler (since the default one dies w/o session)
     error = (err) ->
       console.error err.toString().red
@@ -32,9 +33,11 @@
       attr.os = req.query.os
       attr.platform = req.query.release

+      rollbar.reportMessage("received deploy hook: #{team.name} from #{attr.remoteAddress}");
+
       # save the deploy in the db
       deploy = new Deploy attr
-
+
       deploy.save (err, deploy) ->
         return error(err) if err


fix room getting in log_message function

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -110,11 +110,11 @@

   # events
   log_message = (msg) ->
-    unless msg.room?
+    unless msg.envelope.room?
       robot.logger.debug "msg without room: #{msg}"
       return

-    room = msg.room
+    room = msg.envelope.room
     msg_data =
       nick: msg.user.name
       type: message_type msg


remove debug command

remove debug command

--- scripts/cron_events.coffee
+++ scripts/cron_events.coffee
@@ -124,8 +124,8 @@
       res.finish()

   # debug
-  robot.respond /cron jobs$/, (res) ->
-    console.log cron.jobs
+  # robot.respond /cron jobs$/, (res) ->
+  #   console.log cron.jobs

   # sample for testing purposes
   robot.on 'cron.message', (e) ->


Achievements: Fix awarding when on alts

Achievements: Fix awarding when on alts

--- server/achievements.coffee
+++ server/achievements.coffee
@@ -124,16 +124,17 @@

 # Checks what achievements a player is eligible for
 # The achievements are then awarded to the player
-@checkAndAwardAchievements = (server, player, next = ->) ->
-  checkAchievements player.ratingKey, (err, achievements) ->
+# Note: In the current implementation, playerId is actually a name
+@checkAndAwardAchievements = (server, playerId, ratingKey, next = ->) ->
+  checkAchievements ratingKey, (err, achievements) ->
     return next(err) if err
     return next() if achievements.length == 0

-    filterEarned player.id, achievements, (err, achievements) ->
+    filterEarned playerId, achievements, (err, achievements) ->
       return next(err) if err
       return next() if achievements.length == 0

-      notifyServer player.name, achievements, (err, byStatus) ->
+      notifyServer playerId, achievements, (err, byStatus) ->
         return next(err) if err

         # TODO: Handle errors, probably add them to some queue to retry
@@ -142,11 +143,11 @@

         # Flag all of the achievements that have been earned
         achievementsToFlag = _.union(byStatus.success, byStatus.duplicate)
-        flagEarned(player.id, achievementsToFlag)  if achievementsToFlag.length > 0
+        flagEarned(playerId, achievementsToFlag)  if achievementsToFlag.length > 0

         # for each new achievement, notify the user if said user is online
         if byStatus.success.length > 0
-          user = server.getUser(player.id)
+          user = server.getUser(playerId)
           if user
             user.send('achievementsEarned', byStatus.success)

@@ -160,14 +161,13 @@

   battle.once 'ratingsUpdated', =>
     for player in battle.players
-      @checkAndAwardAchievements(server, player)
+      @checkAndAwardAchievements(server, player.id, player.ratingKey)

 # Returns the achievements a player is eligible for, including already earned ones
-# id is the rating key used to identify the player
-checkAchievements = (id, next) ->
-  ratings.getRatio id, (err, ratio) ->
-    ratings.getStreak id, (err, streak) ->
+checkAchievements = (ratingKey, next) ->
+  ratings.getRatio ratingKey, (err, ratio) ->
+    ratings.getStreak ratingKey, (err, streak) ->
       achievements = ACHIEVEMENTS.filter((o) -> o.conditionFn(ratio, streak))

       # Send everything except the conditionFn attribute
@@ -190,7 +190,9 @@

 # Notifies the server about achievements to add to the user
 # All achievements are separated by server result and passed to next
-notifyServer = (playerName, achievements, next) ->
+# Note: In the current implementation, playerId is actually a name
+# If playerId is refactored to a numerical id, the server will need to be updated
+notifyServer = (playerId, achievements, next) ->
   achievementsByStatus =
     success: []
     duplicate: []
@@ -204,7 +206,7 @@
     do (achievement) -> (callback) ->
       request.post {
         url: "https://www.pokebattle.com/api/v1/achievements/"
-        json: { user: playerName, achievement: achievement.name }
+        json: { user: playerId, achievement: achievement.name }
       }, (err, res, data) ->
         status = "success"
         status = "error"  if err


webapp/assistant: properly fixing 7501e487f4492f925b26c705ab17ffbc8c2df7c1

--- src/smc-webapp/assistant/store.coffee
+++ src/smc-webapp/assistant/store.coffee
@@ -87,14 +87,27 @@
     log: () ->
         lang  = @get('lang')
         path  = @get('path')
-        category0 = @get('category0')
-        category1 = @get('category1')
-        category2 = @get('category2')
-        return if (not category0?) or (not category1?) or (not category2?)
-        c0    = @get_category_list0()[category0]
-        c1    = @get_category_list1()[category1]
-        c2    = @get_category_list2()[category2]
+
+        # active search? then grab the categories from the selected hit list
+        if @get('search_sel')?
+            idx = @get('search_sel')
+            hit = @get('hits').get(idx)
+            return if (not hit?)
+            [lvl1, lvl2, lvl3, title, descr, inDescr] = hit.toArray()
+            [c0, c1, c2] = [lvl1, lvl2, title]
+        else
+            # otherwise, we can assume a selected example
+            category0 = @get('category0')
+            category1 = @get('category1')
+            category2 = @get('category2')
+            # but we double-check!
+            return if (not category0?) or (not category1?) or (not category2?)
+            c0 = @get_category_list0()[category0]
+            c1 = @get_category_list1()[category1]
+            c2 = @get_category_list2()[category2]
+
         entry = [c0, c1, c2]
+
         project_actions = redux.getProjectActions(@get('project_id'))
         project_actions.log
             event     : 'assistant'


jupyter: fix sample nbconvert command line

--- src/smc-webapp/jupyter/nbconvert.cjsx
+++ src/smc-webapp/jupyter/nbconvert.cjsx
@@ -126,7 +126,7 @@

     render_cmd: ->
-        cmd = "jupyter nbconvert #{@args().join(' ')} #{@props.path}"
+        cmd = "jupyter nbconvert #{@args().join(' ')} '#{misc.path_split(@props.path)?.tail}'"

{cmd}

     render_started: ->


Fix missing parenthesis in server user model

--- server/models/user.coffee
+++ server/models/user.coffee
@@ -94,7 +94,7 @@
     hasEmail = if data.email then helpers.checkEmail(data.email) else false
     hasUserName = data?.public_name
     hasTimezone = if data.timezone
-        not timezones.indexOf(data.timezone) is -1
+        not (timezones.indexOf(data.timezone) is -1)
     else
         false
     return hasEmail and hasUserName and hasTimezone


Bugfix for importing.

Bugfix for importing.

--- server/publication.coffee
+++ server/publication.coffee
@@ -283,7 +283,7 @@
         # Ensure that importing contains only this person
         'importing.$': 1
         sha256: 1
-        source: 1
+        cachedId: 1

     # File maybe finished by somebody else, or wrong publicationId, or something else.
     # If the file was maybe finished by somebody else, we do not want really to continue writing


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -46,7 +46,7 @@
 test "#unbind namespaced", ->
   o = Object.extend({}, Bindable())

-  o.bind ""
+  o.bind "test",

 module()


Remove TODOs

Remove TODOs

--- test/bw/moves.coffee
+++ test/bw/moves.coffee
@@ -2154,7 +2154,7 @@
         shared.create.call(this, team2: [Factory("Blissey")])

         @controller.makeMove(@player1, name)
-        @controller.makeMove(@player2, "Recover") # todo: make this rest instead once rest is implemented
+        @controller.makeMove(@player2, "Recover")

         maxHP = @p2.stat('hp')
         expected = maxHP - Math.floor(maxHP / 16)
@@ -2221,7 +2221,7 @@
         shared.create.call(this, team1: [Factory("Blissey"), Factory("Magikarp")])

         @controller.makeMove(@player1, name)
-        @controller.makeMove(@player2, "Recover") # todo: make this rest instead once rest is implemented
+        @controller.makeMove(@player2, "Recover")

         @controller.makeSwitch(@player1, 1)
         @controller.makeMove(@player2, "Splash")
@@ -2617,7 +2617,6 @@
       util.typeEffectiveness(type, @p1.types).should.be.lessThan 1

   describe "Defense Curl", ->
-    # TODO: Merge tests.
     it "raises defense by 1 stage", ->
       shared.create.call(this)
       @p1.stages.defense.should.equal 0


include Cooldown as default module

include Cooldown as default module

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -91,7 +91,6 @@
 test "use existing value of instance variable as starting value if no value param given", ->
   obj = GameObject()

-
   obj.I.health = 3

   obj.cooldown 'health',


Fix previous commit for States

Fix previous commit for States

--- frontend/app/assets/javascripts/spree/frontend/checkout/address.js.coffee
+++ frontend/app/assets/javascripts/spree/frontend/checkout/address.js.coffee
@@ -49,7 +49,6 @@
         else
           stateSelect.removeClass('required')
           stateSpanRequired.hide()
-        end
         stateSelect.removeClass('hidden')
         stateInput.removeClass('required')
       else


removed else condition and moved block to an upper level

--- client/landing/app/CommonViews/opinions/opinionlistitemview.coffee
+++ client/landing/app/CommonViews/opinions/opinionlistitemview.coffee
@@ -121,43 +121,42 @@
     # activity = @getDelegate().getData()

     loggedInId = KD.whoami().getId()
-    if loggedInId is data.originId or       # if comment owner
-       # loggedInId is activity.originId or     # activity owner can remove opinion
-       KD.checkFlag "super-admin", KD.whoami()  # if super-admin
+    # comment owner and super-admin can remove opinion
+    if loggedInId is data.originId or KD.checkFlag "super-admin", KD.whoami()

       @editLink.on "click", =>

-          if @editForm?
-            @editForm?.destroy()
-            delete @editForm
-            @$("p.opinion-body").show()
-            @$(".opinion-size-links").show() if @needsToResize
-
-          else
-            @editForm = new OpinionFormView
-              submitButtonTitle : "Save your changes"
-              title             : "edit-opinion"
-              cssClass          : "edit-opinion-form opinion-container"
-              callback          : (data)=>
-                @getData().modify data, (err, opinion) =>
-                  @$("p.opinion-body").show()
-                  callback? err, opinion
-                  @editForm.reset()
-                  @editForm.submitOpinionBtn.hideLoader()
-                  if err
-                    new KDNotificationView title : "Your changes weren't saved.", type :"mini"
-                  else
-                    @bodyView.render yes
-                    @getDelegate().emit "RefreshTeaser", ->
-                    @emit "OwnOpinionWasAdded", opinion
-                    @editForm.setClass "hidden"
-                    @$("p.opinion-body").show()
-                    @$(".opinion-size-links").show() if @needsToResize
-            , data
-
-            @addSubView @editForm, "p.opinion-body-edit", yes
-            @$("p.opinion-body").hide()
-            @$(".opinion-size-links").hide() if @needsToResize
+        if @editForm?
+          @editForm?.destroy()
+          delete @editForm
+          @$("p.opinion-body").show()
+          @$(".opinion-size-links").show() if @needsToResize
+
+
+        @editForm = new OpinionFormView
+          submitButtonTitle : "Save your changes"
+          title             : "edit-opinion"
+          cssClass          : "edit-opinion-form opinion-container"
+          callback          : (data)=>
+            @getData().modify data, (err, opinion) =>
+              @$("p.opinion-body").show()
+              callback? err, opinion
+              @editForm.reset()
+
+              @editForm.submitOpinionBtn.hideLoader()
+              if err
+                new KDNotificationView title : "Your changes weren't saved.", type :"mini"
+              else
+                @getDelegate().emit "RefreshTeaser", ->
+                @emit "OwnOpinionWasAdded", opinion
+                @editForm.setClass "hidden"
+                @$("p.opinion-body").show()
+                @$(".opinion-size-links").show() if @needsToResize
+          , data
+
+        @addSubView @editForm, "p.opinion-body-edit", yes
+        @$("p.opinion-body").hide()
+        @$(".opinion-size-links").hide() if @needsToResize

       @deleteLink.on "click", =>
         @confirmDeleteOpinion data


Support to save log with shift-jis, fix #36

--- views/akashic-advanced-module.cjsx
+++ views/akashic-advanced-module.cjsx
@@ -569,7 +569,12 @@
           @showMessage '发生错误！请报告开发者'
           return
       if process.platform is 'win32'
-        codeType = 'GB2312'
+        if window.language is 'ja-JP'
+          codeType = 'shiftjis'
+        else if window.language is 'zh-CN' or window.language is 'zh-TW'
+          codeType = 'GB2312'
+        else
+          codeType = 'utf8'
       else
         codeType = 'utf8'
       filename = dialog.showSaveDialog


Remove manual selector from settings

Remove manual selector from settings

--- src/components/widgets-settings/source-selector/source-selector.directive.coffee
+++ src/components/widgets-settings/source-selector/source-selector.directive.coffee
@@ -35,12 +35,12 @@
         { app_instance_id: appInstanceId }

       resetSelectedApps = -> scope.selectedApps = _.mapValues(scope.selectedApps, -> false)
-      updateAppInstances = (appInstances, primary, manual) ->
+      updateAppInstances = (appInstances, primary) ->
         valuesPresent = _.find(appInstances, (hash) ->
           # We push at the same time so we can check for one
           hash.value == 'prm-records-only'
         )
-        if valuesPresent then appInstances else appInstances.push(primary, manual)
+        if valuesPresent then appInstances else appInstances.push(primary)

       retrieveAppInstances = ->
         appInstances = _.find(w.configOptions.selectors, (selector) ->
@@ -52,15 +52,8 @@
           'value': 'prm-records-only'
           'label': 'Primary Only'

-        # Used to select all records added manually
-        # TODO: Use _.replace when updating to Lodash 4.17.5
-        orgUid = w.metadata.organization_ids[0]
-        manUid = orgUid.replace('org', 'man')
-        manual =
-          'value': manUid
-          'label': 'Manual'
-
-        if appInstances then updateAppInstances(appInstances, primary, manual) else (appInstances = [primary, manual])
+        # TODO: re-include manual selector?
+        if appInstances then updateAppInstances(appInstances, primary) else (appInstances = [primary])

         scope.organizationApps = appInstances


Removed duplicate view

Removed duplicate view

--- core/app/backbone/views/sub_comments/react_sub_comments_add.coffee
+++ core/app/backbone/views/sub_comments/react_sub_comments_add.coffee
@@ -1,48 +0,0 @@
-window.ReactSubCommentsAdd = React.createClass
-  getInitialState: ->
-    text: ''
-    phase: 'new'
-
-  updateText: (e)->
-    @setState text: e.target.value
-
-  submit: ->
-    return if @state.phase == 'submitting'
-    @setState phase: 'submitting'
-
-    model = new SubComment
-      content: $.trim(@state.text)
-      created_by: currentUser.toJSON()
-
-    if model.isValid()
-      @props.addToCollection.add(model)
-      model.save {},
-        success: =>
-          @setState phase: 'new'
-          @setState text: ''
-        error: =>
-          @props.addToCollection.remove(model)
-          @setState phase: 'new'
-          FactlinkApp.NotificationCenter.error 'Your comment could not be posted, please try again.'
-    else
-      @setState phase: 'new'
-      FactlinkApp.NotificationCenter.error 'Your comment is not valid.'
-
-  render: ->
-    submit_button_classes = "button button-confirm button-small spec-submit"
-    submit_button =
-      if @state.phase == 'new'
-        R.button className: submit_button_classes, onClick: @submit,
-          Factlink.Global.t.post_subcomment
-      else
-        R.button className: submit_button_classes, disabled: true,
-          'Posting...'
-
-    R.div className: 'sub-comment-add comment-content spec-sub-comments-form',
-      R.textarea
-        className: "text_area_view",
-        placeholder: 'Leave a reply'
-        onChange: @updateText
-        ref: 'textarea'
-        value: @state.text
-      submit_button


Proper bracket placement

Proper bracket placement

--- snippets/php.cson
+++ snippets/php.cson
@@ -41,7 +41,8 @@
     'PHP Public Function Based on PhpStorm':
       'prefix': 'pubf'
       'body': """
-        public function $1($2) {
+        public function $1($2)
+        {
             $0
         }
       """
@@ -49,7 +50,8 @@
     'PHP Protected Function Based on PhpStorm':
       'prefix': 'prof'
       'body': """
-        protected function $1($2) {
+        protected function $1($2)
+        {
             $0
         }
       """
@@ -57,7 +59,8 @@
     'PHP Private Function Based on PhpStorm':
       'prefix': 'prif'
       'body': """
-        private function $1($2) {
+        private function $1($2)
+        {
             $0
         }
       """


Teamwork: Hiding playgrounds button when someone joined the session.

--- client/app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee
+++ client/app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee
@@ -15,6 +15,15 @@

         if environmentManifest
           @workspaceRef.child("environmentManifest").set environmentManifest
+
+      @hidePlaygroundsButton()  unless @amIHost()
+
+      usersRef = @workspaceRef.child "users"
+
+      usersRef.on "child_added", (snapshot) =>
+        joinedUser = snapshot.name()
+        return if not joinedUser or joinedUser is KD.nick()
+        @hidePlaygroundsButton()

   createLoader: ->
     @container.addSubView @loader = new KDCustomHTMLView
@@ -76,6 +85,9 @@
   handleRun: (panel) ->
     console.warn "You should override this method."

+  hidePlaygroundsButton: ->
+    @getActivePanel().headerButtons.Playgrounds?.hide()
+
   showHintModal: ->
     if @markdownContent
       @getDelegate().showMarkdownModal()


Minicart expecting shippingData and sellers

--- src/script/portal-minicart.coffee
+++ src/script/portal-minicart.coffee
@@ -8,7 +8,7 @@
 # CLASS
 class Minicart
 	constructor: (@element, @options) ->
-		@EXPECTED_ORDER_FORM_SECTIONS = ["items", "paymentData", "totalizers"]
+		@EXPECTED_ORDER_FORM_SECTIONS = ["items", "paymentData", "totalizers", "shippingData", "sellers"]

 		@hoverContext = @element.add('.show-minicart-on-hover')
 		@cartData = {}
@@ -79,6 +79,8 @@
 	handleOrderForm: (orderForm, slide = true) =>
 		@cartData.orderFormId = orderForm?.orderFormId
 		@cartData.totalizers = orderForm?.totalizers
+		@cartData.shippingData = orderForm?.shippingData
+		@cartData.sellers = orderForm?.sellers
 		if orderForm?.items?
 			@cartData.items = orderForm.items
 			@prepareCart()
@@ -108,7 +110,7 @@
 				item.availabilityMessage = @getAvailabilityMessage(item)
 				item.formattedPrice = _.intAsCurrency(item.sellingPrice, @options) + if item.measurementUnit and item.measurementUnit != 'un' then " (por cada #{item.unitMultiplier} #{item.measurementUnit})" else ''

-	render: () =>
+	render: =>
 		dust.render 'minicart', $.extend({options: @options}, @cartData), (err, out) =>
 			throw new Error "Minicart Dust error: #{err}" if err
 			@element.html out
@@ -116,11 +118,13 @@
 			$(".vtexsc-productList .cartSkuRemove", @element).on 'click', ->
 				self.deleteItem(this) # Keep reference to event handler

-
 			dependencies = ['js/component/ShippingOptions']

-			vtex.curl dependencies, (ShippingOptions) ->
-				ShippingOptions.attachTo('.minicart-shipping-options')
+			if @cartData.shippingData
+				vtex.curl dependencies, (ShippingOptions) =>
+					ShippingOptions.attachTo('.minicart-shipping-options')
+					$('.minicart-shipping-options').trigger('enable.vtex',
+						[@cartData.shippingData.logisticsInfo, @cartData.items, @cartData.sellers])

 	slide: =>
 		if @cartData.items.length is 0


Add more event emitter wildcard tests

Add more event emitter wildcard tests

--- test/core/eventemitterwildcard.coffee
+++ test/core/eventemitterwildcard.coffee
@@ -7,6 +7,7 @@
 describe 'KDEventEmitterWildcard', ->
   beforeEach ->
     @instance = new KDEventEmitterWildcard
+    emitSpy = sinon.spy @instance.emit

   it 'exists', ->
     KDEventEmitterWildcard.should.exist
@@ -23,3 +24,17 @@
     it 'should set max listeners', ->
       @instance.setMaxListeners 20
       @instance._maxListeners.should.equal 20
+
+  describe 'off', ->
+    it 'should remove all listeners', ->
+      emitter = new KDEventEmitterWildcard
+      spy = sinon.spy emitter.removeAllListeners
+      emitter.off
+      spy.should.be.calledOnce
+
+    it 'should return itself', ->
+      @instance.off().should.deepEqual @instance
+
+  describe 'on', ->
+    it 'should return itself', ->
+      @instance.on('koding', (->)).should.be.an.instanceOf(KDEventEmitterWildcard)


added moar error things

added moar error things

--- test/home/home-controller-spec.coffee
+++ test/home/home-controller-spec.coffee
@@ -52,7 +52,15 @@
       it 'should set a message property with a message', ->
         @rootScope.$digest()
         expect(@sut.message).to.deep.equal @message
-
+
+    describe 'when triggerService.getTriggers rejects the promise', ->
+      beforeEach ->
+        @error = 'ERROR WILL ROBINSON ERROR'
+        @getTriggers.reject @error
+      it 'should have an errorMsg property', ->
+        @rootScope.$digest()
+        expect(@sut.errorMsg).to.deep.equal @error
+
   describe 'when the user does not have a valid uuid', ->
     beforeEach ->
       @cookies.uuid = undefined


Removed redundant include

Removed redundant include

--- test/integration/usersAPITests.coffee
+++ test/integration/usersAPITests.coffee
@@ -3,7 +3,6 @@
 server = require '../../lib/server'
 contact = require '../../lib/contact'
 User = require('../../lib/model/users').User
-users = require '../../lib/api/users'
 testUtils = require "../testUtils"
 auth = require("../testUtils").auth
 sinon = require "sinon"
@@ -230,16 +229,6 @@
               done()

-
-
-
-
-
-
-
-
-
-
     describe '*getUsers()', ->

       it 'should fetch all users', (done) ->


Restore trailing newlines that cause markdown to emit a <br>

--- spec/templates/methods/paragraph_desc.coffee
+++ spec/templates/methods/paragraph_desc.coffee
@@ -8,7 +8,7 @@

   # Public: This is a paragraph with a br.
   #
-  # Equality is based on some condition.
+  # Equality is based on some condition.
   # Oh look, a BR!
   br: ->


fixed test cases

fixed test cases

--- testApp/app/controllers/layers.coffee
+++ testApp/app/controllers/layers.coffee
@@ -146,7 +146,16 @@

   @testwillchangeEvent: ->
     eventHandler = steroids.layers.on 'willchange', (event) ->
-      alert "willchange event -> eventName: #{event.name} target.webview.id: #{event.target.webview.id} source.webview.id: #{event.source.webview.id}"
+      targetId = if event.target.webview.id?
+        event.target.webview.id
+      else
+        ""
+      sourceId = if event.source.webview.id?
+        event.source.webview.id
+      else
+        ""
+
+      alert "willchange event -> eventName: #{event.name} target.webview.id: #{targetId} source.webview.id: #{sourceId}"

     @willChangeHandlers.push eventHandler

@@ -154,7 +163,15 @@

   @testdidchangeEvent: ->
     eventHandler = steroids.layers.on 'didchange', (event) ->
-      alert "didchange event -> eventName: #{event.name} target.webview.id: #{event.target.webview.id} source.webview.id: #{event.source.webview.id}"
+      targetId = if event.target.webview.id?
+        event.target.webview.id
+      else
+        ""
+      sourceId = if event.source.webview.id?
+        event.source.webview.id
+      else
+        ""
+      alert "didchange event -> eventName: #{event.name} target.webview.id: #{targetId} source.webview.id: #{sourceId}"

     @didChangeHandlers.push eventHandler


Improve naming

Improve naming

--- framer/SVG.coffee
+++ framer/SVG.coffee
@@ -52,17 +52,17 @@
 			imageRatio = imageWidth / imageHeight
 			realWidth = svgLayer.height * imageRatio
 			realHeight = svgLayer.width / imageRatio
-			scX = realWidth / svgLayer.width
-			scY = realHeight / svgLayer.height
+			validScaleX = realWidth / svgLayer.width
+			validScaleY = realHeight / svgLayer.height

 			fillBackground = svgLayer.backgroundSize in ["fill", "cover"]

-			if fillBackground and scY > scX or not fillBackground and scY < scX
-				scaleY = scY
-				offsetY = (1 - scY) / 2
+			if fillBackground and validScaleY > validScaleX or not fillBackground and validScaleY < validScaleX
+				scaleY = validScaleY
+				offsetY = (1 - validScaleY) / 2
 			else
-				scaleX = scX
-				offsetX = (1 - scX) / 2
+				scaleX = validScaleX
+				offsetX = (1 - validScaleX) / 2

 			transform = """transform="translate(#{offsetX}, #{offsetY}) scale(#{scaleX}, #{scaleY})" """


Hotfix: Make date-time try first first

Hotfix: Make date-time try first first

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -256,7 +256,10 @@
             log : true

     render_timestamp: ->
-        <TimeAgo date={(new Date(@props.time * 1000)).toISOString()} style={color:'#666'}/>
+        try
+            <TimeAgo date={(new Date(@props.time * 1000)).toISOString()} style={color:'#666'}/>
+        catch
+
Invalid Date Time

     render_download_button: (url_href) ->
         <Button style   = {marginLeft: '1em', background:'transparent'}
@@ -374,7 +377,11 @@

     render_time: ->
         if @props.time?
-            <TimeAgo date={(new Date(@props.time * 1000)).toISOString()} style={color:'#666'} />
+            try
+                <TimeAgo date={(new Date(@props.time * 1000)).toISOString()} style={color:'#666'} />
+            catch
+
Invalid Date Time

+

     render_name_link: ->
         if (@props.display_name and @props.display_name isnt @props.name) or @props.name.length > 50


Add tracking to file listing actions

Add tracking to file listing actions

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -520,13 +520,13 @@
         if @props.file_search.length == 0
             #@props.actions.set_active_tab('new')
             @props.actions.toggle_new(true)
-            analytics_event('project_files', 'listing_create_button', 'empty')
+            analytics_event('project_file_listing', 'listing_create_button', 'empty')
         else if @props.file_search[@props.file_search.length - 1] == '/'
             @props.create_folder()
-            analytics_event('project_files', 'listing_create_button', 'folder')
+            analytics_event('project_file_listing', 'listing_create_button', 'folder')
         else
             @props.create_file()
-            analytics_event('project_files', 'listing_create_button', 'file')
+            analytics_event('project_file_listing', 'listing_create_button', 'file')

     # Returns the full file_search text in addition to the default extension if applicable
     full_path_text: ->
@@ -998,8 +998,12 @@
         obj = file_actions[name]
         get_basename = =>
             misc.path_split(@props.checked_files?.first()).tail
+        handle_click = (e) =>
+            @props.actions.set_file_action(name, get_basename)
+            analytics_event('project_file_listing', 'open ' + name + ' menu')
+
         <Button
-            onClick={=>@props.actions.set_file_action(name, get_basename)}
+            onClick={handle_click}
             disabled={disabled}
             key={name}
         >
@@ -1139,6 +1143,7 @@
             dest : misc.path_to_file(@props.current_path, destination)
         @props.actions.set_all_files_unchecked()
         @props.actions.set_file_action()
+        analytics_event('project_file_listing', 'compress item')

     render_compress: ->
         size = @props.checked_files.size
@@ -1187,7 +1192,7 @@
         @props.actions.set_file_action()
         @props.actions.set_all_files_unchecked()
         @props.actions.fetch_directory_listing()
-
+        analytics_event('project_file_listing', 'delete item')

     render_delete_warning: ->
         if @props.current_path is '.trash'
@@ -1238,11 +1243,13 @@
                     dest           : misc.path_to_file(rename_dir, destination)
                     dest_is_folder : false
                     include_chats  : true
+                analytics_event('project_file_listing', 'rename item')
             when 'duplicate'
                 @props.actions.copy_paths
                     src           : @props.checked_files.toArray()
                     dest          : misc.path_to_file(rename_dir, destination)
                     only_contents : true
+                analytics_event('project_file_listing', 'duplicate item')
         @props.actions.set_file_action()
         @props.actions.set_all_files_unchecked()

@@ -1338,6 +1345,7 @@
             include_chats  : true
         @props.actions.set_file_action()
         @props.actions.set_all_files_unchecked()
+        analytics_event('project_file_listing', 'move item')

     valid_move_input: ->
         src_path = misc.path_split(@props.checked_files.first()).head
@@ -1448,10 +1456,13 @@
                 target_path       : destination_directory
                 overwrite_newer   : overwrite_newer
                 delete_missing    : delete_extra_files
+            analytics_event('project_file_listing', 'copy between projects')
         else
             @props.actions.copy_paths
                 src  : paths
                 dest : destination_directory
+            analytics_event('project_file_listing', 'copy within a project')
+
         @props.actions.set_file_action()

     valid_copy_input: ->
@@ -1530,9 +1541,11 @@
     share_click: ->
         description = ReactDOM.findDOMNode(@refs.share_description).value
         @props.actions.set_public_path(@props.checked_files.first(), {description: description})
+        analytics_event('project_file_listing', 'share item')

     stop_sharing_click: ->
         @props.actions.disable_public_path(@props.checked_files.first())
+        analytics_event('project_file_listing', 'stop sharing item')

     render_share_warning: ->
         <Alert bsStyle='warning' style={wordWrap:'break-word'}>
@@ -1749,6 +1762,7 @@
         filename   = misc.path_split(single_file).tail
         text       = encodeURIComponent("Check out #{filename}")
         site_name  = @props.site_name ? SITE_NAME
+        analytics_event('project_file_listing', 'share item via', where)
         switch where
             when 'facebook'
                 # https://developers.facebook.com/docs/sharing/reference/share-dialog
@@ -1782,6 +1796,7 @@
             path : @props.checked_files.first()
             log : true
         @props.actions.set_file_action()
+        analytics_event('project_file_listing', 'download item')

     download_multiple_click: ->
         destination = ReactDOM.findDOMNode(@refs.download_archive).value
@@ -1799,6 +1814,7 @@
                 @props.actions.fetch_directory_listing()
         @props.actions.set_all_files_unchecked()
         @props.actions.set_file_action()
+        analytics_event('project_file_listing', 'download item')

     render_download_single: (single_item) ->
         target = @props.actions.get_store().get_raw_link(single_item)
@@ -2092,13 +2108,13 @@
     on_create_button_clicked: ->
         if @props.file_search.length == 0
             @props.actions.toggle_new()
-            analytics_event('project_files', 'search_create_button', 'empty')
+            analytics_event('project_file_listing', 'search_create_button', 'empty')
         else if @props.file_search[@props.file_search.length - 1] == '/'
             @props.create_folder()
-            analytics_event('project_files', 'search_create_button', 'folder')
+            analytics_event('project_file_listing', 'search_create_button', 'folder')
         else
             @props.create_file()
-            analytics_event('project_files', 'search_create_button', 'file')
+            analytics_event('project_file_listing', 'search_create_button', 'file')

     render: ->
         <SplitButton


fixed default focus on finderitem delete

--- client/landing/app/MainApp/filetree/itemsubviews/finderitemdeleteview.coffee
+++ client/landing/app/MainApp/filetree/itemsubviews/finderitemdeleteview.coffee
@@ -20,7 +20,7 @@
     @label = new KDLabelView
       title     : 'Are you sure?'

-  show:->
+  viewAppended:->

     super
     @button.$().focus()


filter IOPS

filter IOPS

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -979,7 +979,6 @@
     throw new Error('Cannot fetch the RRDs')

   json = parseXml(body)
-
   # Find index of needed objects for getting their values after
   cpusIndexes = []
   vifsIndexes = []
@@ -995,9 +994,9 @@
       vifsIndexes.push(i)
     else if startsWith(value.name, 'vif_') && endsWith(value.name, '_rx')
       vifsIndexes.push(i)
-    else if startsWith(value.name, 'vbd_xvd') && endsWith(value.name, '_write')
+    else if startsWith(value.name, 'vbd_xvd') && endsWith(value.name, '_write', 14)
       xvdsIndexes.push(i)
-    else if startsWith(value.name, 'vbd_xvd') && endsWith(value.name, '_read')
+    else if startsWith(value.name, 'vbd_xvd') && endsWith(value.name, '_read', 13)
       xvdsIndexes.push(i)
     else if startsWith(value.name, 'memory_internal_free')
       memoryFreeIndex.push(i)


Test for find next in history

Test for find next in history

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -261,6 +261,16 @@

       expect(findView.resultCounter.text()).toEqual('3 of 5')
       expect(editor.getSelectedBufferRange()).toEqual [[8, 11], [8, 15]]
+
+    it "'find-and-replace:find-next' adds to the findEditor's history", ->
+      findView.findEditor.setText 'sort'
+      findView.findEditor.trigger 'find-and-replace:find-next'
+
+      expect(findView.resultCounter.text()).toEqual('3 of 5')
+
+      findView.findEditor.setText 'nope'
+      findView.findEditor.trigger 'core:move-up'
+      expect(findView.findEditor.getText()).toEqual 'sort'

     it "selects the previous match when the previous match button is pressed", ->
       findView.previousButton.click()


Adapt tests to new data structures

Adapt tests to new data structures

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -39,15 +39,15 @@
       #             012345678901
       bufferText = '[1,[1,2,[]]]'
       expect(TextManipulation.findMatchingOpeningBracket bufferText, 11, false)
-       .toEqual([1, [ [1,3] ]])
+       .toEqual({bracketIx: 1, ranges: [ [1,3] ]})
       expect(TextManipulation.findMatchingOpeningBracket bufferText, 10, false)
-       .toEqual([4, [ [4,8] ]])
+       .toEqual({bracketIx: 4, ranges: [ [4,8] ]})
       expect(TextManipulation.findMatchingOpeningBracket bufferText, 9, false)
-       .toEqual([9, [  ]])
+       .toEqual({bracketIx: 9, ranges: [  ]})
       expect(TextManipulation.findMatchingOpeningBracket bufferText, 8, false)
-       .toEqual([4, [ [4,8] ]])
+       .toEqual({bracketIx: 4, ranges: [ [4,8] ]})
       expect(TextManipulation.findMatchingOpeningBracket bufferText, 1, false)
-       .toEqual([1, [  ]])
+       .toEqual({bracketIx: 1, ranges: [  ]})

   describe 'findMatchingClosingBracket', ->
     # TODO: split into separate 'it' clauses
@@ -55,13 +55,13 @@
       #             01234567890123
       bufferText = '[1,[[],2,3],4]'
       expect(TextManipulation.findMatchingClosingBracket bufferText, 1, false)
-       .toEqual([13, [ [1,3], [11,13] ]])
+       .toEqual({bracketIx: 13, ranges: [ [1,3], [11,13] ]})
       expect(TextManipulation.findMatchingClosingBracket bufferText, 3, false)
-       .toEqual([13, [ [11,13] ]])
+       .toEqual({bracketIx: 13, ranges: [ [11,13] ]})
       expect(TextManipulation.findMatchingClosingBracket bufferText, 4, false)
-       .toEqual([10, [ [6,10] ]])
+       .toEqual({bracketIx: 10, ranges: [ [6,10] ]})
       expect(TextManipulation.findMatchingClosingBracket bufferText, 11, false)
-       .toEqual([13, [ [11,13] ]])
+       .toEqual({bracketIx: 13, ranges: [ [11,13] ]})
       expect(TextManipulation.findMatchingClosingBracket bufferText, 0, false)
        .toEqual(null)

@@ -75,15 +75,15 @@
         .toEqual(null)

     it 'should return the elements of the enclosing list even when index is immediately after opening tag', ->
-      expect(TextManipulation.getListElements bufferText, 1)
+      expect(TextManipulation.getListElements bufferText, [1,1])
         .toEqual(_.map [ [1,2], [3,11], [12,13] ], (r) -> new TextManipulation.ListElement bufferText, r)

     it 'should return the elements of a nested list', ->
-      expect(TextManipulation.getListElements bufferText, 4)
+      expect(TextManipulation.getListElements bufferText, [4,4])
         .toEqual(_.map [ [4,6], [7,8], [9,10] ], (r) -> new TextManipulation.ListElement bufferText, r)

     it 'should return the elements (i.e. []) of an empty list', ->
-      expect(TextManipulation.getListElements bufferText, 5)
+      expect(TextManipulation.getListElements bufferText, [5,5])
         .toEqual( [] )

 describe 'ListEdit', ->


Removed duplicated test

Removed duplicated test

--- spec/spark-dev-spec.coffee
+++ spec/spark-dev-spec.coffee
@@ -83,12 +83,6 @@
       atom.workspaceView.trigger 'spark-dev:show-compile-errors'
       expect(sparkIde.showCompileErrors).toHaveBeenCalled()
       jasmine.unspy sparkIde, 'showCompileErrors'
-
-    it 'calls flashCloud() method for spark-dev:flash-cloud event', ->
-      spyOn sparkIde, 'flashCloud'
-      atom.workspaceView.trigger 'spark-dev:flash-cloud'
-      expect(sparkIde.flashCloud).toHaveBeenCalled()
-      jasmine.unspy sparkIde, 'flashCloud'

     it 'calls flashCloud() method for spark-dev:flash-cloud event', ->
       spyOn sparkIde, 'flashCloud'


Remove extra whitespace

Remove extra whitespace

--- spec/vim-state-spec.coffee
+++ spec/vim-state-spec.coffee
@@ -60,7 +60,7 @@
         expect(editorView).not.toHaveClass 'command-mode'

     describe "the V keybinding", ->
-      beforeEach ->
+      beforeEach ->
         editor.setText("012345\nabcdef")
         editor.setCursorScreenPosition([0, 0])
         keydown('V', shift: true)


Fixing typo in module

Fixing typo in module

--- source/core/helper.coffee
+++ source/core/helper.coffee
@@ -11,7 +11,7 @@
 Atoms.Core.Helper = do ->

   mix = (extend, base) ->
-    result = _clone(base)
+    result = @_clone(base)
     if result? then result[prop] = extend[prop] for prop of extend else result = extend
     result

@@ -24,7 +24,7 @@
     return {}.toString.call(value) is '[object Array]'

-  _clone = (obj) ->
+  _clone: (obj) ->
     return obj if not obj? or typeof obj isnt 'object'
     return new Date(obj.getTime()) if obj instanceof Date


Disable atom editor telemetry

Disable atom editor telemetry

--- atom/config.cson
+++ atom/config.cson
@@ -1,6 +1,7 @@
 "*":
   core:
     disabledPackages: []
+    telemetryConsent: "no"
     themes: [
       "one-dark-ui"
       "oceanic-next"


atom: remove old autocomplete-python settings

--- atom/config.cson
+++ atom/config.cson
@@ -1,6 +1,4 @@
 "*":
-  "autocomplete-python":
-    useKite: false
   "bracket-matcher":
     autocompleteBrackets: false
     autocompleteSmartQuotes: false


Sort ignored file type list

Sort ignored file type list

--- atom/config.cson
+++ atom/config.cson
@@ -28,23 +28,23 @@
     ]
     followSymlinks: false
     ignoredNames: [
-      ".sass-cache"
+      "._*"
+      ".DS_Store"
+      ".fseventsd"
       ".git"
       ".hg"
+      ".sass-cache"
+      ".Spotlight-V100"
       ".svn"
-      ".DS_Store"
-      "._*"
+      ".Trashes"
+      "*.alfredworkflow"
+      "*.docx"
+      "*.jpg"
+      "*.map"
+      "*.out"
+      "*.pages"
+      "*.png"
       "Thumbs.db"
-      "*.png"
-      "*.jpg"
-      "*.out"
-      "*.docx"
-      "*.pages"
-      "*.alfredworkflow"
-      ".Spotlight-V100"
-      ".fseventsd"
-      ".Trashes"
-      "*.map"
     ]
     packagesWithKeymapsDisabled: [
       "emmet"


Re-enable deprecation-cop package

Re-enable deprecation-cop package

--- atom/config.cson
+++ atom/config.cson
@@ -26,7 +26,6 @@
       "autosave"
       "background-tips"
       "bookmarks"
-      "deprecation-cop"
       "dev-live-reload"
       "git-diff"
       "language-perl"


Remove keybindings for forsaken svg-preview package

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -71,9 +71,6 @@
   'alt-cmd-o': 'link:open'
   # Create new files using advanced-open-file rather than open existing files
   'alt-cmd-n': 'advanced-open-file:toggle'
-  # Toggles SVG Preview (even if editor is unfocused)
-  'alt-cmd-v': 'unset!'
-  'shift-cmd-v': 'svg-preview:toggle'

 # Prevent Emmet from hijacking autocomplete
 # 'atom-text-editor.autocomplete-active:not([mini])':


Update badges.coffee

Update badges.coffee

--- src/badges.coffee
+++ src/badges.coffee
@@ -46,7 +46,7 @@
   'trekker1', 'trekker2', 'trekker3', 'trekker4', 'trekker5',
   'translator1', 'translator2', 'translator3', 'translator4',
   'translator5',
-  'verified'
+  'verified',
   'vandguard1', 'vandguard2', 'vandguard3', 'vandguard4', 'vandguard5'
 ]


Configurable ping interval

Configurable ping interval

--- src/client.coffee
+++ src/client.coffee
@@ -22,7 +22,8 @@
         @ws = null
         @_messageID = 0
         @_pending = {}
-
+        @_pingInterval = if @options.pingInterval? then @options.pingInterval else defaultPingInterval
+
         @_connAttempts  = 0

         @logger = new Log process.env.MATTERMOST_LOG_LEVEL or 'info'
@@ -100,12 +101,12 @@

                 @logger.debug 'ping'
                 @_send {"action": "ping"}
-                if @_lastPong? and Date.now() - @_lastPong > (2*pingInterval)
+                if @_lastPong? and Date.now() - @_lastPong > (2*@_pingInterval)
                     @logger.error "Last pong is too old: %d", (Date.now() - @_lastPong) / 1000
                     @authenticated = false
                     @connected = false
                     @reconnect()
-            , pingInterval
+            , @_pingInterval

         @ws.on 'message', (data, flags) =>
             @onMessage JSON.parse(data)


no case where there's no event now?

no case where there's no event now?

--- resources/assets/lib/modal.coffee
+++ resources/assets/lib/modal.coffee
@@ -56,7 +56,7 @@

   hideModal: (e) =>
-    @props.onClose?() if !e? || (e.button == 0 && e.target == @ref.current)
+    @props.onClose?() if e.button == 0 && e.target == @ref.current

   render: =>


Simplify matching of word-characters

Simplify matching of word-characters

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -173,7 +173,7 @@
 	name:
 		patterns: [{
 			name: "storage.type.name.apl"
-			match: "[A-Za-z∆⍙_]+[A-Za-z0-9∆⍙_¯]*"
+			match: "[A-Za-z_∆⍙]+[\\w∆⍙¯]*"
 		}]

@@ -352,7 +352,7 @@
 	"command-switches":
 		patterns: [{
 			name: "variable.parameter.switch.apl"
-			begin: "(?<=\\s)(-)([A-Za-z0-9∆⍙_]+[A-Za-z0-9∆⍙_¯]*)(=)"
+			begin: "(?<=\\s)(-)([\\w∆⍙]+[\\w∆⍙¯]*)(=)"
 			end:   "\\b(?=\\s)"
 			beginCaptures:
 				1: {name: "punctuation.delimiter.switch.apl"}


Add returns in LegacyNetwork spec

Add returns in LegacyNetwork spec

--- spec/LegacyNetwork.coffee
+++ spec/LegacyNetwork.coffee
@@ -7,9 +7,9 @@
   noflo = require 'noflo'
   root = 'noflo'

-describe 'NoFlo Legacy Network', ->
+describe.only 'NoFlo Legacy Network', ->
   Split = ->
-    new noflo.Component
+    return new noflo.Component
       inPorts:
         in: datatype: 'all'
       outPorts:
@@ -17,8 +17,9 @@
       process: (input, output) ->
         output.sendDone
           out: input.get 'in'
+        return
   Merge = ->
-    new noflo.Component
+    return new noflo.Component
       inPorts:
         in: datatype: 'all'
       outPorts:
@@ -26,8 +27,9 @@
       process: (input, output) ->
         output.sendDone
           out: input.get 'in'
+        return
   Callback = ->
-    new noflo.Component
+    return new noflo.Component
       inPorts:
         in: datatype: 'all'
         callback:
@@ -40,7 +42,7 @@
         data = input.getData 'in'
         cb data
         output.done()
-
+        return
   describe 'with an empty graph', ->
     g = null
     n = null
@@ -49,34 +51,48 @@
       g.baseDir = root
       n = new noflo.Network g
       n.connect done
+      return
     it 'should initially be marked as stopped', ->
       chai.expect(n.isStarted()).to.equal false
+      return
     it 'should initially have no processes', ->
       chai.expect(n.processes).to.be.empty
+      return
     it 'should initially have no active processes', ->
       chai.expect(n.getActiveProcesses()).to.eql []
+      return
     it 'should initially have to connections', ->
       chai.expect(n.connections).to.be.empty
+      return
     it 'should initially have no IIPs', ->
       chai.expect(n.initials).to.be.empty
+      return
     it 'should have reference to the graph', ->
       chai.expect(n.graph).to.equal g
+      return
     it 'should know its baseDir', ->
       chai.expect(n.baseDir).to.equal g.baseDir
+      return
     it 'should have a ComponentLoader', ->
       chai.expect(n.loader).to.be.an 'object'
+      return
     it 'should have transmitted the baseDir to the Component Loader', ->
       chai.expect(n.loader.baseDir).to.equal g.baseDir
+      return
     it 'should be able to list components', (done) ->
       @timeout 60 * 1000
       n.loader.listComponents (err, components) ->
-        return done err if err
+        if err
+          done err
+          return
         chai.expect(components).to.be.an 'object'
         done()
+        return
       return
     it 'should have an uptime', ->
       chai.expect(n.uptime()).to.be.at.least 0

+      return
     describe 'with new node', ->
       it 'should contain the node', (done) ->
         g.once 'addNode', ->
@@ -84,27 +100,38 @@
             chai.expect(n.processes).not.to.be.empty
             chai.expect(n.processes.Graph).to.exist
             done()
+            return
           , 10
+          return
         g.addNode 'Graph', 'Graph',
           foo: 'Bar'
+        return
       it 'should have transmitted the node metadata to the process', ->
         chai.expect(n.processes.Graph.component.metadata).to.exist
         chai.expect(n.processes.Graph.component.metadata).to.be.an 'object'
         chai.expect(n.processes.Graph.component.metadata).to.eql g.getNode('Graph').metadata
+        return
       it 'adding the same node again should be a no-op', (done) ->
         originalProcess = n.getNode 'Graph'
         graphNode = g.getNode 'Graph'
         n.addNode graphNode, (err, newProcess) ->
-          return done err if err
+          if err
+            done err
+            return
           chai.expect(newProcess).to.equal originalProcess
           done()
+          return
+        return
       it 'should not contain the node after removal', (done) ->
         g.once 'removeNode', ->
           setTimeout ->
             chai.expect(n.processes).to.be.empty
             done()
+            return
           , 10
+          return
         g.removeNode 'Graph'
+        return
       it 'should fail when removing the removed node again', (done) ->
         n.removeNode
           id: 'Graph'
@@ -112,14 +139,19 @@
           chai.expect(err).to.be.an 'error'
           chai.expect(err.message).to.contain 'not found'
           done()
+          return
+        return
+      return
     describe 'with new edge', ->
       before ->
         n.loader.components.Split = Split
         g.addNode 'A', 'Split'
         g.addNode 'B', 'Split'
+        return
       after ->
         g.removeNode 'A'
         g.removeNode 'B'
+        return
       it 'should contain the edge', (done) ->
         g.once 'addEdge', ->
           setTimeout ->
@@ -133,16 +165,23 @@
               port: 'in'
               index: undefined
             done()
+            return
           , 10
+          return
         g.addEdge 'A', 'out', 'B', 'in'
+        return
       it 'should not contain the edge after removal', (done) ->
         g.once 'removeEdge', ->
           setTimeout ->
             chai.expect(n.connections).to.be.empty
             done()
+            return
           , 10
+          return
         g.removeEdge 'A', 'out', 'B', 'in'
-
+        return
+      return
+    return
   describe 'with a simple graph', ->
     g = null
     n = null
@@ -157,31 +196,42 @@
       g.addInitial (data) ->
         chai.expect(data).to.equal 'Foo'
         cb()
+        return
       , 'Callback', 'callback'
       g.addInitial 'Foo', 'Merge', 'in'
       noflo.createNetwork g, (err, nw) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.loader.components.Split = Split
         nw.loader.components.Merge = Merge
         nw.loader.components.Callback = Callback
         n = nw
         nw.connect (err) ->
-          return done err if err
-          done()
+          if err
+            done err
+            return
+          done()
+          return
+        return
       , true
-
+      return
     it 'should send some initials when started', (done) ->
       chai.expect(n.initials).not.to.be.empty
       cb = done
       n.start (err) ->
-        return done err if err
-
+        if err
+          done err
+          return
+        return
+      return
     it 'should contain two processes', ->
       chai.expect(n.processes).to.not.be.empty
       chai.expect(n.processes.Merge).to.exist
       chai.expect(n.processes.Merge).to.be.an 'Object'
       chai.expect(n.processes.Callback).to.exist
       chai.expect(n.processes.Callback).to.be.an 'Object'
+      return
     it 'the ports of the processes should know the node names', ->
       for name, port of n.processes.Callback.component.inPorts.ports
         chai.expect(port.name).to.equal name
@@ -192,19 +242,23 @@
         chai.expect(port.node).to.equal 'Callback'
         chai.expect(port.getId()).to.equal "Callback #{name.toUpperCase()}"

+      return
     it 'should contain 1 connection between processes and 2 for IIPs', ->
       chai.expect(n.connections).to.not.be.empty
       chai.expect(n.connections.length).to.equal 3

+      return
     it 'should have started in debug mode', ->
       chai.expect(n.debug).to.equal true
       chai.expect(n.getDebug()).to.equal true

+      return
     it 'should emit a process-error when a component throws', (done) ->
       g.removeInitial 'Callback', 'callback'
       g.removeInitial 'Merge', 'in'
       g.addInitial (data) ->
         throw new Error 'got Foo'
+        return
       , 'Callback', 'callback'
       g.addInitial 'Foo', 'Merge', 'in'
       n.once 'process-error', (err) ->
@@ -214,21 +268,28 @@
         chai.expect(err.error).to.be.an 'error'
         chai.expect(err.error.message).to.equal 'got Foo'
         done()
+        return
       n.sendInitials()

+      return
     describe 'with a renamed node', ->
       it 'should have the process in a new location', (done) ->
         g.once 'renameNode', ->
           chai.expect(n.processes.Func).to.be.an 'object'
           done()
+          return
         g.renameNode 'Callback', 'Func'
+        return
       it 'shouldn\'t have the process in the old location', ->
         chai.expect(n.processes.Callback).to.be.undefined
+        return
       it 'should fail to rename with the old name', (done) ->
         n.renameNode 'Callback', 'Func', (err) ->
           chai.expect(err).to.be.an 'error'
           chai.expect(err.message).to.contain 'not found'
           done()
+          return
+        return
       it 'should have informed the ports of their new node name', ->
         for name, port of n.processes.Func.component.inPorts.ports
           chai.expect(port.name).to.equal name
@@ -238,7 +299,8 @@
           chai.expect(port.name).to.equal name
           chai.expect(port.node).to.equal 'Func'
           chai.expect(port.getId()).to.equal "Func #{name.toUpperCase()}"
-
+        return
+      return
     describe 'with process icon change', ->
       it 'should emit an icon event', (done) ->
         n.once 'icon', (data) ->
@@ -246,30 +308,42 @@
           chai.expect(data.id).to.equal 'Func'
           chai.expect(data.icon).to.equal 'flask'
           done()
+          return
         n.processes.Func.component.setIcon 'flask'
-
+        return
+      return
     describe 'once stopped', ->
       it 'should be marked as stopped', (done) ->
         n.stop ->
           chai.expect(n.isStarted()).to.equal false
           done()
-
+          return
+        return
+      return
     describe 'without the delay option', ->
       it 'should auto-start', (done) ->
         g.removeInitial 'Func', 'callback'
         newGraph = noflo.graph.loadJSON g.toJSON(), (err, graph) ->
-          return done err if err
+          if err
+            done err
+            return
           cb = done
           # Pass the already-initialized component loader
           graph.componentLoader = n.loader
           graph.addInitial (data) ->
             chai.expect(data).to.equal 'Foo'
             cb()
+            return
           , 'Func', 'callback'
           noflo.createNetwork graph, (err, nw) ->
-            return done err if err
-          return
-
+            if err
+              done err
+              return
+            return
+          return
+        return
+      return
+    return
   describe 'with nodes containing default ports', ->
     g = null
     testCallback = null
@@ -289,7 +363,7 @@
       c.outPorts.add 'out'
       c.process (input, output) ->
         output.sendDone input.get 'in'
-
+        return
       cb = new noflo.Component
       cb.inPorts.add 'in',
         required: true
@@ -297,64 +371,98 @@
       cb.process (input, output) ->
         return unless input.hasData 'in'
         testCallback input.getData 'in'
-
+        return
       g = new noflo.Graph
       g.baseDir = root
       g.addNode 'Def', 'Def'
       g.addNode 'Cb', 'Cb'
       g.addEdge 'Def', 'out', 'Cb', 'in'

+      return
     it 'should send default values to nodes without an edge', (done) ->
       @timeout 60 * 1000
       testCallback = (data) ->
         chai.expect(data).to.equal 'default-value'
         done()
+        return
       noflo.createNetwork g, (err, nw) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.loader.components.Def = -> c
         nw.loader.components.Cb = -> cb
         nw.connect (err) ->
-          return done err if err
+          if err
+            done err
+            return
           nw.start (err) ->
-            return done err if err
+            if err
+              done err
+              return
+            return
+          return
+        return
       , true

+      return
     it 'should not send default values to nodes with an edge', (done) ->
       @timeout 60 * 1000
       testCallback = (data) ->
         chai.expect(data).to.equal 'from-edge'
         done()
+        return
       g.addNode 'Merge', 'Merge'
       g.addEdge 'Merge', 'out', 'Def', 'in'
       g.addInitial 'from-edge', 'Merge', 'in'
       noflo.createNetwork g, (err, nw) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.loader.components.Def = -> c
         nw.loader.components.Cb = -> cb
         nw.loader.components.Merge = Merge
         nw.connect (err) ->
-          return done err if err
+          if err
+            done err
+            return
           nw.start (err) ->
-            return done err if err
+            if err
+              done err
+              return
+            return
+          return
+        return
       , true

+      return
     it 'should not send default values to nodes with IIP', (done) ->
       @timeout 60 * 1000
       testCallback = (data) ->
         chai.expect(data).to.equal 'from-IIP'
         done()
+        return
       g.addInitial 'from-IIP', 'Def', 'in'
       noflo.createNetwork g, (err, nw) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.loader.components.Def = -> c
         nw.loader.components.Cb = -> cb
         nw.loader.components.Merge = Merge
         nw.connect (err) ->
-          return done err if err
+          if err
+            done err
+            return
           nw.start (err) ->
-            return done err if err
+            if err
+              done err
+              return
+            return
+          return
+        return
       , true
-
+      return
+    return
   describe 'with an existing IIP', ->
     g = null
     n = null
@@ -364,26 +472,39 @@
       g.addNode 'Callback', 'Callback'
       g.addNode 'Repeat', 'Split'
       g.addEdge 'Repeat', 'out', 'Callback', 'in'
+      return
     it 'should call the Callback with the original IIP value', (done) ->
       @timeout 6000
       cb = (packet) ->
         chai.expect(packet).to.equal 'Foo'
         done()
+        return
       g.addInitial cb, 'Callback', 'callback'
       g.addInitial 'Foo', 'Repeat', 'in'
       setTimeout ->
         noflo.createNetwork g, (err, nw) ->
-          return done err if err
+          if err
+            done err
+            return
           nw.loader.components.Split = Split
           nw.loader.components.Merge = Merge
           nw.loader.components.Callback = Callback
           n = nw
           nw.connect (err) ->
-            return done err if err
+            if err
+              done err
+              return
             nw.start (err) ->
-              return done err if err
+              if err
+                done err
+                return
+              return
+            return
+          return
         , true
+        return
       , 10
+      return
     it 'should allow removing the IIPs', (done) ->
       @timeout 6000
       removed = 0
@@ -394,33 +515,47 @@
         chai.expect(n.connections.length).to.equal 1, 'Only one connection'
         g.removeListener 'removeInitial', onRemove
         done()
+        return
       g.on 'removeInitial', onRemove
       g.removeInitial 'Callback', 'callback'
       g.removeInitial 'Repeat', 'in'
+      return
     it 'new IIPs to replace original ones should work correctly', (done) ->
       cb = (packet) ->
         chai.expect(packet).to.equal 'Baz'
         done()
+        return
       g.addInitial cb, 'Callback', 'callback'
       g.addInitial 'Baz', 'Repeat', 'in'
       n.start (err) ->
-        return done err if err
-
+        if err
+          done err
+          return
+        return
+      return
     describe 'on stopping', ->
       it 'processes should be running before the stop call', ->
         chai.expect(n.started).to.be.true
         chai.expect(n.processes.Repeat.component.started).to.equal true
+        return
       it 'should emit the end event', (done) ->
         @timeout 5000
         # Ensure we have a connection open
         n.once 'end', (endTimes) ->
           chai.expect(endTimes).to.be.an 'object'
           done()
+          return
         n.stop (err) ->
-          return done err if err
+          if err
+            done err
+            return
+          return
+        return
       it 'should have called the shutdown method of each process', ->
         chai.expect(n.processes.Repeat.component.started).to.equal false
-
+        return
+      return
+    return
   describe 'with a very large network', ->
     it 'should be able to connect without errors', (done) ->
       @timeout 100000
@@ -434,32 +569,48 @@
         g.addEdge "Repeat#{n}", 'out', 'Callback', 'in'
       g.addInitial ->
         called++
+        return
       , 'Callback', 'callback'
       for n in [0..10000]
         g.addInitial n, "Repeat#{n}", 'in'

       nw = new noflo.Network g
       nw.loader.listComponents (err) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.loader.components.Split = Split
         nw.loader.components.Callback = Callback
         nw.once 'end', ->
           chai.expect(called).to.equal 10001
           done()
+          return
         nw.connect (err) ->
-          return done err if err
+          if err
+            done err
+            return
           nw.start (err) ->
-            return done err if err
-      return
+            if err
+              done err
+              return
+            return
+          return
+        return
+      return
+    return

   describe 'with a faulty graph', ->
     loader = null
     before (done) ->
       loader = new noflo.ComponentLoader root
       loader.listComponents (err) ->
-        return done err if err
+        if err
+          done err
+          return
         loader.components.Split = Split
         done()
+        return
+      return
     it 'should fail on connect with non-existing component', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Baz'
@@ -471,6 +622,8 @@
         chai.expect(err).to.be.an 'error'
         chai.expect(err.message).to.contain 'not available'
         done()
+        return
+      return
     it 'should fail on connect with missing target port', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
@@ -482,6 +635,8 @@
         chai.expect(err).to.be.an 'error'
         chai.expect(err.message).to.contain 'No inport'
         done()
+        return
+      return
     it 'should fail on connect with missing source port', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
@@ -494,6 +649,8 @@
         chai.expect(err).to.be.an 'error'
         chai.expect(err.message).to.contain 'No outport'
         done()
+        return
+      return
     it 'should fail on connect with missing IIP target port', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
@@ -506,6 +663,8 @@
         chai.expect(err).to.be.an 'error'
         chai.expect(err.message).to.contain 'No inport'
         done()
+        return
+      return
     it 'should fail on connect with node without component', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
@@ -518,13 +677,17 @@
         chai.expect(err).to.be.an 'error'
         chai.expect(err.message).to.contain 'No component defined'
         done()
+        return
+      return
     it 'should fail to add an edge to a missing outbound node', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
       nw = new noflo.Network g
       nw.loader = loader
       nw.connect (err) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.addEdge {
           from:
             node: 'Repeat2'
@@ -536,13 +699,18 @@
           chai.expect(err).to.be.an 'error'
           chai.expect(err.message).to.contain 'No process defined for outbound node'
           done()
+          return
+        return
+      return
     it 'should fail to add an edge to a missing inbound node', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
       nw = new noflo.Network g
       nw.loader = loader
       nw.connect (err) ->
-        return done err if err
+        if err
+          done err
+          return
         nw.addEdge {
           from:
             node: 'Repeat1'
@@ -554,23 +722,33 @@
           chai.expect(err).to.be.an 'error'
           chai.expect(err.message).to.contain 'No process defined for inbound node'
           done()
+          return
+        return
+      return
+    return
   describe 'baseDir setting', ->
     it 'should set baseDir based on given graph', ->
       g = new noflo.Graph
       g.baseDir = root
       n = new noflo.Network g
       chai.expect(n.baseDir).to.equal root
+      return
     it 'should fall back to CWD if graph has no baseDir', ->
-      return @skip() if noflo.isBrowser()
+      if noflo.isBrowser()
+        @skip()
+        return
       g = new noflo.Graph
       n = new noflo.Network g
       chai.expect(n.baseDir).to.equal process.cwd()
+      return
     it 'should set the baseDir for the component loader', ->
       g = new noflo.Graph
       g.baseDir = root
       n = new noflo.Network g
       chai.expect(n.baseDir).to.equal root
       chai.expect(n.loader.baseDir).to.equal root
+      return
+    return
   describe 'debug setting', ->
     n = null
     g = null
@@ -579,21 +757,31 @@
       g.baseDir = root
       n = new noflo.Network g
       n.loader.listComponents (err, components) ->
-        return done err if err
+        if err
+          done err
+          return
         n.loader.components.Split = Split
         g.addNode 'A', 'Split'
         g.addNode 'B', 'Split'
         g.addEdge 'A', 'out', 'B', 'in'
         n.connect done
+        return
+      return
     it 'should initially have debug enabled', ->
       chai.expect(n.getDebug()).to.equal true
+      return
     it 'should have propagated debug setting to connections', ->
       chai.expect(n.connections[0].debug).to.equal n.getDebug()
+      return
     it 'calling setDebug with same value should be no-op', ->
       n.setDebug true
       chai.expect(n.getDebug()).to.equal true
       chai.expect(n.connections[0].debug).to.equal n.getDebug()
+      return
     it 'disabling debug should get propagated to connections', ->
       n.setDebug false
       chai.expect(n.getDebug()).to.equal false
       chai.expect(n.connections[0].debug).to.equal n.getDebug()
+      return
+    return
+  return


Delete notification-removal callback

Delete notification-removal callback

No longer used since I noticed hitting the ESC key will dismiss them.

--- .config/init.coffee
+++ .config/init.coffee
@@ -9,15 +9,6 @@
 # atom.workspace.observeTextEditors (editor) ->
 #   editor.onDidSave ->
 #     console.log "Saved! #{editor.getPath()}"
-
-
-# Hide scope-related notices shortly after displaying them
-if (delay = atom.config.get("popupDismissDelay"))? and delay isnt false
-	atom.workspace.notificationManager.onDidAddNotification (popup) ->
-		if /^\s*Scopes at Cursor/.test(popup.message)
-			setTimeout (->
-				popup.dismiss()
-			), atom.config.get("popupDismissDelay") || 1000

 # Crude debugging method to see what events we can hook into


Add spec for #308.

Add spec for #308.

--- spec/chromium-spec.coffee
+++ spec/chromium-spec.coffee
@@ -60,3 +60,8 @@
         assert.equal global.changedByIframe, true
         done()
       setTimeout isChanged, 30
+
+  describe 'creating a Uint8Array under browser side', ->
+    it 'does not crash', ->
+      RUint8Array = require('remote').getGlobal 'Uint8Array'
+      new RUint8Array


Use coffeescript's string interpolation

Use coffeescript's string interpolation

--- app/assets/javascripts/trade/controllers/search_results_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/search_results_controller.js.coffee
@@ -139,7 +139,7 @@
         .then(
           # resolve
           (() =>
-            if confirm("This will delete " + @get('total') + " shipments. Are you sure?")
+            if confirm("This will delete #{@get('total')} shipments. Are you sure?")
               $.ajax(
                 url: '/trade/shipments/destroy_batch'
                 type: 'POST'
@@ -147,7 +147,7 @@
                   filters: @get('controllers.search.searchParams')
               )
               .done( (data) =>
-                @flashSuccess(message: 'Successfully deleted ' + data.rows + ' shipments.')
+                @flashSuccess(message: "Successfully deleted #{data.rows} shipments.")
                 @send("dataChanged")
               )
               .fail( (xhr) =>


JStackTemplate: use revive to get group data on ::create and ::update calls

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -7,6 +7,7 @@

   { permit }   = require '../group/permissionset'
   Validators   = require '../group/validators'
+  { revive }   = require './computeutils'

   @trait __dirname, '../../traits/protected'

@@ -124,8 +125,14 @@

   @create = permit 'create stack template',

-    success: (client, data, callback) ->
-
+    success: revive
+
+      shouldReviveClient   : yes
+      shouldReviveProvider : no
+
+    , (client, data, callback) ->
+
+      { group }    = client.r # we have revived JGroup and JUser here ~ GG
       { delegate } = client.connection

       unless data?.title
@@ -271,8 +278,14 @@
       { permission: 'update stack template' }
     ]

-    success: (client, data, callback) ->
-
+    success: revive
+
+      shouldReviveClient   : yes
+      shouldReviveProvider : no
+
+    , (client, data, callback) ->
+
+      { group }    = client.r
       { delegate } = client.connection

       # It's not allowed to change a stack template group or owner


JStackTemplate::build: use payload data in title while cloning

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -669,12 +669,15 @@
       { permission: 'update stack template' }
     ]

-    success: (client, callback) ->
+    success: (client, options, callback) ->
+
+      [ options, callback ] = [ callback, options ]  unless callback
+      options          ?= {}

       cloneData         =
         slug            : @getAt 'slug'
-        title           : "#{@getAt 'title'} - clone"
-        description     : @getAt 'description'
+        title           : options.title ? "#{@getAt 'title'} - clone"
+        description     : options.description ? @getAt 'description'

         config          : @getAt 'config'
         machines        : @getAt 'machines'
@@ -708,7 +711,16 @@
     unless @getAt 'config.verified'
       return callback new KodingError 'Stack needs to be verified first'

-    @clone client, (err, clonedTemplate) ->
+    # TODO: GitHub Payload specific updates, this can be moved some other
+    # place as a middleware ~GG
+    options = {}
+    if payload?.after
+      options.title = "#{@getAt 'title'} - #{payload.after[..10]}"
+      if payload.repository?.url?
+        options.description = \
+          "Auto build for #{payload.repository.url}@#{payload.after}"
+
+    @clone client, options, (err, clonedTemplate) ->
       return callback err  if err
       unless clonedTemplate
         return callback new KodingError 'Failed to clone template'


Don't show Ozaria encouragement modal by default on first view of teacher dashboard

--- app/views/core/CreateAccountModal/teacher/TeacherSignupComponent.coco.coffee
+++ app/views/core/CreateAccountModal/teacher/TeacherSignupComponent.coco.coffee
@@ -39,4 +39,5 @@
       modal?.escape?()

   mounted: ->
-    window.localStorage.setItem('showOzariaEncouragementModal', true)+    # 2020-11-05: Now that we have more Ozaria on the homepage, we don't want to pop it up by default; let them click the banner
+    #window.localStorage.setItem('showOzariaEncouragementModal', true)


:new: Add specs for change

:new: Add specs for change

--- spec/validate-spec.coffee
+++ spec/validate-spec.coffee
@@ -111,3 +111,13 @@
       validate.messages([{type: 'Error', text: 'Well', class: 'error', filePath: '/'}], {name: ''})
       validate.messages([{type: 'Error', html: 'Well', class: 'error', filePath: '/'}], {name: ''})
       validate.messages([{type: 'Error', html: document.createElement('div'), class: 'error', filePath: '/'}], {name: ''})
+    it 'sets name on messages according to linter or message itself', ->
+      linter = {name: 'Wow'}
+
+      message = {type: 'Error', text: 'Some Text'}
+      validate.messages([message], linter)
+      expect(message.linter).toBe('Wow')
+
+      message = {type: 'Error', text: 'Some Text', name: 'Ha!'}
+      validate.messages([message], linter)
+      expect(message.linter).toBe('Ha!')


Projects::init() was not including ProjectMergeRequests.

--- src/Models/Projects.coffee
+++ src/Models/Projects.coffee
@@ -7,6 +7,7 @@
     @issues =  @load 'ProjectIssues'
     @repository = @load 'ProjectRepository'
     @deploy_keys = @load 'ProjectDeployKeys'
+    @merge_requests = @load 'ProjectMergeRequests'

   all: (params={}, fn=null) =>
     if 'function' is typeof params


Restore editing of timeline dates to 'Edit Course Dates' modal

Resolves #390

--- app/assets/javascripts/components/timeline/meetings.cjsx
+++ app/assets/javascripts/components/timeline/meetings.cjsx
@@ -46,7 +46,7 @@

Course Dates

-
1. Confirm the course’s start and end dates.

+
Select the start and end dates of the entire course (not just the Wikipedia assignment).

             <TextInput
               onChange={@updateCourse}
@@ -65,6 +65,32 @@
               type='date'
               label='Course End'
               date_props={minDate: moment(@state.course.start).add(1, 'week')}
+              enabled={@state.course.start?}
+            />
+

+

+

+

+
Select the start and end dates for the Wikipedia assignment timeline. Changing the start date will shift the dates of the entire timeline.

+

+            <TextInput
+              onChange={@updateCourse}
+              value={@state.course.timeline_start}
+              value_key='timeline_start'
+              editable=true
+              type='date'
+              autoExpand=true
+              label='Assignment Start'
+              date_props={timeline_start_props}
+            />
+            <TextInput
+              onChange={@updateCourse}
+              value={@state.course.timeline_end}
+              value_key='timeline_end'
+              editable=true
+              type='date'
+              label='Assignment End'
+              date_props={timeline_end_props}
               enabled={@state.course.start?}
             />


:fire: Nuke Cursor deprecations

:fire: Nuke Cursor deprecations

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -1,7 +1,6 @@
 {Point, Range} = require 'text-buffer'
 {Emitter} = require 'event-kit'
 _ = require 'underscore-plus'
-Grim = require 'grim'
 Model = require './model'

 # Extended: The `Cursor` class represents the little blinking line identifying
@@ -62,18 +61,6 @@
   onDidChangeVisibility: (callback) ->
     @emitter.on 'did-change-visibility', callback

-  on: (eventName) ->
-    return unless Grim.includeDeprecatedAPIs
-
-    switch eventName
-      when 'moved'
-        Grim.deprecate("Use Cursor::onDidChangePosition instead")
-      when 'destroyed'
-        Grim.deprecate("Use Cursor::onDidDestroy instead")
-      else
-        Grim.deprecate("::on is no longer supported. Use the event subscription methods instead")
-    super
-
   ###
   Section: Managing Cursor Position
   ###
@@ -578,7 +565,6 @@
   setVisible: (visible) ->
     if @visible isnt visible
       @visible = visible
-      @emit 'visibility-changed', @visible if Grim.includeDeprecatedAPIs
       @emitter.emit 'did-change-visibility', @visible

   # Public: Returns the visibility of the cursor.
@@ -698,12 +684,3 @@
         position = range.start
         stop()
     position
-
-if Grim.includeDeprecatedAPIs
-  Cursor::getScopes = ->
-    Grim.deprecate 'Use Cursor::getScopeDescriptor() instead'
-    @getScopeDescriptor().getScopesArray()
-
-  Cursor::getMoveNextWordBoundaryBufferPosition = (options) ->
-    Grim.deprecate 'Use `::getNextWordBoundaryBufferPosition(options)` instead'
-    @getNextWordBoundaryBufferPosition(options)


Add new line after "Pending changes for #{@app} #{@env}".

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -17,7 +17,7 @@

     pending = pulsarApi.createJob(app, env, 'deploy:pending')
     pending.on('close', ()->
-      chat.send "Pending changes for #{@app} #{@env}: #{@data.stdout}"
+      chat.send "Pending changes for #{@app} #{@env}:\n#{@data.stdout}"
       return if(@data.status != 'FINISHED')
       deploy = pulsarApi.createJob(app, env, 'deploy')
       deploy.on('create', () ->
@@ -42,7 +42,7 @@
     return unless robot.isAuthorized(chat)
     job = pulsarApi.createJob(chat.match[1], chat.match[2], 'deploy:pending')
     job.on('close', () ->
-      chat.send "Pending changes for #{@app} #{@env}: #{@data.stdout}"
+      chat.send "Pending changes for #{@app} #{@env}:\n#{@data.stdout}"
     ).on('error', (error)->
       chat.send "#{@} failed due to #{JSON.stringify(error)}"
     )


Fix docs for action performing.

Fix docs for action performing.

--- src/OVirtConnection.coffee
+++ src/OVirtConnection.coffee
@@ -100,6 +100,8 @@
   # @param action [ApiNodes.OVirtAction] action object
   # @param callback [Function]
   #
+  # @return [ApiNodes.OVirtApiNode] current node or failure report
+  #
   performAction: (target, action, callback) ->
     bodyDoc = new Document
     body = bodyDoc.node('action').toString()


errors: clean up

errors: clean up

prettier w/ CoffeeScript classes

--- src/errors.coffee
+++ src/errors.coffee
@@ -12,8 +12,6 @@
 # Base class for our well-defined error conditions
 class ServerError extends Error
     constructor: (message) ->
-        #super/Error.apply this, arguments
-
         # Isn't message set by Error()?
         @message = message

@@ -29,30 +27,39 @@
         errorEl

 ##
-# Creates the subclasses of ServerError
-makePrototype = (condition, type) ->
-    p = ->
-        ServerError.apply this, arguments
-    inherits p, ServerError
+# The actual exported error classes

-    if condition
-        p.prototype.condition = condition
-    if type
-        p.prototype.type = type
-    p
+class exports.Forbidden
+    condition: "forbidden"
+    type: "auth"
+
+class exports.Conflict
+    condition: "conflict"
+    type: "cancel"
+
+class exports.BadRequest
+    condition: "bad-request"
+    type: "modify"
+
+class exports.FeatureNotImplemented
+    condition: "feature-not-implemented"
+    type: "cancel"
+
+class exports.InternalServerError
+    condition: "internal-server-error"
+    type: "cancel"
+
+class exports.NotFound
+    condition: "item-not-found"
+    type: "cancel"
+
+class exports.NotAllowed
+    condition: "not-allowed"
+    type: "cancel"

 ##
-# The actual exported error classes
-module.exports =
-    Forbidden: makePrototype("forbidden", "auth")
-    Conflict: makePrototype("conflict", "cancel")
-    BadRequest: makePrototype("bad-request", "modify")
-    FeatureNotImplemented: makePrototype("feature-not-implemented", "cancel")
-    InternalServerError: makePrototype("internal-server-error", "wait")
-    NotFound: makePrototype("item-not-found", "cancel")
-    NotAllowed: makePrototype("not-allowed", "cancel")
-
-class module.exports.StanzaError extends Error
+# For wrapping errors from remote
+class exports.StanzaError extends Error
     constructor: (stanza) ->
         @el = stanza.getChild('error')
         @message = @el?.children[0]?.name


Fix typo

Fix typo

--- server/materials/data/news.coffee
+++ server/materials/data/news.coffee
@@ -4,7 +4,7 @@
 export default allNews = () ->
     return news([
         newsItem("Добавлена информация про районную олимпиаду", String.raw"""
-            Добавлена информиация про районную олимпиаду. Также обновлен архив старых районных олимпиад.
+            Добавлена информация про районную олимпиаду. Также обновлен архив старых районных олимпиад.
         """),

         newsItem("С 23 ноября возобновляются очные занятия в лицее 40", String.raw"""


remove debug statement

remove debug statement

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -23,7 +23,7 @@
         #@setupEditRoute()

     reset: ->
-        console.log("reset")
+        #console.log("reset")
         @_sess("recordCount", "...")
         @_sess('skip', 0)
         @_sess('sortColumn', @sortColumn)


exposed geometries as part of callback data

--- bokehjs/src/coffee/models/tools/gestures/tap_tool.coffee
+++ bokehjs/src/coffee/models/tools/gestures/tap_tool.coffee
@@ -20,14 +20,17 @@
     }

     callback = @mget("callback")
+    @_save_geometry(geometry, final, append)
+
+    cb_data =
+      geometries: @plot_model.get('tool_events').get('geometries')

     for r in @mget('renderers')
       ds = r.get('data_source')
       sm = ds.get('selection_manager')
       sm.select(@, @plot_view.renderers[r.id], geometry, final, append)
-      if callback? then callback.execute(ds)
+      if callback? then callback.execute(ds, cb_data)

-    @_save_geometry(geometry, final, append)
     @plot_view.push_state('tap', {selection: @plot_view.get_selection()})

     return null


Added back graphics require

Added back graphics require

--- src/io/tty.coffee
+++ src/io/tty.coffee
@@ -10,6 +10,7 @@
 _ = require 'lodash'
 Q = require 'q'

+graphics = require './graphics-ascii'
 {whilst, bresenhamLine, arrayRemove, repeatStr: repeat} = require '../util'

 # Initialize log


Make method into private one

Make method into private one

--- api/apps/articles/model/save.coffee
+++ api/apps/articles/model/save.coffee
@@ -323,7 +323,7 @@
     full: url: crop(imageSrc, { width: 1200, height: 706 } )
     thumb: url: crop(imageSrc, { width: 900, height: 530 } )
   html = if article.send_body then getTextSections(article) else ''
-  @cleanArticlesFromSailthru article.slugs
+  cleanArticlesInSailthru article.slugs
   sailthru.apiPost 'content',
     url: "#{FORCE_URL}/article/#{_.last(article.slugs)}"
     date: article.published_at
@@ -343,7 +343,7 @@
     debug err if err
     cb()

-@cleanArticlesFromSailthru = (slugs = []) ->
+cleanArticlesInSailthru = (slugs = []) ->
   if slugs.length > 1
     _.map slugs, (slug, i) =>
       unless i is slugs.length - 1


Finished logger module

Finished logger module

--- src/logger.coffee
+++ src/logger.coffee
@@ -0,0 +1,84 @@
+_ = require 'lodash'
+assert = require 'assert'
+readline = require 'readline'
+
+styler = require './styler'
+{ verbose } = require './cli'
+
+module.exports = @
+
+# Constants
+[INFO,WARN,ERROR,VERBOSE] = TAGS = ["INFO", "WARN", "ERROR", "VERBS"]
+LEFT_MARGIN = _.maxBy(TAGS, _.size).length + 1
+TAG_INDICATOR = ': '
+LEFT_PADSTRING_LONG_LINES = _.repeat(" ", LEFT_MARGIN + TAG_INDICATOR.length)
+MAX_COLS_PER_LINE = Math.max 10, process.stdout.columns - LEFT_PADSTRING_LONG_LINES.length - 1
+
+# Private methods
+tag = (txt, style) -> style(_.padEnd(txt, LEFT_MARGIN)) + TAG_INDICATOR
+
+fold = (str, cols = MAX_COLS_PER_LINE) ->
+    assert cols > 0 # SANITY-CHECK
+    if str.length <= cols # Base case
+         str
+    else # Recursive case
+        cutString = str[...cols]
+        lastSpace = cutString.lastIndexOf(' ')
+
+        # This if-else is in order to avoid cutting a word in half in case it's possible
+        if lastSpace >= 0
+            cutString[...lastSpace] + "\n" +
+            LEFT_PADSTRING_LONG_LINES +
+            fold(str[lastSpace+1..])
+        else # Quite strange there's no space in 70 character string...
+            cutString[...-1] + "-\n" + LEFT_PADSTRING_LONG_LINES + fold(str[cols-1..])
+
+createWriter = (txt, styler, stream = process.stdout) => (msg, endline = true) =>
+        msg += "\n" if endline
+        stream.write tag(txt, styler) + fold(msg)
+        @
+
+# Exposed methods
+@endLine = => console.log(""); @
+
+@eraseLine = =>
+    readline.clearLine(process.stdout)
+    readline.cursorTo(process.stdout, 0)
+    @
+
+@verbose = @v = if verbose? then createWriter VERBOSE, styler.verbose else => @
+
+@info = @i = createWriter INFO, styler.info
+
+@updateInfo = (msg) => @eraseLine(); @info(msg, false); @
+
+@warn = @w = createWriter WARN, styler.warn
+
+@error = @e = do ->
+    errorWriter = createWriter ERROR, styler.error, process.stderr
+    (msg, end = true, exitCode = 1) ->
+        ret = errorWriter msg
+        process.exit exitCode if end
+        ret
+
+
+# Testing Code
+###
+this
+    .i("Thisisastrangelongsentencewithnospacesanditshouldbecutwithahyphenbecause\
+        theresnospaceanditcannotbereadcorrectlyiftheterminalistoonarrow.")
+    .w("This is a normal sentence which has a lot of columns, so it should be
+        cut cause it's too long to be read correctly in the terminal.")
+    .warn("This is a warning")
+    .error("An error occurred", false)
+    .info("Be informed")
+    .verbose("Don't talk so much")
+    .updateInfo("My name is albert")
+    .updateInfo("My name is joan")
+    .endLine()
+
+process.stdout.write "Shouldn't appear"; @eraseLine()
+
+@error("This ends with error code 2", true, 2)
+@info("Should'nt be printed")
+###


fix($tests): fix broken tests due to supposed inexistant factory

One test was testing that factory `foo` didn't exist but another test
was successfully defining it.

--- specs/units/factories.spec.coffee
+++ specs/units/factories.spec.coffee
@@ -137,13 +137,13 @@

 runningSpecs('a factory without a class')
 .shouldStopWith /no class provided/, ->
-  factory 'foo', ->
+  factory 'no_class', ->

 runningSpecs('a factory extending an unexistant factory')
 .shouldStopWith /parent factory 'bar' can't be found/, ->
-  factory 'foo', extends: 'bar', ->
+  factory 'no_parent', extends: 'bar', ->

 runningSpecs('a factory including an unexistant mixin')
 .shouldStopWith /mixin 'bar' can't be found/, ->
-  factory 'foo', class: Object, ->
+  factory 'undefined_mixin', class: Object, ->
     include 'bar'


Remove forgotten console.log in EmailValidator :-/

--- src/model/validations/email_validator.coffee
+++ src/model/validations/email_validator.coffee
@@ -7,7 +7,6 @@
   emailRegexp: /[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*/

   constructor: (options) ->
-    console.log @emailRegexp
     super

   validateEach: (errors, record, key, callback) ->


Use Time component for displaying when task was last worked

--- src/components/scores/concept-coach-cell.cjsx
+++ src/components/scores/concept-coach-cell.cjsx
@@ -1,19 +1,19 @@
 React  = require 'react'
 moment = require 'moment'

+Time = require '../time'
 CellStatusMixin = require './cell-status-mixin'

 ConceptCoachCell = React.createClass

   mixins: [CellStatusMixin] # prop validation

-  lastWorked: ->
-    moment(@props.task.last_worked_at).format('MMM. D')
-
   render: ->

{@props.task.correct_exercise_count} of {@props.task.exercise_count}

-
{@lastWorked()}

+

+        <Time format='MMM. D' date={@props.task.last_worked_at} />
+


reduce some logging in the hub and drop messages if we are getting too many (adjust thresh)

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -45,11 +45,11 @@

 # Anti DOS parameters:
 # If a client sends a burst of messages, we space handling them out by this many milliseconds:.
-MESG_QUEUE_INTERVAL_MS  = 50
+MESG_QUEUE_INTERVAL_MS  = 150
 #MESG_QUEUE_INTERVAL_MS  = 20
 # If a client sends a burst of messages, we discard all but the most recent this many of them:
 #MESG_QUEUE_MAX_COUNT    = 25
-MESG_QUEUE_MAX_COUNT    = 150
+MESG_QUEUE_MAX_COUNT    = 60
 # Any messages larger than this is dropped (it could take a long time to handle, by a de-JSON'ing attack, etc.).
 MESG_QUEUE_MAX_SIZE_MB  = 7

@@ -2781,6 +2781,11 @@
                 @error_to_client(id:mesg.id, error:"message must be defined")
                 return

+            if mesg.message.event == 'project_exec' and mesg.message.command == "ipython-notebook"
+                # we just drop these messages, which are from old non-updated clients (since we haven't
+                # written code yet to not allow them to connect -- TODO!).
+                return
+
             # It's extremely useful if the local hub has a way to distinguish between different clients who are
             # being proxied through the same hub.
             mesg.message.client_id = @id
@@ -4535,7 +4540,7 @@
         @project = bup_server.get_project(@project_id)

     dbg: (m) =>
-        winston.debug("local_hub(#{@project_id}): #{misc.to_json(m)}")
+        #winston.debug("local_hub(#{@project_id}): #{misc.to_json(m)}")

     close: (cb) =>
         @dbg("close")
@@ -5084,7 +5089,7 @@
             multi_response : false
             timeout : 15
             cb      : undefined
-        @dbg("call")
+        #@dbg("call")
         @_fixpath(opts.mesg)
         opts.mesg.project_id = @project_id
         @local_hub.call(opts)


port forwarding -- got the new url schema support to seemingly fully work (but with a database hit every time) in the hub.

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -266,21 +266,53 @@

 httpProxy = require('http-proxy')

-
-# TODO -- should probably have a separate proxy_port option.
+init_http_proxy_server0 = () =>
+    httpProxy.createServer(8888, '10.2.1.4').listen(program.proxy_port, program.host)
+
+
+init_http_proxy_server1 = () =>
+
+    http_proxy_server = httpProxy.createServer (req, res, proxy) ->
+        proxy.proxyRequest req, res, {host:'10.1.4.4', port:8888}
+
+
+    http_proxy_server.listen(program.proxy_port, program.host)
+
+    http_proxy_server.on 'upgrade', (req, socket, head) ->
+        http_proxy_server.proxy.proxyWebSocketRequest(req, socket, head, {host:'10.1.4.4', port:8888})
+
 init_http_proxy_server = () =>
-    #httpProxy.createServer(program.port, program.host).listen(program.port+1, program.host)
-    httpProxy.createServer(8888, '10.1.4.4').listen(program.port+1, program.host)
-
-init_http_proxy_server0 = () =>
-    #httpProxy.createServer(program.port, program.host).listen(program.port+1, program.host)
-    proxy_server = httpProxy.createServer(8888, '10.1.4.4')
-
-    proxy_server.listen(program.port+1, program.host)
-
-    proxy_server.on 'upgrade', (req, socket, head) ->
-        winston.debug("Proxy server upgrade!!")
-        proxy_server.proxy.proxyWebSocketRequest(req, socket, head)
+
+    target = (url, cb) ->
+        v          = url.split('/')
+        project_id = v[2]
+        port       = parseInt(v[3])
+        database.get_project_location
+            project_id : project_id
+            cb         : (err, location) ->
+                #winston.debug("DATABASE location response: #{err}, #{misc.to_json(location)}")
+                if err
+                    cb(err)
+                else
+                    cb(err, {host:location.host, port:port})
+
+    http_proxy_server = httpProxy.createServer (req, res, proxy) ->
+        buffer = httpProxy.buffer(req)  # see http://stackoverflow.com/questions/11672294/invoking-an-asynchronous-method-inside-a-middleware-in-node-http-proxy
+        target req.url, (err, location) ->
+            if err
+                res.writeHead(404)
+                res.end()
+            else
+                proxy.proxyRequest req, res, {host:location.host, port:location.port, buffer:buffer}
+
+    http_proxy_server.listen(program.proxy_port, program.host)
+
+    http_proxy_server.on 'upgrade', (req, socket, head) ->
+        target req.url, (err, location) ->
+            if err
+                winston.debug("websocket upgrade error --  this shouldn't happen since upgrade would only happen after normal thing *worked*. #{err}")
+            else
+                http_proxy_server.proxy.proxyWebSocketRequest(req, socket, head, location)

 #############################################################
@@ -4755,6 +4787,7 @@
 #############################################
 program.usage('[start/stop/restart/status] [options]')
     .option('-p, --port <n>', 'port to listen on (default: 5000)', parseInt, 5000)
+    .option('-p, --proxy_port <n>', 'port that the proxy server listens on (default: 5001)', parseInt, 5001)
     .option('-l, --log_level [level]', "log level (default: INFO) useful options include WARNING and DEBUG", String, "INFO")
     .option('--host [string]', 'host of interface to bind to (default: "127.0.0.1")', String, "127.0.0.1")
     .option('--pidfile [string]', 'store pid in this file (default: "data/pids/hub.pid")', String, "data/pids/hub.pid")