Behaviour: Fix mouse lock.

Behaviour: Fix mouse lock.

--- engine/behaviour.coffee
+++ engine/behaviour.coffee
@@ -421,7 +421,7 @@
                 addListener root, 'mouseout', @_on_pointer_move, options
             if @on_pointer_down?
                 removeListener root, 'pointerdown', @_on_pointer_down, options
-                addListener root, 'mouserdown', @_on_pointer_down, options
+                addListener root, 'mousedown', @_on_pointer_down, options
             if @on_pointer_up?
                 removeListener window, 'pointerup', @_on_pointer_up, options
                 addListener window, 'mouseup', @_on_pointer_up, options
@@ -434,7 +434,7 @@
                 addListener window, 'pointermove', @_on_pointer_move, options
                 addListener window, 'pointerout', @_on_pointer_move, options
             if @on_pointer_down?
-                removeListener root, 'mouserdown', @_on_pointer_down, options
+                removeListener root, 'mousedown', @_on_pointer_down, options
                 addListener root, 'pointerdown', @_on_pointer_down, options
             if @on_pointer_up?
                 removeListener window, 'mouseup', @_on_pointer_up, options


activity: blur from input after submit

activity: blur from input after submit

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -51,6 +51,7 @@

     @input.on 'Escape', @bound 'reset'
     @input.on 'Enter',  @bound 'submit'
+    @input.on 'Enter', => @input.setBlur()

     @on 'SubmitStarted', => @hidePreview()  if @preview

@@ -204,7 +205,9 @@
     @unsetClass "preview-active"

-  focus: -> @input.focus()
+  focus: ->
+    @input.setFocus()
+

   viewAppended: ->


Client: Activity: createdAt parameter added to editMessage action for showing 'edited' label

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -167,21 +167,28 @@
  * @param {string} body
  * @param {object=} payload
 ###
-editMessage = (messageId, body, payload = {}) ->
-
-  { socialapi } = kd.singletons
-  { EDIT_MESSAGE_BEGIN
-    EDIT_MESSAGE_SUCCESS
-    EDIT_MESSAGE_FAIL } = actionTypes
-
-  dispatch EDIT_MESSAGE_BEGIN, { messageId, body, payload }
-
-  socialapi.message.edit {id: messageId, body, payload}, (err, message) ->
-    if err
-      dispatch EDIT_MESSAGE_FAIL, { err, messageId }
-      return
-
-    dispatch EDIT_MESSAGE_SUCCESS, { message, messageId }
+editMessage = (messageId, body, createdAt, payload = {}) ->
+
+    {socialapi} = kd.singletons
+    { EDIT_MESSAGE_BEGIN
+      EDIT_MESSAGE_SUCCESS
+      EDIT_MESSAGE_FAIL
+    } = actionTypes
+
+    dispatch EDIT_MESSAGE_BEGIN, { messageId, body, payload }
+
+    socialapi.message.edit {id: messageId, body, payload}, (err, message) ->
+      if err
+        dispatch EDIT_MESSAGE_FAIL, { err, messageId }
+        return
+
+      kd.utils.wait 673, ->
+        socialapi.message.byId {id: messageId}, (err, message) ->
+          message.body = body
+          message.createdAt = createdAt
+          dispatch EDIT_MESSAGE_SUCCESS, {
+            message, messageId
+          }

 ###*


video: update helper to indicate publisher as well

--- client/app/lib/videocollaboration/helper.coffee
+++ client/app/lib/videocollaboration/helper.coffee
@@ -143,21 +143,21 @@

 ###*
- * Subscribes to audio changes of given subscriber. It will call given
+ * Subscribes to audio changes of given subscriber/publisher. It will call given
  * `callbacks.started` when talking started, and will call `callbacks.stopped`
  * when talking stopped.
  *
- * @param {OT.Subscriber} subscriber
+ * @param {(OT.Subscriber|OT.Publisher)} participant
  * @param {object<string, function>} callbacks
 ###
-subscribeToAudioChanges = (subscriber, callbacks) ->
+subscribeToAudioChanges = (participant, callbacks) ->

   # this object will be used to keep track of talking activity.
   activity = null

-  subscriber.on 'audioLevelUpdated', (event) ->
+  participant.on 'audioLevelUpdated', (event) ->
     now = Date.now()
-    # we detected a sound from subscriber
+    # we detected a sound from participant
     if event.audioLevel > 0.1
       # create initial activity with talking flag is off when there is no
       # talking activity.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_state_cameras.coffee
+++ src/game_state_cameras.coffee
@@ -2,7 +2,7 @@
   cameras = [Camera()]

   self.bind 'afterUpdate', ->
-    self.cameras().each (camera) ->
+    self.cameras().invoke 'trig'
       camera.trigger('afterUpdate')

   self.bind 'draw', (canvas) ->


Removed commented out code.

Removed commented out code.

--- core/app/backbone/views/top_channel_view.coffee
+++ core/app/backbone/views/top_channel_view.coffee
@@ -9,11 +9,6 @@

   onRender: ->
     suggested_topics        = new SuggestedTopics([@model.topic()])
-    # user_channel_collection = new Backbone.Collection
-
-    # suggestions = new FilteredSuggestedTopicsView
-    #                         suggested_topics: suggested_topics
-    #                         addToCollection: user_channel_collection

     add_back_button = new AddChannelToChannelsButtonView
                                 model: @model


change JSMenuView's background color

change JSMenuView's background color

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -7,28 +7,28 @@
     constructor:(frame = JSRectMake(0, 0, 200, 0))->
         super(frame)
         @_textSize = 10
-        @_backgroundColor = JSColor("clearColor")
+        @_backgroundColor = JSColor("white")
         @_clipToBounds = false
         @_containment = false

     addTarget:(@_action)->
-
+
     addMenuItem:(@_menuitem)->
         if (!@_menuitem?)
             return
-
+
         if (!$(@_viewSelector).length)
             return
-
+
         @_div = "

"
         menustr = ""
         for disp in @_menuitem
-            menustr += "
"+disp+"
"
+            menustr += "
"+disp+"
"
         @_div = @_div.replace(//, menustr)
-
+
         if ($(@_viewSelector+"_menu").length)
             $(@_viewSelector+"_menu").remove()
-
+
         $(@_viewSelector).append(@_div)
         $(@_viewSelector+"_menu").css("left", "0px")
         $(@_viewSelector+"_menu").css("top", "0px")


fix #18

fix #18

--- tasks/shimmer.coffee
+++ tasks/shimmer.coffee
@@ -143,15 +143,17 @@
 		req = @data.require

 		createRequire = ->
-			return if !req
-
-			req = 'bootstrap.coffee' if req is 'NGBOOTSTRAP'
-			template = getTemplate 'bootstrap.coffee'
-			compiled = grunt.template.process template
-			dest = path.resolve cwd, req
-
-			removeFromFiles req
-			grunt.file.write dest, compiled
+			if req is 'NGBOOTSTRAP'
+				req = 'bootstrap.coffee'
+				template = getTemplate 'bootstrap.coffee'
+				compiled = grunt.template.process template
+				dest = path.resolve cwd, req
+
+				removeFromFiles req
+				console.log "writing file #{dest}"
+				grunt.file.write dest, compiled
+			else
+				req = 'bootstrap.coffee'

 		createRequire()

@@ -207,6 +209,7 @@
 		processShim = ->
 			template = getTemplate mainFileName
 			trimmedRequire = getFileNameWithoutExtension req
+			console.log 'trimmedRequire', trimmedRequire

 			config =
 				shim: JSON.stringify(shim)


compare_hands(s) returns null if bad input is given

--- KataPokerHands.coffee
+++ KataPokerHands.coffee
@@ -8,8 +8,13 @@

     p = [null, {}, {}] # players 1 and 2

-    [p[1].name, p[1].hand] = regex_hand.exec(s).slice(1)
-    [p[2].name, p[2].hand] = regex_hand.exec(s).slice(1)
+    p1 = regex_hand.exec(s)
+    p2 = regex_hand.exec(s)
+
+    return null if !p1? || !p2?
+
+    [p[1].name, p[1].hand] = p1.slice(1)
+    [p[2].name, p[2].hand] = p2.slice(1)

     p[1].hand = parse_hand(p[1].hand)
     p[2].hand = parse_hand(p[2].hand)


Another minor cleanup

Another minor cleanup

--- src/touchclick.coffee
+++ src/touchclick.coffee
@@ -69,10 +69,9 @@
       $el[type] "touchmove mouseout", touchmove
       $el[type] "touchend mouseup", touchend

-  $.event.special.touchclick = {
+  $.event.special.touchclick =
     setup: ->
       events.call this,"on"

     teardown: ->
       events.call this,"off"
-  }


pipe: fix test

pipe: fix test

--- test/api.pipe.coffee
+++ test/api.pipe.coffee
@@ -1,4 +1,5 @@

+fs = require 'fs'
 generate = require 'csv-generate'
 parse = require '../lib'


added overall comment on what's the scheme of how we chose which parameters are passed as UNevaluated/evaluated

--- fizzylogo_populateMethods.coffee
+++ fizzylogo_populateMethods.coffee
@@ -1,3 +1,39 @@
+# Overall comments -------------------------------------------
+
+# ----
+# in the "for" and in the "repeat" and in all the branches in the if-then-else,
+# the loopCode/branch is passed as UNevaluated.
+# This is because if it were passed as evaluated, it'd have to be passed as
+#   repeat 3 'code
+# which would turn it into a closure.
+# Since out closures are read-only, that usually creates chaos, because
+# you are very likely to reference several variables from outside the
+# loop, and "closing" those as read only is problematic for example
+# this never ends, because the "a" inside the branch "a=a minus 1" is
+# replaced with a 5 when closed, so it never decreases to zero!
+#
+#  a=5
+#
+#  repeat forever:
+#  ﹍if a==0:
+#  ﹍﹍done
+#  ﹍else:
+#  ﹍﹍a=a minus 1
+#  a print
+#
+# On the other hand, in "to" and "answer", the code block is passed as
+# evaluated so it's actually closed (in read-only mode). This is
+# because in those cases one is less prone towards changing variables
+# from the outer scope. You can read them, but you can't write them.
+#
+# ----
+# all signatures are passed as unevaluated. This is for two reasons:
+# 1) we absolutely don't want to risk to close a parameter
+# 2) it would really never happen that one passes the signature
+#    from a parameter. That actually only happens in one place
+#    and in fact we need answerEvalParams for that case.
+
+
 # helper to add default methods -------------------------------------------


Style the test toolbar for debugging

Style the test toolbar for debugging

--- test/src/test.coffee
+++ test/src/test.coffee
@@ -7,5 +7,17 @@
 #= require_tree ./unit

 document.addEventListener "DOMContentLoaded", ->
-  container = """

"""
+  container = """
+

+  """
   document.body.insertAdjacentHTML("afterbegin", container)
+
+  styles = """
+
+  """
+  document.head.insertAdjacentHTML("beforeend", styles)


add order my total money

add order my total money

--- src/hubot-order-menu.coffee
+++ src/hubot-order-menu.coffee
@@ -11,7 +11,7 @@
 # hubot order stores - show all store info.
 #   hubot order <category> (<note> )$<money> - make an order.
 #   hubot order <category> (<note> )$<money> for @someone - make an order for someone.
-#   hubot order my - show your order.
+#   hubot order total - show your order.
 #   hubot order category <category> - show category's orders and calculate total money needed.
 #   hubot order all - show all orders and calculate total money needed.
 # hubot order reset all - reset all orders.
@@ -103,16 +103,26 @@

   robot.respond /order my/i, (msg) ->
     commands = new CommandStore msg
+    categoryMoney = 0
+    totalMoney = 0
     flag = false
     user = msg.message.user.name
     for categoryName, category of robot.brain.data.order
       for userName, order of category when userName is user
         if order?
           commands.Add order
+          categoryMoney += parseInt( order.money, 10 )
+          totalMoney += parseInt( order.money, 10 )
           flag = true
+      if categoryMoney isnt 0
+        commands.Add "Category ${categoryName} total : #{categoryMoney}"
+        categoryMoney = 0
+        flag = true

     if (flag isnt true)
       commands.Add "you have no order."
+    else
+      commands.Add "Total money : #{totalMoney}"
     commands.Send()

   robot.respond /order cate(?:g?o?r?y?) (\S*)/i, (msg) ->
@@ -128,7 +138,7 @@
           totalMoney += parseInt( order.money, 10 )
           flag = true
       if totalMoney isnt 0
-        commands.Add "Total money : #{totalMoney}"
+        commands.Add "Category ${categoryName} total : #{totalMoney}"
         totalMoney = 0
         flag = true

@@ -146,7 +156,7 @@
           commands.Add order
           totalMoney += parseInt( order.money, 10 )
       if totalMoney isnt 0
-        commands.Add "Total money : #{totalMoney}"
+        commands.Add "Category ${categoryName} total : #{totalMoney}"
         totalMoney = 0
         flag = true


disable beta flag

disable beta flag

--- src/my.coffee
+++ src/my.coffee
@@ -43,7 +43,7 @@
 COMMAND_BACKGROUND = 'lightgrey'

 exports.my = {
-  beta: true
+  beta: false
   test: false #is_local
   online: _?
   inspect: inspect


Correcting typo

Correcting typo

Progogation probably was meant to be propagation

--- src/sigs.iced
+++ src/sigs.iced
@@ -277,7 +277,7 @@

   do_recheck : (i) ->
     log.info "We couldn't find a DNS proof for #{@remote_host}.....#{colors.bold('yet')}"
-    log.info "DNS progogation can be slow; we'll keep trying and email you the result."
+    log.info "DNS propagation can be slow; we'll keep trying and email you the result."
     false

 #===========================================


Remove debug trace.

Remove debug trace.

--- src/xo.coffee
+++ src/xo.coffee
@@ -35,7 +35,6 @@
 class $Acl extends $Model
   @create: (subject, object) ->
     return $Acl.hash(subject, object).then((hash) ->
-      console.log(hash)
       return new $Acl {
         id: hash
         subject


Minor style fix (Sign in link)

Minor style fix (Sign in link)

--- client/app/MainApp/maincontroller/mainviews/mainview.coffee
+++ client/app/MainApp/maincontroller/mainviews/mainview.coffee
@@ -120,7 +120,8 @@

     @header.getElement().innerHTML = ''

-    @header.addSubView @logo = new KDCustomHTMLView
+    @header.addSubView wrapper = new KDView
+    wrapper.addSubView @logo = new KDCustomHTMLView
       tagName   : "a"
       domId     : "koding-logo"
       cssClass  : if entryPoint?.type is 'group' then 'group' else ''
@@ -130,7 +131,7 @@
         homeRoute = if KD.isLoggedIn() then "/Activity" else "/Home"
         KD.getSingleton('router').handleRoute homeRoute, {entryPoint}

-    @header.addSubView loginLink = new CustomLinkView
+    wrapper.addSubView loginLink = new CustomLinkView
       domId       : 'header-sign-in'
       title       : 'Already a user? Sign in.'
       icon        :


Cleanup span

Cleanup span

--- client/apps/settings/client/curations/venice_section.coffee
+++ client/apps/settings/client/curations/venice_section.coffee
@@ -154,8 +154,7 @@
           name: 'description'
         }
       div { className: 'field-group' },
-        label {},
-          span {}, 'SEO Description'
+        label {}, 'SEO Description'
         input {
           className: 'bordered-input'
           placeholder: 'SEO Description'


payment: reorder predicates to remove an unnecessary server roundtrip

--- client/landing/app/MainApp/payment/paymentcontroller.coffee
+++ client/landing/app/MainApp/payment/paymentcontroller.coffee
@@ -68,19 +68,16 @@
         JPaymentSubscription.fetchUserSubscriptions (err, subs) ->
           findActiveSubscription subs, planCode, callback

-  confirmPayment: ->
-    console.error 'confirm payment is removed'
-
   makePayment: (paymentMethodId, plan, amount) ->
     vmController = KD.getSingleton('vmController')

-    if 'string' is typeof plan
+    if amount is 0
+      vmController.createGroupVM type, plan.code or plan
+
+    else if 'string' is typeof plan
       @fetchPlanByCode plan, (err, planObj) =>
-        return console.error err  if err
+        return  if KD.showError err
         @makePayment paymentMethodId, planObj, amount
-
-    else if amount is 0
-      vmController.createGroupVM type, plan.code

     # else if type in ['group', 'expensed']
     #   paymentMethod = { plan: plan.code, multiple: yes }


Updated modal resizeHandler.

Updated modal resizeHandler.

--- src/directives/modal.coffee
+++ src/directives/modal.coffee
@@ -130,7 +130,7 @@
       $scope.escapeKeyHandler = (event) ->
         modal.hide() if event.which is keys.ESCAPE

-      $scope.resizeHandler =  (event) -> modal.resize modal.opened
+      $scope.resizeHandler =  (event) -> modal.resize()
       $scope.overlayHandler = (event) -> $scope.closeModal()

       $scope.bindingEvents = (action = "bind") ->


The rest of schema.

The rest of schema.

--- lib/documents/annotation.coffee
+++ lib/documents/annotation.coffee
@@ -29,6 +29,9 @@
   #     slug
   #     givenName
   #     familyName
+  #     gravatarHash
+  #     user
+  #       username
   #   tags: list of
   #     _id
   #     name: ISO 639-1 dictionary


Guard against null element

Guard against null element

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -71,7 +71,7 @@
   adjustSelectionInDirection: (direction) ->
     selection = window.getSelection()
     focusElement = DOM.findElementForContainerAtOffset(selection.focusNode, selection.focusOffset)
-    return unless focusElement.isContentEditable
+    return unless focusElement?.isContentEditable

     selectionNeedsAdjustment = =>
       result = if focusElement.trixCursorTarget and @previousFocus?


Additional confirmations for console view

--- app/modules/console/index.coffee
+++ app/modules/console/index.coffee
@@ -17,7 +17,7 @@
       url: '/consoles/:id'
       controller: 'ConsoleCtrl'
       template: require './view'
-  .controller 'ConsoleCtrl', ($scope, $stateParams, xoApi, xo, xoHideUnauthorizedFilter) ->
+  .controller 'ConsoleCtrl', ($scope, $stateParams, xoApi, xo, xoHideUnauthorizedFilter, modal) ->
     {id} = $stateParams
     {get} = xoApi

@@ -71,8 +71,18 @@
     )

     $scope.startVM = xo.vm.start
-    $scope.stopVM = xo.vm.stop
-    $scope.rebootVM = xo.vm.restart
+    $scope.stopVM = (id) ->
+      modal.confirm
+        title: 'VM shutdown'
+        message: 'Are you sure you want to shutdown this VM ?'
+      .then ->
+        xo.vm.stop id
+    $scope.rebootVM = (id) ->
+      modal.confirm
+        title: 'VM reboot'
+        message: 'Are you sure you want to reboot this VM ?'
+      .then ->
+        xo.vm.restart id

     $scope.eject = ->
       xo.vm.ejectCd id


Show translator role to Admins

Show translator role to Admins

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -5,6 +5,7 @@
 apiClient = require 'panoptes-client/lib/api-client'
 talkClient = require 'panoptes-client/lib/talk-client'
 projectSection = require '../../talk/lib/project-section'
+isAdmin = require '../../lib/is-admin'

 ID_PREFIX = 'LAB_COLLABORATORS_PAGE_'

@@ -47,8 +48,7 @@
     creating: false

   render: ->
-    if @props.project.experimental_tools and @props.project.experimental_tools.indexOf('translator-role') > -1
-      POSSIBLE_ROLES = Object.assign({}, POSSIBLE_ROLES, {translator: 'translator'});
+    @showTranslatorRole()

     style = if @state.creating
       opacity: 0.5
@@ -78,6 +78,10 @@
         </p>
       </form>
     </div>
+
+  showTranslatorRole: ->
+    if (@props.project.experimental_tools?.indexOf('translator-role') > -1) or isAdmin()
+      POSSIBLE_ROLES = Object.assign({}, POSSIBLE_ROLES, {translator: 'translator'});

   handleSubmit: (e) ->
     e.preventDefault()


Improve weather.coffee a little

Improve weather.coffee a little

--- examples/weather.coffee
+++ examples/weather.coffee
@@ -32,12 +32,12 @@
                     el = e.querySelector s
                     if el then el.attributes.data.value else undefined

-                console.log "City: #{data 'weather > forecast_information > city'}"
-                console.log "Current condition: #{data 'weather > current_conditions > condition'}"
-                console.log "Temperature: #{data 'weather > current_conditions > temp_f'} F"
-                console.log data 'weather > current_conditions > humidity'
-                console.log data 'weather > current_conditions > wind_condition'
-                console.log ''
+                console.log """City: #{data 'weather > forecast_information > city'}
+                               Current condition: #{data 'weather > current_conditions > condition'}
+                               Temperature: #{data 'weather > current_conditions > temp_f'} F
+                               #{data 'weather > current_conditions > humidity'}
+                               #{data 'weather > current_conditions > wind_condition'}\n
+                            """

                 forecasts = document.querySelectorAll 'weather > forecast_conditions'
                 for i in forecasts


fix all methods

fix all methods

--- app/scripts/component/tip.coffee
+++ app/scripts/component/tip.coffee
@@ -2,32 +2,23 @@

 class Tip
   constructor: (@options)->
-    @el = @options.el
-    @parent = @options.parent
-    @type = @options.type
-    @direct = @options.direct
-    @title = @options.title
-    @message = @options.message
-    @width = @options.width
-    @left = @options.left
-    @top = @options.top
-    @otherClass = @options.otherClass
-    @otherCss = @options.otherCss
     @getInterval()
+    @tip = $(tipTemplate @options)

   getInterval: ->
-    @interval = if @options.interval then @options.interval else 3000
-    @noInterval = if @options.interval then false else true
+    @options.noInterval = if @options.interval then false else true
+    @options.interval = if @options.interval then @options.interval else 3000

-  tip: =>
-    $(".tip").remove() unless @noInterval
-    $(@parent).addClass("parent-position")
-    $(@parent).append(tipTemplate({type: @type, direct: @direct, message: @message, width: @width, left:@left, top: @top, otherClass: @otherClass, otherCss: @otherCss}))
+  show: ->
+    $(@options.parent).addClass("parent-position")
+    @tip.appendTo($(@options.parent))
+    setTimeout @remove, @options.interval, @tip unless @options.noInterval
+    setTimeout @parentPositionRevert, @options.interval, @options.parent unless @options.noInterval

-  tipOrAlertRemove = (target)->
+  remove: (target) ->
     $(target).remove()

-  parentPositionRevert = (target)->
+  parentPositionRevert: (target) ->
     $(target).removeClass("parent-position")

   # API for element tip


added require_tree declaration to include stories in tests

--- spec/javascripts/stories/index.js.coffee
+++ spec/javascripts/stories/index.js.coffee
@@ -0,0 +1 @@
+#= require_tree .


whoops/

whoops/

--- core/KD.coffee
+++ core/KD.coffee
@@ -41,8 +41,8 @@
   subscriptions   : []
   classes         : {}

-  apiUri: switch KD.config.apiUri
-  appsUri: switch KD.config.appsUri
+  apiUri: KD.config.apiUri
+  appsUri: KD.config.appsUri

   whoami:-> KD.getSingleton('mainController').userAccount


Update clean task

Update clean task

--- grunt/clean.coffee
+++ grunt/clean.coffee
@@ -1,2 +1,2 @@
 module.exports =
-  ['dist', 'tmp']
+  ['dist', 'tmp', 'test/browser']


fix an issue when workspace not opened.

fix an issue when workspace not opened.

--- lib/build/ios-build-view.coffee
+++ lib/build/ios-build-view.coffee
@@ -57,7 +57,7 @@
           else
             each.view.setText destPath[0]

-    @title.setText _.last(atom.project.path.split(path.sep))
+    @title.setText _.last(atom.project.path.split(path.sep)) if atom.project.path
     @version.setText "1.0.0"
     @build.setText "1"


More info

More info

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -15,11 +15,12 @@
   http = require 'http'
   uri = url.parse api
   data = JSON.stringify data
-  console.log 'POST', api, data
+  uri.method = 'POST'
   uri.path = path
   uri.headers =
     'Content-Type': 'application/json'
     'Content-Length': data.length
+  console.log 'POST', api, uri, data
   q = http.request uri
   q.end data


Fix misuse of ternary

Fix misuse of ternary

--- src/kissmetrics-anon.coffee
+++ src/kissmetrics-anon.coffee
@@ -125,10 +125,12 @@
 class AnonKissmetricsClient extends KissmetricsClient
   constructor: (apiKey, options = {}) ->
     @_storage =
-      switch options.storage
-        when 'cookie' then Cookie
-        when 'localStorage' then LocalStorage
-        else window.localStorage ? LocalStorage : Cookie
+      if options.storage
+        switch options.storage
+          when 'cookie' then Cookie
+          when 'localStorage' then LocalStorage
+      else
+        if window.localStorage then LocalStorage else Cookie

     @_storage.key = options.storageKey || 'kissmetricsAnon'


Initiate inputElement before super so you can set placeholder text values in constructor as well as override it later


:lipstick: Use .eq() and stage-ready class name

--- lib/merge-conflicts-view.coffee
+++ lib/merge-conflicts-view.coffee
@@ -27,7 +27,7 @@
             @li click: 'navigate', class: 'list-item navigate', =>
               @span class: 'inline-block icon icon-diff-modified status-modified path', p
               @div class: 'pull-right', =>
-                @button click: 'stageFile', class: 'btn btn-xs btn-success inline-block-tight bReady', style: 'display: none', 'Stage'
+                @button click: 'stageFile', class: 'btn btn-xs btn-success inline-block-tight stage-ready', style: 'display: none', 'Stage'
                 @span class: 'inline-block text-subtle', message
                 @progress class: 'inline-block', max: 100, value: 0
                 @span class: 'inline-block icon icon-dash staged'
@@ -47,7 +47,7 @@
       else
         console.log "Unrecognized conflict path: #{p}"
       if event.total is event.resolved
-        $(@pathList.find("li:contains('#{p}') .bReady")?[0]).show()
+        @pathList.find("li:contains('#{p}') .stage-ready").eq(0).show()

     @subscribe atom, 'merge-conflicts:staged', => @refresh()

@@ -83,7 +83,7 @@
           icon.addClass 'icon-dash'
         else
           icon.addClass 'icon-check text-success'
-          $(@pathList.find("li:contains('#{p}') .bReady")?[0]).hide()
+          @pathList.find("li:contains('#{p}') .stage-ready").eq(0).hide()

       if @state.isEmpty()
         atom.emit 'merge-conflicts:done'


Activate pane after dismissing notification

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -159,6 +159,7 @@
   removeNotification: ->
     @classList.add('remove')
     @removeNotificationAfterTimeout()
+    atom.workspace.getActivePane().activate()

   handleRemoveNotificationClick: ->
     @model.dismiss()


Removed unnecessary ifs

Removed unnecessary ifs

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -94,41 +94,39 @@
     fs.writeFileSync(tempFilePath, buffer.getText())

     rubocopCommand = @rubocopCommand()
-    if (rubocopCommand != undefined)
-      command = rubocopCommand[0]
-      args = rubocopCommand[1]
-        .concat(['-a', tempFilePath])
-        .concat(@projectRootRubocopConfig(buffer.getPath()))
+    command = rubocopCommand[0]
+    args = rubocopCommand[1]
+      .concat(['-a', tempFilePath])
+      .concat(@projectRootRubocopConfig(buffer.getPath()))

-      which command, (err) =>
-        if (err)
-          @rubocopNotFoundError()
+    which command, (err) =>
+      if (err)
+        @rubocopNotFoundError()
+      else
+        rubocop = spawnSync(command, args, { encoding: 'utf-8', timeout: 5000 })
+        if (rubocop.stderr)
+          @rubocopOutput({"stderr": "#{rubocop.stderr}"})
         else
-          rubocop = spawnSync(command, args, { encoding: 'utf-8', timeout: 5000 })
-          if (rubocop.stderr)
-            @rubocopOutput({"stderr": "#{rubocop.stderr}"})
-          else
-            buffer.setTextViaDiff(fs.readFileSync(tempFilePath, 'utf-8'))
-            @rubocopOutput(JSON.parse(rubocop.stdout))
+          buffer.setTextViaDiff(fs.readFileSync(tempFilePath, 'utf-8'))
+          @rubocopOutput(JSON.parse(rubocop.stdout))

   autoCorrectFile: (filePath)  ->
     rubocopCommand = @rubocopCommand()
-    if (rubocopCommand != undefined)
-      command = rubocopCommand[0]
-      args = rubocopCommand[1]
-        .concat(['-a', filePath])
-        .concat(@projectRootRubocopConfig(filePath))
+    command = rubocopCommand[0]
+    args = rubocopCommand[1]
+      .concat(['-a', filePath])
+      .concat(@projectRootRubocopConfig(filePath))

-      stdout = (output) =>
-        @rubocopOutput(JSON.parse(output))
-      stderr = (output) =>
-        @rubocopOutput({"stderr": "#{output}"})
+    stdout = (output) =>
+      @rubocopOutput(JSON.parse(output))
+    stderr = (output) =>
+      @rubocopOutput({"stderr": "#{output}"})

-      which command, (err) =>
-        if (err)
-          @rubocopNotFoundError()
-        else
-          new BufferedProcess({command, args, stdout, stderr})
+    which command, (err) =>
+      if (err)
+        @rubocopNotFoundError()
+      else
+        new BufferedProcess({command, args, stdout, stderr})

   rubocopNotFoundError: ->
     atom.notifications.addError(


Brainfartfix

Brainfartfix

--- test/surveyor.coffee
+++ test/surveyor.coffee
@@ -19,14 +19,14 @@
     fs.unlinkSync './manifest/test_1.json'
     fs.unlinkSync './manifest/test_2.json'
   it 'should concatenate all json files in a dir into one manifest', (done) ->
-    return if done() process.env.TRAVIS
+    return done() if process.env.TRAVIS
     surveyor.getManifest (err) ->
       assert.equal err, null
       assert.equal model.manifest.name1.instances, 7
       assert.equal model.manifest.name2.instances, 3
       done()
   it 'should complain when something is duplicated', (done) ->
-    return if done() process.env.TRAVIS
+    return done() if process.env.TRAVIS
     fs.writeFileSync './manifest/test_2dup.json', JSON.stringify
       name2:
         instances: 3
@@ -35,7 +35,7 @@
       for error in err
         done() if error is "name2 is duplicated"
   it 'should handle malformed JSON', (done) ->
-    return if done() process.env.TRAVIS
+    return done() if process.env.TRAVIS
     fs.writeFileSync './manifest/test_malformed.json', "lol this totally isn't JSON"
     surveyor.getManifest (err, manifest) ->
       fs.unlinkSync './manifest/test_malformed.json'


Updated test case for "delay" function

Updated test case for "delay" function

--- test/t1-delay.coffee
+++ test/t1-delay.coffee
@@ -17,5 +17,5 @@
     timeStamp = (new Date).getTime()
     delay 300, ->
       now = (new Date).getTime()
-      expect(now - timeStamp).to.be.closeTo 300, 10
+      expect(now - timeStamp).to.be.closeTo 300, 100
       done()


Fixing Fair microsite article ordering

Fixing Fair microsite article ordering

--- apps/fair/client/articles.coffee
+++ apps/fair/client/articles.coffee
@@ -4,7 +4,7 @@
 module.exports = class ArticlesAdapter
   constructor: ({ model, fair, el }) ->
     collection = new Articles
-    collection.url = "#{collection.url}/?fair_ids[]=#{fair.get('_id')}&published=true&limit=5"
+    collection.url = "#{collection.url}/?fair_ids[]=#{fair.get('_id')}&published=true&limit=5&sort=-published_at"
     view = new ArticlesFeedView el: el, collection: collection
     el.html '

'
     el.addClass view.className


operations: unify into isAffiliationAtLeast()

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -90,6 +90,9 @@
     'outcast', 'none', 'member',
     'publisher', 'moderator', 'owner'
 ]
+isAffiliationAtLeast = (affiliation1, affiliation2) ->
+    AFFILIATIONS.indexOf(affiliation1) >= AFFILIATIONS.indexOf(affiliation2)
+
 class PrivilegedOperation extends ModelOperation

     transaction: (t, cb) ->
@@ -127,7 +130,7 @@

                 @actorAffiliation = 'none'
                 for affiliation in affiliations
-                    if AFFILIATIONS.indexOf(affiliation) > AFFILIATIONS.indexOf(@actorAffiliation)
+                    if isAffiliationAtLeast affilliation, @actorAffiliation
                         @actorAffiliation = affiliation
                 cb()

@@ -159,7 +162,7 @@
         cb()

     checkRequiredAffiliation: (t, cb) ->
-        if AFFILIATIONS.indexOf(@actorAffiliation) >= AFFILIATIONS.indexOf(@requiredAffiliation)
+        if isAffiliationAtLeast @actorAffiliation, @requiredAffiliation
             cb()
         else
             cb new errors.Forbidden("Requires affiliation #{@requiredAffiliation} (you are #{@actorAffiliation})")
@@ -171,9 +174,9 @@
             when 'open'
                 pass = true
             when 'members'
-                pass = (AFFILIATIONS.indexOf(@actorAffiliation) >= 'member')
+                pass = isAffiliationAtLeast @actorAffiliation, 'member'
             when 'publishers'
-                pass = (AFFILIATIONS.indexOf(@actorAffiliation) >= 'publishers')
+                pass = isAffiliationAtLeast @actorAffiliation, 'publishers'
             else
                 # Owners can always post
                 pass = (@actorAffiliation is 'owner')


operations: fix CreateNode node typo

operations: fix CreateNode node typo

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -391,7 +391,7 @@
                 defaults = defaultConfiguration nodeUser
             else
                 defaults = defaultTopicConfiguration nodeUser
-            defaults = defaults[getNodeType @req.nodenode]
+            defaults = defaults[getNodeType @req.node]
             if defaults
                 # Mix into config
                 for own key, value of defaults


Renamed the child getter

Renamed the child getter

--- src/projects.coffee
+++ src/projects.coffee
@@ -1,5 +1,5 @@
 module.exports = (client) ->
-  getter = (locator) ->
+  childGetter = (locator) ->
     identifier = if locator.compile then locator.compile() else locator

     buildTypes: (cb) ->
@@ -23,16 +23,6 @@
     if cb and typeof cb is 'function'
       getProject locator, cb
     else
-      getter locator
+      childGetter locator

   projects
-
-
-  # buildTypes:
-  #   all: (cb) ->
-  #     @client._get "/projects/#{@locator.compile()}/buildTypes", cb
-
-  # templates:
-  #   all: (cb) ->
-  #     @client._get "/projects/#{@locator.compile()}/templates", cb
-


check in this shitty, pain-in-the-ass session engine

--- library/exposure/session.coffee
+++ library/exposure/session.coffee
@@ -38,6 +38,7 @@

 {Zombie} = require "../nucleus/zombie"
 {RedisClient} = require "../membrane/redis"
+{GraniteKernel} = require "../nucleus/kernel"

 # This zombie service exposes session storage engine implementation.
 # This implementation uses Redis to store the session data. It does
@@ -64,14 +65,18 @@
     # This method is supposed to be called when a session engine asks
     # the storage to destroy a session with the specified session ID.
     # The session may or may not exist. Please refer to the `Connect`.
-    destory: (sid, callback) ->
+    # The backing storage in use is Redis via the `RedisClient` class.
+    this::destory = this::destroySession = (sid, callback) ->
         assert _.isString qualified = @namespaced sid
         assert _.isObject(@redis), "no Redis client yet"
         message = "Redis session engine error at destroy"
         process = "Destroying a Redis stored session %s"
-        logger.debug process.grey, try sid.underline
+        assert kernel = @kernel or GraniteKernel.instance
+        logger.debug process.grey, try sid.underline or 0
+        assert _.isArray hosting = [this, kernel] or null
+        forward = => h.emit arguments... for h in hosting
         @redis.del qualified, (error, trailings...) =>
-            @emit "session-destroy", sid, callback
+            forward "session-destroy", this, sid, error
             logger.error message.red, error if error
             return callback.call this, error if error
             return callback.apply this, arguments
@@ -80,7 +85,8 @@
     # This method is supposed to be called when a session engine asks
     # the storage to suspend a session with the specified session ID.
     # The session may or may not exist. Please refer to the `Connect`.
-    set: (sid, session, callback) ->
+    # The backing storage in use is Redis via the `RedisClient` class.
+    this::set = this::writeSession = (sid, session, callback) ->
         assert encoded = try JSON.stringify session
         assert _.isString qualified = @namespaced sid
         expire = session?.cookie?.maxAge / 1000 | 0
@@ -89,8 +95,11 @@
         encFailed = "failed to encode payload to JSON"
         assert _.isObject(@redis), "no Redis client yet"
         assert not _.isEmpty(encoded or null), encFailed
+        assert kernel = @kernel or GraniteKernel.instance
+        assert _.isArray hosting = [this, kernel] or null
+        forward = => h.emit arguments... for h in hosting
         @redis.setex qualified, expire, encoded, (error) =>
-            @emit "session-set", sid, session, callback
+            forward "session-set", this, sid, session
             logger.error message.red, error if error
             return callback.call this, error if error
             return callback.apply this, arguments
@@ -99,13 +108,17 @@
     # This method is supposed to be called when a session engine asks
     # the storage to retrieve a session with the specified session ID.
     # The session may or may not exist. Please refer to the `Connect`.
-    get: (sid, callback) ->
+    # The backing storage in use is Redis via the `RedisClient` class.
+    this::get = this::restoreSession = (sid, callback) ->
         assert _.isString qualified = @namespaced sid
         message = "Redis session engine error at get"
         df = "failed to decode JSON payload on the get"
         assert _.isObject(@redis), "no Redis client yet"
+        assert kernel = @kernel or GraniteKernel.instance
+        assert _.isArray hosting = [this, kernel] or null
+        forward = => h.emit arguments... for h in hosting
         @redis.get qualified, (error, data, trailings...) =>
-            @emit "session-get", sid, data, callback
+            forward "session-get", this, sid, data
             logger.error message.red, error if error
             return callback.call this, error if error
             return callback.call this unless data
@@ -117,7 +130,8 @@
     # session ID. It tried to lookup the prefix from a configuration.
     # If no configure prefix is found, use the default hardcoded one.
     # A supplied session ID tag must conform to a set of restrictions.
-    namespaced: (sid, prefixOverride) ->
+    # Namespacing is used here to avoid collisions in the Redis DB.
+    this::namespaced = this::nid = (sid, prefixOverride) ->
         noSid = "sid argument has to be non empty"
         wrongSid = "sid argument has to be a string"
         assert not _.isEmpty(sid), noSid.toString()


Fix some mistakes in the remoting module

--- library/visualize/remote.coffee
+++ library/visualize/remote.coffee
@@ -101,7 +101,7 @@
         wrapping = "The wrapper must not have arguments"
         throw new Error objectable unless detected
         throw new Error wrapping unless passed()
-        inspected = _.map parameters, inspector
+        inspected = _.map parameters, util.inspect
         joined = inspected.join(", ").toString()
         stringified = @objectable.toString()
         "new ((#{stringified})()) (#{joined})"
@@ -139,11 +139,10 @@
     # and a set of arguments that must be scalars or similar to them.
     # This method does process the args, each arg being inspected.
     processed: (binder, parameters...) ->
-        inspector = util.inspect
         detected = _.isFunction @executable
         executable = "No valid executable has been set"
         throw new Error executable unless detected
-        inspected = _.map parameters, inspector
+        inspected = _.map parameters, util.inspect
         joined = inspected.join(", ").toString()
         stringified = @executable.toString()
         inspected.unshift binder or "this"


don't attempt fetch any more once all unread posts seen

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -313,11 +313,13 @@
             if data.length > 0
                 this._enableFetch = true
                 this._postPage = this._postPage + 1
-                oldPostCount = this.articleList.length
                 this._processFeedPosts data
                 if (this._seenUnread - this._lastSeenUnread) < 10 && this.localSettings.showOnlyUnread
-                    if this._seenUnread < this.newsFeedView.getSelectedUnread()
+                    if this.newsFeedView? && this._seenUnread < this.newsFeedView.getSelectedUnread()
                         this.fetchMorePosts()
+                    else
+                        this._enableFetch = false
+                        this._lastSeenUnread = null
                 else
                     this._lastSeenUnread = null


OOps forgot to uncomment that

OOps forgot to uncomment that

--- assets/javascripts/app.coffee
+++ assets/javascripts/app.coffee
@@ -300,7 +300,7 @@
     reorderLists(trelloBoard)
   ).then(->
     log.debug("Done building board: #{trelloBoard.url}")
-    # window.location.href = trelloBoard.url
+    window.location.href = trelloBoard.url
   ).catch((error) ->
     abortCreation("Unable to build board!", error)
   )


Made transactions nestable.

Made transactions nestable.

--- src/reactive.coffee
+++ src/reactive.coffee
@@ -41,7 +41,7 @@
   DepMgr = class rx.DepMgr
     constructor: ->
       @uid2src = {}
-      @buffering = false
+      @buffering = 0
       @buffer = []
     # called by Ev.sub to register a new subscription
     sub: (uid, src) ->
@@ -51,13 +51,14 @@
       popKey(@uid2src, uid)
     # transactions
     transaction: (f) ->
-      @buffering = true
+      @buffering += 1
       try
         res = f()
       finally
-        b() for b in @buffer
-        @buffer = []
-        @buffering = false
+        @buffering -= 1
+        if @buffering == 0
+          b() for b in @buffer
+          @buffer = []
       res

   rx._depMgr = depMgr = new DepMgr()


add signalling

add signalling

--- library/exposure/session.coffee
+++ library/exposure/session.coffee
@@ -64,6 +64,7 @@
     # the storage to destroy a session with the specified session ID.
     # The session may or may not exist. Please refer to the `Connect`.
     destory: (sid, callback) ->
+        @emit "session-destroy", sid, callback
         assert _.isString qualified = @namespaced sid
         message = "Redis session engine error at destroy"
         assert _.isObject(@redis), "no Redis client yet"
@@ -80,6 +81,7 @@
     # The session may or may not exist. Please refer to the `Connect`.
     set: (sid, session, callback) ->
         assert encoded = JSON.stringify session
+        @emit "session-set", sid, session, callback
         assert _.isString qualified = @namespaced sid
         expire = session?.cookie?.maxAge / 1000 | 0
         expire = 86400 unless expire and expire > 0
@@ -95,6 +97,7 @@
     # the storage to retrieve a session with the specified session ID.
     # The session may or may not exist. Please refer to the `Connect`.
     get: (sid, callback) ->
+        @emit "session-get", sid, callback
         assert _.isString qualified = @namespaced sid
         message = "Redis session engine error at get"
         assert _.isObject(@redis), "no Redis client yet"


formatting changes

formatting changes

--- library/membrane/billets.coffee
+++ library/membrane/billets.coffee
@@ -174,11 +174,11 @@
         assert not _.isEmpty(event), invalidEvent
         assert method = @autocall Object(), method
         assert _.isObject method.remote.autocall
-        auto = (fun) -> method.remote.auto = fun
-        auto (symbol, key, context) -> _.once ->
+        assert try method.remote.meta.event = event
+        auto = (fn) -> method.remote.auto = fn; method
+        return auto (symbol, key, context) -> _.once ->
             t = "#{symbol}.on(%s, #{symbol}.#{key})"
             return format t, JSON.stringify event
-        method.remote.meta.event = event; method

     # The exclusive directive is a lot like `awaiting`, except it
     # removes all the event listeners that could have been binded
@@ -190,14 +190,14 @@
         assert not _.isEmpty(event), invalidEvent
         assert method = @autocall Object(), method
         assert _.isObject method.remote.autocall
-        auto = (fun) -> method.remote.auto = fun
-        auto (symbol, key, context) -> _.once ->
+        assert try method.remote.meta.event = event
+        auto = (fn) -> method.remote.auto = fn; method
+        return auto (symbol, key, context) -> _.once ->
             k = "#{symbol}.removeAllListeners(%s)"
             t = "#{symbol}.on(%s, #{symbol}.#{key})"
             binder = format t, JSON.stringify event
             killer = format k, JSON.stringify event
             "(#{killer}; #{binder})".toString()
-        method.remote.meta.event = event; method

     # The awaiting directive is a lot like `autocall`, except the
     # implementation will not be immediatelly , but rather when the
@@ -210,8 +210,8 @@
         assert method = @autocall Object(), method
         assert _.isObject method.remote.autocall
         select = "$root".toString().toLowerCase()
-        auto = (fun) -> method.remote.auto = fun
-        auto (symbol, key, context) -> _.once ->
+        assert try method.remote.meta.event = event
+        auto = (fn) -> method.remote.auto = fn; method
+        return auto (symbol, key, context) -> _.once ->
             t = "#{select}.on(%s, #{symbol}.#{key})"
             return format t, JSON.stringify event
-        method.remote.meta.event = event; method


upgrade e-mail client to match redis/mongo clients conventions

--- library/membrane/emailer.coffee
+++ library/membrane/emailer.coffee
@@ -81,8 +81,8 @@
         return next() unless _.isObject kernel.emailer
         noConfig = "missing the emailer configuration"
         {transport, configure} = config or new Object()
-        message = "Disconnecting mailer of %s transport"
-        logger.info "#{message.magenta}", transport.bold
+        msg = "Disconnecting mail client of %s transport"
+        logger.info msg.underline.magenta, transport.bold
         try @emit "no-emailer", envelope.emailer, envelope
         try kernel.emit "no-emailer", envelope.emailer
         envelope.emailer.close (-> return) # empty CB
@@ -105,14 +105,14 @@
         {transport, configure} = config or Object()
         noTransport = "transport has to be a string"
         noConfigure = "configure has to be a object"
-        message = "Connecting mailer via %s transport"
-        intern = "failed to initialize email transport"
+        msg = "Connecting mail client via %s transport"
+        internal = "fail to initialize email transport"
         fx = (a...) -> nodemailer.createTransport a...
         assert _.isString(transport or 0), noTransport
         assert _.isObject(configure or 0), noConfigure
-        logger.info message.magenta, transport.bold
+        logger.info msg.underline.magenta, transport.bold
         envelope.emailer = try fx transport, configure
-        assert _.isObject(@envelope.emailer), intern
+        assert _.isObject(envelope.emailer), internal
         @emit "email-ok", envelope, envelope.emailer
         kernel.emit "email-ok", envelope.emailer
         next.call this, undefined; return this


preserve beginning and end of document

preserve beginning and end of document

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -24,17 +24,29 @@
 		phantom.exit()
 	else
 		page.viewportSize = { width: 320, height: 480 }
-		body = page.evaluate () ->
-			return document.getElementsByTagName('body')[0].outerHTML
+		document = page.evaluate () ->
+			return document.getElementsByTagName('html')[0].outerHTML

-		headIndex = index.indexOf '<head>'
+		beginIndex = index.indexOf '<!-- static-renderer BEGIN -->'
+		beginDocument = document.indexOf '<!-- static-renderer BEGIN -->'
+		endIndex = index.indexOf '<!-- static-renderer END -->'

-		head = index.slice 0, headIndex
+		pre = ""
+		if beginIndex isnt -1
+			pre = index.slice 0, beginIndex
+		if beginDocument isnt -1
+			document = document.slice beginDocument, document.length
+
+		endDocument = document.indexOf '<!-- static-renderer END -->'

-		header = page.evaluate () ->
-			return document.getElementsByTagName('head')[0].outerHTML
+		post = ""
+		if endIndex isnt -1
+			post = index.slice endIndex, index.length
+		if endDocument isnt -1
+			document = document.slice 0, endDocument

-		html = head + header + body + '</hmtl>'
+		console.log document
+		html = pre + document + post

 		console.log 'writing', path.join(dir, filename)
 		fs.write path.join(dir, filename), html, 'w'


add comment

add comment

--- assets/scripts/app/pusher.coffee
+++ assets/scripts/app/pusher.coffee
@@ -39,6 +39,7 @@
     data = @normalize(event, data) if data.id

     # TODO remove job:requeued, once sf-restart-event has been merged
+    # TODO this also needs to clear logs on build:created if matrix jobs are already loaded
     if event == 'job:created' || event == 'job:requeued'
       job = Travis.Job.find(data.job.id)
       job.clearLog() if job


lazy dependencies

lazy dependencies

--- app/settings.coffee
+++ app/settings.coffee
@@ -1,11 +1,11 @@
 _ = require 'underscore'
 $ = require 'jquery'
 Bacon = require 'baconjs'
-modules = require './modules'
-Context = require './context'

 init = ->
-  modules.export exports, 'settings', ({fn}) ->
+  Modules = require './modules'
+  Context = require './context'
+  Modules.export exports, 'settings', ({fn}) ->
     fn 'set', 'Sets a user setting', (ctx, keys..., value) ->
       user_settings.set keys..., value
     fn 'get', 'Gets a setting', (ctx, keys...) ->


Simplify command-option parsing.

Simplify command-option parsing.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -80,14 +80,8 @@
   parseCommandOptions: (command, optionList) ->
     options = {}
     for option in optionList
-      parse = option.split "="
-      switch parse.length
-        when 1
-          options[parse[0]] = true
-        when 2
-          options[parse[0]] = parse[1]
-        else
-          console.log "Vimium configuration error: invalid option: #{option}."
+      parse = option.split "=", 2
+      options[parse[0]] = if parse.length == 1 then true else parse[1]

     # We parse any `count` option immediately (to avoid having to parse it repeatedly later).
     unless @availableCommands[command].noRepeat


Fix child process helper (specs)

Fix child process helper (specs)

--- gulp/helper/child_process.coffee
+++ gulp/helper/child_process.coffee
@@ -1,7 +1,7 @@
 gutil         = require 'gulp-util'
 child_process = require 'child_process'

-module.exports = (name, command, next = ->) ->
+module.exports = (name, command, callback = ->) ->
   child_process.exec(
     command
     (error, stdout, stderr) ->
@@ -14,12 +14,14 @@
         gutil.log gutil.colors.red error
         gutil.log gutil.colors.red customError.message
         gutil.beep()
-        if process.env.NODE_ENV isnt 'vagrant'
+        if process.env.NODE_ENV isnt 'workstation'
           process.exit 1
+        else
+          callback()
+
       else
-
         gutil.log stdout
         gutil.log stderr
         gutil.log gutil.colors.green "Finished: #{name}"
-        next()
+        callback()
   )


Fix andrew's mistakes

Fix andrew's mistakes

--- scripts/hype.coffee
+++ scripts/hype.coffee
@@ -7,17 +7,17 @@
   gettingHyped = "༼ʘ̚ل͜ʘ̚༽ Hype level rising: "
   overHyped = "ヽヽ༼༼ຈຈل͜ل͜ຈຈ༽༽ﾉﾉ TOO MUCH HYPE TO HANDLE! ヽヽ༼༼ຈຈل͜ل͜ຈຈ༽༽ﾉﾉ"

-  resetHype ->
+  resetHype = () ->
     curTime = Date.now()
     total = total - ((curTime - lastTime)%timeDecay)*hypeIncrement
     lastTime = curTime
     total = 0 if total < 0

-  robot.hear /hype/i, (res) ->
+  robot.hear /hype/i, (res) ->
     resetHype
-    if hype < maxHype
-      hype += hypeIncrement
+    if total < maxHype
+      total += hypeIncrement
       res.send "#{gettingHyped} : #{total}%"
     else
       res.send "#{overHyped}"
-    +


Make some fixes to the autocompleter.

Make some fixes to the autocompleter.

Missing "element" no longer causes failures.
Also, sorting is slightly improved, maybe.
Probably.
Possibly.

--- lib/autocomplete/provider.coffee
+++ lib/autocomplete/provider.coffee
@@ -1,4 +1,5 @@
 {filter} = require 'fuzzaldrin'
+{_} = require 'lodash'

 AutoCompletePlusProvider =
   selector: '.source.dart'
@@ -22,15 +23,20 @@
           .then (autocompleteInfo) ->
             items = []
             results = autocompleteInfo.params.results
-            sortedResults = if prefix == "."
-              results
-            else
-              filter(results, prefix, { key: 'completion'})
+            sortedResults = _.chain(results)
+              .where((i) -> i.relevance > 500) # 500 = garbage tier
+              .sort( (a, b) -> a.relevance - b.relevance)
+              .value()
+            # Side-step the analzyer's sad, sad relevance scores.
+            # Both "XmlDocument" and "XmlName" have the same relevance score
+            # for the fragment "XmlDocumen"
+            if prefix != "."
+              sortedResults = filter(results, prefix, { key: 'completion'})

             for result in sortedResults
               items.push
                 text: result.completion,
-                rightLabel: result.element.kind
+                rightLabel: result.element?.kind

             resolve(items)


Have campaign world map use classroom level positions if there are any

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -161,6 +161,10 @@
             @listenToOnce @courseLevels, 'sync', =>
               existing = @campaign.get('levels')
               courseLevels = @courseLevels.toArray()
+              classroomCourse = _.find(currentView.classroom.get('courses'), {_id:currentView.course.id})
+              levelPositions = {}
+              for level in classroomCourse.levels
+                levelPositions[level.original] = level.position if level.position
               for k,v of courseLevels
                 idx = v.get('original')
                 if not existing[idx]
@@ -168,6 +172,9 @@
                   @courseLevelsFake[idx] = v.toJSON()
                 else
                   @courseLevelsFake[idx] = existing[idx]
+                  # carry over positions stored in course, if there are any
+                  if levelPositions[idx]
+                    @courseLevelsFake[idx].position = levelPositions[idx]
                 @courseLevelsFake[idx].courseIdx = parseInt(k)
                 @courseLevelsFake[idx].requiresSubscription = false


TNM size is now an embedded JSON object.

--- app/javascripts/clinical.coffee
+++ app/javascripts/clinical.coffee
@@ -83,10 +83,26 @@
     #       }
     #   }
     #
+
+    # TODO - change stage and grade to an array lookup, e.g.:
+    # Breast =
+    #   GRADE_RANGES = [[3..5], [6..7], [8..9]]
+    #   STAGES = [
+    #    ['IIA', 'IIIA', 'IIIC']
+    #    ...
+    #   ]
+    #   stage = (t, n) ->
+    #     STAGES[t][n] if t?
+    #   summaryGrade = (cumulativeGrade) ->
+    #     1 + _.findIndex(GRADE_RANGES, (range) -> cumulativeGrade in range)
+    #
+    # Then there is no need to convert t, n, m or g to string.
+    #
+
     TUMOR_STAGES =
       'Breast':
         {
-          'X':
+          X:
             {
             }
           0:
@@ -210,22 +226,25 @@
         #
         # Breast or sarcoma collection.
         coll = subject.collection
-        # Obtain the T value without any prefixes (i.e. c or p).
-        if tnm.size
-          size = tnm.size
-          t_value = size.split('T')[1]
-        else
-          size = 'TX'
-          t_value = null
+
+        # TODO - make a getTumorScore(tnm) method.
+        size = if tnm.size? then tnm.size else {}
+
         # Look up the TNM grade based on the overall Nottingham or FNCLCC grade.
         g_value = TUMOR_GRADES[coll][grade]
+        # The T value is the TNM size tumor_size property.
+        t_value = size.tumor_size
         # Obtain the T, N, M, and G values as strings or set to 'X' if null.
-        t = if t_value then t_value else 'X'
-        n = if tnm.lymph_status then tnm.lymph_status.toString() else 'X'
+        t = if t_value? then t_value.toString() else 'X'
+        n = if tnm.lymph_status? then tnm.lymph_status.toString() else 'X'
         m = if tnm.metastasis? then TNM_METASTASIS[tnm.metastasis] else 'X'
         g = if g_value then g_value else 'X'
+        # The size as a string.
+        prefix = if size.prefix? then size.prefix else ''
+        suffix = if size.suffix? then size.suffix else ''
+        size_s = "#{ prefix }T#{ t }#{ suffix }"
         # Create the composite TNMG score.
-        tumor_score = size.concat('N', n, 'M', m, 'G', g)
+        tumor_score = "#{ size_s }N#{ n }M#{ m }G#{ g }"
         # Look up the tumor stage.
         # If metastasis exits (M1), it is stage IV. Otherwise,
         # breast cancer stage is determined by T and N scores


Removed duplicate goal schemas from the deprecated script goal properties.

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -110,8 +110,8 @@
     letterbox: {type: 'boolean', title: 'Letterbox', description: 'Turn letterbox mode on or off. Disables surface and playback controls.'}

   goals: c.object {title: 'Goals (Old)', description: 'Deprecated. Goals added here have no effect. Add goals in the level settings instead.'},
-    add: c.array {title: 'Add', description: 'Deprecated. Goals added here have no effect. Add goals in the level settings instead.'}, GoalSchema
-    remove: c.array {title: 'Remove', description: 'Deprecated. Goals removed here have no effect. Adjust goals in the level settings instead.'}, GoalSchema
+    add: c.array {title: 'Add', description: 'Deprecated. Goals added here have no effect. Add goals in the level settings instead.'}, {}
+    remove: c.array {title: 'Remove', description: 'Deprecated. Goals removed here have no effect. Adjust goals in the level settings instead.'}, {}

   playback: c.object {title: 'Playback', description: 'Control the playback of the level.'},
     playing: {type: 'boolean', title: 'Set Playing', description: 'Set whether playback is playing or paused.'}


Keeping linter happy

Keeping linter happy


fix(folder): require `Task` object

fix(folder): require `Task` object

--- src/flux/tasks/change-folder-task.coffee
+++ src/flux/tasks/change-folder-task.coffee
@@ -1,3 +1,4 @@
+Task = require './task'
 Folder = require '../models/folder'
 Thread = require '../models/thread'
 Message = require '../models/message'


Fix sounds not bein played anymore

Fix sounds not bein played anymore

--- app/views/home-page-view.coffee
+++ app/views/home-page-view.coffee
@@ -111,6 +111,7 @@
       moveDir.Multiply(mediator.player.VELOCITY)

       mediator.player.physBody.SetLinearVelocity moveDir
+      mediator.player.onPositionChange(mediator.player.position) if mediator.PlayWithSounds
     else
       mediator.player.physBody.SetLinearVelocity new @physicsManager.Vec2 0, 0


still need this for rendering after a language switch

--- app/views/kinds/RootView.coffee
+++ app/views/kinds/RootView.coffee
@@ -17,6 +17,10 @@
     "click #logout-button": "logoutAccount"
     'change .language-dropdown': 'showDiplomatSuggestionModal'
     'click .toggle-fullscreen': 'toggleFullscreen'
+
+  afterRender: ->
+    super()
+    @buildLanguages()

   logoutAccount: ->
     logoutUser($('#login-email').val())


Correct word boundary.

Correct word boundary.

#5

--- lib/highlighted-area-view.coffee
+++ lib/highlighted-area-view.coffee
@@ -54,7 +54,7 @@
     @ranges = []
     regexSearch = result[0]
     if atom.config.get('highlight-selected.onlyHighlightWholeWords')
-      regexSearch += "\\b"
+      regexSearch =  "\\b" + regexSearch + "\\b"
     editor.scanInBufferRange new RegExp(regexSearch, 'g'), range,
       (result) =>
         if prefix = result.match[1]


Filter out ANSI escape sequences from REPL output

--- lib/ide-haskell-repl-view.coffee
+++ lib/ide-haskell-repl-view.coffee
@@ -3,6 +3,7 @@
 GHCI = require './ghci'
 Util = require 'atom-haskell-utils'

+termEscapeRx = /\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/g

 module.exports =
 class IdeHaskellReplView
@@ -132,11 +133,11 @@
       cwd: @cwd.getPath()
       load: @uri
       onResponse: (response) =>
-        @log response
+        @log response.replace(termEscapeRx, '')
       onError: (error) =>
-        @setError error
+        @setError error.replace(termEscapeRx, '')
       onFinished: (prompt) =>
-        @setPrompt prompt
+        @setPrompt prompt.replace(termEscapeRx, '')
       onExit: (code) =>
         atom.workspace.paneForItem(@)?.destroyItem?(@)


tool container cleanup

tool container cleanup

--- app/views/tool_container.coffee
+++ app/views/tool_container.coffee
@@ -5,15 +5,11 @@
   className: 'tool-container'

   initialize: =>
-    if @model?
-      @createToolView()
-
-  createToolView: =>
-    if @model.has('type')
-      @toolView = new UbretTool { model: @model, id: @model.get('channel') }
+    unless @model throw 'must pass a model'
+    @toolView = new UbretTool {model: @model, id: @model.get('channel')}

   render: =>
-    @$el.html @toolView?.render().el
+    @$el.html @toolView.render().el
     @


sagews: get %hide, %auto, etc. to work.

sagews: get %hide, %auto, etc. to work.

--- salvus/smc-webapp/sagews.coffee
+++ salvus/smc-webapp/sagews.coffee
@@ -1,6 +1,6 @@
 async = require('async')

-{ MARKERS, FLAGS, ACTION_FLAGS } = require('diffsync')
+{MARKERS, FLAGS, ACTION_FLAGS} = require('diffsync')

 {SynchronizedDocument} = require('./syncdoc')

@@ -739,6 +739,7 @@
                                 @process_output_mesg
                                     mesg    : JSON.parse(mesg)
                                     element : elt
+                                    mark    : mark
                             catch e
                                 console.log(e.stack)
                                 log("BUG: error rendering output: '#{mesg}' -- #{e}")
@@ -895,7 +896,7 @@

         return uuid

-    interact: (output, desc) =>
+    interact: (output, desc, mark) =>
         # Create and insert DOM objects corresponding to this interact
         elt = $("<div class='sagews-output-interact'>")
         interact_elt = $("<span>")
@@ -906,8 +907,12 @@
             interact_elt.text("(interacts not available)").addClass('lighten')
             return

+        f = (opts) =>
+            opts.mark = mark
+            @process_output_mesg(opts)
+
         # Call jQuery plugin to make it all happen.
-        interact_elt.sage_interact(desc:desc, execute_code:@execute_code, process_output_mesg:@process_output_mesg)
+        interact_elt.sage_interact(desc:desc, execute_code:@execute_code, process_output_mesg:f)

     jump_to_output_matching_jquery_selector: (selector) =>
         cm = @focused_codemirror()
@@ -1085,7 +1090,7 @@
             @process_html_output(e)

         if mesg.interact?
-            @interact(output, mesg.interact)
+            @interact(output, mesg.interact, opts.mark)

         if mesg.d3?
             e = $("<span>")
@@ -1184,6 +1189,7 @@
                                         @process_output_mesg
                                             mesg    : mesg
                                             element : output.find(".sagews-output-messages")
+                                            mark    : opts.mark

                             for event, function_name of mesg.events
                                 img.data("salvus-events-#{event}", function_name)
@@ -1241,6 +1247,41 @@
                      cb()
              ])
             )()
+
+        if mesg.show?
+            if opts.mark?
+                line = opts.mark.find()?.from.line
+                if line?
+                    cell = @cell(line)
+                    if cell?
+                        switch mesg.show
+                            when 'input'
+                                cell.remove_cell_flag(FLAGS.hide_input)
+                            when 'output'
+                                cell.remove_cell_flag(FLAGS.hide_output)
+
+        if mesg.hide?
+            if opts.mark?
+                line = opts.mark.find()?.from.line
+                if line?
+                    cell = @cell(line)
+                    if cell?
+                        switch mesg.hide
+                            when 'input'
+                                cell.set_cell_flag(FLAGS.hide_input)
+                            when 'output'
+                                cell.set_cell_flag(FLAGS.hide_output)
+
+        if mesg.auto?
+            if opts.mark?
+                line = opts.mark.find()?.from.line
+                if line?
+                    cell = @cell(line)
+                    if cell?
+                        if mesg.auto
+                            cell.set_cell_flag(FLAGS.auto)
+                        else
+                            cell.remove_cell_flag(FLAGS.auto)

         if mesg.done? and mesg.done
             output.removeClass('sagews-output-running')


Use default copy behavior when text is selected

Closes #72

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -155,7 +155,10 @@

     selection = window.getSelection()
     selectedText = selection.toString()
-    return false if selectedText and selection.baseNode? and selection.baseNode isnt @[0] and not $.contains(@[0], selection.baseNode)
+    selectedNode = selection.baseNode
+
+    # Use default copy event handler if there is selected text inside this view
+    return false if selectedText and selectedNode? and $.contains(@[0], selectedNode)

     atom.clipboard.write(@[0].innerHTML)
     true


Add currentRender and runWithRender, refactor current*

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -140,9 +140,6 @@
 class HasSettings
   getSetting: (key) -> getSetting @settings, key
   getOutputDir: (extension) -> getOutputDir @settings, extension
-
-currentMapping = null
-currentContext = null

 SVGNS = 'http://www.w3.org/2000/svg'
 XLINKNS = 'http://www.w3.org/1999/xlink'
@@ -394,6 +391,51 @@
   ## (spec: https://www.w3.org/TR/2008/REC-xml-20081126/#NT-prolog)
   svg.replace /<\?[^]*?\?>|<![^-][^]*?>|<!--[^]*?-->/g, ''

+currentMapping = null
+currentRender = null
+currentContext = null
+
+getMapping = ->
+  ## Returns current `Mapping` object,
+  ## when used at top level of a JS/CS mapping file.
+  currentMapping
+runWithMapping = (mapping, fn) ->
+  ## Runs the specified function `fn` as if it were called
+  ## at the top level of the specified mapping.
+  oldMapping = currentMapping
+  currentMapping = mapping
+  try
+    fn()
+  finally
+    currentMapping = oldMapping
+
+getRender = ->
+  ## Returns current `Mapping` object,
+  ## when used at top level of a JS/CS mapping file.
+  currentRender
+runWithRender = (render, fn) ->
+  ## Runs the specified function `fn` as if it were called
+  ## from the specified `render`.
+  oldRender = currentRender
+  currentRender = render
+  try
+    fn()
+  finally
+    currentRender = oldRender
+
+getContext = ->
+  ## Returns current `Context` object, when used within a mapping function.
+  currentContext
+runWithContext = (context, fn) ->
+  ## Runs the specified function `fn` as if it were called
+  ## within the specified `context`.
+  oldContext = currentContext
+  currentContext = context
+  try
+    fn()
+  finally
+    currentContext = oldContext
+
 class Tile extends HasSettings
   constructor: (@key, @value, @settings) ->
     ## `@value` can be a string or Preact VDOM.
@@ -753,29 +795,6 @@
 class Styles extends ArrayWrapper
   @itemClass: Style

-getMapping = ->
-  ## Returns current `Mapping` object,
-  ## when used at top level of a JS/CS mapping file.
-  currentMapping
-runWithMapping = (mapping, fn) ->
-  ## Runs the specified function `fn` as if it were called
-  ## at the top level of the specified mapping.
-  oldMapping = currentMapping
-  currentMapping = mapping
-  try
-    fn()
-  finally
-    currentMapping = oldMapping
-
-beforeRender = (fn) ->
-  unless currentMapping?
-    throw new SVGTilerError "svgtiler.beforeRender called outside mapping file"
-  currentMapping.beforeRender fn
-afterRender = (fn) ->
-  unless currentMapping?
-    throw new SVGTilerError "svgtiler.afterRender called outside mapping file"
-  currentMapping.afterRender fn
-
 class Mapping extends Input
   ###
   Base Mapping class.
@@ -848,6 +867,15 @@
     for callback in @afterRenderQueue
       result = callback.call render, render
       onResult? result
+
+beforeRender = (fn) ->
+  unless currentMapping?
+    throw new SVGTilerError "svgtiler.beforeRender called outside mapping file"
+  currentMapping.beforeRender fn
+afterRender = (fn) ->
+  unless currentMapping?
+    throw new SVGTilerError "svgtiler.afterRender called outside mapping file"
+  currentMapping.afterRender fn

 class ASCIIMapping extends Mapping
   @title: "ASCII mapping file"
@@ -1177,7 +1205,7 @@
       "#{escaped}_v#{version}"
     else
       escaped
-  makeDOM: ->
+  makeDOM: -> runWithRender @, => runWithContext (new Context @), =>
     ###
     Main rendering engine, returning an xmldom object for the whole document.
     Also saves the table of tiles in `@tiles`, the corresponding
@@ -1199,7 +1227,6 @@
     missing = new Set
     cache = new Map
     symbolIds = new Set
-    currentContext = new Context @
     @tiles =
       for row, i in @drawing.keys
         for key, j in row
@@ -1508,19 +1535,6 @@
         return true if dep.modified? and dep.modified > modified
     false

-getContext = ->
-  ## Returns current `Context` object, when used within a mapping function.
-  currentContext
-runWithContext = (context, fn) ->
-  ## Runs the specified function `fn` as if it were called
-  ## within the specified `context`.
-  oldContext = currentContext
-  currentContext = context
-  try
-    fn()
-  finally
-    currentContext = oldContext
-
 class Context
   constructor: (@render, i, j) ->
     @drawing = @render.drawing
@@ -1815,7 +1829,7 @@
   Style, CSSStyle, StylusStyle,
   SVGFile,
   extensionMap, Input, DummyInput, ArrayWrapper, Mappings,
-  Render, beforeRender, afterRender,
+  Render, getRender, runWithRender, beforeRender, afterRender,
   Context, getContext, runWithContext,
   SVGTilerError, SVGNS, XLINKNS, escapeId,
   main, renderDOM, defaultSettings, convert,


end method

end method

--- src/template.coffee
+++ src/template.coffee
@@ -137,7 +137,6 @@
         ff ExtendedBuilder::, opts.schema
         # instantiate
         @xml = new ExtendedBuilder opts
-        @end = @xml.end

         # tag class is defined by builder
         Tag = @xml.Tag
@@ -190,6 +189,9 @@
             else
                 @xml.end(template)

+    end: =>
+        @xml.end arguments...
+

 # exports


Improved instancing of things.

Improved instancing of things.

--- packages/landsofillusions/adventure/adventure-things.coffee
+++ packages/landsofillusions/adventure/adventure-things.coffee
@@ -70,21 +70,27 @@

   _instantiateThings: (things) ->
     for thing in things
-      thingId = thing.id()
-
       # Look if the thing was already an instance.
       if thing instanceof LOI.Adventure.Thing
         thingInstance = thing

-      # Look into our cache if we already instantiated this thing.
-      else if @_things[thingId]
-        thingInstance = @_things[thingId]
+      else
+        # Look into our cache if we already instantiated this thing.
+        thingId = _.thingId thing
+        thingClass = _.thingClass thing

-      else
-        # We don't have an instance ready, so we'll have to create it. We do so in a non-reactive
-        # context so that reruns of this autorun don't invalidate instance's autoruns.
-        thingInstance = Tracker.nonreactive => new thing
-        @_things[thingId] = thingInstance
+        if thingEntries = @_things[thingId]
+          thingEntry = _.find thingEntries, (thingEntry) => thingEntry.class is thingClass
+          thingInstance = thingEntry?.instance
+
+        unless thingInstance
+          # We don't have an instance ready, so we'll have to create it. We do so in a non-reactive
+          # context so that reruns of this autorun don't invalidate instance's autoruns.
+          thingInstance = Tracker.nonreactive => new thing
+          @_things[thingId] ?= []
+          @_things[thingId].push
+            class: thingClass
+            instance: thingInstance

       thingInstance


try to make hubot log itself

try to make hubot log itself

--- scripts/eslogger_watcher.coffee
+++ scripts/eslogger_watcher.coffee
@@ -13,6 +13,35 @@
   eslogger = robot.eslogger

   if eslogger.logEnabled? and eslogger.logEnabled isnt 'false'
+
+    # hubot don't `hear` its own messages, we have to 'decorate'
+    # the adapter `send` method to add an after-method
+
+    override = (object, methodName, callback) ->
+      object[methodName] = callback(object[methodName])
+
+    after = (extra) ->
+      (original) ->
+        () ->
+          returnValue = original.apply(this, arguments)
+          extra.apply(this, arguments)
+          returnValue
+
+    logMessageFromRobot = (room, text) ->
+      log = {
+        room: room
+        nick: robot.name
+        message: text
+      }
+      eslogger.logMessageES log, room, robot
+
+    override(robot.adapter, 'send', after( (envelope, strings...) ->
+      if envelope.room?
+        for str in strings
+          logMessageFromRobot envelope.room, str
+    ))
+
+    # normal message on channel
     robot.hear /.*/, (msg) ->
       log = {
         room: msg.message.room
@@ -21,17 +50,6 @@
       }
       eslogger.logMessageES log, msg.message.room, msg

-    # hubot don't `hear` its own messages, we have to log them separately
-    logMessageFromRobot = (room, text) ->
-      log = {
-        room: room
-        nick: robot.name
-        message: text
-      }
-      eslogger.logMessageES log, room, robot
-
-    robot.logMessageFromRobot = logMessageFromRobot
-
     robot.enter (res) ->
       console.log res


get achewood based on keyword

get achewood based on keyword

--- src/scripts/achewood.coffee
+++ src/scripts/achewood.coffee
@@ -12,6 +12,7 @@
 #   hubot achewood - A random Achewood comic
 #   hubot achewood current - The most recent Achewood comic
 #   hubot achewood <date> - Achewood comic from <date> - mm/dd/yyyy format
+#   hubot achewood <keyword> - Achewood comic for keyword
 #   hubot saddest thing - The saddest thing, according to Lie Bot
 #
 # Author:
@@ -36,16 +37,22 @@
       msg.send "http://achewood.com" + comic.src + "#.png"
       msg.send comic.title

-  robot.respond /achewood\s?((0[1-9]|1[0-2]).?(0[1-9]|[1-2][0-9]|3[0-1]).?(20\d{2})$|current)?/i, (msg) ->
+  robot.respond /achewood\s?((0[1-9]|1[0-2]).?(0[1-9]|[1-2][0-9]|3[0-1]).?(20\d{2})$|.*)?/i, (msg) ->
     if msg.match[1] == undefined
       msg.http("http://www.ohnorobot.com/random.pl?comic=636")
           .get() (err, res, body) ->
             fetchAchewood(msg, res.headers['location'])
     else if msg.match[1] == "current"
       fetchAchewood(msg, "http://achewood.com")
-    else
+    else if msg.match[1] =~ /\d{2}.?\d{2}.?\d{4}/
       date = "#{msg.match[2]}#{msg.match[3]}#{msg.match[4]}"
       fetchAchewood(msg, withDate(date))
+    else
+      query = msg.match[1]
+      msg.http("http://www.ohnorobot.com/index.pl?comic=636&lucky=1&s=#{query}"
+          .get() (err, res, body) ->
+            fetchAchewood(msg, res.headers['location'])
+

   robot.respond /.*saddest thing\?*/i, (msg) ->
     saddest = msg.random ["06022003", "11052001", "09052006", "07302007"]


Unlock keychain before signing

Unlock keychain before signing

--- build/tasks/codesign-task.coffee
+++ build/tasks/codesign-task.coffee
@@ -3,6 +3,22 @@

   grunt.registerTask 'codesign', 'Codesign the app', ->
     done = @async()
+
+    if process.env.XCODE_KEYCHAIN
+      unlockKeychain (error) ->
+        if error?
+          done(error)
+        else
+          signApp(done)
+    else
+      signApp(done)
+
+  unlockKeychain = (callback) ->
+    cmd = 'security'
+    args = ['-p', process.env.XCODE_KEYCHAIN_PASSWORD, process.env.XCODE_KEYCHAIN]
+    spawn {cmd, args}, (error) -> callback(error)
+
+  signApp = (callback) ->
     cmd = 'codesign'
     args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
     spawn {cmd, args}, (error) -> done(error)


update doc for change_password, link to API tutorial

--- src/smc-util/message.coffee
+++ src/smc-util/message.coffee
@@ -74,6 +74,7 @@

 ### Additional References

+- The [CoCalc API tutorial](https://cocalc.com/share/65f06a34-6690-407d-b95c-f51bbd5ee810/Public/README.md?viewer=share) illustrates API calls in Python.
 - The CoCalc PostgreSQL schema definition
 [src/smc-util/db-schema.coffee](https://github.com/sagemathinc/cocalc/blob/master/src/smc-util/db-schema.coffee)
 has information on tables and fields used with the API `query` request.
@@ -528,12 +529,12 @@
             init  : required
             desc  : 'must be between 6 and 64 characters in length'
     desc           : """
-Given email address and old password for an account, set a new password.
+Given account_id and old password for an account, set a new password.

 Example:
 ```
   curl -u sk_abcdefQWERTY090900000000: \\
-    -d email_address=... \\
+    -d account_id=... \\
     -d old_password=... \\
     -d new_password=... \\
     https://cocalc.com/api/v1/change_password


fix wording

fix wording

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -2070,7 +2070,7 @@
         <span>
             <Space/> If you have any questions at all, email <HelpEmailLink /> immediately.
             <b>
-                <Space/> Contact us if you are purchasing a course subscription and need a short trial
+                <Space/> Contact us if you are considering purchasing a course subscription and need a short trial
                 to test things out first.<Space/>
             </b>
             <b>


don't count misses in del

don't count misses in del

--- _src/lib/node_cache.coffee
+++ _src/lib/node_cache.coffee
@@ -219,9 +219,6 @@
 				delete @data[ key ]
 				# return true
 				@emit( "del", key, oldVal.v )
-			else
-				# if the key has not been found return an error
-				@stats.misses++

 		return delCount


Do not account WA-attempts on solved problems

--- server/methods/makeTable.coffee
+++ server/methods/makeTable.coffee
@@ -12,9 +12,9 @@
                 for prob in c.problems
                     thisRes = Results.display(user._id, prob)
                     outcomes[prob._id] = thisRes
-                    attempts += thisRes.attempts
                     if thisRes.success > 0
                         solved++
+                        attempts += thisRes.attempts
             res.push
                 user: user._id
                 outcomes: outcomes


fix controller warns

fix controller warns

--- src/stage/controller.coffee
+++ src/stage/controller.coffee
@@ -19,21 +19,21 @@
     try
       node.set key, value
     catch e
-      @inspect node, 'warn'
+      node.print null, 'warn'
       console.error e

   bind: (node, key, expr) ->
     try
       node.bind key, expr
     catch e
-      @inspect node, 'warn'
+      node.print null, 'warn'
       console.error e

   unbind: (node, key) ->
     try
       node.unbind key
     catch e
-      @inspect node, 'warn'
+      node.print null, 'warn'
       console.error e

   add: (node, target = @model.getRoot()) ->


Arrange keywords in alphabetical order and some missing entries.

--- grammars/powershell.cson
+++ grammars/powershell.cson
@@ -125,7 +125,7 @@
   }
   {
     'comment': 'keywords that delimit flow blocks'
-    'match': '\\b(?i:(do|until|begin|catch|else|elseif|end|finally|for|foreach|if|process|switch|trap|try|while))\\b'
+    'match': '\\b(?i:(begin|catch|data|do|dynamicparam|else|elseif|end|filter|finally|for|foreach|from|if|in|inlinescript|parallel|param|process|sequence|switch|trap|try|until|while|workflow))\\b'
     'name': 'keyword.control.flow.powershell'
   }
   {


Automatic generation of test files

Automatic generation of test files

--- grunt/consolidate.coffee
+++ grunt/consolidate.coffee
@@ -1,9 +1,28 @@
-module.exports =
+fs = require 'fs'
+path = require 'path'
+
+dirContent = fs.readdirSync 'test/spec'
+dirContent = dirContent.filter (filename) -> path.extname(filename) is '.js'
+
+consolidateConfig =
   options:
     engine: 'handlebars'
-  test:
+
+dirContent.forEach (filename) ->
+  basename = filename.split(path.extname(filename))[0]
+  scriptName = "spec/#{basename}"
+
+  templateKey = "test/browser/#{basename}.html"
+  templateFile = 'test/templates/browser.html'
+
+  filesObject = {}
+  filesObject[templateKey] = templateFile
+
+  consolidateConfig[basename] =
     options:
       local:
-        script: 'spec/base'
-    files:
-      'test/browser/base.html': 'test/templates/browser.html'
+        script: scriptName
+    files: filesObject
+
+
+module.exports = consolidateConfig


add vector in the formular validator

add vector in the formular validator

--- library/semantic/formular.coffee
+++ library/semantic/formular.coffee
@@ -80,10 +80,12 @@
         conditions = -> (object.checked or false) is yes
         functional = -> check.call object, object.value
         expression = -> check.test object.value or ("")
+        sequential = -> (object.value or null) in check
         select = _.isBoolean(check) and not conditions()
         method = _.isFunction(check) and not functional()
         regexp = _.isRegExp(check) and not expression()
-        failed = (method or regexp or select) or false
+        vector = _.isArray(check) and not sequential()
+        failed = method or regexp or select or vector
         return object.warning = message if failed

     # This method is part of the formular core protocol. It should


Working

Working

--- src/worldcup.coffee
+++ src/worldcup.coffee
@@ -63,7 +63,7 @@
   robot.respond /(who).*(win).*(world).*(cup)/i, (msg) ->
     msg.send "BRASIL!!!"

-  robot.respond /(worldcup|wc).*( yellow|red)( .*)/i, (msg) ->
+  robot.respond /(worldcup|wc).*(yellow|red)( .*)/i, (msg) ->
     color = msg.match[2]
     person = msg.match[3]


Big parts of Wrangler written

Big parts of Wrangler written

--- src/wrangler.coffee
+++ src/wrangler.coffee
@@ -0,0 +1,56 @@
+###
+
+Wrangler
+
+Copyright (c) 2012, Fetchnotes LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+###
+
+window.wrangle = (model, template, options) ->
+
+	# Get the Wrangler to return
+	wrangler = {}
+
+	# Give the Wrangler a model
+	wrangler.model = model
+
+	# Get template element
+	if ($?) and (template instanceof $)
+		template = template[0]
+	if template.toUppercase
+		template = document.getElementById(template)
+
+	# Update the DOM
+	wrangler.update = ->
+
+	# Set a property and then update
+	wrangler.set = (index, property, value) ->
+		if value?
+			@model[index][property] = value
+		else
+			@model[property] = value
+		@update()
+
+	# Do an initial update, which starts the timeout
+	wrangler.update()
+
+	# We're done!
+	return wrangler


Update for Blade API changes

Update for Blade API changes

--- assets/index.coffee
+++ assets/index.coffee
@@ -4,7 +4,7 @@
 QUnit.config.testTimeout = 5000

 QUnit.begin (suiteDetails) ->
-  Blade.suiteBegin(total: suiteDetails.totalTests)
+  Blade.suiteDidBegin(suiteDetails)

 failedAssertions = []

@@ -19,11 +19,11 @@
   name = "#{testDetails.module}: #{testDetails.name}"
   pass = testDetails.failed is 0
   message = formatAssertions(failedAssertions)
-  Blade.testResult({name, pass, message})
+  Blade.testDidEnd({name, pass, message})

 QUnit.done (suiteDetails) ->
   window.global_test_results = suiteDetails
-  Blade.suiteEnd(suiteDetails)
+  Blade.suiteDidEnd(suiteDetails)

 formatAssertions = (assertions = []) ->
   if assertions.length


Small JS fix

Small JS fix

--- src/naovoce/static/coffee/naovoce.coffee
+++ src/naovoce/static/coffee/naovoce.coffee
@@ -11,11 +11,14 @@
 			storage = localStorage

 			@getObject = (name) ->
-				ns = storage.getItem(namespace)
-				if ns then JSON.parse(ns)[name] else null
+				data = storage.getItem(namespace)
+				if data then JSON.parse(data)[name] else null

 			@setObject = (name, val) ->
-				storage.setItem namespace, JSON.stringify "#{ name }": val
+				data = storage.getItem(namespace)
+				data = if data then JSON.parse(data) else {}
+				data[name] = val
+				storage.setItem namespace, JSON.stringify data

 		else
 			@getObject = @setObject = -> null


:lipstick:

:lipstick:


Team: updated team data

Team: updated team data

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -61,11 +61,11 @@
         username  : 'canthefason'
         title     : 'Software Engineer'
       ,
+        username  : 'psanchezdl'
+        title     : 'Growth Engineer'
+      ,
         username  : 'pablostanley'
         title     : 'Illustrator'
-      ,
-        username  : 'pedrosanchez'
-        title     : 'Growth Engineer'
       ,
         username  : 'dicle'
         title     : 'Business Analyst'
@@ -77,7 +77,7 @@
         title     : 'Finance Assistant'
       ,
         username  : 'selin'
-        title     : 'Data scientist'
+        title     : 'Data Scientist'
       ,
         username  : 'juliapracht'
         title     : 'Recruiting & HR'


Reduce the likelihood of falsely-matched man-links

This commit ensures that constructs like `foo(1.5)` or `hex(0xB1)` don't
get highlighted as references to manual-pages (such as function calls in
commented-out code).


copy addresses, link in customer

copy addresses, link in customer

--- client/skr/models/Invoice.coffee
+++ client/skr/models/Invoice.coffee
@@ -89,9 +89,14 @@
                 @trigger('change', @, {})

     copyAssociationsFrom: ( model, associations... ) ->
+        addrs = ['billing_address', 'shipping_address']
         new _.Promise (res, rej) =>
             model.withAssociations(associations).then =>
-                for name in associations
+                for name in addrs
+                    @[name].set(
+                        _.omit( @customer[name].serialize(), 'id' )
+                    )
+                for name in _.without associations, addrs
                     @associations.replace(@, name, model[name])
                 res(@)