Getting basic entry view working

Getting basic entry view working

--- app/assets/javascripts/models/log.coffee
+++ app/assets/javascripts/models/log.coffee
@@ -1,12 +1,17 @@
 Pluto.Log = Em.Object.extend Pluto.TimeManipulation,
+  # This is just here to fix a coffeescript compilation error
+  # where it has decided I'm not passing an object if all I pass are computed
+  # functions.
+  klass: 'log'
+
   displayDate: Em.computed ->
-    moment(@get('date')).format('MMM Do')
-  # .property('date')
+    moment(@get('date')).format 'MMM Do'
+  .property('date')

   displayTime: Em.computed ->
     @formattedTime @get('time')
-  # .property('time')
+  .property 'time'

   location: Em.computed ->
-    if @get('remote') then 'On-site' else 'Off-site'
-  # .property('remote')
+    if @get('remote') then 'Off-site' else 'On-site'
+  .property 'remote'


prevent default behaviour on click

prevent default behaviour on click

--- app/assets/javascripts/mtl/submit.coffee
+++ app/assets/javascripts/mtl/submit.coffee
@@ -1,3 +1,4 @@
 $(document).on 'click', '[data-mtl-submit]', (ev) ->
   selector = $(this).data('mtl-submit')
   $(selector).submit()
+  ev.preventDefault()


Remove context-loader, trigger events

Remove context-loader, trigger events

Assumes github/github#10271 gets merged.

--- app/assets/javascripts/task_lists.coffee
+++ app/assets/javascripts/task_lists.coffee
@@ -23,21 +23,16 @@

 enableTaskList = (container) ->
   if container.attr('data-task-list-update-url').length > 0
-    container.addClass('context-loader-container')
-    unless container.is('.context-loader-overlay') or container.find('.context-loader-overlay').length
-      container.addClass('context-loader-overlay')
     container.
       find('.task-list-item').addClass('enabled').
       find('.task-list-item-checkbox').attr('disabled', null)
-    container.closest('.context-loader-container').find('.context-loader:first').
-      removeClass 'is-context-loading'
+    comment.trigger('tasklist:enabled')

 disableTaskList = (container) ->
   container.
     find('.task-list-item').removeClass('enabled').
     find('.task-list-item-checkbox').attr('disabled', 'disabled')
-  container.closest('.context-loader-container').find('.context-loader:first').
-    addClass 'is-context-loading'
+  comment.trigger('tasklist:disabled')

 # Submit updates to task list items asynchronously.
 # Successful updates won't require re-rendering to represent reality.


improve wisisyg lists

improve wisisyg lists

--- app/coffee/modules/common/wisiwyg.coffee
+++ app/coffee/modules/common/wisiwyg.coffee
@@ -65,28 +65,84 @@
                     markdownDomNode.find(".preview").remove()
                     markItUpDomNode.show()

+        markdownCaretPositon = false
+
+        setCaretPosition = (elm, caretPos) ->
+            if elm.createTextRange
+                range = elm.createTextRange()
+                range.move('character', caretPos)
+                range.select()
+
+            else if elm.selectionStart
+                elm.focus()
+                elm.setSelectionRange(caretPos, caretPos)
+
+        removeEmptyLine = (textarea, line, currentCaretPosition) ->
+            lines = textarea.value.split("\n")
+            removedLineLength = lines[line].length
+
+            lines[line] = ""
+
+            textarea.value = lines.join("\n")
+
+            #return the new position
+            return currentCaretPosition - removedLineLength + 1
+
         markdownSettings =
             nameSpace: 'markdown'
             onShiftEnter: {keepDefault:false, openWith:'\n\n'}
             onEnter:
                 keepDefault: false
-                replaceWith: (data) ->
-                    lastLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").pop()
+                replaceWith: (data) =>
+                    lines = data.textarea.value[0..(data.caretPosition - 1)].split("\n")
+                    lastLine = lines[lines.length - 1]
+
+                    # unordered list -
                     match = lastLine.match /^(\s*- ).*/
-                    return "\n#{match[1]}" if match
-
+                    if match
+                        emptyListItem = lastLine.match /^(\s*)\-\s$/
+
+                        if emptyListItem
+                            markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
+                        else
+                            return "\n#{match[1]}" if match
+
+                    # unordered list *
                     match = lastLine.match /^(\s*\* ).*/
-                    return "\n#{match[1]}" if match
-
-                    match = lastLine.match /^(\d+)\./
-                    return "\n#{parseInt(match[1], 10) + 1}. " if match
+
+                    if match
+                        emptyListItem = lastLine.match /^(\s*\* )$/
+
+                        if emptyListItem
+                            markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
+                        else
+                            return "\n#{match[1]}" if match
+
+                    # ordered list
+                    match = lastLine.match /^(\s*)(\d+)\.\s/
+
+                    if match
+                        emptyListItem = lastLine.match /^(\s*)(\d+)\.\s$/
+
+                        if emptyListItem
+                            markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
+                        else
+                            return "\n#{match[1] + (parseInt(match[2], 10) + 1)}. "

                     return "\n"

                 afterInsert: (data) ->
                     # Calculate the scroll position
+
+                    if markdownCaretPositon
+                        setCaretPosition(data.textarea, markdownCaretPositon)
+                        caretPosition = markdownCaretPositon
+                        markdownCaretPositon = false
+                    else
+                        caretPosition = data.caretPosition
+
                     totalLines = data.textarea.value.split("\n").length
-                    line = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
+                    line = data.textarea.value[0..(caretPosition - 1)].split("\n").length
                     scrollRelation = line / totalLines
                     $el.scrollTop((scrollRelation * $el[0].scrollHeight) - ($el.height() / 2))


Social; use parameterized statistic name

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -104,10 +104,10 @@
   post url, data, callback

 fetchPopularTopics = (data, callback)->
-  if not data.groupName
+  if not data.groupName or not data.type
     return callback {message: "Request is not valid for listing popular topics"}

-  url = "#{SOCIAL_API_URL}/popular/topics/weekly"
+  url = "#{SOCIAL_API_URL}/popular/topics/#{data.type}"
   get url, data, callback

 fetchPinnedMessages = (data, callback)->


Social: send requests with targetId for fetching profile feed

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -167,9 +167,9 @@
   get url, data, callback

 fetchProfileFeed = (data, callback)->
-  if not data.accountId
-    return callback { message: "AccountId should be set"}
-  url = "#{SOCIAL_API_URL}/account/#{data.accountId}/posts"
+  if not data.targetId
+    return callback { message: "targetId should be set"}
+  url = "#{SOCIAL_API_URL}/account/#{data.targetId}/posts"
   get url, data, callback

 post = (url, data, callback)->


only emit MemberAdded when addMember is called for member and not other roles

--- workers/social/lib/social/traits/joinable.coffee
+++ workers/social/lib/social/traits/joinable.coffee
@@ -16,7 +16,7 @@
     @addMember delegate, as, (err)=>
       if err then callback err
       else
-        @emit 'MemberAdded', delegate
+        @emit 'MemberAdded', delegate  if as is 'member'
         @updateCounts()
         callback null
       # TODO: we used to do the below, but on second thought, it's not a very good idea:
@@ -48,7 +48,7 @@
     as ?= 'member'
     {delegate} = client.connection
     @removeMember delegate, as, callback
-    @emit 'MemberRemoved', delegate
+    @emit 'MemberRemoved', delegate  if as is 'member'
       # if err then callback err
       # else delegate.removeGroup this, as, callback


fix typo

fix typo

--- app/templates/app/server/config/express.coffee
+++ app/templates/app/server/config/express.coffee
@@ -29,7 +29,7 @@

   app.configure ->
     # The cookieParser should be above session
-    app.use express.cookieParser
+    app.use express.cookieParser()

     # Request body paring middleware should be above methodOverride
     app.use express.urlencoded()


KDInputView forceCase fixed

KDInputView forceCase fixed

--- client/Framework/components/inputs/KDInputView.coffee
+++ client/Framework/components/inputs/KDInputView.coffee
@@ -156,8 +156,19 @@
   setValue:(value)->
     @getDomElement().val(value) if value?

+  _prevVal = null
+
   setCase:(forceCase)->
-    @setClass forceCase
+
+    @listenTo
+      KDEventTypes       : [ "keyup", "blur" ]
+      listenedToInstance : @
+      callback           : =>
+        val = @getValue()
+        return if val is _prevVal
+        @setValue val
+        _prevVal = val
+

   setValidation:(ruleSet)->


Remove _clamp

Remove _clamp

--- app/scripts/components/ScrollWall.coffee
+++ app/scripts/components/ScrollWall.coffee
@@ -48,7 +48,6 @@

       Crafty.viewport.y = -@y if Crafty.viewport.y isnt -@y
       Crafty.viewport.x = -@x if Crafty.viewport.x isnt -@x
-      Crafty.viewport._clamp() # TODO: Figure out what this does and if we need it

     @onHit 'PlayerControlledShip', (el) ->
       # Push the player forward
@@ -83,3 +82,6 @@
     @wallEnd.removeComponent('Edge')
     @unbind('EnterFrame')

+  remove: ->
+    @unbind('EnterFrame')
+


activity: improve codestyle ButtonWithMenu component

--- client/app/lib/components/buttonwithmenu/index.coffee
+++ client/app/lib/components/buttonwithmenu/index.coffee
@@ -2,17 +2,22 @@
 React = require 'kd-react'

 module.exports = class ButtonWithMenu extends React.Component
+
+  @defaultProps = { items: [], showMenuForMouseAction: no}
+
   constructor: (props) ->
-    @defaultProps = { items: [], showMenuForMouseAction: no}
+
     @state = { showSettingsMenu: props.showMenuForMouseAction or no }

   renderListMenu: ->
+
     @props.items.map (item) ->
-
{item.title}

+
{item.title}

   onButtonClick: (event) ->
+
     kd.utils.stopDOMEvent event
     @setState showSettingsMenu: yes
     @props.showMenuForMouseAction = yes
@@ -21,7 +26,7 @@
   render: ->
     menuListClassName = if @state.showSettingsMenu and @props.showMenuForMouseAction then 'ButtonWithMenuItemsList' else 'ButtonWithMenuItemsList hidden'

-      <button type="button" onClick={@bound 'onButtonClick'} ></button>
+      <button type="button" onClick={@bound 'onButtonClick'}></button>

         {@renderListMenu()}


Refactor the add method to show a js flash message for a specfic time

When time is not defined then the flash message will be displayed till its closed or the page is refreshed

--- src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.flash.coffee
+++ src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.flash.coffee
@@ -1,6 +1,6 @@
 class Flash

-  add: (message, type) ->
+  add: (message, type, time) ->
     alertId = Date.now();

     $('.main-header').append(
@@ -15,6 +15,10 @@
         '</div>'
     );

+    if time?
+      callback = => @remove(alertId)
+      setTimeout callback, time
+
     return alertId

   remove: (alertId) ->


Tracker.Dependency bugfix

Tracker.Dependency bugfix

--- tracker.coffee
+++ tracker.coffee
@@ -260,10 +260,8 @@
         @_dependents = []

-    depend: (computation) ->
-        if not computation?
-            return false if not Tracker.active
-            computation = Tracker.currentComputation
+    depend: (computation = Tracker.currentComputation) ->
+        return false if not computation?

         if computation in @_dependents
             false
@@ -276,7 +274,7 @@

     changed: ->
-        for computation in @_dependents
+        for computation in _.clone @_dependents
             unless computation is Tracker.currentComputation is @creator
                 computation.invalidate()


fix(thread-list-narrow): Missing flex caused items to not be full-width

--- internal_packages/thread-list/lib/thread-list.cjsx
+++ internal_packages/thread-list/lib/thread-list.cjsx
@@ -108,6 +108,7 @@

     cNarrow = new ListTabular.Column
       name: "Item"
+      flex: 1
       resolver: (thread) =>
         pencil = []
         hasDraft = _.find (thread.metadata ? []), (m) -> m.draft


duplicate account/provider vars cleaned

duplicate account/provider vars cleaned

--- client/landing/app/Applications/Members.kdapplication/ContentDisplays/externalprofileview.coffee
+++ client/landing/app/Applications/Members.kdapplication/ContentDisplays/externalprofileview.coffee
@@ -26,12 +26,15 @@
   setLinkedState:->

     return  unless @parent
-    account    = @parent.getData()
-    {provider} = @getOptions()
+    account     = @parent.getData()
+    {firstName} = account.profile
+
+    {provider, nicename} = @getOptions()

     account.fetchStorage "ext|profile|#{provider}", (err, storage)=>
       return warn err  if err
       return           unless storage
+
       @setData storage

       @$().detach()
@@ -42,13 +45,10 @@
         href   : JsPath.getAt @getData().content, @getOption('urlLocation')
         target : '_blank'

-    account     = @parent.getData()
-    {firstName} = account.profile
-    provider    = @getOption 'nicename'

     @setTooltip if KD.isMine account
-    then title : "Go to my #{provider} profile"
-    else title : "Go to #{firstName}'s #{provider} profile"
+    then title : "Go to my #{nicename} profile"
+    else title : "Go to #{firstName}'s #{nicename} profile"

   click:(event)->


[client][app][main][maincontroller] added block user modal and its required submethods

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -176,6 +176,107 @@
             else if data.bongo_.constructorName is 'JAccount'
               kallback data

+  blockUser:(accountId, duration, callback)->
+    KD.whoami().blockUser accountId, duration, callback
+
+  openBlockUserModal:(data)->
+    @modal = modal = new KDModalViewWithForms
+      title                   : "Block User For a Time Period"
+      content                 : """
+

+                                  This will block user from logging in to Koding(with all sub-groups).

+                                  You can specify a duration to block user.
+

+                                """
+      overlay                 : yes
+      cssClass                : "modalformline"
+      width                   : 500
+      height                  : "auto"
+      tabs                    :
+        forms                 :
+          BlockUser           :
+            callback          : =>
+              modal.modalTabs.forms.BlockUser.buttons.blockUser.showLoader()
+              blockingTime = calculateBlockingTime modal.modalTabs.forms.BlockUser.inputs.duration.getValue()
+              @blockUser data.originId, blockingTime, (err, res)->
+                if err then warn err
+                else
+                  modal.destroy()
+                  new KDNotificationView title : "User is blocked!"
+
+            buttons           :
+              blockUser       :
+                title         : "Block User"
+                style         : "modal-clean-gray"
+                type          : "submit"
+                loader        :
+                  color       : "#444444"
+                  diameter    : 12
+                callback      : -> @hideLoader()
+              cancel          :
+                title         : "Cancel"
+                style         : "modal-cancel"
+                callback      : (event)-> modal.destroy()
+            fields            :
+              duration        :
+                label         : "Block User For"
+                itemClass     : KDInputView
+                name          : "duration"
+                placeholder   : "e.g. 1Y 1W 3D 2H..."
+                keyup         : ->
+                  changeButtonTitle @getValue()
+                change        : ->
+                  changeButtonTitle @getValue()
+                validate             :
+                  rules              :
+                    required         : yes
+                    minLength        : 2
+                  messages           :
+                    required         : "Please enter a time period"
+                    minLength        : "You must enter one pair"
+                  events             :
+                    required         : "blur"
+                    minLength        : "blur"
+                iconOptions          :
+                  tooltip            :
+                    placement        : "right"
+                    offset           : 2
+                    title            : """
+                                       You can enter {#}H/D/W/M/Y,
+                                       Order is not sensitive.
+                                       """
+
+    changeButtonTitle = (value)->
+      blockingTime = calculateBlockingTime value
+      if blockingTime > 0
+        date = new Date (Date.now() + blockingTime)
+        modal.modalTabs.forms.BlockUser.buttons.blockUser.setTitle "Block User to: #{date.toUTCString()}"
+      else
+        modal.modalTabs.forms.BlockUser.buttons.blockUser.setTitle "Block User"
+
+
+    calculateBlockingTime = (value)->
+      totalTimestamp = 0
+      if value
+        for val in value.split(" ")
+          intVal = parseInt(val.substring(0, val.length-1), 10) || 0
+          if intVal is 0 then continue
+          hour = intVal * 60 * 60 * 1000
+          timeCase = val.charAt(val.length-1)
+          switch timeCase.toUpperCase()
+            when "H"
+              totalTimestamp = hour
+            when "D"
+              totalTimestamp = hour * 24
+            when "W"
+              totalTimestamp = hour * 24 * 7
+            when "M"
+              totalTimestamp = hour * 24 * 30
+            when "Y"
+              totalTimestamp = hour * 24 * 365
+      return totalTimestamp
+
+
   decorateBodyTag:->
     if KD.checkFlag 'super-admin'
     then $('body').addClass 'super'


helper: environment fix setMachineUser callbacks

--- client/ide/lib/collaboration/helpers/environment.coffee
+++ client/ide/lib/collaboration/helpers/environment.coffee
@@ -51,11 +51,13 @@
     queue = usernames.map (username) ->
       ->
         kite[method]({username}).then ->
-          callback null
           queue.fin()

         .error (err) ->
           queue.fin()
+
+          return  if err.message is 'User not found' and not share
+
           callback err

     sinkrow.dash queue, callback


Added Jake push task.

Added Jake push task.

--- Jakefile.coffee
+++ Jakefile.coffee
@@ -20,3 +20,8 @@
   jake.exec ["tsc --declaration #{TS_FILE}"], ->
         writeHeader()
         complete()
+
+desc 'Push project to github.'
+task 'push', ->
+  console.log 'pushing to github...'
+  jake.exec ['git push --tags origin master'], EXEC_PRINT_OPTS


Fixed configuration loading/saving.

Fixed configuration loading/saving.

--- emulator/frontend/managers/persistence-manager.coffee
+++ emulator/frontend/managers/persistence-manager.coffee
@@ -66,7 +66,7 @@
     ###########################################################

     loadConfiguration: ->
-        config = @storage.read "config"
+        config = @storage.readObject "config"
         if config
             logger.info "Loading configuration"
             @videoManager.readConfiguration config
@@ -79,6 +79,6 @@
         @videoManager.writeConfiguration config
         @inputManager.writeConfiguration config
         @executionManager.writeConfiguration config
-        @storage.write "config", config
+        @storage.writeObject "config", config

 module.exports = PersistenceManager


code style: number <=> string conversion

--- node_modules_koding/processes/src/processes.coffee
+++ node_modules_koding/processes/src/processes.coffee
@@ -275,7 +275,7 @@
     findChildren = (arr, pid) ->
       findChildrenRec = (pid, root = yes) ->
         c={}
-        pid = ""+pid
+        pid = "#{pid}"
         i = -1
         arr.forEach (item)->
           [parent,child] = item.split "."
@@ -303,7 +303,7 @@
           console.log "killing children: #{pid}"
           try
             pidPair.push pid
-            process.kill pid * 1
+            process.kill +pid
             if pidPair.length is 2
               pidPair.reverse()
               deleteFile = pidPair.join(".")


Adds literal to the js driver.

Adds literal to the js driver.

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -729,6 +729,10 @@
     tt: "DESC"
     st: 'desc'

+class Literal extends RDBOp
+    tt: "LITERAL"
+    st: 'literal'
+

 # All top level exported functions

@@ -832,5 +836,7 @@
 rethinkdb.typeOf = ar (val) -> new TypeOf {}, val
 rethinkdb.info = ar (val) -> new Info {}, val

+rethinkdb.literal = varar 0, 1, (args...) -> new Literal {}, args...
+
 # Export all names defined on rethinkdb
 module.exports = rethinkdb


SocialWorker: Tests: remove appstorage related test cases

--- workers/social/lib/social/models/account.test.coffee
+++ workers/social/lib/social/models/account.test.coffee
@@ -11,7 +11,6 @@
 JUser            = require './user'
 JGroup           = require './group'
 JAccount         = require './account'
-JAppStorage      = require './appstorage'
 { Relationship } = require 'jraphical'

@@ -356,132 +355,6 @@
           daisy queue

-    describe 'when an old app storage exists', ->
-
-      it 'should be able migrate it to JCombinedAppStorage', (done) ->
-
-        withConvertedUser ({ account }) ->
-
-          appId               = generateRandomString()
-          version             = '1.0.0'
-          options             = { appId, version }
-          bucket              = {}
-          oldStorage          = null
-          relationshipOptions = {}
-
-          queue = [
-
-            ->
-              # creating an old app storage document
-              options = { account, appId, version }
-              createOldAppStorageDocument options, (err, data) ->
-                expect(err).to.not.exist
-                { storage : oldStorage, relationshipOptions, bucket } = data
-                expect(oldStorage.bongo_.constructorName).to.be.equal 'JAppStorage'
-                queue.next()
-
-            ->
-              # expecting app storage to be migrated
-              account.fetchOrCreateAppStorage options, (err, newStorage) ->
-                expect(err).to.not.exist
-                expect(newStorage.bongo_.constructorName).to.be.equal 'JCombinedAppStorage'
-                expect(newStorage.accountId).to.be.deep.equal account._id
-                expect(newStorage.bucket[appId].data).to.be.deep.equal bucket
-                expect(newStorage.bucket.bucket).to.not.exist
-                expect(newStorage.bucket.version).to.not.exist
-                queue.next()
-
-            ->
-              # expecting old appstorage to be deleted
-              JAppStorage.one { _id : oldStorage._id }, (err, storage) ->
-                expect(err).to.not.exist
-                expect(storage).to.not.exist
-                queue.next()
-
-            ->
-              # expecting relationship to be deleted
-              expectRelation.toNotExist relationshipOptions, (err) ->
-                expect(err).to.not.exist
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
-
-
-  describe 'migrateOldAppStorageIfExists()', ->
-
-    it 'should return null if storage doesnt exist', (done) ->
-
-      withConvertedUser ({ account }) ->
-
-        version             = generateRandomString()
-        appId               = generateRandomString()
-
-        account.migrateOldAppStorageIfExists { appId, version }, (err, storage) ->
-          expect(err).to.not.exist
-          expect(storage).to.not.exist
-          done()
-
-
-    it 'should migrate old storage if there is one', (done) ->
-
-      withConvertedUser ({ account }) ->
-
-        version             = generateRandomString()
-        appId               = generateRandomString()
-        bucket              = {}
-        oldStorage          = {}
-        relationshipOptions = {}
-
-        queue = [
-
-          ->
-            # creating an old app storage document
-            options = { account, appId, version }
-            createOldAppStorageDocument options, (err, data) ->
-              expect(err).to.not.exist
-              { storage : oldStorage, relationshipOptions, bucket } = data
-              expect(oldStorage.bongo_.constructorName).to.be.equal 'JAppStorage'
-              queue.next()
-
-          ->
-            # expecting old app storage document to be migrated
-            options = { appId, version }
-            account.migrateOldAppStorageIfExists options, (err, newStorage) ->
-              expect(err).to.not.exist
-              expect(newStorage).to.be.an 'object'
-              expect(newStorage.bongo_.constructorName).to.be.equal 'JCombinedAppStorage'
-              expect(newStorage.accountId).to.be.deep.equal account._id
-              expect(newStorage.bucket[appId].data).to.be.deep.equal bucket
-              expect(newStorage.bucket.bucket).to.not.exist
-              expect(newStorage.bucket.version).to.not.exist
-              queue.next()
-
-          ->
-            # expecting old storage document to be deleted
-            options = { appId, version }
-            JAppStorage.one options, (err, oldStorage) ->
-              expect(err).to.not.exist
-              expect(oldStorage).to.not.exist
-              queue.next()
-
-          ->
-            # expecting relationship to be deleted
-            expectRelation.toNotExist relationshipOptions, (err, data) ->
-              expect(err).to.not.exist
-              expect(data).to.not.exist
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
 beforeTests()

 runTests()


Proxy one Insteon device to another for controllerships

--- lib/adapters/insteon-cloud/InsteonCloudAdapter.coffee
+++ lib/adapters/insteon-cloud/InsteonCloudAdapter.coffee
@@ -9,7 +9,11 @@
   name: "Insteon"
   defaults:
     initialStatusCheck:   true # Check status on all devices upon connection?
-    batchCommandInterval: 2000 # How long to wait between repeated commands
+    batchCommandInterval: 2000 # How long to wait between repeated commands (ms)
+    proxyDevices:         {}   # Set "deviceId1": "deviceId2" to redirect all
+                               # status messages from the key device to the
+                               # value device. Also excludes the key device from
+                               # bulk status checks. Useful for controllers.

   initialize: ->
     super
@@ -28,8 +32,7 @@
         @setValid true
       else
         @discoverDevices()
-    @_api.on 'error', =>
-      @log 'warn', 'An Insteon error occurred'
+    @_api.on 'error', => @log 'warn', 'An Insteon error occurred'
     @_api.on 'command', (cmd) => @_handleCommandReceived(cmd)

   discoverDevices: ->
@@ -52,9 +55,9 @@
       if @get('initialStatusCheck') then @requestAllDevicesStatus()

   requestAllDevicesStatus: ->
-    ids = _.keys(@_nodesById)
+    ids = _.without(_.keys(@_nodesById), _.keys(@get('proxyDevices'))...)
     interval = @get('batchCommandInterval')
-    @log 'debug', "Requesting status of all #{ids.length} enumerated devices " +
+    @log 'debug', "Requesting status of #{ids.length} devices " +
       "over the course of #{(interval * ids.length) / 1000} seconds"
     for deviceId, i in ids
       setTimeout _.bind(@requestDeviceStatus, this, deviceId), interval * i
@@ -86,6 +89,13 @@
   _handleCommandReceived: (cmd) ->
     @log 'verbose', "Received Insteon command for device #{cmd.device_insteon_id}"
     node = @_nodesByHexId[cmd.device_insteon_id]
+    proxyFor = @get('proxyDevices')[node.id]
+    if proxyFor?
+      node = @children.get(proxyFor)
+      if node?
+        @log 'verbose', "...proxied to #{node.id}"
+      else
+        @log 'warn', "Proxy target device #{proxyFor} does not exist!"
     if node?
       @log 'verbose', "Dispatching #{cmd.status} command to node #{node.id}"
       switch cmd.status


invitation fix

invitation fix

--- client/landing/app/MainApp/routes.coffee
+++ client/landing/app/MainApp/routes.coffee
@@ -34,7 +34,7 @@
         new KDNotificationView
           title: 'Could not redeem invitation because you are already logged in.'
       else bongo.api.JInvitation.byCode inviteToken, (err, invite)->
-        if err or !invite? or invite.status isnt 'active'
+        if err or !invite? or invite.status isnt 'active' or invite.status isnt 'sent'
           new KDNotificationView
             title: 'Invalid invitation code!'
         else


ImageLinkShell: remix uses img tag, rm wrapper

--- coffee/src/shells/imagelink.shell.coffee
+++ coffee/src/shells/imagelink.shell.coffee
@@ -22,18 +22,20 @@

 class ImageLinkShell.MediaView extends LinkShell.MediaView

-  template: _.template '

'
-
   render: =>
     img = $('').attr 'src', @model.get 'link'
-    @$el.html @template()
-    @$el.find('.wrapper').append img
+    @$el.append img
     @

 class ImageLinkShell.RemixView extends LinkShell.RemixView

+  render: =>
+    img = $('').attr 'src', @model.get 'link'
+    @$el.append img
+    @
+

 acorn.registerShellModule ImageLinkShell


Fixed still show error message after reconnect.

--- app/assets/javascripts/app/lib/app_post/websocket.js.coffee
+++ app/assets/javascripts/app/lib/app_post/websocket.js.coffee
@@ -180,7 +180,7 @@
       @connectionWasEstablished = true

       # close error message show up (because try so connect again) if exists
-      @clearDelay('websocket-no-connection-try-reconnect')
+      @clearDelay('websocket-no-connection-try-reconnect-message')
       if @error
         @error.modalHide()
         @error = false


more fixes

more fixes

--- app/assets/javascripts/components/forum-subject-widget.cjsx
+++ app/assets/javascripts/components/forum-subject-widget.cjsx
@@ -50,54 +50,41 @@
     url = @state.connector.search_url(term)
     window.open url, '_blank'

-  render: ->
-    return null if ! @state.connector?
-    create_url = @state.connector.create_url @props.subject
-    search_enabled = @state.connector.search_url()?
+render: ->
+  return null if ! @state.connector?
+  create_url = @state.connector.create_url @props.subject
+  search_enabled = @state.connector.search_url()?

-    subject_posts = @state.posts.subject ? ( @state.posts.subject_set ? [] )
+  subject_posts = @state.posts.subject ? ( @state.posts.subject_set ? [] )

-

-<<<<<<< HEAD
-      { if search_enabled
-        <form onSubmit={@handleSearchFormSubmit} method='get' action='javascript:void(0);'><input type="text" ref="search_term" placeholder="Search forum"/></form>
-      }
-
Discuss

+

+    { if search_enabled
+      <form onSubmit={@handleSearchFormSubmit} method='get' action='javascript:void(0);'><input type="text" ref="search_term" placeholder="Search forum"/></form>
+    }

-      { if @state.loading and search_enabled
-          Searching for discussions about this {@props.project.term('subject')}...
-        else if subject_posts.length > 0
-
-            Discussion about this {@props.project.term('subject')}:
-

-            { for post,i in subject_posts
-

-                "{post.excerpt.truncate 70}"
-
– {post.author}, {moment(post.updated_at).fromNow()}
-

-            }
-

-
-      }
+    { if @state.loading and search_enabled
+        Searching for discussions about this {@props.project.term('subject')}...
+      else if subject_posts.length > 0
+
+          Discussion about this {@props.project.term('subject')}:
+

+          { for post,i in subject_posts
+

+              "{post.excerpt.truncate 70}"
+
– {post.author}, {moment(post.updated_at).fromNow()}
+

+          }
+

+
+    }

-      { if create_url?
-          Start a { if subject_posts.length > 0 then 'new' else '' } discussion about this {@props.project.term('subject set')}
-        else
-          Oops! Disscussions have not been set up for this {@props.project.term('subject set')}.
-      }
+    { if create_url?
+        Start a { if subject_posts.length > 0 then 'new' else '' } discussion about this {@props.project.term('subject set')}
+      else
+        Oops! Disscussions have not been set up for this {@props.project.term('subject set')}.
+    }

-=======
-      <form onSubmit={@handleSearchFormSubmit} method='get' action='javascript:void(0);'><input type="text" ref="search_term" placeholder="Search forum"/></form>
-      { if subject_posts.length > 0
-

-        { for post in subject_posts
-
{post.title} (Updated {post.updated_at})

-        }
-

-      }
-
Start a discussion about this {@props.project.term('subject set')}

->>>>>>> master
-

+

 window.React = React


refactor popup switch

refactor popup switch

--- app/assets/javascripts/monster_mithril/helpers/popup.coffee
+++ app/assets/javascripts/monster_mithril/helpers/popup.coffee
@@ -57,20 +57,12 @@
     else
       model = @$.model
       id    = @$.model.id()
-    switch @_action
-      when 'edit'
-        if @can_pull('edit')
-          @Api[@_controller].edit {id: id}, @attrs()
-       when 'form'
-         if id
-           if @can_pull('edit')
-             @Api[@_controller].edit {id: id}, @attrs()
-         else
-           if @can_pull('new')
-             @Api[@_controller].new @attrs()
-       else
-         if @can_pull()
-           @Api[@_controller][@_action] {id: id}, @attrs()
+    api = @Api[@_controller]
+    a   = @_action
+    if      @can_pull('edit') && a is 'edit'       then api.edit {id: id}, @attrs()
+    else if @can_pull('edit') && a is 'form' && id then api.edit {id: id}, @attrs()
+    else if @can_pull('new')  && a is 'form'       then api.new @attrs()
+    else if @can_pull()                            then api[@_action] {id: id}, @attrs()
     @set_title {id: id}
   set_title:(data)=>
     title =


OX-721 testing

OX-721 testing

--- app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
@@ -135,7 +135,7 @@
               bucketData = @decode(responseBuffer.subarray(offset, offset += (@cube.BUCKET_LENGTH >> 1)))
             else
               bucketData = responseBuffer.subarray(offset, offset += @cube.BUCKET_LENGTH)
-            #console.log "Success: ", bucket, bucketData
+            console.log "Success: ", bucket, bucketData if @BIT_DEPTH = 16
             if @testData
               id = bucket[0] + bucket[1] * 100 + bucket[2] * 10000
               for i in [0...bucketData.length]


Workspace: Old session recover stuff for tabbed editor.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -6,13 +6,17 @@

     log "i am a CollaborativeTabbedEditorPane"

-    @panel         = @getDelegate()
-    @workspace     = @panel.getDelegate()
-    @sessionKey    = @getOptions().sessionKey or @createSessionKey()
-    @workspaceRef  = @workspace.firepadRef.child @sessionKey
+    @panel            = @getDelegate()
+    @workspace        = @panel.getDelegate()
+    @sessionKey       = @getOptions().sessionKey or @createSessionKey()
+    @workspaceRef     = @workspace.firepadRef.child @sessionKey
+    @isJoinedASession = @getOptions().sessionKey
+
+    log "joined an old session again, creating new tabbed editor"

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

   createEditorTabs: ->
     @tabHandleContainer = new ApplicationTabHandleHolder
@@ -23,12 +27,13 @@
       delegate           : @
       tabHandleContainer : @tabHandleContainer

-  createEditorInstance: (file, content) ->
+  createEditorInstance: (file, content, sessionKey) ->
     pane   = new KDTabPaneView
       name : file?.name or "Untitled.txt"

     editor = new CollaborativeEditorPane {
       delegate : @getDelegate()
+      sessionKey
       file
       content
     }
@@ -48,6 +53,14 @@
         path       : file.path
         sessionKey : editor.sessionKey

+  recoverOldSessionTabs: ->
+    @workspaceRef.once "value", (snapshot) =>
+      {tabs} = snapshot.val()?
+      return unless tabs
+      for key, value of tabs
+        file = FSHelper.createFileFromPath value.path
+        @createEditorInstance file, null, value.sessionKey
+
   openFile: CollaborativeTabbedEditorPane::createEditorInstance

   pistachio: ->


Keep last level 2 selection (e. g. admin interface) for 10 minutes (maybe need to be changed later).

--- app/assets/javascripts/app/controllers/_application_controller_generic.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_generic.coffee
@@ -419,8 +419,14 @@

       group.items = _.sortBy( itemsUnsorted, (item) -> return item.prio )

+    # check last selected item
+    selectedItem = undefined
+    selectedItemMeta = App.Config.get("Runtime::#{@configKey}")
+    keepLastMenuFor = 1000 * 60 * 10
+    if selectedItemMeta && selectedItemMeta.date && new Date < new Date( selectedItemMeta.date.getTime() + keepLastMenuFor )
+      selectedItem = selectedItemMeta.selectedItem
+
     # set active item
-    selectedItem = undefined
     for group in @groupsSorted
       if group.items
         for item in group.items
@@ -441,15 +447,22 @@
         @render(selectedItem, true)
     )

-  render: (selectedItem, force) ->
-    if !$( '.' + @configKey )[0] || force
+  render: (selectedItem, force) =>
+
+    # remember latest selected item
+    selectedItemMeta =
+      selectedItem: selectedItem
+      date: new Date
+    App.Config.set("Runtime::#{@configKey}", selectedItemMeta)
+
+    if !$( ".#{@configKey}" )[0] || force
       @html App.view('generic/navbar_l2')(
         groups:     @groupsSorted
         className:  @configKey
       )
     if selectedItem
       @el.find('li').removeClass('active')
-      @el.find('a[href="' + selectedItem.target + '"]').parent().addClass('active')
+      @el.find("a[href=\"#{selectedItem.target}\"]").parent().addClass('active')

       new selectedItem.controller(
         el: @el.find('.main')


Fixed a typo.

Fixed a typo.

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee
@@ -58,69 +58,67 @@
     # console as needed.
     display = (className, message) ->

-      dom.withReflowEventsDisabled ->
+      unless floatingConsole
+        floatingConsole = dom.create
+          class: "tapestry-console",
+          """
+

+

+

+                #{button "clear", "remove", "Clear Console"}
+                #{button "enable", "play", "Enable Console"}
+                #{button "disable", "pause", "Disable Console"}
+

+

+                <input class="form-control" size="40" placeholder="Filter console content">
+

+

+            """

-        unless floatingConsole
-          floatingConsole = dom.create
-            class: "tapestry-console",
-            """
-

-

-

-                  #{button "clear", "remove", "Clear Console"}
-                  #{button "enable", "play", "Enable Console"}
-                  #{button "disable", "pause", "Disable Console"}
-

-

-                  <input class="form-control" size="40" placeholder="Filter console content">
-

-

-              """
+        dom.body.prepend floatingConsole

-          dom.body.prepend floatingConsole
+        # Basically, any non-blank value will enable the floating console. In addition, the special
+        # value "invisible" will enable it but then hide it ... this is useful in tests, since
+        # the console output is captured in the markup, but the visible console can have unwanted interactions
+        # (such as obscuring elements that make them unclickable).
+        if consoleAttribute is "invisible"
+          floatingConsole.hide()

-          # Basically, any non-blank value will enable the floating console. In addition, the special
-          # value "invisible" will enable it but then hide it ... this is useful in tests, since
-          # the console output is captured in the markup, but the visible console can have unwanted interactions
-          # (such as obscuring elements that make them unclickable).
-          if consoleAttribute is "invisible"
-            floatingConsole.hide()
+        messages = floatingConsole.findFirst ".message-container"

-          messages = floatingConsole.findFirst ".message-container"
+        floatingConsole.findFirst("[data-action=enable]").attribute "disabled", true

-          floatingConsole.findFirst("[data-action=enable]").attribute "disabled", true
+        floatingConsole.on "click", "[data-action=clear]", ->
+          floatingConsole.hide()
+          messages.update ""

-          floatingConsole.on "click", "[data-action=clear]", ->
-            floatingConsole.hide()
-            messages.update ""
+        floatingConsole.on "click", "[data-action=disable]", ->

-          floatingConsole.on "click", "[data-action=disable]", ->
+          @attribute "disabled", true
+          floatingConsole.findFirst("[data-action=enable]").attribute "disabled", false

-            @attribute "disabled", true
-            floatingConsole.findFirst("[data-action=enable]").attribute "disabled", false
+          messages.hide()

-            messages.hide()
+          return false

-            return false
+        floatingConsole.on "click", "[data-action=enable]", ->

-          floatingConsole.on "click", "[data-action=enable]", ->
+          @attribute "disabled", true
+          floatingConsole.findFirst("[data-action=disable]").attribute "disabled", false

-            @attribute "disabled", true
-            floatingConsole.findFirst("[data-action=disable]").attribute "disabled", false
+          messages.show()

-            messages.show()
+          return false

-            return false
+        floatingConsole.on "change keyup", "input", ->
+          updateFilter @value()

-          floatingConsole.on "change keyup", "input", ->
-            updateFilter @value()
+          for e in messages.children()
+            visible = filter e

-            for e in messages.children()
-              visible = filter e
+            e[if visible then "show" else "hide"]()

-              e[if visible then "show" else "hide"]()
-
-            return false
+          return false

         div = dom.create
           class: className,


Fix smiling commits not setting proper classes

--- app/assets/javascripts/answerbox/comment/smile.coffee
+++ app/assets/javascripts/answerbox/comment/smile.coffee
@@ -3,7 +3,7 @@
   cid = btn[0].dataset.cId
   action = btn[0].dataset.action
   count = Number $("span#ab-comment-smile-count-#{cid}").html()
-  btn[0].dataset.loadingText = " #{count}"
+  btn[0].dataset.loadingText = " #{count}"
   btn.button "loading"

   target_url = switch action
@@ -36,8 +36,8 @@
           switch action
             when 'smile'
               btn[0].dataset.action = 'unsmile'
-              btn.html " #{count}"
+              btn.html " #{count}"
             when 'unsmile'
               btn[0].dataset.action = 'smile'
-              btn.html " #{count}"
+              btn.html " #{count}"
       , 20


Have always an area select on jcrop

Have always an area select on jcrop

Added option that blocks a "release" on the selection area. Prevents possible
errors from not having an area selected and prevents the user from mistakenly
removing the selected area.

refs #1334

--- app/assets/javascripts/app/application/crop.js.coffee
+++ app/assets/javascripts/app/application/crop.js.coffee
@@ -11,17 +11,18 @@
         aspectRatio: $(image).attr('data-crop-aspect-ratio')
         setSelect: [0, 0, 350, 350]
         minSize: [100, 100]
+        allowSelect: false
         onSelect: (coords) ->
           update(image, coords)
           enableDisableSubmit($(image).attr('data-crop-button'), true)
         onChange: (coords) ->
           update(image, coords)
           enableDisableSubmit($(image).attr('data-crop-button'), true)
+
+        # this doesn't really happen with 'allowSelect' set to false, it's here
+        # just for extra precaution
         onRelease: ->
           enableDisableSubmit($(image).attr('data-crop-button'), false)
-          # select the entire image if the selection area is released
-          # note: only happens when the aspect ratio is fixed, otherwise
-          # it will never release the selection area
           coords =
             x: 0
             y: 0


teams: added a link to the login page

teams: added a link to the login page

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

-      To be able to login to #{KD.config.groupName}.koding.com, you need to be invited by team administrators.
+      To be able to login to #{KD.config.groupName}.koding.com, you need to be invited by team administrators.

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


drawing tools now appear

drawing tools now appear

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -108,13 +108,13 @@
     if MarkComponent.defaultValues?
       defaultValues = MarkComponent.defaultValues mouseCoords
       for key, value of defaultValues
-        annotation[key] = value
+        mark[key] = value

     # Gather initial coords from event into mark instance:
     if MarkComponent.initStart?
-      initValues = MarkComponent.initStart mouseCoords, annotation, e
+      initValues = MarkComponent.initStart mouseCoords, mark, e
       for key, value of initValues
-        annotation[key] = value
+        mark[key] = value

     @setState
       uncommittedMark: mark


Don't show Unassigned in user select when searching.

--- app/assets/javascripts/project_users_select.js.coffee
+++ app/assets/javascripts/project_users_select.js.coffee
@@ -11,14 +11,15 @@
           Api.projectUsers project_id, query.term, (users) ->
             data = { results: users }

-            nullUser = {
-              name: 'Unassigned',
-              avatar: null,
-              username: 'none',
-              id: -1
-            }
+            if query.term.length == 0
+              nullUser = {
+                name: 'Unassigned',
+                avatar: null,
+                username: 'none',
+                id: -1
+              }

-            data.results.unshift(nullUser)
+              data.results.unshift(nullUser)

             query.callback(data)


OX-64 Collapse Chains

OX-64 Collapse Chains

--- app/assets/javascripts/view/abstractTreeViewer.coffee
+++ app/assets/javascripts/view/abstractTreeViewer.coffee
@@ -5,6 +5,9 @@
 NODE_RADIUS = 2
 MAX_NODE_DISTANCE = 100
 CLICK_TRESHOLD = 4
+
+MODE_NORMAL = 0     # draw every node and the complete tree
+MODE_NOCHAIN = 1    # draw only decision points

 class AbsractTreeViewer
   constructor : (width, height) ->
@@ -33,7 +36,9 @@
     # List of {x : ..., y : ..., id: ...} objects
     @nodeList = []

-    @nodeDistance = Math.min(@height / (@getMaxTreeDepth(tree) + 1), MAX_NODE_DISTANCE)
+    mode = MODE_NOCHAIN
+
+    @nodeDistance = Math.min(@height / (@getMaxTreeDepth(tree, mode) + 1), MAX_NODE_DISTANCE)

     # The algorithm works as follows:
     # A tree is given a left and right border that it can use. If
@@ -48,9 +53,12 @@
     # by recordWidths(), the second by drawTreeWithWidths().

     @recordWidths(tree)
-    @drawTreeWithWidths(tree, 0, @width, @nodeDistance)
-
-  drawTreeWithWidths : (tree, left, right, top) ->
+    @drawTreeWithWidths(tree, 0, @width, @nodeDistance, mode)
+
+  drawTreeWithWidths : (tree, left, right, top, mode) ->
+    unless mode
+      mode = MODE_NORMAL
+
     # get the decision point
     decisionPoint = @getNextDecisionPoint(tree)
     # calculate m (middle that divides the left and right tree, if any)
@@ -71,25 +79,39 @@

     # if the decision point has (2) children, draw them and remember their prosition
     if decisionPoint.children.length > 0
-      c1 = @drawTreeWithWidths(decisionPoint.children[0], left,  m, top + (chainCount + 1) * @nodeDistance)
-      c2 = @drawTreeWithWidths(decisionPoint.children[1], m, right, top + (chainCount + 1) * @nodeDistance)
+      # Calculate the top of the children
+      if mode == MODE_NORMAL or chainCount < 3
+        topC = top + (chainCount + 1) * @nodeDistance
+      else if mode == MODE_NOCHAIN
+        topC = top + 3 * @nodeDistance
+
+      c1 = @drawTreeWithWidths(decisionPoint.children[0], left,  m, topC, mode)
+      c2 = @drawTreeWithWidths(decisionPoint.children[1], m, right, topC, mode)
       # set the root's x coordinate to be in between the decisionPoint's childs
       xr = (c1[0] + c2[0]) / 2
       # draw edges from last node in 'chain' (or root, if chain empty)
       # and the decisionPoint's children
-      @drawEdge(xr, top + chainCount * @nodeDistance, c1[0], c1[1])
-      @drawEdge(xr, top + chainCount * @nodeDistance, c2[0], c2[1])
+      @drawEdge(xr, topC - @nodeDistance, c1[0], c1[1])
+      @drawEdge(xr, topC - @nodeDistance, c2[0], c2[1])
     else
       # if decisionPoint is leaf, there's not much to do
       xr = m

-    # Draw the chain and the root, connect them.
-    node = tree
-    for i in [0..chainCount]
-      @drawNode(xr, top + i * @nodeDistance, node.id)
-      node = node.children[0]
-      if i != 0
-        @drawEdge(xr, top + (i - 1) * @nodeDistance, xr, top + i * @nodeDistance)
+    if mode == MODE_NORMAL or chainCount < 3
+      # Draw the chain and the root, connect them.
+      node = tree
+      for i in [0..chainCount]
+        @drawNode(xr, top + i * @nodeDistance, node.id)
+        node = node.children[0]
+        if i != 0
+          @drawEdge(xr, top + (i - 1) * @nodeDistance, xr, top + i * @nodeDistance)
+    else if mode == MODE_NOCHAIN
+      # Draw root, chain indicator and decision point
+      @drawNode(xr, top, tree.id)
+      @drawEdge(xr, top, xr, top + 0.5 * @nodeDistance)
+      @drawChainIndicator(xr, top + 0.5 * @nodeDistance, top + 1.5 * @nodeDistance)
+      @drawEdge(xr, top + 1.5 * @nodeDistance, xr, top + 2 * @nodeDistance)
+      @drawNode(xr, top + 2 * @nodeDistance, decisionPoint.id)

     return [xr, top]

@@ -103,9 +125,19 @@

   drawEdge : (x1, y1, x2, y2) ->
     @ctx.beginPath()
-    @ctx.fillStyle = "#000000"
+    @ctx.strokeStyle = "#000000"
     @ctx.moveTo(x1, y1)
     @ctx.lineTo(x2, y2)
+    @ctx.stroke()
+
+  drawChainIndicator : (x, top, bottom) ->
+    # Draw a dashed line
+    dashLength = (bottom - top) / 7
+    @ctx.beginPath()
+    @ctx.strokeStyle = "#000000"
+    for i in [0, 1, 2]
+      @ctx.moveTo(x, top + (2 * i + 1) * dashLength)
+      @ctx.lineTo(x, top + (2 * i + 2) * dashLength)
     @ctx.stroke()

   # Decision point is any point with point.children.length != 1
@@ -132,20 +164,30 @@
       result += child.width
     return result

-  getMaxTreeDepth : (tree, count) ->
+  getMaxTreeDepth : (tree, mode, count) ->
+    unless count
+      count = 0
     unless tree
-      return 0
-    unless count
-      count = 1
-
+      return count
+    unless mode
+      mode  = MODE_NORMAL
+
+    # One decision point
+    count++
+
+    # Count non decision points
+    chainCount = 0
     while tree.children.length == 1
       tree = tree.children[0]
-      count++
+      chainCount++
+    if mode == MODE_NOCHAIN
+      chainCount = Math.min(chainCount, 2)
+    count += chainCount

     if tree.children.length == 0
       return count
-    return Math.max(@getMaxTreeDepth(tree.children[0], count),
-              @getMaxTreeDepth(tree.children[1], count)) + 1
+    return Math.max(@getMaxTreeDepth(tree.children[0], mode, count),
+              @getMaxTreeDepth(tree.children[1], mode, count))

   onClick : (evt) =>
     id = @getIdFromPos(evt.offsetX, evt.offsetY)


ComputeController: simplfy error codes for kloud

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -3,6 +3,11 @@
   @providers = KD.config.providers

   @timeout = 30000
+
+  @Error = {
+    'TimeoutError', 'KiteError',
+    Pending: '107', NotVerified: '500'
+  }

   constructor:->

@@ -175,11 +180,7 @@

   errorHandler: (call, task, machine)->

-    ComputeErrors = {
-      "TimeoutError", "KiteError", Pending: "107"
-    }
-
-    { timeout }   = ComputeController
+    { timeout, Error } = ComputeController

     retryIfNeeded = KD.utils.throttle 500, (task, machine)=>

@@ -203,16 +204,16 @@

       switch err.name

-        when ComputeErrors.TimeoutError
+        when Error.TimeoutError

           safeToSuspend = task is 'info'
           retryIfNeeded task, machine
           info "Cancelling... #{task} ..."
           call.cancel()

-        when ComputeErrors.KiteError
-
-          if err.code is ComputeErrors.Pending
+        when Error.KiteError
+
+          if err.code is Error.Pending
             retried = retryIfNeeded task, machine
             safeToSuspend = yes
           else


compute/connect: update state when new props received

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

--- client/app/lib/providers/connectcompute.coffee
+++ client/app/lib/providers/connectcompute.coffee
@@ -83,6 +83,11 @@
           computeController.on eventId, @events[eventId]

+    componentWillReceiveProps: (nextProps, nextState) ->
+
+      @setState makeState config, nextProps
+
+
     componentWillUnmount: ->

       @_mounted = no


StackTemplateList: prevent users to delete default stack template

--- client/app/lib/stacks/stacktemplatelist.coffee
+++ client/app/lib/stacks/stacktemplatelist.coffee
@@ -17,10 +17,13 @@
     super options, data

-  # TODO Check if the template is in use and warn user about that! ~ GG
   deleteItem: (item) ->

-    stack = item.getData()
+    template = item.getData()
+
+    currentGroup = kd.singletons.groupsController.getCurrentGroup()
+    if template._id in (currentGroup.stackTemplates ? [])
+      return showError 'This template currently in use by the Team'

     # Since KDModalView.confirm not passing overlay options
     # to the base class (KDModalView) I had to do this hack
@@ -28,11 +31,11 @@
     overlay = new KDOverlayView cssClass: 'second-overlay'

     modal   = KDModalView.confirm
-      title       : 'Remove stack'
-      description : 'Do you want to remove ?'
+      title       : 'Remove stack template ?'
+      description : 'Do you want to remove this stack template ?'
       ok          :
         title     : 'Yes'
-        callback  :  => stack.delete (err) =>
+        callback  :  => template.delete (err) =>
           modal.destroy()
           @emit 'ItemDeleted', item  unless showError err


video: model track connection counts

video: model track connection counts

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -16,6 +16,7 @@
     connectedToSession : no
     maxConnectionCount : 999
     activeParticipant  : null
+    connectionCount    : 0

   # @param {SocialChannel} options.channel
   # @param {BaseChatVideoView} options.view
@@ -126,10 +127,13 @@
       @emit 'ParticipantLeft', { nick: event.stream.name }

     session.on 'connectionCreated', (event) =>
-
-      # TODO: this may not be necessary.
-      data = { nick: getNick() }
-      @_service.sendSignal session, 'initialize', data
+      count = @state.connectionCount
+      @setState { connectionCount: count + 1 }
+
+    session.on 'connectionDestroyed', (event) =>
+      count = @state.connectionCount
+      @setState { connectionCount: count - 1 }
+

     session.on 'sessionDisconnected', (event) =>
       @setState { connectedToSession: no }


Removed comment

Removed comment

--- core/app/backbone/views/interacting_users_names_view.coffee
+++ core/app/backbone/views/interacting_users_names_view.coffee
@@ -53,8 +53,6 @@
   truncatedListSizes: ->
     truncatedListSizes @collection.totalRecords, @number_of_items

-  #Possible method restrict names to one line: http://jsbin.com/esikiv/3/edit
-
   show_all: (e) ->
     e.stopPropagation()
     e.preventDefault()


set/unset changeset class when model is updated

--- client/lanes/components/shared/FieldMixin.cjsx
+++ client/lanes/components/shared/FieldMixin.cjsx
@@ -34,14 +34,17 @@
     _setValue: (value) ->
         if @props.setValue then @props.setValue(value) else @model[@props.name] = value

-    unsetChangeSet: ->
-        @setState(changeset: false) if @isMounted()
+    componentWillUnmount: ->
+        clearTimeout(@state.pendingChangeSetDelay) if @state.pendingChangeSetDelay
+
+    _unsetChangeSet: ->
+        @setState(displayChangeset: false, pendingChangeSetDelay: null)

     setDataState: (state, evname) ->
-        changeset = evname == "changeset"
-        if changeset
-            _.delay(@unsetChangeSet, 2000)
-        @setState  _.extend(state, {changeset: changeset})
+        displayChangeset = @model.updatingFromChangeset and @model.changedAttributes()[@props.name]
+        if displayChangeset
+            pendingChangeSetDelay = _.delay(@_unsetChangeSet, 2000)
+        @setState(_.extend( state, {pendingChangeSetDelay, displayChangeset}))

     handleChange: (ev) ->
         if @props.onChange
@@ -56,7 +59,9 @@
         <span>{value}</span>

     formGroupClassNames: ->
-        _.result(this, 'formGroupClass')
+        _.classnames( _.result(this, 'formGroupClass'), {
+            changeset: @state.displayChangeset
+        })

     _mixinRenderValue: (label, className) ->
         value = (@renderDisplayValue || @renderMixinDisplayValue)?()


Renamed f to fact

Renamed f to fact

--- core/app/backbone/client/facts_new_view.coffee
+++ core/app/backbone/client/facts_new_view.coffee
@@ -108,17 +108,17 @@

     channel_ids = @addToCollection.map (ch)-> ch.id

-    f = new Fact
+    fact = new Fact
       opinion: @wheel.userOpinion()
       displaystring:  @$('textarea#fact').val()
       fact_url: @$('input#url').val()
       fact_title: @$('input#title').val()
       channels: channel_ids

-    f.save {},
+    fact.save {},
       success: =>
-        f.set containing_channel_ids: channel_ids
-        @trigger 'factCreated', f
+        fact.set containing_channel_ids: channel_ids
+        @trigger 'factCreated', fact

   openWhatIsYourOpinionHelpText: ->
     if FactlinkApp.guided


Hide tabs unless current user

Hide tabs unless current user

--- core/app/backbone/views/react_user_tabs.coffee
+++ core/app/backbone/views/react_user_tabs.coffee
@@ -1,5 +1,7 @@
 window.ReactUserTabs = React.createBackboneClass
   render: ->
+    return _span() unless FactlinkApp.isCurrentUser @model()
+
     spaced_middle_dot = " \u00b7 "

     _div ['main-region-tabs'],


Remove hover class when leaving the drop zone.

--- lib/assets/javascripts/jack_up/drag_and_drop.coffee
+++ lib/assets/javascripts/jack_up/drag_and_drop.coffee
@@ -5,20 +5,21 @@
 class @JackUp.DragAndDrop
   constructor: (@droppableElement, @processor) ->
     @droppableElement
-      .bind("dragenter", @_drag)
+      .bind("dragenter", @_dragEnter)
+      .bind("dragleave", @_dragLeave)
       .bind("drop", @_drop)
-      .bind("drop", @_dragOut)

-  _drag: (event) =>
+  _dragEnter: (event) =>
     ignoreEvent event
     event.originalEvent.dataTransfer.dropEffect = "copy"
     @droppableElement.addClass("hover")

-  _dragOut: (event) =>
+  _dragLeave: (event) =>
     ignoreEvent event
     @droppableElement.removeClass("hover")

   _drop: (event) =>
     ignoreEvent event
+    @droppableElement.removeClass("hover")
     @droppableElement.find('[data-placeholder]').hide()
     @processor.processFilesForEvent(event)


Allow specifying that reload should not be called on download.

--- application/chrome/content/wesabe/download/Job.coffee
+++ application/chrome/content/wesabe/download/Job.coffee
@@ -106,14 +106,14 @@

     @player.onLastGoalFinished()

-  recordSuccessfulDownload: (file, suggestedFilename, metadata) ->
+  recordSuccessfulDownload: (file, suggestedFilename, metadata, reload=true) ->
     wesabe.info 'successfully downloaded file to ', file.path
     @data.downloads ||= []
     @data.downloads.push(wesabe.lang.extend({path: file.path, suggestedFilename: suggestedFilename, status: 'ok'}, metadata || {}))
-    @player.onDownloadSuccessful @player.browser, wesabe.dom.page.wrap(@player.browser.contentDocument)
+    @player.onDownloadSuccessful @player.browser, wesabe.dom.page.wrap(@player.browser.contentDocument) if reload

-  recordFailedDownload: (metadata) ->
+  recordFailedDownload: (metadata, reload=true) ->
     wesabe.error 'failed to download file'
     @data.downloads ||= []
     @data.downloads.push(wesabe.lang.extend({status: 'error'}, metadata || {}))
-    @player.onDownloadSuccessful @player.browser, wesabe.dom.page.wrap(@player.browser.contentDocument)
+    @player.onDownloadSuccessful @player.browser, wesabe.dom.page.wrap(@player.browser.contentDocument) if reload


Use default styleFilename

Use default styleFilename

--- exercises/configs/base.coffee
+++ exercises/configs/base.coffee
@@ -1,3 +1,2 @@
 module.exports =
   devPort: '8001'
-  styleFilename: 'exercises.css'


Add threshold to check if admin log viewer is at bottom

--- packages/rocketchat-logger/client/views/viewLogs.coffee
+++ packages/rocketchat-logger/client/views/viewLogs.coffee
@@ -32,8 +32,9 @@

 	template = this

-	template.isAtBottom = ->
-		if wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight
+	template.isAtBottom = (scrollThreshold) ->
+		if not scrollThreshold? then scrollThreshold = 0
+		if wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight
 			newLogs.className = "new-logs not"
 			return true
 		return false
@@ -43,7 +44,7 @@
 		newLogs.className = "new-logs not"

 	template.checkIfScrollIsAtBottom = ->
-		template.atBottom = template.isAtBottom()
+		template.atBottom = template.isAtBottom(100)
 		readMessage.enable()
 		readMessage.read()


document earlier attempt at setting dayHeight

--- src/components/course-calendar/duration.cjsx
+++ src/components/course-calendar/duration.cjsx
@@ -255,10 +255,13 @@
         plansOnDay.length
       ).length

-      # set day height to fit the number of overlapping durations
-      dayHeight = @_calcDayHeight(rangeData.maxPlansOnDay)
-      rangeData.dayHeight = dayHeight if dayHeight > rangeData.dayHeight
+      # set day height to the best-guess for this range based on how many plans it has.
+      # It'll be fine-tuned later across all ranges
+      rangeData.dayHeight = Math.max(
+        @_calcDayHeight(rangeData.maxPlansOnDay), rangeData.dayHeight
+      )
       rangeData
+

   renderChildren: (item) ->
     {courseId} = @props


Yay grammar

Yay grammar

--- bot.iced
+++ bot.iced
@@ -327,7 +327,7 @@
 				return bot.sendMessage chatterID, "The database ran into an error"

 			if /\+verify/i.test(message)
-				bot.sendMessage DogeTipGroupID, "#{shibe} was been tipped #{amount} DOGE by #{user.name}! Wow. Such coin."
+				bot.sendMessage DogeTipGroupID, "#{shibe} was tipped #{amount} DOGE by #{user.name}! Wow. Such coin."
 			# Notify both parties of tip
 			bot.sendMessage chatterID, "You tipped #{shibe} #{amount} DOGE successfully"
 			bot.sendMessage shibeID, "You were tipped #{amount} DOGE by #{user.name}"


optimized gruntfile

optimized gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -250,26 +250,14 @@
             files: "src/*.js"
             tasks: ["jshint:beforeConcat"]

-  grunt.loadNpmTasks "grunt-contrib-clean"
-  grunt.loadNpmTasks "grunt-contrib-compress"
-  grunt.loadNpmTasks "grunt-contrib-concat"
-  grunt.loadNpmTasks "grunt-contrib-connect"
-  grunt.loadNpmTasks "grunt-contrib-copy"
-  grunt.loadNpmTasks "grunt-contrib-csslint"
-  grunt.loadNpmTasks "grunt-contrib-cssmin"
-  grunt.loadNpmTasks "grunt-contrib-jshint"
-  grunt.loadNpmTasks "grunt-contrib-less"
-  grunt.loadNpmTasks "grunt-contrib-qunit"
-  grunt.loadNpmTasks "grunt-contrib-uglify"
-  grunt.loadNpmTasks "grunt-contrib-watch"
-  grunt.loadNpmTasks "grunt-bumpup"
-  grunt.loadNpmTasks "grunt-checkrepo"
-  grunt.loadNpmTasks "grunt-docco2"
-  grunt.loadNpmTasks "grunt-exec"
-  grunt.loadNpmTasks "grunt-html"
-  grunt.loadNpmTasks "grunt-jsdoc"
-  grunt.loadNpmTasks "grunt-tagrelease"
-  grunt.loadNpmTasks "grunt-text-replace"
+  # ----------------------------------------------------------------------------
+
+  # Load "grunt*" dependencies
+
+  for key of grunt.file.readJSON("package.json").devDependencies
+      grunt.loadNpmTasks key  if key isnt "grunt" and key.indexOf("grunt") is 0
+
+  # Register tasks

   grunt.registerTask "server", ["connect:forever"]
   grunt.registerTask "dev", ["connect:dev", "watch"]


Added NuGet pack

Added NuGet pack

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,4 @@
-module.exports = (grunt) ->
+module.exports = (grunt) ->
   require('time-grunt')(grunt)

   grunt.initConfig
@@ -33,8 +33,12 @@
         output: 'reports/mspec'
       specs:
         src: ['src/**/bin/Debug/*.Tests.dll']
+    nugetpack:
+      dist:
+        src: 'src/AutoGrid/AutoGrid.nuspec'
+        dest: '.'

-  grunt.task.registerTask 'banner', () ->
+  grunt.task.registerTask 'banner', () ->
     console.log(grunt.file.read('banner.txt'))

   # grunt.loadNpmTasks 'grunt-exec'
@@ -44,6 +48,6 @@
   grunt.loadNpmTasks 'grunt-dotnet-mspec'

   grunt.registerTask 'default', ['ci']
-  grunt.registerTask 'ci', ['banner','assemblyinfo', 'nugetrestore', 'msbuild', 'mspec']
+  grunt.registerTask 'ci', ['banner','assemblyinfo','nugetrestore','msbuild','mspec','nugetpack']

   null


Auto watch gruntfile changes

Auto watch gruntfile changes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -30,6 +30,10 @@
           spawn: false,
         },
       },
+      grunt: {
+        files: ['Gruntfile.coffee'],
+        tasks: ['default-no-uglify']
+      }
     },
     coffee:
       build:


Using .sr instead of aria-label

Using .sr instead of aria-label

--- common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -243,13 +243,14 @@
       @trigger("thread:removed", thread_id)

     setActiveThread: (thread_id) ->
-      @$(".forum-nav-thread[data-id!='#{thread_id}'] .forum-nav-thread-link").removeClass("is-active").removeAttr('aria-label')
-      @$(".forum-nav-thread[data-id='#{thread_id}'] .forum-nav-thread-link").addClass("is-active").attr('aria-label', 'Current conversation')
+      @$(".forum-nav-thread-link").find(".sr").remove()
+      @$(".forum-nav-thread[data-id!='#{thread_id}'] .forum-nav-thread-link").removeClass("is-active")
+      @$(".forum-nav-thread[data-id='#{thread_id}'] .forum-nav-thread-link").addClass("is-active").find(".forum-nav-thread-wrapper-1").prepend('' + gettext("Current conversation") + '')

     goHome: ->
       @template = _.template($("#discussion-home").html())
       $(".forum-content").html(@template)
-      $(".forum-nav-thread-list a").removeClass("is-active").removeAttr('aria-label')
+      $(".forum-nav-thread-list a").removeClass("is-active").find(".sr").remove()
       $("input.email-setting").bind "click", @updateEmailNotifications
       url = DiscussionUtil.urlFor("notifications_status",window.user.get("id"))
       DiscussionUtil.safeAjax


Update after customizing, else the viewport might be off in Framer

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -290,6 +290,7 @@
 	customize: (deviceProps) =>
 		Devices.custom = _.defaults deviceProps, Devices.custom
 		@deviceType = "custom"
+		@_update()

 	@define "deviceType",
 		get: ->


Fix borderRadius scaling bug / offsetX allowing min > max to be reversed either as positive or negative numbers, setting the value within the SliderComponent declaration now automatically updates the fill as well.

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -38,6 +38,7 @@

 		super options

+		@knobSize = options.knobSize or 30
 		@knob.superLayer = @fill.superLayer = @
 		@fill.height = @height

@@ -48,11 +49,9 @@
 		@knob.draggable.momentumOptions = {friction: 2, tolerance: 1/10}
 		@knob.draggable.bounce = false
 		@knob.draggable.propagateEvents = false
-		@knob.borderRadius = 100
-		@knob.placeBefore(@fill)
+		@knob.borderRadius = "50%"

 		@_updateFrame()
-		@_updateFill()

 		@on("change:frame", @_updateFrame)
 		@on("change:borderRadius", @_setRadius)
@@ -65,13 +64,12 @@
 		# On click/touch of the slider, update the value
 		@on(Events.TouchStart, @_touchDown)

-		@knobSize = options.knobSize or 30

 	_touchDown: (event) =>
 		event.preventDefault()
 		event.stopPropagation()

-		if @min > 0 then offsetX = (@min / @canvasScaleX()) - @min else offsetX = 0
+		offsetX = (@min / @canvasScaleX()) - @min
 		@value = @valueForPoint(event.x - @screenScaledFrame().x) / @canvasScaleX() - offsetX
 		@knob.draggable._touchStart(event)

@@ -79,15 +77,15 @@
 		@fill.width = @knob.midX

 	_updateKnob: =>
-		@knob.midX = @fill.width
+		@knob.midX = @fill.width
 		@knob.centerY()

 	_updateFrame: =>
 		@knob.draggable.constraints =
 			x: -@knob.width / 2
-			y: -@knob.height / 2
 			width: @width + @knob.width
-			height: @height + @knob.height
+			# y: -@knob.height / 2
+			# height: @height + @knob.height

 		@knob.centerY()

@@ -113,11 +111,11 @@
 		set: (value) -> @_max = value

 	@define "value",
-		get: ->
-			@valueForPoint(@knob.midX)
+		get: -> @valueForPoint(@knob.midX)

 		set: (value) ->
 			@knob.midX = @pointForValue(value)
+			@_updateFill()

 	_updateValue: =>
 		@emit("change:value", @value)


Fix knob size % warning

Fix knob size % warning

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -57,7 +57,6 @@
 		@knob.draggable.momentumOptions = {friction: 5, tolerance: 0.25}
 		@knob.draggable.bounce = false
 		@knob.draggable.propagateEvents = false
-		@knob.borderRadius = "50%"

 		@_updateFrame()
 		@_updateKnob()
@@ -136,6 +135,7 @@
 			@_knobSize = value
 			@knob.width = @_knobSize
 			@knob.height = @_knobSize
+			@knob.borderRadius = @knobSize / 2
 			@_updateFrame()

 	@define "min",


EnvironmentListItem: allow to delete kicked member's stack

--- client/app/lib/environment/environmentlistitem.coffee
+++ client/app/lib/environment/environmentlistitem.coffee
@@ -58,7 +58,8 @@
         title       : 'RE-INIT STACK'
         callback    : @bound 'handleStackReinit'

-      unless stack.config?.groupStack
+      config = stack.config ? {}
+      if not config.groupStack or config.oldOwner
         @deleteStackButton = new kd.ButtonView
           cssClass : 'solid compact red delete-stack'
           title    : 'Delete Stack'


Make "load more" in forums not appear incorrectly

The rendering of the link happens in an event handler, and the
collection's attributes were not getting set properly before the reset
event triggering the rendering occurred.

Originally reviewed in #4072

--- common/static/coffee/src/discussion/discussion.coffee
+++ common/static/coffee/src/discussion/discussion.coffee
@@ -62,9 +62,9 @@
           new_threads = [new Thread(data) for data in response.discussion_data][0]
           new_collection = _.union(models, new_threads)
           Content.loadContentInfos(response.annotated_content_info)
-          @reset new_collection
           @pages = response.num_pages
           @current_page = response.page
+          @reset new_collection
         error: error

     sortByDate: (thread) ->


Added a note to the users about adding/removing shortcuts

--- extension/packages/help.coffee
+++ extension/packages/help.coffee
@@ -142,6 +142,9 @@
       #{ _('help_version') } #{ utils.getVersion() }
       ✖

+
Did you know that you can add/remove shortucts in this dialog?

+

+
Click the shortcut to remove it, and click ⊞ to add new shortcut!


Fixed bug in viewCache

Fixed bug in viewCache

--- core/app/backbone/factlink/detached_view_cache.coffee
+++ core/app/backbone/factlink/detached_view_cache.coffee
@@ -47,14 +47,16 @@
     @switchView(@current)

   closeCacheView: (key) ->
-    @views[key].close()
-    @views[key] = undefined
+    @cache[key].close() if @cache[key].close?
+    @cache[key].remove()
+    delete @cache[key]

   clear: ->
-    for key, view of @views
-      @closeView(key)
-      view.remove()
+    for key, view of @cache
+      if view != @_view
+        @closeCacheView(key)

   cleanup: ->
+    @_view = null
     @clear()
     super()


Preloader: wait for audio/video players to load their content until it’s ready to play

--- framer/Extras/Preloader.coffee
+++ framer/Extras/Preloader.coffee
@@ -50,6 +50,9 @@
 	addImagesFromContext: (context) ->
 		_.pluck(context.layers, "image").map @addImage

+	addPlayersFromContext: (context) ->
+		_.pluck(context.layers, "player").map @addPlayer
+
 	addImage: (image) =>
 		if image and image not in @_media
 			@_media.push(image)
@@ -58,6 +61,15 @@
 			Utils.loadImage image, (error) =>
 				@_mediaLoaded.push(image)
 				@_handleProgress()
+
+	addPlayer: (player) =>
+		if player and player.readyState? and player not in @_media
+			if player.readyState < 3
+				@_media.push(player)
+				# Wait until there is enough data for playback to start
+				Events.wrap(player).addEventListener "canplay", =>
+					@_mediaLoaded.push(player)
+					@_handleProgress()

 	start: =>

@@ -80,6 +92,8 @@
 		# By default we take the image from the prototype and the device
 		@addImagesFromContext(Framer.DefaultContext)
 		@addImagesFromContext(Framer.CurrentContext)
+		@addPlayersFromContext(Framer.DefaultContext)
+		@addPlayersFromContext(Framer.CurrentContext)

 		# If we don't need any images to be preloaded we can stop
 		if not @_media.length
@@ -108,7 +122,7 @@

 	_handleTimeout: =>
 		return unless @isLoading
-		console.error "Timeout"
+		console.error "Preloader timeout, ending"
 		@end()

 exports.enable = ->


Clarify value and point conversion methods

--- framer/Components/RangedSliderComponent.coffee
+++ framer/Components/RangedSliderComponent.coffee
@@ -287,26 +287,35 @@
 		@emit(Events.SliderMinValueChange, @minValue)
 		@emit(Events.SliderMaxValueChange, @maxValue)

+	# Retrieve the point (x or y coordinate) of a certain numeric value.
 	pointForValue: (value) ->
 		for knob in [@minKnob, @maxKnob]
+
+			# For horizontal (default) sliders.
 			if @width > @height
 				if @constrained
 					return Utils.modulate(value, [@min, @max], [0 + (knob.width / 2), @width - (knob.width / 2)], true)
 				else
 					return Utils.modulate(value, [@min, @max], [0 , @width], true)
+
+			# For vertical sliders.
 			else
 				if @constrained
 					return Utils.modulate(value, [@min, @max], [0 + (knob.height / 2), @height - (knob.height / 2)], true)
 				else
 					return Utils.modulate(value, [@min, @max], [0, @height], true)

+	# Retrieve the numeric value of a certain point (x or y coordinate).
 	valueForPoint: (value) ->
 		for knob in [@minKnob, @maxKnob]
+			# For horizontal (default) sliders.
 			if @width > @height
 				if @constrained
 					return Utils.modulate(value, [0 + (knob.width / 2), @width - (knob.width / 2)], [@min, @max], true)
 				else
 					return Utils.modulate(value, [0, @width], [@min, @max], true)
+
+			# For vertical sliders.
 			else
 				if @constrained
 					return Utils.modulate(value, [0 + (knob.height / 2), @height - (knob.height / 2)], [@min, @max], true)


reworked storage page layout

reworked storage page layout

--- imports/ui/pages/StoragePage.cjsx
+++ imports/ui/pages/StoragePage.cjsx
@@ -1,7 +1,7 @@
 { Meteor }          = require 'meteor/meteor'
 React               = require 'react'

-{ Article, Box, Header, Heading, Search, Title, Box, Split, Sidebar, Paragraph } = require 'grommet'
+{ Article, Box, Header, Heading, Search, Title, Box, Split, Sidebar, Paragraph, Icons } = require 'grommet'

 DataStoreUsageMeter = require '../viz/DataStoreUsageMeter.cjsx'
 DataBucketsList     = require '../lists/DataBucketsList.cjsx'
@@ -15,21 +15,27 @@

     <Split flex='left' priority='left'>
       <Article>
-          <Header fixed=true pad='medium'>
-            <Title responsive=true truncate=true>Storage Buckets</Title>
-            <Search onDOMChange={@userSearch} placeHolder='Search...' inline=true fill=true size='medium' />
+          <Header fixed=true pad='medium' justify='between'>
+            <Box justify='left' direction='row'>
+              <Title responsive=true truncate=true>Storage Buckets</Title>
+              <Search onDOMChange={@userSearch} placeHolder='Search...' flex=true inline=true iconAlign='start' size='medium' />
+            </Box>
+            <DataStoreUsageMeter used={ds.used} free={ds.free} />
           </Header>
+          <Box align='center'></Box>
           <DataBucketsList buckets={@props.buckets} />
       </Article>
       <Sidebar size='medium' colorIndex='light-2' direction='column'>
         <Box align='center' justify='center' direction='column'>
           <Header pad='medium' size='large' direction='column'>
-            <Title>Data Store</Title>
+            <Title><Icons.Base.Info /></Title>
             <Paragraph align='center'>
-              Overall storage capacity for the storage buckets.
+              Storage buckets hold the data of the instances.
+              Buckets can be copied and deleted.
+              This page lists all storage buckets in your data store.
             </Paragraph>
           </Header>
-          <DataStoreUsageMeter used={ds.used} free={ds.free} />
+
         </Box>
       </Sidebar>
     </Split>


S49819: Remove "Enable scrolling backwards and forwards in time on Iteration Planning Board" toggle.

--- iterationplanningboard/IterationPlanningBoardAppSpec.coffee
+++ iterationplanningboard/IterationPlanningBoardAppSpec.coffee
@@ -124,8 +124,7 @@
       expect(iterationJson._ref).toEqual column.getValue()

   beforeEach ->
-    @stub(Rally.alm.FeatureToggle, 'isEnabled').withArgs("SCROLLING_ON_CARDBOARD").returns(true)
-      .withArgs('ENABLE_SLIM_CARD_DESIGN').returns(true)
+    @stub(Rally.alm.FeatureToggle, 'isEnabled').withArgs('ENABLE_SLIM_CARD_DESIGN').returns(true)

     @ajax.whenQuerying('userstory').respondWith()
     @ajax.whenQuerying('defect').respondWith()


:bug: Fix missing markers patching on view attachment

--- lib/minimap-find-results-view.coffee
+++ lib/minimap-find-results-view.coffee
@@ -18,6 +18,7 @@

       if minimap?
         minimap.miniOverlayer.append(this)
+        @patchMarkers()

     getEditor: ->
       activeView = atom.workspaceView.getActiveView()
@@ -34,6 +35,9 @@
     markersUpdated: (markers) ->
       minimap = @getMinimap()
       super(markers)
+      @patchMarkers()
+
+    patchMarkers: ->
       for k,marker of @markerViews
         marker.intersectsRenderedScreenRows = (range) ->
           return false unless minimap?


Closes #4322; Open new direct rooms correctly

--- packages/rocketchat-lib/client/lib/openRoom.coffee
+++ packages/rocketchat-lib/client/lib/openRoom.coffee
@@ -21,6 +21,7 @@
 				if type is 'd'
 					Meteor.call 'createDirectMessage', name, (err) ->
 						if !err
+							RoomManager.close(type + name)
 							openRoom('d', name)
 						else
 							Session.set 'roomNotFound', {type: type, name: name}


Remove unnecessary commas

Remove unnecessary commas

--- lib/providers/coffee-provider.coffee
+++ lib/providers/coffee-provider.coffee
@@ -34,8 +34,8 @@
   selector: [
     'source.coffee'
     'source.litcoffee'
-    'text.plain.null-grammar',
-    'source.embedded.coffee',
+    'text.plain.null-grammar'
+    'source.embedded.coffee'
     'source.coffee.embedded.html'
   ]
   compiledScope: 'source.js'


Rollback existing tooltip during page load instead

Manual navigations don't trigger `before-cache`.

--- resources/assets/coffee/_classes/tooltip-default.coffee
+++ resources/assets/coffee/_classes/tooltip-default.coffee
@@ -5,7 +5,7 @@
   constructor: ->
     $(document).on 'mouseover touchstart', '[title]:not(iframe)', @onMouseOver
     $(document).on 'mouseenter touchstart', '.u-ellipsis-overflow, .u-ellipsis-overflow-desktop, .u-ellipsis-pre-overflow', @autoAddTooltip
-    $(document).on 'turbolinks:before-cache', @rollback
+    $(document).on 'turbolinks:load', @rollback

   onMouseOver: (event) =>


refactoring

refactoring

--- public/src/figures/MultipleObjectsIntersection.coffee
+++ public/src/figures/MultipleObjectsIntersection.coffee
@@ -10,7 +10,9 @@
     [i21, i22] = i2
     [i21, i22] = [i22, i21] if i21 > i22

-    return if i21 < i11 then @figure1.norm(intersectionPoint, ray) else @figure2.norm(intersectionPoint, ray)
+    f = if i21 < i11 then @figure1 else @figure2
+
+    return f.norm(intersectionPoint, ray)

   solutions: (ray) ->
     i = this.intersection(ray)


missing import

missing import

--- resources/assets/coffee/react/comments-show/main.coffee
+++ resources/assets/coffee/react/comments-show/main.coffee
@@ -17,9 +17,12 @@
 ###

 import { Comment } from 'comment'
+import core from 'osu-core-singleton'
 import * as React from 'react'
 import { a, button, div, h1, li, ol, p, span } from 'react-dom-factories'
 el = React.createElement
+
+store = core.dataStore.commentStore

 export class Main extends React.PureComponent
   render: =>


Rename show-replace keybinding

Rename show-replace keybinding

--- keymaps/find-and-replace.cson
+++ keymaps/find-and-replace.cson
@@ -3,7 +3,7 @@

 '.editor':
   'meta-f': 'find-and-replace:show'
-  'alt-meta-f': 'find-and-replace:display-replace'
+  'alt-meta-f': 'find-and-replace:show-replace'

   'meta-g': 'find-and-replace:find-next'
   'meta-G': 'find-and-replace:find-previous'


update WorkspacePage name for consistency

--- src/scripts/pages/workspace/workspace.coffee
+++ src/scripts/pages/workspace/workspace.coffee
@@ -6,7 +6,7 @@
   template = require('hbs!./workspace-template')
   require('less!./workspace')

-  return class MePage extends BaseView
+  return class WorkspacePage extends BaseView
     template: template
     pageTitle: 'My Workspace'


added random ticket number to groove tickets

--- services/web/public/coffee/main/universties-site.coffee
+++ services/web/public/coffee/main/universties-site.coffee
@@ -12,12 +12,13 @@
 				console.log "email not set"
 				return
 			$scope.sending = true
+			ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32)
 			params =
 				name: $scope.form.name || $scope.form.email
 				email: $scope.form.email
 				labels: $scope.form.source
 				message: "Please contact me with more details"
-				subject: $scope.form.subject
+				subject: $scope.form.subject + " - [#{ticketNumber}]"
 				about : "#{$scope.form.position || ''} #{$scope.form.university || ''}"

 			Groove.createTicket params, (err, json)->


Regex bugfix for wiselinks select tag.

Regex bugfix for wiselinks select tag.

--- lib/assets/javascripts/_select.js.coffee
+++ lib/assets/javascripts/_select.js.coffee
@@ -18,7 +18,7 @@

   _add_param_to_url: (url, param, value) ->
     link = this._create_link(url)
-    result = new RegExp(param + '=([^&]*)', 'i').exec(link.search);
+    result = new RegExp('[\?&]' + param + '=([^&]*)', 'i').exec(link.search);
     result = result && result[1] || '';
     if result == ''
       opt = {}; opt[param] = value