Add comments make it in correct order

Add comments make it in correct order

--- client/app/MainApp/sidebar/sidebarview.coffee
+++ client/app/MainApp/sidebar/sidebarview.coffee
@@ -10,6 +10,7 @@
     @_finderExpanded  = no
     @_popupIsActive   = no

+    # Avatar area
     @avatar = new AvatarView
       tagName    : "div"
       cssClass   : "avatar-image-wrapper"
@@ -21,6 +22,10 @@
     @avatarAreaIconMenu = new AvatarAreaIconMenu
       delegate     : @

+    @statusLEDs = new KDView
+      cssClass : 'status-leds'
+
+    # Main Navigations
     @navController = new NavigationController
       view           : new NavigationList
         type         : "navigation"
@@ -31,6 +36,7 @@

     @nav = @navController.getView()

+    # Main Navigations Footer Menu
     @footerMenuController = new NavigationController
       view           : new NavigationList
         type         : "footer-menu"
@@ -41,17 +47,22 @@

     @footerMenu = @footerMenuController.getView()

+    # Finder Header
     @finderHeader = new KDCustomHTMLView
       tagName   : "h2"
       pistachio : "{{#(profile.nickname)}}.#{location.hostname}"
     , account

+    # File Tree
     @finderController = new NFinderController
       useStorage        : yes
       addOrphansToRoot  : no

     @finder = @finderController.getView()
-
+    KD.registerSingleton "finderController", @finderController
+    @finderController.on 'ManageResources', @bound 'toggleResources'
+
+    # Finder Bottom Controls
     @finderBottomControlsController = new KDListViewController
       view        : new FinderBottomControls
       wrapper     : no
@@ -78,13 +89,7 @@
           callback?()
       ]

-    KD.registerSingleton "finderController", @finderController
     @listenWindowResize()
-
-    @statusLEDs = new KDView
-      cssClass : 'status-leds'
-
-    @finderController.on 'ManageResources', @bound 'toggleResources'

   resetAdminNavController:->
     @utils.wait 1000, =>


Still broken

Still broken

--- test/unit/07-Playbook.coffee
+++ test/unit/07-Playbook.coffee
@@ -19,19 +19,25 @@
     @playbook = new Playbook @robot
     @spy = _.mapObject Playbook.prototype, (val, key) ->
       sinon.spy Playbook.prototype, key # spy on all the class methods
+    require('../scripts/ping.coffee') @robot # run hubot through basic script
+    @robot.on 'respond', (res) => @res = res # store every response sent
+    @room.user.say 'tester', 'hubot ping' # create first response

-  context '.dialogue' ->
+  afterEach ->
+    _.invoke @spy, 'restore' # restore all the methods
+
+  context '.dialogue', ->

     beforeEach ->
-      @dialogue = @playbook.dialogue()
+      @dialogue = @playbook.dialogue @res

     it 'creates Dialogue instance', ->
-      @dialogue.should.be.instanceof Scene
+      @dialogue.should.be.instanceof Dialogue

-    it 'did not throw any errors', ->
+    it 'does not throw any errors', ->
       @spy.dialogue.should.not.have.threw

-  context '.scene' ->
+  context '.scene', ->

     beforeEach ->
       @scene = @playbook.scene()
@@ -39,7 +45,7 @@
     it 'makes a Scene :P', ->
       @scene.should.be.instanceof Scene

-    it 'did not throw any errors', ->
+    it 'does not throw any errors', ->
       @spy.scene.should.not.have.threw

 # describe 'Usage (general messaging test cases)', ->


pull request #107 contains that fails on Windows.

--- test/unit/config.spec.coffee
+++ test/unit/config.spec.coffee
@@ -178,7 +178,7 @@

     it 'should compile coffeescript config', ->
       config = e.parseConfig '/conf/coffee.coffee', {}
-      expect(config.files).toEqual ['/conf/one.js', '/conf/two.js']
+      expect(config.files).toEqual [resolveWinPath('/conf/one.js'), resolveWinPath('/conf/two.js')]

     it 'should set defaults with coffeescript', ->
       config = e.parseConfig '/conf/coffee.coffee', {}


remove unnecessary console log

remove unnecessary console log

--- app/assets/javascripts/modules/ajax.coffee
+++ app/assets/javascripts/modules/ajax.coffee
@@ -23,7 +23,6 @@
     @stack[container] = [] unless @stack[container]
     @stack[container].push targetURL

-    console.log targetURL
     $.ajax
       url: targetURL
       success: (data) ->


Wire up selecting person_id from dynamic list

A bit hackish, but a starting point. Needs styling adjustments.

--- app/assets/javascripts/questions.js.coffee
+++ app/assets/javascripts/questions.js.coffee
@@ -33,6 +33,8 @@
         $("div.twitter div.avatar img").attr 'src', data[0].profile_image_url
         $("div.twitter .select-person div.person-info p").html data[0].description
         $("div.twitter .select-person li").fadeTo(300, 1)
+        # TODO: change below to reset person_id to val of twitter selected person
+        $('#question_person_id').val ''

   $("#twitter").blur(->
@@ -60,6 +62,19 @@
     getPeople()
   )

+  updateSelectedPerson = (e) ->
+    personLi = e.delegateTarget
+
+    $('.select_box').children('input').attr 'checked', false
+    $('.icon-ok-sign').hide()
+
+    selectedPersonInput = $(personLi).children('.select_box').children('input')
+    selectedPersonInput.attr 'checked', true
+    $('#question_person_id').val selectedPersonInput.val()
+
+    $(personLi).children('.icon-ok-sign').show()
+
+
   getPeople = (->
     address = $('#street').val()
     address += ' ' + $('#city').val()
@@ -73,10 +88,11 @@
       success: (data) ->
         $('label.select-person').fadeTo(300, 1)
         personList = $('div.address_lookup ol.people-list').first()
+        personList.html('')
         $(data).each ->
           liVal = '
'
           liVal += '
'
-          liVal += "<input type=\"radio\" name=\"person-select\" id=\"#{@id}\" />
"
+          liVal += "<input type=\"radio\" name=\"person-select\" id=\"#{@id}\" value=\"#{@id}\" />
"

           liVal += '
'
           if @photo_url?
@@ -98,6 +114,10 @@
           liVal += ''

           liVal += "
"
+
           personList.append liVal
+
           personList.children('li:last').fadeTo(300, 1)
+        personList.children('li').on 'click', (e) ->
+          updateSelectedPerson e
     )


Fix setDefaultResults on QuickJump.Base

Fix setDefaultResults on QuickJump.Base

--- app/assets/javascripts/quickjump.js.coffee
+++ app/assets/javascripts/quickjump.js.coffee
@@ -230,7 +230,8 @@
   setDefaultResults: ->
     @results = this.getDefaultResults()
     @renderer.renderResults('')
-    @controls.active_result = -1
+    @active_result = 0
+    this.markActiveResult()

   setResults: (query, results) ->
     OUT.clearLazyTimer "quickjump_request"
@@ -238,7 +239,7 @@
     @results = this.getMatchingResults(query, results)
     @renderer.renderResults(query)
     @active_result = 0
-    this.markActiveResult();
+    this.markActiveResult()

   matchResults: (query, results) ->
     results.filter (result) ->


better catch 'keydown' instead of 'blur' because the latter one cannot be canceled

--- app/assets/javascripts/transactions.coffee
+++ app/assets/javascripts/transactions.coffee
@@ -29,19 +29,11 @@
 #   window.prompt "Cash given (in EUR):"
 #   bind_overlay_hotkeys()

-mark_error = (element) ->
+mark_error = (event, element) ->
   play_error_sound()
   element.parent().addClass "field_with_errors"
-  # deactivate blur handler of newly focused element ->
-  # setTimeout() because this code here is executed _before_ the focus actually changes
-  setTimeout (-> $(document.activeElement).off "blur"), 0
-  # re-focus the currently focused element (will be unfocused right after this blur handler finishes)
-  # ...and afterwards reinstall the blur handler of the intermediately focused element
-  setTimeout( ( ->
-      newly_focused_element = $(document.activeElement)
-      setTimeout (-> newly_focused_element.blur input_blur_handler), 0
-      element.focus() ),
-    1 )
+  event.preventDefault()
+  element.select()

 unmark_error = (element) ->
   element.parent().removeClass "field_with_errors"
@@ -62,7 +54,7 @@
   else
     !text.trim()

-check_and_correct_field = (element, test_function, warn_function) ->
+check_and_correct_field = (event, element, test_function, warn_function) ->
   # wrapper for pass-by-modifiable-reference
   new_val = val: element.val()
   correct = false
@@ -75,36 +67,37 @@
     element.val new_val.val
     unmark_error element
   else
-    mark_error element
+    mark_error event, element

 play_error_sound = ->
   $("audio").trigger "play"

-input_blur_handler = ->
-  field_name = /\[([^\]]*)\]$/.exec( $(this).attr('name') )[1]
-  switch field_name
-    when "price"
-      check_and_correct_field $(this), is_valid_price, is_high_price
-      # TODO also check corresponding seller
-      row_index = /\[(\d+)\]\[[^\]]*\]$/.exec( $(this).attr('name') )[1]
-      transaction_rows = $("table#items_table > tbody > tr")
-      if row_index >= transaction_rows.length - 3
-        last_row = transaction_rows.filter(":last")
-        last_row_index = /\[(\d+)\]\[[^\]]*\]$/.exec( $(".field input", last_row).attr('name') )[1]
-        new_row_index = parseInt(last_row_index) + 1
-        new_row = last_row.clone()
-        $(".field label", new_row).each ->
-          $(this).attr("for", $(this).attr("for").replace(last_row_index, new_row_index))
-        $(".field input", new_row).each ->
-          $(this).attr("id", $(this).attr("id").replace(last_row_index, new_row_index))
-          $(this).attr("name", $(this).attr("name").replace(last_row_index, new_row_index))
-          register_field this
-        new_row.insertAfter(last_row)
-    when "seller_code"
-      check_and_correct_field $(this), is_valid_seller
+input_blur_handler = (event) ->
+  if event.which == 9 || event.which == 13
+    field_name = /\[([^\]]*)\]$/.exec( $(this).attr('name') )[1]
+    switch field_name
+      when "price"
+        check_and_correct_field event, $(this), is_valid_price, is_high_price
+        # TODO also check corresponding seller
+        row_index = /\[(\d+)\]\[[^\]]*\]$/.exec( $(this).attr('name') )[1]
+        transaction_rows = $("table#items_table > tbody > tr")
+        if row_index >= transaction_rows.length - 3
+          last_row = transaction_rows.filter(":last")
+          last_row_index = /\[(\d+)\]\[[^\]]*\]$/.exec( $(".field input", last_row).attr('name') )[1]
+          new_row_index = parseInt(last_row_index) + 1
+          new_row = last_row.clone()
+          $(".field label", new_row).each ->
+            $(this).attr("for", $(this).attr("for").replace(last_row_index, new_row_index))
+          $(".field input", new_row).each ->
+            $(this).attr("id", $(this).attr("id").replace(last_row_index, new_row_index))
+            $(this).attr("name", $(this).attr("name").replace(last_row_index, new_row_index))
+            register_field this
+          new_row.insertAfter(last_row)
+      when "seller_code"
+        check_and_correct_field event, $(this), is_valid_seller

 register_field = (field) ->
-  $(field).blur input_blur_handler
+  $(field).keydown input_blur_handler
   $(field).focus(-> $(this).select())

 # bind_overlay_hotkeys = ->
@@ -130,12 +123,6 @@
             event.which == 38  # up arrow key
           event.stopPropagation()
           set_last_value(event.target)
-# @@@ fix for ENTER press
-#        if event.which == 13
-#          event.stopPropagation()
-#          form_tag = $(event.target).closest("form")
-#          setTimeout ( -> $(event.target).blur() ), 0
-#          setTimeout ( -> form_tag.submit() ), 100
     $(".field input").each ->
       register_field this
     if $(".field_with_errors").length


speed up the fades

speed up the fades

--- app/assets/javascripts/workflows.js.coffee
+++ app/assets/javascripts/workflows.js.coffee
@@ -55,15 +55,15 @@

 @show_job_panel = (show) ->
   if show?
-    $("#job-details-panel").fadeIn(200)
-  else
-    $("#job-details-panel").fadeOut(100)
+    $("#job-details-panel").fadeIn(50)
+  else
+    $("#job-details-panel").fadeOut(50)

 @show_script_details_panel = (show) ->
   if show?
-    $("#script-details-panel").fadeIn(200)
-  else
-    $("#script-details-panel").fadeOut(100)
+    $("#script-details-panel").fadeIn(50)
+  else
+    $("#script-details-panel").fadeOut(50)

 @update_status_label = (id, label) ->
   if label? && id?


Add icon to permission add button

Add icon to permission add button

--- app/assets/rdb/PermissionComponents.coffee
+++ app/assets/rdb/PermissionComponents.coffee
@@ -106,6 +106,7 @@
       th [
         Button
           onClick: (e) => @addPermission()
+          icon: 'plus'
           'Add'
       ]
       th [


renamed View::filter to View::afterRender

Change-Id: If7a19ad055966f2147c173b02ea9011fc4741ee9
Reviewed-on: https://gerrit.instructure.com/13730
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Cameron Matheson <1d572acbfa68c7c6e541c7b840d6b622e5c0dc91@instructure.com>

--- app/coffeescripts/backbone-ext/View.coffee
+++ app/coffeescripts/backbone-ext/View.coffee
@@ -55,13 +55,17 @@
       @[name] = @$(selector) for selector, name of @els if @els

     ##
-    # Filters elements to add behavior and bindings. Can be called automatically
-    # in `render`, so be careful not to call it twice
+    # Add behavior and bindings to elements. Can be called automatically in
+    # `render`, so be careful not to call it twice
     #
     # @api public
-    filter: ->
-      @$('[data-bind]').each => @createBinding.apply this, arguments
+    afterRender: ->
+      @$('[data-bind]').each @createBinding
       #@$('[data-behavior]').each => @_createBehavior.apply this, arguments
+
+    ##
+    # backwards compat for old afterRender name
+    filter: @::afterRender

     ##
     # in charge of getting variables ready to pass to handlebars during render
@@ -97,7 +101,7 @@
     #
{I will always mirror @model.get('foo') in here}

     #
     # @api public
-    createBinding: (index, el) ->
+    createBinding: (index, el) =>
       $el = $ el
       attribute = $el.data 'bind'
       @model.on "change:#{attribute}", (model, value) =>


Remove unnecessary line

Remove unnecessary line

--- app/assets/javascripts/gl_crop.js.coffee
+++ app/assets/javascripts/gl_crop.js.coffee
@@ -110,7 +110,6 @@
     data = $(btn).data()

     if @modalCropImg.data('cropper') && data.method
-      data = $.extend {}, data
       result = @modalCropImg.cropper data.method, data.option

   onFileInputChange: (e, input) ->


remove university UI code

remove university UI code

--- app/assets/javascripts/members.js.coffee
+++ app/assets/javascripts/members.js.coffee
@@ -5,20 +5,6 @@
 $ ->
   $('[data-toggle="tooltip"]').tooltip(placement: 'right')

-  $('select#member_university_name').on 'change', (e) =>
-    parentElement = $('#member_university_name_other').parents('.control-group')
-    parentElement.hide()
-    if e.target.value == 'Other (please specify)'
-      parentElement.show()
-      parentElement.find('input').focus()
-
-  $('select#member_university_qualification').on 'change', (e) =>
-    parentElement = $('#member_university_qualification_other').parents('.control-group')
-    parentElement.hide()
-    if e.target.value == 'Other (please specify)'
-      parentElement.show()
-      parentElement.find('input').focus()
-
   $('select#member_origin').on 'change', (e) =>
     if e.target.value == ''
       $('.third-parties').hide()


[style] Improved coffeescript style.

[style] Improved coffeescript style.

--- app/assets/javascripts/navigation.coffee
+++ app/assets/javascripts/navigation.coffee
@@ -1,12 +1,16 @@
+# A new window/tab event can be one of the following
+# ctrlKey (Windows)
+# metaKey (OSX)
+# shiftKey (OS agnositc)
+# New tab with middle click
+is_new_window_or_tab_event = (event) ->
+  middle_click_event = (event.button && event.button == 1)
+  event.ctrlKey || event.metaKey || event.shiftKey || middle_click_event
+
 ready = ->
   $('.js-navigation').on('click', (event) ->
-    if (event.ctrlKey or # New tab (windows)
-    event.shiftKey or # New window
-    event.metaKey or # New tab (mac)
-    (event.button and event.button == 1) # New tab (middle click)
-    )
-      return
-    return $('.loading-indicator').show())
+    return if is_new_window_or_tab_event(event)
+    $('.loading-indicator').show())

 $(document).ready(ready)
 $(document).on('page:load', ready)


Fix Overlay

Fix Overlay

--- app/assets/javascripts/overlay.js.coffee
+++ app/assets/javascripts/overlay.js.coffee
@@ -40,9 +40,10 @@
     else
       @close()

-  open: (target) ->
+  open: (target = $(document)) ->
+    target = $(target)
     @showSpinner()
-    @prepareOpen($(target))
+    @prepareOpen(target)
     @appendContent()
     @setDimensions()
     @show()
@@ -58,7 +59,7 @@
   hideSpinner: ->
     @spinner.stop()

-  prepareOpen: (target = $(document)) ->
+  prepareOpen: (target) ->
     @closed = false
     @updateUrl(target)
     @dom.mask.show()


Added something

Added something

--- app/controllers/device-controller.coffee
+++ app/controllers/device-controller.coffee
@@ -3,6 +3,7 @@
 debug = require('debug')('meshblu-email-password-authenticator:device-controller')
 _ = require 'lodash'
 validator = require 'validator'
+url = require 'url'

 class DeviceController
   constructor: (meshbluJSON, @meshblu) ->


Direct users to sign up if they anonymously try to buy gems

--- app/views/play/modal/BuyGemsModal.coffee
+++ app/views/play/modal/BuyGemsModal.coffee
@@ -4,6 +4,7 @@
 utils = require 'core/utils'
 SubscribeModal = require 'views/core/SubscribeModal'
 Products = require 'collections/Products'
+CreateAccountModal = require 'views/core/CreateAccountModal'

 module.exports = class BuyGemsModal extends ModalView
   id:
@@ -71,6 +72,7 @@

   onClickProductButton: (e) ->
     @playSound 'menu-button-click'
+    return @openModalView new CreateAccountModal() if me.get('anonymous')
     productID = $(e.target).closest('button').val()
     # Don't throw error when product is not found
     if productID.length == 0


Workspace: Fixed workspaceRef binding for host tabbed editor.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -13,14 +13,18 @@
     @isJoinedASession = @getOptions().sessionKey
     @openedFiles      = []

-    log "joined an old session again, creating new tabbed editor"
+    log "joined an old session again, creating new tabbed editor" if @isJoinedASession

     @createEditorTabs()
-    return @createEditorInstance()  unless @isJoinedASession
+    @createEditorInstance()  unless @isJoinedASession

     @workspaceRef.on "value", (snapshot) =>
-      tabs = snapshot.val() and snapshot.val().tabs # not same with {tabs} = snapshot.val()?
+      val  = snapshot.val()
+      return unless val
+
+      {tabs} = snapshot.val()
       return unless tabs
+
       for key, data of tabs
         if data.path and @openedFiles.indexOf(data.path) is -1
           file = FSHelper.createFileFromPath data.path


add unfold to display buffer

add unfold to display buffer

--- spec/app/display-buffer-spec.coffee
+++ spec/app/display-buffer-spec.coffee
@@ -193,6 +193,21 @@
           fold = displayBuffer.lineForRow(1).fold
           expect(fold.startRow).toBe 1
           expect(fold.endRow).toBe 9
+
+   describe ".unfoldScopeContainingBufferRow(bufferRow)", ->
+      describe "when bufferRow can be unfolded", ->
+        it "destroys a fold based on the syntactic region starting at the given row", ->
+          displayBuffer.foldScopeContainingBufferRow(1)
+          expect(displayBuffer.lineForRow(1).fold).toBeDefined()
+
+          displayBuffer.unfoldScopeContainingBufferRow(1)
+          expect(displayBuffer.lineForRow(1).fold).toBeUndefined()
+
+      describe "when bufferRow can't be unfolded", ->
+        it "does not throw an error", ->
+          expect(displayBuffer.lineForRow(1).fold).toBeUndefined()
+          displayBuffer.unfoldScopeContainingBufferRow(1)
+          expect(displayBuffer.lineForRow(1).fold).toBeUndefined()

   describe "primitive folding", ->
     beforeEach ->


Test logout signal is emitted after destroy

--- test/user/model.spec.coffee
+++ test/user/model.spec.coffee
@@ -4,5 +4,21 @@

 describe 'User', ->

+
   it 'defaults to not logged in', ->
     expect(User.isLoggedIn()).to.be.false
+
+  it 'resets courses when destroy is called, but still emits signals', ->
+    fakeCourse =
+      destroy: -> true
+    sinon.stub(fakeCourse, 'destroy')
+    User.courses = [fakeCourse]
+    logoutSpy = sinon.spy()
+    User.channel.on 'logout.received', logoutSpy
+    User.destroy()
+    expect(User.courses).to.be.empty
+    expect(fakeCourse.destroy).to.have.been.called
+    expect(logoutSpy).not.to.have.been.called
+
+    User._signalLogoutCompleted()
+    expect(logoutSpy).to.have.been.called


add debug info

add debug info

--- src/module/config/remote_config_cache.coffee
+++ src/module/config/remote_config_cache.coffee
@@ -70,6 +70,7 @@
 		JSON.stringify(@_sysData, null, 4)

 	_set : (key, value, needCheck)->
+		Log.debug("_set key:#{key} needCheck:#{needCheck}")
 		if key in SYS_KEYS
 			@_sysData[key] = value
 			return


Fix #30 - cope with missing flight durations

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -237,8 +237,14 @@
       angular.extend(marker.payload, payload)
       p = marker.payload
       if p.userName? && p.id? && p.summaryText?
+        # Some fields might be missing
         avatarImg = if p.userAvatarImage?
           """"""
+        else
+          ""
+
+        durationString = if p.flightDuration?
+          "#{Math.round(p.flightDuration / 60)} minutes
"
         else
           ""

@@ -253,7 +259,7 @@

                 #{p.userName}

                 #{p.summaryText}

-                #{Math.round(p.flightDuration / 60)} minutes

+                #{durationString}


Move domready tableBank block to initializations

--- app/assets/javascripts/domready.coffee
+++ app/assets/javascripts/domready.coffee
@@ -12,6 +12,11 @@
     Orientation.dropdown()
     Orientation.inputSelect()

+    Orientation.tableBank
+      context : $('.markdown')
+      element : $('table')
+      gutter  : '20'
+
     #---------------------------------
     #
     #  Accordion
@@ -25,14 +30,6 @@
     $( '.js-accordion-btn' ).on 'click', ( event ) ->
       $(@).closest( '.js-accordion' ).find( '.js-accordion-content' ).slideToggle()

-    #---------------------------------
-    #  Initialization
-    #---------------------------------
-
-    Orientation.tableBank
-      context : $('.markdown')
-      element : $('table')
-      gutter  : '20'

     # -------------------------------------
     #   Fullscreen Editor


adding one comment by default to the post

--- tests/factories/posts.coffee
+++ tests/factories/posts.coffee
@@ -1,7 +1,11 @@
-Post = Factory.define('post')
-  .sequence 'id'
-  .sequence 'title', (n) -> "Whatever_##{n}"
-  .attr 'content', 'Meh'
+_ = require 'lodash'
+require './comments'

-module.exports = Post
+module.exports =
+  Factory.define('post')
+    .sequence 'id'
+    .sequence 'title', (n) -> "Whatever_##{n}"
+    .attr 'comments', ['comments'], (comments = _.range 1) ->
+      comments.map -> Factory.build 'comment'
+    .attr 'content', 'Meh'


fuera logs

fuera logs

--- app/assets/javascripts/myrtc.js.coffee
+++ app/assets/javascripts/myrtc.js.coffee
@@ -66,7 +66,6 @@
       connection.autoSaveToDisk = false
       connection.onFileProgress = (packets) ->
         appendProgressBar(packets.length, packets.remaining)
-        console.log(packets)
       connection.onFileSent = (file) ->
         removeProgressBar()
         appendDIV('Le llegó el fichero', 'bot')


Fixed sharethis buttons after ajax error call.

--- app/assets/javascripts/order.js.coffee
+++ app/assets/javascripts/order.js.coffee
@@ -47,6 +47,7 @@
     $main_header = $('#wrapper > #main_header').detach()
     $('#wrapper').empty()
     $('#wrapper').html($main_header).append(data.responseText)
+    stButtons.locateElements()
   ).live('ajax:complete', (xhr, data, status) ->
     $.fancybox.hideActivity()
   )


improved check for paagedown toolbar (accomodate back button via turbolinks)

--- app/assets/javascripts/plate.js.coffee
+++ app/assets/javascripts/plate.js.coffee
@@ -47,19 +47,22 @@

   initMarkdownToolbar = () ->
     $('textarea.wmd-input').each (i, input) ->
-      #console.log(input)
-      unless $(input).data('initialized')
+      #console.log $(input).data('initialized')
+      #unless $(input).data('initialized')
+      #console.log $(input).parent().find('.btn-toolbar').length
+      unless $(input).parent().find('.btn-toolbar').length > 0
         attr = $(input).attr('id').split('wmd-input')[1]
         converter = new Markdown.Converter()
         Markdown.Extra.init(converter)
-        help =
-          handler: () ->
-            window.open('http://daringfireball.net/projects/markdown/syntax')
-            return false
-          title: "<%= I18n.t('components.markdown_editor.help', default: 'Markdown Editing Help') %>"
-        editor = new Markdown.Editor(converter, attr, help)
+        #help =
+          #handler: () ->
+            #window.open('http://daringfireball.net/projects/markdown/syntax')
+            #return false
+          #title: "<%= I18n.t('components.markdown_editor.help', default: 'Markdown Editing Help') %>"
+        editor = new Markdown.Editor(converter, attr)
         editor.run()
         $(input).data('initialized', true)
+        console.log $(input).data('initialized')
   initMarkdownToolbar()

 $(document).on 'turbolinks:load', ->


remove coffee script

remove coffee script

--- app/assets/javascripts/posts.js.coffee
+++ app/assets/javascripts/posts.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


Modal works on first click.

Modal works on first click.

Fixed small scheduling bug. I misread the documentation
of the modal() function, which resulted in scheduling by
clicking on an empty slot only working on the second click.

--- app/assets/javascripts/schedule.coffee
+++ app/assets/javascripts/schedule.coffee
@@ -83,7 +83,8 @@
       drop: (event, ui) ->
         add_event_to_slot(ui.draggable, this)
     )
-  $("body").delegate("table.room td", "click", (click_even) ->
+  $("#add-event-modal").modal()
+  $("body").delegate("table.room td", "click", (click_event) ->
     td = $(this)
     $("#add-event-modal #current-time").html(td.data("time"))
     $("ul#unscheduled-events").undelegate("click")
@@ -102,7 +103,7 @@
       $("#add-event-modal").modal('hide')
       click_event.preventDefault()
     )
-    $("#add-event-modal").modal()
+    $("#add-event-modal").modal('show')
     click_event.stopPropagation()
   )
   for event in $("div.event")


remove dev code

remove dev code

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -4,18 +4,11 @@

 $ ->
   $window = $(window)
-  console.log("Screen Resolution: #{screen.width} x #{screen.height}")
-  console.log("Window Size: #{$window.width()} x #{$window.height()}")
-  console.log("HTML 5 Support? #{Modernizr.canvas}")
-  console.log("CSS 3 Support? #{Modernizr.borderradius}")
   plugins = ( "#{plugin.name} #{plugin.description}" for plugin in navigator.plugins)
   window.plugins = plugins
   flash_version = flash for flash in plugins when flash.match /Shockwave/
-  # console.log(flash_version)
-  # console.log("Flash Version: #{flash_version}")
   user_id = parseInt($('#user_id').text())
   user_ip_address = $('#user_ip_address').text()
-  # console.log(user_id)
   audio = Modernizr.audio
   video = Modernizr.video
   audio_formats = "ogg: #{audio.ogg}, wav: #{audio.wav}, m4a: #{audio.m4a}"


fixed chartjs compatibility with angular 1.3.15

--- app/assets/javascripts/mno_enterprise/angular/directives/impac/widgets/common/chart.js.coffee
+++ app/assets/javascripts/mno_enterprise/angular/directives/impac/widgets/common/chart.js.coffee
@@ -4,7 +4,7 @@
 # Component
 #============================================

-module.directive('widgetCommonChart', ['TemplatePath','$compile', (TemplatePath,$compile) ->
+module.directive('widgetCommonChart', ['TemplatePath','$compile','$timeout', (TemplatePath,$compile,$timeout) ->
   return {
       restrict: 'A',
       scope: {
@@ -42,6 +42,7 @@
           ,(value) ->
             if value?
               scope.draw()
+              $timeout((-> scope.draw()),100)
         ,true)
     }
 ])


order highscore desc

order highscore desc

--- app/client/highscore/highscores.coffee
+++ app/client/highscore/highscores.coffee
@@ -7,7 +7,7 @@
     Games.find(
       { score: { $gt: 0 } },
       { state: { $eq: 'finished' } }
-      { sort: { score: 1 } }
+      { sort: { score: -1 } }
     ).fetch()

 UI.registerHelper 'playerUsername', (playerId) ->


App now runs with 'Neft' global object in bundle building

--- process.coffee
+++ process.coffee
@@ -227,8 +227,10 @@
 		module.exports = yaml.safeLoad fs.readFileSync filename, 'utf8'

 	if opts.neftFilePath
-		Neft = require opts.neftFilePath
-	global.Neft = ->
+		console.log = ->
+		global.Neft = require opts.neftFilePath
+	else
+		global.Neft = ->

 	ASSERT_MODULE_PATH = fs.realpathSync('') + '/node_modules/assert'


Use proper coffee comments

Use proper coffee comments

--- railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee
+++ railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee
@@ -1,3 +1,3 @@
-// Place all the behaviors and hooks related to the matching controller here.
-// All this logic will automatically be available in application.js.
-// You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


Oscillator

Oscillator

--- test/oscillator.coffee
+++ test/oscillator.coffee
@@ -8,8 +8,8 @@
   equals o(0), 10 # => 10
   equals o(30), 10 # => 10
   equals o(15), -10 # => -10
-  equals o(7.5).toFixed(), 10 #=> 0
-  equals o(22.5), -10 #=> 0
+  equals o(7.5).toFixed(6), 10 #=> 0
+  equals o(22.5).toFi, -10 #=> 0

 # Clear out the module


requestObjectAt marked as private; expire method

--- addon/lib/sparse-array.coffee
+++ addon/lib/sparse-array.coffee
@@ -49,7 +49,7 @@
     })
     @

-  isExpiredAt: (timestamp) ->
+  isExpiredAt: (timestamp = 0) ->
     return false if get(@, 'is_loading')
     !!(get(@, 'is_stale') or get(@, 'last_fetched_at') <= timestamp)

@@ -270,7 +270,7 @@
     result = get(@, @_pathForIndex(idx)) ? @insertSparseArrayItem(idx)
     if (result and result.isExpiredAt(get(@, 'expired')) isnt true)
       return result
-    @requestObjectAt(idx, dontFetch)
+    @_requestObjectAt(idx, dontFetch)

   ###
     Fetches data regarding the total number of objects in the
@@ -289,35 +289,17 @@
     len

   ###
-    Fetches data at the specified index. If `rangeSize` is greater than 1, this
-    method will also retrieve adjacent items to form a "page" of results.
-
-    @method requestObjectAt
-    @param {Integer} idx The index to fetch content for
-    @param {Boolean} dontFetch Won't obtain remote data if `true`
-    @return {Object|Null} A placeholder object or null if content is empty
-  ###
-  requestObjectAt: (idx, dontFetch = !get(@, 'isStreaming')) ->
-    return (get(@, @_pathForIndex(idx)) ? @insertSparseArrayItem(idx)) if dontFetch
-
-    rangeSize = parseInt(get(@, 'rangeSize'), 10) || 1
-
-    start = Math.floor(idx / rangeSize) * rangeSize
-    start = Math.max start, 0
-    placeholders = start + rangeSize
-    placeholders = Math.min(placeholders, get(@, 'length')) if get(@, 'isLength')
-
-    @insertSparseArrayItems([start...placeholders])
-
-    if typeOf(@didRequestRange) is 'function'
-      @_didRequestRange({start: start, length: rangeSize})
-    else
-      @_didRequestIndex(i) for i in [start...rangeSize]
-
-    get(@, @_pathForIndex(idx))
-
-  ###
-    Empty the sparse data.
+    All items will appear to be stale when fetching with `.objectAt`.
+
+    @method expire
+    @chainable
+  ###
+  expire: ->
+    set(@, 'expired', Date.now())
+    @
+
+  ###
+    Empty the sparse data. (The "nuclear option.")

     @method reset
     @chainable
@@ -421,6 +403,37 @@
   ###
     @private

+    Fetches data at the specified index. If `rangeSize` is greater than 1, this
+    method will also retrieve adjacent items to form a "page" of results.
+
+    @method _requestObjectAt
+    @param {Integer} idx The index to fetch content for
+    @param {Boolean} dontFetch Won't obtain remote data if `true`
+    @return {Object|Null} A placeholder object or null if content is empty
+  ###
+  _requestObjectAt: (idx, dontFetch = !get(@, 'isStreaming')) ->
+    return (get(@, @_pathForIndex(idx)) ? @insertSparseArrayItem(idx)) if dontFetch
+
+    rangeSize = parseInt(get(@, 'rangeSize'), 10) || 1
+
+    start = Math.floor(idx / rangeSize) * rangeSize
+    start = Math.max start, 0
+    placeholders = start + rangeSize
+    placeholders = Math.min(placeholders, get(@, 'length')) if get(@, 'isLength')
+
+    @insertSparseArrayItems([start...placeholders])
+
+    if typeOf(@didRequestRange) is 'function'
+      @_didRequestRange({start: start, length: rangeSize})
+    else
+      @_didRequestIndex(i) for i in [start...rangeSize]
+
+    get(@, @_pathForIndex(idx))
+
+
+  ###
+    @private
+
     Prepare to fetch the total number of available objects from the
     persistence layer.


Remove debugger

Remove debugger

--- app/scripts/alchemy/core/layout.coffee
+++ app/scripts/alchemy/core/layout.coffee
@@ -103,7 +103,6 @@

     positionRootNodes: () ->
-        debugger
         container =
             width: alchemy.conf.graphWidth()
             height: alchemy.conf.graphHeight()


remove an annoying log message

remove an annoying log message

--- workers/social/lib/social/models/messages/post.coffee
+++ workers/social/lib/social/models/messages/post.coffee
@@ -99,7 +99,6 @@
     kodingErr

   @create = secure (client, data, callback)->
-    console.log 'sfsfsf'
     constructor = @
     {connection:{delegate}} = client
     unless delegate instanceof constructor.getAuthorType() # TODO: rethink/improve


recurly group: firstName / lastName should come from account.profile

--- workers/social/lib/social/models/recurly/group.coffee
+++ workers/social/lib/social/models/recurly/group.coffee
@@ -15,7 +15,7 @@

         {email, username} = ownerUser

-        {firstName, lastName} = owner
+        {firstName, lastName} = owner.profile

         groupCode = groupCodeOf group


Bounty!

Bounty!

<Muta_work> https://github.com/desert-planet/arrakis-hubot/blob/master/scripts/ayp.coffee#L163-L168 -- I will put $1 bounty on making this list longer, and more interesting
<Muta_work> [In BTC, obvs]

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -165,6 +165,9 @@
                     "This is awkward...",
                     "Turns out,",
                     "Despite my best efforts",
+                    "Bleep, Bloop, Bop:",
+                    "I have done the thing,",
+                    "Tada!"
                   ]
                   msg.reply "#{prefix} #{AYP_SITE}at/#{now}/ is now -> #{strip_url}"


RealTimeController: simplify initPubNub, group PubNub related calls

--- client/app/lib/realtimecontroller.coffee
+++ client/app/lib/realtimecontroller.coffee
@@ -4,19 +4,17 @@
 sendDataDogEvent = require './util/sendDataDogEvent'
 whoami = require './util/whoami'
 kd = require 'kd'
-KDController = kd.Controller
-KDTimeAgoView = kd.TimeAgoView
 backoff = require 'backoff'
 PubnubChannel = require './pubnubchannel'

 require 'pubnub'

-module.exports = class RealtimeController extends KDController
-
-  { noop } = kd
+module.exports = class RealtimeController extends kd.Controller

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

     # when we remove broker completely, we no longer need to
     # make another caching here
@@ -24,29 +22,24 @@

     # this is used for discarding events that are received multiple times
     @eventCache = {}
-
     @initLocalStorage()
-
-    super options, data
-

     { pubnub } = globals.config

     if pubnub.enabled and pubnub.subscribekey
       @initPubNub()
-      @syncTime()
-      @initAuthentication()
     else
       console.log 'I will be back.'

-  fetchServerTime: (callback = noop) ->
-    @pubnub.time (timestamp) =>
-      return callback Date.now() * 10000  unless timestamp
-
-      @serverTimestamp = timestamp
-      @emit 'Ping', timestamp
-      callback timestamp
+  initLocalStorage: ->
+
+    @localStorage = kd.singletons.localStorageController.storage 'realtime'
+
+    # each forbidden channel name is stored in local storage
+    # this is used for preventing datadog
+    unless @localStorage.getValue 'ForbiddenChannels'
+      @localStorage.setValue 'ForbiddenChannels', {}

   initPubNub: ->
@@ -67,43 +60,36 @@
     # https://www.pivotaltracker.com/story/show/88210800
     @pbNotification = PUBNUB.init options

-
-  syncTime: ->
-
-    @fetchServerTime (timestamp) =>
+    fetchServerTime = (callback = kd.noop) =>
+
+      @pubnub.time (timestamp) =>
+        return callback Date.now() * 10000  unless timestamp
+
+        @serverTimestamp = timestamp
+        @emit 'Ping', timestamp
+        callback timestamp
+
+    # syncServerTime
+    fetchServerTime (timestamp) =>
       @lastSeenOnline = timestamp

-    setInterval =>
-      @fetchServerTime()
-    , 10000
-
-
-  initLocalStorage: ->
-    @localStorage  = kd.getSingleton('localStorageController').storage 'realtime'
-
-    # TODO we can remove this later on
-    @localStorage.unsetKey 'isPubnubEnabled'
-
-    # each forbidden channel name is stored in local storage
-    # this is used for preventing datadog
-    unless @localStorage.getValue 'ForbiddenChannels'
-      @localStorage.setValue 'ForbiddenChannels', {}
-
-
-  initAuthentication: ->
+    kd.utils.repeat 10000, fetchServerTime
+
+
+    # initAuthentication for PubNub
     @authenticated = false

     realtimeToken = kookies.get('realtimeToken')

     if realtimeToken?
-      @setAuthToken realtimeToken
+      @setPubNubAuthToken realtimeToken
       @authenticated = yes

       return

     # in case of realtime token does not exist, fetch it from Gatekeeper
     options = { endPoint : '/api/gatekeeper/token', data: { id: whoami().socialApiId } }
-    @authenticate options, (err) =>
+    @authenticateWithPubNub options, (err) =>

       return kd.warn err  if err

@@ -111,14 +97,15 @@
       @emit 'authenticated'

-  setAuthToken: (token) ->
+  setPubNubAuthToken: (token) ->
+
     @pubnub.auth token
     @pbNotification.auth token

   # channel authentication is needed for notification channel and
   # private channels
-  authenticate: (options, callback) ->
+  authenticateWithPubNub: (options, callback) ->

     return callback null  unless options?

@@ -146,7 +133,7 @@

         return callback { message : 'Could not find realtime token' }  unless realtimeToken

-        @setAuthToken realtimeToken
+        @setPubNubAuthToken realtimeToken

         bo.reset()

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

     options.pbInstance = @pubnub

-    return @subscribePubnub options, callback
+    return @subscribePubNub options, callback

   # unsubscribeChannel unsubscribes the user from given channel
@@ -227,10 +214,10 @@

     options.pbInstance = @pbNotification

-    @subscribePubnub options, callback
-
-
-  subscribePubnub: (options = {}, callback) ->
+    @subscribePubNub options, callback
+
+
+  subscribePubNub: (options = {}, callback) ->

     return @subscribeHelper options, callback  if @authenticated

@@ -243,7 +230,7 @@
     # return channel if it already exists
     return callback null, @channels[pubnubChannelName]  if @channels[pubnubChannelName]

-    @authenticate options.authenticate, (err) =>
+    @authenticateWithPubNub options.authenticate, (err) =>

       return callback err  if err

@@ -255,16 +242,16 @@

       pb.subscribe
         channel : pubnubChannelName
-        message : (message, env, channel) => @handleMessage message, channel
+        message : (message, env, channel) => @handlePubNubMessage message, channel
         connect : =>
           @channels[pubnubChannelName] = channelInstance
           @removeFromForbiddenChannels pubnubChannelName
           callbackCalled = yes
           callback null, channelInstance
         error   : (err) =>
-          @handleError err
+          @handlePubNubError err
           callback err  unless callbackCalled
-        reconnect: (channel) => @reconnect channel, pb
+        reconnect: (channel) => @reconnectToPubNubChannel channel, pb
         # with each channel subscription pubnub resubscribes to every channel
         # and some messages are dropped in this resubscription time interval
         # for this reason for every subscribe request, we are fetching all messages sent
@@ -275,13 +262,14 @@
       return callback err  if err

-  isDisconnected: (err) ->
+  isPubNubDisconnected: (err) ->
     # I know this is so error prone, but they are not sending any error code; just message. :(
     return err?.message is 'Offline. Please check your network settings.'

-  reconnect: (channel, pbInstance) ->
-    KDTimeAgoView.emit 'OneMinutePassed'
+  reconnectToPubNubChannel: (channel, pbInstance) ->
+
+    kd.TimeAgoView.emit 'OneMinutePassed'

     @once 'Ping', (serverTimestamp) =>

@@ -291,10 +279,12 @@
       if @lastSeenOnline < serverTimestamp - 864000000000
         return window.location.reload()

-      @fetchHistory { channel, timestamp: @lastSeenOnline, pbInstance }
-
-
-  fetchHistory: (options) ->
+      @fetchChannelHistoryFromPubNub {
+        channel, timestamp: @lastSeenOnline, pbInstance
+      }
+
+
+  fetchChannelHistoryFromPubNub: (options) ->

     { channel, timestamp, pbInstance } = options

@@ -327,12 +317,12 @@

         return  unless messages?.length and Array.isArray(messages)

-        @handleMessage message, channel  for message in messages
+        @handlePubNubMessage message, channel  for message in messages

         # since the maximum message limit is 100, we are making a recursive call here
         if messages.length is limit
           historyOptions.start = end
-          @fetchHistory { channel, timestamp: end }
+          @fetchChannelHistoryFromPubNub { channel, timestamp: end }
       err: (err) -> kd.warn "Could not fetch history #{err.message}"  unless err

     bo.on 'backoff', -> pb.history historyOptions
@@ -340,7 +330,7 @@
     bo.backoff()

-  handleMessage: (message, channel) ->
+  handlePubNubMessage: (message, channel) ->

     return  unless message

@@ -382,13 +372,13 @@
       return @unsubscribeChannel message.channel

-  handleError: (err) ->
+  handlePubNubError: (err) ->

     return  unless err

     { message, payload } = err

-    if @isDisconnected err
+    if @isPubNubDisconnected err
       @lastSeenOnline = @serverTimestamp

     return kd.warn err  unless payload?.channels
@@ -410,6 +400,7 @@

   removeFromForbiddenChannels: (channelName) ->
+
     forbiddenChannels = @localStorage.getValue 'ForbiddenChannels'

     return  unless forbiddenChannels[channelName]


Tweaks to terms

Tweaks to terms

--- client/app/views/pages/terms.tmpl.coffee
+++ client/app/views/pages/terms.tmpl.coffee
@@ -4,12 +4,14 @@
 terms = """
 By using Sagefy, you agree to these terms.

-This statement is effective as of February 1, 2015.
+If you do not agree to these terms, you must stop using Sagefy.
+
+This document is effective as of October 18, 2015.

 Sagefy may change this document at any time without notice.

-SAGEFY IS PROVIDED AS-IS AND AS-AVAILABLE.
-SAGEFY MAKES NO WARRANTIES.
+SAGEFY IS PROVIDED AS-IS AND AS-AVAILABLE. SAGEFY MAKES NO WARRANTIES.
+
 SAGEFY DOES NOT OFFER WARRANTIES ON MERCHANTABILITY,
 FITNESS FOR A PARTICULAR USE,
 OR NON-INFRINGEMENT OF INTELLECTUAL PROPERTY.
@@ -23,17 +25,18 @@
 Sagefy may collect personally identifying information to provide services.

 Sagefy may send personally identifying information to trusted
-affiliates, such as Google Analytics and UserVoice.
+partners, such as Google Analytics and UserVoice.

-Sagefy may disclose information to law enforcement without notice
+Sagefy may share information to law enforcement without notice only
     a) if required by law,
     b) to defend Sagefy's rights and property, or
     c) to ensure personal safety or public safety.

-You cannot spam to other users of Sagefy.
+You cannot spam, defame, harrass, abuse, threaten, or defraud
+other users of Sagefy.
+
 You cannot impersonate any person or entity on Sagefy.
-You cannot defame, harrass, abuse, threaten, or defraud
-other users of Sagefy.
+
 You cannot use Sagefy to collect information
 about other users without their consent.

@@ -44,21 +47,27 @@
 you must ask a parent or guardian before using Sagefy.

 You cannot interfere with security features of Sagefy.
+
 You cannot interfere with any other user's use of Sagefy.
-You cannot use any sort of automated means to access Sagefy.
+
+You cannot use any sort of automated means, such as bots or scrapers,
+to access Sagefy.
+
 You cannot bypass measures to restrict access to Sagefy.

-You are solely responsible for maintaining
-the confidentiality of your account and any passwords or tokens.
+You are completely responsible for protecting your account,
+passwords, and tokens.
+
 Sagefy is not liable for any damages resulting from
 unauthorized use of your account.

-Sagefy may close accounts and cancel service
-in Sagefy's sole discretion.
+Sagefy may close accounts and cancel service in Sagefy's own judgement.
+
+By providing content to Sagefy, you agree Sagefy may use this content.

 By providing content to Sagefy, you agree you own the rights
 to the content and the legal ability to provide the content.
-By providing content to Sagefy, Sagefy may use this content.
+
 No compensation will be given for user-provided content.

 Sagefy may update and remove user-submitted content,
@@ -66,6 +75,10 @@

 Sagefy is not responsible for content or agreements on
 external websites, even if Sagefy links to them.
+
+If any part of this document is invalid,
+that part is seperable from the rest of the document and
+the rest of this document remains valid.

 If your copyright, patent, or trademark has been violated, contact
 <support@sagefy.org>.


WHITESPACE

WHITESPACE

--- client/assets/scripts/application.coffee
+++ client/assets/scripts/application.coffee
@@ -179,7 +179,7 @@
 			url: url,
 			xhrFields: { withCredentials: true },
 			data: params
-			.done callback
+		.done callback

 	nowPlaying: ->
 		method = 'nowPlaying'


chat: forward added participant event to channel

--- client/ide/views/chat/messagepane.coffee
+++ client/ide/views/chat/messagepane.coffee
@@ -7,6 +7,13 @@
     super options, data

     @on 'AddedParticipant', @bound 'participantAdded'
+
+    # forward this event to channel, so that
+    # it can change in other views as well.
+    # Kind of observable. ~Umut
+    @on 'AddedParticipant', (participant) =>
+      channel = @getData()
+      channel.emit 'AddedToChannel', participant

   createParticipantsView: ->


Profile: removing splitview frmo profile feeder

--- client/Social/Members/AppController.coffee
+++ client/Social/Members/AppController.coffee
@@ -123,6 +123,7 @@
       listControllerClass   : MembersListViewController
       limitPerPage          : 10
       noItemFoundText       : "There is no member."
+      useHeaderNav          : yes
       # singleDataSource      : (selector, options, callback)=>
         # filterFunc selector, options, callback
       help                  :


Client: app: kodingappscontroller: replace dash with async.parallel

--- client/app/lib/kodingappscontroller.coffee
+++ client/app/lib/kodingappscontroller.coffee
@@ -5,10 +5,10 @@

 globals          = require 'globals'
 Promise          = require 'bluebird'
+async            = require 'async'

 FSHelper         = require './util/fs/fshelper'
 registerAppClass = require './util/registerAppClass'
-

 module.exports = class KodingAppsController extends KDController

@@ -41,14 +41,13 @@

   @loadDependencies = (dependencies, callback) ->
-    sinkrow = require 'sinkrow'
-    queue   = []
+    queue = []

     for dependency in dependencies
-      fn = @loadInternalApp.bind this, dependency, -> queue.fin()
-      queue.push fn
+      queue.push (fin) =>
+        @loadInternalApp dependency, -> fin()

-    sinkrow.dash queue, callback
+    async.parallel queue, callback

   ## This is the most important method to put & run additional apps on Koding
   ## Please make sure about your changes on it.


Set parent and splitView options to created split view panels.

--- client/ide/views/tabview/idetabview.coffee
+++ client/ide/views/tabview/idetabview.coffee
@@ -81,4 +81,9 @@
       type     : type
       views    : [ this, newTabView ]

+    @setOption           "splitView", splitView
+    newTabView.setOption "splitView", splitView
+    @setOption           "container", parent
+    newTabView.setOption "container", parent
+
     parent.addSubView splitView


Toggle info on the side of the sticky filter header based on screen width.

--- components/filter/fixed_header/view.coffee
+++ components/filter/fixed_header/view.coffee
@@ -10,6 +10,7 @@
     @$window = $ window
     @mainHeaderHeight = $('#main-layout-header').height()
     @$window.on 'scroll', @popLock
+    @$window.on 'resize', _.throttle(@squeeze, 100)
     @params.on 'change', @scrollToTop
     @wrap()
     @setupJump()
@@ -30,6 +31,14 @@

   popLock: =>
     if @$window.scrollTop() > @$el.offset().top
-      @$el.addClass('filter-fixed-header')
+      @$el.addClass('filter-fixed-header'); @squeeze()
     else
-      @$el.removeClass('filter-fixed-header')+      @$el.removeClass('filter-fixed-header')
+
+  squeeze: =>
+    $squeezee = @$('.filter-fixed-header-left')
+    $squeezer = @$('.filter-fixed-header-nav').children().not($squeezee)
+    left = $squeezee.offset().left + $squeezee.width()
+    right = _.min( _.map($squeezer, (e) -> $(e).offset().left) )
+    $squeezee.css(visibility: "hidden") if left > right
+    $squeezee.css(visibility: "visible") if right > left


Whoops, remove debug lines.

Whoops, remove debug lines.

--- tests/character.coffee
+++ tests/character.coffee
@@ -68,20 +68,12 @@
 	insert 'locations', id: 2, initial: 1
 	insert 'uniusers', id: 1

-	console.log '1111'
-
 	try
-		console.log '+1111'
 		charid = character.createCharacter(conn, 1, 'My First Character', 'elf', 'female')
-		console.log '+2222'
 		char = query.row "SELECT * FROM characters"
-		console.log '+3333'
 		user = query.row "SELECT * FROM uniusers"
-		console.log '+4444'
 	catch ex
 		console.log ex.stack
-
-	console.log '2222'

 	test.strictEqual user.character_id, charid, "should switch user's character to new character"
 	test.strictEqual charid, char.id, 'should return new character id'
@@ -89,8 +81,6 @@
 	test.strictEqual char.location, 2, 'should create character in initial location'
 	test.strictEqual char.race, 'elf', 'should create character with specified race'
 	test.strictEqual char.gender, 'female', 'should create character with specified gender'
-
-	console.log '3333'

 	ex = null
 	try
@@ -100,8 +90,6 @@
 	test.notStrictEqual ex, null, 'should throw exception if such name has been taken'
 	test.strictEqual ex.message, 'character already exists',
 		'should throw CORRECT exception if such name has been taken'
-
-	console.log '4444'

 	energies = [
 		['orc', 'male', 220 ]
@@ -117,23 +105,17 @@
 		test.strictEqual char.energy_max, x[2], "should set correct energy_max value for #{x[1]} #{x[0]}"
 		test.strictEqual char.energy, x[2], "should set correct energy value for #{x[1]} #{x[0]}"

-	console.log '5555'
-
 	test.throws(
 		-> character.createCharacter(conn, 1, 'My First Character', 'murloc', 'female')
 		Error
 		'should not allow weird races'
 	)

-	console.log '6666'
-
 	test.throws(
 		-> character.createCharacter(conn, 1, 'My First Character', 'orc', 'it')
 		Error
 		'should not allow weird genders'
 	)
-
-	console.log '7777'

 	query "UPDATE locations SET initial = 1"
 	test.throws(
@@ -141,8 +123,6 @@
 		Error
 		'should throw if something bad happened'
 	)
-
-	console.log '8888'

 	test.done()


Bugfix, supporting tab wasn't properly showing up because of this.

--- core/app/backbone/models/discussion.coffee
+++ core/app/backbone/models/discussion.coffee
@@ -18,7 +18,7 @@
       else `undefined`

   _getEvidence: ->
-    if @type() == 'supporting' or @type == 'weakening'
+    if @type() == 'supporting' or @type() == 'weakening'
       new EvidenceCollection [], type: @type(), fact: @fact()

   getInteractors: ->


Improved client ticket editable check.

Improved client ticket editable check.

--- app/assets/javascripts/app/models/ticket.coffee
+++ app/assets/javascripts/app/models/ticket.coffee
@@ -249,9 +249,14 @@
     user_id = App.Session.get('id')
     return true if user_id is @customer_id
     group_ids = App.Session.get('group_ids')
-    if _.isEmpty(group_ids[@group_id])
-      return false
-    else if group_ids[@group_id] && !_.include(group_ids[@group_id], 'change') && !_.include(group_ids[@group_id], 'full')
-      return false
-    true
-
+    if group_ids
+      return true if group_ids[@group_id] && (_.include(group_ids[@group_id], 'change') || _.include(group_ids[@group_id], 'full'))
+    role_ids = App.Session.get('role_ids')
+    if role_ids
+      for role_id in role_ids
+        if App.Role.exists(role_id)
+          role = App.Role.find(role_id)
+          if role.group_ids
+            return true if role.group_ids[@group_id] && (_.include(role.group_ids[@group_id], 'change') || _.include(role.group_ids[@group_id], 'full'))
+    false
+


fix clicking selected values

fix clicking selected values

--- app/assets/javascripts/initialization.js.coffee
+++ app/assets/javascripts/initialization.js.coffee
@@ -41,6 +41,7 @@
     $(select).on("select2-opening", -> (
       $(".scheme-finder-frontend").addClass("select2-open")
     )).on("select2-open", -> (
+      $(".select2-selected").addClass("select2-result-unselectable").removeClass("select2-result-selectable")
       $(".select2-drop").each( -> (
         if $(this).css("display") == "block"
           $(".select2-drop").removeClass("height-checked")
@@ -66,7 +67,7 @@
     xStart = e.touches[0].screenX
     yStart = e.touches[0].screenY
   ))
-
+
   document.addEventListener('touchmove', (e) -> (
     if $(".scheme-finder-frontend").hasClass("select2-open")
       xMovement = Math.abs(e.touches[0].screenX - xStart)


OX-729 fixed nasty bug (p.transformTransform() doesn't have side effects on p)

--- app/assets/javascripts/libs/pan_zoom_svg.coffee
+++ app/assets/javascripts/libs/pan_zoom_svg.coffee
@@ -46,7 +46,7 @@
     event.preventDefault()
     return if @mouseDown

-    buffer = @buffer
+    buffer = @buffer
     { wheelDelta, wheelDeltaX, wheelDeltaY, pageX, pageY } = event.originalEvent

     buffer += wheelDeltaY
@@ -123,7 +123,7 @@
     p.x = mouse.x
     p.y = mouse.y

-    p.matrixTransform(@svgElement.getCTM().inverse())
+    p = p.matrixTransform(@svgElement.getCTM().inverse())

     transformationMatrix = @svgRoot.createSVGMatrix()
       .translate(p.x, p.y)


OX-591 check if annotation was already loaded to avoid doubled loaded

--- app/assets/javascripts/main/enhancements.coffee
+++ app/assets/javascripts/main/enhancements.coffee
@@ -201,7 +201,10 @@
     $table.find(".details-toggle").click (event) ->
       $toggle = $(this)

-      dataAjaxHandler(event, $toggle)
+      alreadyFetched = $toggle.data("alreadyFetched")
+      unless alreadyFetched
+        dataAjaxHandler(event, $toggle)
+        $toggle.data("alreadyFetched", true)

       newState = !$toggle.hasClass("open")


Added params to round

Added params to round

--- app/assets/pg/level/round/round.state.js.coffee
+++ app/assets/pg/level/round/round.state.js.coffee
@@ -6,3 +6,6 @@
         controller: 'MainSeasonPgLevelRoundCtrl'
         controllerAs: 'round'
         templateUrl: 'level/round/round.html'
+        params:
+          level: null
+          round: null


Fix numbers immediately after LaTeX command, e.g. \S5


Start paragraphs after LaTeX headings

Start paragraphs after LaTeX headings


Added URL parameter to geo_put: *json*, if specified all other parameters will be ignored, data should be in GeoJSON format

--- lib/geobork.coffee
+++ lib/geobork.coffee
@@ -38,10 +38,13 @@

 app.get '/geo_put', (req, res, next) ->
   parts = url.parse req.url, true
-  meta = {}
-  meta[k] = cohers(v) for k, v of parts.query when not (k in ['lat','lng'])
-  {lat, lng} = parts.query
-  doc = {loc:[parseFloat(lng), parseFloat(lat)], meta}
+  if parts.query.json?
+    doc = map.geoJsonToDoc JSON.parse parts.query.json
+  else
+    meta = {}
+    meta[k] = cohers(v) for k, v of parts.query when not (k in ['lat','lng'])
+    {lat, lng} = parts.query
+    doc = { loc: [ parseFloat(lng), parseFloat(lat) ], meta }
   controller.createGeo doc, (err, doc) ->
     return next(err) if err?
     io.sockets.emit 'new geo', map.docToGeoJson(doc)


Dual barchart values are now animated.

Dual barchart values are now animated.

--- app/scripts/directives/compare-bar-chart.coffee
+++ app/scripts/directives/compare-bar-chart.coffee
@@ -39,11 +39,15 @@
             .enter().append("rect")
             .attr('class', (d, i) ->
               return 'bar' + (i+1)
-            ).attr("x", (d, i) ->
-              return x(Math.min(0, d))
-            ).attr("y", 0)
-            .attr("width", x)
+            ).attr("x", x(0))
+            .attr("y", 0)
+            .attr("width", 0)
             .attr("height", 30)
+            .transition()
+              .duration(1000)
+              .attr("width", x)
+              .attr("x", (d, i) -> return x(Math.min(0, d)))
+

       scope.drawBarChart()


Added eval cache

Added eval cache

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -4,6 +4,7 @@
 evaluate = require 'static-eval'

 expressionCache = {}
+evalFnCache: {}

 module.exports =
   warnVerbose: (args...) ->
@@ -14,7 +15,7 @@

   safeEvalWithContext: (expression, context, clone, thisArg = context, returnNewContext) ->
     context = _.cloneDeep context if clone
-    fn = new Function 'context', "with (context) { return #{expression} }"
+    fn = evalFnCache[expression] or= new Function 'context', "with (context) { return #{expression} }"
     try
       output = fn.call thisArg, context
     catch error


:fire: Remove some unused imports

:fire: Remove some unused imports

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -1,6 +1,3 @@
-{Range} = require('atom')
-path = require 'path'
-child_process = require('child_process')
 minimatch = require('minimatch')

 Helpers = module.exports =


:non-potable_water: Clear config subscriptions when unobserving

--- src/config-observer.coffee
+++ src/config-observer.coffee
@@ -4,5 +4,6 @@
     @configSubscriptions[keyPath] = config.observe(keyPath, args...)

   unobserveConfig: ->
-    for keyPath, subscription of @configSubscriptions ? {}
-      subscription.cancel()
+    if @configSubscriptions?
+      subscription.cancel() for keyPath, subscription of @configSubscriptions
+      @configSubscriptions = null


Fix Chrome preformat bug

Fix Chrome preformat bug

Closes #423

--- src/core/normalizer.coffee
+++ src/core/normalizer.coffee
@@ -56,6 +56,10 @@
         node.style[style] = value
         node.removeAttribute(attribute)
     )
+    # Chrome turns <b> into style in some cases
+    if (node.style.fontWeight == 'bold')
+      node.style.fontWeight = ''
+      dom(node).wrap(document.createElement('b'))
     this.whitelistStyles(node)
     return this.whitelistTags(node)


Dry things up a little

Dry things up a little

--- lib/motions.coffee
+++ lib/motions.coffee
@@ -263,25 +263,26 @@

   execute: (count=1) ->
     _.times count, =>
-      pos = @findNext(@view.value)
-      if pos?
+      @findOrBeep (pos) =>
         @editor.setCursorBufferPosition(pos)
-      else
-        shell.beep()
-
-  select: (count=1) ->
-    _.times count, =>
-      pos = @findNext(@view.value)
-      if pos?
+
+  select: (count=1) ->
+    _.times count, =>
+      @findOrBeep (pos) =>
         cur = @editor.getCursorBufferPosition()
         @editor.setSelectedBufferRange([cur, pos])
         true
-      else
-        shell.beep()

   confirm: (view) =>
     window.motion = @
     @editor.trigger 'vim-mode:search-complete'
+
+  findOrBeep: (callback)->
+      pos = @findNext(@view.value)
+      if pos?
+        callback(pos)
+      else
+        shell.beep()

   findNext: (term) ->
     regexp = new RegExp(term, 'g')


Fixed ruby grammar detection

Fixed ruby grammar detection

--- lib/rubocop.coffee
+++ lib/rubocop.coffee
@@ -23,7 +23,7 @@
   serialize: ->

   isRubyFile: (filePath) ->
-    atom.workspace.getActiveTextEditor().getGrammar().scopeName == 'source.ruby'
+    atom.workspace.getActiveTextEditor().getGrammar().scopeName.includes('ruby')

   autocorrectCurrentFile: ->
     editor = atom.workspace.getActivePaneItem()


Batch updates.

Batch updates.

--- src/coffee/customerxmlimport.coffee
+++ src/coffee/customerxmlimport.coffee
@@ -70,12 +70,18 @@
       if _.size(posts) is 0
         @returnResult true, 'Nothing done.', callback

-      Q.all(posts).fail (msg) =>
-        msg = _.flatten msg if _.isArray msg
-        @returnResult false, msg, callback
-      .then (msg) =>
-        msg = _.flatten msg if _.isArray msg
-        @returnResult true, msg, callback
+      @processInBatches posts, callback
+
+  processInBatches: (posts, callback, numberOfParallelRequest = 50, acc = []) =>
+    current = _.take posts, numberOfParallelRequest
+    Q.all(current).then (msg) =>
+      messages = acc.concat(msg)
+      if _.size(current) < numberOfParallelRequest
+        @returnResult true, messages, callback
+      else
+        @processInBatches _.tail(posts, numberOfParallelRequest), callback, numberOfParallelRequest, messages
+    .fail (msg) =>
+      @returnResult false, msg, callback

   create: (newCustomer, paymentInfo, callback) ->
     deferred = Q.defer()


Fix #37

Fix #37

--- view/coffee/game/game.coffee
+++ view/coffee/game/game.coffee
@@ -38,6 +38,10 @@
           .value()
         line = @lines[obj.id]
         _.extend(true, line, {trains: ordered, name: "#{line.name} #{obj.dist}"})
+      @fromLines = _.chain(@fromLines)
+        .orderBy (x) -> x.name
+        .orderBy (x) -> x.id
+        .value()
     openModal: (train) ->
       @trainModal.setData(train, @game)
       $(trainModalId).modal('show')


Slightly better map icon

Slightly better map icon

--- web2/js/views/main-menu.cjsx
+++ web2/js/views/main-menu.cjsx
@@ -13,7 +13,7 @@
                 POI Generator

-
+
                 Geocache Map


Fix typo

Fix typo

--- flow-router-title.coffee
+++ flow-router-title.coffee
@@ -59,4 +59,4 @@
         _self.titleHandler _context
       _orig.apply @, arguments

-`export { FlowRouter }`+`export { FlowRouterTitle }`


init: add vagrant connection

init: add vagrant connection


Remove function to open collections when clicking on the database label in the tree

--- public/coffee/views/databaseItemView.coffee
+++ public/coffee/views/databaseItemView.coffee
@@ -28,5 +28,3 @@
   gotoDatabase: (event) =>
     console.log 'gotoDatabase'

-
-


Better cleanup for the connection and remote list in dnode_server when clients disconnect.

--- src/browser/dnode_server.coffee
+++ src/browser/dnode_server.coffee
@@ -15,6 +15,8 @@
             conn.on 'end', () ->
                 if browser?
                     browser.removeClient(remote)
+                conns = (c for c in conns when c != conn)
+                remotes = (r for r in remotes when r != conn)

             conn.on 'ready', () ->
                 console.log("Client is ready")


Change undo and redo into actions

Change undo and redo into actions

This fixes the bug where closing the delete-palette-item dialog without
deleting an item undoes the previous action.

It also fixes the bug where adding a new palette item while replacing
an item, and then closing the dialog, doesn't correctly remove the
new item.

--- src/code/utils/undo-redo.coffee
+++ src/code/utils/undo-redo.coffee
@@ -18,6 +18,8 @@

     # listen to all our actions
     @endCommandBatch.listen @_endComandBatch, @
+    @undo.listen @_undo, @
+    @redo.listen @_redo, @

   startCommandBatch: ->
     @currentBatch = new CommandBatch() unless @currentBatch
@@ -52,7 +54,9 @@
     @log() if @debug
     result

-  undo: ->
+  undo: Reflux.createAction()
+
+  _undo: ->
     if @canUndo()
       result = @commands[@stackPosition].undo @debug
       @stackPosition--
@@ -65,7 +69,9 @@
   canUndo: ->
     return @stackPosition >= 0

-  redo: ->
+  redo: Reflux.createAction()
+
+  _redo: ->
     if @canRedo()
       @stackPosition++
       result = @commands[@stackPosition].redo @debug


Adapt to fpb-graph

Adapt to fpb-graph

--- spec/basics.coffee
+++ spec/basics.coffee
@@ -2,9 +2,9 @@
 chai = window.chai or require 'chai'

 parseFBP = (fbpString, callback) ->
-  noflo = window.noflo or require 'noflo'
-  noflo.graph.loadFBP fbpString, (err, n) ->
-    if err instanceof noflo.Graph
+  fbpGraph = window.fbpGraph or require 'fbp-graph'
+  fbpGraph.graph.loadFBP fbpString, (err, n) ->
+    if err instanceof fbpGraph.Graph
       # legacy NoFlo, no error argument
       [err, n] = [null, err]
     return callback err if err


Deeply improve the timer related API.

Deeply improve the timer related API.

--- src/6.timer.coffee
+++ src/6.timer.coffee
@@ -23,10 +23,11 @@
     @getPrecision: ->
         Date.Timer._precision
     @_onCheck: eventField()
-    constructor: (@targetTime = Date.Timer._endOfTime) ->
+    constructor: (options) ->
+        @targetTime = options?.targetTime ? Date.Timer._endOfTime
+        @allowsEqual = options?.allowsEqual ? true
         @_counter = 0
         @_running = false
-        @allowsEqual = true
         @onArrive = eventField()
     run: ->
         if @_running then return
@@ -53,25 +54,34 @@
     resetCounter: -> @_counter = 0
     getCounter: -> @_counter
 class Date.IntervalTimer extends Date.Timer
-    constructor: (@interval = 1000, @startTime = new Date(), @endTime) ->
-        super()
+    constructor: (options) ->
+        super(options)
+        @interval = options?.interval ? 1000
+        @startTime = options?.startTime ? new Date()
+        @endTime = options?.endTime ? Date.Timer._endOfTime.subtract(1000)
+        @includesStart = options?.includesStart ? true
+        @includesEnd = options?.includesEnd ? false
+        @skipsPast = options?.skipsPast ? false
         @targetTime = @startTime
         @_started = false
-        @includesStart = true
-        @includesEnd = false
         @onStart = eventField()
         @onArrive.bind((event) =>
             if @interval < Date.Timer.getPrecision() * 2
                 @stop()
                 return
-            @targetTime = event.idealTime.add(@interval)
+            now = new Date()
+            @targetTime =
+                if @skipsPast
+                    now - (now - event.idealTime) % @interval + @interval
+                else
+                    event.idealTime.add(@interval)
             if not @_started
                 @_started = true
                 if not @includesStart
                     @resetCounter()
                     event.blocksListeners = true
                 @onStart.fire()
-            if @endTime? and (
+            if (
                 if @includesEnd
                     @targetTime > @endTime
                 else
@@ -81,8 +91,15 @@
         )
 class Date.Observer extends Date.IntervalTimer
     @_error: new Error()
-    constructor: (fun) ->
-        super(100)
+    constructor: ->
+        [options, fun] =
+            if typeof arguments[0] == "object"
+                [arguments[0], arguments[1]]
+            else
+                [arguments[1], arguments[0]]
+        clonedOptions = if options? then Object.clone(options) else {}
+        clonedOptions.interval ?= 100
+        super(options)
         @_fun = fun
         @onChange = eventField()
         @onUpdate = eventField()


Text Edit #3

Text Edit #3

Beschreibung der technischen Features

--- src/components/LandingExplain.coffee
+++ src/components/LandingExplain.coffee
@@ -46,12 +46,12 @@

       h2 null, "Wie funktioniert die Karte von morgen?"
       div null,
-        p null, "Suchbegriff in das Suchfeld eingeben"
-        p null, "bei Bedarf nach Kategorien filtern (Initiativen, " +
-            "Events, Unternehmen)"
-        p null, "Suchergebnisse ansehen und auswählen"
-        p null, "Visitenkarten ansehen"
-        p null, "Orte eintragen und editieren"
+        p null, "1. Erkunde mit der Karte von morgen deine Umgebung. Nutze hierfür das Suchfeld - wo befindest du dich?"
+        p null, "2. Über die Kategorien "Initiativen und Unternehmen" grenzt du die Suchergebnisse ein."
+        p null, "3. Nähere Informationen über jeden Eintrag befinden sich in der Visitenkarte, die sich bei der Auswahl aufklappt."
+        p null, "4. Du hast einen Ort, der auf der Karte nicht fehlen darf? Trage ihn gern über die Eingabemaske ein."
+        p null, "5. Möchtest du einen bestehenden Eintrag korrigieren oder ergänzen? Hierfür dient die Editierfunktion."
+        p null, "online suchen - offline handeln"
         br null
           div style: overflow: "visible",
             img style: {width: "100%"}, src: require "../img/postkarte.jpg"


fixed typo

fixed typo

--- src/akismet.coffee
+++ src/akismet.coffee
@@ -68,7 +68,7 @@
     callback or= () ->
     options.blog = @blog
     options.user_agent = @userAgent
-    @postRequest @endpoint, '/1.1/submit-ham', options, (err, status, headers, body) ->
+    @postRequest @endPoint, '/1.1/submit-ham', options, (err, status, headers, body) ->
       callback err


[Bugfix] Changed port binding for heroku settings

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -60,7 +60,8 @@
   process.exit()

 # Run it
-app.listen settings.app.port, ->
-  console.log('App started on port ' + settings.app.port)
+port = process.env.PORT || settings.app.port
+app.listen port, ->
+  console.log('App started on port ' + port)


Fix for #559

Fix for #559

--- lib/mixins/images.coffee
+++ lib/mixins/images.coffee
@@ -13,7 +13,7 @@
     x = x ? options.x ? @x
     y = y ? options.y ? @y

-    unless Buffer.isBuffer(src)
+    if typeof src is 'string'
       image = @_imageRegistry[src]

     if not image
@@ -88,12 +88,12 @@
     return this

   openImage: (src) ->
-    unless Buffer.isBuffer(src)
+    if typeof src is 'string'
       image = @_imageRegistry[src]

     if not image
       image = PDFImage.open src, 'I' + (++@_imageCount)
-      unless Buffer.isBuffer(src)
+      if typeof src is 'string'
         @_imageRegistry[src] = image

     return image


PDF does not like numbers in exponential format (from https://github.com/xk/pdfkit/commit/dfc37fe48a58d8a21a40dcd40286d4bcb86867da - thanks Jorge)

--- lib/mixins/vector.coffee
+++ lib/mixins/vector.coffee
@@ -148,7 +148,14 @@
     matrix: [1, 0, 0, 1, 0, 0]

     transform: (m11, m12, m21, m22, dx, dy) ->
-        values = [m11, m12, m21, m22, dx, dy].join ' '
+        values = [m11, m12, m21, m22, dx, dy].map (v) ->
+          str = v.toFixed 20
+          v = +str
+          if (v >= 1 || v <= -1 || v == 0)
+            return v.toString()
+          else
+            return str
+        values = values.join ' '
         m = @matrix
         #
         # (m[0] m[1] m[4])   (m11  m21 dx)


Implement "<wrap>key</wrap>: value" for headers

--- src/headers-result-converter.coffee
+++ src/headers-result-converter.coffee
@@ -49,8 +49,21 @@

     html = ''

+    canOmitSanitize = @jsonKeyEndTag and @jsonKeyStartTag
+
     for line in lines
-      html += @formatFragment {fragment: "#{line['pathArray']}: #{line['value']}", message: line['message'], status: line['state']}
+      lineKey = line['pathArray']
+      lineVal = line['value']
+      if canOmitSanitize
+        lineKey = @sanitize lineKey
+        lineVal = @sanitize lineVal
+
+      html += @formatFragment {
+        omitSanitize: canOmitSanitize
+        fragment: "#{@jsonKeyStartTag or ''}#{lineKey}#{@jsonKeyEndTag or ''}: #{lineVal}"
+        message: line['message']
+        status: line['state']
+      }

     return html


Quoted a nipple in parse_url

Quoted a nipple in parse_url

--- lib/frontend/http_middleware/parse_url.coffee
+++ lib/frontend/http_middleware/parse_url.coffee
@@ -10,7 +10,7 @@
     raw = request.url
     [no_params, params] = raw.split('?')
     [url..., extension] = no_params.split('.')
-    url = url.join(.)
+    url = url.join('.')
     [ignore, initialDir, actions...] = url.split('/')
     request.ss.parsedURL =
       extension:   (if extension and extension != '/' then extension.toLowerCase() else null)


Disable reply button on archived/closed topics

--- app/assets/javascripts/discourse/views/topic_footer_buttons_view.js.coffee
+++ app/assets/javascripts/discourse/views/topic_footer_buttons_view.js.coffee
@@ -38,6 +38,7 @@

       @addObject Discourse.ButtonView.createWithMixins
         classNames: ['btn', 'btn-primary', 'create']
+        attributeBindings: ['disabled']
         text: (->
           archetype = @get('controller.content.archetype')
           return customTitle if customTitle = @get("parentView.replyButtonText#{archetype.capitalize()}")
@@ -46,6 +47,7 @@
         renderIcon: (buffer) -> buffer.push("<i class='icon icon-plus'></i>")
         click: -> @get('controller').reply()
         helpKey: 'topic.reply.help'
+        disabled: !@get('controller.content.can_create_post')

       unless topic.get('isPrivateMessage')
         @addObject Discourse.DropdownButtonView.createWithMixins


[improvement] Allowed panning the roadmap back to see old milestones (8m)

--- app/assets/javascripts/houston/roadmap/views/thumbnail_roadmap_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/thumbnail_roadmap_view.coffee
@@ -4,8 +4,8 @@
     @milestones = options.milestones
     @parent = options.parent
     @viewport = options.viewport
-    @startDate = 3.weeks().ago()
-    @endDate = 2.years().after(@startDate)
+    @startDate = d3.min @milestones.pluck('startDate').concat(3.weeks().ago())
+    @endDate = d3.max @milestones.pluck('endDate').concat(2.years().after(@startDate))
     @viewerStart = @startDate
     @milestones.bind 'add', @update, @
     @milestones.bind 'change', @update, @


Hour!

Hour!

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -11,7 +11,7 @@
 @defaultNotificationDelays =
   after:
     after: 1
-    unit: 'minute'
+    unit: 'hour'
   batched:
     every: 4
     unit: 'hour'  ## 'hour' or 'day'


new utility on base, Base::getTextForRow

--- lib/provider/base.coffee
+++ lib/provider/base.coffee
@@ -3,26 +3,35 @@
 {
   decorateRange
   saveEditorState
+  padStringLeft
 } = require '../utils'

 module.exports =
 class Base
   wasConfirmed: false
+  lastRowNumberTextLength: null
+
   getName: ->
     @constructor.name

   getTitle: ->
-    @constructor.name
+    _.dasherize(@getName())

   constructor: (@ui, @options={}) ->
     @subscriptions = new CompositeDisposable
     @editor = atom.workspace.getActiveTextEditor()
+
+    @subscribe @editor.onDidStopChanging(@invalidateState)
+
     @editorElement = atom.views.getView(@editor)
     @pane = atom.workspace.getActivePane()
     @restoreEditorState = saveEditorState(@editor)

     @initialize?()
     @ui.start(this)
+
+  invalidateState: =>
+    @lastRowNumberTextLength = null

   subscribe: (args...) ->
     @subscriptions.add(args...)
@@ -71,3 +80,7 @@

     @editor.scrollToBufferPosition(point, center: true)
     @editorElement.component.updateSync()
+
+  getTextForRow: (row) ->
+    @lastRowNumberTextLength ?= String(@editor.getLastBufferRow()).length
+    padStringLeft(String(row + 1), @lastRowNumberTextLength)


Fix navigation jasmine tests

Fix navigation jasmine tests

--- lms/static/coffee/spec/navigation_spec.coffee
+++ lms/static/coffee/spec/navigation_spec.coffee
@@ -16,6 +16,7 @@
             active: 1
             header: 'h3'
             autoHeight: false
+            heightStyle: 'content'

       describe 'when there is no active section', ->
         beforeEach ->
@@ -23,11 +24,12 @@
           $('#accordion').append('

')
           new Navigation

-        it 'activate the accordian with section 1 as active', ->
+        it 'activate the accordian with no section as active', ->
           expect($('#accordion').accordion).toHaveBeenCalledWith
-            active: 1
+            active: 0
             header: 'h3'
             autoHeight: false
+            heightStyle: 'content'

       it 'binds the accordionchange event', ->
         Navigation.bind()


don't uglify in production, not until we ngmin

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -89,7 +89,7 @@
         .pipe(source('index.js'))
         .pipe(buffer())
         .pipe(sourcemaps.init(loadMaps: true))
-        .pipe(if nodeEnv == 'production' then require('gulp-uglify')() else util.noop())
+        #.pipe(if nodeEnv == 'production' then require('gulp-uglify')() else util.noop())
         .pipe(sourcemaps.write('../maps'))
         .pipe(gulp.dest('build/scripts'))
         .pipe(if lr then require('gulp-livereload')(lr) else util.noop())


Updated Gulpfile

Updated Gulpfile

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -3,8 +3,14 @@
 gulp  = require 'gulp'
 mocha = require 'gulp-mocha'

+source =
+  specs: 'specs/*'
+
+gulp.task 'watch', ->
+  gulp.watch source.specs, ['test']
+
 gulp.task 'test', ->
-  gulp.src 'specs/*'
+  gulp.src source.specs
     .pipe mocha
       reporter: 'spec'
       require: 'coffee-script/register'


Added more flexible hooks for Forms.

Added more flexible hooks for Forms.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -37,12 +37,15 @@
         formTemplate = getTemplate(template)
         console.debug 'onSuccess', args, @
         AutoForm.resetForm(name)
-        result = formArgs.onSuccess?.apply(@, args)
+        onSuccess = formArgs.onSuccess ? formArgs.hooks?.onSuccess
+        result = onSuccess?.apply(@, args)
         callback = => formTemplate.settings.onSuccess?.apply(@, args)
         deferCallback(result, callback)

       onError: (operation, error, template) ->
         console.error('Error submitting form', operation, error, template)
+        onError = formArgs.onError ? formArgs.hooks?.onError
+        onError?.apply(@, args)
         throw new Error(error)

       before:


Fixed bug with form title.

Fixed bug with form title.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -206,7 +206,7 @@
     Form.getFormTitle = ->
       collectionName = Collections.getTitle(formArgs.collection)
       docs = Form.getDocs()
-      suffix = Strings.pluralize(Strings.singular(collectionName), docs, collectionName)
+      suffix = Strings.pluralize(Strings.singular(collectionName), docs.length, collectionName)
       if Form.isBulk()
         suffix = docs.length + ' ' + suffix
       (if docs.length > 0 then 'Edit' else 'Create') + ' ' + suffix


rearranged a bit

rearranged a bit

--- src/scripts/opendns-umbrella.coffee
+++ src/scripts/opendns-umbrella.coffee
@@ -8,7 +8,7 @@
 #   OPENDNS_KEY - Sign up at https://investigate.opendns.com
 #
 # Commands:
-#   hubot opendns <url> - Gets OpenDNS Domain Reputation
+#   hubot opendns <domain> - Gets OpenDNS Domain Reputation
 #   hubot opendns rr <ip> - Gets OpenDNS Resource Record history for a given IP
 #   hubot opendns secinfo <domain> - Gets OpenDNS Security Information for a given domain
 #
@@ -16,8 +16,24 @@
 #   Scott J Roberts - @sroberts

 OPENDNS_KEY = process.env.OPENDNS_KEY
-token = OPENDNS_KEY
-opendns_base = "https://investigate.api.opendns.com/"
+
+# module.exports = (robot) ->
+#   robot.respond /opendns (.*)/i, (msg) ->
+#
+#     if OPENDNS_KEY?
+#       artifact = msg.match[1].toLowerCase()
+#
+#       msg.http("https://investigate.api.opendns.com/domains/categorization/#{artifact}")
+#         .headers('Authorization': 'Bearer ' + OPENDNS_KEY)
+#         .get() (err, res, body) ->
+#           if res.statusCode is 200
+#
+#             opendns_json = JSON.parse body
+#
+#           else
+#             msg.send "Doh! #{res.statusCode}: Which means that didn't work."
+#     else
+#       msg.send "OpenDNS API key not configured."

 module.exports = (robot) ->
   robot.respond /opendns (.*)/i, (msg) ->
@@ -26,7 +42,7 @@
       artifact = msg.match[1].toLowerCase()

       msg.http("https://investigate.api.opendns.com/domains/score/#{artifact}")
-        .headers('Authorization': 'Bearer ' + token)
+        .headers('Authorization': 'Bearer ' + OPENDNS_KEY)
         .get() (err, res, body) ->

           if res.statusCode is 200


off-by-1: toArray(limit) didn't respect the limit

--- src/array.coffee
+++ src/array.coffee
@@ -81,8 +81,8 @@
     r = []
     try
       @each (x) ->
+        if r.length >= limit then throw 'break'
         r.push x
-        if r.length >= limit then throw 'break'
         return
     catch e
       if e isnt 'break' then throw e


test account deletion

test account deletion

--- src/smc-hub/test/api/account.coffee
+++ src/smc-hub/test/api/account.coffee
@@ -12,12 +12,15 @@
     before(setup)
     after(teardown)

+    second_account_id = null
+
     it "gets names for empty list of users", (done) ->
         api.call
             event : 'get_usernames'
             body  :
                 account_ids    : []
             cb    : (err, resp) ->
+                expect(err).toEqual(null)
                 expect(resp?.event).toBe('usernames')
                 expect(resp?.usernames).toEqual({})
                 done(err)
@@ -47,6 +50,7 @@
             cb    : (err, resp) ->
                 expect(resp?.event).toBe('account_created')
                 expect(misc.is_valid_uuid_string(resp?.account_id)).toBe(true)
+                second_account_id = resp?.account_id
                 done(err)

     it "tries to create the same account again", (done) ->
@@ -61,6 +65,15 @@
             cb    : (err, resp) ->
                 expect(resp?.event).toBe('account_creation_failed')
                 expect(resp?.reason).toEqual({"email_address":"This e-mail address is already taken."})
+                done(err)
+
+    it "deletes the second account", (done) ->
+        api.call
+            event : 'delete_account'
+            body  :
+                account_id      : second_account_id
+            cb    : (err, resp) ->
+                expect(resp?.event).toBe('account_deleted')
                 done(err)

 describe 'testing invalid input to creating user accounts -- ', ->


Remove setting store to old data

Remove setting store to old data

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -348,6 +348,7 @@
                             @foreground_opened_file(opts.path)
         return

+    # OPTIMIZATION: Some possible performance problems here. Debounce may be necessary
     flag_file_activity: (filename) =>
         if not @_activity_indicator_timers?
             @_activity_indicator_timers = {}
@@ -355,13 +356,13 @@
         if timer?
             clearTimeout(timer)

+        set_inactive = () =>
+            current_files = @get_store().get('open_files')
+            @setState(open_files : current_files.setIn([filename, 'has_activity'], false))
+
+        @_activity_indicator_timers[filename] = setTimeout(set_inactive, 1000)
+
         open_files = @get_store().get('open_files')
-        set_inactive = () =>
-            new_files_data = open_files.setIn([filename, 'has_activity'], false)
-            @setState(open_files : new_files_data)
-
-        @_activity_indicator_timers[filename] = setTimeout(set_inactive, 1000)
-
         new_files_data = open_files.setIn([filename, 'has_activity'], true)
         @setState(open_files : new_files_data)


Allow files starting with '-' in root directory to be copied

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -407,9 +407,20 @@

     copy_files : (opts) =>
         opts = defaults opts,
-            src  : required
+            src  : required     # Should be an array of source paths
             dest : required
             id   : undefined
+
+        # If files start with a -, make them interpretable by rsync
+        validate = (src_path) ->
+            if src_path[0] == '-'
+                "./#{src_path}"
+            else
+                src_path
+
+        # Ensure that src files are not interpreted as an option to rsync
+        opts.src = opts.src.map(validate)
+
         id = opts.id ? misc.uuid()
         @set_activity(id:id, status:"Copying #{opts.src.length} #{misc.plural(opts.src.length, 'file')} to #{opts.dest}")
         @log


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -534,7 +534,6 @@
     ###*
     A copy of the calling color with its hue shifted by `degrees`. This differs from the hue setter in that it adds to the existing hue value and will wrap around 0 and 360.

-

     magenta = Color(255, 0, 255)

     magenta.hue()


adding hsl methods

adding hsl methods

--- src/color.coffee
+++ src/color.coffee
@@ -171,6 +171,35 @@
         shiftLightness(-amount, self)

       rgba: -> "rgba(#{self.r()}, #{self.g()}, #{self.b()}, #{self.a()})"
+
+      desaturate: (amount) ->
+        hsl = self.toHsl()
+        hsl[1] -= amount
+
+        return Color(hslToRgb(hsl))
+
+      saturate: (amount) ->
+        hsl = self.toHsl()
+        hsl[1] += amount
+
+        return Color(hslToRgb(hsl))
+
+      grayscale: ->
+        hsl = self.toHsl()
+
+        g = hsl[2] * 255
+
+        return Color(g, g, g)
+
+      hue: (degrees) ->
+        hsl = self.toHsl()
+        hsl[0] += degrees
+
+        return Color(hslToRgb(hsl))
+
+      complement: ->
+        hsl = self.toHsl()
+        self.adjust_hue(180)

       toHex: ->
         hexString = (number) ->


Fix servicenode object_type in search

Fix servicenode object_type in search

--- src/views/new-search-results.coffee
+++ src/views/new-search-results.coffee
@@ -48,7 +48,7 @@
             switch object_type
                 when 'unit'
                     app.request 'selectUnit', @model, overwrite: true
-                when 'service_node'
+                when 'servicenode'
                     app.request 'addServiceNode', @model, {}
                 when 'address'
                     app.request 'selectPosition', @model


Form: Make labeled checkbox and radio input first.

--- src/lib/components/forms/Form.coffee
+++ src/lib/components/forms/Form.coffee
@@ -72,7 +72,12 @@
         inputOptions[key] = value
         labelOptions[key] = value

-      allOptions   = { labelOptions, inputOptions }
+      allOptions = { labelOptions, inputOptions }
+      inputType  = inputOptions.type
+
+      if inputType is 'checkbox' or inputType is 'radio'
+        allOptions.inputFirst = yes
+
       labeledInput = new spark.components.LabeledInput allOptions

       @inputsContainer.appendView labeledInput


renaming footer tests

renaming footer tests

--- test/components/task-plan/footer.spec.coffee
+++ test/components/task-plan/footer.spec.coffee
@@ -50,7 +50,7 @@
   div

-describe 'Reading Plan', ->
+describe 'Task Plan Footer', ->
   beforeEach ->
     TaskPlanActions.reset()


Sidebar: removed unnecessary data from the channel item

--- client/Social/Activity/sidebar/topicitem.coffee
+++ client/Social/Activity/sidebar/topicitem.coffee
@@ -4,15 +4,13 @@

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

-    {name, typeConstant, participantCount}    = data
+    {name, typeConstant, participantCount} = data

-    # rewrite grop channel as topic?
-    firstRoute              = if typeConstant is "group" then "topic" else typeConstant
-    # uppercase first letter of the type constant for route
-    firstRoute              = "#{firstRoute.charAt(0).toUpperCase()}#{firstRoute.slice 1}"
-    # build route with firstRoute and name
-    options.route           = "#{firstRoute}/#{name}"
-    options.cssClass        = 'clearfix'
+    # rewrite group channel as topic?
+    route            = if typeConstant is 'group' then 'topic' else typeConstant
+    route            = route.capitalize()
+    options.route    = "#{route}/#{name}"
+    options.cssClass = 'clearfix'

     super options, data

@@ -25,7 +23,6 @@

     """
     {span.ttag{ '#' + #(name)}}
-    {span.hidden.participant-count{#(participantCount) + ' followers'}}
     {{> @followButton}}
     {{> @unreadCount}}
     """


 Merge branch 'master.spellingFix' into 'master'

Master.Spelling Fix

--- client/Teamwork/Views/teamworksharemodal.coffee
+++ client/Teamwork/Views/teamworksharemodal.coffee
@@ -8,7 +8,7 @@

Share the url below to invite others to your Koding teamwork/pair coding session.

-          Be aware that makes your VM accessible to your collaborators until you close the browser tab.
+          Be aware that this makes your VM accessible to your collaborators until you close the browser tab.


activity: add setLastSeenTime action

activity: add setLastSeenTime action

--- client/activity/lib/flux/actions/channel.coffee
+++ client/activity/lib/flux/actions/channel.coffee
@@ -484,6 +484,18 @@

   { SET_CHANNEL_SCROLL_POSITION } = actionTypes
   dispatch SET_CHANNEL_SCROLL_POSITION, { channelId, position }
+
+
+###*
+ * Action to set last seen time of given channel.
+ *
+ * @param {string} channelId
+ * @param {number} timestamp
+###
+setLastSeenTime = (channelId, timestamp) ->
+
+  { SET_CHANNEL_LAST_SEEN_TIME } = actionTypes
+  dispatch SET_CHANNEL_LAST_SEEN_TIME, { channelId, timestamp }

 module.exports = {
@@ -509,5 +521,6 @@
   setSidebarPublicChannelsQuery
   setSidebarPublicChannelsTab
   setScrollPosition
+  setLastSeenTime
 }


Client: Activity: Deleted unnecessary event dispatcher for loadedWithScroll flag and added defer to the batch

--- client/activity/lib/flux/actions/message.coffee
+++ client/activity/lib/flux/actions/message.coffee
@@ -42,15 +42,13 @@
       else
         dispatch UNSET_ALL_MESSAGES_LOADED, { channelId }

-      kd.singletons.reactor.batch ->
-        messages.forEach (message) ->
-          dispatchLoadMessageSuccess channelId, message
-        dispatch LOAD_MESSAGES_SUCCESS, { channelId, messages }
-
-        if options.loadedWithScroll
-          dispatch SET_LOADED_WITH_SCROLL, { channelId }
-
-      resolve { messages }
+      kd.utils.defer ->
+        kd.singletons.reactor.batch ->
+          for message in messages
+            dispatchLoadMessageSuccess channelId, message
+
+          dispatch LOAD_MESSAGES_SUCCESS, { channelId, messages }
+        resolve { messages }

 ###*


Remote-Extensions: Account: fix combinedAppStorage caching

--- client/app/lib/remote-extensions/account.coffee
+++ client/app/lib/remote-extensions/account.coffee
@@ -10,10 +10,10 @@
     super

     @_storageQueue = []
+    @_combinedStorage = null

-    @_combinedStorage = if cs = globals.combinedStorage
-    then remote.revive cs
-    else null
+    if (cs = globals.combinedStorage) and Object.keys(cs).length
+      @_combinedStorage = remote.revive cs

   fetchCombinedStorage: (options, callback) ->


fixed bug.

fixed bug.

--- client/coffee/controllers/geojsonmapCtrl.coffee
+++ client/coffee/controllers/geojsonmapCtrl.coffee
@@ -58,7 +58,7 @@
     return ret

-root.controllers.controller('geojsonmapCtrl', ['$scope', '$routeParams', '$location', 'util', ($scope, $routeParams, $location, util) ->
+root.controllers.controller('geojsonmapCtrl', ['$scope', '$routeParams', '$location', '$rootScope', 'util', ($scope, $routeParams, $location, $rootScope, util) ->
     $scope.geojson = {}
     $scope.geojsonData = {}
     $scope.idx = Number($routeParams.idx)
@@ -96,6 +96,11 @@
     $scope.setStyleOnFeature = (feature, element) ->
         if feature.properties and feature.properties.style
             element.setStyle(feature.properties.style)
+        element.on
+            click: (e) ->
+                $rootScope.$broadcast("leafletDirectiveMap.geojsonClick", feature, e)
+
+

     $scope.initSingleDocument = ()->
         url = getSingleDataUrl($routeParams)


Guard against undefined panelCreateCallbacks

--- lib/settings-view.coffee
+++ lib/settings-view.coffee
@@ -132,7 +132,7 @@
         panel = callback()
         @panelsByName ?= {}
         @panelsByName[name] = panel
-        delete @panelCreateCallbacks[name]
+        delete @panelCreateCallbacks?[name]

     panel


Comment checkbox creation code

Comment checkbox creation code

--- lib/split-diff-ui.coffee
+++ lib/split-diff-ui.coffee
@@ -88,21 +88,27 @@
     # add container to UI
     @element.appendChild(@selectionCount)

+    # create ignore whitespace checkbox
     ignoreWhitespaceValue = document.createElement('input')
     ignoreWhitespaceValue.type = 'checkbox'
     ignoreWhitespaceValue.id = 'ignore-whitespace-checkbox'
+    # set checkbox value to current package ignore whitespace setting
     ignoreWhitespaceValue.checked = isWhitespaceIgnored
+    # register command to checkbox
     ignoreWhitespaceValue.addEventListener('change', () ->
       atom.commands.dispatch(atom.views.getView(atom.workspace), 'split-diff:ignore-whitespace')
     )
+    # create ignore whitespace label
     ignoreWhitespaceLabel = document.createElement('label')
     ignoreWhitespaceLabel.htmlFor = 'ignore-whitespace-checkbox'
     ignoreWhitespaceLabel.textContent = 'Ignore Whitespace'
-
+    # create ignore whitespace container
     settings = document.createElement('div')
     settings.classList.add('settings')
+    # add items to container
     settings.appendChild(ignoreWhitespaceValue)
     settings.appendChild(ignoreWhitespaceLabel)
+    # add container to UI
     @element.appendChild(settings)

   # Tear down any state and detach


fixed the way webterm opens

fixed the way webterm opens

--- client/landing/app/Applications/WebTerm.kdapplication/AppController.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/AppController.coffee
@@ -1,17 +1,25 @@
 class WebTermController extends AppController
+
   constructor: (options = {}, data) ->
-    options.view = new WebTermView
+
+    options.view     = new WebTermView
     options.cssClass = "webterm"
+
     super options, data

   bringToFront: ->
-    mainView = @getSingleton('mainView')
-    terminalView = new WebTermView
-    terminalView.tabPane = mainView.mainTabView.createTabPane
-      name: "Terminal"
-      type: "application"
-      cssClass: "webterm"
-      hiddenHandle: no
-    , terminalView

-WebTerm = {}
+    data = new WebTermView
+
+    options =
+      name         : "Terminal"
+      hiddenHandle : no
+      type         : "application"
+      cssClass     : "webterm"
+
+    @propagateEvent
+      KDEventType  : "ApplicationWantsToBeShown"
+      globalEvent  : yes
+    , {options, data}
+
+WebTerm = {}


Disable checking for updates when updates are in progress

Checking for updates while updating produces a pretty nasty race
condition where the update process will most likely return with the new
updates before the old ones have finished updating.  This means that the
old package cards will be removed and new identical ones added.  Since
the update button is only disabled on the *old* card, it appears as if
you can click the update button on the new card to continue updating.
This in fact tries to update the package multiple times, which is...not
desirable.

Alternatively, addUpdateViews can be smarter and not completely empty
the view but rather add and remove cards based on the availableUpdates
array and which packages already exist in the view.  Not sure if that's
worth the effort though.

--- lib/updates-panel.coffee
+++ lib/updates-panel.coffee
@@ -1,4 +1,5 @@
 {$, ScrollView} = require 'atom-space-pen-views'
+{CompositeDisposable} = require 'atom'
 ErrorView = require './error-view'
 PackageCard = require './package-card'

@@ -20,18 +21,32 @@

   initialize: (@packageManager) ->
     super
+
+    @disposables = new CompositeDisposable()
+    @updatingPackages = []
+
     @updateAllButton.on 'click', => @updateAll()
-    @checkButton.on 'click', =>
-      @checkForUpdates(true)
+    @checkButton.on 'click', => @checkForUpdates(true)

     @updateAllButton.hide()
     @checkForUpdates()

-    @packageManagerSubscription = @packageManager.on 'package-update-failed theme-update-failed', ({pack, error}) =>
-      @updateErrors.append(new ErrorView(@packageManager, error))
+    @disposables.add @packageManager.on 'package-updating theme-updating', ({pack, error}) =>
+      @checkButton.prop('disabled', true)
+      @updatingPackages.push(pack)
+
+    @disposables.add @packageManager.on 'package-updated theme-updated package-update-failed theme-update-failed', ({pack, error}) =>
+      @updateErrors.append(new ErrorView(@packageManager, error)) if error?
+
+      for index, update of @updatingPackages
+        if update.name is pack.name
+          @updatingPackages.splice(index, 1)
+
+      unless @updatingPackages.length
+        @checkButton.prop('disabled', false)

   dispose: ->
-    @packageManagerSubscription.dispose()
+    @disposables.dispose()

   beforeShow: (opts) ->
     if opts?.back
@@ -74,6 +89,7 @@
       @updatesContainer.append(new PackageCard(pack, @packageManager, {back: 'Updates'}))

   updateAll: ->
+    @checkButton.prop('disabled', true)
     @updateAllButton.prop('disabled', true)

     packageCards = @getPackageCards()
@@ -95,8 +111,10 @@
           atom.notifications.addSuccess(message, {dismissable: true, buttons})

         if successfulUpdatesCount is totalUpdatesCount
+          @checkButton.prop('disabled', false)
           @updateAllButton.hide()
         else # Some updates failed
+          @checkButton.prop('disabled', false)
           @updateAllButton.prop('disabled', false)

     onUpdateResolved = ->


Update Utils.FullHeightHeader. Put instance into @el so @el can access @compute()

--- app/assets/javascripts/martian_components/components/utils/full_height_header.coffee
+++ app/assets/javascripts/martian_components/components/utils/full_height_header.coffee
@@ -6,6 +6,8 @@
     $('[full-height-header]').each (i, el) => new @($(el))

   constructor: (@el) ->
+    @el.data('fullHeightHeader', @)
+
     @component = @el.data('component')

     if @component
@@ -14,13 +16,13 @@
       @inner = @el

     @lastWidth = null
-    @offset = @inner.offset().top

     @compute()
-    $(window).on 'resize', @compute
+
+    $(window).on 'resize', =>
+      @compute() unless @lastWidth == $(window).width()
+      @lastWidth = $(window).width()

   compute: =>
-    return false if @lastWidth == $(window).width()
-    @inner.css('height', $(window).height() - @offset)
-    @lastWidth = $(window).width()
+    @inner.css('height', $(window).height() - @inner.offset().top)
     @el.trigger 'compute.fullHeightHeader.MC'