voxel size 50->1, and draw RGB axis

voxel size 50->1, and draw RGB axis

--- app/assets/javascripts/static_editors.js.coffee
+++ app/assets/javascripts/static_editors.js.coffee
@@ -5,8 +5,8 @@

 #グリッド線
 MYTHREE.getGrid = ->
-  size = 500
-  step = 50
+  size = 10
+  step = 1
   geometry = new THREE.Geometry
   for i in [-size..size] by step
     geometry.vertices.push( new THREE.Vector3( - size, 0, i ) )
@@ -20,7 +20,7 @@

 #ヒット用平面
 MYTHREE.getRayHitPlane = ->
-  plane = new THREE.Mesh( new THREE.PlaneGeometry( 1000, 1000 ), new THREE.MeshBasicMaterial() );
+  plane = new THREE.Mesh( new THREE.PlaneGeometry( 20, 20 ), new THREE.MeshBasicMaterial() );
   plane.rotation.x = - Math.PI / 2;
   plane.visible = false;
   plane
@@ -71,7 +71,7 @@
   theta = 45
   isShiftDown = false
   isCtrlDown = false
-  target = new THREE.Vector3( 0, 200, 0 )
+  target = new THREE.Vector3( 0, 4, 0 )
   normalMatrix = new THREE.Matrix3()
   ROLLOVERED = null

@@ -86,8 +86,8 @@
     console.log('innerW,H', W, H)
     MYTHREE.global.W = W
     MYTHREE.global.H = H
-    camera = new THREE.PerspectiveCamera( 40, W/H, 1, 10000 )
-    camera.position.y = 800
+    camera = new THREE.PerspectiveCamera( 4, W/H, 1, 10000 )
+    camera.position.y = 400
     scene = new THREE.Scene()
     scene.add( MYTHREE.getGrid() )
     projector = new THREE.Projector()
@@ -95,6 +95,9 @@
     mouse2D = new THREE.Vector3( 0, 10000, 0.5 )
     scene.add( new THREE.AmbientLight( 0x606060 ))
     scene.add( MYTHREE.getDirectionalLight() )
+    #axis
+    axisHelper = new THREE.AxisHelper(10);
+    scene.add(axisHelper);
     renderer = new MYTHREE.getRenderer()
     container.appendChild(renderer.domElement); #<canvas>
     $('canvas').attr('id', 'canvas_id')
@@ -126,36 +129,35 @@
   onDocumentMouseDown = (event) ->
     event.preventDefault()
     intersects = raycaster.intersectObjects(scene.children)
+    console.log  'intersects.length',intersects.length
     if intersects.length > 0
-
       #レイとの交差点
       intersect = intersects[0]
       console.log "intersect.point", intersect.point
       if isCtrlDown
-
         #remove voxel from scene
         scene.remove intersect.object  unless intersect.object is plane
       else
-
         #add voxel to scene
         normalMatrix.getNormalMatrix intersect.object.matrixWorld
         normal = intersect.face.normal.clone()
         normal.applyMatrix3(normalMatrix).normalize()
         position = new THREE.Vector3().addVectors(intersect.point, normal)
-        geometry = new THREE.CubeGeometry(50, 50, 50)
+        geometry = new THREE.CubeGeometry(1,1,1)
         i = 0
-
         while i < geometry.faces.length
           geometry.faces[i].color.setHex 0x00ff80
           i++
         material = new THREE.MeshLambertMaterial(vertexColors: THREE.FaceColors)
         voxel = new THREE.Mesh(geometry, material)
-        voxel.position.x = Math.floor(position.x / 50) * 50 + 25
-        voxel.position.y = Math.floor(position.y / 50) * 50 + 25
-        voxel.position.z = Math.floor(position.z / 50) * 50 + 25
+        voxel.position.x = Math.floor(position.x / 1) * 1 + 0.5
+        voxel.position.y = Math.floor(position.y / 1) * 1 + 0.5
+        voxel.position.z = Math.floor(position.z / 1) * 1 + 0.5
+        console.log 'voxel.position', voxel.position
         voxel.matrixAutoUpdate = false
         voxel.updateMatrix()
         scene.add voxel
+
   onDocumentKeyDown = (event) ->
     switch event.keyCode
       when 16
@@ -176,8 +178,8 @@
     render()
   render = ->
     theta += mouse2D.x * 3  if isShiftDown
-    camera.position.x = 1400 * Math.sin(theta * Math.PI / 360)
-    camera.position.z = 1400 * Math.cos(theta * Math.PI / 360)
+    camera.position.x = 28 * Math.sin(theta * Math.PI / 360)
+    camera.position.z = 28 * Math.cos(theta * Math.PI / 360)
     camera.lookAt target
     raycaster = projector.pickingRay(mouse2D.clone(), camera)
     renderer.render scene, camera


Parse Facebook embeds on posts loaded with xhr

--- app/assets/javascripts/sugar/facebook.js.coffee
+++ app/assets/javascripts/sugar/facebook.js.coffee
@@ -9,7 +9,9 @@
     @appId = Sugar.Configuration.FacebookAppId
     if $(".fb_button").length > 0
       $(".fb_button").addClass("fb_button_large").wrapInner "<span class=\"fb_button_text\" />"
-    @loadAsync() if @appId
+    @loadAsync()
+    $(Sugar).bind "postsloaded", (event, posts) ->
+      Sugar.Facebook.parsePosts(posts)

   loadAsync: ->
     window.fbAsyncInit = ->
@@ -24,3 +26,8 @@
     e.src = document.location.protocol + "//connect.facebook.net/en_US/all.js"
     e.async = true
     document.getElementById("fb-root").appendChild e
+
+  parsePosts: (posts) ->
+    if @appId && FB
+      posts.each ->
+        FB.XFBML.parse(this)


Adjust Facebook login according to #3310

--- app/core/social-handlers/FacebookHandler.coffee
+++ app/core/social-handlers/FacebookHandler.coffee
@@ -15,7 +15,7 @@
 module.exports = FacebookHandler = class FacebookHandler extends CocoClass
   subscriptions:
     'auth:logged-in-with-facebook': 'onFacebookLoggedIn'
-
+
   loggedIn: false

   onFacebookLoggedIn: (e) ->
@@ -27,19 +27,19 @@
       unless me.get(userProp)
         @loggedIn = true
         break
-
+
     if @waitingForLogin and @loggedIn
       @fetchMeForLogin()
-
+
   loginThroughFacebook: ->
     if @loggedIn
       @fetchMeForLogin()
     else
       FB.login()
       @waitingForLogin = true
-
+
   fetchMeForLogin: ->
-    FB.api('/me', @onReceiveMeInfo)
+    FB.api('/me', {fields: 'email,last_name,first_name,gender'}, @onReceiveMeInfo)

   onReceiveMeInfo: (r) =>
     unless r.email


fix js error with ' in titles

fix js error with ' in titles

--- documentation/app/static/scripts/modules/documentation.coffee
+++ documentation/app/static/scripts/modules/documentation.coffee
@@ -15,6 +15,7 @@
   class Documentation extends ApiDocumentation
     groupHtml = (obj, el) ->
       id = $(obj).text().replace(/[\. ,#():-]+/g, '-').toLowerCase()
+      id = id.replace('\'','')
       $set = $()
       $set.push obj
       next = obj.nextSibling
@@ -58,6 +59,7 @@

       @regularArticle.find('h2, h3').each (i, obj) =>
         id = $(obj).text().replace(/[\. ,#():-]+/g, '-').toLowerCase()
+        id = id.replace('\'','')
         title = $(obj).text()
         parent = $(obj).parents('section').attr('id').replace('section-', '')


Activity: added leading '#'s for sidebar topic items

--- client/Social/Activity/sidebar/topicitem.coffee
+++ client/Social/Activity/sidebar/topicitem.coffee
@@ -25,7 +25,7 @@
   pistachio: ->

     """
-    {span.ttag{ #(name)}}
+    {span.ttag{ '#' + #(name)}}
     {span.tag-info{ #(participantCount) + str}}
     {{> @followButton}}
     """


Client: Activity: Added setChannelParticipantsDropdownVisibility action. It dispatches SET_CHANNEL_PARTICIPANTS_DROPDOWN_VISIBILITY event

--- client/activity/lib/flux/actions/channel.coffee
+++ client/activity/lib/flux/actions/channel.coffee
@@ -275,6 +275,15 @@
       return

     dispatch ADD_PARTICIPANTS_TO_CHANNEL_SUCCESS, options
+
+
+###*
+ * Action to set visibility of chat input channels
+###
+setChannelParticipantsDropdownVisibility = (visible) ->
+
+  { SET_CHANNEL_PARTICIPANTS_DROPDOWN_VISIBILITY } = actionTypes
+  dispatch SET_CHANNEL_PARTICIPANTS_DROPDOWN_VISIBILITY, { visible }

 module.exports = {
@@ -289,5 +298,13 @@
   loadPopularMessages
   loadPopularChannels
   loadChannelsByQuery
+  setChatInputChannelsQuery
+  unsetChatInputChannelsQuery
+  setChatInputChannelsSelectedIndex
+  moveToNextChatInputChannelsIndex
+  moveToPrevChatInputChannelsIndex
+  resetChatInputChannelsSelectedIndex
+  setChatInputChannelsVisibility
+  setChannelParticipantsDropdownVisibility
 }


video/helper: add missing docs

video/helper: add missing docs

--- client/app/lib/videocollaboration/helper.coffee
+++ client/app/lib/videocollaboration/helper.coffee
@@ -6,6 +6,14 @@
 getNick         = require 'app/util/nick'
 ProfileTextView = require 'app/commonviews/linkviews/profiletextview'

+###*
+ * Helper utility to be able to pass a fake publisher to the events. Events
+ * mostly don't care about OpenTok specific videoData, so it being `null`
+ * shouldn't affect anything, so be careful when you are passing
+ * `ParticipantType.Participant` instances around.
+ *
+ * @return {object} publisher - a fake object imitates `ParticipantType.Publisher`
+###
 defaultPublisher = ->
   nick      : getNick()
   type      : 'publisher'


tested

tested

--- client/apps/edit/components/header/index.coffee
+++ client/apps/edit/components/header/index.coffee
@@ -1,7 +1,7 @@
 _ = require 'underscore'
 Backbone = require 'backbone'
 CurrentUser = require '../../../../models/current_user.coffee'
-{ openErrorModal } = -> require('../../../../components/error_modal/index.coffee') arguments...
+{ openErrorModal } = require '../../../../components/error_modal/index.coffee'
 sd = require('sharify').data

 module.exports = class EditHeader extends Backbone.View


buckets more link WIP

buckets more link WIP

--- client/landing/app/CommonViews/LinkViews.coffee
+++ client/landing/app/CommonViews/LinkViews.coffee
@@ -84,6 +84,7 @@
       @loadFromOrigins options.group

   loadFromOrigins:(group)->
+
     three = group.slice(-3)
     bongo.cacheable three, (err, bucketContents)=>
       @setData bucketContents
@@ -91,9 +92,11 @@
       @render()

   itemClass:(options, data)->
+
     new (@getOptions().subItemClass) options, data

   createParticipantSubviews:->
+
     participants = @getData()
     for participant, index in participants
       @["participant#{index}"] = @itemClass {}, participant
@@ -101,6 +104,7 @@
     @template.update()

   pistachio:->
+
     participants = @getData()
     {hasMore, totalCount} = @getOptions()
     tmpl = switch participants.length
@@ -110,26 +114,37 @@
         sep = if @participant0.getData() instanceof bongo.api.JAccount then ', ' else ' '
         "{{> @participant0}}#{sep}{{> @participant1}}#{if hasMore then sep else ' and'} {{> @participant2}}"
     tmpl += " and #{totalCount-3} more" if hasMore
-    super tmpl
+    return tmpl

 class ActivityChildViewTagGroup extends LinkGroup
-
+
   render:->
+
     @createParticipantSubviews()
-    # super

   pistachio:->

     participants = @getData()
     {hasMore, totalCount} = @getOptions()

-    tmpl = switch participants.length
+    @more = new KDCustomHTMLView
+      tagName     : "a"
+      cssClass    : "more"
+      partial     : "#{participants.length-3} more"
+      attributes  :
+        href      : "#"
+        title     : "Click to view..."
+      click       : ->
+        new FollowedModalView {}, participants
+
+    switch participants.length
       when 0 then ""
       when 1 then "in {{> @participant0}}"
       when 2 then "in {{> @participant0}}{{> @participant1}}"
-      else "in {{> @participant0}}{{> @participant1}}{{> @participant2}}"
-    tmpl += "and #{totalCount-3} more" if hasMore
-    return tmpl
+      when 3 then "in {{> @participant0}}{{> @participant1}}{{> @participant2}}"
+      when 4 then "in {{> @participant0}}{{> @participant1}}{{> @participant2}}{{> @participant3}}"
+      else "in {{> @participant0}}{{> @participant1}}{{> @participant2}}and {{> @more}}"
+

 class FollowedModalView extends KDModalView

@@ -158,18 +173,24 @@
     options.height = "auto"

     super
+
+  # viewAppended:->
+  #
+  #   @putList()

   putList:->

     participants = @getData()
-    log participants
-
-    controller = new listControllerMap()[@type]
-      subItemClass : listItemMap()[@type]
-    , items : participants
-
-    # log controller.getView()
-    # @addSubView controller.getView(), ".kdmodal-content"
+
+    # controller = new listControllerMap()[@type]
+    # subclass of KDListViewController throws an error !!!
+    controller = new KDListViewController
+      view            : new KDListView
+        subItemClass  : listItemMap()[@type]
+    ,
+      items           : participants
+
+    @addSubView controller.getView(), ".kdmodal-content"

 class AvatarView extends LinkView
   constructor:(options,data)->


Tests: Added time for vm modal

Tests: Added time for vm modal

--- client/test/lib/helpers/waitforvmrunning.coffee
+++ client/test/lib/helpers/waitforvmrunning.coffee
@@ -22,6 +22,7 @@
             console.log ' ✔ VM is building, waiting to finish'
             browser
               .waitForElementNotPresent  modalSelector, 400000
+              .pause                     5000 # wait for sidebar redraw
               .waitForElementVisible     vmSelector, 20000
               .pause 10000

@@ -32,5 +33,6 @@
               .waitForElementVisible     turnOnButtonSelector, 100000
               .click                     turnOnButtonSelector
               .waitForElementNotPresent  modalSelector, 400000
+              .pause                     5000 # wait for sidebar redraw
               .waitForElementVisible     vmSelector, 20000
               .pause 10000


Fix non-working button after converted from link

--- resources/assets/coffee/react/contest-entry/user-entry.coffee
+++ resources/assets/coffee/react/contest-entry/user-entry.coffee
@@ -6,8 +6,8 @@
 el = React.createElement

 export class UserEntry extends React.Component
-  delete: (e) =>
-    e.preventDefault()
+  delete: =>
+    return unless confirm(osu.trans('common.confirmation'))

     params =
       method: 'DELETE'
@@ -26,7 +26,6 @@
         button
           className: 'contest-userentry__delete'
           type: 'button'
-          'data-confirm': osu.trans('common.confirmation')
           title: osu.trans('common.buttons.delete')
           onClick: @delete
           i className: 'fas fa-times'


fix coffeescript equal sign shits

fix coffeescript equal sign shits

--- vendor/assets/javascripts/ajaxify_rails.js.coffee
+++ vendor/assets/javascripts/ajaxify_rails.js.coffee
@@ -1,4 +1,3 @@
-
 active = true
 content_container = 'main'
 base_paths = null
@@ -52,7 +51,7 @@
     # has `no_ajaxify` class.
     form.addClass('no_ajaxify') if link.hasClass('no_ajaxify')

-    if csrf_param !== undefined && csrf_token !== undefined
+    if csrf_param != undefined && csrf_token != undefined
       metadata_input += '<input name="' +
         csrf_param + '" value="' +
         csrf_token + '" type="hidden" />'
@@ -351,4 +350,4 @@
 # --------------------------------------------------------------------------------------------------------------------

 jQuery ->
-  Ajaxify.ajaxify()+  Ajaxify.ajaxify()


update the palette tests to explicitly test for undefined (related to #249 and #219)

--- modules/palette/test/spec.coffee
+++ modules/palette/test/spec.coffee
@@ -13,11 +13,16 @@
         it context, ->
           selection = hx.detached('div')
           hx.palette[type](selection, context).should.equal(selection)
-          selection.classed("#{prefix}-#{context}").should.equal(true)
-          hx.palette[type](selection.node()).should.equal(context)
+          if (context)
+            selection.classed("#{prefix}-#{context}").should.equal(true)
+            hx.palette[type](selection.node()).should.equal(context)
+          else
+            should.not.exist(hx.palette[type](selection.node()))
           hx.consoleWarning.should.not.have.been.called()

       testContexts.forEach testContext
+
+      testContext(undefined)

       it 'should return undefined if there is no context', ->
         should.not.exist(hx.palette.context(hx.detached('div')))


Social: add groupChannelId

Social: add groupChannelId

--- workers/social/lib/social/models/socialapi/helper.coffee
+++ workers/social/lib/social/models/socialapi/helper.coffee
@@ -52,9 +52,10 @@
     delegate.createSocialApiId (err, socialApiId)->
       return callback err if err

-      options.groupName = group.slug
-      options.accountId = socialApiId
-      options.showExempt = delegate.isExempt
+      options.groupChannelId = group.socialApiChannelId
+      options.groupName      = group.slug
+      options.accountId      = socialApiId
+      options.showExempt     = delegate.isExempt

       requests = require './requests'
       requests[funcName] options, callback


Make it possible to pass S3 service to Uploader.

It is useful to change various S3 service parameters like maxRetries
etc.

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3/ManagedUpload.html

--- src/uploader.coffee
+++ src/uploader.coffee
@@ -4,7 +4,7 @@

 class Uploader extends EventEmitter
   # Constructor
-  constructor: ({accessKey, secretKey, sessionToken, region, stream, objectName, objectParams, bucket, partSize, maxBufferSize, waitForPartAttempts, waitTime, debug}, @cb) ->
+  constructor: ({accessKey, secretKey, sessionToken, region, stream, objectName, objectParams, bucket, partSize, maxBufferSize, waitForPartAttempts, waitTime, service, debug}, @cb) ->
     super()
     aws.config.update
       accessKeyId:     accessKey
@@ -22,7 +22,7 @@

     throw new Error "Bucket must be given" unless @objectParams.Bucket

-    @upload = new aws.S3.ManagedUpload { partSize: 10 * 1024 * 1024, queueSize: 1, params: @objectParams }
+    @upload = new aws.S3.ManagedUpload { partSize: 10 * 1024 * 1024, queueSize: 1, service: service, params: @objectParams }
     @upload.minPartSize = 1024 * 1024 * 5
     @upload.queueSize   = 4
     # Progress event


Hotfix modal open in non-signed in state

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -55,8 +55,9 @@
   if history && backbone.History.started
     history.loadUrl url
   else
-    showFrame.onload = window.onModalReady
     showFrame.src = url
+
+showFrame.onload = -> window.onModalReady()

 # initialize the page, so we are ready to render new pages fast
 showUrl '/client/blank', -> window.remote.trigger 'intermediateFrameReady'


Enable restore from visit.

Enable restore from visit.

--- test/e2e/helpers/findable.coffee
+++ test/e2e/helpers/findable.coffee
@@ -156,7 +156,7 @@
     browser.getCurrentUrl().then (url) =>
       url
       # Click the button and resolve to the home page.
-      #@click().then -> restore(url)
+      @click().then -> restore(url)

   # Finds a nested hyperlink. The hyperlink is the href
   # attribute of an anchor element () contained


Scale character by 1/2

Scale character by 1/2

--- test/game/views/character.coffee
+++ test/game/views/character.coffee
@@ -5,8 +5,8 @@
     initialize: (@controller, @renderer, @scene, @texture) ->
       @setElement @renderer.domElement

-      @width = 32
-      @height = 32
+      @width = 16
+      @height = 16

       @texture.done @initializeSprite

@@ -18,7 +18,7 @@
         map: @texture.data
         useScreenCoordinates: false
         scaleByViewport: true
-      @sprite.scale.x = @sprite.scale.y = 1 / 8
+      @sprite.scale.x = @sprite.scale.y = 1 / 16
       @sprite.uvScale.x = 1 / 8
       @sprite.position.y = 32
       @scene.add @sprite, 1
@@ -35,7 +35,7 @@
       @controller.keyState.on "keydown", (keyCode) =>
         collision = @controller.collision.collidesDirections {
           x: @sprite.position.x + 2, y: @sprite.position.y + 2,
-          height: 28, width: 28 }
+          height: @height, width: @width }
         switch keyCode
           when 38
             if collision.y isnt -1
@@ -88,8 +88,8 @@
       pixelHeight = @controller.tilemap.height * @controller.tilemap.tileHeight
       @sprite.position.x = x * 32
       @sprite.position.y = y * 32
-      @sprite.position.x -= (pixelWidth / 2) - 16
-      @sprite.position.y -= (pixelHeight / 2) - 16
+      @sprite.position.x -= (pixelWidth / 2) - (@width / 2)
+      @sprite.position.y -= (pixelHeight / 2) - (@height / 2)
       @sprite.position.y *= -1

     update: =>
@@ -99,7 +99,7 @@
         @controller.cameraView.setPosition @sprite.position
         collision = @controller.collision.collidesDirections {
           x: @sprite.position.x + 2, y: @sprite.position.y + 2,
-          height: 28, width: 28 }
+          height: @height, width: @width }
         @velocity[0] = 0 if collision.x * @velocity[0] < 0
         @velocity[1] = 0 if collision.y * @velocity[1] < 0
         if collision.teleport?


check if json for req params opt during popup or redirection to provider's authorization url.

--- src/presentationLayer/auth/index.coffee
+++ src/presentationLayer/auth/index.coffee
@@ -265,6 +265,12 @@
 							cb null, authorize.url
 			], (err, url) ->
 				return callback err if err
+				isJson = (value) ->
+					try
+						JSON.stringify(value)
+						return true
+					catch e
+						return false

 				#Fitbit and tripit needs this for mobile
 				if provider_conf.mobile?
@@ -275,21 +281,23 @@
 							else
 								url += '&'
 							url += k + '=' + v
-					opts = JSON.parse(req.params.opts)
-					if opts.mobile is 'true' and provider_conf.mobile?.url?
-						url_split = url.split("/oauth/authorize")
-						if url_split.length is 2
-							url = provider_conf.mobile.url + '/oauth/authorize/' + url_split[1]
+					if isJson(req.params.opts)
+						opts = JSON.parse(req.params.opts)
+						if opts.mobile is 'true' and provider_conf.mobile?.url?
+							url_split = url.split("/oauth/authorize")
+							if url_split.length is 2
+								url = provider_conf.mobile.url + '/oauth/authorize/' + url_split[1]

 				# For api like socrata, the endpoint change for every Socrata-powered data site
 				if provider_conf.redefine_endpoint
-					opts = JSON.parse(req.params.opts)
-					if opts.endpoint
-						url_split = url.split("/oauth/")
-						if opts.endpoint[opts.endpoint.length - 1] is '/'
-							opts.endpoint = opts.endpoint.slice(0, opts.endpoint.length - 1)
-						if url_split.length is 2
-							url = opts.endpoint + '/oauth/' + url_split[1]
+					if isJson(req.params.opts)
+						opts = JSON.parse(req.params.opts)
+						if opts.endpoint
+							url_split = url.split("/oauth/")
+							if opts.endpoint[opts.endpoint.length - 1] is '/'
+								opts.endpoint = opts.endpoint.slice(0, opts.endpoint.length - 1)
+							if url_split.length is 2
+								url = opts.endpoint + '/oauth/' + url_split[1]
 				res.setHeader 'Location', url
 				res.send 302
 				next()


add small delay to $timeout, see if that improves chart rendering

--- app/assets/javascripts/angular/app/controllers/fitness_ctrl.coffee
+++ app/assets/javascripts/angular/app/controllers/fitness_ctrl.coffee
@@ -15,7 +15,7 @@

     $scope.setTimeFrame = (time_frame) ->
       $scope.time_frame = time_frame
-      $timeout($scope.getTimeSeriesData)
+      $timeout($scope.getTimeSeriesData, 250)

     $scope.stepChartConfig =
       data:


Debug level 3

Debug level 3

--- app/assets/pg/representatives/representatives.controller.js.coffee
+++ app/assets/pg/representatives/representatives.controller.js.coffee
@@ -7,4 +7,4 @@
         @levels = _.groupBy assignments, 'level'
         @levels = _.toPairs @levels
         # Sort levels by their number
-        @levels = _.sortBy @levels, (l)=> l[1]
+        @levels = _.sortBy @levels, (l)=> l[0]


fix: Always reset the connected attribute when logging out

--- app/web/caretaker-session-manager/caretaker-session-manager.coffee
+++ app/web/caretaker-session-manager/caretaker-session-manager.coffee
@@ -49,6 +49,8 @@

   logout: ->
     @$.signOutRequest.go()
+    @connected = false
+    @userIsAdmin = false

   logoutSucceeded: ->
     @connected = false


including Backbone.Validation mixin

including Backbone.Validation mixin

--- dkobo/koboform/static/js/xlform/XLF.init.coffee
+++ dkobo/koboform/static/js/xlform/XLF.init.coffee
@@ -33,6 +33,6 @@
 # window.log is a substitute for console.log
 @log = (args...)-> console?.log?.apply console, args

-# _.extend Backbone.Model.prototype, Backbone.Validation.mixin
+_.extend Backbone.Model.prototype, Backbone.Validation.mixin

 @viewTemplates = XLF.viewTemplates = {}


Only store host, not path in wiki URL setting

--- services/web/app/coffee/Features/Wiki/WikiController.coffee
+++ services/web/app/coffee/Features/Wiki/WikiController.coffee
@@ -9,7 +9,7 @@
 		if page == ""
 			page = "Main_Page"

-		wikiUrl = "#{settings.apis.wiki.url}/api.php"
+		wikiUrl = "#{settings.apis.wiki.url}/learn-scripts/api.php"

 		logger.log page: page, "proxying request to wiki"


Update capture in camera tab: hide video button if images only

--- app/assets/javascripts/uploadcare/widget/tabs/camera-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/camera-tab.coffee
@@ -15,7 +15,9 @@
         @container.append(tpl('tab-camera-capture'))
         @container.addClass('uploadcare--camera')
         @container.find('.uploadcare--camera__button_type_photo').on('click', @__captureInput('image/*'))
-        @container.find('.uploadcare--camera__button_type_video').on('click', @__captureInput('video/*'))
+        video = @container.find('.uploadcare--camera__button_type_video').on('click', @__captureInput('video/*'))
+        if @settings.imagesOnly
+          video.hide()
       else
         if not @__checkCompatibility()
           @dialogApi.hideTab(@name)


fix dataset import regression

fix dataset import regression

--- app/assets/javascripts/views/spotlight_dataset_list_view.coffee
+++ app/assets/javascripts/views/spotlight_dataset_list_view.coffee
@@ -21,7 +21,6 @@

     @collection.fetch(
       silent : true
-      data : "isActive=true"
     ).done( =>
       @collection.goTo(1)
     )


Added missing requires so test would pass in firefox in isolation

--- test/spec/portfoliokanban/PortfolioKanbanAppSpec.coffee
+++ test/spec/portfoliokanban/PortfolioKanbanAppSpec.coffee
@@ -1,5 +1,14 @@
 Ext = window.Ext4 || window.Ext
-Ext.require ['Rally.data.util.PortfolioItemHelper']
+Ext.require [
+  'Ext.Array'
+  'Rally.app.Context'
+  'Rally.Message'
+  'Ext.Element'
+  'Rally.util.Ref'
+  'Rally.data.util'
+  'Rally.apps.portfoliokanban.PortfolioKanbanApp'
+  'Rally.data.util.PortfolioItemHelper'
+]

 describe 'Rally.apps.portfoliokanban.PortfolioKanbanApp', ->
   helpers


Adeed a bit more descriptive error message on parse errors for the splash fetch json

--- Resources/app/controllers/splash_controller.coffee
+++ Resources/app/controllers/splash_controller.coffee
@@ -18,7 +18,8 @@
 			@splash = splash
 			@window.displaySplash(@splash)
 		, (xhr, status, error) =>
-			d("Error finding a Citrus splash from the decoded data. Status: "+status)
+			e("Error finding a Citrus splash from the decoded data. Status: "+status)
+			e(xhr.responseText)
 			if xhr
 				# The scanned code was a citrus code but there was a problem connecting to the backend.
 				if !Titanium.Network.online


activity: move loader marker if only message is not loaded before

--- client/activity/lib/components/loadmoremessagesmarker/index.coffee
+++ client/activity/lib/components/loadmoremessagesmarker/index.coffee
@@ -32,6 +32,8 @@

     { message: messageActions } = ActivityFlux.actions

+    messagesBefore = kd.singletons.reactor.evaluate ['MessagesStore']
+
     messageActions.loadMessages(channelId, options).then ({ messages }) ->
       messageActions.removeLoaderMarker channelId, messageId, { position }

@@ -41,7 +43,8 @@
       # don't put loader markers on top.
       return  unless position is 'after'

-      messageActions.putLoaderMarker channelId, last.id, { position: 'after', autoload: no }
+      unless messagesBefore.has last.id
+        messageActions.putLoaderMarker channelId, last.id, { position: 'after', autoload: no }

   render: ->


activity: format require statements

activity: format require statements

--- client/activity/lib/views/privatemessage/privatemessagepane.coffee
+++ client/activity/lib/views/privatemessage/privatemessagepane.coffee
@@ -1,21 +1,21 @@
-kd = require 'kd'
-KDButtonView = kd.ButtonView
-KDCustomHTMLView = kd.CustomHTMLView
-KDFormViewWithFields = kd.FormViewWithFields
-KDView = kd.View
+kd                               = require 'kd'
+KDButtonView                     = kd.ButtonView
+KDCustomHTMLView                 = kd.CustomHTMLView
+KDFormViewWithFields             = kd.FormViewWithFields
+KDView                           = kd.View
 ActivityAutoCompleteUserItemView = require '../activityautocompleteuseritemview'
-ParticipantSearchController = require './participantsearchcontroller'
-MessagePane = require '../messagepane'
-PrivateMessageForm = require './privatemessageform'
-PrivateMessageListItemView = require './privatemessagelistitemview'
-PrivateMessageSettingsView = require './privatemessagesettingsview'
-ReplyInputWidget = require './replyinputwidget'
-ReplyPreviousLink = require './replypreviouslink'
-showError = require 'app/util/showError'
-AvatarView = require 'app/commonviews/avatarviews/avatarview'
-dateFormat = require 'dateformat'
-isMyPost = require 'app/util/isMyPost'
-remote = require('app/remote').getInstance()
+ParticipantSearchController      = require './participantsearchcontroller'
+MessagePane                      = require '../messagepane'
+PrivateMessageForm               = require './privatemessageform'
+PrivateMessageListItemView       = require './privatemessagelistitemview'
+PrivateMessageSettingsView       = require './privatemessagesettingsview'
+ReplyInputWidget                 = require './replyinputwidget'
+ReplyPreviousLink                = require './replypreviouslink'
+showError                        = require 'app/util/showError'
+AvatarView                       = require 'app/commonviews/avatarviews/avatarview'
+dateFormat                       = require 'dateformat'
+isMyPost                         = require 'app/util/isMyPost'
+remote                           = require('app/remote').getInstance()


account: sidebar list styling fixed

account: sidebar list styling fixed

--- client/app/Applications/Account.kdapplication/AppController.coffee
+++ client/app/Applications/Account.kdapplication/AppController.coffee
@@ -41,7 +41,7 @@
         , section

         navView.addSubView lc.getView()
-        navView.addSubView new KDCustomHTMLView "hr"
+        navView.addSubView new KDCustomHTMLView tagName : "hr"

         lc.getView().on 'ItemWasAdded', (view, index)=>
           view.on "click", =>


Group VMs by slug

Group VMs by slug

--- client/app/MainApp/filetree/controllers/resourcescontroller.coffee
+++ client/app/MainApp/filetree/controllers/resourcescontroller.coffee
@@ -14,9 +14,18 @@
     @reset()

   reset:->
+    cmp = (a, b)->
+      [groupA, vmA] = a.split('~')
+      [groupB, vmB] = b.split('~')
+      if groupA == groupB
+        vmA > vmB
+      else
+        groupA > groupB
+
     KD.singletons.vmController.resetVMData()
-    KD.singletons.vmController.fetchGroupVMs (err, vms)=>
+    KD.singletons.vmController.fetchVMs (err, vms)=>
       @removeAllItems()
+      vms.sort cmp
       @instantiateListItems vms  unless err
       @deselectAllItems()


Add in js image upload preview

Add in js image upload preview

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -491,8 +491,10 @@
     if @accept_file_upload == "True"
       if window.File and window.FileReader and window.FileList and window.Blob
         @can_upload_files = true
-        @file_upload_area.html('<input type="file" class="file-upload-box">')
+        @file_upload_area.html('<input type="file" class="file-upload-box"><img class="file-upload-preview" src="#" alt="Uploaded image" />')
         @file_upload_area.show()
+        $('.file-upload-preview').hide()
+        $('.file-upload-box').change @preview_image
       else
         @gentle_alert 'File uploads are required for this question, but are not supported in this browser. Try the newest version of google chrome.  Alternatively, if you have uploaded the image to the web, you can paste a link to it into the answer box.'

@@ -548,3 +550,11 @@
   log_feedback_selection: (event) ->
     target_selection = $(event.target).val()
     Logger.log 'oe_feedback_response_selected', {value: target_selection}
+
+  preview_image: () =>
+    if $('.file-upload-box')[0].files && $('.file-upload-box')[0].files[0]
+      reader = new FileReader()
+      reader.onload = (e) =>
+        $('.file-upload-preview').attr('src', e.target.result).width(150).height(150)
+        $('.file-upload-preview').show()
+      reader.readAsDataURL($('.file-upload-box')[0].files[0])


RocketFile: Implement avatar removal

RocketFile: Implement avatar removal

--- packages/file/file.server.coffee
+++ packages/file/file.server.coffee
@@ -40,9 +40,15 @@

 		this.store = new Grid(db, mongo)
 		this.findOneSync = Meteor.wrapAsync this.store.collection(this.name).findOne.bind this.store.collection(this.name)
+		this.removeSync = Meteor.wrapAsync this.store.remove.bind this.store

 	findOne: (fileName) ->
 		return this.findOneSync {_id: fileName}
+
+	remove: (fileName) ->
+		return this.removeSync
+			_id: fileName
+			root: this.name

 	createWriteStream: (fileName, contentType) ->
 		self = this
@@ -84,6 +90,13 @@
 			length: file.length
 		}

+	deleteFile: (fileName) ->
+		file = this.findOne fileName
+		if not file?
+			return undefined
+
+		return this.remove fileName
+

 RocketFile.FileSystem = class
 	constructor: (config={}) ->
@@ -103,6 +116,7 @@
 		this.absolutePath = path.resolve absolutePath
 		mkdirp.sync this.absolutePath
 		this.statSync = Meteor.wrapAsync fs.stat.bind fs
+		this.unlinkSync = Meteor.wrapAsync fs.unlink.bind fs

 	createWriteStream: (fileName, contentType) ->
 		self = this
@@ -124,15 +138,25 @@
 	stat: (fileName) ->
 		return this.statSync path.join this.absolutePath, fileName

+	remove: (fileName) ->
+		return this.unlinkSync path.join this.absolutePath, fileName
+
 	getFileWithReadStream: (fileName) ->
-		stat = this.stat fileName
-		if not stat?
+		try
+			stat = this.stat fileName
+			rs = this.createReadStream fileName
+
+			return {
+				readStream: rs
+				# contentType: file.contentType
+				length: stat.size
+			}
+		catch e
 			return undefined

-		rs = this.createReadStream fileName
-
-		return {
-			readStream: rs
-			# contentType: file.contentType
-			length: stat.size
-		}+	deleteFile: (fileName) ->
+		try
+			stat = this.stat fileName
+			return this.remove fileName
+		catch e
+			return undefined


remove the double validateDropBlockCondition

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -441,10 +441,6 @@
         # console.log "validateDropBlockCondition"
         @validateDropBlockCondition(question)

-      if question.hasClass("js-conditional-drop-block-answer")
-        # console.log "validateDropBlockCondition"
-        @validateDropBlockCondition(question)
-
       if question.hasClass("question-support-requests") ||
          question.hasClass("question-support-uploads")
         # console.log "validateSupportLetters"


Parses the AMS response for the taxons endpoint

--- app/assets/javascripts/sprangular/services/catalog.coffee
+++ app/assets/javascripts/sprangular/services/catalog.coffee
@@ -29,7 +29,7 @@
     taxon: (path) ->
       $http.get("/api/taxons/#{path}")
         .then (response) ->
-          response.data
+          response.data.taxon

     find: (id) ->
       $http.get("/api/products/#{id}", class: Sprangular.Product)


Moves no_data route to voluntary-ember_js.

--- app/assets/javascripts/voluntary_ranking/router.js.coffee
+++ app/assets/javascripts/voluntary_ranking/router.js.coffee
@@ -24,7 +24,4 @@
   @resource 'compare_things', path: '/things/:left_thing_name/vs/:right_thing_name', ->
     @route 'arguments', path: ':side/arguments/page/:page'

-  @resource 'ranking_item', path: '/things/:thing_name/is_one_of_the/:adjective/:topic/:scope/page/:page'
-
-  # This route will be used to reload the current route by going to this route and then back to the current route
-  @route 'no_data'+  @resource 'ranking_item', path: '/things/:thing_name/is_one_of_the/:adjective/:topic/:scope/page/:page'


More facetSearch engine cleanup

More facetSearch engine cleanup

--- app/coffee/modules/search/facetsearch/views/engine.coffee
+++ app/coffee/modules/search/facetsearch/views/engine.coffee
@@ -9,12 +9,9 @@
 defaults =
   items: [{a:2,b:1,c:2},{a:2,b:2,c:1},{a:1,b:1,c:1},{a:3,b:3,c:1}],
   facets: {'a': 'Title A', 'b': 'Title B', 'c': 'Title C'},
-  # resultElement: '#results'
   facetElement: '#facets'
   listItemTemplate: '<div class="facetitem" id="<%= id %>"><%= name %> (<%= count %>)
'
-  orderByTemplate: '
Sort by:
<% _.each(options, function(value, key) { %>' + '<li class=orderbyitem id=orderby_<%= key %>>' + '<%= value %>
 <% }); %>

'
   countTemplate: '
Results
'
-  deselectTemplate: '
Deselect all filters
'
   resultTemplateBypass: null
   orderByOptions:
     'a': 'by A'
@@ -23,7 +20,6 @@
   state:
     orderBy: false
     filters: {}
-  showMoreTemplate: 'Show more'
   enablePagination: true
   paginationCount: 20

@@ -382,16 +378,17 @@
   # TODO - ABSTRACT INTO SEPARATE VIEW
   # FOR RESULT COUNTR AND ORDER CONTROLS
   # Append total result count
-
   bottomContainer = '

'
-
   bottom = $(bottomContainer)
   countHtml = _.template(settings.countTemplate, count: settings.currentResults.length or 0)
   $(bottom).append(countHtml)

   # generate the "order by" options:
-  ordertemplate = _.template(settings.orderByTemplate)
+
+  orderByTemplate = '
Sort by:
<% _.each(options, function(value, key) { %>' + '<li class=orderbyitem id=orderby_<%= key %>>' + '<%= value %>
 <% }); %>

'
+
+  ordertemplate = _.template(orderByTemplate)
   itemHtml = $(ordertemplate('options': settings.orderByOptions))
   $(bottom).append itemHtml
   $(settings.facetElement).append bottom
@@ -416,7 +413,8 @@

   # Append deselect filters button
   # TODO - abstract into Backbone.View
-  deselect = $(settings.deselectTemplate).click((event) ->
+  deselectTemplate = '
Deselect all filters
'
+  deselect = $(deselectTemplate).click((event) ->
     settings.state.filters = {}
     jQuery.facetUpdate()
     return
@@ -543,10 +541,11 @@
   # $(settings.resultElement).append itemHtml

   # Append "MoreButton"
+  showMoreTemplate = 'Show more'
   # TODO - we will _probably_ paginate using BB.Mn
   # TODO - pagination
   # if !moreButton
-  #   moreButton = $(settings.showMoreTemplate).click(showMoreResults)
+  #   moreButton = $(showMoreTemplate).click(showMoreResults)
   #   $(settings.resultElement).after moreButton
   # # ???/
   # if settings.state.shownResults == 0


Distinct between leave and close status.

--- app/assets/javascripts/app/controllers/chat.coffee
+++ app/assets/javascripts/app/controllers/chat.coffee
@@ -252,10 +252,16 @@
       return if data.self_written
       @receiveMessage(data.message.content)
     )
-    @bind('chat_session_left chat_session_closed', (data) =>
+    @bind('chat_session_left', (data) =>
       return if data.session_id isnt @session.session_id
       return if data.self_written
       @addStatusMessage("#{data.realname} has left the conversation")
+      @goOffline()
+    )
+    @bind('chat_session_closed', (data) =>
+      return if data.session_id isnt @session.session_id
+      return if data.self_written
+      @addStatusMessage("#{data.realname} has closed the conversation")
       @goOffline()
     )
     @bind('chat_focus', (data) =>


reactivity: chatinput: test utils package usage

--- client/activity/lib/components/chatinputwidget/test/view.coffee
+++ client/activity/lib/components/chatinputwidget/test/view.coffee
@@ -1,7 +1,7 @@
-React           = require 'react/addons'
+React           = require 'kd-react'
 ReactDOM        = require 'react-dom'
 expect          = require 'expect'
-{ TestUtils }   = React.addons
+TestUtils       = require 'react-addons-test-utils'
 toImmutable     = require 'app/util/toImmutable'
 KeyboardKeys    = require 'app/constants/keyboardKeys'
 EmojiDropbox    = require 'activity/components/emojidropbox'


New attempt to fix URL

New attempt to fix URL

--- app/assets/javascripts/rglossa/data/adapter.coffee
+++ app/assets/javascripts/rglossa/data/adapter.coffee
@@ -9,9 +9,11 @@
   buildURL: (record, suffix) ->
     url = @_super(record, suffix)
     unless record.search(/_search$/) is -1
-      parts = url.split('/')
-      lastElm = parts.length - 1
-      url = parts[0...lastElm].join('/') + '/search_engines/' + parts[lastElm]
+      namespace = DS.RESTAdapter.prototype.namespace
+      if namespace
+        url = url.replace(namespace, namespace + '/search_engines')
+      else
+        url = "/search_engines#{url}"
     url


If people opiniate multiple times, only show last opinion

--- core/app/backbone/views/activities/activities_group_view.coffee
+++ core/app/backbone/views/activities/activities_group_view.coffee
@@ -68,8 +68,8 @@
   activityMadeRedundantBy: (newActivity, oldActivity) -> false
   newActivityIsRedundant: (newActivity) ->
     return false unless @collection.models.length > 1
-    @activityMadeRedundantBy newActivity.get('activity'),
-      @collection.models[@collection.length - 2].get('activity')
+    @activityMadeRedundantBy newActivity,
+      @collection.models[@collection.length - 2]

   appendHtml: (collectionView, itemView, index) ->
     return if @newActivityIsRedundant(itemView.model)
@@ -95,6 +95,12 @@

   appendable: (model) -> super(model) and @sameFact(model)

+  isOpinion: (activity) ->
+    activity.get('action') in ['believes', 'disbelieves', 'doubts']
+
+  activityMadeRedundantBy: (newActivity, oldActivity) ->
+    @isOpinion(oldActivity) && @isOpinion(newActivity)
+
 class UsersFollowedGroupView extends UserActivitiesGroupView
   template: 'activities/users_followed_group'

@@ -102,4 +108,5 @@
   actions: -> UsersFollowedGroupView.actions

   activityMadeRedundantBy: (newActivity, oldActivity) ->
-    newActivity.followed_user.username == oldActivity.followed_user.username
+    newActivity.get('activity').followed_user.username ==
+      oldActivity.get('activity').followed_user.username


graph: increase no. of digits for random id to avoid conflicts

--- workers/social/lib/social/models/graph/fetch.coffee
+++ workers/social/lib/social/models/graph/fetch.coffee
@@ -124,7 +124,7 @@
     return response

   generateUniqueRandomKey: ->
-    randomId = Math.floor(Math.random()*1000)
+    randomId = Math.floor(Math.random()*100000)
     if @usedIds[randomId]
       @generateUniqueRandomKey()
     else


SocialWorker: fix nickname destructuring

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1245,7 +1245,7 @@
         @updateCounts()
         @cycleChannel()

-        { profile: nickname } = client.connection.delegate
+        { profile: { nickname } } = client.connection.delegate

         return  unless nickname


analytics: add heap analytics

analytics: add heap analytics

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -45,6 +45,8 @@

     <!-- MIXPANEL -->
     <script>(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);mixpanel.init("#{KONFIG.mixpanel}");</script>
+
+    <script type="text/javascript">var heap=heap||[];heap.load=function(a){window._heapid=a;var b=document.createElement("script");b.type="text/javascript",b.async=!0,b.src=("https:"===document.location.protocol?"https:":"http:")+"//cdn.heapanalytics.com/js/heap.js";var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d=function(a){return function(){heap.push([a].concat(Array.prototype.slice.call(arguments,0)))}},e=["identify","track"];for(var f=0;f<e.length;f++)heap[e[f]]=d(e[f])};heap.load("112304216");</script>

     <script>KD.config.usePremiumBroker=#{usePremiumBroker}</script>
     <script>KD.customPartial=#{encodedCustomPartial}</script>


Restore import of jquery in plot_canvas

Restore import of jquery in plot_canvas

--- bokehjs/src/coffee/models/plots/plot_canvas.coffee
+++ bokehjs/src/coffee/models/plots/plot_canvas.coffee
@@ -1,4 +1,5 @@
 _ = require "underscore"
+$ = require "jquery"

 Canvas = require "../canvas/canvas"
 CartesianFrame = require "../canvas/cartesian_frame"


Debug/checkboxes selection from purchase invoice modal process

--- app/assets/javascripts/backend/purchase_process/reconciliation.js.coffee
+++ app/assets/javascripts/backend/purchase_process/reconciliation.js.coffee
@@ -76,7 +76,8 @@
   ##############################
   # Open modal on button click
   $(document).on 'click', '#showReconciliationModal', (event) =>
-    open_reconciliation_modal($(event.target))
+    $form = $(event.target).closest('form')
+    open_reconciliation_modal($form)

   # Validate modal
   handle_modal_validation = (modal) =>
@@ -106,15 +107,18 @@
       data['reception'] = action.substr(action.lastIndexOf('/') + 1)
     {url: url, data: data}

+  isPurchaseInvoiceForm = ($form) =>
+    $form.closest('.simple_form').is('.new_purchase_invoice, .edit_purchase_invoice')
+
+  isReceptionForm = ($form) =>
+    $form.closest('.simple_form').is('.new_reception, .edit_reception')
+
   # Returns a promise that resolves to the content for the modal to be opened
   content_for_reconciliation_modal = ($form) =>
-    isPurchaseInvoiceForm = $form.is('.new_purchase_invoice, .edit_purchase_invoice')
-    isReceptionForm = $form.closest('.simple_form').is('.new_reception, .edit_reception')
-
     options = {}
-    if isPurchaseInvoiceForm
+    if isPurchaseInvoiceForm($form)
       options = purchase_invoice_modal_options()
-    else if isReceptionForm
+    else if isReceptionForm($form)
       options = reception_modal_options()
     else return Promise.reject("Modal type cannot be guessed!")
     E.ajax.html options
@@ -128,25 +132,25 @@
       # Attacher un ecouteur evenement sur les checkbox des items
       E.delegateListener root, 'click', ".item-checkbox", (e) =>
         commandContainer = e.target.closest('.model')
-        syncState(commandContainer)
+        syncState(commandContainer, $form)
         return

       # Handle automated selection of multiple checkbox
       E.delegateListener root, 'click', '.model-checkbox', (e) =>
         checked = e.target.checked
         commandContainer = e.target.closest('.model')
-        setAllChildrenTo(commandContainer, checked)
+        setAllChildrenTo(commandContainer, checked, $form)
         return

       displayedItemIds = Array.from(document.querySelectorAll('.nested-fields .nested-item-form[data-item-id]'))
       displayedItemIds = displayedItemIds.map((item) => item.dataset.itemId)

       displayedItemIds.forEach (id) =>
-        checkbox = content.querySelector("input[type='checkbox'][data-id='#{id}']")
+        checkbox = content.querySelector(".item input[type='checkbox'][data-id='#{id}']")
         commandContainer = checkbox.closest('.model')
         if checkbox
           checkbox.checked = true
-          syncState(commandContainer)
+          syncState(commandContainer, $form)
       return

     prom.then (modal) =>
@@ -157,19 +161,19 @@
   # Checks the parent checkbox if all children are checked. Uncheck it if not.
   # If all checked, set 'close command to true, else to false'
   # Also hide/display the close command radio
-  syncState = (commandContainer) =>
+  syncState = (commandContainer, $form) =>
     itemCheckboxes = Array.from(commandContainer.querySelectorAll('.item-checkbox'))
     allItemsCheckboxesChecked = itemCheckboxes.map((cb) => cb.checked).reduce(((acc, e) => acc && e), true)
-    anyItemsCheckboxesChecked = itemCheckboxes.map((cb) => cb.checked).includes(true)
-    closePurchaseOrderBlock = commandContainer.querySelector('.close-purchase-order')
-
     commandContainer.querySelector('.model-checkbox').checked = allItemsCheckboxesChecked
-    setCloseCommand(commandContainer, allItemsCheckboxesChecked)
-
-    if anyItemsCheckboxesChecked
-      closePurchaseOrderBlock.classList.remove('hidden')
-    else
-      closePurchaseOrderBlock.classList.add('hidden')
+
+    if isReceptionForm($form)
+      setCloseCommand(commandContainer, allItemsCheckboxesChecked)
+
+      closePurchaseOrderBlock = commandContainer.querySelector('.close-purchase-order')
+      if itemCheckboxes.map((cb) => cb.checked).includes(true)
+        closePurchaseOrderBlock.classList.remove('hidden')
+      else
+        closePurchaseOrderBlock.classList.add('hidden')

   # Sets the value of the radiobutton to `value`
   setCloseCommand = (commandContainer, value) =>
@@ -177,9 +181,9 @@
     closePurchaseOrderBlock.querySelector("input[type='radio'][value=#{value}").checked = true

   # Sets all children checkboxes to 'value' and sync state
-  setAllChildrenTo = (commandContainer, value) =>
+  setAllChildrenTo = (commandContainer, value, $form) =>
     commandContainer.querySelectorAll('.item-checkbox').forEach((item) => item.checked = value)
-    syncState(commandContainer)
+    syncState(commandContainer, $form)

   E.reconciliation =
     reconciliateItems: (modalContent) ->
@@ -333,8 +337,6 @@
         $(lastLineForm).find('.nested-fields.storing-fields:first .conditionning-quantity').val(itemConditionningQuantity)
         $(lastLineForm).find('.nested-fields.storing-fields:first .conditionning').val(itemConditionning)

-      # purchaseOrderToCloseUpdate(checkboxLine, lastLineForm)
-
   # Given checkbox and formContainer, this method sets the purchase-order-to-close-id property to the purchase order id on the given reception item's form fields
   #    if the purchase order of the selected element is marked for closure when saving the reception
   #
@@ -351,6 +353,11 @@
     urlParams = new URLSearchParams(window.location.search)
     if urlParams.has('purchase_order_ids')
       open_reconciliation_modal $(form)
+
+  E.onElementDetected 'new_purchase_invoice', (form) =>
+    urlParams = new URLSearchParams(window.location.search)
+    if urlParams.has('reception_ids')
+      open_reconciliation_modal $(form)

 ) ekylibre, jQuery


[improvement] Allowed entering "people/sort" as a shortcut for the tags "people" + "people-sort" (10m)

--- app/assets/javascripts/houston/feedback/helpers/jquery_extensions.coffee
+++ app/assets/javascripts/houston/feedback/helpers/jquery_extensions.coffee
@@ -27,6 +27,14 @@

   selectedTags: ->
     text = @.val()
-    tags = _.map text.split(/[,;]/), (tag)->
-      tag.trim().toLowerCase().replace(/^#/, '').replace(/[^\w\?]+/g, '-')
+    tags = _.reduce text.split(/[,;]/), (tags, tag) ->
+
+      # Normalize tags
+      tag = tag.trim().toLowerCase().replace(/^#/, '').replace(/[^\w\/\?]+/g, '-')
+
+      # Convert "feature/subfeature" to ["feature", "feature-subfeature"]
+      tags.concat _.reduce tag.split(/\//), (tags, tag)->
+        tags.concat tags.slice(-1).concat(tag).join("-")
+      , []
+    , []
     _.reject tags, (tag)-> !tag


client: home: fix billing section headers

--- client/home/lib/billing/hometeambillingform.coffee
+++ client/home/lib/billing/hometeambillingform.coffee
@@ -30,7 +30,7 @@
   pistachio: ->

     """
-
Payment Information

+    Payment Information

       Card Number
       {{> @ccNumber}}
@@ -51,7 +51,7 @@
         {{> @ccNameChange}}
       </fieldset>

-
Billing Information

+    Billing Information

       <fieldset>
         Full Name:


FIX: Ensure target date only fetched when description has task def

--- src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.coffee
+++ src/app/projects/states/dashboard/directives/task-dashboard/directives/task-description-card/task-description-card.coffee
@@ -31,8 +31,10 @@
     $scope.dueDate = () ->
       if $scope.task?.due_date?
         return $scope.task.due_date
+      else if $scope.taskDef?
+        return $scope.taskDef?.target_date
       else
-        return $scope.taskDef.target_date
+        return new Date()

     $scope.should_show_deadline = () ->
       # Returns true if we are within 2 weeks of deadline (ms * s * m * h * days = 1,209,600,000)


Made all delete buttons consistent

Made all delete buttons consistent

--- core/app/backbone/views/facts/top_fact_view.coffee
+++ core/app/backbone/views/facts/top_fact_view.coffee
@@ -22,7 +22,6 @@
     new ReactView
       component: ReactDeleteButton
         model: @model,
-        text: 'Undo' if @model.justCreated()
         onDelete: ->
           @model.destroy
             wait: true


Fix failing e2e

Fix failing e2e

--- angular/test/protractor/manage_membership_requests_spec.coffee
+++ angular/test/protractor/manage_membership_requests_spec.coffee
@@ -24,7 +24,7 @@
       membershipRequestsHelper.clickApproveButton()
       membershipRequestsHelper.clickApproveButton()
       emailHelper.openLastEmail()
-      expect(emailHelper.lastEmailSubject().getText()).toContain('Your request to join Dirty Dancing Shoes on Loomio has been approved')
+      expect(emailHelper.lastEmailSubject().getText()).toContain('Your request to join Dirty Dancing Shoes has been approved')

     it 'displays the correct flash message', ->
       membershipRequestsHelper.clickApproveButton()


fix search table event listener

fix search table event listener

--- Framework/Views/Controls/SearchResultsTable_Framework.coffee
+++ Framework/Views/Controls/SearchResultsTable_Framework.coffee
@@ -11,6 +11,7 @@
     @table = Ti.UI.createTableView({
       top: @options.top
     })
+    @table.addEventListener("click", @options.onTableClick)
     @moreRow = root.app.create("SearchResultsTableMoreRow").moreRow

     if @options.infiniteScroll
@@ -51,7 +52,5 @@
       @table.appendRow(@moreRow)
     else
       @table.hasMoreRows = false
-
-    @table.addEventListener("click", @options.onTableClick)

     Ti.API.info("-- Finish Update Table --")


displayedQuery doesn't need to depend on query

--- app/assets/javascripts/rglossa/components/cwb-multiword.coffee
+++ app/assets/javascripts/rglossa/components/cwb-multiword.coffee
@@ -47,7 +47,7 @@
     dq[0].isFirst = true
     dq[dq.length-1].isLast = true
     dq
-  ).property('query')
+  ).property()

   displayedQueryDidChange: (->


removed menu scroll jumps

removed menu scroll jumps

--- app/assets/javascripts/app/common/menu-scroll.coffee
+++ app/assets/javascripts/app/common/menu-scroll.coffee
@@ -0,0 +1,19 @@
+class @MenuScroll
+
+  bindScroll : () ->
+    return unless localStorage
+    $menu = $('.nav-list.with-scroll')
+    $menu.scrollTop localStorage.getItem('scrollTop') - 0
+    $(document).on 'page:before-unload', () ->
+      localStorage.setItem 'scrollTop', $menu.scrollTop()
+
+  @init : () ->
+    menuScroll = new this
+    menuScroll.bindScroll()
+    menuScroll
+
+  @initPlugin : () =>
+    @init()
+
+$ ->
+  $(document).on 'ready page:load init-plugins', MenuScroll.initPlugin


add datatables options (stateSave, sort order)

--- app/assets/javascripts/chaltron/datatables.js.coffee
+++ app/assets/javascripts/chaltron/datatables.js.coffee
@@ -1,9 +1,10 @@
 class DataTableBuilder
   constructor: ->
     @defaultOptions = {
-      destroy: true,
-      autoWidth: false,
+      destroy: true
+      autoWidth: false
       responsive: true
+      stateSave: true
     }

   go: ->
@@ -13,15 +14,15 @@
     # ldap create
     div = $('table#ldap_create')
     @initTable(container: div, params: {
-      paging: false,
+      paging: false
       # default sorting: username (2nd column) asc
       aaSorting: [[1,'asc']]
       aoColumnDefs: [
         { bSortable: false, aTargets: [ 0 ] }
-      ],
-      dom: 'T<"clear">lfrtip',
+      ]
+      dom: 'T<"clear">lfrtip'
       tableTools: {
-        sRowSelect: 'multi',
+        sRowSelect: 'multi'
         aButtons: [ ]
       }}
     )
@@ -34,6 +35,9 @@
       ajax: div.data('source')
       # default sorting: date (2nd column) desc
       aaSorting: [[1,'desc']]
+      aoColumnDefs: [
+        { asSorting: ['desc', 'asc'], aTargets: [ 1 ] }
+      ]
     })


Fix broken syntax in multiple angular files

I have no idea how this was not already throwing errors before...

--- app/assets/javascripts/darkswarm/darkswarm.js.coffee
+++ app/assets/javascripts/darkswarm/darkswarm.js.coffee
@@ -13,7 +13,7 @@
   'angularSlideables'
 ]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
   $httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'
-  $httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*"
+  $httpProvider.defaults.headers.common['Accept'] = "application/json, text/javascript, */*"

   # We manually handle our scrolling
   $anchorScrollProvider.disableAutoScrolling()


Fixing a typo

Fixing a typo

--- app/assets/javascripts/darkswarm/darkswarm.js.coffee
+++ app/assets/javascripts/darkswarm/darkswarm.js.coffee
@@ -1,4 +1,3 @@
-<<<<<<< HEAD
 window.Darkswarm = angular.module("Darkswarm", ["ngResource", "filters", 'mm.foundation', 'angularLocalStorage', 'pasvaz.bindonce', 'infinite-scroll']).config ($httpProvider, $tooltipProvider) ->
   $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
   $httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'


GW-5130 #comment fix filtering select with polymorphic initial value

--- app/assets/javascripts/rails_admin/ra.widgets.coffee
+++ app/assets/javascripts/rails_admin/ra.widgets.coffee
@@ -151,7 +151,6 @@
       object_select.data('options', $("##{type_select_val.toLowerCase()}-js-options").data('options'))
       object_select.filteringSelect("destroy")
       object_select.filteringSelect object_select.data('options')
-      object_select.html('<option value=""></option>')
       object_select.siblings().css('float', 'left').css('display', 'inline-block')
       object_select.css('display', 'none')


Fix search results: clicking n new posts brings first one into view and renders the rest above it

--- assets/javascripts/views/search_fetch_pool.js.coffee
+++ assets/javascripts/views/search_fetch_pool.js.coffee
@@ -72,7 +72,7 @@

     @render()

-    if last_result_el = Marbles.DOM.querySelector("[data-result_cid='#{last_result_cid}']", results_feed_view.el)
+    if last_result_el = Marbles.DOM.querySelector("[data-post_cid='#{last_result_cid}']", results_feed_view.el)
       resultition = last_result_el.offsetTop - 20 # 20 is an arbitrary padding amount
       window.scrollTo(window.scrollX, resultition)


Widgets: Set editor height to 400.

Widgets: Set editor height to 400.

--- client/Dashboard/views/customviews/addnewcustomviewform.coffee
+++ client/Dashboard/views/customviews/addnewcustomviewform.coffee
@@ -33,7 +33,7 @@
       cssClass    : "editor-container"
       size        :
         width     : 876
-        height    : 800
+        height    : 400
       files       : files

     @cancelButton = new KDButtonView


Workspace: Fixed join session button callback.

--- client/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -189,8 +189,7 @@
           title           : "Join Session"
           cssClass        : "modal-clean-green"
           callback        : =>
-            sessionKey    = modal.modalTabs.forms["Join A Session"].inputs.SessionInput.getValue()
-            @joinSession sessionKey
+            @joinSession sessionKeyInput.getValue()
             modal.destroy()
         Close             :
           title           : "Close"


managedvm: minor fix

managedvm: minor fix

--- client/app/lib/providers/managed/addmanagedmachinemodal.coffee
+++ client/app/lib/providers/managed/addmanagedmachinemodal.coffee
@@ -118,7 +118,11 @@
     computeController.managedKiteChecker.addListener @bound 'machineFoundCallback'

     kd.utils.wait 20000, =>
-      @main.addSubView new kd.LoaderView { showLoader: yes, size: { width: 26 } }
+      @main.addSubView new kd.LoaderView
+        cssClass : 'machine-search-loader'
+        showLoader: yes
+        size: { width: 26 }
+
       @setClass 'polling'

@@ -144,10 +148,11 @@

   handleUsageLimit: ->

-    @setTitle 'Uh oh! You already have a managed machine!'
-
     @code.destroy()
     @content.updatePartial '''
+      <h>
+        'Uh oh! You already have a managed machine!'
+      </h>
       <p>
         Free Koding accounts are limited to adding one external machine and
         you already have one connected. Paid accounts are allowed to add unlimited external machines.


set classname as a dynamic variable

set classname as a dynamic variable

--- client/home/lib/myteam/components/hometeamsettings/view.coffee
+++ client/home/lib/myteam/components/hometeamsettings/view.coffee
@@ -88,7 +88,7 @@

   className = if canEdit then 'half' else 'half TeamName'

-  <fieldset className=className>
+  <fieldset className={className}>
     <label>Team Name</label>
     <TeamNameInputArea canEdit={canEdit} value={value} callback={callback} />
   </fieldset>


Check on bottom scrolling

Check on bottom scrolling

--- SingularityUI/app/components/aggregateTail/Contents.cjsx
+++ SingularityUI/app/components/aggregateTail/Contents.cjsx
@@ -159,6 +159,6 @@
     @refs.lines.scrollTo(0)

   scrollToBottom: ->
-    @refs.lines.scrollTo(@state.linesToRender.length)
+    @refs.lines.scrollTo(@state.linesToRender?.length || 0)

 module.exports = Contents


Activity: listcontroller refactoring

Activity: listcontroller refactoring

--- client/Social/Activity/activitylistcontroller.coffee
+++ client/Social/Activity/activitylistcontroller.coffee
@@ -4,14 +4,16 @@

   constructor:(options={}, data)->

-    viewOptions = options.viewOptions or {}
+    viewOptions                   = options.viewOptions or {}
     viewOptions.cssClass          = KD.utils.curry 'activity-related', viewOptions.cssClass
     viewOptions.comments         ?= yes
     viewOptions.itemClass       or= options.itemClass
+
     options.view                or= new KDListView viewOptions, data
     options.startWithLazyLoader  ?= yes
     options.lazyLoaderOptions     = partial : ''
     options.showHeader           ?= yes
+
     options.noItemFoundWidget    ?= new KDCustomHTMLView
       cssClass : "lazy-loader hidden"
       partial  : "There is no activity."
@@ -23,12 +25,29 @@

     super options, data

-    @resetList()
     @hiddenItems = []
-    @_state      = 'public'

-    groupController = KD.getSingleton("groupsController")
-    groupController.on "PostIsCreated", @bound "postIsCreated"
+
+  listActivities: (activities) ->
+
+    @hideLazyLoader()
+
+    return  unless activities.length > 0
+
+    @addItem activity for activity in activities
+
+
+
+
+
+
+
+
+
+
+
+
+  # LEGACY

   postIsCreated: (post) =>
     bugTag   = tag for tag in post.subject.tags when tag.slug is "bug"
@@ -53,51 +72,12 @@
     @bindItemEvents subject
     return subject

-  resetList:-> @lastItemTimeStamp = null

-  loadView:(mainView)->
-
-    data = @getData()
-    mainView.addSubView @activityHeader = new ActivityListHeader
-      cssClass : 'feeder-header clearfix'
-
-    @activityHeader.hide()  unless @getOptions().showHeader
-
-    @activityHeader.on "UnhideHiddenNewItems", =>
-      @unhideNewHiddenItems()
-
-    @emit "ready"
-    KD.getSingleton("activityController").clearNewItemsCount()
-
-    super

   isMine:(activity)->
     id = KD.whoami().getId()
     id? and id in [activity.originId, activity.anchor?.id]

-  listActivities:(activities)->
-    @hideLazyLoader()
-    return  unless activities.length > 0
-    activityIds = []
-    queue = []
-
-    activities.forEach (activity)=>
-      queue.push =>
-        @addItem activity
-        activityIds.push activity._id
-        queue.fin()
-
-    dash queue, =>
-
-      @checkIfLikedBefore activityIds
-
-      @lastItemTimeStamp or= Date.now()
-
-      for obj in activities
-        @bindItemEvents obj
-        objectTimestamp = (new Date(obj.meta.createdAt)).getTime()
-        if objectTimestamp < @lastItemTimeStamp
-          @lastItemTimeStamp = objectTimestamp

   checkIfLikedBefore:(activityIds)->
     KD.remote.api.CActivity.checkIfLikedBefore activityIds, (err, likedIds)=>


Social: don't convert `@` to mentions inside code blocks

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -100,7 +100,7 @@
     ]

     body = fn body for fn in fns
-    body = KD.utils.expandUsernames body, 'pre'
+    body = KD.utils.expandUsernames body, 'code'

     return body


Client: Activity: Added renderFirstMessageDate method to add message date and loading text to top of postpane

--- client/activity/lib/components/chatlist/index.coffee
+++ client/activity/lib/components/chatlist/index.coffee
@@ -21,6 +21,13 @@

     return count

+
+  renderFirstMessageDate: (count) ->
+
+    if @props.thread?.getIn ['flags', 'isMessagesLoading']
+
loading...

+    else if count
+
{count} more

   renderChildren: ->


Listen "ItemSelected" and "CredentialStatusChanged" event and handle them.

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -53,46 +53,44 @@

     @tabView.showPaneByIndex 0

-    @inputTitle            = new kd.FormViewWithFields fields:
-      title                :
-        cssClass           : 'template-title'
-        label              : 'Stack Template Title'
-        defaultValue       : title
-        nextElement        :
-          credentialStatus :
-            cssClass       : 'credential-status'
-            itemClass      : CredentialStatusView
-            stackTemplate  : stackTemplate
-
-    { @credentialStatus } = @inputTitle.inputs
-
-    @editorView      = new StackEditorView { delegate: this, content }
-
-    @outputView      = new OutputView
-
-    @outputView.add 'Welcome to Stack Template Editor'
-
-    @editorView.addSubView new kd.ButtonView
-      title    : 'Logs'
-      cssClass : 'solid compact showlogs-link'
-      callback : @outputView.bound 'raise'
-
-    # FIXME Not liked this ~ GG
-    @editorView.on 'click', @outputView.bound 'fall'
-
-    @cancelButton    = new kd.ButtonView
+    @createMainButtons()
+
+    @providersView.on 'ItemSelected', (credential) =>
+
+      # After adding credential, we are sharing it with the current
+      # group, so anyone in this group can use this credential ~ GG
+      { slug } = kd.singletons.groupsController.getCurrentGroup()
+
+      credential.shareWith { target: slug }, (err) =>
+        console.warn 'Failed to share credential:', err  if err
+        @stackTemplate.credentialStatus.setCredential credential
+
+
+    # TODO getrid off from these css properties ~ GG
+    @previewButton.setCss      'right', '110px'
+    @setAsDefaultButton.setCss 'right', '265px'
+
+    @stackTemplateView.on 'CredentialStatusChanged', (status) =>
+      if status is 'verified'
+      then @saveButton.enable()
+      else @saveButton.disable()
+
+
+  createMainButtons: ->
+
+    @addSubView @cancelButton  = new KDButtonView
       title          : 'Cancel'
       cssClass       : 'solid compact light-gray nav cancel'
       callback       : => @emit 'Cancel'

-    @saveButton      = new kd.ButtonView
+    @addSubView @saveButton    = new KDButtonView
       title          : 'Save & Test'
       cssClass       : 'solid compact green nav next'
       disabled       : yes
       loader         : yes
       callback       : @bound 'handleSave'

-    @previewButton   = new kd.ButtonView
+    @addSubView @previewButton = new KDButtonView
       title          : 'Template Preview'
       cssClass       : 'solid compact light-gray nav next'
       loader         : yes
@@ -101,21 +99,11 @@
         title        : "Generates a preview of this template
                         with your own account information."

-    @setAsDefaultButton = new kd.ButtonView
+    @addSubView @setAsDefaultButton = new KDButtonView
       title          : 'Set as Default for Team'
       cssClass       : 'solid compact nav next hidden'
       loader         : yes
       callback       : @bound 'handleSetDefaultTemplate'
-
-    # TODO getrid off from these css properties ~ GG
-    @previewButton.setCss      'right', '110px'
-    @setAsDefaultButton.setCss 'right', '265px'
-
-
-    @credentialStatus.on 'StatusChanged', (status) =>
-      if status is 'verified'
-      then @saveButton.enable()
-      else @saveButton.disable()

   handleSave: ->


client/redux: update customer module selectors

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/redux/modules/payment/customer.coffee
+++ client/app/lib/redux/modules/payment/customer.coffee
@@ -59,10 +59,18 @@
   }

+coupon = (state) -> state.customer?.discount?.coupon
+
+email = (state) -> state.customer?.email
+
+
 module.exports = _.assign reducer, {
   namespace: withNamespace()
   schema
   reducer
+
+  coupon, email
+
   load, create, update, remove
   LOAD, CREATE, UPDATE, REMOVE
 }


fix test

fix test

--- client/apps/edit/components/layout/test/index.coffee
+++ client/apps/edit/components/layout/test/index.coffee
@@ -95,9 +95,10 @@
   describe '#onFinished', ->

     it 'redirects to the list if the articles is saved', ->
+      $.ajaxStop = sinon.stub()
       @view.redirectToList = sinon.stub()
       @view.onFinished()
-      @view.article.trigger('change').trigger('sync')
+      $.ajaxStop.args[0][0]()
       @view.redirectToList.called.should.be.ok

   describe '#syncTitleTeaser', ->


Break next sibling scanner into its own function

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -54,7 +54,6 @@
     full_xpath = Util.xpathFromNode($(initialTarget), document)
     end_xpath = Util.xpathFromNode($(endTarget), document)

-
     data = {
       start: full_xpath
       startOffset: startIndexOffset + this.savedOffset
@@ -63,8 +62,33 @@
     }

     this.savedOffset = 0
-
     return data
+
+  scanForNextSibling: (initialTarget, currentTarget, offset_to_use, endIndex) ->
+    # here want to test:
+    # 1. is there a sibling element?
+    # 2. is there a parent element with a next sibling?
+    this.currentIndex = 0
+    this.currentSentence = 0
+
+    nextSibling = $(currentTarget).next()
+
+    if offset_to_use == endIndex + 1
+      initialTarget = nextSibling
+
+    if nextSibling != undefined and nextSibling.length != 0
+      this.selectSentence initialTarget, nextSibling
+    else
+      nextSibling = this.findNextJumpNode(currentTarget)
+
+      if offset_to_use == endIndex + 1
+        initialTarget = nextSibling
+
+      if nextSibling != undefined and nextSibling.length != 0
+        this.selectSentence initialTarget, nextSibling
+      else
+        # this needs to gracefully fall through
+        this.selectSentence initialTarget, nextSibling

   selectSentence: (initialTarget, currentTarget = undefined, force = false) ->

@@ -113,30 +137,7 @@
       data = this.packageData(initialTarget, currentTarget, endIndex, offset_to_use)
       this.anchorToPage(data)
     else
-      # here want to test:
-      # 1. is there a sibling element?
-      # 2. is there a parent element with a next sibling?
-      this.currentIndex = 0
-      this.currentSentence = 0
-
-      nextSibling = $(currentTarget).next()
-
-      if offset_to_use == endIndex + 1
-        initialTarget = nextSibling
-
-      if nextSibling != undefined and nextSibling.length != 0
-        this.selectSentence initialTarget, nextSibling
-      else
-        nextSibling = this.findNextJumpNode(currentTarget)
-
-        if offset_to_use == endIndex + 1
-          initialTarget = nextSibling
-
-        if nextSibling != undefined and nextSibling.length != 0
-          this.selectSentence initialTarget, nextSibling
-        else
-          # this needs to gracefully fall through
-          this.selectSentence initialTarget, nextSibling
+      this.scanForNextSibling initialTarget, currentTarget, offset_to_use, endIndex

   findASentence: (event = {}) =>
     if this.operational == false


fix bug in task-overview

fix bug in task-overview

--- app/assets/javascripts/admin/views/task/task_overview_view.coffee
+++ app/assets/javascripts/admin/views/task/task_overview_view.coffee
@@ -91,14 +91,15 @@
       data:
         start: start
         end: end
-    ).done( => @updateMinMaxHours() )
+    )
+    @updateMinMaxHours()

   getMinMaxHours : ->

     # This function calculates the min/max working hours of the users of the selected team
     if _.isEmpty(@minMaxHours)
-      selectedUsers = _.filter(@model.get("userInfos"), (userInfo) => @doDrawUser(userInfo.user))
+      selectedUsers = _.filter(@model.get("userInfos"), (userInfo) => @team in _.pluck(userInfo.user.teams, "team"))
       workingTimes = _.pluck(selectedUsers, "workingTime")

       if _.isEmpty(workingTimes) then workingTimes = [0]
@@ -117,8 +118,10 @@

   updateMinMaxHours : ->

-    @minMaxHours = {}
-    @getMinMaxHours()
+    @fetchPromise.done( =>
+      @minMaxHours = {}
+      @getMinMaxHours()
+    )

   updateSelectedTeam : ->
@@ -157,7 +160,7 @@
     TeamSelectionView = SelectionView.extend(
       teamChanged: =>
         @updateSelectedTeam()
-        @paintGraph()
+        @paintGraphDebounced()
     )

     # sort the collection so the default team is the first one


Removed unused callback

Removed unused callback

--- core/app/backbone/modules/notification_center.coffee
+++ core/app/backbone/modules/notification_center.coffee
@@ -9,21 +9,12 @@
       text: "{{{message}}} x"

     events:
-      "click .close": "removeSelf"
-
-    removeSelf: ->
-      @model.callback()
-      @model.destroy()
-
+      "click .close": -> @model.destroy()

   class NotificationCenter.AlertsView extends Marionette.CollectionView
     itemView: NotificationCenter.AlertView

   class NotificationCenter.Alert extends Backbone.Model
-    callback: ->
-      cb = @get('callback')
-      cb() if cb
-
   class NotificationCenter.Alerts extends Backbone.Collection

   FactlinkApp.addRegions
@@ -34,8 +25,8 @@

   FactlinkApp.alertsRegion.show alertsView

-  alert = (type,message,callback) ->
-    alerts.add new NotificationCenter.Alert message: message, type: type, callback: callback
+  alert = (type, message) ->
+    alerts.add new NotificationCenter.Alert message: message, type: type

   NotificationCenter.info    = -> alert('info',   arguments...)
   NotificationCenter.success = -> alert('success',arguments...)


no more update url for file://

no more update url for file://

--- www/js/audio.coffee
+++ www/js/audio.coffee
@@ -16,7 +16,6 @@
           _instance ?= new Player()

         connect: (url) ->
-          url = url.replace 'file://', ''
           @media = $cordovaMedia.newMedia url
           @media
             .getDuration()
@@ -25,7 +24,6 @@
                 duration: duration

         start: (url) ->
-          url = url.replace 'file://', ''
           @media = $cordovaMedia.newMedia url
           @media.play()


Fix JS bug

Fix JS bug

--- app/assets/javascripts/store/variants_prodline.js.coffee
+++ app/assets/javascripts/store/variants_prodline.js.coffee
@@ -1,4 +1,4 @@
 $ ->
   Spree.updateVariantPrice = (variant) ->
     variantPrice = variant.data('price')
-    ($ '.price.selling')[0].text(variantPrice) if variantPrice
+    ($ '.price.selling').first().text(variantPrice) if variantPrice


Do not alert admins about wrong url if accessing from cordova

--- packages/rocketchat-lib/client/lib/settings.coffee
+++ packages/rocketchat-lib/client/lib/settings.coffee
@@ -32,16 +32,17 @@
 RocketChat.settings.init()

 Meteor.startup ->
-	Tracker.autorun (c) ->
-		siteUrl = RocketChat.settings.get('Site_Url')
-		if not siteUrl or not Meteor.userId()?
-			return
+	if Meteor.isCordova is false
+		Tracker.autorun (c) ->
+			siteUrl = RocketChat.settings.get('Site_Url')
+			if not siteUrl or not Meteor.userId()?
+				return

-		if RocketChat.authz.hasRole(Meteor.userId(), 'admin') is false
+			if RocketChat.authz.hasRole(Meteor.userId(), 'admin') is false
+				return c.stop()
+
+			siteUrl = siteUrl.replace /\/$/, ''
+			if siteUrl isnt location.origin
+				toastr.warning TAPi18n.__('The_configured_URL_is_different_from_the_URL_you_are_accessing'), TAPi18n.__('Warning')
+
 			return c.stop()
-
-		siteUrl = siteUrl.replace /\/$/, ''
-		if siteUrl isnt location.origin
-			toastr.warning TAPi18n.__('The_configured_URL_is_different_from_the_URL_you_are_accessing'), TAPi18n.__('Warning')
-
-		return c.stop()


Team Resources: dynamically update time spent from creation date

--- client/admin/lib/views/resources/resourcelistitem.coffee
+++ client/admin/lib/views/resources/resourcelistitem.coffee
@@ -11,7 +11,6 @@
 StackAdminMessageModal = require 'app/stacks/stackadminmessagemodal'
 async                  = require 'async'
 whoami                 = require 'app/util/whoami'
-timeago                = require 'timeago'

 module.exports = class ResourceListItem extends kd.ListItemView
@@ -91,14 +90,14 @@

     try
       creationDate = new Date resource.createdAt
-      creationDate = timeago creationDate
     catch e

     { nickname } = resource.owner.profile
     @creationInfo = new kd.CustomHTMLView
-      partial  : "Created by #{nickname} #{creationDate ? ''}"
+      partial  : "Created by #{nickname} "
       cssClass : 'creation-info'
-
+    if creationDate
+      @creationInfo.addSubView new kd.TimeAgoView {}, creationDate

     @progressBar = new KDProgressBarView { initial : INITIAL_PROGRESS_VALUE }


opinionheader: fixing updatePartial call on undefined

--- client/app/CommonViews/opinions/opinionviewheader.coffee
+++ client/app/CommonViews/opinions/opinionviewheader.coffee
@@ -142,23 +142,23 @@

     if not @parent? or  @parent.constructor is DiscussionActivityOpinionView
       if @getData().opinionCount > 1
-        @allItemsLink.updatePartial "View #{@getData().opinionCount} Answers"+commentText
+        @allItemsLink?.updatePartial "View #{@getData().opinionCount} Answers"+commentText
       else if @getData().opinionCount is 1
-        @allItemsLink.updatePartial "View one Answer"+commentText
+        @allItemsLink?.updatePartial "View one Answer"+commentText
       else
-        @allItemsLink.updatePartial "No Answers yet#{if repliesCount > 0 then '. View '+repliesCount+' Comment'+(if repliesCount isnt 1 then 's' else '') else ''}"
+        @allItemsLink?.updatePartial "No Answers yet#{if repliesCount > 0 then '. View '+repliesCount+' Comment'+(if repliesCount isnt 1 then 's' else '') else ''}"
     else
       remainingOpinions = @getData().opinionCount-@getDelegate().items.length
       if (remainingOpinions)<@maxCommentToShow
           if remainingOpinions is 1
-            @allItemsLink.updatePartial "View remaining answer"
+            @allItemsLink?.updatePartial "View remaining answer"
           else if remainingOpinions > 1
-            @allItemsLink.updatePartial "View remaining #{remainingOpinions} #{@getOptions().itemTypeString}"
+            @allItemsLink?.updatePartial "View remaining #{remainingOpinions} #{@getOptions().itemTypeString}"
           else
             if @newAnswers is 0
-              @allItemsLink.updatePartial ""
+              @allItemsLink?.updatePartial ""
             else
-              @allItemsLink.updatePartial "View new answers"
+              @allItemsLink?.updatePartial "View new answers"

   pistachio:->
     """


use ? instead of or

use ? instead of or

--- client/landing/app/CommonViews/linkviews/linkview.coffee
+++ client/landing/app/CommonViews/linkviews/linkview.coffee
@@ -6,8 +6,8 @@
     data            or= fake : yes
     data.profile    or= {}

-    data.profile.firstName or= "a koding"
-    data.profile.lastName  or= "user"
+    data.profile.firstName ?= "a koding"
+    data.profile.lastName  ?= "user"

     super options, data


Update the donation slider as it is being dragged

--- src/scripts/pages/donate/donation-slider/donation-slider.coffee
+++ src/scripts/pages/donate/donation-slider/donation-slider.coffee
@@ -34,17 +34,33 @@
     value: 2 # Default donation setting

     events:
+      'mousedown input[type="range"]': 'onSlideStart'
       'change input[type="range"]': 'changeDonation'
       'submit form': 'onSubmit'
-
+
     initialize: (options = {}) ->
       super()
       @uuid = options.uuid
       @type = options.type or 'pdf'

+    onSlideStart: (e) ->
+      slider = e.currentTarget
+
+      onSlide = (e) => @changeDonation.call(@, e)
+
+      onSlideStop = (e) ->
+        # Remove event listeners when the user stops dragging the slider
+        slider.removeEventListener('mousemove', onSlide, false)
+        document.body.removeEventListener('mouseup', onSlideStop, false)
+
+      # Add event listeners when the user starts dragging the slider
+      slider.addEventListener('mousemove', onSlide, false)
+      document.body.addEventListener('mouseup', onSlideStop, false)
+
     changeDonation: (e) ->
       @value = $(e.currentTarget).val()
-      @render()
+      @$el.find('.donation-value').text("$#{donation[@value]}")
+      @$el.find('.donation-message').text("$#{message[@value]}")

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


don't crash if there is a syntax error in a code customization

--- @client/admin.coffee
+++ @client/admin.coffee
@@ -511,7 +511,10 @@
     @local.customization_filter ?= ''
     @local.property_changes ?= {}

-    code_properties = ( [k,v] for k,v of JSON.parse(subdomain.customizations) when typeof(v) == 'string' && v.startsWith(FUNCTION_IDENTIFIER) )
+    try
+      code_properties = ( [k,v] for k,v of JSON.parse(subdomain.customizations) when typeof(v) == 'string' && v.startsWith(FUNCTION_IDENTIFIER) )
+    catch error
+      console.error error

     DIV


add room

add room

--- scripts/sns-notifications.coffee
+++ scripts/sns-notifications.coffee
@@ -1,11 +1,12 @@
 module.exports = (robot) ->
   # Handle all notifications
   robot.on "sns:notification", (msg) ->
-    """
-    Received notification:
-      TopicArn:   #{msg.topicArn}
-      Topic:      #{msg.topic}
-      Message Id: #{msg.messageId}
-      Subject:    #{msg.subject}
-      Message:    #{msg.message}
-    """
+    robot.messageRoom("46691_curbot_stage_notifications@conf.hipchat.com",
+      """
+      Received notification:
+        TopicArn:   #{msg.topicArn}
+        Topic:      #{msg.topic}
+        Message Id: #{msg.messageId}
+        Subject:    #{msg.subject}
+        Message:    #{msg.message}
+      """)


Don't move the button when already visible

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -16,10 +16,15 @@
     @frame.sizeFrameToFitContent()

   setCoordinates: (top, left) =>
+    return if @_visible
+
     @_top = top
     @_left = left

   show: =>
+    return if @_visible
+
+    @_visible = true
     @setStateClass ''
     @frame.fadeIn()

@@ -30,7 +35,9 @@
       left: left + "px"
       top: top + "px"

-  hide: => @frame.fadeOut()
+  hide: =>
+    @frame.fadeOut()
+    @_visible = false

   destroy: => @frame.destroy()


publish update not updated

publish update not updated

--- server/code/model/dataset.coffee
+++ server/code/model/dataset.coffee
@@ -55,7 +55,7 @@
         boxes: boxes
         message: status.message
       env = process.env.NODE_ENV
-      Dataset.redisClient.publish "#{env}.cobalt.dataset.#{@box}.updated", message
+      Dataset.redisClient.publish "#{env}.cobalt.dataset.#{@box}.update", message
       callback err

   @countVisibleDatasets: (user, callback) ->


NEW react perf metrics

NEW react perf metrics

--- source/Art/Engine/DevTools/GlobalEpochStats.coffee
+++ source/Art/Engine/DevTools/GlobalEpochStats.coffee
@@ -15,7 +15,7 @@
   class GlobalEpochStat extends Foundation.BaseClass
     constructor: (@sampleTime, @total, @sampleSet) ->

-    @statFields:  statFields  = ["total", "draw", "aim", "aimLayout", "aimTL", "aimRR", "react", "reactAim", "reactLC", "reactRender", "event", "flux"]
+    @statFields:  statFields  = ["total", "draw", "aim", "aimLayout", "aimTL", "aimRR", "react", "reactAim", "reactUpdate", "reactRender", "event", "flux"]
     @statColors: statColors =
       total:  "gray"
       draw:   "#39c"
@@ -25,7 +25,7 @@
       aimRR:        aimColor.withLightness aimColor.lightness * .7
       react:        reactColor
       reactAim:     reactColor.withLightness reactColor.lightness *.9
-      reactLC:      reactColor.withLightness reactColor.lightness *.8
+      reactUpdate:      reactColor.withLightness reactColor.lightness *.8
       reactRender:  reactColor.withLightness reactColor.lightness *.7
       event:  "#ff6347"
       flux:   "#d936a3"


js => coffee

js => coffee

--- lib/assets/javascripts/active_admin_editor.js.coffee
+++ lib/assets/javascripts/active_admin_editor.js.coffee
@@ -1,24 +1,24 @@
 loadHtmlEditor = ->
-  mainContent = $("#edit_page")
-  mainContent = $("#new_page")  unless mainContent[0]
+  mainContent = $('#edit_page')
+  mainContent = $('#new_page')  unless mainContent[0]
   return  unless mainContent[0]
-
-  mainContent.append "

"

-  pageContent = $("#page_content").val()
+  mainContent.append '

'

-  editor = ace.edit("html_editor")
-  editor.setTheme "ace/theme/textmate"
-  editor.getSession().setMode "ace/mode/html"
+  pageContent = $('#page_content').val()
+
+  editor = ace.edit('html_editor')
+  editor.setTheme 'ace/theme/textmate'
+  editor.getSession().setMode 'ace/mode/html'
   editor.setValue pageContent

-  $("#html_editor").closest("form").submit ->
+  $('#html_editor').closest('form').submit ->
     editorContent = editor.getValue()

-    $("#page_content").val editorContent
+    $('#page_content').val editorContent
     return

   return

 $(document).ready loadHtmlEditor
-$(document).on "page:load", loadHtmlEditor
+$(document).on 'page:load', loadHtmlEditor


staticpage: fix announcement page returns 500 bug

--- servers/lib/crawler/index.coffee
+++ servers/lib/crawler/index.coffee
@@ -76,7 +76,7 @@

 fetchGroupContent = (models, options, callback) ->
   {entrySlug, client, page} = options
-  entrySlug ||= "koding"
+  entrySlug or= "koding"
   {JGroup} = models

   options.channelName = "public"
@@ -107,17 +107,20 @@

   {JGroup} = models

-  options.channelName = "koding"
+  options.channelName = "changelog"

-  # TODO change this slug after groups are implemented
-  JGroup.one slug: "koding", (err, group) ->
-    return callback err  if err
-    return callback notFoundError "group"  unless group
+  fetchGuestUserSession models, (err, sessionToken) ->
+    return callback err if err?

-    options.channelId = group.socialApiAnnouncementChannelId
-    options.route = "Announcement"
-    options.contentType = "post"
-    feed.createFeed models, options, callback
+    JGroup.one slug: "koding", (err, group) ->
+      return callback err  if err
+      return callback notFoundError "group"  unless group
+
+      options.channelId = group.socialApiAnnouncementChannelId
+      options.route        = "Announcement"
+      options.contentType  = "post"
+      options.sessionToken = sessionToken
+      feed.createFeed models, options, callback

 fetchContent = (models, options, callback) ->


Bug [#42056455]

Bug [#42056455]

Fixes the issue where the project name doesn't autocomplete for a client
that already exists on the staffplan.

--- app/assets/javascripts/views/staffplans/assignment.js.coffee
+++ app/assets/javascripts/views/staffplans/assignment.js.coffee
@@ -51,7 +51,10 @@
       isNewClient = if @client() == undefined then true else @client().isNew()
       @$el.html StaffPlan.Templates.StaffPlans.assignment_new
         showClientInput: isNewClient
-
+
+      @$el.find('input[data-model=Project]').typeahead
+        source: if isNewClient then StaffPlan.projects.pluck('name') else @client().getProjects().pluck('name')
+        items: 12
       setTimeout =>
         $('html, body').stop().animate
           scrollTop: (@$el.offset().top - 300)


modelBinding inherited from UserFormView

--- app/assets/javascripts/views/users/users_edit_view.js.coffee
+++ app/assets/javascripts/views/users/users_edit_view.js.coffee
@@ -1,6 +1,5 @@
 Bandango.UsersEditView = Bandango.UserFormView.extend
   templateName: "users/new"
-  modelBinding: "controller.model"

 # editing
   successEditing: (user) ->


profile: removed superfluous calculations from renderhomepage

--- workers/social/lib/social/models/account/render-homepage.coffee
+++ workers/social/lib/social/models/account/render-homepage.coffee
@@ -7,12 +7,6 @@
   lastName  ?= 'User'
   nickname  ?= ''
   about     ?= ''
-
-  sortedTags = []
-  skillTags ?= {}
-  for i in [0...skillTags.length]
-    sortedTags.push skillTags[i]
-  sortedTags.sort()

   """
   <!DOCTYPE html>


Provider Amazon:[backend] create and fetchAvailable implementations

--- workers/social/lib/social/models/computeproviders/amazon.coffee
+++ workers/social/lib/social/models/computeproviders/amazon.coffee
@@ -3,4 +3,68 @@
 module.exports = class Amazon extends ProviderInterface

   @ping = (client, callback)->
-    callback null, "AWS RULEZ #{ client.connection.delegate.profile.nickname }!"+    callback null, "AWS RULEZ #{ client.connection.delegate.profile.nickname }!"
+
+  @create = (client, options, callback)->
+
+    { credential, name } = options
+
+    credential.fetchData (err, credData)->
+
+      if err?
+        return new KodingError "Failed to fetch credential"
+
+      callback null,
+        {
+          "variables": {
+            "aws_access_key": credData.meta.accessKeyId
+            "aws_secret_key": credData.meta.secretAccessKey
+          },
+          "builders": [{
+            "type": "amazon-ebs",
+            "access_key": "{{user `aws_access_key`}}",
+            "secret_key": "{{user `aws_secret_key`}}",
+            "region": credData.meta.region,
+            "source_ami": "ami-de0d9eb7",
+            "instance_type": name,
+            "ssh_username": "ubuntu",
+            "ami_name": "packer-example {{timestamp}}"
+          }]
+        }
+
+  @fetchAvailable = (client, options, callback)->
+
+    callback null, [
+      {
+        name  : "m3.medium"
+        title : "M3 medium"
+        spec  : {
+          cpu : 1, ram: 3.75, storage: 4
+        }
+        price : "$0.070 per Hour"
+      }
+      {
+        name  : "m3.large"
+        title : "M3 large"
+        spec  : {
+          cpu : 2, ram: 7.5, storage: 32
+        }
+        price : "$0.140 per Hour"
+      }
+      {
+        name  : "m3.xlarge"
+        title : "M3 xlarge"
+        spec  : {
+          cpu : 4, ram: 15, storage: 80
+        }
+        price : "$0.280 per Hour"
+      }
+      {
+        name  : "m3.2xlarge"
+        title : "M3 2xlarge"
+        spec  : {
+          cpu : 8, ram: 30, storage: 160
+        }
+        price : "$0.560 per Hour"
+      }
+    ]


Added x,y velocity to quoData data

Added x,y velocity to quoData data

Useful for providing velocity sensitive code.

--- source/quo.gestures.swipe.coffee
+++ source/quo.gestures.swipe.coffee
@@ -18,10 +18,14 @@

   handler : do (base = Quo.Gestures) ->
     GAP = (if window.devicePixelRatio >= 2 then 15 else 20)
+    VELOCITY_SMOOTHING = .5
     _target = null
     _start = null
     _start_axis = null
     _last = null
+    is_first = _last is null
+    _timeStamp = new Date().getTime()
+    dt = (if is_first then 1 else _timeStamp - _last.timeStamp)

     start = (target, data) ->
       if data.length is 1
@@ -31,9 +35,10 @@

     move = (target, data) ->
       if data.length is 1
-        delta = x: (data[0].x - _start.x), y: (data[0].y - _start.y)
-        is_first = _last is null
-        _last = x: data[0].x, y: data[0].y, delta: delta
+        delta = x: (data[0].x - _start.x), y: (data[0].y - _start.y),
+          vx: (if is_first then (data[0].x - _start.x) / dt else _last.delta.vx * VELOCITY_SMOOTHING + ((data[0].x - _last.x) / dt) * (1 - VELOCITY_SMOOTHING)),
+          vy: (if is_first then (data[0].x - _start.y) / dt else _last.delta.vy * VELOCITY_SMOOTHING + ((data[0].y - _last.y) / dt) * (1 - VELOCITY_SMOOTHING))
+        _last = x: data[0].x, y: data[0].y, timeStamp: _timeStamp, delta: delta
         _check(true, is_first)
       else
         _last = null


#271 - Fix bug with shortcuts happening while an input is focused

--- source/scripts/views/keys.coffee
+++ source/scripts/views/keys.coffee
@@ -26,7 +26,14 @@

   keyup: (event)  =>

-    @handleKeyCode event.keyCode
+    keyCode = event.keyCode
+
+    if @focused
+      if keyCode is keys.escape
+        @input.blur()
+      return true
+
+    @handleKeyCode keyCode

   handleKeyCode: (keyCode) =>

@@ -35,17 +42,14 @@
       when keys.escape

         # Escape
-        # - blur inputs
         # - hide modals
         # - collapse tasks

-        if @focused
-          return @input.blur()
+        if Views.modal.displayed
+          Views.modal.current.hide()

-        if Views.modal.displayed
-          return Views.modal.current.hide()
-
-        Views.tasks.collapse()
+        else
+          Views.tasks.collapse()

       when keys.n


fix "topic changed" messages with incorrect timestamp

--- packages/rocketchat-importer-slack/server/SlackImporter.coffee
+++ packages/rocketchat-importer-slack/server/SlackImporter.coffee
@@ -219,9 +219,9 @@

 													RocketChat.sendMessage botUser, msgObj, room
 												else if message.subtype is 'channel_purpose'
-													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.purpose, @getRocketUser(message.user)
+													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.purpose, @getRocketUser(message.user), { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) }
 												else if message.subtype is 'channel_topic'
-													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.topic, @getRocketUser(message.user)
+													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.topic, @getRocketUser(message.user), { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) }
 												else if message.subtype is 'file_share' && message.file.url_private_download isnt undefined
 													details =
 														name: message.file.name


Don't try to parse an upload if it doesn't contain a file. Fixes #2230

--- packages/rocketchat-importer-slack/server/SlackImporter.coffee
+++ packages/rocketchat-importer-slack/server/SlackImporter.coffee
@@ -233,13 +233,14 @@
 													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.purpose, @getRocketUser(message.user), { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) }
 												else if message.subtype is 'channel_topic'
 													RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.topic, @getRocketUser(message.user), { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) }
-												else if message.subtype is 'file_share' && message.file.url_private_download isnt undefined
-													details =
-														name: message.file.name
-														size: message.file.size
-														type: message.file.mimetype
-														rid: room._id
-													@uploadFile details, message.file.url_private_download, @getRocketUser(message.user), room, new Date(parseInt(message.ts.split('.')[0]) * 1000)
+												else if message.subtype is 'file_share'
+													if message.file?.url_private_download isnt undefined
+														details =
+															name: message.file.name
+															size: message.file.size
+															type: message.file.mimetype
+															rid: room._id
+														@uploadFile details, message.file.url_private_download, @getRocketUser(message.user), room, new Date(parseInt(message.ts.split('.')[0]) * 1000)
 												else
 													if not missedTypes[message.subtype] and not ignoreTypes[message.subtype]
 														missedTypes[message.subtype] = message