fixed showing of un-enabled tasking plans

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -81,13 +81,9 @@
       plan.target_id is target_id
     @_change(id, {tasking_plans})

-  _removeEmptyTaskings: (id) ->
-    plan = @_getPlan(id)
-    {tasking_plans} = plan
-    tasking_plans = _.reject tasking_plans, (tasking) ->
+  _removeEmptyTaskings: (tasking_plans) ->
+    _.reject tasking_plans, (tasking) ->
       not (tasking.due_at and tasking.opens_at)
-
-    @_local[id].tasking_plans = tasking_plans

   setPeriods: (id, periods) ->
     plan = @_getPlan(id)
@@ -103,11 +99,12 @@
         tasking
       )

+    if not @exports.isNew(id)
+      tasking_plans = @_removeEmptyTaskings(tasking_plans)
+
     @_change(id, {tasking_plans})
+
     @_setInitialPlan(id)
-
-    if not @exports.isNew(id)
-      @_removeEmptyTaskings(id)

   replaceTaskings: (id, taskings) ->
     @_change(id, {tasking_plans: taskings})


Print compiler errors for non-joined files

Resolves #861

--- src/fs_utils/write.coffee
+++ src/fs_utils/write.coffee
@@ -4,6 +4,7 @@
 sysPath = require 'path'
 generate = require './generate'
 helpers = require '../helpers'
+logger = require 'loggy'

 getPaths = (sourceFile, joinConfig) ->
   sourceFileJoinConfig = joinConfig[sourceFile.type + 's'] or {}
@@ -26,6 +27,9 @@
     paths.forEach (path) ->
       map[path] ?= []
       map[path].push file
+    unless paths.length
+      if file.error
+        logger.error formatError file

   Object.keys(map).map (generatedFilePath) ->
     sourceFiles = map[generatedFilePath]


Null safe access of `id` property, needed as user can be null.

--- app/coffee/UpdatesManager.coffee
+++ app/coffee/UpdatesManager.coffee
@@ -192,7 +192,7 @@
 				# if not, add them to the users list of the earliest update
 				userExists = false
 				for user in earliestUpdate.meta.users
-					if (!user and !update.meta.user) or (user.id == update.meta.user?.id)
+					if (!user and !update.meta.user) or (user?.id == update.meta.user?.id)
 						userExists = true
 						break
 				if !userExists


fix incorrect use of _.union (argument must be array)

--- app/coffee/UpdatesManager.coffee
+++ app/coffee/UpdatesManager.coffee
@@ -221,7 +221,7 @@
 			if earliestUpdate and earliestUpdate.meta.start_ts - update.meta.end_ts < @TIME_BETWEEN_DISTINCT_UPDATES
 				# check if the user in this update is already present in the earliest update,
 				# if not, add them to the users list of the earliest update
-				earliestUpdate.meta.user_ids = _.union earliestUpdate.meta.user_ids, update.meta.user_id
+				earliestUpdate.meta.user_ids = _.union earliestUpdate.meta.user_ids, [update.meta.user_id]

 				doc_id = update.doc_id.toString()
 				doc = earliestUpdate.docs[doc_id]


provide an explicit error if the token, rooms or account options are not provided

--- src/hubot/campfire.coffee
+++ src/hubot/campfire.coffee
@@ -45,11 +45,14 @@

 class CampfireStreaming extends EventEmitter
   constructor: (options) ->
-    @token         = options.token
-    @rooms         = options.rooms.split(",")
-    @account       = options.account
-    @domain        = @account + ".campfirenow.com"
-    @authorization = "Basic " + new Buffer("#{@token}:x").toString("base64")
+    if options.token? && options.rooms? && options.account?
+      @token         = options.token
+      @rooms         = options.rooms.split(",")
+      @account       = options.account
+      @domain        = @account + ".campfirenow.com"
+      @authorization = "Basic " + new Buffer("#{@token}:x").toString("base64")
+    else
+      throw new Error("Not enough parameters provided. I need a token, rooms and account")

   Rooms: (callback) ->
     @get "/rooms", callback


fix to return empty array if list assigned with empty array

--- src/list.coffee
+++ src/list.coffee
@@ -82,9 +82,9 @@

   # XXX: not a fan of below value override... :-(
   @property 'value',
-    get: ->
-      res = @props.map((item) -> item.data).filter(Boolean)
-      if res.length then res else undefined
+    get: -> switch
+      when @state.value? then @props.map((item) -> item.data).filter(Boolean)
+      else undefined

   @property 'props',
     get: -> switch


hub: don't log cookies -- make the logfiles less dangerous

--- salvus/misc.coffee
+++ salvus/misc.coffee
@@ -222,10 +222,19 @@
 # convert object x to a JSON string, removing any keys that have "pass" in them.
 exports.to_safe_str = (x) ->
     obj = {}
-    for key of x
-        if key.indexOf("pass") == -1
+    for key, value of x
+        sanitize = false
+
+        if key.indexOf("pass") != -1
+            sanitize = true
+        else if typeof(value)=='string' and value.slice(0,7) == "sha512$"
+            sanitize = true
+
+        if sanitize
+            obj[key] = '(unsafe)'
+        else
             obj[key] = x[key]
-    return exports.to_json(obj)
+    x = exports.to_json(obj)

 # convert from a JSON string to Javascript
 exports.from_json = (x) ->


lower_email_address -- handle special case of undefined

--- salvus/misc.coffee
+++ salvus/misc.coffee
@@ -535,6 +535,8 @@

 exports.lower_email_address = (email_address) ->
+    if not email_address?
+        return
     if typeof(email_address) != 'string'
         # silly, but we assume it is a string, and I'm concerned about a hacker attack involving that
         email_address = JSON.stringify(email_address)


CSS image now uses BLOB-URL if possible

CSS image now uses BLOB-URL if possible

--- impl/css/level0/image.coffee
+++ impl/css/level0/image.coffee
@@ -1,6 +1,6 @@
 'use strict'

-DATA_URI_RE = ///^(data:[a-z+/]+,)(.*)$///
+DATA_URI_RE = ///^data:([a-z+/]+),(.*)$///

 module.exports = (impl) ->
 	{Item} = impl.Types
@@ -16,11 +16,29 @@
 		image.style.pointerEvents = 'none'
 		data.elem.appendChild image

-	setImageSource: (val) ->
-		# escape data uri for some browsers (e.g. firefox)
-		dataUri = DATA_URI_RE.exec val
-		if dataUri?
-			[_, dec, data] = dataUri
-			val = "#{dec}#{escape data}"
+	setImageSource: do ->
+		prepareDataUri = do ->
+			if typeof Blob is 'function' and URL?.createObjectURL?
+				cache = {}

-		@_impl.image.src = val+				(dataUri) ->
+					[_, type, data] = dataUri
+
+					if val = cache[data]
+						return val
+
+					blob = new Blob [data], type: type
+					url = URL.createObjectURL blob
+					cache[data] = url
+			else
+				(dataUri) ->
+					# escape data uri for some browsers (e.g. firefox)
+					[_, type, data] = dataUri
+					"data:#{type},#{escape data}"
+
+		(val) ->
+			dataUri = DATA_URI_RE.exec val
+			if dataUri?
+				val = prepareDataUri dataUri
+
+			@_impl.image.src = val


first commit

first commit

--- src/main.coffee
+++ src/main.coffee
@@ -5,6 +5,13 @@
 markdown = (s) -> converter.makeHtml(s)

 code = """
+### to run locally, clone the repo and run:
+`npm install`
+
+`grunt serve`
+
+#### understanding the code
+
 ## Bouncing ball

 The **gravitational force** will accelerate an object starting at a certain


Send snapped piece to the back

Send snapped piece to the back

--- src/main.coffee
+++ src/main.coffee
@@ -22,6 +22,8 @@
 	Two pieces
 	###

+	dropZoneLayer = new P.Layer()
+	piecesLayer = new P.Layer()
 	pieces = []

 	for i in [0..1]
@@ -31,6 +33,7 @@
 		dropZone.fillColor = 'white'
 		dropZone.opacity = 0.5
 		dropZone.position.x = i * rectangle.width + rectangle.width / 2
+		dropZoneLayer.addChild dropZone

 		# The edge will serve as the mask for the piece
 		pieceEdge = new P.Shape.Rectangle rectangle
@@ -49,6 +52,7 @@
 		pieceGroup.clipped = true
 		pieceGroup.position.x = i * rectangle.width / 2 + rectangle.width / 2
 		pieceGroup.strokeColor = 'black'
+		piecesLayer.addChild pieceGroup

 		pieces.push
 			zone: dropZone
@@ -96,6 +100,9 @@
 		if actualPiece.zone.contains event.point
 			actualPiece.group.position = background.position

+			# Send behind all the other pieces
+			actualPiece.group.sendToBack()
+
 		# Reset
 		for piece in pieces
 			piece.group.strokeWidth = 0


update color using theory \(TwT)/

update color using theory \(TwT)/

--- src/maps.coffee
+++ src/maps.coffee
@@ -13,7 +13,7 @@
         if star?
             'chst=d_map_xpin_icon&chld=pin_star|restaurant|0ff|ff0'
         else
-            'chst=d_map_pin_icon&chld=restaurant|ac0'
+            'chst=d_map_pin_icon&chld=restaurant|4d0'

 class CustomOverlay
@@ -83,12 +83,12 @@
     restaurants:  new CustomOverlay({t: 'restaurants'})
     obstacles:    new CustomOverlay({t: 'obstacles'}, '#333')
     universities: new CustomOverlay({t: 'universities'}, '#009')
-    search:       new CustomOverlay(null, '#0aa', '#a0a', true)
+    search:       new CustomOverlay(null, '#7E07A9', '#ECFC00', true)

 heatmaps =
-    land_rates:   new CustomOverlay({t: 'land_rates'}, '#0a0', '#aa0')
-    rest_density: new CustomOverlay({t: 'rest_density'}, '#900')
-    univ_density: new CustomOverlay({t: 'univ_density'}, '#00a')
+    land_rates:   new CustomOverlay({t: 'land_rates'}, '#04819E', '#FF7F00')
+    rest_density: new CustomOverlay({t: 'rest_density'}, '#0ACF00')
+    univ_density: new CustomOverlay({t: 'univ_density'}, '#1826B0')

 google.maps.event.addDomListener window, 'load', ->


Set default viewport height to 360

Set default viewport height to 360

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -374,7 +374,7 @@
       storage.set('viewport-height', h)

     viewportMinHeight = 120
-    viewportHeight = parseInt(storage.get('viewport-height'), 10)
+    viewportHeight = parseInt(storage.get('viewport-height'), 10) || 360
     ### jshint ignore:start ###
     unless viewportHeight >= viewportMinHeight
       viewportHeight = viewportMinHeight


handle case where index does not exist

handle case where index does not exist

--- app/coffee/PackManager.coffee
+++ app/coffee/PackManager.coffee
@@ -281,7 +281,7 @@
 				return callback(err) if err?
 				return callback() if not historyPacks?
 				# select only the new packs not already in the index
-				newPacks = (pack for pack in historyPacks when not indexResult[pack._id]?)
+				newPacks = (pack for pack in historyPacks when not indexResult?[pack._id]?)
 				newPacks = (_.omit(pack, 'doc_id', 'project_id', 'n', 'sz') for pack in newPacks)
 				logger.log {project_id, doc_id, n: newPacks.length}, "found new packs"
 				callback(null, newPacks)


testing: trying to also cover call_lock/_call_unlock but I do not know how

--- salvus/test/misc-test.coffee
+++ salvus/test/misc-test.coffee
@@ -902,6 +902,7 @@

         fspy = sinon.spy()
         @objspy._call_with_lock(fspy)
+        @objspy.should.have.properties __call_lock: true
         fspy.should.have.callCount 1

         fspy2 = sinon.spy()
@@ -927,6 +928,21 @@

         cbspy3.should.have.callCount 0
         fspy3.should.have.callCount 1
+
+    it "unlocks when function is called", =>
+        fcl = misc.call_lock(@o)
+
+        fspy = sinon.spy()
+        cbspy2 = sinon.spy()
+        f = () -> fspy()
+        @objspy._call_with_lock(f, cbspy2)
+
+        cbspy2.should.have.callCount 0
+        fspy.should.have.callCount 1
+
+        # TODO I have no idea how to actually call it in such a way,
+        # that this is false
+        @objspy.should.have.properties __call_lock: true

 describe "encode_path", ->
     e = misc.encode_path


Added random start option. Fixed bug when no caption is available. If items are only images, they are now wrapped to fix problems with caption. Improved default position for controls and size.

--- src/jquery.rondell.coffee
+++ src/jquery.rondell.coffee
@@ -2,8 +2,8 @@
   jQuery rondell plugin
   @name jquery.rondell.js
   @author Sebastian Helzle (sebastian@helzle.net or @sebobo)
-  @version 0.8.5
-  @date 01/03/2012
+  @version 0.8.6
+  @date 01/20/2012
   @category jQuery plugin
   @copyright (c) 2009-2012 Sebastian Helzle (www.sebastianhelzle.net)
   @license Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
@@ -12,7 +12,7 @@
 (($) ->
   ### Global rondell plugin properties ###
   $.rondell =
-    version: '0.8.5'
+    version: '0.8.6'
     name: 'rondell'
     defaults:
       showContainer: true       # When the plugin has finished initializing $.show() will be called on the items container
@@ -22,11 +22,11 @@
       currentLayer: 0           # Active layer number in a rondell instance
       container: null           # Container object wrapping the rondell items
       radius:                   # Radius for the default circle function
-        x: 300
+        x: 250
         y: 50
       center:                   # Center where the focused element is displayed
-        left: 400
-        top: 200
+        left: 340
+        top: 160
       size:                     # Defaults to center * 2 on init
         width: null
         height: null
@@ -57,8 +57,8 @@
         enabled: true
         fadeTime: 400           # Show/hide animation speed
         margin:
-          x: 20                 # Distance from left and right edge of the container
-          y: 20                 # Distance from top and bottom edge of the container
+          x: 130                 # Distance from left and right edge of the container
+          y: 270                 # Distance from top and bottom edge of the container
       strings: # String for the controls
         prev: 'prev'
         next: 'next'
@@ -73,6 +73,7 @@
         threshold: 100          # Distance in pixels the "finger" has to swipe to create the touch event
         _start: undefined
         _end: undefined
+      randomStart: false
       funcEase: 'easeInOutQuad' # jQuery easing function name for the movement of items
       theme: 'default'          # CSS theme class which gets added to the rondell container
       preset: ''                # Configuration preset
@@ -143,20 +144,26 @@
     _initItem: (layerNum, item) =>
       @items[layerNum - 1] = item

+      # If item is an img tag, wrap with div
+      if item.object[0].nodeName.toLowerCase() is "img"
+        item.object.wrap("
")
+        item.object = item.object.parent()
+        item.icon = $('img:first', item.object)
+
       # Wrap other content as overlay caption
       captionContent = item.icon?.siblings()
       if not (captionContent?.length or item.icon) and item.object.children().length
         captionContent = item.object.children()

       # Or use title/alt texts as overlay caption
-      if not captionContent.length
-        caption = item.object.attr('title') or item.icon?.attr('alt') or item.icon?.attr('title')
+      unless captionContent?.length
+        caption = item.object.attr('title') or item.icon?.attr('title') or item.icon?.attr('alt')
         if caption
           captionContent = $("
#{caption}
")
           item.object.append(captionContent)

       # Create overlay from caption if found
-      if captionContent.length
+      if captionContent?.length
         captionContainer = $('

')
         captionContainer.addClass('overlay') if item.icon
         captionContent.wrapAll(captionContainer)
@@ -261,7 +268,10 @@

     _start: =>
       # Set currentlayer to the middle item or leave it be if set before and index exists
-      @currentLayer = Math.max(0, Math.min(@currentLayer or Math.round(@maxItems / 2), @maxItems))
+      if @randomStart
+        @currentLayer = Math.round(Math.random() * (@maxItems - 1))
+      else
+        @currentLayer = Math.max(0, Math.min(@currentLayer or Math.round(@maxItems / 2), @maxItems))

       # Set visibleItems to half the maxItems if set to auto
       @visibleItems = Math.max(2, Math.floor(@maxItems / 2)) if @visibleItems is 'auto'


Pull all the state changes out into a separate function

This stuff is super confusing, and this helped clear things up for me

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -409,26 +409,50 @@
   # target is `.defaultTarget` if that property is assigned on the keymap.
   #
   # * `event` A `KeyboardEvent` of type 'keydown'
-  handleKeyboardEvent: (event) ->
+  handleKeyboardEvent: (event, isReplay) ->
+    eventResult = @_handleKeyboardEvent(event, @queuedKeystrokes)
+    return unless eventResult?
+    {keystroke, queuedKeystrokes, exactMatch, partialMatches} = eventResult
+
+    hasPartialMatches = partialMatches? and partialMatches.length > 0
+
+    @queuedKeystrokes.push(keystroke)
+    @queuedKeyboardEvents.push(event)
+
+    if queuedKeystrokes?
+      if hasPartialMatches
+        enableTimeout = (
+          @pendingStateTimeoutHandle? or
+          exactMatch? or
+          characterForKeyboardEvent(@queuedKeyboardEvents[0])?
+        )
+        enableTimeout = false if isReplay
+        @enterPendingState(partialMatches, enableTimeout)
+    else if not exactMatch? and not hasPartialMatches and @pendingPartialMatches?
+      @terminatePendingState()
+    else
+      @clearQueuedKeystrokes()
+
+  _handleKeyboardEvent: (event, queuedKeystrokes=[]) ->
     keystroke = @keystrokeForKeyboardEvent(event)

-    if event.type isnt 'keyup' and @queuedKeystrokes.length > 0 and isAtomModifier(keystroke)
+    if event.type isnt 'keyup' and queuedKeystrokes.length > 0 and isAtomModifier(keystroke)
       event.preventDefault()
       return
-
-    @queuedKeyboardEvents.push(event)
-    @queuedKeystrokes.push(keystroke)
-    keystrokes = @queuedKeystrokes.join(' ')

     # If the event's target is document.body, assign it to defaultTarget instead
     # to provide a catch-all element when nothing is focused.
     target = event.target
     target = @defaultTarget if event.target is document.body and @defaultTarget?

+    queuedKeystrokes = queuedKeystrokes.concat([keystroke])
+    keystrokes = queuedKeystrokes.join(' ')
+
     # First screen for any bindings that match the current keystrokes,
     # regardless of their current selector. Matching strings is cheaper than
     # matching selectors.
-    {partialMatchCandidates, exactMatchCandidates} = @findMatchCandidates(@queuedKeystrokes)
+    {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates} = @findMatchCandidates(queuedKeystrokes)
+    exactMatch = null
     partialMatches = @findPartialMatches(partialMatchCandidates, target)

     # Determine if the current keystrokes match any bindings *exactly*. If we
@@ -438,33 +462,39 @@
     # state with a timeout.
     if exactMatchCandidates.length > 0
       currentTarget = target
-      while currentTarget? and currentTarget isnt document
+      while not event.handled and currentTarget? and currentTarget isnt document
         exactMatches = @findExactMatches(exactMatchCandidates, currentTarget)
-        for exactMatch in exactMatches
-          if exactMatch.command is 'native!'
-            @clearQueuedKeystrokes()
-            return
-
-          if exactMatch.command is 'abort!'
-            @clearQueuedKeystrokes()
+        for exactMatchCandidate in exactMatches
+          if exactMatchCandidate.command is 'native!'
+            return {keystroke, queuedKeystrokes: null}
+
+          if exactMatchCandidate.command is 'abort!'
             event.preventDefault()
-            return
-
-          if exactMatch.command is 'unset!'
+            return {keystroke, queuedKeystrokes: null}
+
+          if exactMatchCandidate.command is 'unset!'
             break

-          foundMatch = true
-          break if partialMatches.length > 0
-          @clearQueuedKeystrokes()
-          @cancelPendingState()
-          if @dispatchCommandEvent(exactMatch.command, target, event)
+          exactMatch = exactMatchCandidate
+          if partialMatches.length > 0
+            allPartialMatchesContainKeyupRemainder = true
+            for partialMatch in partialMatches
+              if keydownExactMatchCandidates.indexOf(partialMatch) < 0
+                allPartialMatchesContainKeyupRemainder = false
+            break if allPartialMatchesContainKeyupRemainder is false
+          else
+            queuedKeystrokes = null
+
+          if @dispatchCommandEvent(exactMatchCandidate.command, target, event)
+            console.log '..dispatching', keystrokes
             @emitter.emit 'did-match-binding', {
               keystrokes,
               eventType: event.type,
-              binding: exactMatch,
+              binding: exactMatchCandidate,
               keyboardEventTarget: target
             }
-            return
+            event.handled = true
+            break
         currentTarget = currentTarget.parentElement

     # If we're at this point in the method, we either found no matches for the
@@ -474,36 +504,30 @@
     # keystroke.
     if partialMatches.length > 0
       event.preventDefault()
-      enableTimeout = (
-        @pendingStateTimeoutHandle? or
-        foundMatch or
-        characterForKeyboardEvent(@queuedKeyboardEvents[0])?
-      )
-
-      @enterPendingState(partialMatches, enableTimeout)
       @emitter.emit 'did-partially-match-binding', {
         keystrokes,
         eventType: event.type,
         partiallyMatchedBindings: partialMatches,
         keyboardEventTarget: target
       }
-    else
+    else if exactMatch is null
       @emitter.emit 'did-fail-to-match-binding', {
         keystrokes,
         eventType: event.type,
         keyboardEventTarget: target
       }
-      if @pendingPartialMatches?
-        @terminatePendingState()
-      else
-        # Some of the queued keyboard events might have inserted characters had
-        # we not prevented their default action. If we're replaying a keystroke
-        # whose default action was prevented and no binding is matched, we'll
-        # simulate the text input event that was previously prevented to insert
-        # the missing characters.
-        @simulateTextInput(event) if event.defaultPrevented
-
-        @clearQueuedKeystrokes()
+      # Some of the queued keyboard events might have inserted characters had
+      # we not prevented their default action. If we're replaying a keystroke
+      # whose default action was prevented and no binding is matched, we'll
+      # simulate the text input event that was previously prevented to insert
+      # the missing characters.
+      if event.defaultPrevented
+        @simulateTextInput(event)
+        console.log 'simulate', keystroke
+      queuedKeystrokes = null
+
+    return {keystroke, exactMatch, partialMatches, queuedKeystrokes}
+

   # Public: Translate a keydown event to a keystroke string.
   #
@@ -538,6 +562,7 @@
   findMatchCandidates: (keystrokeArray) ->
     partialMatchCandidates = []
     exactMatchCandidates = []
+    keydownExactMatchCandidates = []

     for binding in @keyBindings when binding.enabled
       doesMatch = keystrokesMatch(binding.keystrokeArray, keystrokeArray)
@@ -545,7 +570,10 @@
         exactMatchCandidates.push(binding)
       else if doesMatch is 'partial'
         partialMatchCandidates.push(binding)
-    {partialMatchCandidates, exactMatchCandidates}
+      else if doesMatch is 'keydownExact'
+        partialMatchCandidates.push(binding)
+        keydownExactMatchCandidates.push(binding)
+    {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates}

   # Determine which of the given bindings have selectors matching the target or
   # one of its ancestors. This is used by {::handleKeyboardEvent} to determine


Docco for Serializer.pipe.

Docco for Serializer.pipe.

--- src/lib/serializer.coffee
+++ src/lib/serializer.coffee
@@ -140,7 +140,13 @@
   stream: (length) ->
     @_stream = new (require("./writable").WritableStream)(length, @)

-  _pipe: (destination, options) ->
+  #### pipe(destination, options)
+
+  # An implementation of `Stream.pipe` that can use an `Array` or `Buffer` as a
+  # destination. If the destination is another `Stream`, then the inherited
+  # implementation of `Stream.pipe` is invoked after initialization, otherwise,
+  # the pipe logic in `Stream` is non-applicable.
+  pipe: (destination, options) ->
     if destination instanceof Array
       @_buffer = destination
       @_bufferLength = Number.MAX_VALUE


feat(draw-route): allow multiple flows out from source endpoints

Closes #64.

--- app/scripts/stencils/base.coffee
+++ app/scripts/stencils/base.coffee
@@ -255,6 +255,7 @@
           radius: 7
           lineWidth: 3
         isSource: true
+        maxConnections: -1
         connector: [
           "Flowchart"
           {
@@ -291,7 +292,7 @@
           fillStyle: "#7AB02C"
           radius: 11
         hoverPaintStyle: @endpointHoverStyle
-        maxConnections: - 1
+        maxConnections: -1
         dropOptions:
           hoverClass: "hover"
           activeClass: "active"


Correctly check if results should be displayed after command load

--- app/scripts/views/console.coffee
+++ app/scripts/views/console.coffee
@@ -31,7 +31,9 @@
       @escapeClose()

       App.once "load.commands sync.commands",  =>
-        return unless @$input or not @input.val()
+        return unless @$input
+        return unless @$input?.val()
+
         App.trigger "command:search", @$input.val(), true

       App.on


remove AppConfig from dependencies for CurrentUser

--- app/services/current-user.coffee
+++ app/services/current-user.coffee
@@ -1,7 +1,7 @@
 null

 ### @ngInject ###
-global.cobudgetApp.factory 'CurrentUser', (Records, AppConfig, ipCookie) ->
+global.cobudgetApp.factory 'CurrentUser', (Records, ipCookie) ->

   setId: (id) ->
     ipCookie('currentUserId', id)


Throw exception when adding a pane item that has already been destroyed

Closes atom/bracket-matcher#178

--- src/pane.coffee
+++ src/pane.coffee
@@ -355,6 +355,7 @@
   # Returns the added item.
   addItem: (item, index=@getActiveItemIndex() + 1) ->
     throw new Error("Pane items must be objects. Attempted to add item #{item}.") unless item? and typeof item is 'object'
+    throw new Error("Adding a pane item with URI '#{item.getURI?()}' that has already been destroyed") if item.isDestroyed?()

     return if item in @items


Remove variable conflict

Remove variable conflict

--- app/scripts/lib/Dialog.coffee
+++ app/scripts/lib/Dialog.coffee
@@ -42,7 +42,7 @@
         z: 100
       )

-    speaker = Crafty.e('2D, Canvas, Text')
+    speakerText = Crafty.e('2D, Canvas, Text')
       .attr(w: 550, x: back.x + 10, y: back.y + 10, z: 101, alpha: 1)
       .text(speaker)
       .textColor('#707070')
@@ -51,7 +51,7 @@
         weight: 'bold',
         family: 'Bank Gothic'
       })
-    back.attach(speaker)
+    back.attach(speakerText)

     for line, i in lines
       back.attach(Crafty.e('2D, Canvas, Text')


Fix typo

Fix typo

--- app/templates/gulp/config.coffee
+++ app/templates/gulp/config.coffee
@@ -200,7 +200,7 @@
         else libpath.join PROP.path.app, "images", "**", "*.{gif,png,jpg,jpeg,webp}"

     images_preload:->
-      if PROP.isDEV
+      if PROP.isDev
         PROP.path.images()
       else
         PROP.path.images("dest_src")


Fixes small search history bug where it wouldn't work when backing to empty forms

--- app/views/commits/show.js.coffee
+++ app/views/commits/show.js.coffee
@@ -34,8 +34,11 @@
         new Flash('alert').text("Couldn't update commit export status")

   prefillForm = () ->
-    for own param, val of $.url().param()
-      searchForm.find("[name=#{param}]").val(val.trim())
+    if $.isEmptyObject($.url().param())
+      searchForm.trigger('reset')
+    else
+      for own param, val of $.url().param()
+        searchForm.find("[name=#{param}]").val(val.trim())

   prefillForm()

@@ -89,9 +92,11 @@
     scroll.loadNextPage()
     false

-  window.onpopstate =  (e) ->
-    prefillForm()
-    table.find('tbody').empty()
-    scroll.reset()
-    scroll.loadNextPage()
-    false+  window.addEventListener 'load', ->
+    setTimeout ->
+      window.onpopstate =  (e) ->
+        prefillForm()
+        table.find('tbody').empty()
+        scroll.reset()
+        scroll.loadNextPage()
+    , 0


ボードゲーム登録時のid生成方法を修正

ボードゲーム登録時のid生成方法を修正

--- scripts/add.coffee
+++ scripts/add.coffee
@@ -22,8 +22,19 @@
       return

     boardgames = storage.getBoardgames(robot)
+    new_id = boardgames.length + 1
+
+    boardgames.sort (a, b) ->
+      if a.id < b.id
+        -1
+      else
+        1
+    .map (item) ->
+      if item.id == new_id
+        new_id += 1
+
     boardgame = {
-      id: boardgames.length + 1,
+      id: new_id,
       name: boardgame_name,
       min: min,
       max: max


The case of the two speakers.

The case of the two speakers.

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -32,7 +32,7 @@
       buildComic lines, (err, image) ->
         return msg.reply "SOMETHING TERRIBLE HAPPENED: #{err}" if err

-        console.log " => Built image: #{image.width}x#{image.height}"
+        msg.reply " => Built image: #{image.width}x#{image.height}"
         # TODO: Temp file name
         outPath = path.resolve(ROOT, "out.png")
         try fs.unlinkSync(outPath) # lol
@@ -155,9 +155,24 @@
       top = (frame.height - char.height)
       compositeImage frame, char, left, top
       # TODO: Here, I would add the text
-    else
-      return cb("TODO: Draw each on their own side", frame)
-      # TODO: Also, draw the text for both
+    else # We have two speakers
+      first = true
+      for line in lines
+        [who, what] = line
+        char = names[who]
+        top = (frame.height - char.height)
+
+        if first
+          left = 0
+        else
+          left = frame.width - char.width
+
+        compositeImage frame, char, left, top
+        # TODO: Draw `what`
+
+    # Return the frame to the caller from `namesReady`
+    # [Comment highlights indent]
+    # TODO: JESUS GOD REFACTOR THIS FLOW
     return cb(false, frame)

 # Composite `sprite` onto `dst` in full.


automatically clense Bower sink if error happens

--- library/membrane/bower.coffee
+++ library/membrane/bower.coffee
@@ -40,6 +40,7 @@

 {EOL} = require "os"
 {Screenplay} = require "./visual"
+{rmdirSyncRecursive} = require "wrench"

 # This abstract base class provides the dynamic Bower support for
 # the services that inherit or compose this ABC. This implementation
@@ -129,6 +130,12 @@
         bowerings = @constructor.bowerings ?= Array()
         assert installer = install targets, {}, options
         kernel.domain.add installer if kernel.domain.add
+        assert removing = "Clense (rm) Bower sink at %s"
+        assert _.isString directory = bowerings.directory
+        fwd = (arg) => kernel.domain.emit "error", arg...
+        mark = => logger.warn removing.yellow, directory
+        destroy = => mark try rmdirSyncRecursive directory
+        installer.on "error", -> destroy(); fwd arguments
         return installer.on "end", (installed) => do =>
             message = "Get Bower library %s@%s at %s"
             assert bowerings.installed = installed or 0


Added fixes for callback hopefully:

Added fixes for callback hopefully:

--- src/meme-generator.coffee
+++ src/meme-generator.coffee
@@ -83,19 +83,17 @@
     text1: text1
   })), (e, res, body) ->
     if e
-      console.log err
+      msg.reply err
       return

     jsonBody = JSON.parse(body)
     success = jsonBody?.success

     unless success
-      console.log jsonBody
+      msg.reply jsonBody
       return

     img = jsonBody.data?.url
-
-    console.log img

     unless img
       msg.reply "Ugh, I got back weird results from imgflip.net. Expected an image URL, but couldn't find it in the result. Here's what I got:", inspect(jsonBody)


resurrection.account: fix default section

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -5,7 +5,7 @@
   KD.registerAppClass this,
     name                         : "Account"
     routes                       :
-      "/:name?/Account"          : -> KD.singletons.router.handleRoute '/Account/login-email'
+      "/:name?/Account"          : -> KD.singletons.router.handleRoute '/Account/user-profile'
       "/:name?/Account/:section" : ({params:{section}})-> handler.call null, (app)-> app.openSection section
     behavior                     : "hideTabs"
     hiddenHandle                 : yes


Add more options to containment padding

Add more options to containment padding

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -36,6 +36,7 @@
     mouseleave    : "mouseLeave"
     mousemove     : "mouseMove"
     mousewheel    : "mouseWheel"
+    mouseover     : "mouseOver"
     contextmenu   : "contextMenu"
     dragstart     : "dragStart"
     dragenter     : "dragEnter"
@@ -601,11 +602,13 @@

   mouseUp:(event)->    yes

-  paste:(event)->      yes
+  mouseOver:(event)->  yes

   mouseDown:(event)->
     (KD.getSingleton "windowController").setKeyView null
     yes
+
+  paste:(event)->      yes

   # HTML5 DND
   dragEnter:(e)->
@@ -696,7 +699,7 @@
           dragState.containment.viewBounds = @[view].getBounds()
         dragState.containment.viewBounds or= @parent.getBounds()
         dragState.containment.padding = \
-          options.containment.padding or x: 0, y: 0
+          options.containment.padding or top: 0, right: 0, bottom: 0, left: 0

       # TODO: should move these lines
       dragState.handle      = options.handle
@@ -755,8 +758,8 @@
     dragGlobDir.x = if x > 0 then 'right'  else 'left'
     dragGlobDir.y = if y > 0 then 'bottom' else 'top'

-    el = @$()
     if @dragIsAllowed
+      el = @$()
       dragMeta   = @dragState.meta
       targetPosX = if dragMeta.right  and not dragMeta.left then 'right'  else 'left'
       targetPosY = if dragMeta.bottom and not dragMeta.top  then 'bottom' else 'top'
@@ -768,10 +771,10 @@
         m  = w: @getWidth(), h: @getHeight()
         p  = containment.viewBounds
         cp = containment.padding
-        if newX <= 0 + cp.x then newX = 0 + cp.x
-        if newY <= 0 + cp.y then newY = 0 + cp.y
-        if newX + m.w + cp.x >= p.w - cp.x then newX = p.w - m.w - cp.x
-        if newY + m.h + cp.y >= p.h - cp.y then newY = p.h - m.h - cp.y
+        if newX <= 0 + cp.left then newX = 0 + cp.left
+        if newY <= 0 + cp.top  then newY = 0 + cp.top
+        if newX + m.w >= p.w - cp.right  then newX = p.w - m.w - cp.right
+        if newY + m.h >= p.h - cp.bottom then newY = p.h - m.h - cp.bottom

       el.css targetPosX, newX unless axis is 'y'
       el.css targetPosY, newY unless axis is 'x'


payment: fix interval constant

payment: fix interval constant

--- client/Main/payment/workflow.coffee
+++ client/Main/payment/workflow.coffee
@@ -33,7 +33,7 @@
     modal = new PaymentModal
       state          :
         subscription : name
-        interval     : PaymentWorkflow.MONTH_INTERVAL
+        interval     : PaymentWorkflow.interval.MONTH
         price        : price


Terminal: removed unnecessary protection on size calculation

--- client/Terminal/src/Terminal.coffee
+++ client/Terminal/src/Terminal.coffee
@@ -174,12 +174,9 @@

   updateSize: (force = no) ->

-
-    [swidth, sheight] = [@parent.getWidth()  or @currentWidth,\
-                         @parent.getHeight() or @currentHeight]
-
     @updateAppSize()

+    [swidth, sheight] = [@parent.getWidth(), @parent.getHeight()]

     return  if not force and swidth is @currentWidth and sheight is @currentHeight


Reverse logic

Reverse logic

--- client/app/MainApp/KD.extend.coffee
+++ client/app/MainApp/KD.extend.coffee
@@ -71,7 +71,7 @@
     mainController = KD.getSingleton('mainController')
     delete mainController?.userAccount

-  isLoggedIn:-> KD.whoami().type is 'registered'
+  isLoggedIn:-> KD.whoami().type isnt 'unregistered'

   isMine:(account)-> KD.whoami().profile.nickname is account.profile.nickname


Fix escaping bug in latex-fold

Fix escaping bug in latex-fold

--- client/codemirror/latex-fold.coffee
+++ client/codemirror/latex-fold.coffee
@@ -9,7 +9,7 @@
     i = s.indexOf('{')
     j = s.indexOf('}')
     if i != -1 and j != -1
-        return s.slice(i+1,j).trim()
+        return escapeRegExp s.slice(i+1,j).trim()
     else
         return undefined


Login: reload ontime after registration

Login: reload ontime after registration

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -375,14 +375,9 @@
         return location.reload()  unless KD.remote.isConnected()

         @headBanner.hide()
-        #could not joined to the group. Directing to Koding
-        window.location.href= "/" if err
-
-        KD.utils.wait 1000, =>
-          @registerForm.reset()
-          @registerForm.button.hideLoader()
-          @hide()
-          location.replace('/')
+
+        window.location.href= '/'
+

   doFinishRegistration: (formData) ->
     (KD.getSingleton 'mainController').handleFinishRegistration formData, @bound 'afterLoginCallback'


always set name if it's blank

always set name if it's blank

--- client/skr/models/CreditCard.coffee
+++ client/skr/models/CreditCard.coffee
@@ -36,14 +36,16 @@
         @listenTo(@linkToAddress, 'change:name', @onAddressNameChange) if @linkToAddress

     onAddressNameChange: ->
-        if @name is @linkToAddress.previousAttributes().name
+        if !@name || @name is @linkToAddress.previous('name')
             @name = @linkToAddress.name

     onNameChange: ->
         @parent.name = @name

     onExpiryChange: ->
-        [@month, @year] = @expiry.split(' / ')
+        [month, year] = @expiry.split(' / ')
+        @month = month if month
+        @year  = year  if year

     dataForSave: (options = {}) ->
         attrs = super


add Repo#revert

add Repo#revert

--- src/repo.coffee
+++ src/repo.coffee
@@ -119,7 +119,7 @@
   # callback - Receives `(err, callback)`
   #
   status: (callback) ->
-    return new Status(this, callback)
+    return Status(this, callback)

   # Public: Get the repository's tags.
@@ -224,3 +224,7 @@
   remove: (files, callback) ->
     files = [files] if _.isString files
     @git "rm", {}, files, callback
+
+  # Public: Revert the given commit.
+  revert: (sha, callback) ->
+    @git "revert", {}, sha, callback


Allow mocking of id.

Allow mocking of id.

--- server/auth.coffee
+++ server/auth.coffee
@@ -65,11 +65,11 @@
   randomName += "Fan" + Math.floor(Math.random() * 10000)
   randomName

-generateId = ->
-  Math.floor(1000000 * Math.random())
+generateId = (req) ->
+  req.param('id') || Math.floor(1000000 * Math.random())

 generateUser = (req) ->
-  {id: generateId(), username: generateUsername(req)}
+  {id: generateId(req), username: generateUsername(req)}

 # Authorization


removed drop_db file

removed drop_db file

--- src/oneoff/drop_db.coffee
+++ src/oneoff/drop_db.coffee
@@ -1,8 +0,0 @@
-models = require '../lib/models'
-models.sequelize
-.drop()
-.complete (err) ->
-  if err?
-    console.log "An error occurred while creating the table", err
-  else
-    console.log "dropped tables."


Removed duplicates

Removed duplicates


Fix: Update test.response.body faild.

Fix: Update test.response.body faild.

--- src/test.coffee
+++ src/test.coffee
@@ -73,7 +73,7 @@
       schema = @parseSchema @response.schema
       assert.jsonSchema (JSON.parse body), schema, 'Response Body'

-    # Update @response
-    @response.body = JSON.parse body
+      # Update @response
+      @response.body = JSON.parse body

 module.exports = Test


minor: remove irreversible and useless sand rule


Classroom: Be sure storage fetched before doing anything.

--- client/app/Applications/Classroom.kdapplication/AppView.coffee
+++ client/app/Applications/Classroom.kdapplication/AppView.coffee
@@ -17,19 +17,18 @@
     storage = @appStorage

     @readFileContent url, (manifests) =>
-      storage.ready =>
-        enrolled    = storage.getValue("Enrolled")  or {}
-        related     = storage.getValue("Related")   or {}
-        imported    = storage.getValue("Imported")  or {}
-        completed   = storage.getValue("Completed") or {}
-
-        for manifest in manifests
-          {name}        = manifest
-          related[name] = manifest  unless enrolled[name] or imported[name]
-
-        @addSubView @coursesView = new ClassroomCoursesView
-          delegate : this
-        , { enrolled, related, completed, imported }
+      enrolled    = storage.getValue("Enrolled")  or {}
+      related     = storage.getValue("Related")   or {}
+      imported    = storage.getValue("Imported")  or {}
+      completed   = storage.getValue("Completed") or {}
+
+      for manifest in manifests
+        {name}        = manifest
+        related[name] = manifest  unless enrolled[name] or imported[name]
+
+      @addSubView @coursesView = new ClassroomCoursesView
+        delegate : this
+      , { enrolled, related, completed, imported }

   createHeader: ->
     @addSubView @header = new KDView
@@ -75,13 +74,13 @@

   getManifestFromAppStorage: (courseName, callback = noop) ->
     appStorage  = @appStorage
-    @appStorage.ready =>
-      enrolled  = appStorage.getValue "Enrolled"
-      imported  = appStorage.getValue "Imported"
-      @manifest = enrolled[courseName] or imported[courseName] or null
-      return warn "Course manifest file is not exist"  unless @manifest
-
-      callback()
+
+    enrolled  = appStorage.getValue("Enrolled") or {}
+    imported  = appStorage.getValue("Imported") or {}
+    @manifest = enrolled[courseName] or imported[courseName] or null
+    return warn "Course manifest file is not exist"  unless @manifest
+
+    callback()

   handleGoToChapter: (chapterIndex) ->
     courseManifest = @manifest
@@ -116,15 +115,16 @@
     @appStorage.setValue "Completed", completedCourses

   handleQuery: (query) ->
-    @destroySubViews()
-    unless query.course
-      @createHeader()
-      @fetchCourses()
-    else
-      if query.chapter
-        @goToChapter query.course, query.chapter - 1
+    @appStorage.fetchStorage (storage) =>
+      @destroySubViews()
+      unless query.course
+        @createHeader()
+        @fetchCourses()
       else
-        @goToCourse query.course
+        if query.chapter
+          @goToChapter query.course, query.chapter - 1
+        else
+          @goToCourse query.course

   isCourseStarted: ->
     {startDate} = @manifest


Client: Sidebar: Set false to showLeaveSharedMachineWidget state for not show after page loaded

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -37,7 +37,7 @@

     @state = {
       collapsed : status isnt Machine.State.Running and not @props.active
-      showLeaveSharedMachineWidget : yes
+      showLeaveSharedMachineWidget : no
     }


mlkshk is dead and also we don't need this and also also it is broken

--- scripts/mlkshk-observer.coffee
+++ scripts/mlkshk-observer.coffee
@@ -1,36 +0,0 @@
-# Description:
-#   Returns title when MLKSHK images are posted
-#
-# Dependencies:
-#   "jsdom": "0.2.15"
-#
-# Configuration:
-#
-# Commands:
-#   http://mlkshk.com/r/<IMG>.jpg - prints the title for the image
-#
-# Author:
-#   ivey
-#   Based on http-info by ajacksified
-
-jsdom = require 'jsdom'
-_     = require 'underscore'
-
-module.exports = (robot) ->
-
-  robot.hear /http:\/\/(s\.)?mlkshk.com\/r\/(\w+)(\.(png|jpg|jpeg|gif))?/i, (msg) ->
-    imageId = msg.match[2]
-
-    jsdom.env(
-      html: "http://mlkshk.com/p/#{imageId}"
-      scripts: [
-        'http://code.jquery.com/jquery-1.7.2.min.js'
-      ]
-      done: (errors, window) ->
-        unless errors
-          $ = window.$
-          title = $('.image-title h1').text()
-
-          if title
-            msg.send title
-     )


fix callback prototype in getLock

fix callback prototype in getLock

--- app/coffee/LockManager.coffee
+++ app/coffee/LockManager.coffee
@@ -41,7 +41,7 @@
 				logger.log {doc_id}, "doc is locked"
 				callback err, false

-	getLock: (doc_id, callback = (error) ->) ->
+	getLock: (doc_id, callback = (error, lockValue) ->) ->
 		startTime = Date.now()
 		do attempt = () ->
 			if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME


client: home: chatlio view adjustments according to its api use

--- client/home/lib/utilities/homeutilitiescustomerfeedback.coffee
+++ client/home/lib/utilities/homeutilitiescustomerfeedback.coffee
@@ -1,6 +1,8 @@
+_              = require 'lodash'
 kd             = require 'kd'
 JView          = require 'app/jview'
 KodingSwitch   = require 'app/commonviews/kodingswitch'
+CustomLinkView = require 'app/customlinkview'

 module.exports = class HomeUtilitiesCustomerFeedback extends kd.CustomHTMLView
@@ -11,14 +13,30 @@

     super options, data

-    @switch  = new KodingSwitch
-      cssClass: 'small'
-      callback: (state) =>
-        if state
-          @emit 'ChatlioActivated'
-        else
-          @emit 'ChatlioDeactivated'
+    team          = kd.singletons.groupsController.getCurrentGroup()
+    { customize } = team
+    id            = customize.chatlioId

+    @input = new kd.InputView
+      defaultValue : id  if id
+
+    @guide = new CustomLinkView
+      cssClass : 'HomeAppView--button'
+      title    : 'VIEW GUIDE'
+      href     : 'https://www.koding.com/docs/chatlio'
+
+    @save = new CustomLinkView
+      cssClass : 'HomeAppView--button primary fr'
+      title    : 'SAVE'
+      click    : =>
+        id = @input.getValue().trim()
+        team.modify { 'customize.chatlioId': id }, (err) ->
+          new kd.NotificationView if err
+          then { title: 'There was an error, please try again!' }
+          else
+            if id
+            then { title: 'Chatlio id successfully saved!' }
+            else { title: 'Chatlio integration successfully turned off!' }

   pistachio: ->
@@ -26,20 +44,19 @@

     Customer Feedback
     Enable Chatlio.com for real-time customer feedback
-    {{> @switch}}
+
+
+      Chatlio will allow you to talk with your team members using your
+      existing Slack service. For this integration you need to create an
+      account at chatlio.com. * Requires Slack integration.
+

+      Once you get your Chatlio data-widget-id
+      and paste below, we will complete the integration for you.
+
+    <filedset>
+    Chatlio.com data-widget-id
+    {{> @input}}
+    {{> @save}}
+    {{> @guide}}

-    """
-
-    # @switch  = new KodingSwitch
-    #   cssClass: 'small'
-    #   callback: (state) =>
-    #     if state
-    #     then @setClass 'on'
-    #     else @unsetClass 'on'
-
-    # {{> @switch}}
-    #

-    # Customer Feedback
-    # Enable Chatlio.com for real-time customer feedback
-    #

-    #

+    """


API: added jobs endpoint since lever doesn't provide jsonp

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -250,6 +250,17 @@
 app.get "/-/version", (req, res) ->
   res.jsonp(version:KONFIG.version)

+app.get "/-/jobs", (req, res) ->
+
+  request = require 'request'
+  options =
+    url   : 'https://api.lever.co/v0/postings/koding'
+    json  : yes
+
+  request options, (err, r, postings) ->
+    res.send 404 if err
+    res.jsonp postings
+
 app.get "/sitemap:sitemapName", (req, res)->
   {JSitemap}       = koding.models


Declare screenLines ivar in DisplayBuffer

--- src/app/display-buffer.coffee
+++ src/app/display-buffer.coffee
@@ -13,6 +13,7 @@
 module.exports =
 class DisplayBuffer
   @idCounter: 1
+  screenLines: null
   rowMap: null
   tokenizedBuffer: null
   markers: null


add event method

add event method

--- src/view.coffee
+++ src/view.coffee
@@ -57,6 +57,30 @@
       name : 'prepend'
       transform: (old, result) -> "#{result}#{old}"

+  #
+  #
+  # @param [String] - event name
+  # @param [String] - custom event name, which will be fired on event name
+  # @param [Array]  - parameters which will be pass into method for custom event name
+  # @note  First param for bind method is an Custom Event
+  # @note  Second param for bind method is an Original Event
+  # @example
+  #    //html
+  #    <button id="click">click me</button>
+  #
+  #    //js
+  #    buttonView = Sirius.View("#click")
+  #    buttonView.on("click", "button:click", 'param1', 'param2', param3')
+  #
+  #    routes =
+  #      "button:click": (custom_event, original_event, p1, p2, p3) ->
+  #         # you code
+  on: (event_name, custom_event_name, params...) ->
+    adapter = Sirius.Application.adapter
+    adapter.bind(document, @element, event_name, (e) ->
+      params = [e].concat(params)
+      adapter.fire(document, custom_event_name, params...)
+    )

   # @nodoc


Don't deserialize null pane container root

--- src/app/pane-container.coffee
+++ src/app/pane-container.coffee
@@ -28,7 +28,10 @@
     @state.on 'changed', ({key, newValue, site}) =>
       return if site is @state.site.id
       if key is 'root'
-        @setRoot(deserialize(newValue))
+        if newValue?
+          @setRoot(deserialize(newValue))
+        else
+          @setRoot(null)

     @destroyedItemStates = []

@@ -91,7 +94,7 @@
   getRoot: ->
     @children().first().view()

-  setRoot: (root, options={}) ->
+  setRoot: (root) ->
     @empty()
     @append(root) if root?
     @state.set(root: root?.getState())


Tests: IDE: Terminal: Fixed terminateSession test.

--- client/test/lib/ide/terminal.coffee
+++ client/test/lib/ide/terminal.coffee
@@ -84,8 +84,6 @@

   terminateSession: (browser) ->

-    return browser.end()
-
     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)

@@ -93,7 +91,7 @@

     getSessionData = =>

-      selector = '.kdcontextmenu ul + li:not(.disabled)'
+      selector = '.kdcontextmenu ul + li.disabled'
       matcher  = /session-(\w+)\S+/

       return document.querySelector(selector).getAttribute('class').match(matcher)


Fixed secure websocket url

Fixed secure websocket url

--- cmdr_web/src/scripts/websock.coffee
+++ cmdr_web/src/scripts/websock.coffee
@@ -12,7 +12,7 @@
   should_reconnect: yes
   ever_connected: no
   options:
-    host: "wss://#{window.location.host.split(":")[0]}/websock"
+    host: "wss://#{window.location.host.split(":")[0]}/websocket"
     connect_timeout: 1000
     reconnection_delay: 500
     max_delay: 15*1000


improve trackForm

improve trackForm

--- src/client/track/index.coffee
+++ src/client/track/index.coffee
@@ -44,16 +44,27 @@
   trackForm: (forms, ev, properties) =>
     return false unless !!forms
     forms = [forms] if _.isElement(forms)
-    _.map forms, (form) =>
+    _.map forms, (form) =>
       return console.log("Not an HTML element", form) unless _.isElement(form)
       trackSubmit = (e) =>
         e.preventDefault();
         evt = if _.isFunction(ev) then ev(form) else ev
         props = if _.isFunction(properties) then properties(form) else properties
+
+        _isSubmitted = false
+        submit = ->
+          return if _isSubmitted
+          _isSubmitted = true
+          form.submit()
+
         setTimeout () =>
-          form.submit()
-        , 500
-        @track(evt, props)
+          submit()
+        , 1000
+
+        @track(evt, props).then =>
+          submit()
+        , =>
+          submit()
       $ = (window.jQuery || window.Zepto)
       if $ then $(form).submit(trackSubmit) else listen(form, 'submit', trackSubmit)
     true


bugfix, overlay doesn't get more information when renderers are pushed

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -521,7 +521,7 @@
           can_ctx.fillText(
             date_tick.toLocaleDateString(), x, 20)
         last_tick_end = (x + text_width) + 10
-
+
       @plot_view.ctx.beginPath()
       @plot_view.ctx.moveTo(xpos(current_tick), 0)
       @plot_view.ctx.lineTo(xpos(current_tick), @mget('height') * op_scale)
@@ -871,8 +871,8 @@
   render : () ->
     window.overlay_render += 1
     super()
-    for temp in _.zip(@mget('renderers'), @rendererviews)
-      [renderer, rendererview] = temp
+    for renderer in @mget('renderers')
+      rendererview = @plot_view.renderers[renderer.id]
       renderer = @model.resolve_ref(renderer)
       selected = {}
       if renderer.get_ref('data_source').get('selecting') == false


radius 3 for scattters by default, made sure lines are the write color

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -659,8 +659,8 @@
     data = @model.get_ref('data_source').get('data')
     @calc_buffer(data)

-    @plot_view.ctx.fillStyle = 'blue'
-    @plot_view.ctx.strokeStyle = @mget('color')
+    @plot_view.ctx.fillStyle = @mget('foreground-color')
+    @plot_view.ctx.strokeStyle = @mget('foreground-color')
     @plot_view.ctx.beginPath()

     @plot_view.ctx.moveTo(@screenx[0], @screeny[0])


Issue 47: Replace use of jQuery 'delegate' or 'live' with 'on'

--- src/account/summary.coffee
+++ src/account/summary.coffee
@@ -137,14 +137,14 @@

 		# Upon the user expanding a summary line,
 		# we will refresh the line and its inner plugins.
-		.live 'expand', (e, ui) ->
+		.on 'expand', (e, ui) ->
 			$(@).publish $('h3', @).prop 'id' # The h3 element id is the name of the data channel to publish on
 			$('.plugin', @).refresh()
 			return false

 		# Upon the user collapsing a summary line,
 		# we will empty its inner plugin content.
-		.live 'collapse', (e, ui) ->
+		.on 'collapse', (e, ui) ->
 			$('.plugin', @).empty()
 			return false


add missing callback call to destroy function

--- server/lib/logmessage.coffee
+++ server/lib/logmessage.coffee
@@ -76,4 +76,5 @@
 LogMessage.destroy = (mailbox, callback) ->
     Notifications.destroy "newmail-#{mailbox.id}"
     Notifications.destroy "download-#{mailbox.id}"
-    Notifications.destroy "importprogress-#{mailbox.id}"+    Notifications.destroy "importprogress-#{mailbox.id}"
+    callback()


move Promise wrapper around in section model

--- server/models/section.coffee
+++ server/models/section.coffee
@@ -95,33 +95,32 @@
   )

 sectionSchema.methods.cloneVisualisationsFrom = (sectionId) ->
-  new Promise( (resolve, reject) =>
-    Promise.promisify(Visualisation.find, Visualisation)(
-      section: sectionId
-    ).then( (visualisations) =>
+  Promise.promisify(Visualisation.find, Visualisation)(
+    section: sectionId
+  ).then( (visualisations) =>
+    new Promise((resolve, reject) =>
       async.map(visualisations, cloneVisualisation.bind(@), (err, clonedvisualisations) ->
         if err?
           reject(err)

         resolve(clonedvisualisations)
       )
-    ).catch(reject)
+    )
   )

 sectionSchema.methods.cloneNarrativesFrom = (sectionId) ->

-  new Promise( (resolve, reject) =>
-    Promise.promisify(Narrative.find, Narrative)(
-      section: sectionId
-    ).then( (narratives) =>
-
+  Promise.promisify(Narrative.find, Narrative)(
+    section: sectionId
+  ).then((narratives) =>
+    new Promise((resolve, reject) =>
       async.map(narratives, cloneNarrative.bind(@), (err, clonedNarratives) ->
         if err?
           reject(err)

         resolve(clonedNarratives)
       )
-    ).catch(reject)
+    )
   )


Make level session selection random

Make level session selection random

Rather than weighted random on submission time

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -138,14 +138,14 @@
     selection = "team totalScore transpiledCode teamSpells levelID creatorName creator submitDate"
     LevelSession.count queryParams, (err, numberOfHumans) =>
       if err? then return errors.serverError(res, "Couldn't get the number of human games")
-      humanSkipCount = selectRandomSkipIndex(numberOfHumans)
+      humanSkipCount = Math.floor(Math.random() * numberOfHumans)
       ogreCountParams =
         "levelID": "greed"
         "submitted":true
         "team":"ogres"
       LevelSession.count ogreCountParams, (err, numberOfOgres) =>
         if err? then return errors.serverError(res, "Couldnt' get the number of ogre games")
-        ogresSkipCount = selectRandomSkipIndex(numberOfOgres)
+        ogresSkipCount = Math.floor(Math.random() * numberOfOgres)

         query = LevelSession
           .aggregate()


Removed giant commented code section

Removed giant commented code section

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -304,76 +304,7 @@
     if err? then return callback err, {"error":"There was an error retrieving the session."}
     callback null, session

-###
-module.exports.processTaskResult = (req, res) ->
-  clientResponseObject = verifyClientResponse req.body, res
-
-  return unless clientResponseObject?
-  TaskLog.findOne {_id: clientResponseObject.taskID}, (err, taskLog) ->
-    return errors.serverError res, "There was an error retrieiving the task log object" if err?
-
-    taskLogJSON = taskLog.toObject()
-
-    return errors.badInput res, "That computational task has already been performed" if taskLogJSON.calculationTimeMS
-    return handleTimedOutTask req, res, clientResponseObject if hasTaskTimedOut taskLogJSON.sentDate
-
-    scoringTaskQueue.deleteMessage clientResponseObject.receiptHandle, (err) ->
-      console.log "Deleted message."
-      if err? then return errors.badInput res, "The queue message is already back in the queue, rejecting results."
-
-      LevelSession.findOne(_id: clientResponseObject.originalSessionID).lean().exec (err, levelSession) ->
-        if err? then return errors.serverError res, "There was a problem finding the level session:#{err}"
-
-        supposedSubmissionDate = new Date(clientResponseObject.sessions[0].submitDate)
-
-        if Number(supposedSubmissionDate) isnt Number(levelSession.submitDate)
-          return sendResponseObject req, res, {"message":"The game has been resubmitted. Removing from queue..."}
-
-        logTaskComputation clientResponseObject, taskLog, (logErr) ->
-          if logErr? then return errors.serverError res, "There as a problem logging the task computation: #{logErr}"
-
-          updateSessions clientResponseObject, (updateError, newScoreArray) ->
-            if updateError? then return errors.serverError res, "There was an error updating the scores.#{updateError}"
-
-            newScoresObject = _.indexBy newScoreArray, 'id'
-
-            addMatchToSessions clientResponseObject, newScoresObject, (err, data) ->
-              if err? then return errors.serverError res, "There was an error updating the sessions with the match! #{JSON.stringify err}"
-
-              incrementUserSimulationCount req.user._id, 'simulatedBy'
-              incrementUserSimulationCount levelSession.creator, 'simulatedFor'
-
-              originalSessionID = clientResponseObject.originalSessionID
-              originalSessionTeam = clientResponseObject.originalSessionTeam
-              originalSessionRank = parseInt clientResponseObject.originalSessionRank
-
-              determineIfSessionShouldContinueAndUpdateLog originalSessionID, originalSessionRank, (err, sessionShouldContinue) ->
-                if err? then return errors.serverError res, "There was an error determining if the session should continue, #{err}"
-
-                if sessionShouldContinue
-                  opposingTeam = calculateOpposingTeam(originalSessionTeam)
-                  opponentID = _.pull(_.keys(newScoresObject), originalSessionID)
-                  sessionNewScore = newScoresObject[originalSessionID].totalScore
-                  opponentNewScore = newScoresObject[opponentID].totalScore
-
-                  levelOriginalID = levelSession.level.original
-                  levelOriginalMajorVersion = levelSession.level.majorVersion
-                  findNearestBetterSessionID levelOriginalID, levelOriginalMajorVersion, originalSessionID, sessionNewScore, opponentNewScore, opponentID, opposingTeam, (err, opponentSessionID) ->
-                    if err? then return errors.serverError res, "There was an error finding the nearest sessionID!"
-                    if opponentSessionID
-                      addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
-                        if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
-                        sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
-                    else
-                      LevelSession.update {_id: originalSessionID}, {isRanking: false}, {multi: false}, (err, affected) ->
-                        if err? then return errors.serverError res, "There was an error marking the victorious session as not being ranked."
-                        return sendResponseObject req, res, {"message":"There were no more games to rank (game is at top)!"}
-                else
-                  console.log "Player lost, achieved rank #{originalSessionRank}"
-                  LevelSession.update {_id: originalSessionID}, {isRanking: false}, {multi: false}, (err, affected) ->
-                    if err? then return errors.serverError res, "There was an error marking the completed session as not being ranked."
-                    sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
-###
+
 module.exports.processTaskResult = (req, res) ->
   async.waterfall [
     verifyClientResponse.bind(@,req.body)


Updated definitions titles-

Updated definitions titles-

--- snippets/bootstrap3_forms.cson
+++ snippets/bootstrap3_forms.cson
@@ -1,6 +1,6 @@
 '.text.html.php.blade, .text.html, .text.html.php':

-  "Bootstrap3 Form - Form Group":
+  "Form Group":
     "prefix":"bootstrap3:formgroup"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 form-group + label'
@@ -12,7 +12,7 @@
       $6
     """

-  "Bootstrap3 Form - Button Group":
+  "Button Group":
     "prefix":"bootstrap3:buttonGroup"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Button Group'
@@ -24,7 +24,7 @@

     """

-  "Bootstrap3 Form - Form tag":
+  "Form tag":
     "prefix":"bootstrap3:form"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Form declaration'
@@ -34,7 +34,7 @@
       </form>
     """

-  "Bootstrap3 Form - Text input":
+  "Text input":
     "prefix":"bootstrap3:input"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Input'
@@ -43,7 +43,7 @@
       $11
     """

-  "Bootstrap3 Form - Textarea input":
+  "Textarea input":
     "prefix":"bootstrap3:textarea"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Text area'
@@ -52,7 +52,7 @@
       $7
     """

-  "Bootstrap3 Form - Checkbox input":
+  "Checkbox input":
     "prefix":"bootstrap3:checkbox"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Checkbox'
@@ -66,7 +66,7 @@
       $9
     """

-  "Bootstrap3 Form - Checkbox input Inline":
+  "Checkbox input Inline":
     "prefix":"bootstrap3:checkboxinline"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Checkbox inline'
@@ -80,7 +80,7 @@
       $8
     """

-  "Bootstrap3 Form - Radio input":
+  "Radio input":
     "prefix":"bootstrap3:radio"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Radio button'
@@ -94,7 +94,7 @@
       $9
     """

-  "Bootstrap3 Form - Inline Radio input":
+  "Inline Radio input":
     "prefix":"bootstrap3:radioinline"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Radio button inline'
@@ -108,7 +108,7 @@
       $8
     """

-  "Bootstrap3 Form - Select input":
+  "Select input":
     "prefix":"bootstrap3:select"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Select input'
@@ -119,7 +119,7 @@
       $5
     """

-  "Bootstrap3 Form - Fieldset input":
+  "Fieldset":
     "prefix":"bootstrap3:fieldset"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Fieldset'
@@ -130,7 +130,7 @@
       $4
     """

-  "Bootstrap3 Form - Button":
+  "Button":
     "prefix":"bootstrap3:button"
     'leftLabelHTML': 'Ⓢ'
     'rightLabelHTML': 'Bootstrap 3 Button'


fixed footnotetext

fixed footnotetext

--- snippets/language-latex2e.cson
+++ snippets/language-latex2e.cson
@@ -69,7 +69,7 @@
     'body': '\\\\footnotemark'
   '\\footnotetext{}':
     'prefix': '\\footnotetext'
-    'body': '\\\\footnotemtext{$1}$0'
+    'body': '\\\\footnotetext{$1}$0'
   '\\mintinline{}':
     'prefix': '\\mintinline'
     'body': '\\\\mintinline{$1}$0'


Improving howdoi documentation.

Improving howdoi documentation.

--- src/scripts/howdoi.coffee
+++ src/scripts/howdoi.coffee
@@ -9,7 +9,7 @@
 #   None
 #
 # Commands:
-#   howdoi
+#   hubot howdoi <do thing> - Searches Stack Overflow for a way of doing thing.
 #
 # Author:
 #   pettazz


Bugfix in parsing viewBox (needed parseFloat)

--- svgtiler.coffee
+++ svgtiler.coffee
@@ -28,6 +28,7 @@
   ##   - text
   if xml.documentElement.hasAttribute 'viewBox'
     xml.documentElement.getAttribute('viewBox').split /\s+/
+    .map parseFloat
   else
     recurse = (node) ->
       if node.nodeType != node.ELEMENT_NODE or


hub/api: attempt to fix #3916 (not tested)

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -1975,6 +1975,7 @@
                 @database.get_stripe_customer_id
                     account_id : @account_id
                     cb         : (err, customer_id) =>
+                        # note: customer_id could be undefined
                         @stripe_customer_id = customer_id  # cache for later
                         for x in @_stripe_customer_id_cbs
                             {id, cb} = x
@@ -2007,7 +2008,7 @@
                 cb(err); return
             cb(undefined, customer_id)

-    # id : user's CoCalc account id
+    # id : message.id
     stripe_get_customer: (id, cb) =>
         dbg = @dbg("stripe_get_customer")
         dbg("getting id")
@@ -2626,13 +2627,8 @@
         locals = {}
         async.series([
             (cb) =>
-                dbg("get stripe id")
-                @stripe_get_customer_id @account_id, (err, id) =>
-                    locals.id = id
-                    cb(err)
-            (cb) =>
                 dbg("get stripe customer data")
-                @stripe_get_customer locals.id, (err, stripe_customer) =>
+                @stripe_get_customer mesg.id, (err, stripe_customer) =>
                     locals.stripe_data = stripe_customer?.subscriptions?.data
                     cb(err)
             (cb) =>


Removed debug line

Removed debug line

--- web-components/cs-music-player/js/script.coffee
+++ web-components/cs-music-player/js/script.coffee
@@ -184,7 +184,6 @@
 						update_cover									= (cover) ->
 							cover	= cover || 'img/bg.jpg'
 							if body.style.backgroundImage != "url(#{cover})"
-								console.log 'update cover'
 								element.shadowRoot.querySelector('cs-cover').style.backgroundImage	= "url(#{cover})"
 								# Resize cover to 128px max to speed-up blurring
 								resize_image(


Minor improvement

Minor improvement

--- cake/tasks.coffee
+++ cake/tasks.coffee
@@ -35,5 +35,5 @@
   help:
     npm:
       command:      'help:npm'
-      description:  'Help for those running commands from npm'
+      description:  'Help for those running commands from npm\n'
       task:         -> do Help.npm


Fix modal.coffee transition bug

Fix modal.coffee transition bug

--- vendor/assets/javascripts/mice/modal.coffee
+++ vendor/assets/javascripts/mice/modal.coffee
@@ -64,7 +64,7 @@

       if transition
         @$element.find('.modal-dialog') # wait for modal to slide in
-          .one 'bsTransitionEnd', -> @$element.trigger('focus').trigger(e)
+          .one 'miceTransitionEnd', -> @$element.trigger('focus').trigger(e)
           .emulateTransitionEnd(300)
       else
         @$element.trigger('focus').trigger(e)
@@ -95,7 +95,7 @@

     if $.support.transition && @$element.hasClass('fade')
       @$element
-        .one('bsTransitionEnd', $.proxy(@hideModal, @))
+        .one('miceTransitionEnd', $.proxy(@hideModal, @))
         .emulateTransitionEnd(300)
     else
       @hideModal()
@@ -154,7 +154,7 @@
       return unless callback

       if doAnimate
-        @$backdrop.one('bsTransitionEnd', callback).emulateTransitionEnd(150)
+        @$backdrop.one('miceTransitionEnd', callback).emulateTransitionEnd(150)
       else
         callback()

@@ -167,7 +167,7 @@

       if $.support.transition && @$element.hasClass('fade')
         @$backdrop
-          .one('bsTransitionEnd', callbackRemove)
+          .one('miceTransitionEnd', callbackRemove)
           .emulateTransitionEnd(150)
       else
         callbackRemove()


add contributor

add contributor

--- views/components/settings/parts/others.cjsx
+++ views/components/settings/parts/others.cjsx
@@ -27,6 +27,7 @@

 马里酱

 吴钩霜雪明

 Rui

+
 Artoria

         </Col>
       </Grid>


add ellipsis in artist name

add ellipsis in artist name

--- resources/assets/coffee/react/_components/beatmapset-panel.coffee
+++ resources/assets/coffee/react/_components/beatmapset-panel.coffee
@@ -82,7 +82,7 @@
           div className: 'beatmapset-panel__title-artist-box',
             div className: 'u-ellipsis-overflow beatmapset-panel__header-text beatmapset-panel__header-text--title',
               BeatmapHelper.getTitle(beatmapset)
-            div className: 'beatmapset-panel__header-text',
+            div className: 'u-ellipsis-overflow beatmapset-panel__header-text',
               BeatmapHelper.getArtist(beatmapset)

           div className: 'beatmapset-panel__counts-box',


add a box for a view

add a box for a view


capture user_id before making call.

capture user_id before making call.

props can be unset when callback triggers.

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -258,12 +258,13 @@

     @xhr?.abort()

+    user = @props.beatmapset.user_id
     url = laroute.route('beatmapsets.destroy', beatmapset: @props.beatmapset.id)
     params = method: 'DELETE'

     @xhr = $.ajax(url, params)
       .done ->
-        Turbolinks.visit laroute.route('users.show', user: @props.beatmapset.user_id)
+        Turbolinks.visit laroute.route('users.show', { user })
       .fail osu.ajaxError
       .always LoadingOverlay.hide


button rearranging

button rearranging

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -45,16 +45,6 @@
         onClick: @nominate

-  lovedButton: (disabled = false) =>
-    el BigButton,
-      modifiers: ['full']
-      text: osu.trans 'beatmaps.nominations.love'
-      icon: 'fas fa-heart'
-      props:
-        disabled: disabled
-        onClick: @love
-
-
   render: =>
     showHype = @props.beatmapset.can_be_hyped

@@ -64,6 +54,7 @@
       hype = _.min([requiredHype, hypeRaw])
       userAlreadyHyped = _.find(@props.currentDiscussions.byFilter.hype.generalAll, user_id: @props.currentUser.id)?

+    mapCanBeLoved = _.includes(['pending', 'wip', 'graveyard'],  @props.beatmapset.status)
     mapCanBeNominated = @props.beatmapset.status == 'pending' && hypeRaw >= requiredHype
     mapIsQualified = (@props.beatmapset.status == 'qualified')

@@ -177,11 +168,18 @@
                         else
                           @nominationButton @props.beatmapset.nominations.nominated

-              div
-                className: "#{bn}__row"
-                div className: "#{bn}__row-left"
-                div className: "#{bn}__row-right",
-                  @lovedButton false
+          if mapCanBeLoved
+            div
+              className: "#{bn}__row"
+              div className: "#{bn}__row-left"
+              div className: "#{bn}__row-right",
+                el BigButton,
+                  modifiers: ['full']
+                  text: osu.trans 'beatmaps.nominations.love'
+                  icon: 'fas fa-heart'
+                  props:
+                    onClick: @love
+

           div
             className: "#{bn}__footer #{if mapCanBeNominated then "#{bn}__footer--extended" else ''}",


Don't blow up when replies can't be fetched for conversation

--- lib/assets/javascripts/views/post/conversation/children.js.coffee
+++ lib/assets/javascripts/views/post/conversation/children.js.coffee
@@ -18,7 +18,7 @@

   fetchPosts: (options = {}) =>
     reference_post = @post()
-    reference_post.fetchReplies(_.extend(
+    reference_post.fetchReplies?(_.extend(
       success: (posts) =>
         @render(posts)
     , options))


disable auto insert of brackets and quotes in ace

--- services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -58,6 +58,11 @@

 				editor = ace.edit(element.find(".ace-editor-body")[0])
 				editor.$blockScrolling = Infinity
+
+				# disable auto insertion of brackets and quotes
+				editor.setOption('behavioursEnabled', false)
+				editor.setOption('wrapBehavioursEnabled', false)
+
 				window.editors ||= []
 				window.editors.push editor


Refactoring: Use registerFollowVariant in for [up-follow] and [up-target]; up.link.makeFollowable now considers [up-popup] and [up-modal] as followable

--- lib/assets/javascripts/up/link.js.coffee
+++ lib/assets/javascripts/up/link.js.coffee
@@ -191,6 +191,64 @@
     u.option(options.method, $link.attr('up-method'), $link.attr('data-method'), 'get').toUpperCase()

   ###*
+  @function up.link.childClicked
+  @internal
+  ###
+  childClicked = (event, $link) ->
+    $target = $(event.target)
+    $targetLink = $target.closest('a, [up-href]')
+    $targetLink.length && $link.find($targetLink).length
+
+  shouldProcessLinkEvent = (event, $link) ->
+    u.isUnmodifiedMouseEvent(event) && !childClicked(event, $link)
+
+  followVariantSelectors = []
+
+  ###*
+  No-op that is called when we allow a browser's default action to go through,
+  so we can spy on it in unit tests. See `link_spec.js`.
+
+  @function allowDefault
+  @internal
+  ###
+  allowDefault = (event) ->
+
+  registerFollowVariant = (selector, handler) ->
+    followVariantSelectors.push(selector)
+    up.on 'click', "a#{selector}, [up-href]#{selector}", (event, $link) ->
+      if shouldProcessLinkEvent(event, $link)
+        if $link.is('[up-instant]')
+          # If the link was already processed on mousedown, we still need
+          # to prevent the later click event's default behavior.
+          event.preventDefault()
+        else
+          event.preventDefault()
+          handler($link)
+      else
+        allowDefault(event)
+
+    up.on 'mousedown', "a#{selector}[up-instant], [up-href]#{selector}[up-instant]", (event, $link) ->
+      if shouldProcessLinkEvent(event, $link)
+        event.preventDefault()
+        handler($link)
+
+  isFollowable = ($link) ->
+    u.any followVariantSelectors, (selector) -> $link.is(selector)
+
+  ###*
+  Makes sure that the given link is handled by Up.js.
+
+  This is done by giving the link an `up-follow` attribute
+  unless it already have it an `up-target` or `up-follow` attribute.
+
+  @function up.link.makeFollowable
+  @internal
+  ###
+  makeFollowable = (link) ->
+    $link = $(link)
+    $link.attr('up-follow', '') unless isFollowable($link)
+
+  ###*
   Follows this link via AJAX and replaces a CSS selector in the current page
   with corresponding elements from a new page fetched from the server:

@@ -256,15 +314,8 @@
     Set this to `'false'` to prevent the current URL from being updated.
   @stable
   ###
-  up.on 'click', 'a[up-target], [up-href][up-target]', (event, $link) ->
-    if shouldProcessLinkEvent(event, $link)
-      if $link.is('[up-instant]')
-        # If the link was already processed on mousedown, we still need
-        # to prevent the later click event's default behavior.
-        event.preventDefault()
-      else
-        event.preventDefault()
-        follow($link)
+  registerFollowVariant '[up-target]', ($link) ->
+    follow($link)

   ###*
   By adding an `up-instant` attribute to a link, the destination will be
@@ -285,45 +336,6 @@
   @selector a[up-instant]
   @stable
   ###
-  up.on 'mousedown', 'a[up-instant], [up-href][up-instant]', (event, $link) ->
-    if shouldProcessLinkEvent(event, $link)
-      event.preventDefault()
-      follow($link)
-
-  ###*
-  @function up.link.childClicked
-  @internal
-  ###
-  childClicked = (event, $link) ->
-    $target = $(event.target)
-    $targetLink = $target.closest('a, [up-href]')
-    $targetLink.length && $link.find($targetLink).length
-
-  shouldProcessLinkEvent = (event, $link) ->
-    u.isUnmodifiedMouseEvent(event) && !childClicked(event, $link)
-
-  ###*
-  Makes sure that the given link is handled by Up.js.
-
-  This is done by giving the link an `up-follow` attribute
-  unless it already have it an `up-target` or `up-follow` attribute.
-
-  @function up.link.makeFollowable
-  @internal
-  ###
-  makeFollowable = (link) ->
-    $link = $(link)
-    if u.isMissing($link.attr('up-target')) && u.isMissing($link.attr('up-follow'))
-      $link.attr('up-follow', '')
-
-  ###*
-  No-op that is called when we allow a browser's default action to go through,
-  so we can spy on it in unit tests. See `link_spec.js`.
-
-  @function allowDefault
-  @internal
-  ###
-  allowDefault = (event) ->

   ###*
   If applied on a link, Follows this link via AJAX and replaces the
@@ -357,17 +369,8 @@
     within the response.
   @stable
   ###
-  up.on 'click', 'a[up-follow], [up-href][up-follow]', (event, $link) ->
-    if shouldProcessLinkEvent(event, $link)
-      if $link.is('[up-instant]')
-        # If the link was already processed on mousedown, we still need
-        # to prevent the later click event's default behavior.
-        event.preventDefault()
-      else
-        event.preventDefault()
-        follow($link)
-    else
-      allowDefault(event)
+  registerFollowVariant '[up-follow]', ($link) ->
+    follow($link)

   ###*
   Add an `up-expand` class to any element that contains a link
@@ -437,23 +440,6 @@
     u.setMissingAttrs($element, newAttrs)
     $element.removeAttr('up-dash')

-  registerFollowVariant = (selector, handler) ->
-    up.on 'click', "a#{selector}, [up-href]#{selector}", (event, $link) ->
-      if shouldProcessLinkEvent(event, $link)
-        if $link.is('[up-instant]')
-        # If the link was already processed on mousedown, we still need
-        # to prevent the later click event's default behavior.
-          event.preventDefault()
-        else
-          event.preventDefault()
-          handler($link)
-      else
-        allowDefault(event)
-
-    up.on 'mousedown', "a#{selector}[up-instant], [up-href]#{selector}[up-instant]", (event, $link) ->
-      if shouldProcessLinkEvent(event, $link)
-        event.preventDefault()
-        handler($link)

   knife: eval(Knife?.point)
   visit: visit


JNewApp: use removeJNames as internal method

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -415,7 +415,7 @@
     success: (client, callback)->
       @remove callback

-      removeJNames: (names)->
+      removeJNames = (names)->
         names.forEach (name)->
           JName.one {name}, (err, jname)->
             return console.error "Failed to get JName: ", err  if err


handle click, update featured property in view

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -14,6 +14,12 @@
   render: ->
     @$el.html @template @model.attributes
     @
+  events:
+    'click .unfeature': 'unfeature'
+  unfeature: ->
+    @model.save(featured: false)
+    @remove()
+

 App.AlbumsListView = Backbone.View.extend
   el: '#featured'


more work on \$ issue

more work on \$ issue

--- salvus/page/misc_page.coffee
+++ salvus/page/misc_page.coffee
@@ -1286,6 +1286,8 @@
     if has_mathjax
         for i in [0...w.length]
             s = s.replace("@@@@#{i}@@@@", misc.mathjax_escape(w[i].replace(/\$/g, "$$$$")))
+    else if '\$' in s
+        has_mathjax = true # still need to parse it to turn \$'s to $'s.

     return {s:s, has_mathjax:has_mathjax}


minimal error handling to sbawn if spawn errors out. displays error instead of ENOENT

--- src/steroids/sbawn.coffee
+++ src/steroids/sbawn.coffee
@@ -64,6 +64,8 @@
       @spawned.stdout.on "data", @onStdoutData
       @spawned.stderr.on "data", @onStderrData
     @spawned.on "exit", @onExit
+    @spawned.on "error", (err)=>
+      console.log err

   kill: () =>
     @spawned.kill("SIGKILL")


Call Editor.insertText directly instead of creating a TextInput event

--- benchmark/benchmark-suite.coffee
+++ benchmark/benchmark-suite.coffee
@@ -13,6 +13,6 @@
     window.shutdown()

   benchmark "inserting and deleting a character", ->
-    editor.hiddenInput.textInput('x')
+    editor.insertText('x')
     editor.backspace()


Hotfix

Hotfix

--- lib/controllers/contest.coffee
+++ lib/controllers/contest.coffee
@@ -190,7 +190,7 @@
                                             for taskProgress in taskProgressEntries
                                                 task = _.findWhere tasks, _id: taskProgress.taskId
                                                 if task? and not _.contains countedTaskIds, taskProgress.taskId
-                                                    countedTaskIds.push task.id
+                                                    countedTaskIds.push taskProgress.taskId
                                                     totalScore += task.value
                                                     if lastUpdatedAt?
                                                         if lastUpdatedAt.getTime() < taskProgress.createdAt.getTime()


devicecommunication: Output the error message

--- lib/devicecommunication.coffee
+++ lib/devicecommunication.coffee
@@ -230,7 +230,7 @@
         catch err
             out = 'ERROR'
             console.log 'MICROFLO RECV ERROR:', buf.length, buf, out if debug_comms
-            @_handleCommandReceived out
+            @_handleCommandReceived out, err.message

     _handleCommandReceived: (type) ->
         @sender.onResponse type


Don't iterate over every line when reseting

[#19]

--- lib/highlight-line-view.coffee
+++ lib/highlight-line-view.coffee
@@ -111,15 +111,17 @@
     @showHighlight()

   resetBackground: ->
-    $('.line').css('background-color','')
-              .css('border-top','')
-              .css('border-bottom','')
-              .css('margin-bottom','')
-              .css('margin-top','')
-    $('.line[data-screen-row]').each (index, line) ->
+    $('.highlight-line-pkg').css('background-color','')
+                            .css('border-top','')
+                            .css('border-bottom','')
+                            .css('margin-bottom','')
+                            .css('margin-top','')
+    $('.highlight-line-pkg[data-screen-row]').each (index, line) ->
       if $(line).attr('style')?.indexOf('background') isnt -1
         top = $(line).css('top')
-        $(line).attr 'style', "position: absolute; top: #{top};" if top?
+        $(line).attr('style', "position: absolute; top: #{top}; " +
+          "width: 100%;") if top?
+    $('.highlight-line-pkg').removeClass('highlight-line-pkg')

   makeLineStyleAttr: ->
     styleAttr = ''
@@ -174,6 +176,7 @@
       styleAttr += "position: absolute; top: #{topPX}; width: 100%;" if topPX?

     $(lineElement).attr 'style', styleAttr
+    $(lineElement).addClass('highlight-line-pkg')

   handleMultiLine: (styleAttr) =>
     selectionStyleAttrs = @makeSelectionStyleAttr()
@@ -195,6 +198,9 @@
       $(startLine).attr 'style', selectionStyleAttrs[0]
       $(endLine).attr 'style', selectionStyleAttrs[1]

+      $(startLine).addClass('highlight-line-pkg')
+      $(endLine).addClass('highlight-line-pkg')
+
   findLineElementForRow: (editorView, row) ->
     if editorView.lineElementForScreenRow?
       editorView.lineElementForScreenRow(row)


or= assignment

or= assignment


undated namespace in global

undated namespace in global

--- lib/generators/rails_script/install/templates/global.js.coffee
+++ lib/generators/rails_script/install/templates/global.js.coffee
@@ -1 +1 @@
-window.App ||= {}+window.<%= RailsScript.app_namespace %> ||= {}


Remove unused classes

Remove unused classes

--- lib/package-keymap-view.coffee
+++ lib/package-keymap-view.coffee
@@ -4,8 +4,8 @@
 module.exports =
 class PackageKeymapView extends View
   @content: ->
-    @section class: 'package-keymap', =>
-      @div class: 'section-heading package-keymap-heading icon icon-keyboard', 'Keybindings'
+    @section =>
+      @div class: 'section-heading icon icon-keyboard', 'Keybindings'
       @table outlet: 'keymapTable', class: 'package-keymap-table table native-key-bindings text', tabindex: -1, =>
         @thead =>
           @tr =>


Ensure that comment notifications pickup all comment styles.  Closes zooniverse/Talk#155

--- app/pages/notifications/comment.cjsx
+++ app/pages/notifications/comment.cjsx
@@ -20,10 +20,12 @@

   render: ->
     if @state.comment
-     <Comment
-       data={@state.comment}
-       user={@props.user}
-       project={@props.project} />
+

+        <Comment
+          data={@state.comment}
+          user={@props.user}
+          project={@props.project} />
+

     else

         <Loading />


forward force option

forward force option

--- lib/runtime/completions.coffee
+++ lib/runtime/completions.coffee
@@ -11,11 +11,12 @@
   filterSuggestions: true
   excludeLowerPriority: false

-  rawCompletions: ({editor, bufferPosition: {row, column}}) ->
+  rawCompletions: ({editor, bufferPosition: {row, column}, activatedManually}) ->
     completions
       mod: modules.current()
       line: editor.getTextInBufferRange [[row, 0], [row, Infinity]]
       column: column+1
+      force: activatedManually || false

   toCompletion: (c, pre) ->
     if c.constructor is String


Login before showing add-friends dialog

Login before showing add-friends dialog

--- public/scripts/backbone-schedule/views/scheduler/friends-list.coffee
+++ public/scripts/backbone-schedule/views/scheduler/friends-list.coffee
@@ -50,8 +50,9 @@
         @$list.append friend.listView.el if @$list

-    findAndAddFriends: (e) ->
-      e.stopPropagation()
+    findAndAddFriends: (e) =>
+      return Shark.session.login(@findAndAddFriends) if not Shark.session.authenticated()
+      e?.stopPropagation()
       @friendPicker?.teardown()
       @friendPicker = new FriendsFromSiteView target: @$el.find '#find-and-add-friends'


getting ks from url params for non-html5 mode

--- app/scripts/common/kmc-config.coffee
+++ app/scripts/common/kmc-config.coffee
@@ -5,7 +5,7 @@
 	module.provider 'kmc', ->
 		$get: ->
 			ks = 'ZWQ5MWRkNmU3Zjc2YzA5Y2I5ZDVjNTdkNzhkY2E4YjIxY2JmOTkyN3w5MzkzNDE7OTM5MzQxOzE0NDYwNDQwNzI7MjsxNDQ1OTU3NjcyLjU1Njtya3NoYXJlZGJveEBnbWFpbC5jb207ZGlzYWJsZWVudGl0bGVtZW50Ozs='
-			for param in location.search?.split('?')[1]?.split('&') or []
+			for param in (location.search or location.hash)?.split('?')[1]?.split('&') or []
 				parts = param.split '='
 				if parts[0] is 'ks'
 					ks = parts[1]


Initial implementation of binding element properties to node property values.

Conflicts:
	app/models/node_shape.coffee

--- app/scripts/models/node_shape.coffee
+++ app/scripts/models/node_shape.coffee
@@ -9,22 +9,25 @@
       @elements = elements

     draw: (node) ->
-      children = (@createElement(e, node.position) for e in @elements)
+      children = (@createElement(e, node) for e in @elements)
       new paper.Group(children)

-    createElement: (e, position) =>
+    createElement: (e, node) =>
       e.x or= 0
       e.y or= 0
-      path = @createPath(e)
-      path.position = new paper.Point(position).add(e.x, e.y)
-      path.fillColor = e.fillColor
-      path.strokeColor = e.borderColor
+      path = @createPath(e, node)
+      path.position = new paper.Point(node.position).add(@getOption(e.x, node, 0), @getOption(e.y, node, 0))
+      path.fillColor = @getOption(e.fillColor, node, "white")
+      path.strokeColor = @getOption(e.borderColor, node, "black")
       path

-    createPath: (options) =>
+    createPath: (options, node) =>
+      width = @getOption(options.size.width, node, 100)
+      height = @getOption(options.size.height, node, 100)
+
       switch options.figure
         when "rounded"
-          rect = new paper.Rectangle(0, 0, options.size.width, options.size.height)
+          rect = new paper.Rectangle(0, 0, width, height)
           new paper.Path.RoundRectangle(rect, new paper.Size(10, 10))
         when "ellipse"
           rect = new paper.Rectangle(0, 0, options.size.width*2, options.size.height*2)
@@ -40,6 +43,29 @@
           for point in options.points
             path.add(new paper.Point(point.x, point.y))
           path
+
+    getOption: (content, node, defaultValue) =>
+      if (typeof content is 'string' or content instanceof String) and content.length and content[0] is "$"
+        # What happens if there is a problem with the expression
+        # for example ${unknown} where unknown is not a property
+        # that is defined for this shape
+
+        # strip off opening the ${ and the closing }
+        evalable = content.substring(2, content.length - 1)
+        content = node.getPropertyValue(evalable)
+
+        # What happens if this fails?
+        if content is ""
+          defaultValue
+        else
+          # Eventually, we probably want to store type information
+          # for parameter values so that we can perform a more
+          # knowledgable conversion, rather than trial-and-error
+          value = parseInt(content,10)
+          value = content if isNaN(value)
+          value
+      else
+        content

   class NodeShape extends Spine.Model


msgflo-newrelic-jobstats: Support --since option

Before was hardcoded at 1 week. This is still the default

--- src/utils/newrelic.coffee
+++ src/utils/newrelic.coffee
@@ -7,7 +7,7 @@
   insights = new Insights options
   return callback new Error 'Missing app name' if not app

-  q = "SELECT average(duration), stddev(duration), percentile(duration, 90) FROM MsgfloJobCompleted FACET role SINCE 1 week ago WHERE outport != 'error' AND appName = '#{app}'"
+  q = "SELECT average(duration), stddev(duration), percentile(duration, 90) FROM MsgfloJobCompleted FACET role SINCE #{options.since} ago WHERE outport != 'error' AND appName = '#{app}'"
   insights.query q, (err, body) ->
     return callback err if err

@@ -43,6 +43,7 @@
     .option('--query-key <hostname>', 'Query Key to access New Relic Insights API', String, '')
     .option('--account-id <port>', 'Account ID used to access New Relic Insights API', String, '')
     .option('--app <app>', 'App name in New Relic. Can be specified multiple times', addApp, [])
+    .option('--since <datetime>', 'Timeperiod to extract metrics for', String, '1 week')
     .parse(args)

 normalize = (options) ->


Improve readability

Improve readability

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -29,7 +29,6 @@

   disqualify: ->
     reason = prompt('Reason for disqualification?')
-
     return unless reason

     @doAjax 'disqualify', reason
@@ -61,53 +60,55 @@
   render: ->
     @reboot()

-    if @disqualificationEvent
-      disqualificationNotice = div className: "#{bdn}__disqualification-notice",
-        span null,
-          "#{@disqualificationEvent.reason}"
-        span className: "#{bdn}__disqualification-time", dangerouslySetInnerHTML:
-          __html: osu.trans 'beatmaps.nominations.disqualifed-at', time_ago: osu.timeago(@disqualificationEvent.created_at)
+    disqualificationBanner =
+      if @disqualification
+        div className: 'osu-layout__row osu-layout__row--sm1 osu-layout__row--page-compact',
+          div className: "#{bdn}__disqualification-notice",
+            span null,
+              "#{@disqualification.reason}"
+            span className: "#{bdn}__disqualification-time", dangerouslySetInnerHTML:
+              __html: osu.trans 'beatmaps.nominations.disqualifed-at', time_ago: osu.timeago(@disqualification.created_at)

-    nominationsRequiredText =
-      if @mapCanBeNominated
-        osu.trans 'beatmaps.nominations.required-text',
-          current: @currentNominations,
-          required: @nominationsRequired
-      else if @mapIsQualified
-        if @rankingETA
-          date = osu.timeago(@rankingETA)
-        else
-          date = osu.trans 'beatmaps.nominations.soon'
+    nominationBanner =
+      if @mapCanBeNominated or @mapIsQualified
+        div className: bdn,
+          disqualificationBanner

-        span dangerouslySetInnerHTML:
-          __html: osu.trans 'beatmaps.nominations.qualified', date: date
+          div className: 'osu-layout__row osu-layout__row--sm1 osu-layout__row--page-compact',
+            div className: "#{bdn}__count-area#{(if @mapIsQualified then '-qualified' else '')}",
+              span className: "#{bdn}__count-text",
+                if @mapCanBeNominated
+                  osu.trans 'beatmaps.nominations.required-text',
+                    current: @currentNominations,
+                    required: @nominationsRequired
+                else if @mapIsQualified
+                  if @rankingETA
+                    date = osu.timeago(@rankingETA)
+                  else
+                    date = osu.trans 'beatmaps.nominations.soon'

-    div className: bdn,
-      div className: 'osu-layout__row osu-layout__row--sm1 osu-layout__row--page-compact',
-        disqualificationNotice
+                  span dangerouslySetInnerHTML:
+                    __html: osu.trans 'beatmaps.nominations.qualified', date: date

-      div className: 'osu-layout__row osu-layout__row--sm1 osu-layout__row--page-compact',
-        div className: "#{bdn}__count-area#{(if @mapIsQualified then '-qualified' else '')}",
-          span className: "#{bdn}__count-text",
-            nominationsRequiredText
+              if @userCanPerformNominations
+                span className: "#{bdn}__button-area",
+                  if @mapIsQualified
+                    button
+                      className: "btn-osu-lite btn-osu-lite--pink"
+                      onClick: => $.publish 'beatmapset:disqualify', all: 'disqualify'
+                      el Icon, name: 'thumbs-down'
+                      span className: "#{bd}s__collapse-button-text",
+                        osu.trans 'beatmaps.nominations.disqualify'
+                  else if @mapCanBeNominated
+                    button
+                      className: "btn-osu-lite btn-osu-lite--green"
+                      disabled: @alreadyNominated
+                      onClick: => $.publish 'beatmapset:nominate', all: 'nominate'
+                      el Icon, name: 'thumbs-up'
+                      span className: "#{bd}s__collapse-button-text",
+                        osu.trans 'beatmaps.nominations.nominate'

-          if @userCanPerformNominations
-            span className: "#{bdn}__button-area",
-              if @mapIsQualified
-                button
-                  className: "btn-osu-lite btn-osu-lite--pink"
-                  onClick: => $.publish 'beatmapset:disqualify', all: 'disqualify'
-                  el Icon, name: 'thumbs-down'
-                  span className: "#{bd}s__collapse-button-text",
-                    osu.trans 'beatmaps.nominations.disqualify'
-              else if @mapCanBeNominated
-                button
-                  className: "btn-osu-lite btn-osu-lite--green"
-                  disabled: @alreadyNominated
-                  onClick: => $.publish 'beatmapset:nominate', all: 'nominate'
-                  el Icon, name: 'thumbs-up'
-                  span className: "#{bd}s__collapse-button-text",
-                    osu.trans 'beatmaps.nominations.nominate'
+    return nominationBanner or null

   reboot: ->
     osu.pageChange()
@@ -122,6 +123,6 @@
       nominations = @props.beatmapset.nominations.data
       @nominationsRequired = nominations.required
       @currentNominations = nominations.current
-      @disqualificationEvent = nominations.disqualification
-      if @canPerformNominations
+      @disqualification = nominations.disqualification
+      if @userCanPerformNominations
         @alreadyNominated = nominations.nominated


Fix typo error -> err

Fix typo error -> err

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -949,7 +949,7 @@
         log.info 'creating new storage for application', appId, version
         newStorage = new JAppStorage {appId, version}
         newStorage.save (err) =>
-          if err then callback error
+          if err then callback err
           else
             # manually add the relationship so that we can
             # query the edge instead of the target C.T.


worker: use forcedRecipient for both username & email in dev

--- workers/social/lib/social/models/tracker.coffee
+++ workers/social/lib/social/models/tracker.coffee
@@ -37,10 +37,13 @@
     @track userId, event, eventProperties

-  @identify = (userId, traits={}) ->
-    userId = forcedRecipient or userId
+  @identify = (username, traits={}) ->
+    # use `forcedRecipient` for both username and email
+    if forcedRecipient
+      username     = forcedRecipient
+      traits.email = forcedRecipient
+
     traits = @addDefaults traits
-
     analytics.identify {userId, traits}

     # force flush so identify call doesn't sit in queue, while events
@@ -49,7 +52,11 @@

   @track = (username, mail, options={})->
-    mail.to           = forcedRecipient or mail.to
+    # use `forcedRecipient` for both username and email
+    if forcedRecipient
+      username = forcedRecipient
+      mail.to  = forcedRecipient
+
     mail.from       or= defaultFromMail
     mail.properties   = @addDefaults { options, username }


ownprofile: link to static profile should open in new window

--- client/landing/app/Applications/Members.kdapplication/ContentDisplays/staticprofilesettings.coffee
+++ client/landing/app/Applications/Members.kdapplication/ContentDisplays/staticprofilesettings.coffee
@@ -48,6 +48,6 @@
     {{> @staticPageSwitch}}

-    Visit your Public Page
+    Visit your Public Page

     """


[LoadingFree] if filter is public and activity filter is everything and if we have `activity.main` feed do not send request to backend, just populate it here

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -199,16 +199,24 @@

   fetchPublicActivities:(options = {})->
     {CStatusActivity} = KD.remote.api
-    eventSuffix = "#{@getFeedFilter()}_#{@getActivityFilter()}"
+
+    if @getFeedFilter() is "Public" and @getActivityFilter() is "Everything"
+      if activity = KD.prefetchedFeeds["activity.main"]
+        return @prepareCacheForListing activity
+
     CStatusActivity.fetchPublicActivityFeed options, (err, cache)=>
       return @emit "activitiesCouldntBeFetched", err  if err
-
-      cache.overview.reverse()  if cache.overview
-
-      @sanitizeCache cache, (err, sanitizedCache)=>
-        if err
-        then @emit "activitiesCouldntBeFetched", err
-        else @emit "publicFeedFetched_#{eventSuffix}", sanitizedCache
+      @prepareCacheForListing  cache
+
+  prepareCacheForListing: (cache)->
+    eventSuffix = "#{@getFeedFilter()}_#{@getActivityFilter()}"
+
+    cache.overview.reverse()  if cache.overview
+
+    @sanitizeCache cache, (err, sanitizedCache)=>
+      if err
+      then @emit "activitiesCouldntBeFetched", err
+      else @emit "publicFeedFetched_#{eventSuffix}", sanitizedCache

   fetchFollowingActivities:(options = {})->