Initialise taxon concept single selection with select2

--- app/assets/javascripts/admin/admin_in_place_editor.js.coffee
+++ app/assets/javascripts/admin/admin_in_place_editor.js.coffee
@@ -197,7 +197,8 @@
     )

   initSelect2Inputs: () ->
-    $('.taxon-concept-multiple').select2($.extend(window.defaultTaxonSelect2Options,window.multiTaxonSelect2Options))
+    $('.taxon-concept').select2(window.defaultTaxonSelect2Options)
+    $('.taxon-concept-multiple').select2($.extend({}, window.defaultTaxonSelect2Options,window.multiTaxonSelect2Options))

 class TaxonConceptsEditor extends AdminEditor
   init: () ->


Fixed edit form validation.

Fixed edit form validation.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -390,7 +390,7 @@
           bind: {
             name:     'ticket_article_type_id'
             relation: 'TicketArticleType'
-            value:    ["email"]
+            value:    ['email']
           },
           change: {
             action: 'show'
@@ -448,7 +448,17 @@
     ticket = App.Ticket.retrieve( @ticket.id )

     @log 'notice', 'update', params, ticket
-    article_type = App.TicketArticleType.find( params['ticket_article_type_id'] )
+
+    # find sender_id
+    if @isRole('Customer')
+      sender       = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
+      article_type = App.TicketArticleType.findByAttribute( 'name', 'web' )
+      params.ticket_article_type_id   = article_type.id
+      params.ticket_article_sender_id = sender.id
+    else
+      sender       = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
+      article_type = App.TicketArticleType.find( params['ticket_article_type_id'] )
+      params.ticket_article_sender_id = sender.id

     # update ticket
     ticket_update = {}
@@ -465,6 +475,7 @@
       alert( App.i18n.translateContent('Title needed') )
       return

+    # validate email params
     if article_type.name is 'email'

       # check if recipient exists
@@ -483,9 +494,8 @@
       attachmentTranslatedRegExp = new RegExp( attachmentTranslated, 'i' )
       if params['body'].match(/attachment/i) || params['body'].match( attachmentTranslatedRegExp )
         if !confirm( App.i18n.translateContent('You use attachment in text but no attachment is attached. Do you want to continue?') )
+          @autosaveStart()
           return
-        else
-          @autosaveStart()

     ticket.load( ticket_update )
     @log 'notice', 'update ticket', ticket_update, ticket
@@ -493,59 +503,54 @@
     # disable form
     @formDisable(e)

+    # validate ticket
     errors = ticket.validate()
     if errors
       @log 'error', 'update', errors
       @formEnable(e)
       @autosaveStart()
+      return
+
+    # validate article
+    if params['body']
+      article = new App.TicketArticle
+      params.from      = @Session.get( 'firstname' ) + ' ' + @Session.get( 'lastname' )
+      params.ticket_id = ticket.id
+      params.form_id   = @form_id
+
+      if !params['internal']
+        params['internal'] = false
+
+      @log 'notice', 'update article', params, sender
+      article.load(params)
+      errors = article.validate()
+      if errors
+        @log 'error', 'update article', errors
+        @formEnable(e)
+        @autosaveStart()
+        return

     ticket.save(
       success: (r) =>

-        # create article
-        if params['body']
-          article = new App.TicketArticle
-          params.from      = @Session.get( 'firstname' ) + ' ' + @Session.get( 'lastname' )
-          params.ticket_id = ticket.id
-          params.form_id   = @form_id
-
-          if !params['internal']
-            params['internal'] = false
-
-          # find sender_id
-          if @isRole('Customer')
-            sender = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
-            type   = App.TicketArticleType.findByAttribute( 'name', 'web' )
-            params['ticket_article_type_id'] = type.id
-          else
-            sender = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
-          params.ticket_article_sender_id = sender.id
-          @log 'notice', 'update article', params, sender
-          article.load(params)
-          errors = article.validate()
-          if errors
-            @log 'error', 'update article', errors
-
-          # reset form after save
-          App.TaskManager.update( @task_key, { 'state': {} })
-
+        # reset form after save
+        if article
           article.save(
             success: (r) =>
               @ui.fetch( ticket.id, true )
+
+              # reset form after save
+              App.TaskManager.update( @task_key, { 'state': {} })
             error: (r) =>
               @log 'error', 'update article', r
           )
         else
+
           # reset form after save
           App.TaskManager.update( @task_key, { 'state': {} })

           @ui.fetch( ticket.id, true )
     )
-
-#    errors = article.validate()
-#    @log 'error new', errors
-#    @formValidate( form: e.target, errors: errors )
-    return false

   reset: (e) =>
     e.preventDefault()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/javascripts/_cornerstone/array_extensions.js.coffee
+++ source/javascripts/_cornerstone/array_extensions.js.coffee
@@ -432,7 +432,7 @@

     while i < end
       result.push(this[i.mod(this.length)])
-      i++
+      i += 1

     return result
   else


Add within

Add within

--- app/models/place.coffee
+++ app/models/place.coffee
@@ -33,7 +33,7 @@
         types.push t.name
     conv = new Converter([query.Start, query.End])
     polygon conv.getFlat()[0], conv.getFlat()[1], query.Radius, (values) =>
-      @where('kind').in(types).exec (err, values) =>
+      @where('kind').in(types).where('coord').within({polygon:values}).exec (err, values) =>
         cb values

 module.exports = Place


Pause the game

Pause the game

--- app/scripts/game.coffee
+++ app/scripts/game.coffee
@@ -2,6 +2,14 @@
   paused: no
   togglePause: ->
     @paused = !@paused
+    if @paused
+      Crafty('Delay').each -> @pauseDelays()
+      Crafty('Tween').each -> @pauseTweens()
+      Crafty('Particles').each -> @pauseParticles()
+    else
+      Crafty('Delay').each -> @resumeDelays()
+      Crafty('Tween').each -> @resumeTweens()
+      Crafty('Particles').each -> @resumeParticles()
     Crafty.trigger('GamePause', @paused)

   # Initialize and start our game


naming

naming

--- app/scripts/test.coffee
+++ app/scripts/test.coffee
@@ -1 +1 @@
-$('#app').html('
gulp-htccs-webapp
')
+$('#app').html('
htccs-webapp
')


Use the deprecation warning's recommendation

--- apps/auth/routes.coffee
+++ apps/auth/routes.coffee
@@ -40,4 +40,4 @@
   res.redirect sanitizeRedirect(url)

 @logout = (req, res, next) ->
-  res.send 200, { msg: "success" }
+  res.status(200).send msg: 'success'


[gardening] remove unused polyfill

[gardening] remove unused polyfill

--- assets/js/Zonard.coffee
+++ assets/js/Zonard.coffee
@@ -1,12 +1,3 @@
-# polyfill:
-# We check what the rotation transformation name is
-# in the browser
-@transformName = null
-d = document.createElement('div')
-for b in ['transform', 'webkitTransform', "MozTransform", 'msTransform', "OTransform"] when d.style[b]?
-  @transformName = b
-d = null
-
 Cards = 'n,s,e,w,nw,ne,se,sw'.split ','
 ordCards = 's,sw,w,nw,n,ne,e,se'.split ','


Use Buffer for fs.write

Use Buffer for fs.write

--- src/app.coffee
+++ src/app.coffee
@@ -85,7 +85,8 @@
 	connectionsFromFile.push({ ssid, passphrase })
 	fs.openAsync(connectionFile, 'w')
 	.tap (fd) ->
-		fs.writeAsync(fd, JSON.stringify(connectionsFromFile))
+		buf = new Buffer(JSON.stringify(connectionsFromFile))
+		fs.writeAsync(fd, buf, 0, buf.length, null)
 	.tap (fd) ->
 		fs.fsyncAsync(fd)
 	.then (fd) ->


coffeescript :rolleyes:

coffeescript :rolleyes:

--- src/bgg.coffee
+++ src/bgg.coffee
@@ -14,11 +14,9 @@
 # Author:
 #   Bart Dorsey[@<org>]

-// all options are optional
-var options = {
-  timeout: 10000, // timeout of 10s (5s is the default)
-
-  // see https://github.com/cujojs/rest/blob/master/docs/interceptors.md#module-rest/interceptor/retry
+options = {
+  timeout: 10000,
+
   retry: {
     initial: 100,
     multiplier: 2,
@@ -26,7 +24,7 @@
   }
 }

-var bgg = require('bgg')(options);
+bgg = require('bgg')(options)

 module.exports = (robot) ->
   robot.respond /bgg search (.*)/, (msg) ->


Uncomment grunt.task.run call

Uncomment grunt.task.run call

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -109,4 +109,4 @@
     dependencies = ['compile', "generate-license:save"]
     dependencies.push('copy-info-plist') if process.platform is 'darwin'
     dependencies.push('set-exe-icon') if process.platform is 'win32'
-    # grunt.task.run(dependencies...)
+    grunt.task.run(dependencies...)


No donkeys, one alien

No donkeys, one alien

--- src/generation/maps.coffee
+++ src/generation/maps.coffee
@@ -70,11 +70,11 @@
 		{x, y} = generatePos map
 		creatureGen.generateStrangeGoo x, y

-	creatures.push (
-		for i in [1..3]
-			{x, y} = generatePos map
-			creatureGen.generateViolentDonkey x, y
-	)...
+	# creatures.push (
+	# 	for i in [1..3]
+	# 		{x, y} = generatePos map
+	# 		creatureGen.generateViolentDonkey x, y
+	# )...

 	mapItems = for i in [1..3]
 		{x, y} = generatePos map
@@ -84,6 +84,10 @@

 	map.addEntity creatures...
 	map.addEntity mapItems...
+
+	for i in [1..1]
+		{x, y} = generatePos map
+		map.addEntity (creatureGen.generateTinyAlien x, y)

 	# exits and entrances (incl. stairs)
 	map.positions =


Uneeded interpolation

Uneeded interpolation

--- resources/assets/coffee/react/_components/click-to-copy.coffee
+++ resources/assets/coffee/react/_components/click-to-copy.coffee
@@ -59,6 +59,6 @@
       href: '#'
       onClick: @click
       title: osu.trans('common.buttons.click_to_copy')
-      "#{@props.label ? @props.value}"
+      @props.label ? @props.value
       i
         className: "fas fa-paste #{bn}__icon"


render dash instead

render dash instead

--- resources/assets/coffee/react/beatmapset-page/score-top.coffee
+++ resources/assets/coffee/react/beatmapset-page/score-top.coffee
@@ -28,7 +28,7 @@
     .map (m) -> "#{bn}--#{m}"
     .join ' '

-  position = if props.position? then "##{props.position}" else ''
+  position = if props.position? then "##{props.position}" else '-'

   div className: "#{bn} #{topClasses}",
     div className: "#{bn}__section",


Colors: Fix blue in Windows (special case in chalk). Fix yellow in browser. Faster general case in getStructured(). Warn for unknown codes

--- src/gral/ansiColors.coffee
+++ src/gral/ansiColors.coffee
@@ -3,15 +3,16 @@
   30: 'color: black'
   31: 'color: red'
   32: 'color: green'
-  33: 'color: yellow'
+  33: 'color: orange'
   34: 'color: blue'
+  94: 'color: blue'
   35: 'color: magenta'
   36: 'color: cyan'
   37: 'color: lightgrey'
   40: 'color: white;background-color: black'
   41: 'color: white;background-color: red'
   42: 'color: white;background-color: green'
-  43: 'color: white;background-color: yellow'
+  43: 'color: white;background-color: orange'
   44: 'color: white;background-color: blue'
   45: 'color: white;background-color: magenta'
   46: 'color: white;background-color: cyan'
@@ -26,15 +27,16 @@
   30: {color: 'black'}
   31: {color: 'red'}
   32: {color: 'green'}
-  33: {color: 'yellow'}
+  33: {color: 'orange'}
   34: {color: 'blue'}
+  94: {color: 'blue'}
   35: {color: 'magenta'}
   36: {color: 'cyan'}
   37: {color: 'lightgrey'}
   40: {color: 'white', backgroundColor: 'black'}
   41: {color: 'white', backgroundColor: 'red'}
   42: {color: 'white', backgroundColor: 'green'}
-  43: {color: 'white', backgroundColor: 'yellow'}
+  43: {color: 'white', backgroundColor: 'orange'}
   44: {color: 'white', backgroundColor: 'blue'}
   45: {color: 'white', backgroundColor: 'magenta'}
   46: {color: 'white', backgroundColor: 'cyan'}
@@ -65,7 +67,7 @@
   return null if not str?
   regex = /\u001b\[(\d+)*m/gi
   tokens = str.split regex
-  return '' if not tokens.length
+  return [str] if tokens.length <= 1
   lev = 0
   tmp = [{lev, style: '', txt: tokens[0]}]
   for idx in [1...tokens.length] by 2
@@ -75,7 +77,10 @@
       style = {}
       lev--
     else
-      style = MAP_ADD_STYLE_REACT[code] ? {}
+      style = MAP_ADD_STYLE_REACT[code]
+      if not style?
+        console.warn "Unknown ANSI code #{code} in string #{str}"
+        style = {}
       lev++
     tmp.push {lev, style, txt}
   out = [


For checkouts/details/status line, change 'Remaining renewals' to 'Renewal used'.

--- src/account/checkouts.coffee
+++ src/account/checkouts.coffee
@@ -34,7 +34,7 @@

 			<%= circ_type %>
 			Due date
-			Remaining renewals
+			Renewal used

 	'''


Firewall: Updated copy.

Firewall: Updated copy.

--- client/Environments/views/scene/environmentrulecontainer.coffee
+++ client/Environments/views/scene/environmentrulecontainer.coffee
@@ -5,7 +5,7 @@
     new Promise (resolve, reject) ->
       KD.remote.api.JProxyFilter.fetch {}, (err, filters) ->
         if err or not filters or filters.length is 0
-          warn "Failed to fetch domains", err  if err
+          warn "Failed to fetch filters", err  if err
           return resolve []

         filter.title = filter.name  for filter in filters


privatemessage: use leave method for removing a user from a channel instead removeParticipants

--- client/Social/Activity/views/privatemessage/settingsview.coffee
+++ client/Social/Activity/views/privatemessage/settingsview.coffee
@@ -41,12 +41,13 @@
     @prepareModal @leaveModal

     channelId = @getData().getId()
-    accountIds = [ KD.whoami().getId() ]

-    channel.removeParticipants {channelId, accountIds}, (err) =>
+    {channel} = KD.singletons.socialapi
+    channel.leave {channelId}, (err) =>
       return @handleModalError @leaveModal, err if err?

       @leaveModal.destroy()
+      KD.singletons.router.handleRoute '/Activity/Public'

   prepareModal: (modal) ->
     confirmButton = modal.buttons['OK']


Remove unused lines.

Remove unused lines.

--- client/account/lib/views/accountcredentiallistcontroller.coffee
+++ client/account/lib/views/accountcredentiallistcontroller.coffee
@@ -236,11 +236,6 @@
         username        :
           label         : 'User'
           placeholder   : 'Enter group slug or username'
-          # type          : "hidden"
-          # nextElement   :
-          #   userWrapper :
-          #     itemClass : KDView
-          #     cssClass  : "completed-items"
         owner           :
           label         : 'Give ownership'
           itemClass     : KodingSwitch
@@ -285,33 +280,4 @@
             view.form.destroy()
             @loadItems()

-
-    # {fields, inputs, buttons} = view.form
-
-    # @userController       = new KDAutoCompleteController
-    #   form                : view.form
-    #   name                : "username"
-    #   itemClass           : MemberAutoCompleteItemView
-    #   itemDataPath        : "profile.nickname"
-    #   outputWrapper       : fields.userWrapper
-    #   selectedItemClass   : MemberAutoCompletedItemView
-    #   listWrapperCssClass : "users"
-    #   submitValuesAsText  : yes
-    #   dataSource          : (args, callback) =>
-    #     {inputValue} = args
-    #     if /^@/.test inputValue
-    #       query = 'profile.nickname': inputValue.replace /^@/, ''
-    #       remote.api.JAccount.one query, (err, account) =>
-    #         if not account
-    #           @userController.showNoDataFound()
-    #         else
-    #           callback [account]
-    #     else
-    #       remote.api.JAccount.byRelevance inputValue, {}, (err, accounts) ->
-    #         callback accounts
-
-    # fields.username.addSubView userRequestLineEdit = @userController.getView()
-    # @userController.on "ItemListChanged", (count) ->
-    #   userRequestLineEdit[if count is 0 then 'show' else 'hide']()
-
     view.addSubView view.form


activity: chatinput: fix for failed tests

--- client/activity/lib/flux/chatinput/tests/channel/getters.coffee
+++ client/activity/lib/flux/chatinput/tests/channel/getters.coffee
@@ -12,14 +12,13 @@
 describe 'ChatInputChannelGetters', ->

   channels = [
-    { id : 'public', name : 'public', typeConstant: 'group' }
     { id : 'qwerty', name : 'qwerty', typeConstant: 'topic' }
     { id : 'qwerty2', name : 'qwerty2', typeConstant: 'topic' }
     { id : 'qwerty3', name : 'qwerty3', typeConstant: 'privatemessage' }
     { id : 'koding', name : 'koding', typeConstant: 'topic' }
     { id : 'whoa', name : 'whoa', typeConstant: 'privatemessage' }
   ]
-  publicChannels = [ channels[1], channels[2], channels[4] ]
+  publicChannels = [ channels[0], channels[1], channels[3] ]
   stateId = '123'
   config  = {
     component       : React.Component
@@ -80,14 +79,14 @@
       @reactor.dispatch ChatInputActionTypes.SET_DROPBOX_QUERY_AND_CONFIG, { stateId, query : 'qwerty', config }
       items = @reactor.evaluateToJS getters.dropboxChannels stateId

-      filteredItems = [ channels[1], channels[2] ]
+      filteredItems = [ channels[0], channels[1] ]

       expect(items).toEqual filteredItems

   describe '#channelsSelectedIndex', ->

-     it 'returns -1 if channels are empty', ->
+    it 'returns -1 if channels are empty', ->

       { getters } = ChatInputFlux

@@ -97,7 +96,7 @@
       expect(index).toBe -1

-   it 'returns 0 by default', ->
+    it 'returns 0 by default', ->

       { getters } = ChatInputFlux

@@ -171,4 +170,4 @@

       selectedItem = @reactor.evaluateToJS getters.channelsSelectedItem stateId

-      expect(selectedItem).toEqual channels[2]
+      expect(selectedItem).toEqual channels[1]


use screenwrapper

use screenwrapper

--- client/lanes/access/screens/user-management/UserManagement.cjsx
+++ client/lanes/access/screens/user-management/UserManagement.cjsx
@@ -29,7 +29,7 @@
             />

     render: ->
-

+        <LC.ScreenWrapper identifier="user-management">

Users Management

             <LC.Grid
                 editorProps={{syncImmediatly: true}}
@@ -38,4 +38,4 @@
                 editor={Lanes.Access.Screens.UserManagement.Editor}
                 columEditors={@editors()}
                 ref="grid"/>
-

+        </LC.ScreenWrapper>


tighten up link validation

tighten up link validation

--- nodeko.coffee
+++ nodeko.coffee
@@ -103,7 +103,7 @@
     @ensurePermitted person, =>
       attributes: @params.post
       delete attributes.password if attributes.password is ''
-      attributes.link: '' unless /^https?:\/\//.test attributes.link
+      attributes.link: '' unless /^https?:\/\/.+\./.test attributes.link
       person.update attributes
       person.save (error, resp) =>
         @redirectToTeam person


Log User.add

Log User.add

--- server/code/model/user.coffee
+++ server/code/model/user.coffee
@@ -172,6 +172,7 @@

   # Add and email the user
   @add: (opts, callback) ->
+    console.log "User.add"
     recurlyRand = String(Math.random()).replace('0.', '')
     newUser =
       shortName: opts.newUser.shortName


Reenable source maps.

Reenable source maps.

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -137,13 +137,17 @@

 		less:
 			app:
-				# options:
-				# 	sourceMap: true
+				options:
+					sourceMap: true
+					sourceMapFilename: "public/stylesheets/style.map"
+					sourceMapBasepath: "public/stylesheets"
 				files:
 					"public/stylesheets/style.css": "public/stylesheets/style.less"
 			ol:
-				# options:
-				# 	sourceMap: true
+				options:
+					sourceMap: true
+					sourceMapFilename: "public/stylesheets/ol-style.map"
+					sourceMapBasepath: "public/stylesheets"
 				files:
 					"public/stylesheets/ol-style.css": "public/stylesheets/ol-style.less"


Replacing taxon directive

Replacing taxon directive

--- app/assets/javascripts/darkswarm/directives/taxon_selector.js.coffee
+++ app/assets/javascripts/darkswarm/directives/taxon_selector.js.coffee
@@ -1,5 +1,6 @@
 Darkswarm.directive "taxonSelector",  (FilterSelectorsService)->
   restrict: 'E'
+  replace: true
   scope:
     objects: "&"
     results: "="


Added toggleExsitingAddress() to address-selection

--- app/assets/javascripts/sprangular/directives/addressSelection.coffee
+++ app/assets/javascripts/sprangular/directives/addressSelection.coffee
@@ -16,3 +16,6 @@
           existing.same($scope.address)

         $scope.existingAddress = true if found
+
+    $scope.toggleExistingAddress = ->
+      $scope.existingAddress = !$scope.existingAddress


Cleaned up css

Cleaned up css

--- src/css.coffee
+++ src/css.coffee
@@ -7,7 +7,7 @@

 exports.addStyle "
-.uilayer {
+.framer {
 	display: block;
 	visibility: visible;
 	position: absolute;
@@ -18,16 +18,7 @@
 	opacity:1;
 	box-sizing: border-box;
 	-webkit-box-sizing: border-box;
-}
-.uilayer.textureBacked {
-	-webkit-transform: matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
 	-webkit-transform-origin: 50% 50% 0%;
 	-webkit-backface-visibility: hidden;
 	-webkit-transform-style: flat;
-}
-.uilayer.animated {
-	-webkit-transition-duration: 500ms;
-	-webkit-transition-timing-function: linear;
-	-webkit-transition-delay: 0;
-	-webkit-transition-property: none;
 }"


pricing: start using footer view

pricing: start using footer view

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -54,6 +54,9 @@
     @plans = new PricingPlansView { @state }

     @footer = @initFooter()
+
+    @kodingFooter = new FooterView
+

   initEvents: ->
     @plans.on 'PlanSelected', @bound 'planSelected'
@@ -175,6 +178,7 @@
       {{> @intervalToggle}}
       {{> @plans}}
       {{> @footer}}
+      {{> @kodingFooter}}
     """


Avoid buggy multipart upload up to 50MB instead of 5MB (amazon default)

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -70,7 +70,11 @@
                 Body: file
                 ACL: "public-read"

-            bucket.upload fileParams, (err, data) ->
+            options =
+                partSize: 50 * 1024 * 1024
+                queueSize: 1
+
+            bucket.upload fileParams, options, (err, data) ->
                 if err
                     defer.reject(err)
                 else defer.resolve(url: data.Location)


Fix cross domain worker - load worker locally

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -89,8 +89,8 @@
         _uploadWorker: (file) ->
             defer = $q.defer()
             script = document.querySelectorAll('[src*="file-upload"]')[0]
-            workerUrl = script.src.replace 'file-upload.js', 'file-upload-worker.js'
-            worker = new Worker workerUrl
+            workerUrl = new URL script.src.replace 'file-upload.js', 'file-upload-worker.js'
+            worker = new Worker workerUrl.pathname
             worker.onmessage = (e) ->
                 switch e.data.message
                     when 'load' then defer.resolve(e.data.body)


add test variables to fshelper

add test variables to fshelper

--- client/ide/fs/fshelper.coffee
+++ client/ide/fs/fshelper.coffee
@@ -212,11 +212,13 @@
     get    : (name)->
       "#{KD.config.uploadsUri}/#{KD.whoami().getId()}/#{name}"

-    upload : (name, content, callback)->
+    upload : (name, content, type, slug, callback)->
       vmController = KD.getSingleton 'vmController'
+      # TODO : change before merging
+      args = {name, bucket: "groups", "groupName", content}
       vmController.run
         method    : 's3.store'
-        withArgs  : {name, content}
+        withArgs  : args
       , (err, res)->
         if err then callback err
         else callback null, FSHelper.s3.get name


fixed an issue on mac where notifications closed themselves and then threw an error when manually closed later

--- src/chat/notification.coffee
+++ src/chat/notification.coffee
@@ -19,25 +19,25 @@
         @_message

   _addOnClickListener: ->
-    @notification.onclick = =>
+    @notification?.onclick = =>
       @cancel()
       @emit 'clicked'

   delete: ->
-    @notification.onclick = null
+    @notification?.onclick = null
     delete @notification

   ##
   # Display the notification.
   ##
   show: ->
-    @notification.show()
+    @notification?.show()

   ##
   # Close the notification.
   ##
   cancel: ->
-    @notification.cancel()
+    @notification?.cancel()

   ##
   # Used as a hash function for notifications.


Remove $ from atom-space-pen-views

Remove $ from atom-space-pen-views

--- spec/encoding-selector-spec.coffee
+++ spec/encoding-selector-spec.coffee
@@ -1,5 +1,5 @@
 path = require 'path'
-{$, WorkspaceView, View} = require 'atom'
+{$} = require 'atom-space-pen-views'

 describe "EncodingSelector", ->
   [editor, editorView] =  []


Fix missing ships.

Fix missing ships.

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -604,8 +604,10 @@
             text: """When defending, if you are stressed, you may change 2 of your %FOCUS% results to %EVADE% results."""
         '"Zeta Leader"':
             text: '''When attacking, if you are not stressed, you may receive 1 stress token to roll 1 additional die.'''
+            ship: "TIE/EO-Jäger"
         '"Epsilon Ace"':
             text: '''While you do not have any Damage cards, treat your pilot skill value as "12."'''
+            ship: "TIE/EO-Jäger"
         "Kanan Jarrus":
             text: """When an enemy ship at Range 1-2 is attacking, you may spend a focus token.  If you do, the attacker rolls 1 fewer attack die."""
         '"Chopper"':
@@ -620,6 +622,7 @@
             text: '''Once per round, after you discard an %ELITE% Upgrade card, flip that card faceup.'''
         'Ello Asty':
             text: '''While you are not stressed, you may treat your %TROLLLEFT% and %TROLLRIGHT% maneuvers as white maneuvers.'''
+            ship: "T-70-X-Flügler"

     upgrade_translations =
         "Ion Cannon Turret":


Fix TIE advanced ship name. #565

Fix TIE advanced ship name. #565

--- coffeescripts/cards-ru.coffee
+++ coffeescripts/cards-ru.coffee
@@ -193,7 +193,7 @@

     # ship translations
     exportObj.renameShip 'Lambda-Class Shuttle', 'Шаттл класса Лямбда'
-    exportObj.renameShip 'TIE Advanced', 'TIE-улучшенный'
+    exportObj.renameShip 'TIE Advanced', 'TIE улучшенный'
     exportObj.renameShip 'TIE Bomber', 'TIE бомбардировщик'
     exportObj.renameShip 'TIE Fighter', 'TIE истребитель'
     exportObj.renameShip 'TIE Interceptor', 'TIE перехватчик'


Create specs for generating packages in dev mode.

--- spec/package-generator-spec.coffee
+++ spec/package-generator-spec.coffee
@@ -100,22 +100,38 @@
           activationPromise

       describe "when the package is created outside of the packages directory", ->
+        [apmExecute] = []
+
+        generateOutside = (callback) ->
+          packageGeneratorView = atom.workspaceView.find(".package-generator").view()
+          expect(packageGeneratorView.hasParent()).toBeTruthy()
+          packageGeneratorView.miniEditor.setText(packagePath)
+          apmExecute = spyOn(packageGeneratorView, 'runCommand').andCallFake (command, args, exit) ->
+            process.nextTick -> exit()
+          packageGeneratorView.trigger "core:confirm"
+          waitsFor ->
+            atom.open.callCount is 1
+
+          runs callback
+
         it "calls `apm init` and `apm link`", ->
-          packageGeneratorView = atom.workspaceView.find(".package-generator").view()
-          expect(packageGeneratorView.hasParent()).toBeTruthy()
-          packageGeneratorView.miniEditor.setText(packagePath)
-          apmExecute = spyOn(packageGeneratorView, 'runCommand').andCallFake (command, args, exit) ->
-            process.nextTick -> exit()
-          packageGeneratorView.trigger "core:confirm"
-
-          waitsFor ->
-            atom.open.callCount is 1
-
-          runs ->
+          atom.config.set 'package-generator.createInDevMode', false
+
+          generateOutside ->
             expect(apmExecute.argsForCall[0][0]).toBe atom.packages.getApmPath()
             expect(apmExecute.argsForCall[0][1]).toEqual ['init', '--package', "#{packagePath}"]
             expect(apmExecute.argsForCall[1][0]).toBe atom.packages.getApmPath()
             expect(apmExecute.argsForCall[1][1]).toEqual ['link', "#{packagePath}"]
+            expect(atom.open.argsForCall[0][0].pathsToOpen[0]).toBe packagePath
+
+        it "calls `apm init` and `apm link --dev`", ->
+          atom.config.set 'package-generator.createInDevMode', false
+
+          generateOutside ->
+            expect(apmExecute.argsForCall[0][0]).toBe atom.packages.getApmPath()
+            expect(apmExecute.argsForCall[0][1]).toEqual ['init', '--package', "#{packagePath}"]
+            expect(apmExecute.argsForCall[1][0]).toBe atom.packages.getApmPath()
+            expect(apmExecute.argsForCall[1][1]).toEqual ['link', '--dev', "#{packagePath}"]
             expect(atom.open.argsForCall[0][0].pathsToOpen[0]).toBe packagePath

       describe "when the package is created inside the packages directory", ->


add simple save button

add simple save button

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -19,6 +19,10 @@
     ExerciseActions.updateTags(@getId(), tagsArray)

   getId: -> @props.id or prompt('Enter exercise id:')
+
+  saveExercise: ->
+    if confirm('Are you sure you want to save?')
+      ExerciseActions.save(@props.id)

   renderLoading: ->

Loading exercise: {@getId()}

@@ -48,5 +52,5 @@
         Tags
         <textarea onChange={@updateTags}>{ExerciseStore.getTags(id).join(',')}</textarea>

+      <button onClick={@saveExercise}>Save</button>

-


logout close was using an undefined close

--- src/concept-coach/index.cjsx
+++ src/concept-coach/index.cjsx
@@ -123,8 +123,8 @@

   open: (props) ->
     # wait until our logout request has been received and the close
-    User.channel.once 'logout.received', ->
-      props.close()
+    User.channel.once 'logout.received', =>
+      @close()

     openProps = _.extend({}, props, open: true)
     @component.setProps(openProps)


Closes #1879; Get the correct language when senging messages via email

--- packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee
+++ packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee
@@ -32,6 +32,8 @@
 		name = user.name
 		email = user.emails?[0]?.address

+		data.language = data.language.split('-').shift().toLowerCase()
+
 		if data.language isnt 'en'
 			localeFn = Meteor.call 'loadLocale', data.language
 			if localeFn


Modify needs to know current course

Modify needs to know current course

--- src/course/registration.cjsx
+++ src/course/registration.cjsx
@@ -16,7 +16,7 @@
   render: ->
     course = @getUser().getCourse(@props.collectionUUID)
     body = if course?.isRegistered()
-      <ModiyCourseRegistration {...@props} />
+      <ModifyCourseRegistration {...@props} course={course} />
     else
       <NewCourseRegistration {...@props} />


tutor review link

tutor review link

--- src/components/scores/table-cc.cjsx
+++ src/components/scores/table-cc.cjsx
@@ -104,16 +104,16 @@
     i += @props.firstDataColumn # for the first/last name columns

-    if heading.plan_id? or true ## temp until plan_id
+    if heading.plan_id?
       linkParams =
-        ## id: heading.plan_id
+        id: heading.plan_id
         periodIndex: @props.periodIndex
         courseId: @props.courseId

       review =
         <span className="review-link">
           <Router.Link
-            to='dashboard'
+            to='reviewTaskPeriod'
             params={linkParams}>
             Review
           </Router.Link>


config: use latest docker

config: use latest docker

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -415,7 +415,7 @@
     }

     function runCountlyDocker () {
-        docker run -d -p $KONFIG_COUNTLY_APIPORT:80 --env 'COUNTLY_PATH=/countly' --name=countly koding/countly-server:docker-configure
+        docker run -d -p $KONFIG_COUNTLY_APIPORT:80 --env 'COUNTLY_PATH=/countly' --name=countly koding/countly-server:latest
         check_connectivity countly
     }


no need to return this from _stageReset

no need to return this from _stageReset

--- oridomi.coffee
+++ oridomi.coffee
@@ -738,8 +738,6 @@
       panel.style[css.transform] = @_transform 0, @_lastOp.anchor
       @_setShader i, @_lastOp.anchor, 0 if @_shading

-    @
-

   # Converts a shorthand anchor name to a full one.
   _getLonghandAnchor: (shorthand) ->


boolean consistency

boolean consistency

--- oridomi.coffee
+++ oridomi.coffee
@@ -752,8 +752,8 @@
     # Attach touch/drag event listeners in related pairs.
     for eventPair in eventPairs
       for eString in eventPair
-        unless eString is 'TouchLeave' and not mouseLeaveSupport
           @_stageHolder[listenFn] eString.toLowerCase(), @['_on' + eventPair[0]], false
+        unless eString is 'TouchLeave' and !mouseLeaveSupport
         else
           @_stageHolder[listenFn] 'mouseout', @_onMouseOut, false
           break
@@ -844,7 +844,7 @@
   # A fallback for browsers that don't support `mouseleave`.
   _onMouseOut: (e) =>
     return unless @_touchEnabled and @_touchStarted
-    @_onTouchEnd() if e.toElement and not @el.contains e.toElement
+    @_onTouchEnd() if e.toElement and !@el.contains e.toElement

   _unfold: (callback) ->
@@ -976,7 +976,7 @@
     # Loop through the panels in this stage.
     @_iterate anchor, (panel, i) =>
       # If it's an odd-numbered panel, reverse the angle.
-      if i % 2 isnt 0 and not options.twist
+      if i % 2 isnt 0 and !options.twist
         deg = -angle
       else
         deg = angle


createEl helper

createEl helper

--- oridomi.coffee
+++ oridomi.coffee
@@ -33,6 +33,12 @@

 capitalize = (s) ->
   s[0].toUpperCase() + s[1...]
+
+createEl = (className) ->
+  el = document.createElement 'div'
+  el.className = elClasses[className]
+  el
+

 # This function is used to extend option object literals with a set of defaults.
 # It is simple and one dimensional.


simpler styling syntax

simpler styling syntax

--- oridomi.coffee
+++ oridomi.coffee
@@ -297,8 +297,7 @@
     hMask.className = 'oridomi-mask-h'
     hMask.style.position = 'absolute'
     hMask.style.overflow = 'hidden'
-    hMask.style.width = '100%'
-    hMask.style.height = '100%'
+    hMask.style.width = hMask.style.height = '100%'
     # Adding `translate3d(0, 0, 0)` prevents flickering during transforms.
     hMask.style[css.transform] = 'translate3d(0, 0, 0)'
     # Add the `contentHolder` div to the mask prototype.


Stop requiring 'addons' as causing warning now + no longer needed

--- src/index.cjsx
+++ src/index.cjsx
@@ -1,4 +1,4 @@
-React = require 'react/addons'
+React = require 'react'
 Textarea = require('react-textarea-autosize')
 marked = require 'marked'
 classNames = require 'classnames'


Fixing reference to element which triggered the event

--- app/assets/javascripts/ama_layout/mailcheck/autocorrect_email.coffee
+++ app/assets/javascripts/ama_layout/mailcheck/autocorrect_email.coffee
@@ -61,9 +61,9 @@
         empty: (element) ->
           $('.email_hint').html ''

-    $(document).on 'click', '.email_hint .suggestion a.email_domain', =>
+    $(document).on 'click', '.email_hint .suggestion a.email_domain', (e) =>
       @trackUsage()
-      email_hint = $(this).parents('.email_hint')
+      email_hint = $(e.originalEvent.target).parents('.email_hint')
       email = $(email_hint).prevAll('input[type=email]:last')
       $(email).val $('.suggestion').first().text()
       $('.email_hint').remove()


Improved error handling if type is not existing, use default type. Issue #55.

--- app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee
@@ -16,11 +16,13 @@
     # set title
     @title 'New Ticket'
     @form_id = App.ControllerForm.formId()
-    @navupdate '#ticket_create'

     @edit_form = undefined
+
+    # set article attributes
+    default_type = 'call_inbound'
     if !@type
-      @type = 'call_inbound'
+      @type = default_type
     article_sender_type_map =
       call_inbound:
         sender:  'Customer'
@@ -35,6 +37,13 @@
         article: 'email'
         title:   'Email'
     @article_attributes = article_sender_type_map[@type]
+
+    # if no map entry exists, route to default
+    if !@article_attributes
+      @navigate '#ticket_create/' + default_type
+
+    # update navbar highlighting
+    @navupdate '#ticket_create/' + @type

     @fetch(params)


refactoring the table text maker

refactoring the table text maker

--- tech-tests/primes-table/src/table-to-text-converter.coffee
+++ tech-tests/primes-table/src/table-to-text-converter.coffee
@@ -14,6 +14,12 @@
                 max = number
     max.toString().length

+getTableWidth = (numberOfColumns, maxNumberWidth, padding) ->
+    widthOfNumbersInRow = numberOfColumns * maxNumberWidth
+    widthOfPaddingInRow = numberOfColumns * padding * 2
+    widthOfVerticalSeparatorsInRow = numberOfColumns + 1
+    widthOfNumbersInRow + widthOfPaddingInRow + widthOfVerticalSeparatorsInRow
+
 createRow = (listOfNumbers, maxNumberWidth) ->
     row = "|"
     for number in listOfNumbers
@@ -22,19 +28,15 @@
         row = row + " #{padding}#{number} |"
     row

-module.exports.convertToText = (listOfLists, padding = 1) ->
-    maxNumberWidth = findLargestNumberWidth(listOfLists)
-    widthOfNumbersInRow = listOfLists.length * maxNumberWidth
-    widthOfPaddingInRow = listOfLists.length * padding * 2
-    widthOfVerticalSeparatorsInRow = listOfLists.length + 1
-
-    tableWidth = widthOfNumbersInRow +
-        widthOfPaddingInRow +
-        widthOfVerticalSeparatorsInRow
-
+createTextTable = (listOfLists, tableWidth, maxNumberWidth) ->
     textTable = ""
     for list in listOfLists
         textTable += horizontalLine("-", tableWidth) + "\n"
         textTable += createRow(list, maxNumberWidth) + "\n"
     textTable += horizontalLine("-", tableWidth)
-    textTable
+
+module.exports.convertToText = (listOfLists, padding = 1) ->
+    maxNumberWidth = findLargestNumberWidth(listOfLists)
+    tableWidth = getTableWidth(listOfLists.length, maxNumberWidth, padding)
+
+    createTextTable(listOfLists, tableWidth, maxNumberWidth)


no need for remotedriver anymore

no need for remotedriver anymore

--- shared/idle/IdleServer.coffee
+++ shared/idle/IdleServer.coffee
@@ -1,16 +1,15 @@

 if Meteor.isServer

-  driver = new MongoInternals.RemoteCollectionDriver "mongodb://localhost:27017/idlelands"
-  IdlePlayers = new Mongo.Collection "players", _driver: driver
-  IdleAnalytics = new Mongo.Collection "analytics", _driver: driver
-  IdleGuilds = new Mongo.Collection "guilds", _driver: driver
+  IdlePlayers = new Mongo.Collection "players"
+  IdleAnalytics = new Mongo.Collection "analytics"
+  IdleGuilds = new Mongo.Collection "guilds"

   share.IdlePlayers = IdlePlayers
   share.IdleAnalytics = IdleAnalytics

-  IdleMonsters = new Mongo.Collection "monsters", _driver: driver
-  IdleItems = new Mongo.Collection "items", _driver: driver
+  IdleMonsters = new Mongo.Collection "monsters"
+  IdleItems = new Mongo.Collection "items"

   singlePlayerFields =
     pushbulletApiKey: 0


Prevent default event behavior only for keys in the configured keyboard shortcuts. Other keys behave normally.

--- app/assets/javascripts/ng-services/ng-KeyboardShortcutsSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-KeyboardShortcutsSvc.js.coffee
@@ -55,6 +55,10 @@
               feedsFoldersSvc.show_read() # show read entries
             readSvc.read_entries_page()
             menuCollapseSvc.close()
+          else
+            return
+
+        event.preventDefault()

   #---------------------------------------------
   # Stop responding to keyboard shortcuts, if keyboard shortcuts are enabled.


Add note to mod.coffee, explaining why it's still .coffee if everything else will be .js

--- mod.coffee
+++ mod.coffee
@@ -1,3 +1,6 @@
+# I'm leaving this as a .coffee file, so it can still generate mod.js without issue
+# Is that cheating? :)
+
 #import "vendor/aurora.js/src/aurora.coffee"
 #import "src/demuxer.js"
 #import "src/decoder.js"


display error if archive membership fails

--- app/components/remove-membership-dialog/remove-membership-dialog.coffee
+++ app/components/remove-membership-dialog/remove-membership-dialog.coffee
@@ -20,9 +20,15 @@
       $mdDialog.cancel()
     $scope.proceed = ->
       $mdDialog.hide()
-      membership.cancel().then ->
-        Dialog.alert(
-          title: 'Success!'
-          content: "#{$scope.member.name} was removed from #{$scope.group.name}"
-        ).then ->
-          $window.location.reload()
+      membership.cancel()
+        .then ->
+          Dialog.alert(
+            title: 'Success!'
+            content: "#{$scope.member.name} was removed from #{$scope.group.name}"
+          ).then ->
+            $window.location.reload()
+        .catch (err) ->
+          Dialog.alert(
+            title: 'Error!'
+            content: err.data.errors
+            )


separate lines in case of notice

separate lines in case of notice

--- src/irc.coffee
+++ src/irc.coffee
@@ -62,10 +62,12 @@
     # Flatten out strings from send
     flattened = []
     for str in strings
-      if Array.isArray str
-        flattened = flattened.concat str
-      else
-        flattened.push str
+      if typeof str != 'undefined'
+        for line in str.toString().split(/\r?\n/)
+          if Array.isArray line
+            flattened = flattened.concat line
+          else
+            flattened.push line

     for str in flattened
       if not str?


will come back

will come back

--- scripts/defprogramming.coffee
+++ scripts/defprogramming.coffee
@@ -1,16 +0,0 @@
-# def programming - Display a random programming quote from defprogramming.com
-
-Select     = require("soupselect").select
-HtmlParser = require "htmlparser"
-
-module.exports = (robot) ->
-    robot.respond /def programming/i, (msg) ->
-        msg.http("http://www.defprogramming.com/random")
-           .get() (err, res, body) ->
-               handler = new HtmlParser.DefaultHandler()
-               parser  = new HtmlParser.Parser handler
-
-               parser.parseComplete body
-
-               results = Select handler.dom, "cite a p"
-               msg.send results[0].children[0].raw


Remove debug stuff.

Remove debug stuff.

--- src/fs_utils/generate.coffee
+++ src/fs_utils/generate.coffee
@@ -124,8 +124,6 @@
     'stylesheet'
   optimizer = minifiers.filter((minifier) -> minifier.type is type)[0]

-  console.log 1212
-
   sorted = sort sourceFiles, config
   joined = join sorted, path, type, config._normalized.modules.definition


Client: Activity: Tests: Deleted test.coffee file to separate tests

--- client/activity/lib/components/browsepublicchannelsmodal/test.coffee
+++ client/activity/lib/components/browsepublicchannelsmodal/test.coffee
@@ -1,271 +0,0 @@
-React              = require 'kd-react'
-ReactDOM           = require 'react-dom'
-expect             = require 'expect'
-Modal              = require './index'
-ModalView          = require './view'
-TabView            = require './tabview'
-Header             = require './header'
-TestUtils          = require 'react-addons-test-utils'
-ActivityFlux       = require 'activity/flux'
-Tabs               = require 'activity/constants/sidebarpublicchannelstabs'
-expectCorrectRoute = require 'activity/test/helpers/expectCorrectRoute'
-
-
-describe 'BrowsePublicChannelsModal', ->
-
-  { Simulate
-    createRenderer
-    renderIntoDocument
-    getItemFromContainer
-    findAllInRenderedTree
-    scryRenderedDOMComponentsWithClass } = TestUtils
-
-  { channel } = ActivityFlux.actions
-
-  it 'renders BrowsePublicChannelsModal with PublicChannelListModal and ChannelList-Modal classNames', ->
-
-    shallowRenderer = createRenderer()
-    shallowRenderer.render(<Modal />)
-
-    modal = shallowRenderer.getRenderOutput()
-
-    expect(modal.props.isOpen).toBeTruthy()
-    expect(modal.props.className).toContain 'PublicChannelListModal'
-    expect(modal.props.className).toContain 'ChannelList-Modal'
-
-
-  it 'checks rendered modal child component className', ->
-
-    shallowRenderer = createRenderer()
-    shallowRenderer.render(<Modal />)
-
-    modal = shallowRenderer.getRenderOutput()
-    { children } = modal.props
-
-    expect(children.props.className).toEqual 'ChannelListWrapper'
-    expect(children.props.children.length).toEqual 3
-
-
-  it 'checks rendered modal child component DOM node className', ->
-
-    modal = renderIntoDocument(<Modal isOpen={no}/>)
-    node  = ReactDOM.findDOMNode modal
-
-    expect(node.className).toEqual 'Modal-container'
-
-
-  it 'should render modal-container and checks onItemClick method of modal', ->
-
-    modal = renderIntoDocument(<Modal.Container isOpen={no}/>)
-
-    expect(modal.skipCloseHandling).toBeFalsy()
-    expect(modal.onItemClick()).toBeTruthy()
-    expect(modal.skipCloseHandling).toBeTruthy()
-
-
-  it 'should call channel actions loadChannels and setSidebarPublicChannelsTab methods', ->
-
-    modal           = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    channelsTabSpy  = expect.spyOn channel, 'setSidebarPublicChannelsTab'
-    loadChannelsSpy = expect.spyOn channel, 'loadChannels'
-
-    modal.onOtherChannelsClick()
-
-    expect(loadChannelsSpy).toHaveBeenCalled()
-    expect(channelsTabSpy).toHaveBeenCalledWith Tabs.OtherChannels
-
-
-  it 'should call channel actions loadFollowedPublicChannels and setSidebarPublicChannelsTab methods', ->
-
-    modal                   = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    channelsTabSpy          = expect.spyOn channel, 'setSidebarPublicChannelsTab'
-    loadFollowedChannelsSpy = expect.spyOn channel, 'loadFollowedPublicChannels'
-
-    modal.onYourChannelsClick()
-
-    expect(loadFollowedChannelsSpy).toHaveBeenCalled()
-    expect(channelsTabSpy).toHaveBeenCalledWith Tabs.YourChannels
-
-
-  it 'should call channel actions loadChannelsByQuery and setSidebarPublicChannelsQuery methods', ->
-
-    modal                  = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    loadChannelsByQuerySpy = expect.spyOn channel, 'loadChannelsByQuery'
-    publicChannelsQuerySpy = expect.spyOn channel, 'setSidebarPublicChannelsQuery'
-
-    dummyValue = 'input-dummy-value'
-
-    mockEvent =
-      target:
-        value: ''
-
-    modal.onSearchInputChange mockEvent
-
-    expect(loadChannelsByQuerySpy).toNotHaveBeenCalled()
-    expect(publicChannelsQuerySpy).toHaveBeenCalledWith ''
-
-    mockEvent.target.value = dummyValue
-
-    modal.onSearchInputChange mockEvent
-
-    expect(loadChannelsByQuerySpy).toHaveBeenCalledWith dummyValue
-    expect(publicChannelsQuerySpy).toHaveBeenCalledWith dummyValue
-
-
-  it 'should call modal-container onYourChannelsClick method', ->
-
-    modal = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    spy   = expect.spyOn modal, 'onYourChannelsClick'
-
-    modal.onTabChange Tabs.YourChannels
-
-    expect(spy).toHaveBeenCalled()
-
-
-  it 'should call modal-container onOtherChannelsClick method', ->
-
-    modal = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    spy   = expect.spyOn modal, 'onOtherChannelsClick'
-
-    modal.onTabChange Tabs.OtherChannels
-
-    expect(spy).toHaveBeenCalled()
-
-
-  it 'should call channel actions loadChannelsByQuery method', ->
-
-    modal   = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    spy     = expect.spyOn channel, 'loadChannelsByQuery'
-    query   = 'dummy-query'
-    options = { key : 'dummy-value' }
-
-    modal.setState { query }
-
-    modal.onThresholdReached options
-
-    expect(spy).toHaveBeenCalledWith 'dummy-query', options
-
-
-  it 'should call channel actions loadFollowedPublicChannels method', ->
-
-    modal   = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    spy     = expect.spyOn channel, 'loadFollowedPublicChannels'
-    query   = 'dummy-query'
-    options = { key : 'dummy-value' }
-
-    modal.setState tab: Tabs.YourChannels
-
-    modal.onThresholdReached options
-
-    expect(spy).toHaveBeenCalledWith options
-
-
-  it 'should call channel actions loadChannels method', ->
-
-    modal   = renderIntoDocument(<Modal.Container isOpen={no}/>)
-    spy     = expect.spyOn channel, 'loadChannels'
-    options = { key : 'dummy-value' }
-
-    modal.setState tab: Tabs.OtherChannels
-
-    modal.onThresholdReached options
-
-    expect(spy).toHaveBeenCalledWith options
-
-
-  it 'should call router.handleRoute method with public route parameter', ->
-
-    expectCorrectRoute Modal, 'koding-dummy-channel', 'topic', "/Channels/koding-dummy-channel"
-
-
-  it 'should call router.handleRoute method with private route parameter', ->
-
-    expectCorrectRoute Modal, 'koding-12345', 'privatemessage', "/Messages/koding-12345"
-
-
-  it 'checks rendered modal props type', ->
-
-    modal = renderIntoDocument(<ModalView isOpen={no}/>)
-
-    expect(modal.props.query).toBeA 'string'
-    expect(modal.props.className).toBeA 'string'
-    expect(modal.props.onClose).toBeA 'function'
-    expect(modal.props.onTabChange).toBeA 'function'
-    expect(modal.props.onItemClick).toBeA 'function'
-    expect(modal.props.onThresholdReached).toBeA 'function'
-    expect(modal.props.onSearchInputChange).toBeA 'function'
-    expect(modal.props.isOpen).toBeA 'boolean'
-    expect(modal.props.isSearchActive).toBeA 'boolean'
-
-
-  it 'checks rendered tabView and its children props', ->
-
-    shallowRenderer = createRenderer()
-    shallowRenderer.render(<TabView />)
-
-    tabView = shallowRenderer.getRenderOutput()
-    { children } = tabView.props
-
-    expect(tabView.type).toEqual 'div'
-    expect(tabView.props.className).toEqual 'ChannelList-tabs'
-    expect(children.length).toEqual 3
-    expect(children[0].ref).toEqual 'yourChannels'
-    expect(children[1].ref).toEqual 'otherChannels'
-    expect(children[0].props.children).toEqual 'Your Channels'
-    expect(children[1].props.children).toEqual 'Other Channels'
-    expect(children[0].props.className).toEqual 'ChannelList-tab'
-    expect(children[1].props.className).toEqual 'ChannelList-tab'
-    expect(children[2].props.className).toEqual 'clearfix'
-
-
-  it 'should call onChange method of tabview', ->
-
-    onChange      = expect.createSpy()
-    tabView       = renderIntoDocument(<TabView onChange={onChange}/>)
-    yourChannels  = tabView.refs.yourChannels
-    otherChannels = tabView.refs.otherChannels
-
-    TestUtils.Simulate.click yourChannels
-
-    expect(onChange).toHaveBeenCalledWith(Tabs.YourChannels)
-
-    TestUtils.Simulate.click otherChannels
-
-    expect(onChange).toHaveBeenCalledWith(Tabs.OtherChannels)
-
-
-  it 'checks rendered header and its children props', ->
-
-    shallowRenderer = createRenderer()
-    shallowRenderer.render(<Header />)
-
-    header = shallowRenderer.getRenderOutput()
-    { children } = header.props
-
-    expect(header.type).toEqual 'div'
-    expect(children.length).toEqual 2
-    expect(children[0].props.children).toEqual 'Channels'
-    expect(children[0].props.className).toEqual 'ChannelList-title'
-
-
-  it 'checks rendered search input properties', ->
-
-    header = renderIntoDocument(<Header />)
-    input  = header.refs.ChannelSearchInput
-    inputs = scryRenderedDOMComponentsWithClass header, 'ChannelList-searchInput'
-
-    expect(inputs.length).toEqual 1
-    expect(input.className).toEqual 'ChannelList-searchInput'
-    expect(input.placeholder).toEqual 'Search'
-
-
-  it 'checks onSearchInputChange method of header and should call onChange function', ->
-
-    onChange = expect.createSpy()
-    header   = renderIntoDocument(<Header onSearchInputChange={onChange}/>)
-    input    = header.refs.ChannelSearchInput
-
-    TestUtils.Simulate.change input
-
-    expect(onChange).toHaveBeenCalled()
-


show dashboard before fetching finished if we already have some records

--- client/angular/pages/dashboard_page/dashboard_page_controller.coffee
+++ client/angular/pages/dashboard_page/dashboard_page_controller.coffee
@@ -70,6 +70,10 @@
     params:
       filter: @filter
       per: 50
+
+
+  @dashboardLoaded = Records.discussions.collection.data.length > 0
+
   @loader.fetchRecords().then => @dashboardLoaded = true

   @noGroups        = -> !Session.user().hasAnyGroups()


oskite: Added noScreen option to webterm.

--- client/landing/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -74,6 +74,7 @@
           session  : @getOption('delegate').getOption('session')
           sizeX  : @terminal.sizeX
           sizeY  : @terminal.sizeY
+          noScreen: @getOption('delegate').getOption('noScreen')
       , (err, remote) =>
         if err
           # We don't create any error popup not to be annoying. User can handle the error.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -113,6 +113,7 @@
   else if min?
     Math.min(this, min)
   else if max?
+    Math.max(this, ma)

 ###*
 A mod method useful for array wrapping. The range of the function is


Improve generated library doc file for KiCad

--- src/kicad-generator.coffee
+++ src/kicad-generator.coffee
@@ -38,7 +38,7 @@

     log.start "KiCad library doc '#{@name}.dcm'"
     fd = fs.openSync "#{dir}/#{@name}.dcm", 'w'
-    fs.writeSync fd, "EESchema-DOCLIB Version 2.0 Date: #{timestamp}\n"
+    fs.writeSync fd, "EESchema-DOCLIB Version 2.0 Date: #{timestamp}\n#\n"
     for element in @library.elements
       @_generateDoc fd, element
     fs.writeSync fd, '# End Doc Library\n'
@@ -56,11 +56,18 @@
   # Write doc entry to library doc file
   #
   _generateDoc: (fd, element) ->
+    fields = ['description', 'keywords', 'datasheet']
+    keys = ['D', 'K', 'F']
+    empty = true
+    for field in fields
+      if element[field]?
+        empty = false
+        break
+    if empty then return
     fs.writeSync fd, "$CMP #{element.name}\n"
-    if element.description? then fs.writeSync fd, "D #{element.description}\n"
-    if element.keywords? then fs.writeSync fd, "K #{element.keywords}\n"
-    if element.datasheet? then fs.writeSync fd, "F #{element.datasheet}\n"
-    fs.writeSync fd, "$ENDCMP\n"
+    for i in [0..(fields.length - 1)]
+      if element[fields[i]]? then fs.writeSync fd, "#{keys[i]} #{element[fields[i]]}\n"
+    fs.writeSync fd, '$ENDCMP\n#\n'

   #
   # Write pattern file


shorten desc so len<=80

shorten desc so len<=80

--- src/rules/camel_case_classes.coffee
+++ src/rules/camel_case_classes.coffee
@@ -7,12 +7,12 @@
     rule:
         name: 'camel_case_classes'
         level : 'error'
-        message : 'Class names should be UpperCamelCased'
+        message : 'Class name should be UpperCamelCased'
         description: """
-            This rule mandates that all class names are UpperCamelCased. Camel
-            casing class names is a generally accepted way of distinguishing
-            constructor functions - which require the 'new' prefix to behave
-            properly - from plain old functions.
+            This rule mandates that all class names are UpperCamelCased.
+            Camel casing class names is a generally accepted way of
+            distinguishing constructor functions - which require the 'new'
+            prefix to behave properly - from plain old functions.
             <pre>
             <code># Good!
             class BoaConstrictor


Use CSS selectors in scoped properties

Use CSS selectors in scoped properties

--- src/package-converter.coffee
+++ src/package-converter.coffee
@@ -6,6 +6,7 @@
 CSON = require 'season'
 plist = require 'plist'
 request = require 'request'
+{ScopeSelector} = require 'first-mate'
 tar = require 'tar'
 temp = require 'temp'

@@ -88,7 +89,8 @@

     if properties = @convertSettings(settings)
       preferences = {}
-      preferences[scope] = properties
+      selector = new ScopeSelector(scope).toCssSelector()
+      preferences[selector] = properties
       preferences

   convertFile: (sourcePath, destinationDir) ->


Fix a crash when buffer is not defined on files

--- src/lib/models/file.coffee
+++ src/lib/models/file.coffee
@@ -514,7 +514,7 @@
 		changes = {}

 		# Detect Encoding
-		if encoding? is false or opts.reencode is true
+		if buffer and encoding? is false or opts.reencode is true
 			isText = balUtil.isTextSync(relativePath, buffer)

 			# Text
@@ -573,7 +573,7 @@
 			encoding = changes.encoding = 'utf8'  if encoding? is false

 			# Set
-			source = buffer.toString('utf8')
+			source = buffer?.toString('utf8') or ''
 			content = source

 			# Apply


Add GitHub Enterprise support to github-commiters

--- src/scripts/github-commiters.coffee
+++ src/scripts/github-commiters.coffee
@@ -6,10 +6,14 @@
 #
 # Configuration:
 #   HUBOT_GITHUB_TOKEN
+#   HUBOT_GITHUB_API
 #
 # Commands:
 #   hubot repo commiters <repo> - shows commiters of repository
 #   hubot repo top-commiters <repo> - shows top commiters of repository
+#
+# Notes:
+#   HUBOT_GITHUB_API allows you to set a custom URL path (for Github enterprise users)
 #
 # Author:
 #   vquaiato
@@ -35,12 +39,17 @@

   read_contributors = (msg, response_handler) ->
       repo = github.qualified_repo msg.match[1]
-      url = "https://api.github.com/repos/#{repo}/contributors"
+      base_url = process.env.HUBOT_GITHUB_API || 'https://api.github.com'
+      url = "#{base_url}/repos/#{repo}/contributors"
       github.get url, (commits) ->
         if commits.message
           msg.send "Achievement unlocked: [NEEDLE IN A HAYSTACK] repository #{commits.message}!"
         else if commits.length == 0
           msg.send "Achievement unlocked: [LIKE A BOSS] no commits found!"
         else
-          msg.send "https://github.com/#{repo}"
-          response_handler commits
+          unless process.env.HUBOT_GITHUB_API
+            msg.send "https://github.com/#{repo}"
+          else
+            ghe_url = base_url.replace /\/api\/v3/, ''
+            msg.send "#{ghe_url}/#{repo}"
+            response_handler commits


Added timeout handling to automatically logout after about 15 minutes inactivity. Resolves #80

This seems reasonable, but is nothing to do with sessions.

--- src/main/client/app.coffee
+++ src/main/client/app.coffee
@@ -101,6 +101,29 @@
     $httpProvider.defaults.useXDomain = true

+
+  .run Array '$rootScope', '$interval', '$document', (scope, $interval, $document) ->
+
+    IDLE_TIMEOUT = 15 * 60
+    idleSecondsTimer = null
+    idleSecondsCounter = 0
+
+    resetCounter = () ->
+      idleSecondsCounter = 0
+
+    $document.on 'click', resetCounter
+    $document.on 'mousemove', resetCounter
+    $document.on 'keypress', resetCounter
+
+    checkIdleTime = () ->
+      idleSecondsCounter++
+      if idleSecondsCounter > IDLE_TIMEOUT
+        $interval.cancel idleSecondsTimer
+        scope.$broadcast 'timeout:logout'
+
+    idleSecondsTimer = $interval checkIdleTime, 1000
+
+
   .run Array '$rootScope', '$http', '$timeout', '$state', 'authenticationService', (scope, $http, $timeout, $state, authenticationService) ->

     class User
@@ -141,6 +164,10 @@
       authenticationService.logout()
       $state.go 'logout'

+    scope.$on 'timeout:logout', (evt) ->
+      authenticationService.logout()
+      $state.go 'logout'
+

   .run () ->


Remove plans.less form Gruntfile

Remove plans.less form Gruntfile

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -72,9 +72,6 @@
 			app:
 				files:
 					"public/stylesheets/mainStyle.css": "public/stylesheets/mainStyle.less"
-			plans:
-				files:
-					"public/brand/plans.css": "public/brand/plans.less"

 		requirejs:
 			compile:


Add French

Add French

--- app/main.coffee
+++ app/main.coffee
@@ -18,10 +18,11 @@
 languageManager = new LanguageManager
   translations:
     en: label: 'English', strings: enUs
-    de: label: "Deutsch", strings: "./translations/de.json"
-    es: label: "Español", strings: "./translations/es.json"
-    id: label: "Bahasa Indonesia", strings: "./translations/id.json"
-    da: label: "Dansk", strings: "./translations/da.json"
+    es: label: "Español"
+    de: label: "Deutsch"
+    fr: label: 'Français'
+    id: label: "Bahasa Indonesia"
+    da: label: "Dansk"

 languageManager.on 'change-language', (e, code, languageStrings) ->
   t7e.load languageStrings


Move things around in main.coffee

Move things around in main.coffee

--- app/main.coffee
+++ app/main.coffee
@@ -1,5 +1,4 @@
 Spine = require 'spine'
-
 require 'spine/lib/manager'
 require 'spine/lib/relation'
 require 'spine/lib/route'
@@ -8,13 +7,6 @@
 require './models/Archive'
 require './models/Badge'

-SubPage = require './controllers/PageController'
-BadgesController = require './controllers/BadgesController'
-FAQController = require './controllers/FAQController'
-Home = require './controllers/home'
-LoginController = require './controllers/LoginController'
-Profile = require './controllers/profile'
-
 Api = require 'zooniverse/lib/api'

 Project = require 'zooniverse/models/project'
@@ -22,47 +14,37 @@
 Subject = require 'zooniverse/models/subject'
 User = require 'zooniverse/models/user'

-TopBar = require 'zooniverse/controllers/top-bar'
-Footer = require 'zooniverse/controllers/footer'
-
-Archive = require './models/Archive'
 Badge = require './models/Badge'
 Institute = require './models/Institute'
-
-Header = require './controllers/layout/header'
-Notifications = require './controllers/notifications'

 new Api project: 'notes_from_nature'

 app = {}
 app.container = '#app'

-app.models = {}
-app.models.Archive = Archive
-app.models.Badge = Badge
-app.models.Institute = Institute
-
+Header = require './controllers/layout/header'
 app.header = new Header
 app.header.el.prependTo app.container

+SubPage = require './controllers/PageController'
 app.stack = new Spine.Stack
   controllers:
-    home: Home
+    home: require './controllers/home'
     archives: require './controllers/archives/list'
-    archivesItem: require './controllers/archives/item'
+    archiveDetails: require './controllers/archive-details'
     transcriptions: require './controllers/TranscriptionController'
     about: class extends SubPage
       content: require './views/about/index'
       title: 'About'
     aboutProject: class extends SubPage then content: require './views/about/project'
-    badges: BadgesController
-    login: LoginController
-    profile: Profile
+    badges: require './controllers/BadgesController'
+    login: require './controllers/LoginController'
+    profile: require './controllers/profile'

   routes:
     '/': 'home'
     '/archives': 'archives'
-    '/archives/:id': 'archivesItem'
+    '/archives/:id': 'archiveDetails'
     '/archives/:id/transcribe': 'transcriptions'

     '/badges/:id': 'badges'
@@ -77,12 +59,15 @@

 app.stack.el.appendTo app.container

+Notifications = require './controllers/notifications'
 app.notifications = new Notifications
 app.notifications.el.prependTo app.container

+Footer = require 'zooniverse/controllers/footer'
 app.footer = new Footer
 app.footer.el.appendTo app.container

+TopBar = require 'zooniverse/controllers/top-bar'
 app.topBar = new TopBar
 app.topBar.el.prependTo 'body'


fix disregard

fix disregard

--- src/run.coffee
+++ src/run.coffee
@@ -97,7 +97,6 @@
                   merge action, action.callback.call ctx, ctx, (err, statusOrMsg) =>
                     actionRun.end() if statusOrMsg is ctx.STOP
                     done err, statusOrMsg
-                    disregard_done = true
               catch e then done e
             # Timeout, default to 100s
             action.timeout ?= 100000
@@ -105,6 +104,7 @@
               timeout = setTimeout ->
                 retry = 0 # Dont retry on timeout or we risk to get the callback called multiple times
                 done new Error 'TIMEOUT'
+                disregard_done = true
               , action.timeout
             run()
           .on 'both', (err) =>


retenciones now support compras or ventas

--- app/assets/javascripts/models/concerns/retencion/retencion_base_mixin.js.coffee
+++ app/assets/javascripts/models/concerns/retencion/retencion_base_mixin.js.coffee
@@ -1,13 +1,17 @@
 Bandango.RetencionBaseMixin = Ember.Mixin.create
+  parent: (->
+    @get("compra") or @get("venta")
+  ).property("compra", "venta")
+
   setIva: (->
-    iva = @get("compra.ivaTwelve") * (@get("tarifaIva") / 100)
+    iva = @get("parent.ivaTwelve") * (@get("tarifaIva") / 100)
     @set "iva", iva
-  ).observes("tarifaIva", "compra.ivaTwelve")
+  ).observes("tarifaIva", "parent.ivaTwelve")

   setIce: (->
-    ice = @get("compra.ice") * (@get("tarifaIce") / 100)
+    ice = @get("parent.ice") * (@get("tarifaIce") / 100)
     @set "ice", ice
-  ).observes("tarifaIce", "compra.ice")
+  ).observes("tarifaIce", "parent.ice")

   setTarifas: (->
     porcentaje = @get("conceptoRetencionFuente.porcentaje")


fix redundant SR error message on admin tools user search

fixes CNVS-35962

test plan:
- go to accounts/<account_id>/admin_tools > View Notifications
- start a search
- navigate down to the list of users
- note that you will never hear that error anymore as it was redundant
  (the search accepts both name and user id and it returns everything
  if nothing is entered, so the error didn't make sense in the first
  place)

Change-Id: I7e7c2603cb1abd1f844fd8e98dbc40aab2ef83a8
Reviewed-on: https://gerrit.instructure.com/125347
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
Tested-by: Jenkins
QA-Review: David Tan <6283e310a2278fc51bdc4fb5974f70dd9c3366b9@instructure.com>
Product-Review: Felix Milea-Ciobanu <9384aa226ac9d3237ed0bc52e18ffcb251ee0bf7@instructure.com>

--- app/coffeescripts/views/accounts/admin_tools/UserDateRangeSearchFormView.coffee
+++ app/coffeescripts/views/accounts/admin_tools/UserDateRangeSearchFormView.coffee
@@ -66,7 +66,7 @@
       @collection.on 'sync', @notificationsFound

     resultsFound: =>
-      $.screenReaderFlashMessage(I18n.t('results_found', "%{length} results found", { length: @usersView.collection.length }))
+      $.screenReaderFlashMessage(I18n.t('%{length} results found', { length: @usersView.collection.length }))

     notificationsFound: =>
       $.screenReaderFlashMessage(I18n.t('%{length} notifications found', { length: @collection.length }))
@@ -91,15 +91,6 @@

       valid = true
       errors = {}
-      if !json.user_id
-        valid = false
-        errors['user_id'] =
-          [
-            {
-            type: 'required'
-            message: I18n.t('cant_be_blank', "Canvas User ID can't be blank")
-            }
-          ]
       # If have both start and end, check for values to make sense together.
       if json.start_time && json.end_time && (json.start_time > json.end_time)
         valid = false
@@ -107,7 +98,7 @@
           [
             {
             type: 'invalid'
-            message: I18n.t('cant_come_before_from', "'To Date' can't come before 'From Date'")
+            message: I18n.t('"To Date" can\'t come before "From Date"')
             }
           ]
       # Show any errors


snapshots: Added upgrade error modal for free plans.

--- client/app/lib/providers/machinesettingssnapshotsview.coffee
+++ client/app/lib/providers/machinesettingssnapshotsview.coffee
@@ -3,6 +3,7 @@
 kd                        = require 'kd'
 remote                    = require('app/remote').getInstance()

+ComputeErrorUsageModal    = require './computeerrorusagemodal'
 MachineSettingsCommonView = require './machinesettingscommonview'
 SnapshotListItem          = require './snapshotlistitem'
 snapshotHelpers           = require './snapshothelpers'
@@ -151,6 +152,15 @@
   ###
   showAddView: ->
     @isWithinSnapshotLimit (err, isWithin, current, max) =>
+      # If the max is 0, the user has no allotted snapshots (free plan)
+      if max is 0
+        new ComputeErrorUsageModal
+          plan    : 'free'
+          message : 'VM share feature is only available for paid accounts.'
+
+        return @emit 'ModalDestroyRequested'
+
+
       kd.warn err  if err
       if not isWithin
         msg = "Your current plan allows for a maximum of #{max} Snapshots"


groups: indentation

groups: indentation

--- client/landing/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/landing/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -148,6 +148,8 @@
     @utils.defer =>
       groupLogoView.setClass 'animate'
       @landingView._windowDidResize()
+
+
   checkGroupUserRelation:->

     KD.remote.cacheable @groupEntryPoint, (err, groups, name)=>


debug photo rotation in production

debug photo rotation in production

--- server/controllers/photo.coffee
+++ server/controllers/photo.coffee
@@ -29,42 +29,48 @@
             app.io.sockets.emit 'uploadprogress', cid: cid, p: percent

         req.form.on 'end', =>
-            raw = req.files['raw']
-            im.readMetadata raw.path, (err, metadata) ->
-                console.log err if err?
-                if metadata?.exif?.orientation?
-                    req.body.orientation = metadata.exif.orientation
-                else
-                    req.body.orientation = 1
-                if metadata.exif?.dateTime?
-                    req.body.date = metadata.exif.dateTime
-                photo = new Photo req.body
-                Photo.create photo, (err, photo) ->
-                    return res.error 500, "Creation failed.", err if err
+            photo = new Photo req.body
+            Photo.create photo, (err, photo) ->
+                return res.error 500, "Creation failed.", err if err

-                    async.parallel [
-                        (cb) ->
-                            raw = req.files['raw']
-                            data = name: 'raw', type: raw.type
-                            photo.attachFile raw.path, data, cb
-                        (cb) ->
-                            screen = req.files['screen']
-                            data = name: 'screen', type: screen.type
-                            photo.attachFile screen.path, data, cb
-                        (cb) ->
-                            thumb = req.files['thumb']
-                            data = name: 'thumb', type: thumb.type
-                            photo.attachFile thumb.path, data, cb
-                    ], (err) ->
-                        for name, file of req.files
-                            fs.unlink file.path, (err) ->
-                                if err
-                                    console.log 'Could not delete', file.path
+                async.parallel [
+                    (cb) ->
+                        raw = req.files['raw']
+                        data = name: 'raw', type: raw.type
+                        photo.attachFile raw.path, data, cb
+                    (cb) ->
+                        screen = req.files['screen']
+                        data = name: 'screen', type: screen.type
+                        photo.attachFile screen.path, data, cb
+                    (cb) ->
+                        thumb = req.files['thumb']
+                        data = name: 'thumb', type: thumb.type
+                        photo.attachFile thumb.path, data, cb
+                ], (err) ->
+                    raw = req.files['raw']
+                    im.readMetadata raw.path, (err, metadata) ->
+                        console.log err if err?
+                        if metadata?.exif?.orientation?
+                            orientation = metadata.exif.orientation
+                        else
+                            orientation = 1
+                        if metadata.exif?.dateTime?
+                            date = metadata.exif.dateTime
+                        data =
+                            orientation: orientation
+                            date: date
+                        photo.updateAttributes data, (err) =>
+                            photo.orientation = orientation
+                            photo.date = date
+                            for name, file of req.files
+                                fs.unlink file.path, (err) ->
+                                    if err
+                                        console.log 'Could not delete', file.path

-                        if err
-                            return res.error 500, "Creation failed.", err
-                        else
-                            res.send photo, 201
+                            if err
+                                return res.error 500, "Creation failed.", err
+                            else
+                                res.send photo, 201

     screen: (req, res) ->
         res.setHeader 'Content-Type', 'image/jpg'


fixes the multiple delete issue

fixes the multiple delete issue

--- app/assets/javascripts/trade/controllers/shipments_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/shipments_controller.js.coffee
@@ -71,7 +71,6 @@
       # process deletes
       @get('content').filterBy('_destroyed', true).forEach (shipment) ->
         shipment.deleteRecord()
-        shipment.get("transaction").commit()
       # process updates
       @get('store').commit()
       @openShipmentsPage(@get('page'))


now, it needs to escalate to parent of parent view's

--- app/assets/javascripts/views/order_items/order_item_form_view.js.coffee
+++ app/assets/javascripts/views/order_items/order_item_form_view.js.coffee
@@ -7,5 +7,5 @@
     item = @_super(itemObject)
     store = @get("controller.store")
     orderItem = store.createRecord("orderItem", cantidad: @get("cantidad"), item: item)
-    @get("parentView.model").addOrderItem orderItem
+    @get("parentView.parentView.model").addOrderItem orderItem
     $(".item_textfield").val null


IDE: privatemessagepane to know if it is used as a collab pane

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -95,6 +95,12 @@

   addMessageDeferred: (item, i, total) ->
+
+    # temp.
+    # until we have a separate message type for collaboration messages
+    # we need to do this to be able to distinguish them - SY
+    if item.payload?.collaboration then @setOption 'collaboration', yes
+
     # Super method defers adding list items to minimize page load
     # congestion. This function is overrides super function to render
     # all conversation messages to be displayed at the same time


teams: /Teams homepage made create team modal

--- client/landing/site.landing/coffee/teams/AppView.coffee
+++ client/landing/site.landing/coffee/teams/AppView.coffee
@@ -1,199 +1,56 @@
-CustomLinkView             = require './../core/customlinkview'
-MainHeaderView             = require './../core/mainheaderview'
-JView                      = require './../core/jview'
-FooterView                 = require './../home/footerview'
-TeamsHomeFeaturesSection   = require './partials/teamshomefeaturessection'
-TeamsSignupForm            = require './teamssignupform'
-TeamsLaunchForm            = require './teamslaunchform'
-
+MainHeaderView  = require './../core/mainheaderview'
+JView           = require './../core/jview'
+TeamsSignupForm = require './teamssignupform'

 module.exports = class TeamsView extends JView

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

+    options.cssClass = KD.utils.curry 'Team', options.cssClass
+
     super options, data

-    { mainController, router } = KD.singletons
+    { mainController } = KD.singletons
+    { group }          = KD.config

-    teamsLogo = new KDCustomHTMLView
-      tagName   : 'a'
-      cssClass  : 'teams-header-logo'
-      partial   : 'Koding Teams'
-      click     : (event) ->
-        KD.utils.stopDOMEvent event
-        KD.singletons.router.handleRoute '/'
+    @header = new MainHeaderView
+      cssClass : 'team'
+      navItems : [
+        { title : 'Blog',        href : 'http://blog.koding.com',   name : 'blog' }
+        { title : 'Features',    href : '/Features',                name : 'features' }
+      ]

-    @thanks = new KDCustomHTMLView
-      cssClass : 'ribbon hidden'
-      partial  : 'Thank You!'
+    @form = new TeamsSignupForm
+      cssClass : 'login-form'
+      callback : (formData) ->

-    @comingSoon = new KDCustomHTMLView
-      cssClass : 'ribbon hidden'
-      partial  : 'Coming Soon!'
+        finalize = (email) ->
+          KD.utils.storeNewTeamData 'signup', formData
+          KD.singletons.router.handleRoute '/Team/Domain'

+          return  unless email

-    if KD.config.hasTeamAccess
+          KD.utils.getProfile email,
+            error   : ->
+            success : (profile) ->
+              formData.profile = profile  if profile
+              KD.utils.storeNewTeamData 'signup', formData

-      @header = new MainHeaderView
-        headerLogo : teamsLogo
-        navItems : [
-          { title : 'Success Stories',  href : 'http://blog.koding.com',  name : 'stories' }
-          { title : 'Learn More',       href : '/Teams',                  name : 'learn' }
-          { title : 'SIGN IN',          href : '/Team/Login',             name : 'buttonized yellow login',  attributes : testpath : 'login-link' }
-        ]
-
-      @title = new KDCustomHTMLView
-        tagName : 'h1'
-        partial : "Koding for Teams!"
-
-      @subTitle = new KDCustomHTMLView
-        tagName   : 'p'
-        cssClass  : 'intro'
-        partial   : 'Onboard, develop, deploy, test and work together with your team right away, without a setup!'
-
-      @form = new TeamsSignupForm
-        cssClass : 'TeamsModal--middle login-form'
-        callback : (formData) ->
-
-          finalize = (email) ->
-            KD.utils.storeNewTeamData 'signup', formData
-            KD.singletons.router.handleRoute '/Team/Domain'
-
-            return  unless email
-
-            KD.utils.getProfile email,
-              error   : ->
-              success : (profile) ->
-                formData.profile = profile  if profile
-                KD.utils.storeNewTeamData 'signup', formData
-
-          { email } = formData
-          KD.utils.validateEmail { email },
-            success : -> formData.alreadyMember = no; do finalize
-            error   : -> formData.alreadyMember = yes; finalize email
-
-      @playVideoIcon = new KDCustomHTMLView
-        tagName  : 'span'
-        cssClass : 'icon play'
-        click    : ->
-          alert 'clicked'
-
-      @teamsHomeFeaturesSection = new TeamsHomeFeaturesSection
-
-      @features = new KDCustomHTMLView
-        cssClass : 'hidden'
-
-      @footer = new FooterView
-
-    else
-
-      @header = new MainHeaderView
-        headerLogo : teamsLogo
-        cssClass   : 'hasNotTeamAccess'
-        navItems  : [
-          { title : 'Blog',     href : 'http://blog.koding.com',  name : 'stories' }
-          { title : 'Teams',    href : '/Teams',                  name : 'learn' }
-          { title : 'SIGN IN',  href : '/Team/Login',             name : 'buttonized yellow login',  attributes : testpath : 'login-link' }
-        ]
-
-      @title = new KDCustomHTMLView
-        tagName : 'h1'
-        partial : 'Announcing Koding for Teams!'
-
-      @subTitle = new KDCustomHTMLView
-        tagName  : 'h2'
-        partial  : 'Imagine your-company.koding.comyour-university.koding.comyour-class.koding.comyour-project.koding.com'
-
-      @form = new TeamsLaunchForm
-        cssClass : 'TeamsModal--middle login-form pre-launch'
-        callback : (formData) =>
-          KD.utils.earlyAccess formData,
-            success : @bound 'earlyAccessSuccess'
-            error   : @bound 'earlyAccessFailure'
-
-      @features = new KDCustomHTMLView
-        tagName : 'ul'
-        partial : """
-
-
Reduce setup time for new team members.

-
Treat your infrastructure as code. Easy to manage and even easier to upgrade!

-
Predefine the compute resources that your team should have.

-
Collaborate using video/audio without the need to install anything!

-
Most cloud-providers are supported so you can use your existing infrastructure.

-
Integrate common services like GitHub, Pivotal, Asana, etc. into your Koding environment.

-
Learn more on our blog.

-          """
-
-      @playVideoIcon = new KDCustomHTMLView
-        cssClass : 'hidden'
-
-      @footer = new KDCustomHTMLView
-        cssClass : 'hidden'
-
-      @teamsHomeFeaturesSection = new KDCustomHTMLView
-        cssClass : 'hidden'
-
-      @animateTargets()
-      @comingSoon.show()
-
-
-
-  earlyAccessFailure: ({responseText}) ->
-
-    if responseText is 'Already applied!'
-      responseText = 'Thank you! We\'ll let you know when we launch it!'
-      @form.hide()
-      @comingSoon.hide()
-      @thanks.show()
-
-    new KDNotificationView
-      title    : responseText
-      duration : 3000
-
-
-  earlyAccessSuccess: ->
-
-    @form.hide()
-    @comingSoon.hide()
-    @thanks.show()
-    new KDNotificationView
-      title    : "We'll let you know when we launch it!"
-      duration : 3000
-
-
-  animateTargets: ->
-
-    $els = @subTitle.$('span i')
-    i    = 0
-    KD.utils.repeat 2000, ->
-      $els.css 'opacity', 0
-      $els[i].style.opacity = 1
-      i = if i is $els.length - 1 then 0 else i + 1
-
+        { email } = formData
+        KD.utils.validateEmail { email },
+          success : -> formData.alreadyMember = no; do finalize
+          error   : -> formData.alreadyMember = yes; finalize email

   pistachio: ->

-    hasTeamAccess = unless KD.config.hasTeamAccess then 'hasNotTeamAccess' else ''
-    hiddenClass   = unless KD.config.hasTeamAccess then 'hidden' else ''
-
     """
     {{> @header }}
-

-      {{> @title}}
-      {{> @subTitle}}
+

+
Create a team

       {{> @form}}
-      {{> @thanks}}
-      {{> @comingSoon}}
-      {{> @features}}
-

-
-
-

-

-        {{> @playVideoIcon}}
-

-

-    {{> @teamsHomeFeaturesSection}}
-    {{> @footer}}
-    """
+

+
+      Acceptable user policyCopyright/DMCA guidelinesTerms of servicePrivacy policy
+
+    """


:up: keymap bindings

:up: keymap bindings

related to the latest atom upd

--- dotfiles/config-atom/keymap.cson
+++ dotfiles/config-atom/keymap.cson
@@ -31,12 +31,11 @@
 # Atom Flight Manual:
 # http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson

-'atom-text-editor atom-workspace':
+'atom-text-editor':
+  'ctrl-alt-l': 'beautify:beautify-editor'
   'ctrl-shift-m': 'minifier:minify'
   'ctrl-shift-t': 'atom-terminal:open'
   'alt-shift-t': 'atom-terminal:open-project-root'
   'alt-j': 'fixmyjs'
   'alt-c': 'css-comb:comb'
   'alt-x': 'autoprefixer'
-'.editor':
-  'ctrl-alt-l': 'atom-beautify:beautify-editor'


Prepare message from string or object (#150)

--- src/rocketchat_driver.coffee
+++ src/rocketchat_driver.coffee
@@ -62,14 +62,24 @@

 		return r.updated

-	sendMessage: (text, room) =>
+	prepareMessage: (content, roomid) =>
+		@logger.debug "Preparing message from #{ typeof content }"
+		if typeof content is 'string'
+			message = {msg: content, rid: roomid}
+		else
+			message = content
+			message.rid = roomid
+		return message
+
+	sendMessage: (message, room) =>
 		@logger.info "Sending Message To Room: #{room}"
 		r = @getRoomId room
 		r.then (roomid) =>
-			@sendMessageByRoomId text, roomid
+			@sendMessageByRoomId message, roomid

-	sendMessageByRoomId: (text, roomid) =>
-		@asteroid.call('sendMessage', {msg: text, rid: roomid})
+	sendMessageByRoomId: (content, roomid) =>
+		message = @prepareMessage content, roomid
+		@asteroid.call('sendMessage', message)
 		.then (result)->
 			@logger.debug('[sendMessage] Success:', result)
 		.catch (error) ->


uses field as key

uses field as key

--- vendor/assets/javascripts/brainstem/brainstem-model.coffee
+++ vendor/assets/javascripts/brainstem/brainstem-model.coffee
@@ -225,10 +225,10 @@

   _linkCollection: (collectionName, models, collectionOptions, field) ->
     @_associatedCollections ?= {}
-    @_associatedCollections.collectionName ?= base.data.createNewCollection(collectionName, models, collectionOptions)
-
-    @_associatedCollections.collectionName.on 'add', => @_onAssociatedCollectionChange.call(this, field, arguments)
-    @_associatedCollections.collectionName.on 'remove', => @_onAssociatedCollectionChange.call(this, field, arguments)
+    @_associatedCollections.field ?= base.data.createNewCollection(collectionName, models, collectionOptions)
+
+    @_associatedCollections.field.on 'add', => @_onAssociatedCollectionChange.call(this, field, arguments)
+    @_associatedCollections.field.on 'remove', => @_onAssociatedCollectionChange.call(this, field, arguments)

   _onAssociatedCollectionChange: (field, collectionChangeDetails) =>
     @attributes[@constructor.associationDetails(field).key] = collectionChangeDetails[1].pluck('id')


GuestCleanerWorker: remove guestcleaner worker

--- config/main.prod.coffee
+++ config/main.prod.coffee
@@ -274,10 +274,10 @@
       nginx             :
         locations       : ["/xhr"]

-    guestCleaner        :
-      group             : "webserver"
-      supervisord       :
-        command         : "#{GOBIN}/guestcleanerworker -c #{configName}"
+    # guestCleaner        :
+      # group             : "webserver"
+      # supervisord       :
+        # command         : "#{GOBIN}/guestcleanerworker -c #{configName}"

     # clientWatcher       :
     #   group             : "webserver"


Only block scroll for fixed left-column

Only block scroll for fixed left-column

--- core/app/assets/javascripts/frontend/left-column.coffee
+++ core/app/assets/javascripts/frontend/left-column.coffee
@@ -1 +1 @@
-$('#left-column').preventScrollPropagation();
+$('.fixed #left-column').preventScrollPropagation();


Removed plugins.js (merged with Enactus.js)

--- config/variables.coffee
+++ config/variables.coffee
@@ -1,6 +1,6 @@
 variables = (req, res, next) -> # Set template globals
     date = new Date()
-    res.locals =
+    res.locals =
         page:
             title: 'LSE SU Enactus'
             description: 'London School of Economics and Political Science Student Union Enactus – Seeing possibilities. Taking action. Enabling progress.'
@@ -20,7 +20,6 @@
         scripts: [
             '//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js'
             '//cdnjs.cloudflare.com/ajax/libs/foundation/5.0.2/js/foundation.min.js'
-            '/js/plugins.js'
             '/js/enactus.js'
         ]
         contacts: [


Changed this for @

Changed this for @

--- core/app/backbone/collections/channel_activities.coffee
+++ core/app/backbone/collections/channel_activities.coffee
@@ -2,9 +2,9 @@
   model: Activity

   initialize: (models, opts) ->
-    this.channel = opts.channel;
+    @channel = opts.channel;

   url:  ()-> '/' + this.channel.get('created_by').username + '/channels/' + this.channel.get('id') + '/activities';
   link: ()-> '/' + this.channel.get('created_by').username + '/channels/' + this.channel.get('id') + '/activities';

-_.extend(ChannelActivities.prototype, AutoloadCollectionOnTimestamp);+_.extend(ChannelActivities.prototype, AutoloadCollectionOnTimestamp);


Show profile behind NDP

Show profile behind NDP

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -11,10 +11,7 @@
     # fact.on 'destroy', => @onFactRemoved fact.id

     fact.fetch success: =>
-      user = new User fact.get('created_by')
-      window.Channels.setUsernameAndRefreshIfNeeded user.get('username') # TODO: check if this can be removed
-      FactlinkApp.Sidebar.showForChannelsOrTopicsAndActivateCorrectItem(window.Channels, null, user)
-      @showSidebarProfile(user)
+      @showProfile fact.get('created_by').username

       newClientModal = new ClientModalLayout
       FactlinkApp.discussionModalRegion.show newClientModal


Don't assume that webpages have meta tags with descriptions.

--- src/scripts/http-info.coffee
+++ src/scripts/http-info.coffee
@@ -46,7 +46,7 @@
       unless errors
         $ = window.$
         title = $('head title').text().replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim()
-        description = $('head meta[name=description]').attr('content').replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim() || ''
+        description = $('head meta[name=description]')?.attr('content')?.replace(/(\r\n|\n|\r)/gm,'')?.replace(/\s{2,}/g,' ')?.trim() || ''

         if title and description and not process.env.HUBOT_HTTP_INFO_IGNORE_DESC
           msg.send "#{title}\n#{description}"


fix reassigning to an escalation policy

fix reassigning to an escalation policy

an indent bug meant triggered events would only be reassigned to
users... escalation policies would silently fail after creating the
initial incident.

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -182,16 +182,15 @@
                     }
                 }

-
-                pagerduty.put "/incidents", data , (err, json) ->
-                  if err?
-                    robot.emit 'error', err, msg
-                    return
-
-                  if json?.incidents.length == 1
-                    msg.reply ":pager: assigned to #{results.name}!"
-                  else
-                    msg.reply "Problem reassigning the incident :/"
+              pagerduty.put "/incidents", data , (err, json) ->
+                if err?
+                  robot.emit 'error', err, msg
+                  return
+
+                if json?.incidents.length == 1
+                  msg.reply ":pager: assigned to #{results.name}!"
+                else
+                  msg.reply "Problem reassigning the incident :/"
           , 10000

   robot.respond /(?:pager|major)(?: me)? ack(?:nowledge)? (.+)$/i, (msg) ->


Updated to supported languages as of 20120801.

This updates the supported languages to the ones listed in the [Language reference](https://developers.google.com/translate/v2/using_rest#language-params) as of 20120801.

Specifically it adds:

* Azerbaijani (az)
* Basque (eu)
* Bengali (bn)
* Esperanto (eo
* Georgian (ka)
* Gujarati (gu)
* Haitian Creole (ht)
* Kannada (kn)
* Latin (la)
* Tamil (ta)
* Telugu (te)
* Urdu (ur)

--- src/scripts/translate.coffee
+++ src/scripts/translate.coffee
@@ -9,6 +9,9 @@
   "af": "Afrikaans",
   "sq": "Albanian",
   "ar": "Arabic",
+  "az": "Azerbaijani",
+  "eu": "Basque",
+  "bn": "Bengali",
   "be": "Belarusian",
   "bg": "Bulgarian",
   "ca": "Catalan",
@@ -19,13 +22,17 @@
   "da": "Danish",
   "nl": "Dutch",
   "en": "English",
+  "eo": "Esperanto",
   "et": "Estonian",
   "tl": "Filipino",
   "fi": "Finnish",
   "fr": "French",
   "gl": "Galician",
+  "ka": "Georgian",
   "de": "German",
   "el": "Greek",
+  "gu": "Gujarati",
+  "ht": "Haitian Creole",
   "iw": "Hebrew",
   "hi": "Hindi",
   "hu": "Hungarian",
@@ -34,7 +41,9 @@
   "ga": "Irish",
   "it": "Italian",
   "ja": "Japanese",
+  "kn": "Kannada",
   "ko": "Korean",
+  "la": "Latin",
   "lv": "Latvian",
   "lt": "Lithuanian",
   "mk": "Macedonian",
@@ -52,9 +61,12 @@
   "es": "Spanish",
   "sw": "Swahili",
   "sv": "Swedish",
+  "ta": "Tamil",
+  "te": "Telugu",
   "th": "Thai",
   "tr": "Turkish",
   "uk": "Ukranian",
+  "ur": "Urdu",
   "vi": "Vietnamese",
   "cy": "Welsh",
   "yi": "Yiddish"


Moderation: Circular synonym definition is prevented

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -297,6 +297,11 @@
           create {title, group}, delegate, (err, tag) =>
             return callback err if err
             addSynonym tag
+        else if tag.status is 'synonym' #for preventing circular synonym definition
+          tag.fetchSynonym (err, synonym) =>
+            if String(synonym?.getId()) is String(@getId())
+              return callback new KodingError "Circular Synonym Definition!"
+            addSynonym tag
         else
           addSynonym tag


update batch actions JS to respond to dynamically added elements

for @amuntasim in #2731

--- app/assets/javascripts/active_admin/lib/batch_actions.js.coffee
+++ app/assets/javascripts/active_admin/lib/batch_actions.js.coffee
@@ -31,7 +31,7 @@
     else
       $(".paginated_collection").checkboxToggler()

-    $(".paginated_collection :checkbox").change ->
+    $(document).on 'change', '.paginated_collection :checkbox', ->
       if $(".paginated_collection :checkbox:checked").length
         $("#batch_actions_selector").aaDropdownMenu("enable")
       else


hub/raw server: fix serving path by looking at request path, not parsed path

--- src/smc-hub/share/raw.coffee
+++ src/smc-hub/share/raw.coffee
@@ -60,7 +60,7 @@

Public Projects

                     """
                     for row in result.rows
-                        out += "#{row.prefix}... (#{row.num})
"
+                        out += "#{row.prefix}... (#{row.num})
"
                     res.send(out)

     router.get /^\/[0-9a-z]$/, (req, res) ->
@@ -80,11 +80,11 @@
                     res.send(JSON.stringify(err))
                 else
                     out = """
-
Public Projects

+
Public Projects: #{c1}...

                     UP

                     """
                     for row in result.rows
-                        out += "#{row.prefix}... (#{row.num})
"
+                        out += "#{row.prefix}... (#{row.num})
"
                     res.send(out)

     router.get /^\/[0-9a-z]{2}$/, (req, res) ->
@@ -101,13 +101,13 @@
                     res.send(JSON.stringify(err))
                 else
                     out = """
-
Public Projects

+
Public Projects: #{c2}...

                     UP

                     Found #{result.rowCount}

                     """
                     for row in result.rows
                         pid = row.project_id
-                        out += "#{pid}
"
+                        out += "#{pid}
"
                     res.send(out)

     router.get '*', (req, res) ->
@@ -145,7 +145,7 @@
                     serve_raw_path(req, res, os_path.join(dir, path), info)

 serve_raw_path = (req, res, path, info) ->
-    if path.length > 0 and path[path.length - 1] == '/'
+    if path.length > 0 and req.path[req.path.length - 1] == '/'
         send_directory_listing(req, res, path, info)
         return
     fs.lstat path, (err, stats) ->


webapp/printing sagews: properly handle when file is in HOME or subdir

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -2075,7 +2075,8 @@
                                 url = mesg.url + "?nocache=#{Math.random()}"
                                 dialog.find(".salvus-file-printing-link").attr('href', url).text(pdf).show()
                                 if is_subdir
-                                    subdir_texfile = p.head + "/#{base}-sagews2pdf/tmp.tex"
+                                    {join} = require('path')
+                                    subdir_texfile = join(p.head, "#{base}-sagews2pdf", "tmp.tex")
                                     # if not reading it, tmp.tex is blank (?)
                                     salvus_client.read_file_from_project
                                         project_id : @project_id


When loading folders from the server, insert only new folders, and remove any folders not returned by the server.

The old behavior was simply to replace the full list of folders with the one returned by the server. This meant that even for folders that had not changed, the object representing them in the scope was replaced by a new one. This caused complications keeping track of which folder was open in the sidebar at a given time.

With the new behavior, folders that haven't changed will not change in the angularjs model layer. This makes more sense, even if the price to pay is a slightly more complicated logic.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -104,7 +104,20 @@
     $http.get("/api/folders.json?time=#{now.getTime()}")
     .success (data)->
       reset_timer()
-      $rootScope.folders = data.slice()
+
+      # Remove folders no longer existing in the server
+      if $rootScope.folders? && $rootScope.folders?.length > 0
+        for folder in $rootScope.folders
+          existing_folder = findSvc.find_folder folder.id, data
+          if !existing_folder?
+            index = $rootScope.folders.indexOf folder
+            $rootScope.folders.splice index, 1
+
+      # Add new folders
+      if data? && data.length? > 0
+        for folder in data
+          add_folder folder
+
       $rootScope.folders_loaded = true
     .error (data, status)->
       timerFlagSvc.start 'error_loading_folders' if status!=0
@@ -145,7 +158,7 @@
   #---------------------------------------------
   # PRIVATE FUNCTION: Push a feed in the feeds array if it isn't already present there.
   #
-  # If the feeds array is empty, create it anew, ensuring angularjs ng-repeat is triggered.
+  # If the feeds array has not been created in the root scope, create it.
   #
   # If the feed is already in the feeds array, its unread_entries attribute is updated instead of
   # pushing it in the array again.
@@ -161,6 +174,20 @@
         $rootScope.feeds.push feed

   #---------------------------------------------
+  # PRIVATE FUNCTION: Push a folder in the folders array if it isn't already present there.
+  #
+  # If the folders array has not been created in the root scope, create it.
+  #
+  # If the folder is already in the folders array, it is ignored
+  #---------------------------------------------
+  add_folder = (folder)->
+    if !$rootScope.folders || $rootScope.folders?.length == 0
+      $rootScope.folders = [folder]
+    else
+      old_folder = findSvc.find_folder folder.id
+      $rootScope.folders.push folder if !old_folder?
+
+  #---------------------------------------------
   # PRIVATE FUNCTION: Update the feeds and their unread counts, for feeds in a folder.
   #
   # Receives as arguments the folder and an array of feeds.


further ui clean up for android support

further ui clean up for android support

--- src/smc-webapp/file_use.cjsx
+++ src/smc-webapp/file_use.cjsx
@@ -502,7 +502,7 @@
         @actions('page').toggle_show_file_use()

     render_mark_all_read_button: ->
-        <Button key='mark_all_read_button' bsStyle='warning'
+        <Button key='mark_all_read_button'
             onClick={@click_mark_all_read}>
             <Icon name='check-square'/> Mark all Read
         </Button>
@@ -547,10 +547,10 @@
     render: ->

             <Row key='top'>
-                <Col sm={8}>
+                <Col sm={7}>
                     {@render_search_box()}
                 </Col>
-                <Col sm={4}>
+                <Col sm={5}>

                         {@render_mark_all_read_button()}