[replays] speed control changes speed of battle

--- client/app/js/views/battles/battle_view.coffee
+++ client/app/js/views/battles/battle_view.coffee
@@ -28,6 +28,7 @@
     @chatView = null
     @lastMove = null
     @skip     = null
+    @speed    = 1
     @render()
     @listenTo(@model, 'change:teams[*].pokemon[*].status', @handleStatus)
     @listenTo(@model, 'change:teams[*].pokemon[*].percent', @handlePercent)
@@ -295,12 +296,12 @@
       $pokeball.css(opacity: 0)
       $newSprite
         .transition(y: -15, scale: .1, 0)
-        .transition(scale: 1, 250)
-        .transition y: 0, 250, 'out', =>
+        .transition(scale: 1, 250 * @speed)
+        .transition y: 0, 250 * @speed, 'out', =>
           @removePokeball($pokeball)
-      setTimeout(done, 500)
+      setTimeout(done, 500 * @speed)
       $newPokemon.transition(opacity: 1)
-    setTimeout(releasePokemon, 250)
+    setTimeout(releasePokemon, 250 * @speed)

   switchOut: (player, slot, done) =>
     $pokemon = @$pokemon(player, slot)
@@ -315,12 +316,12 @@

     width = $sprite.width()
     height = $sprite.height()
-    $sprite.transition(scale: 0.1, x: width >> 1, y: height, 150)
-    $pokemon.transition opacity: 0, 250, ->
+    $sprite.transition(scale: 0.1, x: width >> 1, y: height, 150 * @speed)
+    $pokemon.transition opacity: 0, 250 * @speed, ->
       $pokemon.addClass('hidden').css(opacity: 1)
       $sprite.popover('destroy')
       $sprite.transition(scale: 1, x: 0, y: 0, 0)
-    setTimeout(done, 250)
+    setTimeout(done, 250 * @speed)

   makePokeball: (x, y) =>
     $pokeball = $("""<div class="pokeball"/>""")
@@ -367,56 +368,56 @@
     scale = (if front then 1.3 else 1/1.3)
     if moveName == 'Earthquake'
       $attacker.add($defender).each (index) ->
-        $(this).transition(x: -75, 62.5, 'easeInOutCubic')
-          .transition(x: 75, 125, 'easeInOutCubic')
-          .transition(x: -30, 125, 'easeInOutCubic')
-          .transition(x: 30, 125, 'easeInOutCubic')
-          .transition(x: 0, 62.5, 'easeInOutCubic')
-      setTimeout(done, 500)
+        $(this).transition(x: -75, 62.5 * @speed, 'easeInOutCubic')
+          .transition(x: 75, 125 * @speed, 'easeInOutCubic')
+          .transition(x: -30, 125 * @speed, 'easeInOutCubic')
+          .transition(x: 30, 125 * @speed, 'easeInOutCubic')
+          .transition(x: 0, 62.5 * @speed, 'easeInOutCubic')
+      setTimeout(done, 500 * @speed)
     else if 'contact' in moveData.flags
       # Simple attack animation
       # Tackling the opponent
       $attacker
-        .transition(x: dx - ax, y: dy - ay, scale: scale, 250, 'in')
-        .transition(x: 0, y: 0, scale: 1, 250, 'out')
-      $defender.delay(400)
-        .transition(x: (if front then -16 else 8), 50, 'easeOutCubic')
-        .transition(x: 0, 50, 'easeInOutCubic')
-      setTimeout(done, 500)
+        .transition(x: dx - ax, y: dy - ay, scale: scale, 250 * @speed, 'in')
+        .transition(x: 0, y: 0, scale: 1, 250 * @speed, 'out')
+      $defender.delay(400 * @speed)
+        .transition(x: (if front then -16 else 8), 50 * @speed, 'easeOutCubic')
+        .transition(x: 0, 50 * @speed, 'easeInOutCubic')
+      setTimeout(done, 500 * @speed)
     else if moveData['power'] > 0
       # Non-contact attacking move
       # Projectile
       $projectile = @$projectile(player, slot, moveData)
       [transX, transY] = [(dx - ax), (dy - ay)]
       $projectile
-        .transition(x: transX / 2, y: transY / 2, scale: (scale + 1) / 2, 200, 'easeOutCubic')
-        .transition(x: transX, y: transY, scale: scale, 200, 'easeOutCubic')
-        .transition(opacity: 0, 100, -> $projectile.remove())
-      $defender.delay(400)
-        .transition(x: -4, 0, 'linear').delay(50)
-        .transition(x: 4, 0, 'linear').delay(50)
+        .transition(x: transX / 2, y: transY / 2, scale: (scale + 1) / 2, 200 * @speed, 'easeOutCubic')
+        .transition(x: transX, y: transY, scale: scale, 200 * @speed, 'easeOutCubic')
+        .transition(opacity: 0, 100 * @speed, -> $projectile.remove())
+      $defender.delay(400 * @speed)
+        .transition(x: -4, 0, 'linear').delay(50 * @speed)
+        .transition(x: 4, 0, 'linear').delay(50 * @speed)
         .transition(x: 0, 0, 'linear')
-      setTimeout(done, 500)
+      setTimeout(done, 500 * @speed)
     else if player != targetPlayer || slot != targetSlot
       # This is a non-attacking move that affects another pokemon
       # S-shaped movement
       $projectile = @$projectile(player, slot, moveData)
       [transX, transY] = [(dx - ax), (dy - ay)]
       $projectile
-        .transition(x: transX * 2 / 3, y: transY / 3, 150, 'easeInOutSine')
-        .transition(x: transX / 3, y: transY * 2 / 3, 100, 'easeInOutSine')
-        .transition(x: transX, y: transY, 150, 'easeInOutSine')
-        .transition opacity: 0, 100, 'easeInOutSine', ->
+        .transition(x: transX * 2 / 3, y: transY / 3, 150 * @speed, 'easeInOutSine')
+        .transition(x: transX / 3, y: transY * 2 / 3, 100 * @speed, 'easeInOutSine')
+        .transition(x: transX, y: transY, 150 * @speed, 'easeInOutSine')
+        .transition opacity: 0, 100 * @speed, 'easeInOutSine', ->
           $projectile.remove()
-      setTimeout(done, 500)
+      setTimeout(done, 500 * @speed)
     else
       # Side-to-side movement
       $attacker = @$sprite(player, slot)
       $attacker
-        .transition(x: -16, 125, 'easeInOutSine')
-        .transition(x: 16, 250, 'easeInOutSine')
-        .transition(x: 0, 125, 'easeInOutSine')
-      setTimeout(done, 500)
+        .transition(x: -16, 125 * @speed, 'easeInOutSine')
+        .transition(x: 16, 250 * @speed, 'easeInOutSine')
+        .transition(x: 0, 125 * @speed, 'easeInOutSine')
+      setTimeout(done, 500 * @speed)

   cannedText: (cannedString, args...) =>
     @parseCannedText(CannedText[cannedString], args, ->)
@@ -434,7 +435,7 @@
     switch cannedTextName
       when 'MOVE_MISS', 'JUMP_KICK_MISS', 'MOVE_FAIL', 'IMMUNITY'
         @addSummary(cannedText)
-        setTimeout(done, 500)
+        setTimeout(done, 500 * @speed)
       when 'PARALYZE_CONTINUE', 'FREEZE_CONTINUE', 'SLEEP_CONTINUE',\
             'SUN_END', 'RAIN_END', 'SAND_END', 'HAIL_END',\
             'SAND_CONTINUE', 'HAIL_CONTINUE'
@@ -488,10 +489,12 @@
     $ability.addClass((if isFront then 'front' else 'back'))
     $ability.width(1)
     $ability.appendTo(@$('.battle_pane'))
-    $ability.transition opacity: 1, width: 150, 100, 'easeInQuad', ->
-      $ability.delay(3000).transition opacity: 0, 300, 'easeInQuad', ->
+    $ability
+      .transition(opacity: 1, width: 150, 100 * @speed, 'easeInQuad')
+      .delay(3000)
+      .transition opacity: 0, 300 * @speed, 'easeInQuad', ->
         $ability.remove()
-    setTimeout(done, 500)
+    setTimeout(done, 500 * @speed)

   cancelSuccess: (done) =>
     @enableButtons()
@@ -510,21 +513,21 @@
       done()
       return

-    $sprite.fadeOut 200, =>
+    $sprite.fadeOut 200 * @speed, =>
       @renderPokemon $spriteContainer, ($image) ->
         $sprite.popover('destroy')
         $sprite.remove()
-        $image.hide().fadeIn(200)
+        $image.hide().fadeIn(200 * @speed)

     # Don't waste time changing the sprite if we can't see it.
     if @model.getPokemon(player, slot).isFainted()
       done()
     else
-      setTimeout(done, 400)
+      setTimeout(done, 400 * @speed)

   changeWeather: (newWeather, done) =>
     $overlays = @$('.battle_overlays')
-    $overlays.find('.weather').transition(opacity: 0, 500, -> $(this).remove())
+    $overlays.find('.weather').transition(opacity: 0, 500 * @speed, -> $(this).remove())
     [overlayWidth, overlayHeight] = [600, 300]
     $weather = switch newWeather
       when Weather.RAIN
@@ -585,7 +588,7 @@
         $overlays.append($weather)
         $weather
       else $()
-    $weather.transition(opacity: 1, 500)
+    $weather.transition(opacity: 1, 500 * @speed)
     done()

   attachPokemon: (player, slot, attachment, done) =>
@@ -611,11 +614,11 @@
           setTimeout ->
             $image
               .transition(y: -yOffset, 0)
-              .transition(y: 0, 200, 'easeInQuad')
-              .transition(y: -yOffset >> 3, 100, 'easeOutQuad')
-              .transition(y: 0, 100, 'easeInQuad')
+              .transition(y: 0, 200 * @speed, 'easeInQuad')
+              .transition(y: -yOffset >> 3, 100 * @speed, 'easeOutQuad')
+              .transition(y: 0, 100 * @speed, 'easeInQuad')
           , 0
-          setTimeout(done, 500)
+          setTimeout(done, 500 * @speed)
       when 'ConfusionAttachment'
         @addPokemonEffect($pokemon, "confusion", "Confusion")
         @addLog("#{pokemon.escape('name')} became confused!")
@@ -666,10 +669,10 @@
           done()
         else
           $div.css(top: oldY, left: oldX, opacity: 0)
-          $div.animate(top: newY, left: newX, opacity: 1, 500)
-            .delay(1000).animate(opacity: .5)
+          $div.transition(top: newY, left: newX, opacity: 1, 500 * @speed)
+            .delay(1000 * @speed).transition(opacity: .5)
           $battlePane.prepend($div)
-          setTimeout(done, 500)
+          setTimeout(done, 500 * @speed)
       when "ToxicSpikesAttachment"
         $div = $("<div/>").addClass("field-#{player} team-toxic-spikes")
         previousLayers = @$(".field-#{player}.team-toxic-spikes").length
@@ -697,10 +700,10 @@
           done()
         else
           $div.css(top: oldY, left: oldX, opacity: 0)
-          $div.animate(top: newY, left: newX, opacity: 1, 500)
-            .delay(1000).animate(opacity: .5)
+          $div.transition(top: newY, left: newX, opacity: 1, 500 * @speed)
+            .delay(1000 * @speed).transition(opacity: .5)
           $battlePane.prepend($div)
-          setTimeout(done, 500)
+          setTimeout(done, 500 * @speed)
       when "SpikesAttachment"
         $div = $("<div/>").addClass("field-#{player} team-spikes")
         previousLayers = @$(".field-#{player}.team-spikes").length
@@ -732,10 +735,10 @@
           done()
         else
           $div.css(top: oldY, left: oldX, opacity: 0)
-          $div.animate(top: newY, left: newX, opacity: 1, 500)
-            .delay(1000).animate(opacity: .5)
+          $div.transition(top: newY, left: newX, opacity: 1, 500 * @speed)
+            .delay(1000 * @speed).transition(opacity: .5)
           $battlePane.prepend($div)
-          setTimeout(done, 500)
+          setTimeout(done, 500 * @speed)
       when "StickyWebAttachment"
         $div = $("<div/>").addClass("field-#{player} team-sticky-web")
         if isFront
@@ -750,10 +753,10 @@
           done()
         else
           $div.css(top: oldY, left: oldX, opacity: 0)
-          $div.animate(top: newY, left: newX, opacity: 1, 1000, 'easeOutElastic')
-            .delay(1000).animate(opacity: .2)
+          $div.animate(top: newY, left: newX, opacity: 1, 1000 * @speed, 'easeOutElastic')
+            .delay(1000 * @speed).animate(opacity: .2)
           $battlePane.prepend($div)
-          setTimeout(done, 500)
+          setTimeout(done, 500 * @speed)
       when "ReflectAttachment"
         @cannedText('REFLECT_START', player)
         @attachScreen(player, 'blue', 10, done)
@@ -783,9 +786,9 @@
       done()
     else
       $screen
-        .transition(width: finalSize, x: -halfSize, 250, 'easeInOutCubic')
-        .transition(height: finalSize, y: -halfSize, 250, 'easeInOutCubic')
-      setTimeout(done, 500)
+        .transition(width: finalSize, x: -halfSize, 250 * @speed, 'easeInOutCubic')
+        .transition(height: finalSize, y: -halfSize, 250 * @speed, 'easeInOutCubic')
+      setTimeout(done, 500 * @speed)

   unattachScreen: (player, slot, klass, done=->) =>
     if arguments.length == 3
@@ -793,7 +796,7 @@
     selector = ".team-screen.#{klass}.field-#{player}"
     selector += ".slot-#{slot}"  if slot
     $selector = @$(selector)
-    $selector.fadeOut(500, -> $selector.remove())
+    $selector.fadeOut(500 * @speed, -> $selector.remove())
     done()

   boost: (player, slot, deltaBoosts, options = {}) =>
@@ -884,9 +887,9 @@
           $substitute.remove()
           done()
         else
-          $substitute.transition y: 300, opacity: 0, 300, ->
+          $substitute.transition y: 300, opacity: 0, 300 * @speed, ->
             $substitute.remove()
-          setTimeout(done, 300)
+          setTimeout(done, 300 * @speed)
       when 'ProtectAttachment', 'KingsShieldAttachment', 'SpikyShieldAttachment'
         @unattachScreen(player, slot, 'pink', done)
       when 'Air Balloon'
@@ -1149,6 +1152,9 @@
   returnToLobby: =>
     PokeBattle.navigation.focusLobby()

+  changeBattleSpeed: (e) =>
+    @speed = Number($(e.currentTarget).val()) || 1
+
   $pokemon: (player, slot) =>
     if arguments.length == 1
       pokemon = player
@@ -1187,10 +1193,10 @@
       done()
       return

-    $sprite.transition y: 100, opacity: 0, 250, 'ease-in', ->
+    $sprite.transition y: 100, opacity: 0, 250 * @speed, 'ease-in', ->
       $sprite.popover('destroy')
       $sprite.remove()
-    setTimeout(done, 250)
+    setTimeout(done, 250 * @speed)
     @renderUserInfo()

   resetPopovers: =>


video: model add ensurePublishing

video: model add ensurePublishing

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -278,6 +278,25 @@

   ###*
+   * Ensures that user is publishing before executing the callback.
+   *
+   * @param {object} options
+   * @param {function} callback
+  ###
+  ensurePublishing: (options, callback) ->
+    return callback()  if @state.publishing
+
+    successCb = =>
+      @handlePublishSuccess publisher
+      callback()
+
+    @setState options
+    @startPublishing options,
+      success : successCb
+      error   : (err) ->
+
+
+  ###*
    * Handler for successful video publishing. It transforms and registers given
    * `OT.Publisher` instance. Sets active participant to user.
    *


Remove trailing whitespaces

Remove trailing whitespaces

--- client/landing/app/CommonViews/LikeView.coffee
+++ client/landing/app/CommonViews/LikeView.coffee
@@ -74,7 +74,6 @@
         @_lastUpdatedCount = likes.length

   click:(event)->
-
     if $(event.target).is("a.action-link")
       if KD.isLoggedIn()
         @getData().like (err)=>


Hide resolve marker if no change

Hide resolve marker if no change

--- resources/assets/coffee/react/beatmap-discussions/discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussion.coffee
@@ -52,6 +52,8 @@

     lineClasses = "#{bn}__line"
     lineClasses += " #{bn}__line--resolved" if @props.discussion.resolved
+
+    lastResolvedState = false

     div
       className: topClasses
@@ -82,7 +84,12 @@
           className: "#{bn}__expanded #{'hidden' if @state.collapsed}"
           div
             className: "#{bn}__replies"
-            @props.discussion.beatmap_discussion_posts.slice(1).map (reply) =>
+            for reply in @props.discussion.beatmap_discussion_posts.slice(1)
+              if reply.system && reply.message.type == 'resolved'
+                currentResolvedState = reply.message.value
+                continue if lastResolvedState == currentResolvedState
+                lastResolvedState = currentResolvedState
+
               @post reply, 'reply'

           el BeatmapDiscussions.NewReply,


Update load-markdown-documents.coffee

Update load-markdown-documents.coffee

--- documentation/app/scripts/modules/load-markdown-documents.coffee
+++ documentation/app/scripts/modules/load-markdown-documents.coffee
@@ -9,14 +9,16 @@
   apps: require("../../documents/api/apps.md")
   create_a_credit_example: require("../../documents/api/create-a-credit-example.md")
   create_a_credit: require("../../documents/api/create-a-credit.md")
+  create_a_credit_order: require("../../documents/api/create-a-credit-order.md")
+  create_a_credit_order_example: require("../../documents/api/create-a-credit-order-example.md")
   create_a_debit_example: require("../../documents/api/create-a-debit-example.md")
   create_a_debit: require("../../documents/api/create-a-debit.md")
+  create_a_debit_order: require("../../documents/api/create-a-debit-order.md")
+  create_a_debit_order_example: require("../../documents/api/create-a-debit-order-example.md")
   create_a_mv_delegate_example: require("../../documents/api/create-a-mv-delegate-example.md")
   create_a_mv_delegate: require("../../documents/api/create-a-mv-delegate.md")
   create_a_mv_example: require("../../documents/api/create-a-mv-example.md")
   create_a_mv: require("../../documents/api/create-a-mv.md")
-  create_a_redeem_order: require("../../documents/api/create-a-redeem-order.md")
-  create_a_redeem_order_example: require("../../documents/api/create-a-redeem-order-example.md")
   create_a_report_example: require("../../documents/api/create-a-report-example.md")
   create_a_report_instance_example: require("../../documents/api/create-a-report-instance-example.md")
   create_a_report_instance: require("../../documents/api/create-a-report-instance.md")
@@ -25,14 +27,16 @@
   create_account_credentials: require("../../documents/api/create-account-credentials.md")
   create_an_app_example: require("../../documents/api/create-an-app-example.md")
   create_an_app: require("../../documents/api/create-an-app.md")
-  create_an_earn_order: require("../../documents/api/create-an-earn-order.md")
-  create_an_earn_order_example: require("../../documents/api/create-an-earn-order-example.md")
   create_an_order_example: require("../../documents/api/create-an-order-example.md")
   create_an_order: require("../../documents/api/create-an-order.md")
   create_sandbox_permissions_example: require("../../documents/api/create-sandbox-permissions-example.md")
   create_sandbox_permissions: require("../../documents/api/create-sandbox-permissions.md")
+  credit_orders: require("../../documents/api/credit-orders.md")
+  credit_order_example: require("../../documents/api/credit-order-example.md")
   credits_example: require("../../documents/api/credits-example.md")
   credits: require("../../documents/api/credits.md")
+  debit_orders: require("../../documents/api/debit-orders.md")
+  debit_order_example: require("../../documents/api/debit-order-example.md")
   debits_example: require("../../documents/api/debits-example.md")
   debits: require("../../documents/api/debits.md")
   delete_account_credentials_example: require("../../documents/api/delete-account-credentials-example.md")
@@ -40,8 +44,6 @@
   delete_sandbox_permissions_example: require("../../documents/api/delete-sandbox-permissions-example.md")
   delete_sandbox_permissions: require("../../documents/api/delete-sandbox-permissions.md")
   documentation_overview: require("../../documents/api/documentation-overview.md")
-  earn_orders: require("../../documents/api/earn-orders.md")
-  earn_orders_example: require("../../documents/api/earn-orders-example.md")
   get_a_credit_example: require("../../documents/api/get-a-credit-example.md")
   get_a_credit: require("../../documents/api/get-a-credit.md")
   get_a_debit_example: require("../../documents/api/get-a-debit-example.md")
@@ -114,8 +116,6 @@
   mv_request_schema: require("../../documents/api/mv-request-schema.md")
   orders_example: require("../../documents/api/orders-example.md")
   orders: require("../../documents/api/orders.md")
-  redeem_orders: require("../../documents/api/redeem-orders.md")
-  redeem_orders_example: require("../../documents/api/redeem-orders-example.md")
   release_notes: require("../../documents/api/release-notes.md")
   report_instances_example: require("../../documents/api/report-instances-example.md")
   report_instances: require("../../documents/api/report-instances.md")


Fix small bug sendind data to analytics

Fix small bug sendind data to analytics

--- coffee/subscriptions.controller.coffee
+++ coffee/subscriptions.controller.coffee
@@ -88,7 +88,7 @@

         promise = @subscriptionsService.fetchPublicPlans()
         promise.then () =>
-            @analytics.addEcStep("change-plan", @.myPlan.current_plan.plan_id, null)
+            @analytics.addEcStep("change-plan", @.myPlan?.current_plan?.plan_id, null)
             @.loadingRecommendedPlan = false
             @lightboxService.open('tg-lb-plans')
             @.selectedPlan = 'valid'
@@ -99,7 +99,7 @@
         @.loading = false
         @.selectedPlan = false
         counter = 1
-        @analytics.addEcStep("change-plan", @.myPlan.current_plan.plan_id, null)
+        @analytics.addEcStep("change-plan", @.myPlan?.current_plan?.plan_id, null)
         @analytics.setEcAction("add", "Plans list")
         @lightboxService.open('tg-lb-plans')


Quick fix for spectate zoom not working on Escort Duty

--- app/views/play/SpectateView.coffee
+++ app/views/play/SpectateView.coffee
@@ -203,6 +203,9 @@
     @surface.camera.setBounds(bounds)
     zoom = =>
       @surface.camera.zoomTo({x: (worldBounds.right - worldBounds.left) / 2, y: (worldBounds.top - worldBounds.bottom) / 2}, 0.1, 0)
+      if @level.get('name') is 'Escort Duty'
+        @surface.camera.setBounds [{x: -7, y: -5}, {x: 87, y: 75}]
+        @surface.camera.zoomTo null, 0.1, 0
     _.delay zoom, 4000  # call it later for some reason (TODO: figure this out)

   findPlayerNames: ->


Fix tests to add new Features dependency

--- services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
+++ services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
@@ -72,6 +72,8 @@
 		@Modules =
 			hooks:
 				fire: sinon.stub()
+		@Features =
+			hasFeature: sinon.stub()
 		@ProjectController = SandboxedModule.require modulePath, requires:
 			"settings-sharelatex":@settings
 			"logger-sharelatex":
@@ -102,6 +104,7 @@
 			"../../infrastructure/Modules": @Modules
 			"./ProjectEntityHandler": @ProjectEntityHandler
 			"../Errors/Errors": Errors
+			"../../Infrastructure/Features": @Features

 		@projectName = "£12321jkj9ujkljds"
 		@req =


client: avatararea: refactored for readability and different states

--- client/app/lib/avatararea/avatararea.coffee
+++ client/app/lib/avatararea/avatararea.coffee
@@ -21,79 +21,146 @@

     super options, data

+    if isKoding()
+      if isSoloProductLite()
+      then @createLiteSoloViews()
+      else @createSoloViews()
+    else
+      @createTeamViews()
+
+
+  createLiteSoloViews: ->
+
     { mainView } = kd.singletons
     account      = @getData()
-    {profile} = @getData()

-    href = if isSoloProductLite() then '/#' else "/#{profile.nickname}"
+    @accountPopup = new AccountPopup
+
+    @avatar = new AvatarStaticView
+      cssClass   : 'avatar-image-wrapper'
+      attributes :
+        title    : 'View your notifications and account settings'
+      size       :
+        width    : 25
+        height   : 25
+    , account

     @profileName = new JCustomHTMLView
       tagName    : 'a'
       cssClass   : 'profile'
       attributes :
-        href     : href
+        href     : '#'
         title    : 'Your profile'
       pistachio  : '{{ #(profile.firstName) }}'
     , account
-
-    @accountPopup = new AccountPopup
-
-    if isSoloProductLite()
-      @notificationsIcon = new KDCustomHTMLView { cssClass: 'hidden' }
-    else
-      @notificationsPopup = new PopupNotifications
-        cssClass : if isKoding() then 'notification-list' else 'notification-list team'
-
-      @notificationsIcon = new AvatarAreaIconLink
-        cssClass   : 'notifications acc-notification-icon'
-        attributes :
-          title    : 'Notifications'
-      helpers.makePopupButton @notificationsIcon, @notificationsPopup

     @accountIcon = new AvatarAreaIconLink
       cssClass   : 'acc-dropdown-icon'
       attributes :
         title    : 'Account'
         testpath : 'AvatarAreaIconLink'
+
     helpers.makePopupButton @accountIcon, @accountPopup

-    if isKoding() and not isSoloProductLite()
-      @avatar = new AvatarView
-        cssClass   : 'avatar-image-wrapper'
-        attributes :
-          title    : 'View your public profile'
-        size       :
-          width    : 25
-          height   : 25
-      , account
-    else
-      @avatar = new AvatarStaticView
-        cssClass   : 'avatar-image-wrapper'
-        attributes :
-          title    : 'View your notifications and account settings'
-        size       :
-          width    : 25
-          height   : 25
-      , account
-      helpers.makePopupButton @avatar, @notificationsPopup
+    @once 'viewAppended', -> mainView.addSubView @accountPopup

-    @on 'viewAppended', ->
+  createSoloViews: ->

+    { mainView } = kd.singletons
+    account      = @getData()
+    { profile }  = account
+
+    @notificationsPopup = new PopupNotifications
+      cssClass : 'notification-list'
+
+    @accountPopup = new AccountPopup
+
+    @avatar = new AvatarView
+      cssClass   : 'avatar-image-wrapper'
+      attributes :
+        title    : 'View your public profile'
+      size       :
+        width    : 25
+        height   : 25
+    , account
+
+    @profileName = new JCustomHTMLView
+      tagName    : 'a'
+      cssClass   : 'profile'
+      attributes :
+        href     : "/#{profile.nickname}"
+        title    : 'Your profile'
+      pistachio  : '{{ #(profile.firstName) }}'
+    , account
+
+    @notificationsIcon = new AvatarAreaIconLink
+      cssClass   : 'notifications acc-notification-icon'
+      attributes :
+        title    : 'Notifications'
+        href     : '#'
+
+    @accountIcon = new AvatarAreaIconLink
+      cssClass   : 'acc-dropdown-icon'
+      attributes :
+        title    : 'Account'
+        testpath : 'AvatarAreaIconLink'
+
+    helpers.makePopupButton @notificationsIcon, @notificationsPopup
+    helpers.makePopupButton @accountIcon, @accountPopup
+
+    @once 'viewAppended', ->
       mainView.addSubView @accountPopup
+      mainView.addSubView @notificationsPopup
+      @bindNotificationsPopupEvents()

-      return  if isSoloProductLite()

+  createTeamViews: ->
+
+    { mainView } = kd.singletons
+    account      = @getData()
+
+    @notificationsPopup = new PopupNotifications
+      cssClass : 'notification-list team'
+
+    @avatar = new AvatarStaticView
+      cssClass   : 'avatar-image-wrapper'
+      attributes :
+        title    : 'View your notifications and account settings'
+      size       :
+        width    : 25
+        height   : 25
+    , account
+
+    @notificationsIcon = new AvatarAreaIconLink
+      cssClass   : 'notifications acc-notification-icon'
+      attributes :
+        title    : 'Notifications'
+        href     : '#'
+
+    helpers.makePopupButton @avatar, @notificationsPopup
+
+    @once 'viewAppended', ->
       mainView.addSubView @notificationsPopup
+      @bindNotificationsPopupEvents()

-      @notificationsPopup.on 'NotificationCountDidChange', (count)=>
-        kd.utils.killWait @notificationsPopup.loaderTimeout
-        @notificationsIcon.updateCount count
+
+  bindNotificationsPopupEvents: ->
+
+    @notificationsPopup.on 'NotificationCountDidChange', (count)=>
+      kd.utils.killWait @notificationsPopup.loaderTimeout
+      @notificationsIcon.updateCount count

   pistachio: ->

-    if isKoding()
+    if isKoding() and isSoloProductLite()
+      """
+      {{> @avatar}}
+      {{> @profileName}}
+      {{> @accountIcon}}
+      """
+    else if isKoding()
       """
       {{> @avatar}}
       {{> @profileName}}
@@ -102,7 +169,6 @@
       """
     else
       """
-      {{> @accountIcon}}
       {{> @avatar}}
       {{> @notificationsIcon}}
       """


Don't return unused value

Don't return unused value

--- js-library/app/js/jail_iframe/util/modal.coffee
+++ js-library/app/js/jail_iframe/util/modal.coffee
@@ -35,8 +35,6 @@

     FactlinkJailRoot.showFactlinkCreatedNotification()

-    fct
-
   stopHighlightingFactlink: (id) ->
     $("span.factlink[data-factid=#{id}]").each (i, val) ->
       $(val).contents().unwrap()


Fix initial selected input hint index.

Fix initial selected input hint index.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -337,7 +337,7 @@

       hint

-    hints[0].classList.add 'internalVimiumSelectedInputHint'
+    hints[selectedInputIndex].classList.add 'internalVimiumSelectedInputHint'

     hintContainingDiv = DomUtils.addElementList(hints,
       { id: "vimiumInputMarkerContainer", className: "vimiumReset" })


PassNextKey; fix tests.

PassNextKey; fix tests.

Oversight from 116ac2c2a279f8497ffd5396f43ac4fd7fd5de67.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -530,7 +530,7 @@
   # Subject to internationalization issues since we're using keyIdentifier instead of charCode (in keypress).
   #
   # TOOD(ilya): Revisit this. Not sure it's the absolute best approach.
-  if keyChar == "" &&
+  if not keyChar &&
      (currentCompletionKeys.indexOf(KeyboardUtils.getKeyChar(event)) != -1 ||
       isValidFirstKey(KeyboardUtils.getKeyChar(event)))
     DomUtils.suppressPropagation(event)


Fix color of uneditable name/email in settings

--- .Gruntfile.coffee
+++ .Gruntfile.coffee
@@ -90,6 +90,7 @@
             'input-color':                '@text-color'
             'input-border':               '@gray-dark'
             'input-color-placeholder':    '@gray-light'
+            'input-bg-disabled':          '@gray-dark'
             'legend-color':               '@text-color'
             'legend-border-color':        '@gray-dark'
             'progress-bg':                '@gray-darker'


atom: config entries auto alphabetically ordered

--- .atom/config.cson
+++ .atom/config.cson
@@ -1,8 +1,9 @@
 "*":
-  "exception-reporting":
-    userId: "ff49e55f-cf0d-c4b0-dc96-0290b690d294"
-  welcome:
-    showOnStartup: false
+  "autocomplete-plus": {}
+  "autocomplete-python":
+    addBracketAfterFunction: true
+    addDotAfterModule: true
+    useSnippets: "required"
   build:
     refreshOnShowTargetList: true
     saveOnBuild: true
@@ -17,32 +18,31 @@
       "spell-check"
     ]
   editor:
-    invisibles: {}
     fontFamily: "inconsolata"
     fontSize: 21
+    invisibles: {}
     showInvisibles: true
     showIndentGuide: true
     softWrap: true
-  "linter-pep8": {}
-  "autocomplete-python":
-    addDotAfterModule: true
-    addBracketAfterFunction: true
-    useSnippets: "required"
-  "linter-flake8": {}
+  "exception-reporting":
+    userId: "ff49e55f-cf0d-c4b0-dc96-0290b690d294"
+  "keyboard-localization":
+    useKeyboardLayout: "fr_FR"
   linter:
     lintOnFly: false
+  "linter-flake8": {}
   "linter-pep257": {}
-  "autocomplete-plus": {}
+  "linter-pep8": {}
+  "linter-python-pep257": {}
   "tree-view":
     autoReveal: true
     focusOnReveal: false
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
+  welcome:
+    showOnStartup: false
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
-  "keyboard-localization":
-    useKeyboardLayout: "fr_FR"
-  "linter-python-pep257": {}
 ".csv.semicolon.text":
   editor:
     tabLength: 8


Do not get field `usernames` with room on joinDefaultChannel

--- packages/rocketchat-lib/server/methods/joinDefaultChannels.coffee
+++ packages/rocketchat-lib/server/methods/joinDefaultChannels.coffee
@@ -7,7 +7,7 @@

 		RocketChat.callbacks.run 'beforeJoinDefaultChannels', user

-		RocketChat.models.Rooms.findByDefaultAndTypes(true, ['c', 'p']).forEach (room) ->
+		RocketChat.models.Rooms.findByDefaultAndTypes(true, ['c', 'p'], {fields: {usernames: 0}}).forEach (room) ->

 			# put user in default rooms
 			RocketChat.models.Rooms.addUsernameById room._id, user.username


Add the real number of primaries/secondaries in server/index

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -23,8 +23,8 @@
             #TODO Replace later with server_status
             query = r.db(system_db).table('server_config').merge( (server) ->
                 id: server("uuid")
-                primary_count: 0
-                secondary_count: 0
+                primary_count: r.db(system_db).table('table_config').concatMap( (table) -> table("shards") ).filter( (shard) -> shard("directors").nth(0).eq(server("name")) ).count()
+                secondary_count: r.db(system_db).table('table_config').concatMap( (table) -> table("shards") ).filter( (shard) -> shard("replicas").nth(0).eq(server("name")) ).count()
                 status:
                     reachable: true
                     last_seen: $.timeago(new Date())


fix test

fix test

--- apps/home/test/client.coffee
+++ apps/home/test/client.coffee
@@ -92,7 +92,6 @@
       'featuredShowsTemplate'
       'featuredPostsTemplate'
       'featuredArtistsTemplate'
-      'artworkItemTemplate'
     ]
     mod.__set__ 'HeroUnitView', ->
     sinon.stub Backbone, 'sync'


Bring back deferReadiness(), it was accidentally removed

--- assets/scripts/travis.coffee
+++ assets/scripts/travis.coffee
@@ -35,6 +35,7 @@
 window.Travis = TravisApplication.create(
   LOG_TRANSITIONS: true
 )
+Travis.deferReadiness()

 $.extend Travis,
   run: ->


Whoopss.... :scissors:

Whoopss.... :scissors:

--- app/assets/javascripts/app/app.js.coffee
+++ app/assets/javascripts/app/app.js.coffee
@@ -1,11 +1,11 @@
 angular.module('timeoverflow').controller 'UserListCtrl', ($scope, $modal, $http, $location) ->

   $scope.sortBy = (field) ->
-    $scope.sort = if $scope.sort == field then '-#{field}' else field
+    $scope.sort = if $scope.sort == field then "-#{field}" else field
   $scope.filterTerm = ''
   $scope.$location = $location

-  for prop in ['filterTerm', 'sort']
+  ['filterTerm', 'sort'].map (prop) ->
     Object.defineProperty($scope, prop,
       get: -> $location.search()[prop]
       set: (val) -> $location.search(prop, val || null)


Aufräumen

Aufräumen

--- app/assets/javascripts/login/test.coffee
+++ app/assets/javascripts/login/test.coffee
@@ -1,48 +0,0 @@
-class LoginCtrl
-
-  constructor: (@$log, @UserControlService, @$location, @$alert) ->
-    @$log.debug "LoginCtrl.constructor()"
-    @user = {
-      email: "",
-      password: ""
-    }
-    @warningMessage = {
-      warning: false,
-      message: ""
-    }
-    body = document.getElementsByTagName('body')[0];
-    body.style.background = "#FFFFFF";
-
-
-
-  login: () ->
-    @$log.debug "LoginCtrl.login()"
-
-    @UserControlService.loginUser(@user.email, @user.password)
-    .then(
-      (data) =>
-        @$log.debug "Promise returned #{data} Meetings"
-        @$location.path("/");
-        myAlert = @$alert({title: 'Successfully Logged In!', content: "Welcome", placement: 'top', type: 'success', show: true, duration: 5})
-    ,
-    (error) =>
-      @$log.error "Unable to Login: #{error}"
-      @showWarning("Unable to Login: #{error}")
-    )
-
-  showWarning: (warningMessage) ->
-    @warningMessage = {
-      warning: true,
-      message: warningMessage
-    }
-
-  removeWarning: () ->
-    @warningMessage = {
-      warning: false,
-      message: ""
-    }
-
-
-
-
-controllersModule.controller('LoginCtrl', LoginCtrl)


Fixed pagination of featured

Fixed pagination of featured

--- client/events.coffee
+++ client/events.coffee
@@ -64,16 +64,12 @@

   return event

-get_events = (criteria, projection) ->
-  if not criteria?
-    criteria = {}
-  if not projection?
-    projection = {}
-  _.extend(criteria, {to: {$gte: new Date()}}) if not criteria.to
-
-  skip = projection.skip or parseInt(Session.get("params")?.start) or 0
-  limit = projection.limit or 10
-  _.extend(projection, {sort: {from: 1}, limit: limit, skip: skip})
+get_events = (criteria = {}, projection = {}) ->
+  criteria = _.extend({to: {$gte: new Date()}}, criteria)
+
+  skip = parseInt(Session.get("params")?.start) or 0
+  projection = _.extend({sort: {from: 1}, limit: 10, skip: skip}, projection)
+
   Events.find(criteria, projection)

 Template.sidebar.helpers
@@ -247,7 +243,7 @@

 Template.all.helpers
   'all_events': -> get_events(starred: {$exists: false})
-  'featured_events': -> get_events(starred: {$exists: true}, {skip: 0, limit: 10})
+  'featured_events': -> get_events({starred: {$exists: true}}, {skip: 0, limit: 10})

 # events template
 Template.events.helpers


Motion form: Refactor date handling

Motion form: Refactor date handling

--- app/assets/javascripts/motions.js.coffee
+++ app/assets/javascripts/motions.js.coffee
@@ -4,12 +4,11 @@

 $ ->
   if $("#motion-form").length > 0
-    #** pad out hour to two digits **
     pad2 = ((number) ->
       if number < 10
         '0' + number
       else
-        number
+        number.toString()
     )
     if $("#new-motion").length > 0
       #** New Motion **
@@ -22,15 +21,11 @@
       $("#motion_close_date").val(datetime)
     else
       #** Edit Motion **
-      # TODO: this should be using the Application.timestampToDateObject
-      #       function (main.js)
-      date = $("#motion_close_date").val()
-      date_offset = new Date()
-      offset = date_offset.getTimezoneOffset()/-60
-      day = date.substring(8,10)
-      month = date.substring(5, 7)
-      year = date.substring(2,4)
-      hour = (parseInt(date.substring(11,13), 10) + offset).toString()
+      date = Application.timestampToDateObject($("#motion_close_date").val())
+      year = date.getFullYear().toString().substring(2,4)
+      month = pad2(date.getMonth() + 1)
+      day = pad2(date.getDate())
+      hour = pad2(date.getHours())
       date_string = "#{day}-#{month}-#{year}"
       $("#input_date").datepicker({"dateFormat": "dd-mm-yy"})
       $("#input_date").datepicker("setDate", date_string)


Serialize update and finalize in checksum computation.

--- client/import.coffee
+++ client/import.coffee
@@ -135,9 +135,9 @@
         $set:
           errored: true
           status: error.toString()
-    return
-
-  hash.finalize callback
+      return
+
+    hash.finalize callback

 testPDF = (file, callback) ->
   PDFJS.getDocument(data: file.content, password: '').then callback, (message, exception) ->


routing for beers

routing for beers

--- client/router.coffee
+++ client/router.coffee
@@ -67,6 +67,36 @@
       AccountsEntry.signInRequired(this)
       this.next()

+  @route "ampere",
+    path: "/ampere"
+    onBeforeAction: ->
+      @redirect("/beers/ampere")
+      this.next()
+
+  @route "short-circuit",
+    path: "/short-circuit"
+    onBeforeAction: ->
+      @redirect("/beers/short-circuit")
+      this.next()
+
+  @route "flipswitch",
+    path: "/flipswitch"
+    onBeforeAction: ->
+      @redirect("/beers/flip-switch")
+      this.next()
+
+  @route "wheatstone",
+    path: "/wheatstone"
+    onBeforeAction: ->
+      @redirect("/beers/wheatstone")
+      this.next()
+
+  @route "schottky",
+    path: "/schottky"
+    onBeforeAction: ->
+      @redirect("/beers/schottky")
+      this.next()
+
   # Beers
   @route 'beers',
     path: '/beers/:beer?'


Define Analytics.track method

Define Analytics.track method

--- client/landing/site.landing/coffee/core/analytics.coffee
+++ client/landing/site.landing/coffee/core/analytics.coffee
@@ -4,3 +4,9 @@

     options = { method, data }
     $.ajax "/-/analytics/#{path}", options
+
+
+  @track: (action, properties) ->
+
+    data = { action, properties }
+    request 'track', 'post', data


Nicer UX for color input in browsers that don't support native color pickers

--- app/classes/ColorInput.coffee
+++ app/classes/ColorInput.coffee
@@ -20,13 +20,17 @@
     @_lastValidValue = parts.map((v) -> parseInt(v, 16) / 255 or 0)
     return @_lastValidValue

+  _parseValue: ->
+

   _updateInputElement: (value) ->
-    @_inputElement.value = @_valueToHex value
+    hexValue = @_valueToHex value
+    unless @_inputElement.value.toLowerCase() is hexValue.toLowerCase()
+      @_inputElement.value = hexValue
     return

   _formatValueForUser: (value) ->
-    if @_supported
+    if @_colorInputSupported
       return @_valueToHex value
     else
       return '#RRGGBB'
@@ -34,13 +38,18 @@
   _makeInputElement: ->
     el = document.createElement 'input'
     el.type = 'color'
-    if el.type is 'color'
-      @_supported = true
-      event = 'input'
-    else
-      @_supported = false
-      event = 'change'
-    el.addEventListener event, @_notifyOfValueChange
+    @_colorInputSupported = el.type is 'color'
+
+    el.addEventListener 'input', =>
+      if @_inputElement.value.match(/^\s*#[a-zA-Z0-9]{6}\s*$/)
+        @_notifyOfValueChange()
+      return
+    el.addEventListener 'change', =>
+      unless @_colorInputSupported
+        @_updateInputElement(@_getValue()) # ensure text input is nicely formatted when we've finished
+      @_notifyOfValueChange()
+      return
+
     return el

   _valueToHex: (value) ->


wMinor fixes in default filter (ignore 0 and '') Now is when :stuck_out_tongue:

--- app/coffee/modules/common/filters.coffee
+++ app/coffee/modules/common/filters.coffee
@@ -22,7 +22,7 @@

 defaultFilter = ->
     return (value, defaultValue) ->
-        if value in [null, undefined]
+        if value is [null, undefined]
             return defaultValue
         return value


fix #2385 - refactor wysiwyg list insert

--- app/coffee/modules/common/wisiwyg.coffee
+++ app/coffee/modules/common/wisiwyg.coffee
@@ -24,6 +24,33 @@

 module = angular.module("taigaCommon")

+# How to test lists (-, *, 1.)
+# test it with text after & before the list
+# + is the cursor position
+
+# CASE 1
+# - aa+
+# --> enter
+# - aa
+# - +
+
+# CASE 1
+# - +
+# --> enter
+
+# +
+
+# CASE 3
+# - bb+cc
+# --> enter
+# - bb
+# - cc
+
+# CASE 3
+# +- aa
+# --> enter
+
+# - aa

 #############################################################################
 ## WYSIWYG markitup editor directive
@@ -64,35 +91,52 @@
                     markdown.off(".preview")
                     closePreviewMode()

-        markdownCaretPositon = false
-
-        setCaretPosition = (elm, caretPos) ->
-            if elm.createTextRange
-                range = elm.createTextRange()
-                range.move("character", caretPos)
+        setCaretPosition = (textarea, caretPosition) ->
+            if textarea.createTextRange
+                range = textarea.createTextRange()
+                range.move("character", caretPosition)
                 range.select()

-            else if elm.selectionStart
-                elm.focus()
-                elm.setSelectionRange(caretPos, caretPos)
-
-        removeEmptyLine = (textarea, line, currentCaretPosition) ->
+            else if textarea.selectionStart
+                textarea.focus()
+                textarea.setSelectionRange(caretPosition, caretPosition)
+
+            # Calculate the scroll position
+            totalLines = textarea.value.split("\n").length
+            line = textarea.value[0..(caretPosition - 1)].split("\n").length
+            scrollRelation = line / totalLines
+            $el.scrollTop((scrollRelation * $el[0].scrollHeight) - ($el.height() / 2))
+
+        addLine = (textarea, nline, replace) ->
             lines = textarea.value.split("\n")
-            removedLineLength = lines[line].length
-
-            lines[line] = ""
+
+            if replace
+                lines[nline] = replace + lines[nline]
+            else
+                lines[nline] = ""
+
+            cursorPosition = 0
+
+            for line, key in lines
+                cursorPosition += line.length + 1 || 1
+
+                break if key == nline

             textarea.value = lines.join("\n")

             #return the new position
-            return currentCaretPosition - removedLineLength + 1
+            if replace
+                return cursorPosition - lines[nline].length + replace.length - 1
+            else
+                return cursorPosition

         markdownSettings =
             nameSpace: "markdown"
             onShiftEnter: {keepDefault:false, openWith:"\n\n"}
             onEnter:
-                keepDefault: false
-                replaceWith: (data) =>
+                keepDefault: false,
+                replaceWith: () -> "\n"
+                afterInsert: (data) ->
                     lines = data.textarea.value.split("\n")
                     cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
                     newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
@@ -105,12 +149,9 @@
                         emptyListItem = lastLine.match /^(\s*)\-\s$/

                         if emptyListItem
-                            markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
+                            markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
                         else
-                            breakLineAtBeginning = newLineContent.match /^(\s*)\-\s/
-
-                            if !breakLineAtBeginning
-                                return "\n#{match[1]}" if match
+                            markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1]}")

                     # unordered list *
                     match = lastLine.match /^(\s*\* ).*/
@@ -119,12 +160,9 @@
                         emptyListItem = lastLine.match /^(\s*\* )$/

                         if emptyListItem
-                            markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
+                            markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
                         else
-                            breakLineAtBeginning = newLineContent.match /^(\s*)\*\s/
-
-                            if !breakLineAtBeginning
-                                return "\n#{match[1]}" if match
+                            markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1]}")

                     # ordered list
                     match = lastLine.match /^(\s*)(\d+)\.\s/
@@ -133,29 +171,12 @@
                         emptyListItem = lastLine.match /^(\s*)(\d+)\.\s$/

                         if emptyListItem
-                            markdownCaretPositon = removeEmptyLine(data.textarea, lines.length - 1, data.caretPosition)
+                            markdownCaretPositon = addLine(data.textarea, cursorLine - 1)
                         else
-                            breakLineAtBeginning = newLineContent.match /^(\s*)(\d+)\.\s/
-
-                            if !breakLineAtBeginning
-                                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..(caretPosition - 1)].split("\n").length
-                    scrollRelation = line / totalLines
-                    $el.scrollTop((scrollRelation * $el[0].scrollHeight) - ($el.height() / 2))
+                            markdownCaretPositon = addLine(data.textarea, cursorLine, "#{match[1] + (parseInt(match[2], 10) + 1)}. ")
+
+
+                    setCaretPosition(data.textarea, markdownCaretPositon) if markdownCaretPositon

             markupSet: [
                 {


Test that up.request() sends an X-Up-Version header

--- spec_app/spec/javascripts/up/network_spec.js.coffee
+++ spec_app/spec/javascripts/up/network_spec.js.coffee
@@ -103,6 +103,14 @@
           done()

       describe 'transfer of meta attributes', ->
+
+        it "sends Unpoly's version as an X-Up-Version request header", asyncSpec (next) ->
+          up.request(url: '/foo')
+
+          next =>
+            versionHeader = @lastRequest().requestHeaders['X-Up-Version']
+            expect(versionHeader).toBePresent()
+            expect(versionHeader).toEqual(up.version)

         it 'submits information about the fragment update as HTTP headers, so the server may choose to optimize its responses', asyncSpec (next) ->
           makeLayers(2)


Simplified if statements

Simplified if statements

--- app/js/util/scrollto.coffee
+++ app/js/util/scrollto.coffee
@@ -7,6 +7,6 @@
         axis: 'y'

 $(window).bind 'factlink.factsLoaded', ->
-  if Factlink? and FactlinkConfig? and FactlinkConfig.scrollto
+  if FactlinkConfig?.scrollto
     # Get al the facts on the current page
-    Factlink.scrollTo FactlinkConfig.scrollto
+    Factlink?.scrollTo FactlinkConfig.scrollto


Removed elastic animation when we change thang scale (#675)

* Removed elastic animation when we change thang scale

* Removed redundant vars

--- app/lib/surface/Lank.coffee
+++ app/lib/surface/Lank.coffee
@@ -353,16 +353,8 @@
     @sprite.scaleX = @sprite.baseScaleX * @scaleFactorX * scaleX
     @sprite.scaleY = @sprite.baseScaleY * @scaleFactorY * scaleY

-    newScaleFactorX = @thang?.scaleFactorX ? @thang?.scaleFactor ? 1
-    newScaleFactorY = @thang?.scaleFactorY ? @thang?.scaleFactor ? 1
-    if @layer?.name is 'Land' or @thang?.isLand or @thang?.spriteName is 'Beam' or @isCinematicLank
-      @scaleFactorX = newScaleFactorX
-      @scaleFactorY = newScaleFactorY
-    else if @thang and (newScaleFactorX isnt @targetScaleFactorX or newScaleFactorY isnt @targetScaleFactorY)
-      @targetScaleFactorX = newScaleFactorX
-      @targetScaleFactorY = newScaleFactorY
-      createjs.Tween.removeTweens(@)
-      createjs.Tween.get(@).to({scaleFactorX: @targetScaleFactorX, scaleFactorY: @targetScaleFactorY}, 2000, createjs.Ease.elasticOut)
+    @scaleFactorX = @thang?.scaleFactorX ? @thang?.scaleFactor ? 1
+    @scaleFactorY = @thang?.scaleFactorY ? @thang?.scaleFactor ? 1

   updateAlpha: ->
     @sprite.alpha = if @hiding then 0 else 1


QUALITY: Cleanup and small improvements

QUALITY: Cleanup and small improvements

--- src/app/common/audio-recorder/audio-recorder.coffee
+++ src/app/common/audio-recorder/audio-recorder.coffee
@@ -13,8 +13,12 @@
     $scope.canRecord = true

     mediaRecorder = new recorderService()
+
+    # Required for recording multiple times
     mediaRecorder.config.stopTracksAndCloseCtxWhenFinished = true
+    # Required for visualising the stream
     mediaRecorder.config.createAnalyserNode = true
+
     mediaRecorder.em.addEventListener('recording', (evt) -> onNewRecording(evt))
     $scope.recordingAvailable = false
     $scope.isRecording = false
@@ -25,8 +29,6 @@
     # Need to get non-angular bindable components
     canvas = document.getElementById('audio-recorder-visualiser')
     audio = document.getElementById('audioPlayer')
-
-    audioCtx = new (window.AudioContext || webkitAudioContext)()
     canvasCtx = canvas.getContext("2d")

     $scope.playStop = () ->
@@ -52,16 +54,16 @@
       draw = ->
         WIDTH = canvas.width
         HEIGHT = canvas.height
-        requestAnimationFrame draw
+        requestAnimationFrame(draw)
         analyser.getByteTimeDomainData(dataArray)
         analyser.getByteFrequencyData(dataArray)
-        canvasCtx.fillstyle = '#2196F3'
+        # canvasCtx.fillstyle = '#2196F3'

         canvasCtx.clearRect(0, 0, WIDTH, HEIGHT)
         i = 0
         bar_width = 1
         while i < WIDTH
-          bar_x = i * 10
+          bar_x = i * 14
           bar_y = HEIGHT / 2
           bar_height = -(dataArray[i] / 4) + 1
           canvasCtx.fillRect bar_x, bar_y, bar_width, bar_height


Use browser trim function

Use browser trim function

--- app/js/jail_iframe/util/highlight.coffee
+++ app/js/jail_iframe/util/highlight.coffee
@@ -1,9 +1,7 @@
-trim = (string) -> string.replace(/^\s+|\s+$/g, '');
-
 # Chrome, Firefox, Safari
 searchWithWindowFind = (searchString) ->
   # Trim
-  searchString = trim(searchString);
+  searchString = searchString.trim();

   # If the user currently has selected some text, store the selection
   selection = window.document.getSelection()


Improve code readablity in cities.coffee

--- app/controllers/cities.coffee
+++ app/controllers/cities.coffee
@@ -1,9 +1,9 @@
 module.exports = (app) ->
 #  TODO Move this check to model's lifecycle callback
   checkCityForUniqueness = (req, res, cb) ->
-    app.models.city.
-    findOneByName(req.body.name).
-    exec (err, nonUniqueCity) ->
+    app.models.city
+    .findOneByName(req.body.name)
+    .exec (err, nonUniqueCity) ->
       if nonUniqueCity
         req.flash 'error', 'City with same name already exists'
         res.redirect 'back'


Update otter.coffee

Update otter.coffee

--- app/lib/field-guide-content/otter.coffee
+++ app/lib/field-guide-content/otter.coffee
@@ -1,8 +1,8 @@
 module.exports =
   description: '''
-    There are two otter species in Gorongosa: spotted-necked otter (Hydrictis maculicollis) and cape clawless otter (Aonyx capensis). The spotted-necked otter is a medium-sized otter, varying in color but generally dark brown, with distinctive spots and streaks of paler color on the throat, neck, and in some cases the upper lip. More aquatic than other African otters, this species has fully webbed paws with sharp, well-developed claws. They have a broad head, blunt muzzle, short legs, and a long, flattened tail.
+    Two otter species live in Gorongosa: the spotted-necked otter (Hydrictis maculicollis) and the Cape clawless otter (Aonyx capensis). The spotted-necked otter is a medium-sized otter, varying in color but generally dark brown, with distinctive spots and streaks of paler color on the throat, neck, and in some cases the upper lip. More aquatic than other African otters, this species has fully webbed paws with sharp, well-developed claws. They have a broad head, blunt muzzle, short legs, and a long, flattened tail.

-    The Cape clawless otter is a large otter species with a big head, a wide, flat skull, and powerful jaws. They have a sleek coat that is tan to dark chocolate brown in color. Its underparts are lighter and they also have unspotted white or buff patches on the chin, throat, and chest extending onto their neck and face below the eyes. The Cape clawless otter has forefeet that resemble hands, with opposable thumbs, whereas their hind feet are webbed to their outer joints. The tail is long, bulky, and tapered toward the tip.
+    The Cape clawless otter is a large otter species with a big head, a wide, flat skull, and powerful jaws. They have a sleek coat that is tan to dark chocolate brown in color. Their underparts are lighter and they also have unspotted white or buff patches on the chin, throat, and chest extending onto their neck and face below the eyes. The Cape clawless otter has forefeet that resemble hands, with opposable thumbs, whereas their hind feet are webbed to their outer joints. The tail is long, bulky, and tapered toward the tip.
   '''

   scientificName: ''
@@ -32,10 +32,10 @@

   sections: [{
     title: 'Habitat'
-    content: 'Otter can be found occupying rivers, streams, reservoirs, lakes, streams, swamps, canals.'
+    content: 'Rivers, streams, reservoirs, lakes, swamps, and canals'
   }, {
     title: 'Diet'
-    content: 'Fish, amphibians, shellfish, insects and reptile'
+    content: 'Fish, amphibians, shellfish, insects, and reptiles'
   }, {
     title: 'Predators'
     content: 'Crocodiles, pythons, and eagles'
@@ -57,7 +57,7 @@

Spotted-necked otters can run 4 to 5 km per hour (2.5-3 mph).

         Spotted-necked otters use 15 to 20 distinct vocalizations to communicate.

-        Cape clawless otters can close its nose underwater.

+        The Cape clawless otter can close its nose underwater.

     '''
   },{


Fix ouroboros sync

Fix ouroboros sync

--- app/lib/ouroboros_sync.coffee
+++ app/lib/ouroboros_sync.coffee
@@ -8,7 +8,7 @@
     "http://localhost:3000"
   else
     "https://dev.zooniverse.org"
-  options.url = baseURL + "/project/#{Manager.get('project')}" + _.result(model, 'url')
+  options.url = baseURL + "/projects/#{Manager.get('project')}" + _.result(model, 'url')
   options.crossDomain = true
   options.beforeSend = (xhr) ->
     xhr.setRequestHeader 'Authorization', "Basic #{btoa("#{User.current.name}:#{User.current.apiToken}")}"


nonce

nonce

--- app/spec/utils/dongle/mock_dongle.coffee
+++ app/spec/utils/dongle/mock_dongle.coffee
@@ -278,27 +278,50 @@
     else
       # ECDH key exchange
       ecdhdomain = JSUCrypt.ECFp.getEcDomainByName("secp256k1")
-      l 'ecdhdomain', ecdhdomain
       ecdhprivkey = new JSUCrypt.key.EcFpPrivateKey(256, ecdhdomain, @_m2faPrivKey.match(/^(\w{2})(\w{64})(01)?(\w{8})$/)[2])
-      l 'ecdhprivkey', ecdhprivkey
+      # @_m2faPrivKey.match(/^(\w{2})(\w{64})(01)?(\w{8})$/)[2] = "dbd39adafe3a007706e61a17e0c56849146cfe95849afef7ede15a43a1984491"
+      # Ex : 99430387343382539980001755421992980571505177374957036595592547669622383985809
+
       ecdh = new JSUCrypt.keyagreement.ECDH_SVDP(ecdhprivkey)
-      l 'ecdh', ecdh
+      # ecdh.key.d.toString() = 99430387343382539980001755421992980571505177374957036595592547669622383985809
+
       aKey = pubKey.match(/^(\w{2})(\w{64})(\w{64})$/)
-      l 'aKey', aKey
-      secret = ecdh.generate(new JSUCrypt.ECFp.AffinePoint(aKey[2], aKey[3], ecdhdomain.curve)) # 32 bytes secret is obtained, split into two 16 bytes components S1 and S2
-      l 'secret', secret
-      sessionKey = (Convert.toHexByte(secret[i] ^ secret[16+i]) for i in [0...16]).join('') # 16 bytes 3DES-2 session key
-      l 'sessionKey', sessionKey
-      sessionKey
-
-      # Deuxieme call
+      secret = ecdh.generate(new JSUCrypt.ECFp.AffinePoint(aKey[2], aKey[3], ecdhdomain.curve)) # 32 bytes secret is obtained
+
+      # Split into two 16 bytes components S1 and S2. S1 and S2 are XORed to produce a 16 bytes 3DES-2 session key
+      sessionKey = (Convert.toHexByte(secret[i] ^ secret[16+i]) for i in [0...16]).join('')
+      # Ex : b4ad97549846132b8ad8639f9d50391f
+
+      #@emit 'm2fa.identify', data.public_key
+
+
+      # Challenge - 4 bytes
+      keycard = ledger.keycard.generateKeycardFromSeed('dfaeee53c3d280707bbe27720d522ac1')
+      challenge = []
+      for i in [0..3]
+        randomNum = _.random(_.size(keycard) - 1)
+        challenge.push Object.keys(keycard)[randomNum]
+
+
+
+
+
+
       ###
         The remote screen public key is sent to the dongle, which generates a cleartext random 8 bytes nonce,
         a 4 bytes challenge on the printed keycard and a random 16 bytes 3DES-2 pairing key, concatenated and encrypted
         using 3DES CBC and the generated session key
       ###
-      _computeChallenge: (challenge=@lastChallenge) ->
-      l "%c[_computeChallenge] challenge=", "color: #888888", challenge
+
+      @emit 'm2fa.challenge', challenge
+
+      # 8 bytes Nonce
+      randomNumBlob = crypto.getRandomValues(new Uint32Array(10))
+      nonce = ''
+      for i in randomNumBlob
+        nonce += randomNumBlob[i]
+
+
       [nonce, blob] = [challenge[0...16], challenge[16..-1]]
       l "%c[_computeChallenge] nonce=", "color: #888888", nonce, ", blob=", blob
       bytes = @_decryptChallenge(blob)
@@ -308,8 +331,15 @@
       l "%c[_computeChallenge] cardChallenge=", "color: #888888", JSUCrypt.utils.byteArrayToHexStr(cardChallenge), ", pairingKey=", @pairingKey
       cardResp = JSUCrypt.utils.byteArrayToHexStr(@_prompt(cardChallenge))
       l "%c[_computeChallenge] cardResp=", "color: #888888", cardResp
-      resp = JSUCrypt.utils.byteArrayToHexStr(@_cryptChallenge(nonce + cardResp + "00000000"))
-      return [resp, @pairingKey]
+
+      # Crypt Challenge
+      cipher = new JSUCrypt.cipher.DES(JSUCrypt.padder.None, JSUCrypt.cipher.MODE_CBC)
+      key = new JSUCrypt.key.DESKey(@sessionKey)
+      cipher.init(key, JSUCrypt.cipher.MODE_ENCRYPT)
+      blob = cipher.update(nonce + cardResp + "00000000")
+
+      resp = JSUCrypt.utils.byteArrayToHexStr(blob)
+


Fix bug on zoomcam when a user stream is removed

--- app/static/scripts/modules/camera.coffee
+++ app/static/scripts/modules/camera.coffee
@@ -29,9 +29,8 @@
         @emitter.on 'stream.create', @newStream
         @emitter.on 'stream.remove', (event, user) =>
             for key, value of @zoomCams
-                if key is user.id
-                    @zoom user.id, undefined
-                    return
+                if key is user.uid
+                    @zoom user.uid, undefined
             do ($ "[data-streamid=#{user.id}]").remove
         @emitter.on 'peer.remove', @delStream
         @emitter.on 'camera.localstream', (event, data) =>


blog client rendering

blog client rendering

--- coffee/static.coffee
+++ coffee/static.coffee
@@ -1,24 +1,39 @@
 Mod.require 'Docscript.Parser', (Parser) ->
- code = document.getElementById 'code'
- main = document.getElementById 'main'
- sidebar = document.getElementById 'sidebar'
+ process = (n, doc) ->
+  code = doc.getElementsByClassName 'docscript-code'
+  if code.length isnt 1
+   throw new Error 'No code element'
+  code = code[0]
+  main = doc.getElementsByClassName 'docscript-main'
+  if main.length isnt 1
+   throw new Error 'No main element'
+  main = main[0]
+  sidebar = doc.getElementsByClassName 'docscript-sidebar'
+  if sidebar.length isnt 1
+   throw new Error 'No sidebar element'
+  sidebar = sidebar[0]

- parser = new Parser
-  text: code.textContent
- parser.parse()
- parser.collectElements
-  main: main
-  sidebar: sidebar
- window.requestAnimationFrame ->
-  parser.mediaLoaded ->
-   parser.setFills()
-   n = 0
-   int = setInterval ->
+  parser = new Parser
+   text: code.textContent
+   id: n * 10000
+  parser.parse()
+  parser.collectElements
+   main: main
+   sidebar: sidebar
+  window.requestAnimationFrame ->
+   parser.mediaLoaded ->
     parser.setFills()
-    n++
-    if n is 10
-     clearInterval int
-   , 1000
+    n = 0
+    int = setInterval ->
+     parser.setFills()
+     n++
+     if n is 10
+      clearInterval int
+    , 1000
+
+ docs = document.getElementsByClassName 'docscript'
+ for doc, i in docs
+  process i, doc

 document.addEventListener 'DOMContentLoaded', ->
  Mod.set 'Weya', Weya


Add boundary check to @username mentions

--- app/talk/comment-preview.cjsx
+++ app/talk/comment-preview.cjsx
@@ -26,7 +26,7 @@
       .replace(/@(\b[\w-]+\b)\/(\b[\w-]+\b)\^([0-9]+)/g, "$1/$2 - Subject $3")

       # user mentions : @username
-      .replace(/@(\b[\w-]+\b)/g, "$1")
+      .replace(/\B@(\b[\w-]+\b)/g, "$1")

       # hashtags #tagname
       .replace /\B\#(\w+)/g, (fullTag, tagName) ->


rework reving, force build to be synchronous


userScripts: fix a bug with rexclude

userScripts: fix a bug with rexclude

--- chrome/userScripts.uc.coffee
+++ chrome/userScripts.uc.coffee
@@ -36,7 +36,7 @@
         $.merge(includes, (userScripts.stringToRegexp inc for inc in script.includes)) if script.includes?
         $.merge(excludes, (userScripts.stringToRegexp exc for exc in script.excludes)) if script.excludes?
         includes.push script.rinclude if script.rinclude?
-        excludes.push script.rexculde if script.rexclude?
+        excludes.push script.rexclude if script.rexclude?
         $.merge(includes, script.rincludes) if script.rincludes?
         $.merge(excludes, script.rexcludes) if script.rexcludes?


refactor azure.test.coffee file

refactor azure.test.coffee file

--- workers/social/lib/social/models/computeproviders/azure.test.coffee
+++ workers/social/lib/social/models/computeproviders/azure.test.coffee
@@ -19,15 +19,12 @@

     it 'should reply to ping request', (done) ->

-      userFormData = generateDummyUserFormData()
+      withConvertedUser ({ client, account }) ->

-      withConvertedUser { userFormData }, (data) ->
-
-        { client, account } = data
-
+        expectedPong = "Azure is cool #{client.connection.delegate.profile.nickname}!"
         Azure.ping client, (err, data) ->
           expect(err?.message).to.not.exist
-          expect(data).to.be.equal "Azure is cool #{client.connection.delegate.profile.nickname}!"
+          expect(data).to.be.equal expectedPong
           done()


prevent flickering

prevent flickering

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -253,6 +253,7 @@
 											inMenu = true

 											x = ->
+												return unless inMenu
 												menu.css css
 												menu.show()
 												return


Implement helpers

Implement helpers

--- lib/Task/DecisionTask.coffee
+++ lib/Task/DecisionTask.coffee
@@ -1,4 +1,5 @@
 _ = require "underscore"
+camelize = require "underscore.string/camelize"
 Promise = require "bluebird"
 Match = require "mtr-match"
 errors = require "../../helper/errors"
@@ -12,11 +13,15 @@
   execute: ->
     new Promise (resolve, reject) =>
       try
+        @barriers = {}
         @decisions = []
         @modifier = {}
         for event in @events
           if @[event.eventType]
-            @[event.eventType](event)
+            attributes = camelize(event.eventType, true) + "EventAttributes"
+            console.log attributes
+            input = JSON.parse event[attributes].input if event[attributes].input
+            @[event.eventType](event, event[attributes], input)
           else
             throw new errors.EventHandlerNotImplementedError
               message: "Event handler '#{event.eventType}' not implemented"
@@ -31,7 +36,22 @@
   DecisionTaskScheduled: (event) ->
   DecisionTaskStarted: (event) ->
   DecisionTaskCompleted: (event) ->
-  ActivityTaskScheduled: (event) ->
   ActivityTaskStarted: (event) ->
+  # default decisions
+  ScheduleActivityTask: (activityTypeName, input) ->
+    decisionType: "ScheduleActivityTask"
+    scheduleActivityTaskDecisionAttributes:
+      activityType:
+        name: activityTypeName
+        version: "1.0.0"
+      activityId: activityTypeName
+      input: JSON.stringify input
+  # workflow helpers
+  createBarrier: (name, obstacles) ->
+    @barriers[name] = obstacles
+  removeBarrierObstacle: (name, obstacle) ->
+    @barriers[name] = _.without @barriers[name], obstacle
+  isBarrierPassed: (name) ->
+    not @barriers[name].length

 module.exports = DecisionTask


Remove not needed anymore src/coffee/jquery-private.coffee

--- bokehjs/src/coffee/jquery-private.coffee
+++ bokehjs/src/coffee/jquery-private.coffee
@@ -1 +0,0 @@
-module.exports = require("jquery").noConflict(true)


Removed more unused variables

Removed more unused variables

--- app/assets/javascripts/bootstrap_builders/view-port-detection.coffee
+++ app/assets/javascripts/bootstrap_builders/view-port-detection.coffee
@@ -9,7 +9,7 @@
   $.bbViewPort = ->
     width = window.innerWidth

-    for size, width_and_height of sizes
+    for size of sizes
       size_width = sizes[size][0]
       size_height = sizes[size][1]
       return size if width >= size_width && width < size_height
@@ -20,7 +20,7 @@
     current_size = $.bbViewPort()
     reached = false

-    for size, width_and_height of sizes
+    for size of sizes
       reached = true if size == current_size

       if size == given_size


Update copy for course creator.

Update copy for course creator.

--- app/assets/javascripts/components/course_creator/course_creator.cjsx
+++ app/assets/javascripts/components/course_creator/course_creator.cjsx
@@ -83,7 +83,7 @@
     <Modal>

Create a New Course

-
After you create your course, the wizard will walk you through creating an assignment timeline.

+
The title, school and term will become part of course page url, and cannot be changed. The rest of the course information may be edited later on. After you create your course, the wizard will walk you through creating an assignment timeline.


Remove logs

Remove logs

--- endpoints/socket/index.coffee
+++ endpoints/socket/index.coffee
@@ -9,7 +9,7 @@
 _connections = {}

 module.exports = (server) ->
-  io = socketio.listen(server).of("/socket")
+  io = socketio.listen(server, log: false).of("/socket")

   io.on "connection", (client) =>
     client.on "connectToRoom", (room_name) ->


Pulse channel envelope.

Pulse channel envelope.

--- emulator/core/channels/pulse-channel.coffee
+++ emulator/core/channels/pulse-channel.coffee
@@ -36,12 +36,14 @@
         value

     setDutyEnvelope: (value) ->
-        @dutySelection = (value & 0xC0) >> 6
+        @dutySelection = (value & 0xC0) >> 6     # Selects output waveform
         @lengthCounterHalt = value & 0x20 isnt 0 # Disables length counter decrementation
+        @useConstantVolume = value & 0x10 isnt 0 # 0 - envelope is used as volume / 1 - volume is constant value
+        @constantVolume = value & 0x0F           # Constant value of output volume
         @envelopeLoop = @lengthCounterHalt       # Envelope is looping (length counter hold alias)
-        @useConstantVolume = value & 0x10 isnt 0 # 0 - envelope itself is volume / 1 - volume is constant value
-        @constantVolume = value & 0x0F           # Constant value of output volume
         @envelopePeriod = @constantVolume        # Envelope duration period (constant volume alias)
+        @envelopeCycle or= 0                     # Cycle of envelope divider
+        @envelope or= 0                          # Envelope counter value

     ###########################################################
     # Sweep register
@@ -53,12 +55,12 @@
         value

     setSweep: (value) ->
-        @sweepEnabled = value & 0x80 isnt 0
-        @sweepPeriod = (value & 0x70) >>> 4
-        @sweepNegate = value & 0x08 isnt 0
-        @sweepShift = value & 0x07
-        @sweepReload = true
-        @sweepCycle or= 0
+        @sweepEnabled = value & 0x80 isnt 0 # Sweeping enabled
+        @sweepPeriod = (value & 0x70) >>> 4 # Period after which sweep is applied
+        @sweepNegate = value & 0x08 isnt 0  # 0 - sweep is added to timer period / 1 - sweep is subtracted from timer period
+        @sweepShift = value & 0x07          # Shift of timer period when computing sweep
+        @sweepReload = true                 # Sweep counter will be reloaded
+        @sweepCycle or= 0                   # Sweep counter

     ###########################################################
     # Timer register
@@ -70,7 +72,7 @@
         value

     setTimer: (value) ->
-         @timerPeriod = (@timerPeriod or 0) & 0x700 | (value & 0xFF) # Low 8 bits
+         @timerPeriod = (@timerPeriod or 0) & 0x700 | (value & 0xFF) # Lower 8 bits of timer

     ###########################################################
     # Length counter / Timer register
@@ -82,9 +84,10 @@
         value

     setLenghtCounter: (value) ->
-        @timerPeriod = (@timerPeriod or 0) & 0x0FF | (value & 0x7) << 8 # High 3 bits
-        @lengthCounter = (value & 0xF8) >>> 3
-        @dutyPosition = 0
+        @timerPeriod = (@timerPeriod or 0) & 0x0FF | (value & 0x7) << 8 # Higher 3 bits of timer
+        @lengthCounter = (value & 0xF8) >>> 3  # Length counter value
+        @dutyPosition = 0                      # Output waveform position is reseted
+        @envelopeReload = true                 # Envelope and its divider will be reseted

     ###########################################################
     # Tick
@@ -113,7 +116,17 @@
     ###########################################################

     updateEnvelope: ->
-
+        if @envelopeReset
+            @envelopeReset = false
+            @envelopeCycle = @envelopePeriod
+            @envelope = 0xF
+        else if @envelopeCycle > 0
+            @envelopeCycle--
+        else if @envelope > 0 # Envelope cycle is 0
+            @envelopeCycle = @envelopePeriod
+            @envelope--
+        else if @envelopeLoop # Envelope is 0
+            @envelope = 0xF

     ###########################################################
     # Length counter


fix typo in alpha constraint

fix typo in alpha constraint

--- assets/scripts/coffee/hoarder/validator/constraints/alpha_constraint.coffee
+++ assets/scripts/coffee/hoarder/validator/constraints/alpha_constraint.coffee
@@ -7,6 +7,6 @@

   rulePasses: (element)-> element.value.match(/^[A-Za-z\s]*$/)

-  errorMessage: -> "This field only accepts numbers and characters (0-9, A-Z, a-z)."
+  errorMessage: -> "This field only accepts characters (A-Z, a-z)."

 return AlphaConstraint


Use KD instead @ since its static now

Use KD instead @ since its static now

--- client/app/MainApp/fs/appswatcher.coffee
+++ client/app/MainApp/fs/appswatcher.coffee
@@ -46,4 +46,4 @@
   isManifest = (change)-> /manifest\.json$/.test change.file.fullPath
   getAppName = (change)->
     (change.file.fullPath.match /Applications\/([^\/]+)\.kdapp/)[1]
-  throttle   = (cb)-> do @utils.throttle cb, 300
+  throttle   = (cb)-> do KD.utils.throttle cb, 300


client: remove id assignment from fetchAccounts util

--- client/app/lib/util/fetchAccounts.coffee
+++ client/app/lib/util/fetchAccounts.coffee
@@ -17,7 +17,6 @@

   _accounts = []
   queue = origins.map (origin) -> ->
-    origin.id or= origin._id
     fetchAccount origin, (err, account) ->
       return queue.fin err  if err
       _accounts.push account


MessagePane: removed duplicate event usage

--- client/ide/views/chat/messagepane.coffee
+++ client/ide/views/chat/messagepane.coffee
@@ -11,13 +11,6 @@
     @define 'visible', => @getDelegate().visible

     @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

     @input.input.on 'focus', (event) => @handleFocus yes, event

@@ -164,6 +157,12 @@

   participantAdded: (participant) ->

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

     appManager = KD.getSingleton 'appManager'


Profile: feed is put back

Profile: feed is put back

--- client/Social/Members/ContentDisplays/ContentDisplayControllerMember.coffee
+++ client/Social/Members/ContentDisplays/ContentDisplayControllerMember.coffee
@@ -122,6 +122,7 @@
       listControllerClass   : ActivityListController
       listCssClass          : "activity-related"
       limitPerPage          : 8
+      useHeaderNav          : yes
       help                  :
         subtitle            : "Learn Personal feed"
         tooltip             :


different logic

different logic

--- client/landing/app/MainApp/routes.coffee
+++ client/landing/app/MainApp/routes.coffee
@@ -34,7 +34,8 @@
         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' or invite.status isnt 'sent'
+        if err or !invite? or invite.status not in ['active','sent']
+          if err then error err
           new KDNotificationView
             title: 'Invalid invitation code!'
         else


stop reloading activities over and over

stop reloading activities over and over

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -312,10 +312,13 @@

   continueLoadingTeasers:->

+    lastTimeStamp = (new Date @lastFrom).getTime()
+    if @continueLoadingTeasersLastTimeStamp is lastTimeStamp
+      return
+
     # HACK: this gets called multiple times if there's no wait
     KD.utils.wait 1000, =>
       return  unless @lastFrom?
-
       lastTimeStamp = (new Date @lastFrom).getTime()
       @populateActivity {slug : "before/#{lastTimeStamp}", to: lastTimeStamp}


Added missing requires so test will pass in isolation in firefox

--- test/spec/roadmapplanningboard/RoadmapPlanningBoardAppSpec.coffee
+++ test/spec/roadmapplanningboard/RoadmapPlanningBoardAppSpec.coffee
@@ -6,6 +6,13 @@
   'Rally.apps.roadmapplanningboard.SplashContainer'
   'Rally.test.mock.ModelObjectMother'
   'Rally.util.BrowserValidation'
+  'Rally.app.Context'
+  'Rally.ui.notify.Notifier'
+  'Deft.Injector'
+  'Deft.Deferred'
+  'Deft.promise.Deferred'
+  'Deft.Promise'
+  'Ext.Ajax'
 ]

 describe 'Rally.apps.roadmapplanningboard.RoadmapPlanningBoardApp', ->


Extended expand/collapse function to Groups

--- jsapp/xlform/src/view.surveyApp.coffee
+++ jsapp/xlform/src/view.surveyApp.coffee
@@ -244,6 +244,7 @@
       @_getViewForTarget(evt).toggleSettings()

     toggleGroupExpansion: (evt)->
+      console.log evt
       view = @_getViewForTarget(evt)
       groupsAreShrunk = view.$el.hasClass('group--shrunk')
       @surveyStateStore.setState({
@@ -379,11 +380,18 @@

     expandMultioptions: ->
       if @expand_all_multioptions()
+        $('.survey__row--group:not(.group--shrunk)').each (i, el) ->
+          if !$(el).hasClass('group--shrunk')
+            $(el).find('.group__caret').click()
         @$(".card--expandedchoices").each (i, el)=>
           @_getViewForTarget(currentTarget: el).hideMultioptions()
           ``
         _expanded = false
       else
+        # while loop is potentially bad if there is a future change that no
+        # longer updates `group--shrunk` when collapsing groups
+        while $('.survey__row--group.group--shrunk').length > 0
+            $('.survey__row--group.group--shrunk').each (i, el) => $(el).find('.group__caret').click()
         @$(".card--selectquestion").each (i, el)=>
           @_getViewForTarget(currentTarget: el).showMultioptions()
           ``
@@ -393,6 +401,7 @@
           multioptionsExpanded: _expanded
         })
       return
+

     closeWarningBox: (evt)->
       @$('.survey-warnings').hide()
@@ -451,7 +460,7 @@
       group_rows = @formEditorEl.find('.group__rows')
       group_rows.each (index) =>
         $(group_rows[index]).sortable({
-          cancel: 'button, .btn--addrow, .well, ul.list-view, li.editor-message, .editableform, .row-extras, .js-cancel-sort, .js-cancel-group-sort' + index
+          cancel: 'button, .btn--addrow, .well, ul.list-view, li.editor-message, .editableform, .row-extras, .js-cancel-sort, .js-cancel-group-sort'# + index
           cursor: "move"
           distance: 5
           items: "> li"


GoogleMap#setOptions(options) method added

--- vendor/assets/javascripts/rails-google-maps/google_maps.js.coffee
+++ vendor/assets/javascripts/rails-google-maps/google_maps.js.coffee
@@ -25,6 +25,9 @@
   # gmapOptions: google map api options
   constructor: (options = {}, gmapOptions = {})->
     @gmapOptions = $.extend {}, GoogleMap.defaultGmapOptions, gmapOptions
+    this.setOptions options
+
+  setOptions: (options)->
     @immutable = true if options['immutable']
     @mapSelector = options['selector'] if options['selector']
     @errorField = options['errorField'] if options['errorField']
@@ -32,6 +35,7 @@
       @saveLangLat = true
       @longitudeInput = options['longitudeInput']
       @latitudeInput = options['latitudeInput']
+

   apply: ()->
     @map = new google.maps.Map $(@mapSelector)[0], @gmapOptions


don't go into an infinite request loop if we're expecting step contents from a metadata request but don't get any

This shouldn't happen unless something is wrong w/ the step definition in the controller. But an infinite loop is never an acceptable failure mode.

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -328,12 +328,18 @@
     this.updateStepFromMetadata()

   metadataRequestCallback: (data) =>
+    # set empty step contents if we didn't get any
+    # makes sure we can tell whether or not we've already requested metadata
+    stepId = this.currentStepId()
+    emptyStepContents = { hatTrickStepContents: {} }
+    emptyStepContents["hatTrickStepContents"][stepId] = ""
+    data.data = $.extend({}, data.data, emptyStepContents) unless data.data.hatTrickStepContents?
     this.handleServerData(data)
     this.removeLinkField() # updateStepFromMetadata sets this to currentStep
     this.setupButtonsForCurrentStep()
     this.updateButtons()
     this.setFormFields(hatTrick.model)
-    @form.trigger "step_changed", { currentStep: this.currentStepId() }
+    @form.trigger "step_changed", { currentStep: stepId }

   requestMetadataFromServer: ->
     metadataUrl = document.location.pathname


unify the step_shown and initialize steps as much as possible

Hopefully this will eliminate some of the inconsistencies between a step being shown via JS vs. being loaded from the Rails app.

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -2,23 +2,36 @@

 class HatTrickWizard
   constructor: (formElem, @wizard) ->
-    this.formwizardEnabled = false
     @form = $(formElem)
-    this.addStepClass()
-    # prevent submitting the step that happens to be the last fieldset
-    this.addFakeLastStep()
-    this.addDefaultButtons()
-    this.setButtonMetadataForCurrentStep()
-    this.setupButtonsForAllSteps()
-    this.enableFormwizard()
-    this.setupButtonsForCurrentStep()
+    this.stepShownCallback() # because we just showed the first step (or the one in the URL)
+
+  linkClass: "_ht_link"
+
+  buttons: {}
+
+  stepShownCallback: ->
+    if not @formwizardEnabled
+      this.addStepClass()
+      # prevent submitting the step that happens to be the last fieldset
+      # TODO: Figure out a better way to do this
+      this.addFakeLastStep()
+      this.addDefaultButtons()
+    this.updateStepFromMetadata()
+    if not @formwizardEnabled
+      this.setupButtonsForAllSteps()
+      this.enableFormwizard() # this clobbers our button customizations for the current step
+      this.setupButtonsForCurrentStep() # so we run this to reconfigure it
+      this.bindEvents()
+    else
+      this.setupButtonsForCurrentStep()
+      # this must be run here & after setupButtonsForCurrentStep() to support setContents()
+      if @stepsNeedUpdate
+        this.updateSteps()
+        @stepsNeedUpdate = false
     this.setCurrentStepField()
+    this.clearNextStepField()
+    this.setFormFields(hatTrick.model)
     this.createDummyModelField() unless this.currentStepHasModelFields()
-    this.bindEvents()
-
-  linkClass: "_ht_link"
-
-  buttons: {}

   addStepClass: ->
     @form.find("fieldset").addClass("step")
@@ -321,10 +334,7 @@

   bindEvents: ->
     @form.bind "step_shown", (event, data) =>
-      this.setCurrentStepField()
-      this.clearNextStepField()
-      this.setFormFields(hatTrick.model)
-      this.setupButtonsForCurrentStep()
+      this.stepShownCallback()

 $ ->
   if $("form.wizard").length > 0


proper apply changes functionality

proper apply changes functionality

--- app/assets/javascripts/item_sets.js.coffee
+++ app/assets/javascripts/item_sets.js.coffee
@@ -1,5 +1,36 @@
 itemSetNamespace = angular.module('itemSet', ["checklist-model"])

+# from
+# http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
+deepClone = (obj) ->
+
+  # Handle the 3 simple types, and null or undefined
+  return obj  if null is obj or "object" isnt typeof obj
+
+  # Handle Date
+  if obj instanceof Date
+    copy = new Date()
+    copy.setTime obj.getTime()
+    return copy
+
+  # Handle Array
+  if obj instanceof Array
+    copy = []
+    i = 0
+    len = obj.length
+
+    while i < len
+      copy[i] = deepClone(obj[i])
+      i++
+    return copy
+
+  # Handle Object
+  if obj instanceof Object
+    copy = {}
+    for attr of obj
+      copy[attr] = deepClone(obj[attr])  if obj.hasOwnProperty(attr)
+    return copy
+  throw new Error("Unable to copy obj! Its type isn't supported.")

 $(document).on('ready page:load', ->
   angular.bootstrap(document.body, ['itemSet'])
@@ -39,7 +70,7 @@
 # we are not...
 checkRequiredChampion = (itemData) ->
   _.filter(itemData, (itemObject) ->
-    if itemObject.requiredChampion != undefined
+    if itemObject.requiredChampion?
       # if we are not the requiredChampion attribute
       # this needs to return false
       gon.champion == itemObject.requiredChampion
@@ -136,13 +167,26 @@
   , true)

   $scope.$watch('itemSetBlocks', () ->
-    if _.isEqual($scope.itemSetblocks, $scope.oldItemSetBlocks)
+    oldString = JSON.stringify($scope.oldItemSetBlocks)
+    currentString = JSON.stringify($scope.itemSetBlocks)
+
+    console.log(JSON.stringify($scope.itemSetBlocks))
+    console.log(JSON.stringify($scope.oldItemSetBlocks))
+
+    if oldString != currentString
+      console.log('is not equal')
       $scope.hasChanged = true
+    else
+      console.log('is equal')
+      $scope.hasChanged = false
+
   , true)

   # sends an ajax request to the rails web server to
   # sync the current item set to the ones stored
   # on the rails server
+  #
+  # this is called whenever apply changes is clicked
   $scope.updateItemSet = () ->
     obj = buildJSON($scope.itemSetBlocks, $scope.mapOption, $scope.selectedMap)
     console.log(obj)
@@ -154,7 +198,8 @@
     $http.put("/item_sets/#{gon.itemSetId}/update_json", data).success(
       (data) ->
         if data.status == 'success'
-          $scope.oldItemSetBlocks = $scope.itemSetBlocks
+          $scope.oldItemSetBlocks = deepClone($scope.itemSetBlocks)
+          $scope.hasChanged = false
         else
           console.log('error occured while trying to update')
     )
@@ -261,7 +306,7 @@

     # initialize oldItemSetBlocks, so we can compare to see
     # if change has occured
-    $scope.oldItemSetBlocks = $scope.itemSetBlocks
+    $scope.oldItemSetBlocks = deepClone($scope.itemSetBlocks)

   $scope.performFilter()
   $scope.initItemSetBlocks()


Ignore 2 more esc sequences

Ignore 2 more esc sequences

--- app/assets/javascripts/player/vt.js.coffee
+++ app/assets/javascripts/player/vt.js.coffee
@@ -159,6 +159,8 @@

     "\x1b\x3e": (data) -> # DECKPNM - Set keypad to numeric mode (digits intead of ESCape seq)

+    "\x1b\\\\": -> # String Terminator (VT125 exits graphics)
+
     "\x1b\x5d[012]\x3b.*?\x07": (data, match) -> # OSC - Operating System Command (terminal title)

     "\x1b\\[>c": (data) -> # Secondary Device Attribute request (?)
@@ -169,6 +171,7 @@
     "\x1bE": -> @newLine()
     "\x1bH": -> @setHorizontalTabStop()
     "\x1bM": -> @reverseIndex()
+    "\x1bk": -> # NAPLPS lock-shift G1 to GR

     "\x1b7": (data) -> # save cursor pos and char attrs
       @saveTerminalState()


draw legs with markers

draw legs with markers

--- app/assets/javascripts/views/map.js.coffee
+++ app/assets/javascripts/views/map.js.coffee
@@ -13,17 +13,15 @@
       maxZoom: 18)
     seattle = new L.LatLng(47.62167,-122.349072)
     @map.setView(seattle, 13).addLayer(cloudmade)
+    @on 'plan_complete', @draw_route

   render: =>
     # route
-    points = decodeLine('wzqaHn{tiVAEo@oBk@mBm@mBi@}AEOm@mBm@qB{A}EkAqDQk@o@mBQk@Qg@Sq@qAgEe@wAGUm@oBWw@EQC]@{AaFC?yBAcC?y@?yC?sE?_G?gB?[AiB?]iFDQ?g@@iB?eA?}@?')
-    latlngs = (new L.LatLng(point[0], point[1]) for point in points)
-    polyline = new L.Polyline(latlngs, color: 'red')
-    # zoom the map to the polyline
-    @map.fitBounds(new L.LatLngBounds(latlngs))
-    # add the polyline to the map
-    @map.addLayer(polyline)
-    @plan()
+    from = new L.Marker(new L.LatLng(47.63320158032844, -122.36168296958942))
+    @map.addLayer(from)
+    to = new L.Marker(new L.LatLng(47.618624, -122.320796))
+    @map.addLayer(to)
+    @plan(from, to)
     this

@@ -37,8 +35,26 @@
       maxWalkDistance: 840
       date: '2012-06-01'
       routerId: ''
-      toPlace: '47.618624,-122.320796'
-      fromPlace: '47.633202,-122.361823'
     }
+    data.fromPlace = "#{from.getLatLng().lat},#{from.getLatLng().lng}"
+    data.toPlace = "#{to.getLatLng().lat},#{to.getLatLng().lng}"
     $.get '/otp/plan', data, (response) =>
-      console.log response
+      @trigger 'plan_complete', response.plan
+
+
+  draw_route: (plan) =>
+    console.log plan
+    itinerary = plan.itineraries[0]
+    for leg in itinerary.legs
+      @draw_polyline(leg.legGeometry.points, if leg.mode == 'BUS' then 'red' else 'black')
+
+
+  draw_polyline: (points, color) =>
+    points = decodeLine(points)
+    latlngs = (new L.LatLng(point[0], point[1]) for point in points)
+    polyline = new L.Polyline(latlngs, color: color)
+    # zoom the map to the polyline
+    #@map.fitBounds(new L.LatLngBounds(latlngs))
+    # add the polyline to the map
+    @map.addLayer(polyline)
+


Change in user avatar the gray color

Change in user avatar the gray color


fix: rescan bug

fix: rescan bug

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -99,6 +99,9 @@
       rfb = @get "rfb"
       if rfb._rfb_connection_state is 'connected'
         rfb.disconnect()
+      if rfb._rfb_connection_state is 'disconnected'
+        @set "rfb", null
+        @setupRFB()

 `export default VncViewerComponent`


only assign record if a value is present, occasionally theres cases where the server doesnt need to resend the entire payload

--- lib/generators/nas_extjs/templates/public/app/model/BelongsTo.coffee
+++ lib/generators/nas_extjs/templates/public/app/model/BelongsTo.coffee
@@ -89,7 +89,8 @@
             return ret;

     read: (record, reader, associationData)->
-        record[this.instanceName] = reader.read([associationData]).records[0];
+        if ( data = reader.read([associationData]).records[0] )
+            record[this.instanceName] = data

     createSetter: ->
         me = this


heroku + postgress testing

heroku + postgress testing

--- system/libraries/Session/drivers/Session_sql.coffee
+++ system/libraries/Session/drivers/Session_sql.coffee
@@ -57,6 +57,10 @@

         return $callback($err) if log_message('debug', 'Session::get %s %s', $sid, $err) if $err
         #return $callback($err) if show_error($err)
+        if $result.num_rows isnt 0
+          console.log '----------------------------------'
+          console.log $result.row().session
+          console.log '----------------------------------'
         $callback null, if $result.num_rows is 0 then null else JSON.parse($result.row().session)


Rework key-mapping parsing.

Rework key-mapping parsing.

There are two changes here:

1. Treat built-in key mappings and custom key mappings in the same way;
that is, we prepend the built-in mappings to the custom mappings and
then parse them all together.  This results in a number of
simplifications due to previous duplication of logic and the elimination
of special cases.

2. Parse key mappings in reverse order, so we can just ignore key
mappings after first encountering a key sequence.  So, `map`, `unmap`
and `unmapAll` are all treated in more or less the same way.

This is preparatory to reworking some aspects of the help page.  In
particular, regardless of the order of maps, unmaps, etc., this approach
makes it easier to find the mapping in effect for a key sequence (and be
able to recreate the order of those mappings).

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -1,36 +1,54 @@
 Commands =
+  availableCommands: {}
+  keyToCommandRegistry: null
+  mapKeyRegistry: null
+
   init: ->
-    for own command, descriptor of commandDescriptions
-      @addCommand(command, descriptor[0], descriptor[1])
+    for own command, [description, options] of commandDescriptions
+      @availableCommands[command] = extend (options ? {}), description: description
+
+    Settings.postUpdateHooks["keyMappings"] = @loadKeyMappings.bind this
     @loadKeyMappings Settings.get "keyMappings"
-    Settings.postUpdateHooks["keyMappings"] = @loadKeyMappings.bind this

   loadKeyMappings: (customKeyMappings) ->
-    @clearKeyMappingsAndSetDefaults()
-    @parseCustomKeyMappings customKeyMappings
-    @generateKeyStateMapping()
+    @keyToCommandRegistry = {}
+    @mapKeyRegistry = {}
+
+    configLines = ("map #{key} #{command}" for own key, command of defaultKeyMappings)
+    configLines.push BgUtils.parseLines(customKeyMappings)...
+    seen = {}
+    unmapAll = false
+    for line in configLines.reverse()
+      tokens = line.split /\s+/
+      switch tokens[0]
+        when "map"
+          if 3 <= tokens.length and not unmapAll
+            [_, key, command, optionsList...] = tokens
+            if not seen[key] and registryEntry = @availableCommands[command]
+              seen[key] = true
+              keySequence = @parseKeySequence key
+              options = @parseCommandOptions command, optionsList
+              @keyToCommandRegistry[key] = extend {keySequence, command, options}, @availableCommands[command]
+        when "unmap"
+          if tokens.length == 2
+            seen[tokens[1]] = true
+        when "unmapAll"
+          unmapAll = true
+        when "mapkey"
+          if tokens.length == 3
+            fromChar = @parseKeySequence tokens[1]
+            toChar = @parseKeySequence tokens[2]
+            @mapKeyRegistry[fromChar[0]] = toChar[0] if fromChar.length == toChar.length == 1
+
     chrome.storage.local.set mapKeyRegistry: @mapKeyRegistry
-
-  availableCommands: {}
-  keyToCommandRegistry: {}
-
-  # Registers a command, making it available to be optionally bound to a key.
-  # options:
-  #  - background: whether this command needs to be run against the background page.
-  addCommand: (command, description, options = {}) ->
-    if command of @availableCommands
-      BgUtils.log "#{command} is already defined! Check commands.coffee for duplicates."
-      return
-
-    @availableCommands[command] = extend options, description: description
-
-  mapKeyToCommand: ({ key, keySequence, command, options }) ->
-    unless @availableCommands[command]
-      BgUtils.log "#{command} doesn't exist!"
-      return
-
-    options ?= {}
-    @keyToCommandRegistry[key] = extend { keySequence, command, options }, @availableCommands[command]
+    @installKeyStateMapping()
+
+    # Push the key mapping for passNextKey into Settings so that it's available in the front end for insert
+    # mode.  We exclude single-key mappings (that is, printable keys) because when users press printable keys
+    # in insert mode they expect the character to be input, not to be droppped into some special Vimium
+    # mode.
+    Settings.set "passNextKeyKeys",
+      (key for own key of @keyToCommandRegistry when @keyToCommandRegistry[key].command == "passNextKey" and 1 < key.length)

   # Lower-case the appropriate portions of named keys.
   #
@@ -60,43 +78,6 @@
       else
         [key[0], @parseKeySequence(key[1..])...]

-  parseCustomKeyMappings: (customKeyMappings) ->
-    for line in BgUtils.parseLines customKeyMappings
-      tokens = line.split /\s+/
-      switch tokens[0]
-        when "map"
-          [ _, key, command, optionList... ] = tokens
-          keySequence = @parseKeySequence key
-          if command? and @availableCommands[command]
-            key = keySequence.join ""
-            BgUtils.log "mapping [\"#{keySequence.join '", "'}\"] to #{command}"
-            @mapKeyToCommand { key, command, keySequence, options: @parseCommandOptions command, optionList }
-          else
-            BgUtils.log "skipping [\"#{keySequence.join '", "'}\"] for #{command} -- something is not right"
-
-        when "unmap"
-          if tokens.length == 2
-            keySequence = @parseKeySequence tokens[1]
-            key = keySequence.join ""
-            BgUtils.log "Unmapping #{key}"
-            delete @keyToCommandRegistry[key]
-
-        when "unmapAll"
-          @keyToCommandRegistry = {}
-
-        when "mapkey"
-          if tokens.length == 3
-            fromChar = @parseKeySequence tokens[1]
-            toChar = @parseKeySequence tokens[2]
-            @mapKeyRegistry[fromChar[0]] = toChar[0] if fromChar.length == toChar.length == 1
-
-    # Push the key mapping for passNextKey into Settings so that it's available in the front end for insert
-    # mode.  We exclude single-key mappings (that is, printable keys) because when users press printable keys
-    # in insert mode they expect the character to be input, not to be droppped into some special Vimium
-    # mode.
-    Settings.set "passNextKeyKeys",
-      (key for own key of @keyToCommandRegistry when @keyToCommandRegistry[key].command == "passNextKey" and 1 < key.length)
-
   # Command options follow command mappings, and are of one of two forms:
   #   key=value     - a value
   #   key           - a flag
@@ -113,16 +94,9 @@

     options

-  clearKeyMappingsAndSetDefaults: ->
-    @keyToCommandRegistry = {}
-    @mapKeyRegistry = {}
-    for own key, command of defaultKeyMappings
-      keySequence = @parseKeySequence key
-      key = keySequence.join ""
-      @mapKeyToCommand { key, command, keySequence }
-
-  # This generates a nested key-to-command mapping structure. There is an example in mode_key_handler.coffee.
-  generateKeyStateMapping: ->
+  # This generates and installs a nested key-to-command mapping structure. There is an example in
+  # mode_key_handler.coffee.
+  installKeyStateMapping: ->
     keyStateMapping = {}
     for own keys, registryEntry of @keyToCommandRegistry
       currentMapping = keyStateMapping
@@ -136,7 +110,7 @@
         else
           currentMapping[key] = extend {}, registryEntry
           # We don't need these properties in the content scripts.
-          delete registryEntry[prop] for prop in ["keySequence", "description"]
+          delete currentMapping[key][prop] for prop in ["keySequence", "description"]
     chrome.storage.local.set normalModeKeyStateMapping: keyStateMapping

   # An ordered listing of all available commands, grouped by type. This is the order they will


Visual/edit modes: remove "e" binding for edit mode.

The "e" binding for edit mode is super convenient.  However, it clashes
with "e" bindings on Gmail and Google's Inbox.  If you get too used to
it, then you keep archiving messages unintentionally.

So, let's try just the "gv" binding for now.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -200,7 +200,6 @@
   "i": "enterInsertMode"
   "v": "enterVisualMode"
   "V": "enterVisualLineMode"
-  "e": "enterEditMode"
   "gv": "enterEditMode"

   "H": "goBack"


Remove log

Remove log

--- lib/update-package-dependencies.coffee
+++ lib/update-package-dependencies.coffee
@@ -13,7 +13,6 @@
     args = ['update']
     process = childProcess.spawn command, args, {cwd: atom.project.getPath()}
     process.on 'close', (code, signal) ->
-      console.log "not cool"
       view.empty().focus().on 'focusout', -> view.remove()
       atom.workspaceView.once 'core:cancel', -> view.remove()