Strategy is a keyword

Strategy is a keyword

--- release/grammars/paket.dependencies.cson
+++ release/grammars/paket.dependencies.cson
@@ -75,7 +75,7 @@
   'keywords':
     'patterns': [
       {
-        'match': '\\b(source|github|nuget|gist|http|group)\\b'
+        'match': '\\b(source|github|nuget|gist|http|group|strategy)\\b'
         'name': 'keyword.other.fsharp'
       }
     ]


:art: Cleanup

:art: Cleanup

--- spec/masked_input_component_spec.cjsx
+++ spec/masked_input_component_spec.cjsx
@@ -41,7 +41,6 @@
       preventDefault: -> defaultPrevented = true

     unless defaultPrevented || key.length > 1
-      # key = if typeof key is 'number' then String.fromCharCode(key) else key
       prevVal = getInputValue()
       newVal = prevVal.substring(0, cursorPos) + key + prevVal.substr(cursorPos)
       getInput()._setSelection(cursorPos + 1)
@@ -470,7 +469,7 @@
       simulateFocus -> done()

     setupTests ->
-      describe 'setting an initial value', -> # TODO: Is this the name for this test?
+      describe 'the placeholder', ->
         context 'when a format is given', ->
           before ->
             format = 'mm/dd/yyyy'


Use camera position for feature visibility checking against shadows grid.    cliprect points will not be < 0, so features on the extremes could end up being shadowed improperly.

--- src/adventure/mapView.coffee
+++ src/adventure/mapView.coffee
@@ -146,7 +146,7 @@
   renderFeatures:(clipRect) ->
     for x in [clipRect.point.x ... clipRect.getRight()]
       for y in [clipRect.point.y ... clipRect.getBottom()]
-        continue if @map.dark and @shadows[y - clipRect.point.y][x - clipRect.point.x]
+        continue if @map.dark and @shadows[y - @camera.point.y][x - @camera.point.x]
         continue if not @game.getFeatures(x,y)
         feature = @getTopFeature(x, y)
         @drawTile(feature.icon, x, y) if feature and feature.icon


"process.openStdin()" -> "process.stdin.resume()"

--- png-guts.coffee
+++ png-guts.coffee
@@ -18,7 +18,8 @@
     typeWhitelist = {'IHDR', 'PLTE', 'IDAT', 'IEND'}

   process.stdout.write PNG_FILE_HEADER
-  reader = new PNGChunkReader process.openStdin()
+  process.stdin.resume()
+  reader = new PNGChunkReader process.stdin
   reader.on 'chunk', (type, raw) ->
     return if typeWhitelist and not (typeWhitelist[type]?)
     return if typeBlacklist[type]?


fix video playing

fix video playing

--- project/coffee/view/components/ObjectsList.coffee
+++ project/coffee/view/components/ObjectsList.coffee
@@ -23,6 +23,10 @@
         null

     beforeChange : (event, slick, currentSlide, nextSlide) =>
+        @$el.find('video').each ->
+            @.pause()
+            @.currentTime = 0
+
         @trigger 'beforeChange'
         null


Fixed bulk aktion.

Fixed bulk aktion.

--- app/assets/javascripts/app/controllers/ticket_overview.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.js.coffee
@@ -416,7 +416,7 @@
       noFieldset: true
     )

-    html.bind('js-submit', (e) =>
+    html.bind('submit', (e) =>
       e.preventDefault()
       @bulk_submit(e)
     )


Tabs - Fade out tab-pane and show it again after a timeout. It is necessary cause collapse truncation need some time to complete

--- app/assets/javascripts/martian_components/components/tabs.coffee
+++ app/assets/javascripts/martian_components/components/tabs.coffee
@@ -12,6 +12,14 @@
     @nav.addClass('odd') if @navItems.length % 2 > 0

     @scrollOnLoad()
+
+    $('a', @navItems).on 'show.bs.tab', (ev, a) =>
+      $(component).data('component').refresh() for component in @nestedComponents
+
+      tabPane = $($(ev.delegateTarget).attr('href'))
+      tabPane.css('opacity', 0)
+
+      setTimeout (=> tabPane.css('opacity', 1)), 1000

     $('a', @navItems).on 'shown.bs.tab', (ev, a) =>
       $(component).data('component').refresh() for component in @nestedComponents


add 'activated' event

add 'activated' event

--- app/assets/javascripts/typus_translate/views/tabs_view.js.coffee
+++ app/assets/javascripts/typus_translate/views/tabs_view.js.coffee
@@ -23,9 +23,12 @@

   showTab: ->
     className = ".lang-#{@lang}"
+    @tabs.find('a').removeClass('active')
+    @form.css({ 'min-height': @form.height() }) # prevents jumping
     @form.find('.translation').hide().removeClass('active')
-    @tabs.find('a').removeClass('active')
     @form.find(className).addClass('active').show()
+    @form.css({ 'min-height': '' })
+    $(window).trigger('translate:activated', [@lang])

   activateTab: (e) ->
     lang = $(e.target).data('lang')


Fix for eight ball syntax

Fix for eight ball syntax

--- scripts/eight-ball.coffee
+++ scripts/eight-ball.coffee
@@ -8,10 +8,11 @@
 #   None
 #
 # Commands:
-#   eightball|eight ball|eight-ball|8ball <query> - Ask the magic eight ball a question
+#   eightball|eight ball|eight-ball|8ball|8 ball| 8-ball|should I|I wonder if I should <query> - Ask the magic eight ball a question
 #
 # Author:
 #   ryanatwork
+#   pjcabrera

 ball = [
   "http://pjtrix.com/funbot/8ball/8ball01.png",
@@ -33,5 +34,5 @@
 ]

 module.exports = (robot) ->
-  robot.hear /(eight(ball| ball|-ball)|8ball|should I|I wonder if I should)(.*)/i, (msg) ->
+  robot.hear /((eight|8)(ball| ball|-ball)|should I|I wonder if I should)(.*)/i, (msg) ->
     msg.reply msg.random ball


colors!

colors!

--- toolbar.cson
+++ toolbar.cson
@@ -5,6 +5,8 @@
     callback: "application:new-file"
     tooltip: "New File"
     iconset: "ion"
+    style:
+      color: "tan"
   }
   {
     type: "button"
@@ -12,6 +14,8 @@
     iconset: "ion"
     tooltip: "Open..."
     callback: "application:open-file"
+    style:
+      color: "tan"
   }
   {
     type: "spacer"
@@ -27,6 +31,7 @@
     type: "button"
     icon: "circuit-board"
     callback: "git-diff:toggle-diff-list"
+    tooltip: "Toggle Diff List"
     style:
       color: "#FA4F28"
   }
@@ -55,6 +60,8 @@
     iconset: "icomoon"
     tooltip: "Save As Project"
     callback: "project-manager:save-project"
+    style:
+      color: "slateblue"
   }
   {
     type: "button"
@@ -62,6 +69,8 @@
     iconset: "icomoon"
     tooltip: "Open Project"
     callback: "project-manager:list-projects"
+    style:
+      color: "slateblue"
   }
   {
     type: "spacer"


Update fussball.de table widget

Update fussball.de table widget

--- app/assets/javascripts/fussball_de_widget.coffee
+++ app/assets/javascripts/fussball_de_widget.coffee
@@ -1,7 +1,7 @@
 fussballDeWidget =
   tableCFB: ->
     new fussballdeWidgetAPI()
-      .showWidget('tableCFB', '01TB9KHR74000000VS541L4QVTSA3RAV')
+      .showWidget('tableCFB', '021MCNLGJG000000VS541L4FVT19MEPC')

   scheduleCFB: ->
     new fussballdeWidgetAPI()


sks-134 change trend icons to bootstrap3 ones

--- app/assets/javascripts/helpers/helpers.js.coffee
+++ app/assets/javascripts/helpers/helpers.js.coffee
@@ -1,19 +1,15 @@
 Ember.Handlebars.registerBoundHelper 'trend', (value, options) ->
     trendClasses = ""
-    trend = ""
     if value
         switch value
             when 'steady'
-                trend = '↔'
-                trendClasses = 'trend steady'
+                trendClasses = 'trend steady glyphicon glyphicon-minus'
             when 'upward'
-              trend = '↗'
-              trendClasses = 'trend upward'
+              trendClasses = 'trend upward glyphicon glyphicon-chevron-up'
             when 'downward'
-              trend = '↘'
-              trendClasses = 'trend downward'
+              trendClasses = 'trend downward glyphicon glyphicon-chevron-down'

-       new Handlebars.SafeString("#{trend}")
+       new Handlebars.SafeString("")

 Ember.Handlebars.registerBoundHelper 'avatar', (gravatar) ->
   if gravatar


clean up canvas-graph code

clean up canvas-graph code

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -1,5 +1,3 @@
-# $ = window.jQuery
-
 class CanvasGraph

   DEBUG = false
@@ -40,9 +38,6 @@
   disableMarking: ->
     # console.log 'CANVAS GRAPH: disableMarking()'
     @markingDisabled = true
-    # @canvas.removeEventListener 'mousedown', (e) => @onMouseDown(e)
-    # @canvas.removeEventListener 'touchstart', (e) => @addMarkToGraph(e)
-    # @canvas.removeEventListener 'mousemove', (e) => @onMouseMove(e) # TODO: FIX (disabled for now)

   enableMarking: ->
     # console.log 'CANVAS GRAPH: enableMarking()'
@@ -66,14 +61,11 @@
     # return # just for now
     return if @markingDisabled
     return if classifier.el.find('#graph').hasClass('is-zooming')
-    # @zoomLevel = classifier.zoomLevel
-    # @zoomRanges = classifier.zoomRanges
     val = +classifier.el.find("#ui-slider").val()
     xClick = e.pageX - e.target.getBoundingClientRect().left - window.scrollX
     yClick = e.pageY - e.target.getBoundingClientRect().top - window.scrollY

     @plotPoints(val, val+@zoomRanges[@zoomLevel])
-    # @rescaleMarks(val, val+zoomRanges[@zoomLevel])

     if xClick < @leftPadding
       # draw triangle
@@ -138,8 +130,6 @@
     return Math.sqrt( sum / data.length )

   mean: (data) ->
-    # console.log 'mean()'
-    # console.log 'length: ', data.length
     sum = 0
     for value in data
       sum = sum + value
@@ -156,19 +146,6 @@
     for entry in [@prevMarks...]
       # console.log '[',entry.xL,',',entry.xR,']' # DEBUG
       @highlightCurve(entry.xL,entry.xR)
-
-    # DEBUG
-    # @zoomOut( =>
-    #   maxMarks = 5
-    #   minMarks = 1
-    #   howMany = Math.floor( Math.random() * (maxMarks-minMarks) + minMarks )
-    #   # console.log 'randomly generating ', howMany, ' (fake) marks' # DEBUG
-    #   @generateFakePrevMarks( howMany )
-    #   for entry in [@prevMarks...]
-    #     # console.log '[',entry.xL,',',entry.xR,']' # DEBUG
-    #     @highlightCurve(entry.xL,entry.xR)
-    #   return howMany
-    # )

   generateFakePrevMarks: (n) ->
     minWid = 0.5 # [days]
@@ -203,7 +180,6 @@
           @ctx.fillRect(x,y,2,2)

   plotPoints: (xMin = @smallestX, xMax = @largestX, yMin = @smallestY, yMax = @largestY) ->
-    # console.log 'plotPoints(): [',xMin,',',xMax,']'
     @xMin = xMin
     @xMax = xMax
     @yMin = yMin
@@ -212,8 +188,6 @@

     # get necessary values from classifier
     val = +classifier.el.find('#ui-slider').val()
-    # zoomRanges = classifier.zoomRanges
-    # @zoomLevel  = classifier.zoomLevel

     # draw points
     for i in [0...@dataLength]
@@ -226,13 +200,6 @@

     @drawHighlights()

-    # # draw y-axis gradient
-    # gradient = @ctx.createLinearGradient(0,0,60,0);
-    # gradient.addColorStop(0,'rgba(0,0,0,1.0)');
-    # gradient.addColorStop(1,'rgba(0,0,0,0.0');
-    # @ctx.fillStyle = gradient
-    # @ctx.fillRect(0,0,60,@canvas.height)
-
     if $('#graph-container').hasClass('showing-prev-data')
       @showPrevMarks()

@@ -244,7 +211,6 @@
     @scale = (parseFloat(@largestX) - parseFloat(@smallestX)) / (parseFloat(@xMax) - parseFloat(@xMin))
     @rescaleMarks(xMin, xMax)

-
     return

   rescaleMarks: (xMin, xMax) ->
@@ -253,8 +219,6 @@
     # draw marks
     if @marks
       for mark in @marks.all
-        # scaledMin = ((mark.dataXMinRel - xMin) / (xMax - xMin)) * @canvas.width
-        # scaledMax = ((mark.dataXMaxRel - xMin) / (xMax - xMin)) * @canvas.width
         scaledMin = ((parseFloat(mark.dataXMinRel) + parseFloat(@toDays(@leftPadding)) - parseFloat(xMin) - parseFloat(val) ) / (parseFloat(xMax) - parseFloat(xMin)) ) * parseFloat(@canvas.width-@leftPadding)
         scaledMax = ((parseFloat(mark.dataXMaxRel) + parseFloat(@toDays(@leftPadding)) - parseFloat(xMin) - parseFloat(val) ) / (parseFloat(xMax) - parseFloat(xMin)) ) * parseFloat(@canvas.width-@leftPadding)
         #                                ^ prevents from moving towards left                          ^ prevents marks from moving towards right
@@ -262,25 +226,6 @@
         mark.element.style.left = parseFloat(scaledMin) + "px"
         mark.save(scaledMin, scaledMax)

-    # console.log """
-    #               -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-    #               ---------------- RESCALE MARKS ----------------
-    #                                     val: #{val}  <--- slider value
-    #                       canvasGraph.scale: #{@scale}
-    #                                    xMin: #{xMin}  <------------- lightcurve display limits
-    #                                    xMax: #{xMax}
-    #                               scaledMin: #{scaledMin} <---/------ mark limits
-    #                               scaledMax: #{scaledMax} <--/
-    #                        mark.dataXMinRel: #{mark.dataXMinRel} <-- data limits
-    #                        mark.dataXMaxRel: #{mark.dataXMaxRel}
-    #                       mark width (data): #{(mark.dataXMaxRel-mark.dataXMinRel)}
-    #                     mark width (canvas): #{mark.element.style.width}       <----- CSS style
-    #                 mark.element.style.left: #{mark.element.style.left}
-    #               -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-    #             """
-
-
-    # @highlightCurve(10,14) # test
     @scale = (@largestX - @smallestX) / (@xMax - @xMin)
     return

@@ -321,10 +266,6 @@
     classifier.el.find('#graph').addClass('is-zooming')
     [cMin, cMax] = [@xMin, @xMax]

-    # # DEBUG CODE
-    # @plotPoints(wMin,wMax)
-    # @rescaleMarks(wMin,wMax)
-
     zoom = setInterval (=>
       @plotPoints(cMin,cMax)
       @rescaleMarks(cMin,cMax)
@@ -455,14 +396,6 @@
     # REQUIRED FOR MEAN NORMALIZATION
     yMean = @mean(@data.y)

-    # # DRAW MEAN LINE (DEBUG PURPOSE)
-    # yPos = -@toCanvasYCoord(yMean) + @canvas.height
-    # @ctx.moveTo( 0, yPos )
-    # @ctx.lineTo( @canvas.width, yPos ) # minor tick
-    # @ctx.lineWidth = 1
-    # @ctx.strokeStyle = tickColor
-    # @ctx.stroke()
-
     for tick, i in [yTicks...]
       continue if i is 0               # skip first value
       continue if i is yTicks.length-1 # skip last value


Add custom belt color for Code Ninja hero

--- app/lib/surface/Lank.coffee
+++ app/lib/surface/Lank.coffee
@@ -5,6 +5,7 @@
 Label = require './Label'
 AudioPlayer = require 'lib/AudioPlayer'
 {me} = require 'core/auth'
+ThangType = require 'models/ThangType'

 # We'll get rid of this once level's teams actually have colors
 healthColors =
@@ -57,7 +58,7 @@
     'level:set-letterbox': 'onSetLetterbox'
     'surface:ticked': 'onSurfaceTicked'
     'sprite:move': 'onMove'
-
+
   constructor: (@thangType, options={}) ->
     super()
     spriteName = @thangType.get('name')
@@ -66,9 +67,8 @@
     @gameUIState = @options.gameUIState
     @handleEvents = @options.handleEvents
     @setThang @options.thang
-    if @thang?
-      options = @thang?.getLankOptions?()
-      @options.colorConfig = options.colorConfig if options and options.colorConfig
+    @setColorConfig()
+
     console.error @toString(), 'has no ThangType!' unless @thangType

     # this is a stub, use @setSprite to swap it out for something else later
@@ -84,6 +84,18 @@
     if @thangType.isFullyLoaded() then @onThangTypeLoaded() else @listenToOnce(@thangType, 'sync', @onThangTypeLoaded)

   toString: -> "<Lank: #{@thang?.id}>"
+
+  setColorConfig: ->
+    return unless colorConfig = @thang?.getLankOptions?().colorConfig
+    if @thangType.get('original') is ThangType.heroes['code-ninja']
+      unlockedLevels = me.levels()
+      if '5522b98685fca53105544b53' in unlockedLevels  # vital-powers, start of course 5
+        colorConfig.belt = {hue: 0.4, saturation: 0.75, lightness: 0.25}
+      else if '56fc56ac7cd2381f00d758b4' in unlockedLevels  # friend-and-foe, start of course 3
+        colorConfig.belt = {hue: 0.067, saturation: 0.75, lightness: 0.5}
+      else
+        colorConfig.belt = {hue: 0.167, saturation: 0.75, lightness: 0.4}
+    @options.colorConfig = colorConfig

   onThangTypeLoaded: ->
     @stillLoading = false
@@ -648,7 +660,7 @@
   addMark: (name, layer, thangType=null) ->
     @marks[name] ?= new Mark name: name, lank: @, camera: @options.camera, layer: layer ? @options.groundLayer, thangType: thangType
     @marks[name]
-
+
   removeMark: (name) ->
     @marks[name].destroy()
     delete @marks[name]


activity: add editInputValue state variable to chat item

--- client/activity/lib/components/chatlistitem/index.coffee
+++ client/activity/lib/components/chatlistitem/index.coffee
@@ -18,20 +18,22 @@
 module.exports = class ChatListItem extends React.Component

   @defaultProps =
-    hover      : no
-    editMode   : no
-    isDeleting : no
-    isMenuOpen : no
+    hover          : no
+    editMode       : no
+    isDeleting     : no
+    isMenuOpen     : no
+    editInputValue : ''

   constructor: (props) ->

     super props

     @state =
-      hover      : @props.hover
-      editMode   : @props.editMode
-      isDeleting : @props.isDeleting
-      isMenuOpen : @props.isMenuOpen
+      hover          : @props.hover
+      editMode       : @props.editMode
+      isDeleting     : @props.isDeleting
+      isMenuOpen     : @props.isMenuOpen
+      editInputValue : @props.message.get 'body'

   isEditedMessage: ->
@@ -123,7 +125,7 @@

   cancelEdit: ->

-    @setState editMode: no
+    @setState editMode: no, editInputValue: @props.message.get('body')

   onMenuToggle: (isMenuOpen) -> @setState { isMenuOpen }


notification controller: fix login bug

notification controller: fix login bug

--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -19,7 +19,7 @@

     @getSingleton('mainController').on "AccountChanged", =>
       @off 'NotificationHasArrived'
-      @notificationChannel?.off().unsubscribe()
+      @notificationChannel?.close().off()
       @setListeners()

   setListeners:->


Make change-to-line-end get insert-after

Horrible. I don't want to talk about it.

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -153,6 +153,7 @@
     operator.execute(count)

     @vimState.activateInsertMode()
+    @editor.moveCursorRight() if @motion.constructor.name == 'MoveToLastCharacterOfLine'

   # Public: Marks this as complete and saves the motion.
   #


Remove other in treemap chart

Remove other in treemap chart

--- app/assets/javascripts/charts/treemap-chart.coffee
+++ app/assets/javascripts/charts/treemap-chart.coffee
@@ -35,18 +35,18 @@
     @total = d3.sum @data, (d) -> return d.amount

     # filter data to unify small parts in a 'Others' item
-    other = 0
-    @data.forEach (d, i) =>
-      if d.amount/@total < 0.003
-        d.remove = true
-        other += d.amount
-    if other > 0
-      @data = @data.filter (d) -> return d.remove != true
-      @data.push
-        id: 'ob.other'
-        entity: 'Otros'
-        amount: other
-        type: 'others'
+    # other = 0
+    # @data.forEach (d, i) =>
+    #   if d.amount/@total < 0.001
+    #     d.remove = true
+    #     other += d.amount
+    # if other > 0
+    #   @data = @data.filter (d) -> return d.remove != true
+    #   @data.push
+    #     id: 'ob.other'
+    #     entity: 'Otros'
+    #     amount: other
+    #     type: 'others'

     # get sizes
     @getSizes()


Fix #1423 - extname is deprecated

Fix #1423 - extname is deprecated

--- src/beautifiers/index.coffee
+++ src/beautifiers/index.coffee
@@ -140,19 +140,31 @@
     ) or beautifiers[0]
     return beautifier

+  getExtension : (filePath) ->
+    if filePath
+      return path.extname(filePath).substr(1)
+
+  getLanguages : (grammar, filePath) ->
+    # Get language
+    fileExtension = @getExtension(filePath)
+
+    if fileExtension
+      languages = @languages.getLanguages({grammar, extension: fileExtension})
+    else
+      languages = @languages.getLanguages({grammar})
+
+    logger.verbose(languages, grammar, fileExtension)
+
+    return languages
+
   getLanguage : (grammar, filePath) ->
-    # Get language
-    fileExtension = path.extname(filePath)
-    # Remove prefix "." (period) in fileExtension
-    fileExtension = fileExtension.substr(1)
-    languages = @languages.getLanguages({grammar, extension: fileExtension})
-    logger.verbose(languages, grammar, fileExtension)
+    languages = @getLanguages(grammar, filePath)
+
     # Check if unsupported language
-    if languages.length < 1
-      return null
-    else
-      # TODO: select appropriate language
+    if languages.length > 0
       language = languages[0]
+
+    return language

   getOptionsForLanguage : (allOptions, language) ->
     # Options for Language
@@ -241,15 +253,10 @@
         logger.info('beautify', text, allOptions, grammar, filePath, onSave)
         logger.verbose(allOptions)

-        # Get language
-        fileExtension = path.extname(filePath)
-        # Remove prefix "." (period) in fileExtension
-        fileExtension = fileExtension.substr(1)
-        languages = @languages.getLanguages({grammar, extension: fileExtension})
-        logger.verbose(languages, grammar, fileExtension)
+        language = @getLanguage(grammar, filePath)

         # Check if unsupported language
-        if languages.length < 1
+        if !language
           unsupportedGrammar = true

           logger.verbose('Unsupported language')
@@ -260,18 +267,13 @@
             # not intended to be beautified
             return resolve( null )
         else
-          # TODO: select appropriate language
-          language = languages[0]
-
           logger.verbose("Language #{language.name} supported")

           # Get language config
           langDisabled = atom.config.get("atom-beautify.#{language.namespace}.disabled")

-
           # Beautify!
           unsupportedGrammar = false
-

           # Check if Language is disabled
           if langDisabled
@@ -370,6 +372,7 @@
           if atom.config.get("atom-beautify.general.muteUnsupportedLanguageErrors")
             return resolve( null )
           else
+            fileExtension = @getExtension(filePath)
             repoBugsUrl = pkg.bugs.url
             title = "Atom Beautify could not find a supported beautifier for this file"
             detail = """


(WIP) add stub methods for interior panel toggling

DOM lookups are pretty sloppy ATM!

--- app/assets/javascripts/panels/image_library.coffee
+++ app/assets/javascripts/panels/image_library.coffee
@@ -1,4 +1,5 @@
 class CMSimple.Panels.ImageLibrary extends Mercury.Panel
+
   @toggle: (region)->
     @instance ?= new CMSimple.Panels.ImageLibrary()
     @instance.toggle()
@@ -8,6 +9,30 @@
     @button = $('.mercury-insertMedia-button')
     @loadContent JST['views/image_library']()

+    # MK junk!
+    @testActions()
+
   toggle: ->
     super
     @resize() if @visible
+
+
+  # Gabe, these are some test actions for the info bar and uploader form
+  # Let's discuss when you have a chance, need to be optimized a bunch!!
+  testActions: ->
+    $('.media-actions > .info').on('click', @toggleInfo)
+    $('.media-info > .close').on('click', @toggleInfo)
+    $('#add_image').on('click', @toggleUploader)
+
+
+  toggleInfo: (e) ->
+    e.preventDefault()
+    info = $(e.target).parents('li').first().find('.media-info')[0]
+    $(info).toggleClass('active')
+
+  toggleUploader: (e) ->
+    target = $(e.target)
+    form = $(target.siblings('.add-image-action')[0])
+    form.toggleClass('active')
+    if form.hasClass('active') then target.text('Cancel') else target.text('Add Image')
+


Use powerlognorm function by default instead of genlogistic

--- app/static/script/controller/mineral-ctrl.coffee
+++ app/static/script/controller/mineral-ctrl.coffee
@@ -23,7 +23,7 @@
         $scope.chart.src = "/#{config.data_dir}/tsv/#{$scope.minerals[$scope.mineral]}"

     $scope.functions = {}
-    $scope.currentFunction = 'genlogistic'
+    $scope.currentFunction = 'powerlognorm'
     $http.get('/api/v1/estimate')
       .success (response) ->
         $scope.functions = response


Updated config values

Updated config values

--- docpad.coffee
+++ docpad.coffee
@@ -15,12 +15,10 @@
 		# Specify some site properties
 		site:
 			# The production url of our website
-			url: "http://website.com"
+			url: "http://drskipper.github.io/"

 			# Here are some old site urls that you would like to redirect from
 			oldUrls: [
-				'www.website.com',
-				'website.herokuapp.com'
 			]

 			# The default title of our website


fix spec

fix spec

--- spec/project-folder-spec.coffee
+++ spec/project-folder-spec.coffee
@@ -264,7 +264,7 @@
       spyOn(view, "getSelectedItem").andReturn(itemDirNormalDir1)
       spyOn(atom, "open")
       view.openInNewWindow()
-      expect(atom.open).toHaveBeenCalledWith({pathsToOpen: [normalDir1], newWindow: true})
+      expect(atom.open).toHaveBeenCalledWith(pathsToOpen: [normalDir1], newWindow: true, devMode: atom.inDevMode())

   describe "user defined project-group", ->
     userConfigEditor = null


Add a quiz sample to tools.js file

Add a quiz sample to tools.js file

Since the quiz hadnle the embed code from admin, we need to test one quiz
directly on test harness, so adding this does not affect the quiz syndication at
all

--- app/assets/javascripts/syndication/tools.js.coffee
+++ app/assets/javascripts/syndication/tools.js.coffee
@@ -51,9 +51,11 @@

     quiz:
       en:
-        path: "en/tools/quiz"
-      cy:
-        path: "cy/tools/quiz"
+        path: "en/tools/quiz/widgets/22/start"
+      cy:
+        path: "cy/tools/quiz/widgets/22/start"
+      width: "100%"
+      height: "2000px"
       title: "Quiz"

     christmas_planner:


Removed unused stuff.

Removed unused stuff.

--- app/assets/javascripts/views/clouds/chat.js.coffee
+++ app/assets/javascripts/views/clouds/chat.js.coffee
@@ -24,9 +24,6 @@

   # Binds events on certain elemnts
   bindEvents: ->
-    # @.$('textarea.chat-message-content').BetterGrow
-    #   initial_height: 18
-    #   do_not_enter: null
     @.$('textarea.chat-message-content').expandingTextarea().keydown (e) =>
       @resizeElements()
       if e.which == 13 and e.shiftKey == false
@@ -53,7 +50,7 @@
       @refreshPresence(payload)

   # Creates and saves a new message and then appends it to
-  # the DOM for the current topic.
+  # the DOM for the current topic.
   #
   # args - The arguments to be passed to the message model
   # do_save - Boolean determining if the model should be saved or not


drone speed calculation

drone speed calculation

--- packages/desc/desc.coffee
+++ packages/desc/desc.coffee
@@ -21,6 +21,7 @@
   ATTR_KINETICDAMAGE: 117
   ATTR_THERMALDAMAGE: 118
   ATTR_LOCKRANGE: 76
+  ATTR_MAXVELOCITY: 37
   # Missiles
   ATTR_MISSILEDAMAGEMULTIPLIER: 212
   ATTR_FLIGHTTIME: 281
@@ -137,7 +138,7 @@
     tank

   getNavigation: ->
-    attr = @getShipAttributes [37, 552]
+    attr = @getShipAttributes [@ATTR_MAXVELOCITY, 552]

     navigation = {speed: attr[37], sig: attr[552]}

@@ -257,6 +258,7 @@
             result.drones = {}
             result.drones.dps = 0
             result.drones.range = @dogmaContext.getCharacterAttribute(@ATTR_DRONECONTROLRANGE) / 1000
+            result.drones.speed = @dogmaContext.getDroneAttribute(d.typeID, @ATTR_MAXVELOCITY)
           result.drones.dps += dps

     _.each result, (item) ->


fixed bug in PagingScrollElement

fixed bug in PagingScrollElement

--- src/art/engine/elements/paging_scroll_element.coffee
+++ src/art/engine/elements/paging_scroll_element.coffee
@@ -419,7 +419,13 @@
         #     atEndEdge: atEndEdge

         if referenceFrame.page && 0 > pages.indexOf referenceFrame.page
-          console.warn "PagingScrollElement#pages setter: New page list does not contain the current referenceFrame. ALWAYS include the current referenceFrame when setting pages. Screen will jump!"
+          console.warn(
+            """
+            PagingScrollElement#pages setter: New page list does not contain the current referenceFrame. ALWAYS include the current referenceFrame when setting pages. Screen will jump!
+
+            page keys: #{inspect (page.key for page in pages)}
+            """
+          )

         if referenceFrame.page != page || referenceFrame.atEndEdge != atEndEdge
           # setReferenceFrame will call _updatePagesSplit
@@ -573,7 +579,7 @@
         totalSize += @getMainCoordinate page.getCurrentSize()
         break if totalSize >= pixelsOnScreen

-      if totalSize < pixelsOnScreen
+      if totalSize < pixelsOnScreen && pages.length > 0 && totalSize > 0
         averagePageSize = totalSize / pages.length
         pixelsLeft = pixelsOnScreen - totalSize
         count += ceil pixelsLeft / averagePageSize
@@ -589,7 +595,7 @@
         totalSize += @getMainCoordinate page.getCurrentSize()
         break if totalSize >= pixelsOnScreen

-      if totalSize < pixelsOnScreen
+      if totalSize < pixelsOnScreen && pages.length > 0 && totalSize > 0
         averagePageSize = totalSize / pages.length
         pixelsLeft = pixelsOnScreen - totalSize
         count += ceil pixelsLeft / averagePageSize


Fixing icons for new version of font awesome

--- lms/static/coffee/src/instructor_dashboard/queries.coffee
+++ lms/static/coffee/src/instructor_dashboard/queries.coffee
@@ -304,10 +304,10 @@
     $('.emailWidget.queryTableBody tr').remove()

   rename_button_click:(event) ->
-    $saveCancelEditButton = $ _.template('

-          <%= labelSave %>

+    $saveCancelEditButton = $ _.template('

+          <%= labelSave %>

            <%= labelCancel %>
', {labelSave: 'Save' ,labelCancel: 'Cancel' })
-    $renameBtn = $ _.template('

+    $renameBtn = $ _.template('

        <%= label %>
', {label: 'Rename'})
     targ = event.target
     while (!targ.classList.contains('editName'))
@@ -367,7 +367,7 @@
     rows = table[0].children
     row = table[0].insertRow(rows)
     row.setAttribute('groupQuery', id)
-    $renameBtn = $ _.template('

+    $renameBtn = $ _.template('

        <%= label %>
', {label: 'Rename'})
     for num in [0..1]
       cell = row.insertCell(num)
@@ -381,7 +381,7 @@
     $renameBtn.click (event) =>
       @rename_button_click(event)

-    $loadBtn = $ _.template('

+    $loadBtn = $ _.template('

        <%= label %>
', {label: 'Load'})
     $loadBtn.click (event) =>
       @delete_temporary()
@@ -422,7 +422,7 @@
     $td.append($loadBtn)
     row.appendChild($td[0])
     $deleteBtn = $(_.template('

-       <%= label %>
', {label: 'Delete'}))
+       <%= label %>
', {label: 'Delete'}))

     $deleteBtn.click (event) =>
       targ = event.target
@@ -470,7 +470,7 @@
     @get_students (error, students) =>
       if error
         $broken_icon = $ _.template('

-           <%= label %>
',
+           <%= label %>
',
           {label: gettext("Sorry, we're having a problem with this query.
             Please delete this row and try again.")})
         tr.children()[4].innerHTML = $broken_icon[0].outerHTML
@@ -546,12 +546,12 @@
         cell.id = item['id']
     progressCell = row.insertCell(4)
     $progress_icon = $ _.template('

-      <%= label %>
',
+      <%= label %>
',
       {label: 'Working'})
-    $done_icon = $ _.template('

+    $done_icon = $ _.template('

        <%= label %>
', {label: 'Done'})
     $broken_icon = $ _.template('

-       <%= label %>
',
+       <%= label %>
',
       {label: gettext("Sorry, we're having a problem with this query.
         Please delete this row and try again.")})
     if arr.length == 4
@@ -564,7 +564,7 @@
         row.classList.remove('working')
       else
         progressCell.innerHTML = $progress_icon[0].outerHTML
-    $removeBtn = $(_.template('

+    $removeBtn = $(_.template('

        <%= label %>
', {label: 'Remove'}))
     $removeBtn.click (event) =>
       targ = event.target


Fix error in playing victory sound

Fix error in playing victory sound

--- app/views/play/level/modal/HeroVictoryModal.coffee
+++ app/views/play/level/modal/HeroVictoryModal.coffee
@@ -222,9 +222,10 @@
   initializeAnimations: ->
     if @level.get('type', true) is 'hero'
       @updateXPBars 0
+    #playVictorySound = => @playSound 'victory-title-appear'  # TODO: actually add this
     @$el.find('#victory-header').delay(250).queue(->
       $(@).removeClass('out').dequeue()
-      @playSound 'victory-title-appear'  # TODO: actually add this
+      #playVictorySound()
     )
     complete = _.once(_.bind(@beginSequentialAnimations, @))
     @animatedPanels = $()


fix minor annoyance with dev menu

fix minor annoyance with dev menu

Former-commit-id: 8c86871a520b6752bcac0e9b26397be748e1c182 [formerly bb5c07d2138adc09b7608366e92869b5e1cfd25a] [formerly 64eae360b0b037ce3f9b22cf2bc14538f79fe5d6 [formerly da481f85ab2cdbf93007b194e4e78453f1691db6]] [formerly 3f50fd0f01cf843e1a4e647d8304c955e2354d34 [formerly 1c0705651720930b596061202afa67b5dbdd3620] [formerly a70232eb0445169ef8a00fbb8e7b6be6631413c3 [formerly a70232eb0445169ef8a00fbb8e7b6be6631413c3 [formerly 30a0f1363d0a1043eaf4586285356c9d82bde33a]]]]
Former-commit-id: f2882541daae282d1c628187b0c8eee5e5f92374 [formerly 1e0f7175b2e96329c1f8320f5fc6c42fba937d36] [formerly 9e3c419e564ac5c409c8ac4f47931f6919780da3 [formerly 56b6408c9eec81e563cfb6eed2ae57b1961a69ff]]
Former-commit-id: 7aad66338bfe4abf464165a128c2a369a7427ab2 [formerly 0b2cdb6c0a14ea92ae8fb9ad0f0863adab9fe6c5]
Former-commit-id: a1d862e292f783338a3020176390e37ad1114acb
Former-commit-id: a63cbdef3f56fd0737895be0be1af7c705f8962b

--- @client/development.coffee
+++ @client/development.coffee
@@ -59,11 +59,10 @@

           DIV
             style:
-              display: 'inline-block'
+              width: '100%'

             if @local.hover_second == 'change subdomain'
-              UL null,
-
+              UL null,
                 for sub, idx in subdomains
                   LI
                     style:
@@ -80,8 +79,8 @@
                         display: 'inline-block'
                       sub.name

-            if @local.hover_second == 'change application'
-              UL null,
+            else if @local.hover_second == 'change application'
+              UL null,

                 for app, idx in apps.apps
                   LI


bind_keys: add C-# to load a die into the dropper regardless of focus.

--- oghma/bind_keys.coffee
+++ oghma/bind_keys.coffee
@@ -26,8 +26,19 @@

   for k, sides of dice_map
     do ( sides ) ->
-      O.table.onKey( k, ( x, y, k, e ) ->
-        O.action.roll_die( sides, x, y, e.shiftKey )
+      O.table.addBinding(
+        key:  k
+        ctrl: false
+        handler: ( x, y, k, e ) ->
+          O.action.roll_die( sides, x, y, e.shiftKey )
+      )
+      O.keymap.addBinding(
+        key:  k
+        ctrl: true
+        handler: ->
+          O.table.load_dropper( ( x, y, e ) ->
+            O.action.roll_die( sides, x, y, e.shiftKey )
+          )
       )

   # +


menu/dice: Code cleanup; remove weird sided dice.

--- oghma/menu/dice.coffee
+++ oghma/menu/dice.coffee
@@ -24,32 +24,35 @@
 # @author Christopher Alfeld (calfeld@calfeld.net)
 # @copyright 2013 Christopher Alfeld
 Oghma.Menu.dice = ( O ) ->
-  dice = [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 20, 100 ]
+  dice = [ 4, 6, 8, 10, 12, 20, 100 ]
   items = []
+  roll_die = ( sides, x, y, e ) ->
+    is_private = e.shiftKey
+    if is_private
+      r = 18
+      font_size = 18
+      visible_to = [ O.me().gets( 'name' ), O.GM ]
+    else
+      r = 25
+      font_size = 24
+      visible_to = null
+    O.tableverse.create( 'dice',
+      sides:      sides
+      x:          x
+      y:          y
+      r:          r
+      font_size:  font_size
+      visible_to: visible_to
+    )
+    null
+
   for die in dice
     do ( die ) =>
       items.push(
         text: "d#{die}"
         handler: ( item, e ) =>
           O.load_dropper( ( x, y, e ) ->
-            is_private = e.shiftKey
-            if is_private
-              r = 18
-              font_size = 18
-              visible_to = [ O.me().gets( 'name' ), O.GM ]
-            else
-              r = 25
-              font_size = 24
-              visible_to = null
-            O.tableverse.create( 'dice',
-              sides:      die
-              x:          x
-              y:          y
-              r:          r
-              font_size:  font_size
-              visible_to: visible_to
-            )
-            null
+            roll_die( die, x, y, e )
           )
       )
   items.push('-')


Make PDFReference an actual Node stream so we can pipe stuff to it

--- lib/reference.coffee
+++ lib/reference.coffee
@@ -4,9 +4,11 @@
 ###

 zlib = require 'zlib'
+stream = require 'stream'

-class PDFReference
+class PDFReference extends stream.Writable
   constructor: (@document, @id, @data = {}) ->
+    super decodeStrings: no
     @gen = 0
     @deflate = null
     @compress = @document.compress and not @data.Filter
@@ -22,8 +24,8 @@
       @data.Length += chunk.length

     @deflate.on 'end', @finalize
-
-  write: (chunk) ->
+
+  _write: (chunk, encoding, callback) ->
     unless Buffer.isBuffer(chunk)
       chunk = new Buffer(chunk + '\n', 'binary')

@@ -36,10 +38,11 @@
     else
       @chunks.push chunk
       @data.Length += chunk.length
+
+    callback()

   end: (chunk) ->
-    if typeof chunk is 'string' or Buffer.isBuffer(chunk)
-      @write chunk
+    super

     if @deflate
       @deflate.end()


Add keymaps supported by Linux and Windows

--- keymaps/simple-align.cson
+++ keymaps/simple-align.cson
@@ -7,5 +7,8 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-'atom-workspace':
+'.platform-darwin':
   'ctrl-cmd-a': 'simple-align:align'
+
+'.platform-win32, .platform-linux':
+  'ctrl-alt-a': 'simple-align:align'


teams: login page signup link fix [fixes #99593612]

--- client/landing/site.landing/coffee/team/tabs/teamlogintab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamlogintab.coffee
@@ -64,7 +64,7 @@

       {{> @inviteDesc}}
-
Trying to create a team? Sign up on the home page to get started.

+
Trying to create a team? Sign up on the home page to get started.

       Acceptable user policyCopyright/DMCA guidelinesTerms of servicePrivacy policy


fix whitespace

fix whitespace

--- resources/assets/coffee/react/_components/comment.coffee
+++ resources/assets/coffee/react/_components/comment.coffee
@@ -218,6 +218,7 @@
           onClick: @delete
           osu.trans('common.buttons.delete')

+
   renderDeletedBy: =>
     if @props.comment.isDeleted && @props.comment.canModerate
       deleter = userStore.get(@props.comment.deletedById)
@@ -232,6 +233,7 @@
               else
                 _.escape deleter.username

+
   renderPin: =>
     if @props.comment.canPin
       div className: 'comment__row-item',
@@ -266,6 +268,7 @@
               else
                 _.escape editor.username

+
   renderOwnerBadge: (meta) =>
     return null unless @props.comment.userId == meta.owner_id


fix import

fix import

--- resources/assets/coffee/react/profile-page/detail.coffee
+++ resources/assets/coffee/react/profile-page/detail.coffee
@@ -3,13 +3,13 @@

 import { DetailBar } from './detail-bar'
 import { RankChart } from './rank-chart'
-import { RankCount } from './rank-count'
 import { Observer } from 'mobx-react'
 import core from 'osu-core-singleton'
 import MedalsCount from 'profile-page/medals-count'
 import PlayTime from 'profile-page/play-time'
 import Pp from 'profile-page/pp'
 import Rank from 'profile-page/rank'
+import RankCount from 'profile-page/rank-count'
 import * as React from 'react'
 import { div } from 'react-dom-factories'
 el = React.createElement


Fixing the options passing in render from controller

--- vendor/assets/javascripts/amoeba/router.js.coffee
+++ vendor/assets/javascripts/amoeba/router.js.coffee
@@ -1,6 +1,6 @@
 class Amoeba.Router extends Backbone.Router
-  render: (partial) ->
+  render: (partial, options = {}) ->
     throw "Render already called with #{@currentView}" if @currentView?
     view = Amoeba.app.lookupContext.find(partial)

-    Amoeba.app.currentView = @currentView = new view(@)
+    Amoeba.app.currentView = @currentView = new view(options)


[grunt] refactor `[task].files.rename` into `humanReadableUrl()` function to make it reusable

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -33,6 +33,17 @@
   layoutsDir = grunt.config('path.source.layouts')
   localesDir = grunt.config('path.source.locales')

+  humanReadableUrl = (pagepath, ext) ->
+    exclude  = /^(index|\d\d\d)$/
+
+    # @todo Find way to get extension from current config dynamically
+    #       `grunt.task.current.data.files[0].ext` will work during initialization
+    #       but will fail when `grunt-newer` will run, since it will override `task.current`
+    basename = path.basename(src, ext)
+
+    if !exclude.test(basename)
+      pagepath = pagepath.replace(basename + ext, basename + '/index' + ext)
+    pagepath

   # Output or not locale's dir name based on whether it's base locale or not.
   getLocaleDir = (locale) ->
@@ -368,17 +379,7 @@
         # and move them to directory named after basename of the file
         # @example `/posts/2015-10-12-article.nj` -> `/posts/2015-10-12-article.nj/index.html`
         rename: (dest, src) =>
-          exclude  = /^(index|\d\d\d)$/
-
-          # @todo Find way to get extension from current config dynamically
-          #       `grunt.task.current.data.files[0].ext` will work during initialization
-          #       but will fail when `grunt-newer` will run, since it will override `task.current`
-          ext      = '.html'
-          basename = path.basename(src, ext)
-
-          if !exclude.test(basename)
-            src = src.replace(basename + ext, basename + '/index' + ext)
-
+          src = humanReadableUrl(src, '.html')
           path.join(dest, src);
       ]


Revert sales and purchases decimals

Revert sales and purchases decimals

--- app/assets/javascripts/backend/purchase_process/forms.js.coffee
+++ app/assets/javascripts/backend/purchase_process/forms.js.coffee
@@ -35,17 +35,6 @@
         $(this).val(val)
         $(this).closest('.storing-fields').find('.conditionning-quantity').trigger('change')

-  # Prevent having more than 2 digits on amount fields
-  $(document).on "keyup", "[data-trade-component=amount], [data-trade-component=pretax_amount], [data-trade-component=unit_pretax_amount]", ->
-    if this.value.includes('.')
-      amount_with_digits = this.value.split('.')
-      amount = amount_with_digits[0]
-      digits = amount_with_digits[1]
-      if digits.length > 2
-        digits_kept = digits.substring(0, 2)
-        amount_displayed = amount + '.' + digits_kept
-        this.value = parseFloat(amount_displayed)
-
   $(document).ready ->
     $('table.list').on 'cocoon:after-insert', ->
       $('*[data-iceberg]').on "iceberg:inserted", (element) ->


Added follow topic button

Added follow topic button

--- core/app/backbone/views/topics/topic_popover_content_view.coffee
+++ core/app/backbone/views/topics/topic_popover_content_view.coffee
@@ -1,5 +1,12 @@
-class window.TopicPopoverContentView extends Backbone.Marionette.ItemView
+class window.TopicPopoverContentView extends Backbone.Marionette.Layout
   template:
     text: """
       {{title}}
+

     """
+
+  regions:
+    favouriteButtonRegion: '.js-favourite-button-region'
+
+  onRender: ->
+    @favouriteButtonRegion.show new FavouriteTopicButtonView(topic: @model, mini: true)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -6,192 +6,5 @@
     height: 8
     activeBounds: Rectangle(0, 0, App.width, App.height)

-
-  position: (newPosition) ->
-    if newPosition?
-      I.x = newPosition.x
-      I.y = newPosition.y
-    else
-      Point(I.x, I.y)
-
-  changePosition: (delta) ->
-    I.x += delta.x
-    I.y += delta.y
-
-    self
-
-  ###*
-  Does a check to see if this object is overlapping
-  with the bounds passed in.
-
-

-  player = Core
-    x: 4
-    y: 6
-    width: 20
-    height: 20
-
-  player.include(Bounded)
-
-  player.collides({x: 5, y: 7, width: 20, height: 20})
-  # => true
-

-
-  @name collides
-  @methodOf Bounded#
-  @returns {Point} The position of this object
-  ###
-  collides: (bounds) ->
-    Collision.rectangular(self.bounds(), bounds)
-
-  ###*
-  This returns a modified bounds based on the collision margin.
-  The area of the bounds is reduced if collision margin is positive
-  and increased if collision margin is negative.
-
-

-  player = Core
-    collisionMargin:
-      x: -2
-      y: -4
-    x: 50
-    y: 50
-    width: 20
-    height: 20
-
-  player.include(Bounded)
-
-  player.collisionBounds()
-  # => {x: 38, y: 36, height: 28, width: 24}
-
-  player.collisionBounds(10, 10)
-  # => {x: 48, y: 46, height: 28, width: 24}
-

-
-  @name collisionBounds
-  @methodOf Bounded#
-  @param {Number} xOffset the amount to shift the x position
-  @param {Number} yOffset the amount to shift the y position
-  @returns {Object} The collision bounds
-  ###
-  collisionBounds: (xOffset, yOffset) ->
-    bounds = self.bounds(xOffset, yOffset)
-
-    bounds.x += I.collisionMargin.x
-    bounds.y += I.collisionMargin.y
-    bounds.width -= 2 * I.collisionMargin.x
-    bounds.height -= 2 * I.collisionMargin.y
-
-    return bounds
-
-  ###*
-  The bounds method returns infomation about the location
-  of the object and its dimensions with optional offsets.
-
-

-  player = Core
-    x: 3
-    y: 6
-    width: 2
-    height: 2
-
-  player.include(Bounded)
-
-  player.bounds()
-  # => {x: 3, y: 6, width: 2, height: 2}
-
-  player.bounds(7, 4)
-  # => {x: 10, y: 10, width: 2, height: 2}
-

-
-  @name bounds
-  @methodOf Bounded#
-  @param {Number} xOffset the amount to shift the x position
-  @param {Number} yOffset the amount to shift the y position
-  ###
-  bounds: (xOffset, yOffset) ->
-    center = self.center()
-
-    x: center.x - I.width/2 + (xOffset || 0)
-    y: center.y - I.height/2 + (yOffset || 0)
-    width: I.width
-    height: I.height
-
-  ###*
-  The centeredBounds method returns infomation about the center
-  of the object along with the midpoint of the width and height.
-
-

-  player = Core
-    x: 3
-    y: 6
-    width: 2
-    height: 2
-
-  player.include(Bounded)
-
-  player.centeredBounds()
-  # => {x: 4, y: 7, xw: 1, yw: 1}
-

-
-  @name centeredBounds
-  @methodOf Bounded#
-  ###
-  centeredBounds: () ->
-    center = self.center()
-
-    x: center.x
-    y: center.y
-    xw: I.width/2
-    yw: I.height/2
-
-  ###*
-  The center method returns the {@link Point} that is
-  the center of the object.
-
-

-  player = Core
-    x: 50
-    y: 40
-    width: 10
-    height: 30
-
-  player.include(Bounded)
-
-  player.center()
-  # => {x: 30, y: 35}
-

-
-  @name center
-  @methodOf Bounded#
-  @returns {Point} The middle of the calling object
-  ###
-  center: (newCenter) ->
-    self.position(newCenter)
-
-  ###*
-  Return the circular bounds of the object. The circle is
-  centered at the midpoint of the object.
-
-

-  player = Core
-    radius: 5
-    x: 50
-    y: 50
-    other: "stuff"
-
-  player.include(Bounded)
-
-  player.circle()
-  # => {radius: 5, x: 50, y: 50}
-

-
-  @name circle
-  @methodOf Bounded#
-  @returns {Object} An object with a position and a radius
-  ###
-  circle: () ->
-    circle = self.center()
-    circle.radius = I.radius || I.width/2 || I.height/2
-
-    return circle
+
+


When retrieving feeds via AJAX, feeds already in the scope are updated, new feeds are inserted in scope, but if a feed is in scope but it's not in the returned JSON, it's left in the scope untouched.

This fixes the following situation: with the old behaviour (the retrieved feeds list simply replaces the feeds list in scope), if a user marked as read all entries in a feed and, keeping that feed selected, waited for the next automatic reload of entries (every minute), the feed he had selected disappeared from the sidebar.

There are already mechanisms in place for removing feeds from the sidebar when they have no unread entries and the user selects a different feed, as long as he's not toggled the "show read" button. Feeds should not disappear from the sidebar because of the automatic refresh every minute (but they should appear in the sidebar automatically, if there's been a background fetch of a feed which had no unread entries and now it does have unread entries).

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -13,7 +13,19 @@
   load_feeds = ->
     $http.get("/feeds.json?include_read=#{$rootScope.show_read}")
     .success (data)->
-      $rootScope.feeds = data
+      alert
+      if !$rootScope.feeds || $rootScope.feeds?.length==0
+        # If there are no feeds in scope, just store the feeds returned.
+        $rootScope.feeds = data
+      else
+        # If there are feeds already loaded in scope, replace them with the ones returned, without removing any feed
+        # (i.e. if a feed in scope is not among the returned feeds, do not remove it from scope).
+        for feed_new in data
+          feed_old = findSvc.find_feed feed_new.id
+          if feed_old
+            feed_old.unread_entries = feed_new.unread_entries
+          else
+            $rootScope.feeds.push feed_new
       $rootScope.feeds_loaded = true
     .error ->
       timerFlagSvc.start 'error_loading_feeds'


Couchbase has come static

Couchbase has come static

--- server/src/Component/Builtin/DataSource/Couchbase.coffee
+++ server/src/Component/Builtin/DataSource/Couchbase.coffee
@@ -19,6 +19,8 @@
 #    n1qlPort: 8093

 class Couchbase
+    @static = true
+
     constructor: (dataSourceName) ->
         dataSourceName = dataSourceName or 'default'
         @_couchbase = require 'couchbase'


Added configurable AWS region to email settings (#847)

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -290,6 +290,9 @@
 		template:
 			customFooter: process.env["SHARELATEX_CUSTOM_EMAIL_FOOTER"]

+	if process.env["SHARELATEX_EMAIL_AWS_SES_REGION"]?
+		settings.email.parameters.region = process.env["SHARELATEX_EMAIL_AWS_SES_REGION"]
+
 	if process.env["SHARELATEX_EMAIL_SMTP_USER"]? or process.env["SHARELATEX_EMAIL_SMTP_PASS"]?
 		settings.email.parameters.auth =
 			user: process.env["SHARELATEX_EMAIL_SMTP_USER"]


Removed indexOf for IE compatibility

Removed indexOf for IE compatibility

--- src/client/microevent.coffee
+++ src/client/microevent.coffee
@@ -13,8 +13,16 @@

 	removeListener: (event, fct) ->
 		@_events ||= {}
-		idx = @_events[event]?.indexOf fct
-		@_events[event].splice(idx, 1) if idx? and idx >= 0
+		listeners = (@_events[event] ||= [])
+
+		# Sadly, there's no IE8- support for indexOf.
+		i = 0
+		while i < listeners.length
+			if listeners[i] == fct
+				listeners.splice i, 1
+			else
+				i++
+
 		this

 	emit: (event, args...) ->


ggplot works with new ported cdx

ggplot works with new ported cdx

--- src/coffee/model_base.coffee
+++ src/coffee/model_base.coffee
@@ -459,7 +459,7 @@
     # ### method HasProperties::url
     #model where our API processes this model

-    base = "/cdx/bb/" + Continuum.docid + "/" + @type + "/"
+    base = "/bokeh/bb/" + Continuum.docid + "/" + @type + "/"
     if (@isNew())
       return base
     return base + @get('id')


Model:Workspace: Make generateUniqueName to work with machineUId.

--- workers/social/lib/social/models/workspace.coffee
+++ workers/social/lib/social/models/workspace.coffee
@@ -57,7 +57,7 @@
     # to prevent storing any kind of data in it. -- acetz!
     data.layout = {}

-    generateUniqueName { originId, name }, (err, res)->
+    generateUniqueName { originId, name, machineUId }, (err, res)->

       return callback err  if err?

@@ -73,12 +73,12 @@
         return callback null, workspace

-  generateUniqueName = ({originId, name, index}, callback)->
+  generateUniqueName = ({originId, machineUId, name, index}, callback)->

     slug = if index? then "#{name}-#{index}" else name
     slug = slugify slug

-    JWorkspace.count { originId, slug }, (err, count)->
+    JWorkspace.count { originId, slug, machineUId }, (err, count)->

       return callback err  if err?

@@ -92,7 +92,7 @@
         index ?= 0
         index += 1

-        generateUniqueName { originId, name, index }, callback
+        generateUniqueName { originId, machineUId, name, index }, callback

   @fetch = secure (client, query = {}, callback) ->


Wrap "multi" commands in try-catch to reduce bot death

--- scripts/pudding.coffee
+++ scripts/pudding.coffee
@@ -104,7 +104,10 @@
     n = +msg.match[1]
     msg.match = msg.match[1..]
     for i in [1..n] by 1
-      start_instance_response()(robot, msg)
+      try
+        start_instance_response()(robot, msg)
+      catch err
+        robot.logger.error(err)

   whitelist_respond robot, /start inst(:?ance)? [io]n ([a-z]+) ([a-z]+) with (.+)/i, start_instance_response()

@@ -112,7 +115,10 @@
     n = +msg.match[1]
     msg.match = msg.match[1..]
     for i in [1..n] by 1
-      start_instance_response()(robot, msg)
+      try
+        start_instance_response()(robot, msg)
+      catch err
+        robot.logger.error(err)

   whitelist_respond robot, /sum(:?marize)? inst(:?ances)?/i, (robot, msg) ->
     list_instances robot, host, '', '', default_role, token, send_instances_summary_cb(robot, msg)


Restore part of 85f04a that was reverted.

This appears to be a problem with any document with images. @render() is called
multiple times, and you have to clean up and re-initialise Aloha after
every render. Strictly speaking it would be better to set the content
of the editor rather than rerender the whole thing, but we'll fight that dragon
some other day.

--- scripts/views/workspace/content/aloha-edit.coffee
+++ scripts/views/workspace/content/aloha-edit.coffee
@@ -63,7 +63,11 @@
       $(Aloha, 'body').off 'aloha-smart-content-changed.updatemodel'

       # Kill old editable, otherwise they accumulate on Aloha.editables.
-      Aloha.getEditableById(@$el.attr('id')).destroy()
+      @$el.mahalo?()
+
+      # reset some of the state of our root element so aloha doesn't freak out
+      @$el.removeClass('aloha-block-blocklevel-sortable')
+      @$el.removeData()

     onRender: () ->
       # update model after the user has stopped making changes


Fix regexp for detecting beginning and end of tt command.

^ and $ take higher precedence than | so it needs ().

Test Plan:
cross-fingers

Auditors: csilvers

--- scripts/tab_tab.coffee
+++ scripts/tab_tab.coffee
@@ -19,7 +19,7 @@
 endswith = (file, suffix) -> file.indexOf(suffix, file.length - suffix.length) isnt -1

 module.exports = (robot) ->
-	robot.hear /^tab tab|tt$/i, (msg) ->
+	robot.hear /^(tab tab|tt)$/i, (msg) ->
         if not robot.fromSelf msg
             scriptPath = path.join(".", "scripts")


Remove more old code

Remove more old code

--- apps/user/client/all.coffee
+++ apps/user/client/all.coffee
@@ -49,10 +49,6 @@

     setupBlockCollection options

-    blocks.on 'remove', ->
-      unless ( blocks.any (block) -> block.get('class') is 'Tip' )
-        mediator.shared.current_user.save show_tour: false
-
   new PathView
     el: $('section.path--header')
     model: user


Fixed not working menu when online core is selected

--- lib/spark-ide.coffee
+++ lib/spark-ide.coffee
@@ -224,7 +224,8 @@
       fs.writeFileSync settingsFile, '{}'

     @configSubscription ?= @PathWatcher.watch settingsFile, (eventType) =>
-      if eventType is 'change' and @configSubscription?
+      if eventType is 'change' and @configSubscription? and @accessToken != @SettingsHelper.get('access_token')
+        @accessToken = @SettingsHelper.get 'access_token'
         @updateToolbarButtons()
         @MenuManager.update()
         atom.workspaceView.trigger 'spark-ide:update-login-status'


Api: CreateUser: simplify validateData by using verifyApiToken, remove daisy

--- servers/lib/server/handlers/api/createuser.coffee
+++ servers/lib/server/handlers/api/createuser.coffee
@@ -6,6 +6,7 @@
   handleClientIdNotFound
   checkAuthorizationBearerHeader } = require '../../helpers'
 { sendApiError
+  verifyApiToken
   sendApiResponse
   checkApiAvailability
   isUsernameLengthValid
@@ -87,38 +88,15 @@

 validateData = (data, callback) ->

-  { JUser, JGroup, JApiToken } = koding.models
   { token, username, suggestedUsername } = data

-  apiToken = null
+  verifyApiToken token, (err, apiToken) ->
+    return callback err  if err

-  queue = [
+    handleUsername username, suggestedUsername, (err, username) ->
+      return callback err  if err

-    ->
-      # checking if token is valid
-      JApiToken.one { code : token }, (err, apiToken_) ->
-        return callback apiErrors.internalError    if err
-        return callback apiErrors.invalidApiToken  unless apiToken_
-
-        apiToken = apiToken_
-        queue.next()
-
-    ->
-      checkApiAvailability { apiToken }, (err) ->
-        return callback err  if err
-        queue.next()
-
-    ->
-      handleUsername username, suggestedUsername, (err, username_) ->
-        return callback err  if err
-        username = username_
-        queue.next()
-
-    -> callback null, { apiToken, username }
-
-  ]
-
-  daisy queue
+      callback null, { apiToken, username }

 handleUsername = (username, suggestedUsername, callback) ->


Add updateAllResults completion notice

Add updateAllResults completion notice

--- server/models/user.coffee
+++ server/models/user.coffee
@@ -102,6 +102,7 @@
             logger.info("Updated 30 users, continuing")
             promises = []
     await Promise.all(promises)
+    logger.info("Updated all users")

 usersSchema.index
     userList: 1


Auto-indent doesn't cascade indent changes.

--- src/app/selection.coffee
+++ src/app/selection.coffee
@@ -130,10 +130,12 @@
       mode = @editor.getCurrentMode()
       row = @cursor.getScreenPosition().row
       state = @editor.stateForScreenRow(row)
+      lineBeforeCursor = @cursor.getCurrentBufferLine()[0..@cursor.getBufferPosition().column]
       if text[0] == "\n"
-        indent = mode.getNextLineIndent(state, @cursor.getCurrentBufferLine(), @editor.tabText)
+        indent = mode.getNextLineIndent(state, lineBeforeCursor, @editor.tabText)
+        console.log indent.length
         text = text[0] + indent + text[1..]
-      else if mode.checkOutdent(state, @cursor.getCurrentBufferLine(), text)
+      else if mode.checkOutdent(state, lineBeforeCursor, text)
         shouldOutdent = true

     {text, shouldOutdent}


Reset sort to names when period is changed

--- src/components/performance/index.cjsx
+++ src/components/performance/index.cjsx
@@ -26,6 +26,7 @@

 # Index of first column that contains data
 FIRST_DATA_COLUMN = 2
+INITIAL_SORT = { key: 'last_name', asc: false }

 Performance = React.createClass
   displayName: 'Performance'
@@ -49,7 +50,7 @@
     colSetWidth: 225
     colResizeWidth: 225
     colResizeKey: 0
-    sort: { key: 'last_name', asc: false }
+    sort: INITIAL_SORT

   componentDidMount: ->
     window.addEventListener("resize", @state.debounce)
@@ -125,8 +126,24 @@
         when 'external' then <ExternalCell key='extern'   {...props} />
     columns

+  renderNameColumn: ({width, dataKey, sortKey, label}) ->
+    headerProps = {sortKey, sortState: @state.sort, onSort: @changeSortingOrder}
+    columnProps = {width, dataKey, label, fixed: true}
+    header = <SortingHeader {...headerProps}>{label}</SortingHeader>
+    <Column
+      key={dataKey} {...columnProps} cellRenderer={-> @cellData} headerRenderer={ -> header} />
+
+  changeSortingOrder: (key) ->
+    asc = if @state.sort.key is key then not @state.sort.asc else false
+    @setState(sort: { key, asc})
+
+  isSortingByData: ->
+    _.isNumber(@state.sort.key)
+
   selectPeriod: (period) ->
-    @setState({period_id: period.id})
+    newState = {period_id: period.id}
+    newState.sort = INITIAL_SORT if @isSortingByData()
+    @setState(newState)

   setPeriodIndex: (key) ->
     @setState({periodIndex: key + 1})
@@ -190,17 +207,6 @@
       </div>
     </div>

-  renderNameColumn: ({width, dataKey, sortKey, label}) ->
-    headerProps = {sortKey, sortState: @state.sort, onSort: @changeSortingOrder}
-    columnProps = {width, dataKey, label, fixed: true}
-    header = <SortingHeader {...headerProps}>{label}</SortingHeader>
-    <Column
-      key={dataKey} {...columnProps} cellRenderer={-> @cellData} headerRenderer={ -> header} />
-
-  changeSortingOrder: (key) ->
-    asc = if @state.sort.key is key then not @state.sort.asc else false
-    @setState(sort: { key, asc})
-
 PerformanceShell = React.createClass
   contextTypes:
     router: React.PropTypes.func


SplitView: animated option deprecated

SplitView: animated option deprecated

--- src/components/split/splitview.coffee
+++ src/components/split/splitview.coffee
@@ -17,7 +17,6 @@
     options.duration  or= 200               # a Number in miliseconds
     options.separator or= null              # a KDView instance or null for default separator
     options.colored    ?= no
-    options.animated   ?= yes           # a Boolean
     options.type        = options.type.toLowerCase()
     options.cssClass    = KD.utils.curry "kdsplitview kdsplitview-#{options.type}", options.cssClass


Fixed the mailchimp webhook.

Fixed the mailchimp webhook.

--- server/routes/mail.coffee
+++ server/routes/mail.coffee
@@ -16,22 +16,22 @@
       badLog("Bad post type: #{post.type}")
       return res.end()

-    unless post['data[email]']
+    unless post.data.email
       badLog("Ignoring because no email: #{JSON.stringify(req.body, null, '\t')}")
       return res.end()

-    unless post['data[email]'] is 'sderickson@gmail.com'
+    unless post.data.email is 'sderickson@gmail.com'
       badLog("Ignoring because this is a test: #{JSON.stringify(req.body, null, '\t')}")
       return res.end()

-    User.findOne {'mailChimp.euid':post['data[id]']}, (err, user) ->
+    User.findOne {'mailChimp.euid':post.data.id}, (err, user) ->
       return errors.serverError(res) if err
       if not user
-        badLog("could not find user for...: #{{'mailChimp.euid':post['data[id]']}}")
+        badLog("could not find user for...: #{{'mailChimp.euid':post.data.id}}")
         return res.end()

-      handleProfileUpdate(post, user) if post.type is 'profile'
-      handleUnsubscribe(post, user) if post.type is 'unsubscribe'
+      handleProfileUpdate(user, post) if post.type is 'profile'
+      handleUnsubscribe(user) if post.type is 'unsubscribe'

       res.end()
       user.updatedMailChimp = true # so as not to echo back to mailchimp
@@ -40,18 +40,18 @@
         res.end()

-handleProfileUpdate = (data, user) ->
-  groups = data['data[merges][INTERESTS]'].split(', ')
+handleProfileUpdate = (user, post) ->
+  groups = post.data.merges.INTERESTS.split(', ')
   groups = (map[g] for g in groups when map[g])
   user.set 'emailSubscriptions', groups

   mailChimpInfo = user.get 'mailChimp'
-  mailChimpInfo.email = data['data[email]']
+  mailChimpInfo.email = post.data.email
   user.set 'mailChimp', mailChimpInfo

   badLog("Updating user object to: #{JSON.stringify(user.toObject(), null, '\t')}")

-handleUnsubscribe = (data, user) ->
+handleUnsubscribe = (user) ->
   user.set 'emailSubscriptions', []

   badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")


Activity: changed naming on sidebar titles

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -174,8 +174,8 @@
   addHotTopics: ->

     @wrapper.addSubView @sections.hot = new ActivitySideView
-      title      : 'HOT'
-      cssClass   : 'hot topics'
+      title      : 'TRENDING'
+      cssClass   : 'hot topics hidden'
       itemClass  : SidebarTopicItem
       dataPath   : 'popularTopics'
       delegate   : this
@@ -188,7 +188,7 @@
   addFollowedTopics: ->

     @wrapper.addSubView @sections.followedTopics = new ActivitySideView
-      title      : 'Followed Topics'
+      title      : 'My Feeds'
       cssClass   : 'followed topics'
       itemClass  : SidebarTopicItem
       dataPath   : 'followedChannels'
@@ -202,7 +202,7 @@
   addFollowedPosts: ->

     @wrapper.addSubView @sections.followedPosts = new ActivitySideView
-      title      : 'Followed Posts'
+      title      : 'Conversations'
       cssClass   : 'threads users'
       itemClass  : SidebarPinnedItem
       dataPath   : 'pinnedMessages'
@@ -216,7 +216,7 @@
   addMessages: ->

     @wrapper.addSubView @sections.messages = new ActivitySideView
-      title      : 'Messages'
+      title      : 'Private Conversations'
       cssClass   : 'inbox users'
       itemClass  : SidebarMessageItem
       dataPath   : 'privateMessages'


Kites: plan fee is represented in cents

Kites: plan fee is represented in cents

--- client/Social/Kites/Views/kiteplanlistitemview.coffee
+++ client/Social/Kites/Views/kiteplanlistitemview.coffee
@@ -32,7 +32,7 @@
       partial: """

#{title}

#{description}

-
#{KD.utils.formatMoney feeAmount}/#{feeUnit}

+
#{KD.utils.formatMoney feeAmount / 100}/#{feeUnit}

       """

     @addSubView @subscribeButton


fix combined sortie plane count

fix combined sortie plane count

--- parts/navigation-bar.cjsx
+++ parts/navigation-bar.cjsx
@@ -11,7 +11,10 @@
               if i == 2
                 list.push <Col xs={tmp}>{@props.enemyName + @props.enemyPlane}</Col>
                 list.push <Col xs={tmp}>{@props.HP}</Col>
-              else
+              else if i == 1
+                list.push <Col xs={tmp}>{@props.sortieFleet}</Col>
+                list.push <Col xs={tmp}>{@props.HP}</Col>
+              else
                 list.push <Col xs={tmp}>{@props.sortieFleet + @props.sortiePlane}</Col>
                 list.push <Col xs={tmp}>{@props.HP}</Col>
             <Grid>


translate about page

translate about page

- reuse code from reference.coffee
- translate tab title by checking paneItem constructor name

--- lib/about.coffee
+++ lib/about.coffee
@@ -1,7 +1,36 @@
 class About

   @localize: (defA) ->
-    console.error defA
-    # TODO replace DOM like preferences.coffee does
+    @defA = defA
+    @updateAbout()  # first time localize
+    atom.workspace.onDidChangeActivePaneItem (item) =>
+      if item.__proto__.constructor.name is 'AboutView'
+        setTimeout(@updateAbout, 0)
+
+  @updateAbout: () =>
+    aboutTab = document.querySelector('.tab[data-type="AboutView"]')
+    aboutEnabled = aboutTab.className.includes 'active' if aboutTab
+    return unless aboutTab && aboutEnabled
+    aboutTab.querySelector('.title').textContent = @defA.About.tabTitle
+
+    about = document.querySelector('.about .about-container')
+    return if about is null
+    unless about.getAttribute('data-localized') is 'true'
+      releaseNotes = about.querySelector('.about-header-info a.about-header-release-notes')
+      releaseNotes.text = @defA.About.releaseNotes
+
+      buttons = about.querySelector('.about-actions.group-item .btn-group')
+      buttons.childNodes[0].textContent = @defA.About.license
+      buttons.childNodes[1].textContent = @defA.About.termsOfUse
+
+      loves = about.querySelector('.about-love.group-start')
+      loves.childNodes[1].textContent = @defA.About.with
+      loves.childNodes[3].textContent = @defA.About.by
+
+      credits = about.querySelector('.about-credits.group-item')
+      credits.querySelector('span').textContent = @defA.About.andTheAwesome
+      credits.querySelector('a').text = @defA.About.atomCommunity
+
+      about.setAttribute('data-localized', 'true')

 module.exports = About


Fix instanceof check on doubleclick on an entry

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -207,7 +207,7 @@
       when 2
         if entry instanceof FileView
           @unfocus()
-        else if DirectoryView
+        else if entry instanceof DirectoryView
           entry.toggleExpansion(isRecursive)

     false


Add missing classes based on side

Add missing classes based on side

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -106,9 +106,12 @@

   attach: ->
     return unless atom.project.getPath()
+    @removeClass('panel-right, panel-left')
     if atom.config.get('tree-view.showOnRightSide')
+      @addClass('panel-right')
       atom.workspaceView.appendToRight(this)
     else
+      @addClass('panel-left')
       atom.workspaceView.appendToLeft(this)

   detach: ->


Add missing require(bluebird)

Add missing require(bluebird)

--- src/blobTransport.coffee
+++ src/blobTransport.coffee
@@ -6,6 +6,7 @@
 async = require "async"
 winston = require "winston"
 chunk = require "chunk"
+Promise = require "bluebird"

 Transport = winston.Transport


Use #createAppendBlobFromText if file doesnt exists

--- src/blobTransport.coffee
+++ src/blobTransport.coffee
@@ -22,18 +22,27 @@
       return

     _buildCargo: =>
-      async.cargo (tasks, whenFinishCargo) =>
-        whenFinishTasks = ->
+      async.cargo (tasks, __whenFinishCargo) =>
+        __whenFinishTasks = ->
+          debug "Finish append all lines to blob"
           _.each tasks, ({callback}) -> callback null, true
-          whenFinishCargo()
+          __whenFinishCargo()

         debug "Log #{tasks.length}th lines"
         logBlock = _.map(tasks, "line").join ""
-        debug "Starting append log line to blob. Size #{logBlock.length}"
+        debug "Starting append log lines to blob. Size #{logBlock.length}"
         @client.appendFromText @containerName, @blobName, logBlock, (err, result) =>
-          debug "Error in append", err if err
-          debug "Finish append all lines to blob"
-          whenFinishTasks()
+          return @_retryIfNecessary(err, logBlock, __whenFinishTasks) if err
+          __whenFinishTasks()
+
+    _retryIfNecessary: (err, block, callback) =>
+      __append = => @client.createAppendBlobFromText @containerName, @blobName, block, {}, __handle
+      __doesNotExistFile = -> err.code? && err.code is "NotFound"
+      __handle = (err) ->
+        debug "Error in append", err if err
+        callback()
+
+      if __doesNotExistFile() then __append() else __handle err

     _formatLine: ({level, msg, meta}) => "[#{level}] - #{@_timestamp()} - #{msg} #{@_meta(meta)} \n"


:fire: leftover #27

:fire: leftover #27

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -25,7 +25,6 @@
 Developer = null # delay

 packageScope = 'vim-mode-plus'
-MOUSE_BUTTON_LEFT = 0

 module.exports =
 class VimState


cleanup unused

cleanup unused

--- src/components/loadable.cjsx
+++ src/components/loadable.cjsx
@@ -1,6 +1,5 @@
 React = require 'react'
 BS = require 'react-bootstrap'
-_ = require 'underscore'

 BindStoreMixin = require './bind-store-mixin'

@@ -22,9 +21,6 @@
     renderLoading: React.PropTypes.func.isRequired
     renderError: React.PropTypes.func.isRequired
     renderBug: React.PropTypes.func.isRequired
-
-  getInitialState: ->
-    refreshButton: false

   getDefaultProps: ->


teams: fix team login routes and root route to /Teams

--- client/landing/site.landing/coffee/core/routes.coffee
+++ client/landing/site.landing/coffee/core/routes.coffee
@@ -11,8 +11,20 @@
     return if location.hash.length

     { router } = kd.singletons
+    { groupName, group, environment } = kd.config

-    return router.handleRoute '/Teams'
+    # root is home if group is koding
+    if groupName is 'koding'
+      return router.handleRoute '/Teams'
+
+    # if there is no such group take user to group creation with given group info
+    if not group
+      newUrl = "http://#{location.host.replace(groupName + '.', '')}/Teams?group=#{groupName}"
+      return location.replace newUrl
+
+    # if there is a group then take user to group login page
+    else
+      return router.openSection 'Team', null, null, (app) -> app.jumpTo 'login'

   handleInvitation = ({params : {token}, query}) ->


Don’t try-catch invalid routings in layout.

--- src/chaplin/views/layout.coffee
+++ src/chaplin/views/layout.coffee
@@ -134,13 +134,9 @@
     options = {query}

     # Pass to the router, try to route the path internally.
-    try
-      @publishEvent '!router:route', path, options
-      # Prevent default handling if the URL could be routed.
-      event.preventDefault()
-    catch error
-      console?.error 'Routing error', error
-      location.href = path unless isAnchor
+    @publishEvent '!router:route', path, options
+    # Prevent default handling if the URL could be routed.
+    event.preventDefault()
     return

   # Region management


Bind to store vs calling .sync and add QuestionFormat

--- src/components/question.cjsx
+++ src/components/question.cjsx
@@ -1,6 +1,9 @@
 React = require 'react'
+BS = require 'react-bootstrap'
+
 _ = require 'underscore'

+QuestionFormatType = require './question-format-type'
 Answer = require './answer'
 {QuestionActions, QuestionStore} = require '../stores/question'
 {AnswerActions, AnswerStore} = require '../stores/answer'
@@ -9,35 +12,35 @@
   displayName: 'Question'

   getInitialState: -> {}
+  update: -> @forceUpdate()
+
+  componentWillMount: ->
+    QuestionStore.addChangeListener(@update)
+
+  componentWillUnmount: ->
+    QuestionStore.removeChangeListener(@update)

   changeAnswer: (answerId) ->
     curAnswer = QuestionStore.getCorrectAnswer(@props.id)
     QuestionActions.setCorrectAnswer(@props.id, answerId, curAnswer?.id)
-    @props.sync()

   updateStimulus: (event) ->
     QuestionActions.updateStimulus(@props.id, event.target?.value)
-    @props.sync()

   updateStem: (event) ->
     QuestionActions.updateStem(@props.id, event.target?.value)
-    @props.sync()

   updateSolution: (event) ->
     QuestionActions.updateSolution(@props.id, event.target?.value)
-    @props.sync()

   addAnswer: ->
     QuestionActions.addNewAnswer(@props.id)
-    @props.sync()

   removeAnswer:(answerId) ->
     QuestionActions.removeAnswer(@props.id, answerId)
-    @props.sync()

   moveAnswer: (answerId, direction) ->
     QuestionActions.moveAnswer(@props.id, answerId, direction)
-    @props.sync()

   multipleChoiceClicked: (event) -> QuestionActions.toggleMultipleChoiceFormat(@props.id)
   freeResponseClicked: (event) -> QuestionActions.toggleFreeResponseFormat(@props.id)
@@ -45,6 +48,7 @@

   render: ->
     { id, removeQuestion, moveQuestion, canMoveLeft, canMoveRight } = @props
+
     answers = []

     for answer, index in QuestionStore.getAnswers(id)
@@ -77,45 +81,27 @@
           }

       }
-

-        Question Formats
-

-

-        <input onChange={@multipleChoiceClicked}
-          id="multipleChoiceFormat#{id}"
-          type="checkbox"
-          defaultChecked={QuestionStore.isMultipleChoice(id)} />
-        Multiple Choice
-

-

-        <input onChange={@freeResponseClicked}
-          id="freeResponseFormat#{id}"
-          type="checkbox"
-          defaultChecked={QuestionStore.isFreeResponse(id)}
-        /> Requires viewing choices to answer question
-

+      <QuestionFormatType questionId={id} />
+
+      <BS.Input type="checkbox" label="Order Matters"
+        onChange={@preserveOrderClicked}
+        checked={QuestionStore.isOrderPreserved(id)} />
+

         Question Stem
         <textarea onChange={@updateStem} defaultValue={QuestionStore.getStem(id)}></textarea>
-

-

-        Detailed Solution
-        <textarea onChange={@updateSolution} defaultValue={QuestionStore.getSolution(id)}></textarea>

           Answers:

         Add New
-

-          <input onChange={@preserveOrderClicked}
-            id="preserveOrder#{id}"
-            type="checkbox"
-            defaultChecked={QuestionStore.isOrderPreserved(id)}
-          /> Order Matters
-

           {answers}

+

+        Detailed Solution
+        <textarea onChange={@updateSolution} defaultValue={QuestionStore.getSolution(id)}></textarea>
+


--- src/trix/controllers/level_2_input_controller.coffee
+++ src/trix/controllers/level_2_input_controller.coffee
@@ -29,13 +29,11 @@
       else
         if handler = @keys[event.key]
           unless event.altKey or event.shiftKey
-            @event = event
-            handler.call(this)
+            @withEvent(event, handler)

     beforeinput: (event) ->
       if handler = @inputTypes[event.inputType]
-        @event = event
-        handler.call(this)
+        @withEvent(event, handler)
         @scheduleRender()

     dragenter: (event) ->
@@ -342,6 +340,14 @@

   # Event helpers

+  withEvent: (event, fn) ->
+    @event = event
+    try
+      result = fn.call(this)
+    finally
+      @event = null
+    result
+
   dragEventHasFiles = (event) ->
     "Files" in (event.dataTransfer?.types ? [])


TBCampaign: Added one more digit to GB counter.

--- client/landing/Intro/tbcampaignhomepageview.coffee
+++ client/landing/Intro/tbcampaignhomepageview.coffee
@@ -12,8 +12,7 @@
     [left]     = leftInTB.split " "
     left       = "99.99"  if left is "100.00"
     [tb, gb]   = left.split "."
-
-    tb = ["0", tb.first]  if tb.length is 1
+    tb         = ["0", tb.first]  if tb.length is 1

     return """

#{tb[0] or 0}

@@ -21,6 +20,7 @@

,

#{gb[0] or 0}

#{gb[1] or 0}

+
9

     """

   getDaysLeft: ->


If constructor is a JAccount means this is a followee bucket which has no view to show

--- client/landing/app/CommonViews/messagesList.coffee
+++ client/landing/app/CommonViews/messagesList.coffee
@@ -169,7 +169,7 @@
       appManager.tell 'Inbox', "goToMessages"
     else if @snapshot.anchor.constructorName in ["JComment", "JReview", "JOpinion"]
       KD.remote.api[@snapshot.anchor.constructorName].fetchRelated @snapshot.anchor.id, showPost
-    else
+    else unless @snapshot.anchor.constructorName is "JAccount"
       KD.remote.api[@snapshot.anchor.constructorName].one _id : @snapshot.anchor.id, showPost

     # {sourceName,sourceId} = @getData()[0]


fix opening first module in a folder when you edit it

--- scripts/views/workspace/sidebar/toc-branch.coffee
+++ scripts/views/workspace/sidebar/toc-branch.coffee
@@ -259,7 +259,7 @@
       # certain that the request to edit was initiated by a click in the
       # toc/picker.
       model = @model
-      if not model.getRoot?()
+      if model.getChildren?().length
         # Find the 1st leaf node (editable model)
         model = model.findDescendantDFS? (model) -> return model.getChildren().isEmpty()
         # if @model does not have `.findDescendantDFS` then use the original model


Groups: NotPermitted error is handled for setAlwaysOn

--- client/Environments/views/vms/vmalwaysontogglebuttonview.coffee
+++ client/Environments/views/vms/vmalwaysontogglebuttonview.coffee
@@ -11,10 +11,13 @@

     KD.remote.api.JVM.setAlwaysOn {vmName, status}, (err) =>
       if err
-        if err.name is "NOTSUBSCRIBED"
-          KD.showError "You have to upgrade your account for getting Always On VM"
-        else
-          KD.showError "You have exceeded your \"Always On\" VM quota"
+        switch err.name
+          when "NOTPERMITTED"
+            KD.showError "You are not allowed for this operation"
+          when "NOTSUBSCRIBED"
+            KD.showError "You have to upgrade your account for getting Always On VM"
+          else
+            KD.showError "You have exceeded your \"Always On\" VM quota"
         @decorate off

   decorate: (status) ->


activity: bind message event listeners to instances representing comments

--- client/Social/Activity/views/comments/listviewcontroller.coffee
+++ client/Social/Activity/views/comments/listviewcontroller.coffee
@@ -11,10 +11,21 @@

     super options, data

+    @eventHelper = new MessageEventHelper
+

   instantiateListItems: (items) ->

+    items.forEach @eventHelper.bound 'bindListeners'
+
     super items.sort (a, b) -> a.meta.createdAt - b.meta.createdAt
+
+
+  addItem: (item, index) ->
+
+    @eventHelper.bindListeners item
+
+    super item, index

   loadView: (mainView) ->


ApiAccess: fix initial state issue on api toggle

--- client/home/lib/utilities/components/apiaccess/container.coffee
+++ client/home/lib/utilities/components/apiaccess/container.coffee
@@ -15,7 +15,7 @@
       apiAccessState: TeamFlux.getters.apiAccessState
     }

-  componentWillMount: ->
+  componentDidMount: ->

     TeamFlux.actions.fetchApiTokens()
     TeamFlux.actions.fetchCurrentStateOfApiAccess()


Add copy operations to property based testing

--- test/integration/quickcheck.coffee
+++ test/integration/quickcheck.coffee
@@ -12,8 +12,8 @@

 deviceNames = [
-    "test#{faker.hacker.noun()}1"
-    "test#{faker.hacker.noun()}2"
+    "test#{faker.hacker.abbreviation()}1"
+    "test#{faker.hacker.abbreviation()}2"
 ]

 folders = [
@@ -69,7 +69,9 @@
     pid.kill()

-# List of directories and files that can be used when generating operations
+# List of directories and files that can be used when generating operations.
+# To ease debugging, filenames starts with an uppercase letter and dirnames
+# starts with a lowercase letter.
 dirs = ['.']
 files = []

@@ -101,9 +103,25 @@
     return createFileOperation() if files.length is 0
     root = if Math.random() > 0.5 then folders[0] else folders[1]
     src = faker.random.arrayElement files
-    dst = faker.company.bs()
+    parent = path.dirname src
+    dst = path.join parent, faker.name.firstName()
+    files.push dst
     op =
         move: 'file'
+        src: path.join root, src
+        dst: path.join root, dst
+    return op
+
+# Create a random copy file operation
+copyFileOperation = ->
+    return createFileOperation() if files.length is 0
+    root = if Math.random() > 0.5 then folders[0] else folders[1]
+    src = faker.random.arrayElement files
+    parent = path.dirname src
+    dst = path.join parent, faker.name.firstName()
+    files.push dst
+    op =
+        copy: 'file'
         src: path.join root, src
         dst: path.join root, dst
     return op
@@ -134,9 +152,25 @@
     return mkdirOperation() if dirs.length is 1
     root = if Math.random() > 0.5 then folders[0] else folders[1]
     src = faker.random.arrayElement dirs[1..]
-    dst = faker.company.catchPhrase()
+    parent = path.dirname src
+    dst = path.join parent, faker.company.bs()
+    dirs.push dst
     op =
         move: 'dir'
+        src: path.join root, src
+        dst: path.join root, dst
+    return op
+
+# Create a random copy dir operation
+copyDirOperation = ->
+    return mkdirOperation() if dirs.length is 1
+    root = if Math.random() > 0.5 then folders[0] else folders[1]
+    src = faker.random.arrayElement dirs[1..]
+    parent = path.dirname src
+    dst = path.join parent, faker.company.bs()
+    dirs.push dst
+    op =
+        copy: 'dir'
         src: path.join root, src
         dst: path.join root, dst
     return op
@@ -150,6 +184,8 @@
         when r < 0.40 then removeFileOperation()
         when r < 0.45 then moveFileOperation()
         when r < 0.50 then moveDirOperation()
+        when r < 0.52 then copyFileOperation()
+        when r < 0.54 then copyDirOperation()
         else createFileOperation()

 # Apply an operation on the file system
@@ -163,6 +199,8 @@
             fs.remove op.path, callback
         else if op.move
             fs.move op.src, op.dst, callback
+        else if op.copy
+            fs.copy op.src, op.dst, callback
         else
             throw new Error "Unsupported operation: #{op}"

@@ -171,7 +209,7 @@
     op = randomOperation()
     applyOperation op, ->
         random = Math.random()
-        wait = Math.floor random * random * random * 400
+        wait = Math.floor random * random * random * 1600
         remaining = timeout - wait
         if remaining > 0
             setTimeout (-> makeOperations remaining, callback), wait
@@ -180,9 +218,9 @@

 # In this test, we launch 2 cozy-desktops in two directories, and make them
-# synchronize via a central cozy instance. During ~20 seconds, we do some
+# synchronize via a central cozy instance. During ~10 seconds, we do some
 # operations in both directory (like creating files, or moving folders).
-# After that, we wait 10 seconds and we compare the 2 directories. They should
+# After that, we wait 20 seconds and we compare the 2 directories. They should
 # be identical: same files and folders.
 describe 'Property based testing', ->
     @slow 1000
@@ -195,7 +233,7 @@
         fs.ensureDirSync folders[0]
         fs.ensureDirSync folders[1]
         fs.unlink logs[2], ->
-            makeOperations 1000, done
+            makeOperations 2000, done

     it 'registers two devices', (done) ->
         registerDevice 0, (err) ->
@@ -210,10 +248,10 @@
                 done()

     it 'makes some operations', (done) ->
-        makeOperations 20000, done
+        makeOperations 10000, done

     it 'waits that the dust settle', (done) ->
-        setTimeout done, 10000
+        setTimeout done, 20000

     it 'stops the two cozy-desktop instances', (done) ->
         stopCozyDesktop @one, (code) =>


Tweak

Tweak

--- test/spec/join_tables.tests.coffee
+++ test/spec/join_tables.tests.coffee
@@ -109,7 +109,7 @@
           assert.ok(!err, "no errors: #{err}")
           assert.equal(res.status, 200, "status not 200. Status: #{res.status}. Body: #{util.inspect(res.body)}")
           owner_id = JSONUtils.parse(res.body).id
-          assert.ok(!!owner_id, "found owner")
+          assert.ok(!!owner_id, 'found owner')

           request(app)
             .get(JOIN_TABLE_ROUTE)
@@ -167,7 +167,7 @@
           assert.ok(!err, "no errors: #{err}")
           assert.equal(res.status, 200, "status not 200. Status: #{res.status}. Body: #{util.inspect(res.body)}")
           owner_id = JSONUtils.parse(res.body).id
-          assert.ok(!!owner_id, "found owner")
+          assert.ok(!!owner_id, 'found owner')

       Owner.cursor({$one: true}).include('reverses').toJSON (err, test_model) ->
         assert.ok(!err, "No errors: #{err}")


avoiding innecessary information

avoiding innecessary information

--- lib/Bumped.default.coffee
+++ lib/Bumped.default.coffee
@@ -22,10 +22,7 @@
     }

   logger:
-    outputType: (type, diff = '') ->
-      now = new Date()
-      date = "#{now.getHours()}:#{now.getMinutes()}:#{now.getSeconds()}"
-      "#{chalk.white('[')}#{chalk.gray(date)}#{chalk.white(']')} #{@keyword} "
+    outputType: (type, diff = '') -> "#{@keyword} "
     color: true
     align: false
     diff: false


Handle exception case where all *msg are null

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -43,7 +43,7 @@

       response = xhr.responseJSON

-      cause = if response?.exception_msg
+      cause = if response?.__meta?.schema_type is 'H2OError'
         serverError = new Flow.Error response.exception_msg
         serverError.stack = "#{response.dev_msg} (#{response.exception_type})" + "\n  " + response.stacktrace.join "\n  "
         serverError


fix(utils): Don’t overwrite _ in global scope

--- src/flux/models/utils.coffee
+++ src/flux/models/utils.coffee
@@ -225,8 +225,9 @@
   # to match an email address. We'd rather let false positives through.
   toEquivalentEmailForm: (email) ->
     # https://regex101.com/r/iS7kD5/3
-    [_, user, domain] = /^([^+]+).*@(.+)$/gi.exec(email)
+    [ignored, user, domain] = /^([^+]+).*@(.+)$/gi.exec(email) || [null, "", ""]
     "#{user}@#{domain}".trim().toLowerCase()
+

   emailIsEquivalent: (email1="", email2="") ->
     return true if email1 is email2


add HEALTH_CHECK_PROJECT_ID

add HEALTH_CHECK_PROJECT_ID

--- services/docstore/config/settings.defaults.coffee
+++ services/docstore/config/settings.defaults.coffee
@@ -11,7 +11,7 @@

 	docstore:
 		healthCheck:
-			project_id: ""
+			project_id: process.env['HEALTH_CHECK_PROJECT_ID']

 	max_doc_length: 2 * 1024 * 1024 # 2mb


null byte check for JSON.stringify in archiving

--- services/track-changes/app/coffee/MongoAWS.coffee
+++ services/track-changes/app/coffee/MongoAWS.coffee
@@ -46,6 +46,9 @@
 			return callback new Error("cannot find pack to send to s3") if not result?
 			return callback new Error("refusing to send pack with TTL to s3") if result.expiresAt?
 			uncompressedData = JSON.stringify(result)
+			if uncompressedData.indexOf("\u0000") != -1
+				error = new Error("null bytes found in upload")
+				logger.error err: error, project_id: project_id, doc_id: doc_id, pack_id: pack_id, error.message
 			zlib.gzip uncompressedData, (err, buf) ->
 				logger.log {project_id, doc_id, pack_id, origSize: uncompressedData.length, newSize: buf.length}, "compressed pack"
 				return callback(err) if err?


quick chat fix

quick chat fix

--- packages/rocketchat-ui-message/message/messageBox.coffee
+++ packages/rocketchat-ui-message/message/messageBox.coffee
@@ -31,16 +31,16 @@
 	subscribed: ->
 		return RocketChat.roomTypes.verifyCanSendMessage @_id
 	allowedToSend: ->
-		if RocketChat.roomTypes.readOnly @_id, Meteor.user()
-			return false
-
-		if RocketChat.roomTypes.archived @_id
-			return false
-
-		roomData = Session.get('roomData' + this._id)
-		if roomData?.t is 'd'
-			if ChatSubscription.findOne({ rid: this._id }, { fields: { archived: 1 } })?.archived
-				return false
+		# if RocketChat.roomTypes.readOnly @_id, Meteor.user()
+		# 	return false
+		#
+		# if RocketChat.roomTypes.archived @_id
+		# 	return false
+		#
+		# roomData = Session.get('roomData' + this._id)
+		# if roomData?.t is 'd'
+		# 	if ChatSubscription.findOne({ rid: this._id }, { fields: { archived: 1 } })?.archived
+		# 		return false

 		return true


Fixed Not showing upload button on safari

--- packages/rocketchat-ui-message/message/messageBox.coffee
+++ packages/rocketchat-ui-message/message/messageBox.coffee
@@ -67,7 +67,7 @@
 		return Template.instance().showVideoRec.get()

 	showSend: ->
-		if not Template.instance().isMessageFieldEmpty.get() or not Template.instance().showMicButton.get()
+		if not Template.instance().isMessageFieldEmpty.get()
 			return 'show-send'

 	showLocation: ->


Rename bookmarkRow to bookmarkStartRow

Rename bookmarkRow to bookmarkStartRow

--- lib/bookmarks-view.coffee
+++ lib/bookmarks-view.coffee
@@ -56,13 +56,13 @@
     @setItems(bookmarks)

   viewForItem: (bookmark) ->
-    bookmarkRow = bookmark.marker.getStartPosition().row
+    bookmarkStartRow = bookmark.marker.getStartPosition().row
     bookmarkEndRow = bookmark.marker.getEndPosition().row
     if filePath = bookmark.buffer.getPath()
-      bookmarkLocation = "#{path.basename(filePath)}:#{bookmarkRow + 1}"
+      bookmarkLocation = "#{path.basename(filePath)}:#{bookmarkStartRow + 1}"
     else
-      bookmarkLocation = "untitled:#{bookmarkRow + 1}"
-    if bookmarkRow isnt bookmarkEndRow
+      bookmarkLocation = "untitled:#{bookmarkStartRow + 1}"
+    if bookmarkStartRow isnt bookmarkEndRow
       bookmarkLocation += "-#{bookmarkEndRow + 1}"
     lineText = @getLineText(bookmark)


Changed generation from line to selection

--- lib/class-complete.coffee
+++ lib/class-complete.coffee
@@ -8,12 +8,9 @@
         buffer = ""
         editor = atom.workspace.getActivePaneItem()
         cursor = editor.cursors[0]
-        text = cursor.getCurrentBufferLine().trim()
+        text = editor.getSelections()[0].getText()

         classdef = module.exports.parseClassdef(text)
-
-        cursor.moveToEndOfLine()
-        editor.selectToBeginningOfLine()

         buffer += "#{classdef.fullname} = (function() {\n"

@@ -53,8 +50,6 @@

         def.fullname = string.split(":")[0]

-        string = string.split(".")
-        string = string[string.length - 1]
         string = string.replace(/\s/g, "")
         parts = string.split(":")


--- app/controllers/sync.coffee
+++ app/controllers/sync.coffee
@@ -100,7 +100,7 @@
     @setModel(className, id, model)
     # Set timestamp
     timestamp = data[2] or Date.now()
-    @setTime className, id, 'all', timestamp
+    @time.set className, id, 'all', timestamp
     log "Created #{ className }: #{ model.name }"
     # Broadcast event to connected clients
     @broadcast 'create', [className, model]
@@ -140,7 +140,7 @@
     timestamps = data[2]
     if timestamps
       for attr, time of timestamps
-        old = @getTime className, id, attr
+        old = @time.get className, id, attr
         if old > time
           delete timestamps[attr]
           delete changes[attr]
@@ -151,7 +151,7 @@
         continue if k is 'id'
         timestamps[k] = now

-    @setTime className, id, timestamps
+    @time.set className, id, timestamps

     # Update list
     if className is 'Task' and changes.list?
@@ -181,7 +181,7 @@
     model = @findModel className, id

     # Check that the model hasn't been updated after this event
-    return unless @checkTime className, id, timestamp
+    return unless @time.check className, id, timestamp

     # Destroy all tasks within that list
     if className is 'List'
@@ -201,7 +201,7 @@
       deleted: yes

     # Set timestamp
-    @setTime className, id, 'deleted', timestamp
+    @time.set className, id, 'deleted', timestamp
     log "Destroyed #{ className } #{ id }"
     @broadcast 'destroy', [className, id]


Match theme transitions more closely.

Match theme transitions more closely.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -108,7 +108,7 @@
     canvas.parentNode.removeChild(canvas)
     "rgba(#{data.data[0]}, #{data.data[1]}, #{data.data[2]}, #{data.data[3]})"

-  setNativeTheme: (calculateOverlay = false) ->
+  setNativeTheme: (calculateOverlay = false, timeout = 10) ->
     if feedbin.native && feedbin.data && feedbin.data.theme
       message = {
         action: "titleColor"
@@ -127,7 +127,7 @@

       setTimeout ( ->
         feedbin.nativeMessage("performAction", message)
-      ), 1
+      ), timeout

   nativeMessage: (name, data) ->
     if typeof(webkit) != "undefined"
@@ -2121,7 +2121,7 @@

       $(document).on 'hidden.bs.modal', () ->
         $("body").removeClass("modal-shown")
-        feedbin.setNativeTheme()
+        feedbin.setNativeTheme(false, 40)

     modalScrollPosition: ->
       $('.modal').on 'scroll', (event) ->


Show flash message instead of alert when note is invalid

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -112,7 +112,8 @@
   ###
   renderNote: (note) ->
     unless note.valid
-      alert('You have already used this award emoji !') if note.award
+      if note.award
+        new Flash('You have already used this award emoji !', 'alert')
       return

     # render note if it not present in loaded list


Parallax!

Parallax!

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -55,6 +55,13 @@
     $(this).closest('ul').css "overflow", "auto"
     return false

+  window.onscroll = ->
+    posX = (if (document.documentElement.scrollLeft) then document.documentElement.scrollLeft else window.pageXOffset)
+    posY = (if (document.documentElement.scrollTop) then document.documentElement.scrollTop else window.pageYOffset)
+    ground = document.getElementById("s")
+    groundparallax = calcParallax(100, 5, posY)
+    ground.style.backgroundPosition = "0 " + groundparallax + "px"
+
 fade_social = (icon, direction)->
   if direction == 'in'
     val = 1
@@ -63,3 +70,6 @@

   icon.clearQueue()
   icon.fadeTo 400, val
+
+calcParallax = (tileheight, speedratio, scrollposition) ->
+  (tileheight) - (Math.floor(scrollposition / speedratio) % (tileheight + 1))


Add support for data-expand links.

Add support for data-expand links.

--- app/assets/javascripts/pants.js.coffee
+++ app/assets/javascripts/pants.js.coffee
@@ -16,6 +16,12 @@
   _gs('track') if (_gs?)
   ga('send', 'pageview') if (ga?)

+expandElement = (e) ->
+  el = $(this).data('expand')
+  $(el).toggleClass('expanded')
+  e.preventDefault()
+
 $(document)
   .on 'page:load', initPage
   .on 'ready', initPage
+  .on 'click', 'a[data-expand]', expandElement


Show number of played on songs index

Show number of played on songs index

--- app/assets/javascripts/spark.js.coffee
+++ app/assets/javascripts/spark.js.coffee
@@ -4,11 +4,12 @@
     showIds = songs.data("show-ids")

     height = 16
+    labelOffset = 30

     svg = d3.select("#sparkline").
       append("svg:svg").
       attr("height", height).
-      attr("width", showIds.length * 2)
+      attr("width", showIds.length * 2 + labelOffset)

     node = svg.
       selectAll("g").
@@ -28,9 +29,15 @@
           else
             0

+        d3.select(clone).append("svg:text")
+          .attr("x", labelOffset - 7)
+          .attr("y", 13)
+          .style("text-anchor", "end")
+          .text(performedShowIds.length)
+
         clonedNode = d3.select(clone).selectAll("g").data(showIds).enter()
         clonedNode.append("svg:rect")
-            .attr("x", (d, i) -> i * 2 + 1)
-            .attr("y", calculateY)
-            .attr("height", height / 2)
-            .attr("width", 1)
+          .attr("x", (d, i) -> i * 2 + labelOffset)
+          .attr("y", calculateY)
+          .attr("height", height / 2)
+          .attr("width", 1)


Use gravatar data on-click

Use gravatar data on-click

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -8,21 +8,21 @@
             message: I18n.t('js.errors.email.invalid')
           notEmpty:
             message: I18n.t('js.errors.email.blank')
-
+
       "user[password]":
         validators:
           notEmpty:
             message: I18n.t('js.errors.password.blank')
           identical:
-            field: 'user[password_confirmation]'
+            field: 'user[password_confirmation]'
             message: I18n.t('js.errors.password.invalid')
-
+
       "user[password_confirmation]":
         validators:
           notEmpty:
             message: I18n.t('js.errors.password_confirmation.blank')
           identical:
-            field: 'user[password]'
+            field: 'user[password]'
             message: I18n.t('js.errors.password_confirmation.invalid')
   $('.session_form').bootstrapValidator
     message: I18n.t('js.errors.value.invalid')
@@ -33,10 +33,14 @@
             message: I18n.t('js.errors.email.invalid')
           notEmpty:
             message: I18n.t('js.errors.email.blank')
-
+
       "user[password]":
         validators:
           notEmpty:
             message: I18n.t('js.errors.password_confirmation.blank')

-$(document).on "ready page:load", load_bootstrap_validator+$(document).on "ready page:load", load_bootstrap_validator
+
+$ ->
+  $('.from-gravatar').click (e) ->
+    $('input[name="'+$(this).data('for')+'"]').val($(this).data('value'))


change session on login

change session on login

--- app/controllers/auth_controller.coffee
+++ app/controllers/auth_controller.coffee
@@ -1,15 +1,24 @@
 ApplicationController = Caboose.get('ApplicationController')
+passport = Caboose.app.passport

 class AuthController extends ApplicationController
-  before_action ((next) ->
-    Caboose.app.passport.authenticate('google', {successRedirect: '/', failureRedirect: '/login'})(@request, @response, next)
-    ), {only: ['google_new', 'google_create']}
+  before_action (next) ->
+    Caboose.app.passport.authenticate('google')(@request, @response, next)
+  , {only: ['google_new', 'google_create']}

   google_new: -> @render()
-  google_create: -> @redirect_to('/')
-
+  google_create: ->
+    user = @request.user
+    init = passport.initialize()
+    sess = passport.session()
+
+    @request.session.regenerate =>
+      init @request, @response, =>
+        sess @request, @response, =>
+          @request.logIn user, =>
+            @redirect_to('/')
+
   destroy: ->
     @request.logOut()
     @session.destroy()
-    @response.clearCookie('connect.sid');
     @redirect_to '/'