make code graceful

make code graceful

--- index.cjsx
+++ index.cjsx
@@ -233,58 +233,55 @@
     enemyHp.now[i - 1] -= damage

 TorpedoSalvo = (sortieHp, enemyHp, raigeki, sortieInfo) ->
-  if raigeki.api_edam?
-    for damage, i in raigeki.api_edam
-      damage = Math.floor(damage)
-      continue if damage <= 0
-      enemyHp.dmg[i - 1] += damage
-      enemyHp.now[i - 1] -= damage
-    for damage, i in raigeki.api_fydam
-      damage = Math.floor(damage)
-      continue if damage <= 0
-      sortieHp.atk[i - 1] += damage
-  if raigeki.api_fdam?
-    for damage, i in raigeki.api_eydam
-      damage = Math.floor(damage)
-      continue if damage <= 0
-      enemyHp.atk[i - 1] += damage
-    for damage, i in raigeki.api_fdam
-      damage = Math.floor(damage)
-      continue if damage <= 0
-      sortieHp.dmg[i - 1] += damage
-      sortieHp.now[i - 1] -= damage
-      if sortieHp.now[i - 1] <= 0
-        repairNum = checkRepair sortieInfo[i - 1]
-        if repairNum == 42
-          sortieHp.now[i - 1] = Math.floor(sortieHp.max[i - 1] / 5)
-          sortieHp.dmg[i - 1] = 0
-        else if repairNum == 43
-          sortieHp.now[i - 1] = sortieHp.max[i - 1]
-          sortieHp.dmg[i - 1] = 0
+  # 雷撃ターゲット
+  for target, i in raigeki.api_frai
+    continue if target <= 0
+    damage = Math.floor(raigeki.api_fydam[i])
+    continue if damage <= 0
+    sortieHp.atk[i - 1] += damage
+    enemyHp.dmg[target - 1] += damage
+    enemyHp.now[target - 1] -= damage
+  # 雷撃ターゲット
+  for target, i in raigeki.api_erai
+    continue if target <= 0
+    damage = Math.floor(raigeki.api_eydam[i])
+    continue if damage <= 0
+    enemyHp.atk[i - 1] += damage
+    sortieHp.dmg[target - 1] += damage
+    sortieHp.now[target - 1] -= damage
+    if sortieHp.now[target - 1] <= 0
+      repairNum = checkRepair sortieInfo[target - 1]
+      if repairNum == 42
+        sortieHp.now[target - 1] = Math.floor(sortieHp.max[target - 1] / 5)
+        sortieHp.dmg[target - 1] = 0
+      else if repairNum == 43
+        sortieHp.now[target - 1] = sortieHp.max[target - 1]
+        sortieHp.dmg[target - 1] = 0

 Shelling = (sortieHp, enemyHp, hougeki, sortieInfo) ->
   for damageFrom, i in hougeki.api_at_list
     continue if damageFrom == -1
+    damageFrom -= 1
     for damage, j in hougeki.api_damage[i]
       damage = Math.floor(damage)
-      damageTo = hougeki.api_df_list[i][j]
+      target = hougeki.api_df_list[i][0] - 1
       continue if damage <= 0
-      if damageTo < 7
-        enemyHp.atk[damageFrom - 1 - 6] += damage
-        sortieHp.dmg[damageTo - 1] += damage
-        sortieHp.now[damageTo - 1] -= damage
-        if sortieHp.now[damageTo - 1] <= 0
-          repairNum = checkRepair sortieInfo[damageTo - 1]
+      if target < 6
+        enemyHp.atk[damageFrom - 6] += damage
+        sortieHp.dmg[target] += damage
+        sortieHp.now[target] -= damage
+        if sortieHp.now[target] <= 0
+          repairNum = checkRepair sortieInfo[target]
           if repairNum == 42
-            sortieHp.now[damageTo - 1] = Math.floor(sortieHp.max[damageTo - 1] / 5)
-            sortieHp.dmg[damageTo - 1] = 0
+            sortieHp.now[target] = Math.floor(sortieHp.max[target] / 5)
+            sortieHp.dmg[target] = 0
           else if repairNum == 43
-            sortieHp.now[damageTo - 1] = sortieHp.max[damageTo - 1]
-            sortieHp.dmg[damageTo - 1] = 0
+            sortieHp.now[target] = sortieHp.max[target]
+            sortieHp.dmg[target] = 0
       else
-        sortieHp.atk[damageFrom - 1] += damage
-        enemyHp.dmg[damageTo - 7] += damage
-        enemyHp.now[damageTo - 7] -= damage
+        sortieHp.atk[damageFrom] += damage
+        enemyHp.dmg[target - 6] += damage
+        enemyHp.now[target - 6] -= damage

 getShipInfo = (sortieHp, sortieInfo) ->
   {_ships} = window
@@ -295,13 +292,13 @@
       sortieHp.now[i] = _ships[shipId].api_nowhp
       sortieHp.max[i] = _ships[shipId].api_maxhp

-simulateKouku = (api_kouku, planeCount) ->
+simulateAerial = (api_kouku, planeCount) ->
   if api_kouku.api_stage2?
     planeCount.sortie[0] -= api_kouku.api_stage2.api_f_lostcount
     planeCount.enemy[0] -= api_kouku.api_stage2.api_e_lostcount

-simulateBattle = (sortieHp, enemyHp, combinedHp, isCombined, isWater, body, leastHp, planeCount, sortieInfo, combinedInfo, mvpPos) ->
+simulateBattle = (sortieHp, enemyHp, combinedHp, isCombined, isSurface, body, leastHp, planeCount, sortieInfo, combinedInfo, mvpPos) ->
   # First air battle

   if body.api_kouku?
@@ -312,7 +309,7 @@
       planeCount.sortie[1] = tmp.api_f_count
       planeCount.enemy[0] = tmp.api_e_count - tmp.api_e_lostcount
       planeCount.enemy[1] = tmp.api_e_count
-    simulateKouku body.api_kouku, planeCount
+    simulateAerial body.api_kouku, planeCount

     if body.api_kouku.api_stage3?
       AerialCombat sortieHp, enemyHp, body.api_kouku.api_stage3, sortieInfo
@@ -321,7 +318,7 @@
   # Second air battle

   if body.api_kouku2?
-    simulateKouku body.api_kouku2, planeCount
+    simulateAerial body.api_kouku2, planeCount

     if body.api_kouku2.api_stage3?
       AerialCombat sortieHp, enemyHp, body.api_kouku2.api_stage3, sortieInfo
@@ -353,7 +350,7 @@
   # First hougeki battle

   if body.api_hougeki1?
-    if isCombined && !isWater
+    if isCombined && !isSurface
       Shelling combinedHp, enemyHp, body.api_hougeki1, combinedInfo
     else
       Shelling sortieHp, enemyHp, body.api_hougeki1, sortieInfo
@@ -364,7 +361,7 @@
   # Combined hougeki battle

   if body.api_hougeki3?
-    if isCombined && isWater
+    if isCombined && isSurface
       Shelling combinedHp, enemyHp, body.api_hougeki3, combinedInfo
     else
       Shelling sortieHp, enemyHp, body.api_hougeki3, sortieInfo
@@ -431,11 +428,11 @@
       {sortieHp, enemyHp, combinedHp, sortieInfo, enemyInfo, combinedInfo, getShip, getItem, planeCount, enemyFormation, enemyIntercept, enemyName, result, enableProphetDamaged, prophetCondShow, combinedFlag, goBack, mvpPos, mapArea, mapCell, nowSpot, nextSpot, nextSpotKind} = @state
       enableProphetDamaged = config.get 'plugin.prophet.notify.damaged', true
       prophetCondShow = config.get 'plugin.prophet.show.cond', true
-      flag = false
+      shouldRender = false
       switch path
         # First enter map in battle
         when '/kcsapi/api_req_map/start'
-          flag = true
+          shouldRender = true
           if parseInt(postBody.api_deck_id) != 1
             combinedFlag = 0
           if combinedFlag == 0
@@ -463,7 +460,7 @@

         # Enter next point in battle
         when '/kcsapi/api_req_map/next'
-          flag = true
+          shouldRender = true
           sortieHp.dmg[i] = enemyHp.dmg[i] = combinedHp.dmg[i] = sortieHp.atk[i] = enemyHp.atk[i] = combinedHp.atk[i] = 0 for i in [0..5]
           enemyInfo.lv = Object.clone initId
           enemyFormation = enemyIntercept = 0
@@ -485,7 +482,7 @@

         # Normal battle
         when '/kcsapi/api_req_sortie/airbattle', '/kcsapi/api_req_battle_midnight/sp_midnight', '/kcsapi/api_req_sortie/battle', '/kcsapi/api_req_battle_midnight/battle'
-          flag = true
+          shouldRender = true
           # The damage in day battle
           daySortieDmg = Object.clone sortieHp.dmg
           dayEnemyDmg = Object.clone enemyHp.dmg
@@ -502,7 +499,7 @@

         # Practice battle
         when '/kcsapi/api_req_practice/battle', '/kcsapi/api_req_practice/midnight_battle'
-          flag = true
+          shouldRender = true
           # If practice
           if path == '/kcsapi/api_req_practice/battle'
             sortieHp.dmg[i] = enemyHp.dmg[i] = combinedHp.dmg[i] = sortieHp.atk[i] = enemyHp.atk[i] = combinedHp.atk[i] = 0 for i in [0..5]
@@ -522,18 +519,18 @@

         # Combined battle
         when '/kcsapi/api_req_combined_battle/airbattle', '/kcsapi/api_req_combined_battle/sp_midnight', '/kcsapi/api_req_combined_battle/battle', '/kcsapi/api_req_combined_battle/battle_water', '/kcsapi/api_req_combined_battle/midnight_battle'
-          flag = true
+          shouldRender = true
           escapeId = towId = -1
-          isWater = false
+          isSurface = false
           # If water combined
           if path == '/kcsapi/api_req_combined_battle/battle_water'
-            isWater = true
+            isSurface = true
           daySortieDmg = Object.clone sortieHp.dmg
           dayEnemyDmg = Object.clone enemyHp.dmg
           dayCombinedDmg = Object.clone combinedHp.dmg
           if path != '/kcsapi/api_req_combined_battle/midnight_battle'
             getEnemyInfo enemyHp, enemyInfo, body, false
-          result = simulateBattle sortieHp, enemyHp, combinedHp, true, isWater, body, 1, planeCount, sortieInfo, combinedInfo, mvpPos
+          result = simulateBattle sortieHp, enemyHp, combinedHp, true, isSurface, body, 1, planeCount, sortieInfo, combinedInfo, mvpPos
           for i in [0..5]
             sortieHp.dmg[i] -= daySortieDmg[i]
             enemyHp.dmg[i] -= dayEnemyDmg[i]
@@ -541,7 +538,7 @@

         # Battle Result
         when '/kcsapi/api_req_practice/battle_result', '/kcsapi/api_req_sortie/battleresult', '/kcsapi/api_req_combined_battle/battleresult'
-          flag = true
+          shouldRender = true
           if path != '/kcsapi/api_req_practice/battle_result'
             if body.api_escape_flag? && body.api_escape_flag > 0
               escapeId = body.api_escape.api_escape_idx[0] - 1
@@ -564,7 +561,7 @@

         # Return to port
         when '/kcsapi/api_port/port'
-          flag = true
+          shouldRender = true
           goBack = Object.clone initData
           combinedFlag = body.api_combined_flag
           combinedFlag ?= 0
@@ -605,7 +602,7 @@
           enemyPlane = " #{planeCount.enemy[0]}/#{planeCount.enemy[1]}"
         seiku = dispSeiku[planeCount.seiku]

-      if flag
+      if shouldRender
         @setState
           sortieHp: sortieHp
           enemyHp: enemyHp


fix mistake

fix mistake

--- index.cjsx
+++ index.cjsx
@@ -309,12 +309,10 @@
       SupportFire enemyFleet, body.api_support_info.api_support_hourai.api_damage
     else
       SupportFire enemyFleet, body.api_support_info.api_damage
-  else if body.api_air_base_attack?
-    if body.api_air_base_attack.length == 1 && body.api_air_base_attack[0]?.api_stage3?.api_edam?
-      SupportFire enemyFleet, body.api_air_base_attack[0].api_stage3.api_edam
-    else if body.api_air_base_attack.length == 2 && body.api_air_base_attack[0]?.api_stage3?.api_edam? && body.api_air_base_attack[1]?.api_stage3?.api_edam?
-      SupportFire enemyFleet, body.api_air_base_attack[0].api_stage3.api_edam
-      SupportFire enemyFleet, body.api_air_base_attack[1].api_stage3.api_edam
+  if body.api_air_base_attack?
+    for airBase in body.api_air_base_attack
+      if airBase.api_stage3?.api_edam?
+        SupportFire enemyFleet, airBase.api_stage3.api_edam
   # Opening battle
   if body.api_opening_atack?
     if combinedFlag > 0


Fix double show answer bug

Fix double show answer bug

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -192,8 +192,11 @@
     if file_not_selected
       errors.push 'You did not select any files to submit'

-    if errors.length > 0
-        alert errors.join("\n")
+    error_html = '
\n'
+    for error in errors
+      error_html += '
' + error + '
\n'
+    error_html += '
'
+    @gentle_alert error_html

     abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted

@@ -208,7 +211,7 @@
             @render(response.contents)
             @updateProgress response
           else
-            alert(response.success)
+            @gentle_alert response.success

     if not abort_submission
       $.ajaxWithPrefix("#{@url}/problem_check", settings)
@@ -220,8 +223,10 @@
         when 'incorrect', 'correct'
           @render(response.contents)
           @updateProgress response
+          if @el.hasClass 'showed'
+            @el.removeClass 'showed'
         else
-          alert(response.success)
+          @gentle_alert response.success

   reset: =>
     Logger.log 'problem_reset', @answers
@@ -253,15 +258,19 @@
       @el.removeClass 'showed'
       @$('.show').val 'Show Answer'

+  gentle_alert: (msg) =>
+    if @el.find('.capa_alert').length
+      @el.find('.capa_alert').remove()
+    alert_elem = "
" + msg + "
"
+    @el.find('.action').after(alert_elem)
+    @el.find('.capa_alert').animate(opacity: 0, 500).animate(opacity: 1, 500)
+
   save: =>
     Logger.log 'problem_save', @answers
     $.postWithPrefix "#{@url}/problem_save", @answers, (response) =>
       if response.success
-        if @el.find('.save_message').length
-            @el.find('.save_message').animate(opacity: 0, 500).animate(opacity: 1, 500)
-        else
-            saveMessage = "
Your answers have been saved but not graded. Hit 'Check' to grade them.
"
-            @el.find('.action').after(saveMessage)
+        saveMessage = "Your answers have been saved but not graded. Hit 'Check' to grade them."
+        @gentle_alert saveMessage
       @updateProgress response

   refreshMath: (event, element) =>


Impl lifecycle. Remove setting

Impl lifecycle. Remove setting

--- index.cjsx
+++ index.cjsx
@@ -64,10 +64,20 @@
     shipgraph: []
   componentDidMount: ->
     window.addEventListener 'game.response', @handleResponse
-    window.addEventListener 'secretary.config.disable', @handleDisable
+    window.addEventListener 'secretary.load', @pluginDidLoad
+    window.addEventListener 'secretary.unload', @pluginDidUnload
   componentWillUnmount: ->
     window.removeEventListener 'game.response', @handleResponse
-    window.removeEventListener 'secretary.config.disable', @handleDisable
+    window.removeEventListener 'secretary.load', @pluginDidLoad
+    window.removeEventListener 'secretary.unload', @pluginDidUnload
+
+  pluginDidLoad: ->
+    if @state.notifySecretary > 0
+      @updateNotifyConfig(@state.notifySecretary)
+
+  pluginDidUnload: ->
+    for key, id of CONFIG
+      config.set(key, null)

   handleResponse: (e) ->
     {method, path, body, postBody} = e.detail
@@ -111,12 +121,12 @@
     return unless ship_id > 0
     admiral_id = parseInt(window._nickNameId) || 0
     server = SERVERS[(ship_id + admiral_id) % SERVERS.length]
-    shipFN = @state.shipgraph[ship_id]?.api_filename
+    shipFilename = @state.shipgraph[ship_id]?.api_filename
     return unless server
-    return unless shipFN
+    return unless shipFilename
     for key, id of CONFIG
       audioFN = convertFilename ship_id, id
-      setConfig(key, "http://#{server}/kcs/sound/kc#{shipFN}/#{audioFN}.mp3")
+      setConfig(key, "http://#{server}/kcs/sound/kc#{shipFilename}/#{audioFN}.mp3")

   handleShipChange: (e) ->
     ship_id = parseInt(e.target.value)
@@ -222,28 +232,9 @@
     </div>

-SecretarySettingArea = React.createClass
-  handleDisable: ->
-    # Write config
-    for key, id of CONFIG
-      config.set(key, null)
-    config.set('plugin.secretary.ship', -1)
-    # Dispatch event
-    event = new Event 'secretary.config.disable'
-    window.dispatchEvent event
-
-  render: ->
-    <div>
-      <Grid>
-        <Col xs={6}>
-          <Button bsStyle='warning' style={width: '100%'} onClick={@handleDisable}>
-            {__ 'Reset to default audio poi'}
-          </Button>
-        </Col>
-      </Grid>
-    </div>
-
-
 module.exports =
   reactClass: SecretaryArea
-  settingsClass: SecretarySettingArea
+  pluginDidLoad: ->
+    window.dispatchEvent new Event 'secretary.load'
+  pluginDidUnload: ->
+    window.dispatchEvent new Event 'secretary.unload'


Update height of DisplayBuffer from editor component on overflow changed

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -223,6 +223,9 @@
     @refs.verticalScrollbar.getDOMNode().scrollTop -= event.wheelDeltaY
     event.preventDefault()

+  onOverflowChanged: ->
+    @props.editor.setHeight(@refs.scrollView.getDOMNode().clientHeight)
+
   onInput: (char, replaceLastChar) ->
     @props.editor.insertText(char)


Fix external library (js-yaml)

Fix external library (js-yaml)

--- src/editor/mrbviewer.coffee
+++ src/editor/mrbviewer.coffee
@@ -86,7 +86,7 @@
         m.push("This file is not supported")
       if m.length > 0
         r.messages = m
-      return jsyaml.safeDump(r, {styles: {"!!int": "hex"}}) # Last PromiseValue
+      return JsYaml.safeDump(r, {styles: {"!!int": "hex"}}) # Last PromiseValue
     ) # return Promise.resolve().then()

   #--------------------------------------------------------------------------------
@@ -322,7 +322,8 @@
         else
           args = ",...,R(#{b+c})"
         return ["OP_HASH\t#{a}, #{b}, #{c}", "R(#{a}) := hash_new(R(#{b})#{args})"]
-      # when 0x40 => OP_LAMBDA
+      when 0x40
+        return ["OP_LAMBDA\t#{a}, #{b}, #{c}", "R(#{a}) := lambda(SEQ[#{b}],#{c})"]
       when 0x41
         return ["OP_RANGE\t#{a}, #{b}, #{c}", "R(#{a}) := range_new(R(#{b}),R(#{b+1}),C)"]
       when 0x42
@@ -354,4 +355,4 @@
         return [sprintf("0x%08x", code), "Unknown opcode"]

 # Post dependencies
-# (none)
+JsYaml = global.Libs.JsYaml


fixed coffeelint errors on workers/social/lib/social/models/payment/resourceplan.coffee

--- workers/social/lib/social/models/payment/resourceplan.coffee
+++ workers/social/lib/social/models/payment/resourceplan.coffee
@@ -1,4 +1,5 @@
-{ Base } = require 'bongo'
+{ Base }    = require 'bongo'
+KodingError = require '../../error'

 module.exports = class JResourcePlan extends Base

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

     plan.fetchProducts (err, products) ->
       return callback err  if err
-      return callback message: "no products found"  unless products?.length
+      return callback new KodingError 'no products found'  unless products?.length

       quantities = {}

@@ -64,3 +65,5 @@
       options.plan = plan

       @subscribeToPlan client, options, callback
+
+


taking advantage of backbone.collections' de-duping to prevent exporting a list twice. fixes tp #538

--- dkobo/koboform/static/js/xlform/XLF.model.survey.coffee
+++ dkobo/koboform/static/js/xlform/XLF.model.survey.coffee
@@ -57,14 +57,14 @@
       rowObjects: oRows

     choicesCsvJson = do =>
-      lists = []
+      lists = new XLF.ChoiceLists()
       @forEachRow (r)->
         if (list = r.getList())
-          lists.push(list)
+          lists.add list

       rows = []
       cols = ["list name", "name", "label"]
-      for choiceList in lists
+      for choiceList in lists.models
         choiceList.set("name", XLF.txtid(), silent: true)  unless choiceList.get("name")
         clName = choiceList.get("name")
         for option in choiceList.options.models


Deleted time icon at comment-time

Deleted time icon at comment-time

--- core/app/backbone/views/evidence/react_comment.coffee
+++ core/app/backbone/views/evidence/react_comment.coffee
@@ -11,7 +11,6 @@
         rel: "backbone"
         @model().creator().get('name')
       R.span className:"comment-bottom-action comment-post-time",
-          R.i className:"icon-time"
           @model().get('time_ago')
           " "
           Factlink.Global.t.ago


working on better wall graphic

working on better wall graphic

--- src/environment/Wall.coffee
+++ src/environment/Wall.coffee
@@ -12,9 +12,17 @@

     @sprite = new Pixi.Graphics()
+    @path = []
+    @corners = [new Pixi.Point(0.5*@w, 0.5*@h),
+    new Pixi.Point(0.5*@w, -0.5*@h),
+    new Pixi.Point(-0.5*@w, -0.5*@h),
+    new Pixi.Point(-0.5*@w, 0.5*@h)]
     @sprite.beginFill(0x000000)
-    @sprite.drawRect(-0.5 * @w, -0.5 * @h, @w, @h)
+    @sprite.drawPolygon(@corners)
     @sprite.endFill()
+    # @sprite.beginFill(0x000000)
+    # @sprite.drawRect(-0.5 * @w, -0.5 * @h, @w, @h)
+    # @sprite.endFill()

     @body = new p2.Body {
       position: [x, y]


resurrection.activity: remove `TagsChanged` event handler of `ActivityItemChild` class

--- client/Social/Activity/views/activityitemchild.coffee
+++ client/Social/Activity/views/activityitemchild.coffee
@@ -36,12 +36,6 @@

     @author = new ProfileLinkView { origin }

-    @tags = new ActivityChildViewTagGroup
-      itemsToShow   : 3
-      itemClass  : TagLinkView
-    , data.tags
-
-
     # for discussion, switch to the View that supports nested structures
     # JDiscussion,JTutorial
     # -> JOpinion
@@ -75,11 +69,6 @@
     super options, data

     data = @getData()
-    data.on 'TagsChanged', (tagRefs)=>
-      KD.remote.cacheable tagRefs, (err, tags)=>
-        @getData().setAt 'tags', tags
-        @tags.setData tags
-        @tags.render()

     deleteActivity = (activityItem)->
       activityItem.slideOut -> activityItem.destroy()


Add read only functionnality on published node

--- BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
@@ -56,6 +56,12 @@
     for area of @node.get('areas')
       @addAreaToView(@node.get('areas')[area])
     @addVersionToView()
+    if @node.attributes.status.published
+        $('.ui-model *', @el).unbind()
+        $('.js-widget-panel').hide()
+    else
+      $("ul.ui-model-areas, ul.ui-model-blocks", @$el).each ->
+        refreshUl $(this)
     return
   addAreaToView: (area) ->
     areaElement = new Area
@@ -66,12 +72,6 @@
       displayClass: (if @node.get("bo_direction") is "v" then "inline" else "block")
     )
     @$el.find('ul.ui-model-areas').first().append  areaView.render().el
-    if @node.attributes.status.published
-        $('.ui-model *', @el).unbind()
-        $('.js-widget-panel').hide()
-    else
-      $("ul.ui-model-areas, ul.ui-model-blocks", @$el).each ->
-        refreshUl $(this)
     return
   addVersionToView: ->
     viewContext = this


#167 Shrink mouse wheel delta (due to new jquery-mousewheel version)

--- app/assets/javascripts/libs/input.coffee
+++ app/assets/javascripts/libs/input.coffee
@@ -294,6 +294,8 @@

   mouseWheel : (event, delta) =>

+    delta /= 20
+
     event.preventDefault()
     if event.shiftKey
       @trigger("scroll", delta, "shift")


fix wrong check for ctrl key being pressed in keyboard hook; #1342

--- app/assets/javascripts/libs/input.coffee
+++ app/assets/javascripts/libs/input.coffee
@@ -37,7 +37,7 @@
     shouldIgnore = (event) ->
       bindingHasCtrl  = key.toLowerCase().indexOf("ctrl") != -1
       bindingHasShift = key.toLowerCase().indexOf("shift") != -1
-      eventHasCtrl  = event.ctrl or event.metaKey
+      eventHasCtrl  = event.ctrlKey or event.metaKey
       eventHasShift = event.shiftKey
       return (eventHasCtrl and not bindingHasCtrl) or
         (eventHasShift and not bindingHasShift)


Added ViewResized event emitter for content panel.

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -173,9 +173,11 @@
       @finderResizeHandle._dragged = yes
       newFpWidth = @_fpWidth + x
       return @finderResizeHandle.$().css left: '' if newFpWidth < 13
-      cp.$().css left : cp._left + x, width : cp._width - x
+      cpWidth = cp._width - x
+      cp.$().css left : cp._left + x, width : cpWidth
       @finderResizeHandle.$().css left: ''
       $fp.css "width", newFpWidth
+      cp.emit "ViewResized", {newWidth : cpWidth, unit: "px"}

     KD.utils.wait 8000, =>
       @$('#finder-bottom-controls').addClass 'go-down'


List drawing tools

List drawing tools

--- app/classifier/tasks/generic-editor.cjsx
+++ app/classifier/tasks/generic-editor.cjsx
@@ -1,5 +1,6 @@
 React = require 'react'
 handleInputChange = require '../../lib/handle-input-change'
+drawingTools = require '../drawing-tools'

 NextTaskSelector = React.createClass
   displayName: 'NextTaskSelector'
@@ -79,8 +80,8 @@
                   [

                     Type{' '}
                     <select name="tasks.#{@props.taskKey}.#{choicesKey}.#{index}.type" value={choice.type} onChange={handleChange}>
-                      <option>Point</option>
-                      <option disabled>TODO: List available drawing tools.</option>
+                      {for toolName of drawingTools
+                        <option value={toolName}>{toolName}</option>}
                     </select>

@@ -95,12 +96,12 @@
                       <option value="#ff00ff">Magenta</option>
                       <option value="#000000">Black</option>
                       <option value="#ffffff">White</option>
-                      <option disabled>TODO: Use a real color picker.</option>
+                      <option disabled>TODO: Picker</option>
                     </select>

-                    TODO: Add a details editor, maybe in a tooltip.
+                    TODO: Add a details editor, probably in a popup.

]}

             <button type="button" className="workflow-choice-remove-button" title="Remove choice" onClick={@removeChoice.bind this, choicesKey, index}>×</button>


Focus choice buttons in alphabetical order

--- app/classifier/tasks/survey/chooser.cjsx
+++ app/classifier/tasks/survey/chooser.cjsx
@@ -19,6 +19,24 @@
   componentWillMount: ->
     # refs for the choices
     @choiceButtons = []
+
+  componentDidMount: ->
+    @sortChoiceButtons()
+
+  componentDidUpdate: ->
+   @sortChoiceButtons()
+
+  sortChoiceButtons: ->
+    # overrides default DOM focus order by sorting the buttons in alphabetical order
+    @choiceButtons = @choiceButtons
+      .filter Boolean
+      .sort (a, b) =>
+        order = 0
+        if a.textContent < b.textContent
+          order = -1
+        if a.textContent > b.textContent
+          order = 1
+        order

   getFilteredChoices: ->
     for choiceID in @props.task.choicesOrder


Small fix on history loading errors

Small fix on history loading errors

--- app/coffee/modules/common/history.coffee
+++ app/coffee/modules/common/history.coffee
@@ -61,7 +61,7 @@
         return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId)

-HistoryDirective = ($log, $loading, $qqueue, $template) ->
+HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
     templateChangeDiff = $template.get("common/history/history-change-diff.html", true)
     templateChangePoints = $template.get("common/history/history-change-points.html", true)
     templateChangeGeneric = $template.get("common/history/history-change-generic.html", true)
@@ -357,4 +357,4 @@
     }

-module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", "$tgTemplate", HistoryDirective])
+module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", "$tgTemplate", "$tgConfirm", HistoryDirective])


fix luhn test description (oops)

fix luhn test description (oops)

--- exercises/luhn/luhn.spec.coffee
+++ exercises/luhn/luhn.spec.coffee
@@ -66,6 +66,6 @@
     luhn = new Luhn('055b 444 285')
     expect(luhn.valid).toEqual(false)

-  xit 'using ascii value for non-doubled non-digit isn\'t allowed', ->
+  xit 'using ascii value for doubled non-digit isn\'t allowed', ->
     luhn = new Luhn(':9')
     expect(luhn.valid).toEqual(false)


Adding black backdrop to start screen

Adding black backdrop to start screen

--- src/game/start_stage.coffee
+++ src/game/start_stage.coffee
@@ -25,10 +25,13 @@
     @ctx.textAlign = 'center'
     @ctx.font = '24px Georgia'

+    @ctx.fillStyle = 'black'
+    @ctx.fillRect(0, @height / 2, @width, @height / 2)
+
     @ctx.fillStyle = 'red'
     @ctx.fillText('Red Guard', beginW, beginH)

-    @ctx.fillStyle = 'black'
+    @ctx.fillStyle = 'white'
     @ctx.fillText('W', beginW, beginH + heightInc)
     @ctx.fillText('S', beginW, beginH + heightInc * 2)
     @ctx.fillText('D', beginW, beginH + heightInc * 3)
@@ -38,10 +41,10 @@
     @ctx.fillText('- Move Down - ', middle, beginH + heightInc * 2)
     @ctx.fillText('- Fire -', middle, beginH + heightInc * 3)

-    @ctx.fillStyle = 'blue'
+    @ctx.fillStyle = 'cyan'
     @ctx.fillText('Blue Guard', endWidth, beginH)

-    @ctx.fillStyle = 'black'
+    @ctx.fillStyle = 'white'
     @ctx.fillText('UP', endWidth, beginH + heightInc)
     @ctx.fillText('DOWN', endWidth, beginH + heightInc * 2)
     @ctx.fillText('LEFT', endWidth, beginH + heightInc * 3)


Conditional field presence validation.

Conditional field presence validation.

--- api/models/UserPassword.coffee
+++ api/models/UserPassword.coffee
@@ -11,9 +11,33 @@
   attributes:
     user_id:
       type: 'string'
+      required: yes
       hexadecimal: yes
-      required: yes
     email:
       type: 'string'
+      required: yes
       email: yes
+    mobile:
+      type: 'string'
       required: yes
+    application_id:
+      type: 'string'
+    email_template:
+      type: 'string'
+
+  # Validate presence at least one of fields.
+  beforeValidate: (values, cb) ->
+    # The list of fields.
+    fields = ['user_id', 'email', 'mobile']
+
+    # Check if at least one of the fields is found.
+    found = false
+    for field in fields
+      if values[field]? and !_.isEmpty values[field]
+        found = true
+        break
+
+    # Turn off presence validations when at least one fields is found,
+    # turn on when all fields absent.
+    this._validator.validations[field].required = !found for field in fields
+    cb()


SO PROGRESS. MUCH THINK.

SO PROGRESS. MUCH THINK.

--- src/backend/channel.coffee
+++ src/backend/channel.coffee
@@ -55,6 +55,27 @@
         if not @verifyServerSignedT2 result.signature
           throw new Error "Couldn't verify server agreed to and signed T2 transaction"

+        # FIXME
+        agreementT1Sig = @agreementTxT1.someFunctionToGetTheSig()
+
+        # Create a payment that is just a fully unlocked refund
+        paymentTxT3Builder = @createPayTxT3(0, undefined).tx
+        paymentTxT3 = paymentTxT3Builder.sign(@privkeyK1).build()
+
+
+        params =
+          "channel.id": @channelId
+          "tx.commit": agreementT1Sig
+          "tx.firstPayment": paymentTxT3.outs[0].s.toString('hex')
+
+        return Q.nfcall(rpcClient.call, "channel.commit", [params], {})
+
+    ).then(
+      (result) ->
+        # Now, we have established the protocol, and just need to start
+        # requesting services by altering the amount in paymentTxT3, re-sign,
+        # and send it to the server
+
     ).done(
       (result) -> callback result
       callback
@@ -71,10 +92,7 @@
     return CoinUtils.buildRollingRefundTxFromMultiSigOutput @agreementTxT1, @pubkeyK1, 0, undefined, timeToLock

   verifyServerSignedT2: (signature) ->
-    # TODO
-    return true
-
-  signRefundTx: ->
+    return CoinUtils.verifyTxSig @refundTxT2, signature

   createPayTxT3: (amount, serverPubKey) ->
     return CoinUtils.buildRollingRefundTxFromMultiSigOutput @agreementTxT1, @pubkeyK1, amount, serverPubKey, 0


tweaked subpage pjax transition

tweaked subpage pjax transition

--- app/_src/pjaxnavigation.coffee
+++ app/_src/pjaxnavigation.coffee
@@ -99,13 +99,12 @@
         # short transition when moving between pages
         TweenLite.fromTo(@$content[0], 0.5, {
           y: @yTo/3
-          #opacity: 0
+          opacity: 0
           display: 'block'
         },
         {
           y: 0,
-          #opacity: 1
-          delay: 0.1
+          opacity: 1
           ease: Circ.easeOut
           clearProps: 'all'
         })


only log err if there is an error

only log err if there is an error

--- app/coffee/BuildManager.coffee
+++ app/coffee/BuildManager.coffee
@@ -21,7 +21,8 @@
 			return callback(error) if error?
 			# Build in the background
 			BuildManager.buildCommit ghclient, repo, sha, (error) ->
-				logger.error err:error, repo: repo, sha: sha, "background build failed"
+				if error?
+					logger.error err:error, repo: repo, sha: sha, "background build failed"
 			callback()

 	buildCommit: (ghclient, repo, sha, callback = (error) ->) ->


fix: enable buttons after request

fix: enable buttons after request

--- app/components/auth-mfa.coffee
+++ app/components/auth-mfa.coffee
@@ -66,12 +66,12 @@
       @set "isEnablingMFA", true
       @get("ajax").post ENV.endpoints.enableMFA, data: data
       .then (data)->
-        that.get "isEnablingMFA", false
+        that.set "isEnablingMFA", false
         that.get("notify").success tMFAEnabled
         that.set "enableMFAOTP", ""
         that.set "showMFAEnableModal", false
       .catch (error) ->
-        that.get "isEnablingMFA", false
+        that.set "isEnablingMFA", false
         that.get("notify").error error.payload.message
         for error in error.errors
           that.get("notify").error error.detail?.message
@@ -85,15 +85,15 @@
         return @get("notify").error tEnterOTP if !isValidOTP otp
       data =
         otp: disableMFAOTP
-      @set "isDisablingMFA", true
+      @set "isDisablingMFA", true
       @get("ajax").post ENV.endpoints.disableMFA, data: data
       .then (data)->
-        that.get "isDisablingMFA", false
+        that.set "isDisablingMFA", false
         that.get("notify").success tMFADisabled
         that.set "disableMFAOTP", ""
         that.set "showMFADisableModal", false
       .catch (error) ->
-        that.get "isDisablingMFA", false
+        that.set "isDisablingMFA", false
         that.get("notify").error error.payload.message
         for error in error.errors
           that.get("notify").error error.detail?.message


add roll_high parameter for rolling

add roll_high parameter for rolling

--- app/js/controllers/dice.coffee
+++ app/js/controllers/dice.coffee
@@ -41,11 +41,15 @@
         $scope.precision = $scope.balance.precision;
         $scope.balance = $scope.balance.amount / $scope.balance.precision
         $scope.reloadDices()
-        $scope.diceSmall =$scope.diceBig = ->
+        dice = (roll_high)->
             Wallet.dice(account.name, $scope.amount, $scope.payouts).then (tx)->
                 console.log(tx);
                 $scope.transactions.splice(0,0,{transaction:tx, transaction_id_prev:'????', jackpot:{play_amount:$scope.amount,payouts:$scope.payouts}});
                 setTimeout($scope.reloadDices, 10000)
+        $scope.diceSmall = ->
+            dice(false)
+        $scope.diceBig = ->
+            dice(true)

     $scope.enlargeBetSizeBy= (enlargeBy)->
         $scope.amount*=enlargeBy;


Update zh-HANS.coffee (#5560)

Update zh-HANS.coffee (#5560)

* Update zh-HANS.coffee

* Update zh-HANS.coffee

* Update zh-HANS.coffee

* Update zh-HANS.coffee

--- app/locale/zh-HANS.coco.coffee
+++ app/locale/zh-HANS.coco.coffee
@@ -1268,7 +1268,7 @@
     create_account_subtitle: "在 CodeCombat 班级中使用老师专用工具。 创建班级添加学生、观察学生进度！"
     convert_account_title: "升级为老师账号"
     not: "不是"
-#    full_name_required: "First and last name required"
+    full_name_required: "姓和名是必填项"

   versions:
     save_version_title: "保存新版本"
@@ -1359,7 +1359,7 @@
     maximize_editor: "最大化/最小化代码编辑器"

 #  cinematic:
-#    click_anywhere_continue: "click anywhere to continue"
+    click_anywhere_continue: "点击任意区域继续"

   community:
     main_title: "CodeCombat社区"
@@ -2340,12 +2340,12 @@
     left_xp_postfix: ""

   account:
-#    title: "Account"
-#    settings_title: "Account Settings"
-#    unsubscribe_title: "Unsubscribe"
-#    payments_title: "Payments"
-#    subscription_title: "Subscription"
-#    invoices_title: "Invoices"
+    title: "账户"
+    settings_title: "账户设置"
+    unsubscribe_title: "取消订阅"
+    payments_title: "支付"
+    subscription_title: "订阅"
+    invoices_title: "发票"
 #    prepaids_title: "Prepaids"
     payments: "支付方式"
     prepaid_codes: "预付费码"
@@ -2414,11 +2414,11 @@
     you_can3: "使用在您的账户或者给其他人。"

 #  ozaria_chrome:
-#    sound_off: "Sound Off"
-#    sound_on: "Sound On"
-#    back_to_map: "Back to Map"
-#    level_options: "Level Options"
-#    restart_level: "Restart Level"
+    sound_off: "关闭声音"
+    sound_on: "开启声音"
+    back_to_map: "返回地图"
+    level_options: "关卡选项"
+    restart_level: "重新开始这个关卡"

   impact:
     hero_heading: "创建世界一流的计算机科学课程"
@@ -2520,8 +2520,8 @@
     arithmetic: "四则运算"
     array_2d: "二维数组"
     array_index: "数组下标"
-#    array_iterating: "Iterating Over Arrays"
-#    array_literals: "Array Literals"
+    array_iterating: "数组迭代"
+    array_literals: "数组字面量"
     array_searching: "数组遍历"
     array_sorting: "数组排序"
     arrays: "数组"
@@ -2561,7 +2561,7 @@
     game_goals: "游戏目标"
 #    game_spawn: "Game Spawn"
     graphics: "图形"
-#    graphs: "Graphs"
+    graphs: "图"
     heaps: "堆"
     if_condition: "If条件语句"
     if_else_if: "If/Else If语句"
@@ -2582,7 +2582,7 @@
     jquery_selectors: "jQuery选择器"
     length: "数组长度"
 #    math_coordinates: "Coordinate Math"
-#    math_geometry: "Geometry"
+    math_geometry: "几何"
     math_operations: "数学程序库操作"
 #    math_proportions: "Proportion Math"
 #    math_trigonometry: "Trigonometry"
@@ -2607,7 +2607,7 @@
     while_condition_loops: "使用条件语句的While循环"
     while_loops_simple: "While循环"
     while_loops_nested: "While嵌套循环"
-#    xy_coordinates: "Coordinate Pairs"
+    xy_coordinates: "坐标对"
     advanced_strings: "高级字符串" # Rest of concepts are deprecated
     algorithms: "算法"
     boolean_logic: "布尔逻辑"


routes でメソッドを直接登録できるように

routes でメソッドを直接登録できるように

index =()->
	console.log "hage"

class Router extends Kazitori
	routes:
		"/":index

--- src/coffee/kazitori.coffee
+++ src/coffee/kazitori.coffee
@@ -214,7 +214,13 @@

 	registHandler:(rule, name, isBefore, callback )->
 		if not callback
-			callback = if isBefore then @_bindFunctions(name) else @[name]
+			if isBefore
+				callback = @_bindFunctions(name)
+			else if typeof name is "function"
+				callback = name
+			else
+				callback = @[name]
+
 		target = if isBefore then @.beforeHandlers else @.handlers

 		target.unshift new Rule(rule, (fragment)->


修改request对应代码

修改request对应代码

--- lib/module/publish-module-view.coffee
+++ lib/module/publish-module-view.coffee
@@ -111,24 +111,24 @@
 											modulePath: $(checkbox).attr('value')
 										#获取模板最新版本
 										params =
-											success: (data) =>
+											cb: (err, httpResponse, body) =>
 												console.log data
-												contentList = JSON.parse(data)
-												console.log contentList
-												if data['version'] == ""
-													obj['version'] = contetnList['version']
+												if !err && httpResponse.statusCode is 200
+													data = JSON.parse(body)
+													if data['version'] == ""
+														obj['version'] = contetnList['version']
+													else
+														obj['version'] = "0.0.0"
+														console.log obj['version']
+													item = new ModuleMessageItem(obj)
+													item.find('button').attr("disabled",true)
+													_moduleMessageList.append(item)
+													util.fileCompression(PathM.join $(checkbox).attr('value'),'..')
+													callbackOper = ->
+														item.find('button').attr("disabled",false)
+													$(".#{obj.identifier}").fadeOut(3000,callbackOper)
 												else
-													obj['version'] = "0.0.0"
-													console.log obj['version']
-												item = new ModuleMessageItem(obj)
-												item.find('button').attr("disabled",true)
-												_moduleMessageList.append(item)
-												util.fileCompression(PathM.join $(checkbox).attr('value'),'..')
-												callbackOper = ->
-													item.find('button').attr("disabled",false)
-												$(".#{obj.identifier}").fadeOut(3000,callbackOper)
-											error: =>
-												console.log "获取模板最新版本 的url 调不通"
+													console.log "获取模板最新版本 的url 调不通"
 										#调用 获取模块最新版本接口，成功：则返回最新版本【其中空表示为无版本】并显示模块部分信息
 										#失败 则提示 url 调用不成功
 										client.getModuleLastVersion(params,obj.identifier)


show default and conflict programs

show default and conflict programs

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -74,19 +74,22 @@
         world.map_children (child) ->
           label = child.label() if child
           label if label and label.length > 1
-      _AUTHORITY: (world) -> world.find_parent('program_loader')
-      BUTTON2: {
-        i: (world) -> world.get('_INDEX') / 2.0
-        j: 0
-      }
       _CHILDREN: [
         {
           _LABEL: 'default'
-          _CHILDREN: [{_LABEL: 'forward'},{_LABEL: 'forward'}]
+          _AUTHORITY: (world) -> world.get('BUTTON')
+          _CHILDREN: [
+            {_LABEL: "default",stroke: "white",fill: "white"}
+            'forward','left','forward','right'
+          ]
         }
         {
           _LABEL: 'conflict'
-          _CHILDREN: [{_LABEL: 'reverse'}]
+          _AUTHORITY: (world) -> world.get('BUTTON')
+          _CHILDREN: [
+            {_LABEL: "conflict",stroke: "white",fill: "white"}
+            'reverse','left','left'
+          ]
         }
       ]
     }


asset.urlOrDescription: On second thought, make file:// urls come out as file system paths relative to CWD.

--- lib/assets/Asset.coffee
+++ lib/assets/Asset.coffee
@@ -591,20 +591,19 @@
    * @readOnly
   ###
   @getter 'urlOrDescription', ->
-    makeRelativeToAssetGraphRootUrlIfPossible = (url) ->
-      if rootUrl and url.indexOf(rootUrl) is 0
-        "/#{url.substr(rootUrl.length)}"
+    makeRelativeToCwdIfPossible = (url) ->
+      if /^file:\/\//.test(url)
+        return path.relative(process.cwd(), urlTools.fileUrlToFsPath(url))
       else
         url

-    rootUrl = @assetGraph and @assetGraph.root
     if @url
-      makeRelativeToAssetGraphRootUrlIfPossible(@url)
+      makeRelativeToCwdIfPossible(@url)
     else
       "inline #{@type}#{
         if @nonInlineAncestor
           " in #{
-            makeRelativeToAssetGraphRootUrlIfPossible(@nonInlineAncestor.url)
+            makeRelativeToCwdIfPossible(@nonInlineAncestor.url)
           }"
         else
           ''


fix #5

fix #5

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -63,8 +63,8 @@
       cursorStyle.parentNode.removeChild cursorStyle
       cursorStyle = null

-  applyCursorType: (cursorTypeName) =>
-    cursorType = cursorTypeMap[cursorTypeName]
+  applyCursorType: (cursorTypeName) ->
+    cursorType = cursorTypeMap[cursorTypeName] ? cursorTypeName
     workspaceView = atom.views.getView atom.workspace
     workspaceView.className = workspaceView.className.replace /block-cursor-(block|bordered-box|i-beam|underline)/, ''
     workspaceView.className += " block-cursor-#{cursorType}"
@@ -85,7 +85,8 @@
     (interval) ->
       sub?.dispose?()
       sub = atom.workspace.observeTextEditors (editor) ->
-        editorPresenter = atom.views.getView(editor).component.presenter
+        editorPresenter = atom.views.getView(editor)?.component?.presenter
+        return unless editorPresenter
         editorPresenter.stopBlinkingCursors true
         if interval > 0
           editorPresenter.cursorBlinkPeriod = interval


retest optimization for all feeds page

retest optimization for all feeds page

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -497,7 +497,8 @@
                     successWrapper,
                     errorWrapper)
             else
-                NR.API.GetAllPosts(
+                NR.API.GetAllPostsInMultipleFeeds(
+                    this.feedList.filter((i) -> i.numUnread > 0).map((i) -> i.id),
                     0,
                     lastArticleId,
                     this._maxId,


oops, fix regression

oops, fix regression

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -290,6 +290,11 @@

         if this._enableFetch
             this._enableFetch = false
+            minId = this.articleList.at(0).article.id
+            this.articleList.each (i) =>
+                if i.article.id < minId
+                    minId = i.article.id
+
             if this._fid > 0
                 NR.API.GetPostsForFeed(
                     this._fid,


test #2

test #2

--- lib/gesture-side.coffee
+++ lib/gesture-side.coffee
@@ -16,6 +16,7 @@
     top: {}

   constructor: (options) ->
+    console.log "GuestureSide aktivate"
     {@on,@space,@minActivate} = options
     options.element = document.body
     super options


Fix for multiple continuations on the same line

--- lib/line_wrapper.coffee
+++ lib/line_wrapper.coffee
@@ -13,6 +13,7 @@
         @startY      = @document.y
         @column      = 1
         @ellipsis    = options.ellipsis
+        @continuedX  = 0

         # calculate the maximum Y position the text can appear at
         if options.height?
@@ -33,7 +34,7 @@
             @once 'line', =>
                 @document.x -= indent
                 @lineWidth += indent
-                @continuedX = null
+                @continuedX = 0 unless options.continued

         # handle left aligning last lines of paragraphs
         @on 'lastLine', (options) =>
@@ -186,7 +187,7 @@
         # to start the first line of the next segment at, and reset
         # the y position
         if options.continued is yes
-            @continuedX = textWidth
+            @continuedX += textWidth
             @document.y = y
         else
             @document.x = @startX


Fix bug in Line_Wrapper ignoring \n in most cases.  (Causes text wrapping to be wrong)

--- lib/line_wrapper.coffee
+++ lib/line_wrapper.coffee
@@ -64,7 +64,7 @@
             for word, wi in words
                 w = wordWidths[word] ?= width(word, options) + charSpacing + wordSpacing

-                if w > spaceLeft
+                if w > spaceLeft or word is '\n'
                     options.textWidth = width(buffer.trim(), options) + wordSpacing * (wc - 1)
                     @emit 'line', buffer.trim(), options, this


Reset continuedX if there is more than one line

--- lib/line_wrapper.coffee
+++ lib/line_wrapper.coffee
@@ -27,7 +27,7 @@
             # if this is the first line of the text segment, and
             # we're continuing where we left off, indent that much
             # otherwise use the user specified indent option
-            indent = @continuedX ? @indent
+            indent = @continuedX or @indent
             @document.x += indent
             @lineWidth -= indent

@@ -109,6 +109,7 @@
         buffer = ''
         textWidth = 0
         wc = 0
+        lc = 0

         y = @document.y # used to reset Y pos if options.continued (below)
         emitLine = =>
@@ -117,6 +118,7 @@
             options.lineWidth = @lineWidth
             y = @document.y
             @emit 'line', buffer, options, this
+            lc++

         @emit 'sectionStart', options, this

@@ -187,7 +189,8 @@
         # to start the first line of the next segment at, and reset
         # the y position
         if options.continued is yes
-            @continuedX += textWidth
+            @continuedX = 0 if lc > 1
+            @continuedX += options.textWidth
             @document.y = y
         else
             @document.x = @startX


:fire: Remove marker logic completely from linter-views

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -10,7 +10,6 @@
     @state = @linter.state
     @subscriptions = new CompositeDisposable
     @messages = []
-    @markers = new WeakMap()
     @panel = new BottomPanel(@state.scope)
     @bottomContainer = new BottomContainer().prepare(@linter.state)
     @bottomBar = null
@@ -98,18 +97,7 @@
     @bottomContainer.setCount(@count)

   renderPanelMarkers: ({added, removed}) ->
-    @removeMarkers(removed)
-    activeEditor = atom.workspace.getActiveTextEditor()
-    return unless activeEditor
-    added.forEach (message) =>
-      return unless message.currentFile
-      @markers.set(message, marker = activeEditor.markBufferRange message.range, {invalidate: 'inside'})
-      activeEditor.decorateMarker(
-        marker, type: 'line-number', class: "linter-highlight #{message.class}"
-      )
-      activeEditor.decorateMarker(
-        marker, type: 'highlight', class: "linter-highlight #{message.class}"
-      ) if @underlineIssues
+

   attachBottom: (statusBar) ->
     @subscriptions.add atom.config.observe('linter.statusIconPosition', (statusIconPosition) =>
@@ -122,20 +110,11 @@
       @bottomContainer.setVisibility(displayLinterInfo)
     )

-  removeMarkers: (messages = @messages) ->
-    messages.forEach((message) =>
-      return unless @markers.has(message)
-      marker = @markers.get(message)
-      marker.destroy()
-      @markers.delete(message)
-    )
-
   removeBubble: ->
     @bubble?.destroy()
     @bubble = null

   dispose: ->
-    @removeMarkers()
     @removeBubble()
     @subscriptions.dispose()
     @bottomBar?.destroy()


:fire: unused

:fire: unused

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -92,10 +92,6 @@
         @vimState.register.set('.', {text})
         # grouping changes for undo checkpoint need to come later than
         @editor.groupChangesSinceCheckpoint(undo)
-      # else
-      #   throw "HOGE"
-      #   console.log item?.constructor.name
-      # @resetUndoCheckpoint()

       replaceModeDeactivator?.dispose()
       replaceModeDeactivator = null


Fixes #48. Reverting the last changes. It actually is worse.

--- lib/preview-view.coffee
+++ lib/preview-view.coffee
@@ -155,12 +155,13 @@
         @hideMessage()
         # Force focus on the editor
         focusOnEditor = =>
-          if @lastEditor?
-            # console.log "Focus on last editor!", @lastEditor
-            uri = @lastEditor.getUri()
-            if pane = atom.workspace.paneForUri(uri)
-              # console.log pane
-              pane.activate()
+          return
+          # if @lastEditor?
+          #   # console.log "Focus on last editor!", @lastEditor
+          #   uri = @lastEditor.getUri()
+          #   if pane = atom.workspace.paneForUri(uri)
+          #     # console.log pane
+          #     pane.activate()

         if error?
           focusOnEditor()


:fire: Remove jQuery from click event listener

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -79,7 +79,7 @@

     @element.addEventListener "mousedown", @onMouseDown
     @element.addEventListener "dblclick", @onDoubleClick
-    @on 'click', '.tab .close-icon', @onClick
+    @element.addEventListener "click", @onClick

     RendererIpc.on('tab:dropped', @onDropOnOtherWindow)

@@ -376,6 +376,8 @@
       preventDefault()

   onClick: ({target}) =>
+    return unless contains(@element.querySelectorAll(".tab .close-icon"), target)
+
     tab = closest(target, '.tab')
     @pane.destroyItem(tab.item)
     false


:fire: Remove a redundant view

:fire: Remove a redundant view

--- lib/views/bubble.coffee
+++ lib/views/bubble.coffee
@@ -1,40 +0,0 @@
-{Range} = require 'atom'
-Message = require './message'
-
-class Bubble
-  constructor: (@linter) ->
-    @bubble = null
-  render: (message) ->
-    root = document.createElement 'div'
-    root.id = 'linter-inline'
-    MessageLine = new Message()
-    MessageLine.initialize(message, false)
-    root.appendChild MessageLine
-    if message.trace and message.trace.length
-      message.trace.forEach (trace) ->
-        MessageLine = new Message()
-        MessageLine.initialize(trace, true)
-        root.appendChild MessageLine
-    root
-
-  update: (point) ->
-    @remove()
-    return unless @linter.views._messages.length
-    textEditor = @linter.activeEditor
-    found = false
-    @linter.views._messages.forEach (message) =>
-      return if found
-      return unless message.currentFile
-      return unless message.range
-      return unless message.range.containsPoint point
-      marker = textEditor.markBufferRange message.range, {invalidate: 'never'}
-      @bubble = textEditor.decorateMarker(marker, {
-        type: 'overlay',
-        position: 'tail',
-        item: @render(message)
-      })
-      found = true
-  remove: ->
-    @bubble?.destroy()
-
-module.exports = Bubble


Social: limit popular topics

Social: limit popular topics

--- workers/social/lib/social/cache/socialapi.coffee
+++ workers/social/lib/social/cache/socialapi.coffee
@@ -14,6 +14,7 @@
     options =
       type        : "weekly"
       channelName : "koding"
+      limit       : 5

     SocialChannel.fetchPopularTopics client, options, cb


NEW: FullScreenApp now supports loading AFTER Dom ready

--- source/Art/Engine/FullScreenApp.coffee
+++ source/Art/Engine/FullScreenApp.coffee
@@ -2,7 +2,9 @@

 {merge, Promise, parseQuery, log, ConfigRegistry, isPlainObject} = Foundation
 {Meta, Link} = Foundation.Browser.DomElementFactories
+{getDomReadyPromise} = Foundation.Browser
 {FontLoader} = require 'art-canvas'
+{rgbColor} = require 'art-atomic'

 module.exports = class FullScreenApp

@@ -21,24 +23,30 @@

       ###
       TODO:
-      this indirectly requires jquery...
-      1) dom-console doesn't really need jquery, it just needs a refactor
-      2) I'd like a way to easily build production vs dev code.
-      3) DomConsole should only be included in dev code.
+      1) I'd like a way to easily build production vs dev code.
+      2) DomConsole should only be included in dev code.
+      ANSWER:
+        1. make DomConsole its own NPM
+        2. let webpack rewrite the following require into a noop for production.
       ###
-      DomConsole = require 'art-foundation/dev_tools/dom_console'
+      require 'art-foundation/dev_tools/dom_console'
+      {DomConsole} = Neptune.Art.Foundation.DevTools

       DomConsole.enable()
       Engine.DevTools.GlobalEpochStats.enable() if query.perfGraphs == "true"

     log "Art.Engine.FullScreenApp: app ready"

-  @getDomReadyPromise: ->
-    new Promise (resolve) =>
-      document.onreadystatechange = =>
-        if document.readyState == "interactive"
-          @_domReady()
-          resolve()
+  @_setBodyStyles: ({backgroundColor})->
+    if global.document
+      {body, documentElement} = global.document
+      body.style.padding = "0px"
+      body.style.margin = "0px"
+      body.style.backgroundColor = "#{rgbColor backgroundColor || "#eee"}"
+      body.style.overflow = "hidden"
+      body.style.fontSize = "0px"
+      body.style.height = "100%"
+      documentElement.style.height = "100%"

   ###
   IN:
@@ -65,10 +73,13 @@
     @writeDom config
     Promise.all [
       Promise.resolve fonts && FontLoader.loadFonts fonts
-      @getDomReadyPromise()
+      getDomReadyPromise()
+      .then =>
+        @_domReady()
+        @_setBodyStyles config
     ]

-  @writeDom: ({title, styleSheets, scripts, meta, link, manifest})->
+  @writeDom: ({title, styleSheets, scripts, meta, link, manifest, backgroundColor})->

     document.title = title || "Art App"
     scripts ||= []
@@ -111,7 +122,7 @@
           body {
             padding: 0px;
             margin: 0px;
-            background-color: #eee;
+            background-color: #{rgbColor backgroundColor || "#eee"};
             overflow: hidden;
             font-size: 0px;
             height: 100%;


Support running out of site subdirectory by using ROOT_URL_PATH_PREFIX

--- cookies.coffee
+++ cookies.coffee
@@ -282,7 +282,7 @@
   ###
   send: ->
     if @runOnServer
-      HTTP.get '/___cookie___/set', () -> return
+      HTTP.get (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '') + '/___cookie___/set', () -> return
     else
       throw new Meteor.Error '400', 'Can\'t send cookies on server when `runOnServer` is false.'

@@ -333,4 +333,4 @@
       self.handler and self.handler _cookie
       next()

-Cookies.isLoadedOnServer = false if Meteor.isServer+Cookies.isLoadedOnServer = false if Meteor.isServer


activity cache WIP

activity cache WIP

--- core/KD.coffee
+++ core/KD.coffee
@@ -22,6 +22,9 @@
 String.prototype.capitalize   = ()-> this.charAt(0).toUpperCase() + this.slice(1)
 String.prototype.decapitalize = ()-> this.charAt(0).toLowerCase() + this.slice(1)
 String.prototype.trim         = ()-> this.replace(/^\s+|\s+$/g,"")
+
+# unless Array.prototype.last
+#   Array.prototype.__defineGetter__ "last", -> this[this.length-1]

 # KD Global
 KD = @KD or {}


bump bokehjs version number

bump bokehjs version number

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -6,7 +6,7 @@

   Bokeh = {}

-  Bokeh.version = '0.3.0'
+  Bokeh.version = '0.4.0'

   # common
   Bokeh.Collections       = require("common/base").Collections


Ensure package stylesheets are loaded in autocomplete spec

--- src/packages/autocomplete/spec/autocomplete-spec.coffee
+++ src/packages/autocomplete/spec/autocomplete-spec.coffee
@@ -11,6 +11,7 @@

   beforeEach ->
     editor = new Editor(editSession: fixturesProject.buildEditSessionForPath('sample.js'))
+    atom.loadPackage('autocomplete')
     autocomplete = new Autocomplete(editor)
     miniEditor = autocomplete.miniEditor


course is required

course is required

--- src/course/error-list.cjsx
+++ src/course/error-list.cjsx
@@ -4,7 +4,7 @@
 ErrorList = React.createClass

   propTypes:
-    course: React.PropTypes.instanceOf(Course)
+    course: React.PropTypes.instanceOf(Course).isRequired

   render: ->
     return null unless @props.course.hasErrors()


Ensure custom gutters work properly

Ensure custom gutters work properly

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -2978,6 +2978,54 @@
             expect(decorationState[decoration1.id]).toBeUndefined()
             expect(decorationState[decoration3.id].top).toBeDefined()

+          it "updates when block decorations are added, changed or removed", ->
+            # block decoration before decoration1
+            blockDecoration1 = editor.addBlockDecorationForScreenRow(0)
+            presenter.setBlockDecorationDimensions(blockDecoration1, 0, 3)
+            # block decoration between decoration1 and decoration2
+            blockDecoration2 = editor.addBlockDecorationForScreenRow(3)
+            presenter.setBlockDecorationDimensions(blockDecoration2, 0, 5)
+            # block decoration between decoration2 and decoration3
+            blockDecoration3 = editor.addBlockDecorationForScreenRow(10)
+            presenter.setBlockDecorationDimensions(blockDecoration3, 0, 7)
+
+            decorationState = getContentForGutterWithName(presenter, 'test-gutter')
+            expect(decorationState[decoration1.id].top).toBe lineHeight * marker1.getScreenRange().start.row
+            expect(decorationState[decoration1.id].height).toBe lineHeight * marker1.getScreenRange().getRowCount() + 3
+            expect(decorationState[decoration1.id].item).toBe decorationItem
+            expect(decorationState[decoration1.id].class).toBe 'test-class'
+            expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 3 + 5
+            expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7
+            expect(decorationState[decoration2.id].item).toBe decorationItem
+            expect(decorationState[decoration2.id].class).toBe 'test-class'
+            expect(decorationState[decoration3.id]).toBeUndefined()
+
+            presenter.setScrollTop(scrollTop + lineHeight * 5)
+
+            decorationState = getContentForGutterWithName(presenter, 'test-gutter')
+            expect(decorationState[decoration1.id]).toBeUndefined()
+            expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 3 + 5
+            expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7
+            expect(decorationState[decoration2.id].item).toBe decorationItem
+            expect(decorationState[decoration2.id].class).toBe 'test-class'
+            expect(decorationState[decoration3.id].top).toBe lineHeight * marker3.getScreenRange().start.row + 3 + 5 + 7
+            expect(decorationState[decoration3.id].height).toBe lineHeight * marker3.getScreenRange().getRowCount()
+            expect(decorationState[decoration3.id].item).toBe decorationItem
+            expect(decorationState[decoration3.id].class).toBe 'test-class'
+
+            waitsForStateToUpdate presenter, -> blockDecoration1.destroy()
+            runs ->
+              decorationState = getContentForGutterWithName(presenter, 'test-gutter')
+              expect(decorationState[decoration1.id]).toBeUndefined()
+              expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 5
+              expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7
+              expect(decorationState[decoration2.id].item).toBe decorationItem
+              expect(decorationState[decoration2.id].class).toBe 'test-class'
+              expect(decorationState[decoration3.id].top).toBe lineHeight * marker3.getScreenRange().start.row + 5 + 7
+              expect(decorationState[decoration3.id].height).toBe lineHeight * marker3.getScreenRange().getRowCount()
+              expect(decorationState[decoration3.id].item).toBe decorationItem
+              expect(decorationState[decoration3.id].class).toBe 'test-class'
+
           it "updates when ::scrollTop changes", ->
             # This update will scroll decoration1 out of view, and decoration3 into view.
             expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5)


CHG: make sure jquery ajax request send csrf token

--- frontend/javascripts/index.coffee
+++ frontend/javascripts/index.coffee
@@ -6,8 +6,15 @@
 import AttributeManager from './attribute_manager'
 import helpers from './helpers'

+import $ from 'jquery'
 import Rails from 'rails-ujs'
 import Turbolinks from 'turbolinks'
+
+# Make sure that every Ajax request sends the CSRF token
+$.ajaxPrefilter (options, originalOptions, xhr) ->
+  if !options.crossDomain
+    token = $('meta[name=csrf-token]').attr('content')
+    xhr.setRequestHeader('X-CSRF-Token', token) if token

 Rails.start()
 Turbolinks.start()


We cannot extract change:username binding to another component, as the discussion has to be rerendered also

--- core/app/backbone/views/facts/react_discussion.coffee
+++ core/app/backbone/views/facts/react_discussion.coffee
@@ -17,9 +17,16 @@
           key: comment.get('id') || ('new' + new_comment_id++)
           fact_opinionators: @model().fact.getOpinionators()

-window.RefreshIfCurrentUserChanges = React.createClass
+
+window.ReactDiscussion = React.createBackboneClass
+  displayName: 'ReactDiscussion'
+
   getInitialState: ->
     step: 0
+
+  onChange: ->
+    console.info 're-re-refresh'
+    @setState step: @state.step + 1

   componentDidMount: ->
     window.currentUser?.on 'change:username', @onChange, @
@@ -27,40 +34,23 @@
   componentWillUnmount: ->
     window.currentUser?.off null, null, @

-  onChange: ->
-    console.info 're-re-refresh'
-    @setState step: @state.step + 1
-
   render: ->
-    _div ['refresh-on-current-user-changes', key: @state.step],
-      @props.children
-
-
-
-window.ReactDiscussion = React.createBackboneClass
-  displayName: 'ReactDiscussion'
-
-  getInitialState: ->
-    step: 0
-
-  render: ->
-    RefreshIfCurrentUserChanges {},
-      _div ['discussion'],
-        _div ['top-annotation'],
-          _div ['top-annotation-text'],
-            if @model().get('displaystring')
-              @model().get('displaystring')
-            else
-              _div ["loading-indicator-centered"],
-                ReactLoadingIndicator()
-          if Factlink.Global.can_haz.opinions_of_users_and_comments
-            ReactOpinionateArea
-              model: @model().getOpinionators()
-        if FactlinkApp.signedIn()
-          ReactAddComment
-            model: @model().comments()
-            initiallyFocus: @props.initiallyFocusAddComment
-        else
-          ReactOpinionHelp()
-        ReactComments
+    _div ['discussion', key: @state.step],
+      _div ['top-annotation'],
+        _div ['top-annotation-text'],
+          if @model().get('displaystring')
+            @model().get('displaystring')
+          else
+            _div ["loading-indicator-centered"],
+              ReactLoadingIndicator()
+        if Factlink.Global.can_haz.opinions_of_users_and_comments
+          ReactOpinionateArea
+            model: @model().getOpinionators()
+      if FactlinkApp.signedIn()
+        ReactAddComment
           model: @model().comments()
+          initiallyFocus: @props.initiallyFocusAddComment
+      else
+        ReactOpinionHelp()
+      ReactComments
+        model: @model().comments()


payment/payment: normalize callback signature

--- workers/social/lib/social/models/payment/payment.coffee
+++ workers/social/lib/social/models/payment/payment.coffee
@@ -32,7 +32,7 @@
       active          : Boolean

   @makePayment = secure (client, data, callback)->
-    @initialize client, data, (err, group, account, plan)=>
+    @initialize client, data, (err, { group, account, plan })=>
       if err then return callback { message: 'Unable to access payment backend, please try again later.' }

       data.quantity ?= 1  # Default quantity is 1
@@ -79,7 +79,7 @@
           return callback err  if err
           @fetchPlan data.plan, (err, plan) ->
             return callback err  if err
-            callback null, group, account, plan
+            callback null, { group, account, plan }

   # Get plan
   @fetchPlan = (planCode, callback) ->


Fix diplomat view

Fix diplomat view

--- app/views/contribute/DiplomatView.coffee
+++ app/views/contribute/DiplomatView.coffee
@@ -111,6 +111,7 @@
     pl: ['Anon', 'Kacper Ciepielewski', 'TigroTigro', 'kvasnyk']             # język polski, Polish
     it: ['AlessioPaternoster', 'flauta', 'Atomk']              # italiano, Italian
     tr: ['Nazım Gediz Aydındoğmuş', 'cobaimelan', 'gediz', 'ilisyus', 'wakeup']             # Türkçe, Turkish
+    nl: []        # Nederlands, Dutch
     'nl-BE': ['Glen De Cauwsemaecker', 'Ruben Vereecken']        # Nederlands (België), Dutch (Belgium)
     'nl-NL': ['Guido Zuidhof', "Jasper D\'haene"]        # Nederlands (Nederland), Dutch (Netherlands)
     fa: ['Reza Habibi (Rehb)']             # فارسی, Persian


enable strict mode

enable strict mode

--- src/github-calendar.coffee
+++ src/github-calendar.coffee
@@ -1,3 +1,4 @@
+'use strict'
 console.log 'init github-calendar'
 GITHUB_USER_URL = 'https://api.github.com/users'
 user = 'eguitarz'


print event

print event

--- src/github-calendar.coffee
+++ src/github-calendar.coffee
@@ -2,6 +2,10 @@
 GITHUB_USER_URL = 'https://api.github.com/users'
 user = 'eguitarz'

-$.get( GITHUB_USER_URL + '/' + user + '/events').done( (raw)->
-	console.log(raw)
+$.get( GITHUB_USER_URL + '/' + user + '/events').done( (events)->
+	events.forEach (e)->
+		console.log e
+		commitsLength = if e.payload.commits then e.payload.commits.length else 0
+		grid = $('<div>').html( e.type+' '+e.created_at+' '+e.repo.name+' commits:'+commitsLength)
+		$('#github-calendar').append grid
 )


Add Slime->Slim mapping

Add Slime->Slim mapping

--- atom/config.cson
+++ atom/config.cson
@@ -34,6 +34,9 @@
       ]
       "source.ruby": [
         "Brewfile"
+      ]
+      "text.slim": [
+        "slime"
       ]
       "text.xml.plist": [
         "plist"
@@ -113,7 +116,6 @@
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
-    squashDirectoryNames: true
   welcome:
     showOnStartup: false
 ".asciidoc.source":


Update atom config to support chlorine changes

--- atom/config.cson
+++ atom/config.cson
@@ -1,6 +1,10 @@
 "*":
+  chlorine:
+    "eval-mode": "cljs"
+    "refresh-mode": "simple"
   core:
     allowPendingPaneItems: false
+    automaticallyUpdate: false
     disabledPackages: [
       "language-gfm"
       "markdown-preview"
@@ -8,6 +12,7 @@
       "chlorine"
     ]
     openEmptyEditorOnStart: false
+    packagesWithKeymapsDisabled: []
     projectHome: "/Users/thomasmattachione/source"
     telemetryConsent: "no"
     themes: [
@@ -27,8 +32,6 @@
     ignoreErrorsAndWarnings: "D203,D212,D213,D404,D211,D100,D101"
   "linter-ui-default":
     hidePanelWhenEmpty: true
-    panelHeight: 69
-    showPanel: true
   "lisp-paredit":
     strict: false
   "markdown-writer":


optional s at the end of days

optional s at the end of days

--- src/google-calendar.coffee
+++ src/google-calendar.coffee
@@ -46,7 +46,7 @@
     robot.brain.set 'gcal', gcal
     robot.reply "OK, set your calendar to #{calendarId}"

-  robot.respond /gcal look (.*) days ahead/, (msg)->
+  robot.respond /gcal look (.*) day?s ahead/, (msg)->
     daysAhead       = msg.match[1]
     userId          = msg.envelope.user.id
     gcal            = robot.brain.get('gcal') || {}


Add chlorine keybindings to atom

Add chlorine keybindings to atom

Found out that the problem was that the bindings provided by Clojure
suggest using `-,` and this does not seem to work so I had to remove the
`-,`

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -30,3 +30,20 @@
 # If you are unfamiliar with CSON, you can read more about it in the
 # Atom Flight Manual:
 # http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson
+
+'atom-text-editor[data-grammar="source clojure"]':
+  'ctrl-y':       'chlorine:connect-clojure-socket-repl'
+  'ctrl-j':       'chlorine:connect-clojurescript-socket-repl'
+  'ctrl-e':       'chlorine:disconnect'
+  'ctrl-k':       'chlorine:clear-console'
+  'ctrl-f':       'chlorine:load-file'
+  'ctrl-b':       'chlorine:evaluate-block'
+  'ctrl-B':       'chlorine:evaluate-top-block'
+  'ctrl-i':       'chlorine:inspect-block'
+  'ctrl-I':       'chlorine:inspect-top-block'
+  'ctrl-s':       'chlorine:evaluate-selection'
+  'ctrl-C':       'chlorine:break-evaluation'
+  'ctrl-c':       'chlorine:source-for-var'
+  'ctrl-d':       'chlorine:doc-for-var'
+  'ctrl-x':       'chlorine:run-tests-in-ns'
+  'ctrl-t':       'chlorine:run-test-for-var'


cobalt

cobalt

--- code/serv.coffee
+++ code/serv.coffee
@@ -33,7 +33,7 @@
         return true
   if matched?
     name = "#{req.method} #{matched.path}"
-    res.nodetimePromise = nodetime.time 'Custard request ', name, req.url
+    res.nodetimePromise = nodetime.time 'Cobalt request ', name, req.url
     oldSend = res.send
     res.send = (args... ) ->
       res.nodetimePromise.end()


Disable auction reminders on the Miami Fair Week page

--- desktop/components/auction_reminders/blacklist.coffee
+++ desktop/components/auction_reminders/blacklist.coffee
@@ -25,6 +25,7 @@
     '^/venice-biennale.*'
     '^/gallery-partnerships.*'
     '^/article/.*'
+    '^/miami-fair-week'
   ]

   check: ->


Fix config.

Fix config.

--- server/index.coffee
+++ server/index.coffee
@@ -1,7 +1,7 @@
 config =
   port: process.env.PORT or 3000
   mongodb: if process.env.DB_PORT then process.env.DB_PORT.replace('tcp', 'mongodb') + '/educraft' else process.env.MONGODB
-  config:
+  oauth:
     apiUrl: process.env.API_URL
     clientId: process.env.API_CLIENT_ID
     clientSecret: process.env.API_CLIENT_SECRET


Sim: Should actually be max, not min, for determining high-level matches.

--- server/index.coffee
+++ server/index.coffee
@@ -303,7 +303,7 @@
         playerIds = battle.getPlayerIds()
         ratings.getRanks playerIds, (err, ranks) ->
           ranks = _.compact(ranks)
-          if 1 <= Math.min(ranks...) <= MAX_RANK_DISPLAYED
+          if 1 <= Math.max(ranks...) <= MAX_RANK_DISPLAYED
             playerNames = battle.getPlayerNames()
             playerNames = playerNames.map((p, i) -> "#{p} (Rank ##{ranks[i]})")
             message = """A high-level match is being played!


adding a filtered object comprehension test

--- test/fixtures/execution/test_array_comprehension.coffee
+++ test/fixtures/execution/test_array_comprehension.coffee
@@ -1,12 +1,14 @@
-nums: n * n for n in [1, 2, 3] when n % 2 isnt 0
+nums:    n * n for n in [1, 2, 3] when n % 2 isnt 0
 results: n * 2 for n in nums

-obj: {one: 1, two: 2, three: 3}
+obj:   {one: 1, two: 2, three: 3}
 names: key + '!' for value, key in obj
+odds:  key + '!' for value, key in obj when value % 2 isnt 0

 # next: for n in [1, 2, 3] if n % 2 isnt 0
 #   print('hi') if false
 #   n * n * 2

 print(results.join(',') is '2,18')
-print(names.join(' ') is "one! two! three!")+print(names.join(' ')   is "one! two! three!")
+print(odds.join(' ')    is "one! three!")


localize new context items

localize new context items

--- def/context.cson
+++ def/context.cson
@@ -12,10 +12,10 @@
     "core:paste": "ペースト"
     "core:delete": "削除"
     "core:select-all": "すべて選択"
-    "pane:split-up": "ペイン分割 ↑"
-    "pane:split-down": "ペイン分割 ↓"
-    "pane:split-left": "ペイン分割 ←"
-    "pane:split-right": "ペイン分割 →"
+    "pane:split-up-and-copy-active-item": "ペイン分割･複製 ↑"
+    "pane:split-down-and-copy-active-item": "ペイン分割･複製 ↓"
+    "pane:split-left-and-copy-active-item": "ペイン分割･複製 ←"
+    "pane:split-right-and-copy-active-item": "ペイン分割･複製 →"
     "pane:close": "ペインを閉じる"
   "atom-pane":
     "pane:split-up": "ペイン分割 ↑"
@@ -40,12 +40,15 @@
     "tabs:close-tab": "タブを閉じる"
     "tabs:close-other-tabs": "他のタブをすべて閉じる"
     "tabs:close-tabs-to-right": "右側のタブを閉じる"
+    "tabs:close-tabs-to-left": "左側のタブを閉じる"
     "tabs:close-saved-tabs": "保存したタブを閉じる"
     "tabs:close-all-tabs": "タブをすべて閉じる"
     "tabs:split-up": "ペイン分割 ↑"
     "tabs:split-down": "ペイン分割 ↓"
     "tabs:split-left": "ペイン分割 ←"
     "tabs:split-right": "ペイン分割 →"
+  ".tab.texteditor":
+    "tabs:open-in-new-window": "新規ウインドウで開く"
   ".tab.pending-tab":
     "tabs:keep-pending-tab": "プレビュー状態を解除"
   ".tab-bar":


Zero pad 9

Zero pad 9

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -32,7 +32,7 @@
     @xaxis ?= d3.svg.axis().scale(@x).orient('top').tickFormat (d) =>
                d = new Date(@stats.startDate.getTime() + (d * 6e4))
                mins = d.getMinutes()
-               mins = "0#{mins}" if mins < 9
+               mins = "0#{mins}" if mins <= 9
                "#{d.getHours()}:#{mins}"
     @xaxis.tickSize(-h + pt + pb, 50)


Improving local storage methods - making sure that methods exist for both local storage and cookies.

--- assets/coffeescript/local_storage.coffee
+++ assets/coffeescript/local_storage.coffee
@@ -46,8 +46,15 @@
         Object.keys(localStorage).length

       values: ()->
-        values = (v for v,k in localStorage)
-        return values
+        vals = (v for k,v of localStorage)
+        return vals
+
+      keys: ()->
+        keys = (k for k,v of localStorage)
+        return keys
+
+      clear: ->
+        localStorage.clear()
     }
   else
     createCookie = (name, value, days) ->
@@ -78,5 +85,23 @@
         value = LocalStorage.get key
         createCookie key, "", -1
         value
+
+      length: () ->
+        len = document.cookie.split(";").length
+        return len
+
+      values: ()->
+        vals = (v.split("=").pop() for v in document.cookie.split(';'))
+        return vals
+
+      keys: ()->
+        keys = (k.split("=",1) for k in document.cookie.split(';'))
+        return keys
+
+      clear: () ->
+        for cookie in document.cookie.split(';')
+          eqPos = cookie.indexOf('=')
+          name = if eqPos > -1 then cookie.substr(0,eqPos) else cookie
+          document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"
     }


Fixed crash for IPN callbacks that couldn't find the specified user

--- bot.iced
+++ bot.iced
@@ -381,6 +381,7 @@
 	# Credit the user / or cancel the transaction
 	await Users_collection.findOne {lastAddFundTx: ipnParams.tx}, defer(err, user)
 	if err then return console.error err
+	unless user then return console.error "Unable to find user for IPN callback: #{JSON.stringify ipnParams}"

 	amount = 0
 	for transaction, transactionIndex in user.history


Adding ability to learn triggers from teachers

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -7,8 +7,10 @@
 # Notes:
 #   Add helpful triggers when you think of them!

-pasteUrl = "http://laravel.io/bin, https://gist.github.com, or http://kopy.io"
-helpUrl  = "http://help.laravel.io"
+whitelist = require '../support/whitelist'
+
+pasteUrl  = "http://laravel.io/bin, https://gist.github.com, or http://kopy.io"
+helpUrl   = "http://help.laravel.io"

 triggers =
   # Question Assistance
@@ -57,11 +59,18 @@
 triggers["!whois#{nick}"] = triggers["!#{nick}"]

 module.exports = (robot) ->
-  robot.hear /(([^:\s!]+)[:\s]+)?(!\w+)(.*)/i, (msg) ->
+  robot.respond /learn trigger (\![a-zA-Z-_\&\^\!\#]+) (.*)/, (msg) ->
+    return unless whitelist.canAddTriggers(msg.message.user)
+    [name, phrase] = msg.match[1..2]
+
+    robot.brain.set "trigger:#{name}", phrase
+    msg.reply "Got it.  Learned '#{name}' as '#{phrase}'."
+
+  robot.hear /^(([^:\s!]+)[:\s]+)?(!\w+)(.*)/i, (msg) ->
     user          = msg.match[2]
     trigger       = msg.match[3]
     args          = msg.match[4]
-    triggerPhrase = triggers[trigger]
+    triggerPhrase = robot.brain.get("trigger:#{trigger}") || triggers[trigger]

     if triggerPhrase
       if user?


mekko: added legend. Closes #1013

mekko: added legend. Closes #1013

--- app/assets/javascripts/d3/mekko.coffee
+++ app/assets/javascripts/d3/mekko.coffee
@@ -31,6 +31,7 @@
       @node_list = namespace.node_list = new D3.mekko.NodeList()
       @initialize_defaults()

+    outer_height: -> 410
     # lots of ugly transformations to adapt the current conventions
     prepare_data: =>
       sectors  = []
@@ -49,6 +50,7 @@
       for carrier in carriers
         @carrier_list.add
           key: carrier.label
+          label: carrier.label # used by the legend
           color: carrier.color

       for s in @model.series.models
@@ -65,11 +67,11 @@
       margins =
         top: 10
         bottom: 100
-        left: 33
+        left: 35
         right: 10

       @width = 494 - (margins.left + margins.right)
-      @height = 310 - (margins.top + margins.bottom)
+      @height = 410 - (margins.top + margins.bottom)
       @series_height = 190 # the rest of the height will be taken by the legend
       @svg = d3.select("#d3_container_#{@model.get 'key'}")
         .append("svg:svg")
@@ -110,9 +112,16 @@
       @sectors.append("svg:text")
         .text((d) -> d.get 'key')
         .attr("class", "sector_label")
-        .attr("x", - @height - 5)
+        .attr("x", -195)
         .attr("transform", "rotate(270)")
         .attr("text-anchor", "end")
+
+      @draw_legend
+        svg: @svg
+        series: @carrier_list.models
+        width: @width
+        vertical_offset: @series_height + 120
+        columns: 2

       $('rect.carrier').qtip
         content: -> $(this).attr('data-tooltip')


comet: Update documentation.

comet: Update documentation.

--- client/comet.coffee
+++ client/comet.coffee
@@ -78,11 +78,11 @@
 # a message handler on construction. After instantiating, call {#connect}.
 # Any messages from the server will then be passed to the message handler.
 #
-# There are a number of additional options and hnalders available.  See the
+# There are a number of additional options and handlers available.  See the
 # constructor for details.
 #
 # @author Christopher Alfeld (calfeld@calfeld.net)
-# @copyright 2010-2012 Christopher Alfeld
+# @copyright 2010-2013 Christopher Alfeld
 class Heron.Comet

   # Constructor


Add header to FileSummaryElement

Add header to FileSummaryElement

--- lib/changes/file-summary-element.coffee
+++ lib/changes/file-summary-element.coffee
@@ -1,3 +1,11 @@
+# FileSummaryElement
+# ==================
+#
+# This is the element that is shown in the left pane when committing under
+# "Unstaged changes" and "Staged changes". It shows the filename for a change,
+# color coded and with an icon for add/remove/modify, and affords stage and
+# unstage actions.
+
 {CompositeDisposable, Disposable} = require 'atom'

 observe = require '../observe'


remove tracing

remove tracing

--- client/gitrt.coffee
+++ client/gitrt.coffee
@@ -24,7 +24,6 @@

 # note: this isn't called when switching between files
 Template.fileEdit.rendered = ->
-  console.log 'rendered'
   GitRt.cm = CodeMirror.fromTextArea(editor,
     lineNumbers: true
   )
@@ -32,7 +31,6 @@

 # note: this isn't called when switching between files
 Template.fileEdit.destroyed = ->
-  console.log 'destroyed'
   if GitRt.cm
     $(GitRt.cm.getWrapperElement()).remove()
     delete GitRt.cm
@@ -43,7 +41,6 @@
   sendOperation: (revision, operation, selection) ->
     self = this

-    console.log ['send op', arguments...]
     Meteor.call('sendOperation', @filePath, revision, operation,
       selection, -> self.trigger('ack'))

@@ -58,7 +55,6 @@

 Deps.autorun ->
   fileInfo = FileInfo.findOne()
-  console.log ['autorun fileInfo', fileInfo]
   if fileInfo
     # TODO is GitRt.cm guaranteed to be set here? looks like no
     try
@@ -69,7 +65,6 @@
     GitRt.cm.focus()
     # TODO are we going to handle clients ourselves?
     clients = []
-    console.log fileInfo
     serverAdapter = new MeteorServerAdapter(fileInfo._id)
     GitRt.editorClient = new ot.EditorClient(fileInfo.revision, clients,
       serverAdapter, GitRt.cmAdapter)
@@ -82,7 +77,6 @@
       operations = FileOperations.find(
         {_id: {$gt: lastRevision}}, sort: {_id: 1})
       operations.forEach (operation) ->
-        console.log operation
         lastRevision = operation._id
         if operation.clientId != fileInfo.clientId
           serverAdapter.trigger('operation', operation.operation)


changing name changes page correctly

changing name changes page correctly

--- app/assets/javascripts/lists.js.coffee
+++ app/assets/javascripts/lists.js.coffee
@@ -1,5 +1,7 @@
 class App.backbone.List extends Backbone.Model
-  change: -> @save()
+  change: ->
+    @save()
+    @propertiesView.render()

   updateItems: ->
     @set
@@ -203,7 +205,7 @@
 class App.backbone.ListPropertiesView extends Backbone.View
   render: ->
     @$(".name").text(@model.get("name"))
-    $(".list-#{@model.get("id")} a").text(@model.get("name"))
+    $("li[data-list-id=#{@model.get("id")}] a").text(@model.get("name"))
     @$(".description").text(@model.get("description"))
     this


Fix javascript issue

Fix javascript issue

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -11,4 +11,5 @@
   .bind "blur", ->
     if( $(this).val() == "" )
       $(this).parents("form.note").removeClass "active"
+  .blur()


Very minor cleanup of inline image expansion

--- app/assets/javascripts/posts.js.coffee
+++ app/assets/javascripts/posts.js.coffee
@@ -6,6 +6,5 @@
     e.preventDefault();
     $(this).toggleClass('unexpanded');
     a = $(this).children('a');
-    src = a.attr('href');
-    a.children('img').attr('src', src);
+    a.children('img').attr('src', a.attr('href'));
 );


update pivot table views

update pivot table views

--- modules/pivot-table/main/index.coffee
+++ modules/pivot-table/main/index.coffee
@@ -22,13 +22,10 @@
   rowViewEnter = (data, isHead) ->
     rowNode = @append('tr').node()
     rowView = hx.select(rowNode).view('.hx-pivot-table-cell')
-      .enter (datum, index) ->
-        # index starts at 0 for new cells added to a row so we override the
-        # value here.
+      .enter (datum) ->
         index = data.indexOf(datum)
         cellViewEnter.call(this, datum, index, isHead)
       .update (datum, node, index) ->
-        index = data.indexOf(datum)
         cellViewUpdate(datum, node, index, isHead)
     # create the view once on enter and re-use it in the update
     hx.component.register(rowNode, { view: rowView })


Non-blocking image load

Non-blocking image load

--- app/assets/javascripts/settings.coffee
+++ app/assets/javascripts/settings.coffee
@@ -4,15 +4,28 @@
     evt.preventDefault()
     $("button[data-target=#modal-passwd]").trigger 'click'

+readImage = (file, callback) ->
+  fr = new FileReader()
+  fr.addEventListener "load", (e) ->
+    callback e.target.result
+  fr.readAsBinaryString file
+
+freeURL = () ->
+
+if window.URL? or window.webkitURL?
+  readImage = (file, callback) ->
+    callback (window.URL || window.webkitURL).createObjectURL file
+  freeURL = (url) ->
+    URL.revokeObjectURL url
+

 # Profile pic
 ($ document).on 'change', 'input#user_profile_picture[type=file]', ->
-  input = ($ this)[0]
+  input = this

   ($ '#profile-picture-crop-controls').slideUp 400, ->
     if input.files and input.files[0]
-      fr = new FileReader()
-      ($ fr).on 'load', (e) ->
+      readImage input.files[0], (src) ->
         cropper = ($ '#profile-picture-cropper')
         preview = ($ '#profile-picture-preview')

@@ -30,6 +43,9 @@
 #            marginTop: '-' + Math.round(ry * data.y) + 'px'

         cropper.on 'load', ->
+          if ({}.toString).call(src) == "[object URL]"
+            freeURL src
+
           side = if cropper[0].naturalWidth > cropper[0].naturalHeight
             cropper[0].naturalHeight
           else
@@ -48,17 +64,14 @@
             ($ '#profile-picture-crop-controls')[0].dataset.bound = true
           ($ '#profile-picture-crop-controls').slideDown()

-        cropper.attr 'src', e.target.result
-
-    fr.readAsDataURL(input.files[0])
+        cropper.attr 'src', src

 ($ document).on 'change', 'input#user_profile_header[type=file]', ->
-  input = ($ this)[0]
+  input = this

   ($ '#profile-header-crop-controls').slideUp 400, ->
     if input.files and input.files[0]
-      fr = new FileReader()
-      ($ fr).on 'load', (e) ->
+      readImage input.files[0], (src) ->
         cropper = ($ '#profile-header-cropper')
         preview = ($ '#profile-header-preview')

@@ -69,6 +82,9 @@
           ($ '#crop_h_h').val Math.floor(data.h / data.scale)

         cropper.on 'load', ->
+          if ({}.toString).call(src) == "[object URL]"
+            freeURL src
+
           cropper.guillotine
             width: 1500
             height: 350
@@ -82,6 +98,4 @@
             ($ '#profile-header-crop-controls')[0].dataset.bound = true
           ($ '#profile-header-crop-controls').slideDown()

-        cropper.attr 'src', e.target.result
-
-    fr.readAsDataURL(input.files[0])
+        cropper.attr 'src', src


Breathe!

Breathe!

--- src/lib/byte-buffer.coffee
+++ src/lib/byte-buffer.coffee
@@ -149,7 +149,7 @@

   # Seeks given number of bytes
   # Note: Backwards seeking is supported
-  seek: (bytes=1) ->
+  seek: (bytes = 1) ->
     @index += bytes
     return this


fix(specs): Properly spy on streaming connection

- Prevent error logs and actually trying to start delta streaming
connections

--- internal_packages/worker-sync/spec/nylas-sync-worker-spec.coffee
+++ internal_packages/worker-sync/spec/nylas-sync-worker-spec.coffee
@@ -43,11 +43,11 @@
         return throw new Error("Not stubbed! #{key}")

+    spyOn(DeltaStreamingConnection.prototype, 'start')
     @account = new Account(clientId: TEST_ACCOUNT_CLIENT_ID, serverId: TEST_ACCOUNT_ID, organizationUnit: 'label')
     @worker = new NylasSyncWorker(@api, @account)
     @worker._metadata = {"a": [{"id":"b"}]}
     @connection = @worker.connection()
-    spyOn(@connection, 'start')
     advanceClock()

   it "should reset `busy` to false when reading state from disk", ->


Simplify logic of HasProps.attach_document()

--- bokehjs/src/coffee/core/has_props.coffee
+++ bokehjs/src/coffee/core/has_props.coffee
@@ -373,23 +373,19 @@
         throw new Error("models must be owned by only a single document")
       else
         @document._notify_attach(@)
-        return
-
-    first_attach = @document == null
-    @document = doc
-
-    if doc != null
-      doc._notify_attach(@)
-      if first_attach
-        for c in @_immediate_references()
-          c.attach_document(doc)
-
-    # TODO (bev) is there are way to get rid of this?
-    for name, prop of @properties
-      prop.update()
-
-    if @_doc_attached?
-      @_doc_attached()
+    else
+      @document = doc
+      @document._notify_attach(@)
+
+      for ref in @_immediate_references()
+        ref.attach_document(@document)
+
+      # TODO (bev) is there are way to get rid of this?
+      for name, prop of @properties
+        prop.update()
+
+      if @_doc_attached?
+        @_doc_attached()

   detach_document: () ->
     if @document != null


Change order for open sprints

Change order for open sprints

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -157,7 +157,7 @@
                 sprint.user_stories = _.sortBy(sprint.user_stories, "sprint_order")

             @scope.sprints = sprints
-            @scope.openSprints = _.filter(sprints, (sprint) => not sprint.closed)
+            @scope.openSprints = _.filter(sprints, (sprint) => not sprint.closed).reverse()
             @scope.closedSprints =  _.filter(sprints, (sprint) => sprint.closed)
             @scope.sprintsCounter = sprints.length
             @scope.sprintsById = groupBy(sprints, (x) -> x.id)


Fixed bar limits to meet boundaries of the bar

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -1009,8 +1009,8 @@
                     projectPointsPercentaje = 100
                     closedPointsPercentaje = closedPoints * 100 / totalPoints

-                projectPointsPercentaje = adjustPercentaje(projectPointsPercentaje)
-                closedPointsPercentaje = adjustPercentaje(closedPointsPercentaje)
+                projectPointsPercentaje = adjustPercentaje(projectPointsPercentaje - 3)
+                closedPointsPercentaje = adjustPercentaje(closedPointsPercentaje - 3)
                 render($el, projectPointsPercentaje, closedPointsPercentaje)

         $scope.$on "$destroy", ->


Restore installing from a git url feature

--- client/app/views/market.coffee
+++ client/app/views/market.coffee
@@ -67,7 +67,7 @@

     onEnterPressed: (event) =>
         if event.which is 13 and not @popover?.$el.is(':visible')
-            @onInstallClicked()
+            @onInstallClicked(event)
         else if event.which is 13
             @popover?.confirmCallback()

@@ -76,7 +76,6 @@

         @parsedGit data
         event.preventDefault()
-        return false

     # parse git url before install application
     parsedGit: (app) ->
@@ -98,12 +97,15 @@
                 $('#no-app-message').hide()
                 @popover.hide()
                 @appList.show()
-                @waitApplication appWidget, true
-                @runInstallation appWidget.app
-                , =>
-                    @hideApplication appWidget
-                , =>
-                    @waitApplication appWidget, false
+                if appWidget.$el
+                    @waitApplication appWidget, true
+                    @runInstallation appWidget.app
+                    , =>
+                        @hideApplication appWidget
+                    , =>
+                        @waitApplication appWidget, false
+                else
+                    @runInstallation appWidget.app
             cancel: (application) =>
                 @popover.hide()
                 @appList.show()


Renaming template variable file -> path

Renaming template variable file -> path

--- src/processors/files.coffee
+++ src/processors/files.coffee
@@ -40,7 +40,7 @@
     pattern = args.shift()

     func = files pattern, (filePath, callback) ->
-      command = utils.expandVars args.join(' '), path: filePath
+      command = utils.expandVars args.join(' '), file: filePath
       utils.runCommand command, repo.workdir(), callback

     func repo, finished


Improve content list filter interface

Improve content list filter interface

--- manifest_content.coffee
+++ manifest_content.coffee
@@ -27,13 +27,15 @@
   ###
   addFinalizeFilter: (filter) -> @finalizeFilters.push filter

-
   list: (filter) ->
     return @contentList unless pattern
-    if filter instanceof RegExp
-      return @contentList.filter (item) -> item.match pattern
-    if filter instanceof Function
-      return @contentList.filter filter
+    filterComments = (line) ->
+      return false unless line
+      return false if line.match /^\s*#/
+    stripComments = (line) -> line.replace /#.*/, ""
+    list = @contentList.filter( filterComments ).map( stripComments )
+    return list.filter((item) -> item.match pattern) if filter instanceof RegExp
+    return list.filter(filter) if filter instanceof Function

   ###
   Compile manifest to content through these registered filters.


Avoid trying to load images multiple times.

--- client/entry/ImageBlock.coffee
+++ client/entry/ImageBlock.coffee
@@ -30,6 +30,9 @@
                 is_pinned   = JSON.parse(el.dataset.pinned)

                 image_resize_fns.push ->
+                    if image_block.datset.is_loading is 'true'
+                        return
+
                     height = content_el.offsetWidth / image_block.dataset.aspect_ratio

                     height += caption_el?.offsetHeight or 0
@@ -42,6 +45,7 @@
                         image_el.style.height = "100vh"

                     if not image_block.dataset.loaded and visibilityCheck(image_block)
+                        image_block.datset.is_loading = true
                         if content_el.offsetWidth * px_ratio > 1330 and image_block.dataset.src_2560
                             src = image_block.dataset.src_2560
                         else if content_el.offsetWidth * px_ratio > 668
@@ -52,10 +56,12 @@
                         if is_pinned
                             image_el.style.backgroundImage = "url('#{ src }')"
                             image_block.dataset.loaded = true
+                            image_block.datset.is_loading = false
                         else
                             image_el.src = src
                             image_el.onload = ->
                                 image_block.dataset.loaded = true
+                                image_block.datset.is_loading = false

     renderAllImages = ->


Framework: Added KDSplitComboView.

Framework: Added KDSplitComboView.

--- client/landing/includes.coffee
+++ client/landing/includes.coffee
@@ -39,6 +39,7 @@
   "Framework/components/split/splitview.coffee",
   "Framework/components/split/splitresizer.coffee",
   "Framework/components/split/splitpanel.coffee",
+  "Framework/components/split/splitcomboview.coffee",

   # header
   "Framework/components/header/KDHeaderView.coffee",


Remove duplicated setting

Remove duplicated setting

--- packages/rocketchat-lib/server/startup/settings.coffee
+++ packages/rocketchat-lib/server/startup/settings.coffee
@@ -167,7 +167,6 @@
 	@add 'Message_AllowEditing_BlockEditInMinutes', 0, { type: 'int', public: true, i18nDescription: 'Message_AllowEditing_BlockEditInMinutesDescription' }
 	@add 'Message_AllowDeleting', true, { type: 'boolean', public: true }
 	@add 'Message_AllowDeleting_BlockDeleteInMinutes', 0, { type: 'int', public: true, i18nDescription: 'Message_AllowDeleting_BlockDeleteInMinutes' }
-	@add 'Message_AllowPinning', true, { type: 'boolean', public: true }
 	@add 'Message_AlwaysSearchRegExp', false, { type: 'boolean' }
 	@add 'Message_ShowEditedStatus', true, { type: 'boolean', public: true }
 	@add 'Message_ShowDeletedStatus', false, { type: 'boolean', public: true }


Changed Accounts_AllowDeleteOwnAccount  default value to false

--- packages/rocketchat-lib/server/startup/settings.coffee
+++ packages/rocketchat-lib/server/startup/settings.coffee
@@ -3,7 +3,7 @@
 	RocketChat.models.Settings.createWithIdAndValue 'uniqueID', process.env.DEPLOYMENT_ID or Random.id()

 RocketChat.settings.addGroup 'Accounts', ->
-	@add 'Accounts_AllowDeleteOwnAccount', true, { type: 'boolean', public: true }
+	@add 'Accounts_AllowDeleteOwnAccount', false, { type: 'boolean', public: true }
 	@add 'Accounts_AllowUserProfileChange', true, { type: 'boolean', public: true }
 	@add 'Accounts_AllowUserAvatarChange', true, { type: 'boolean', public: true }
 	@add 'Accounts_AllowUsernameChange', true, { type: 'boolean', public: true }


Delete correct apm symlink when installing

--- build/tasks/install-task.coffee
+++ build/tasks/install-task.coffee
@@ -33,8 +33,7 @@

       # Create relative symbol link for apm.
       process.chdir(binDir)
-      apmSymlinkPath = path.join('..', '..', 'share', 'atom', 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm')
-      rm(apmSymlinkPath)
-      fs.symlinkSync(apmSymlinkPath, 'apm')
+      rm('apm')
+      fs.symlinkSync(path.join('..', '..', 'share', 'atom', 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), 'apm')

       fs.chmodSync(path.join(shareDir, 'atom'), "755")


only allow advancing through the steps if the current screen is valid.

--- src/coffee/donations-form-model.coffee
+++ src/coffee/donations-form-model.coffee
@@ -115,8 +115,21 @@
     self.incrementInputSet = ->
       self.visibleInputSet(self.visibleInputSet() + 1)

+    self.isAdvanceAllowed = (index) ->
+      switch index
+        when 1
+          self.inputSet1.isValid()
+        when 2
+          self.inputSet1.isValid() && self.inputSet2.isValid()
+        when 3
+          self.inputSet1.isValid() && self.inputSet2.isValid() && self.inputSet3.isValid()
+        else
+          true
+
+
     self.setInputSet = (index) ->
-      self.visibleInputSet(index)
+      if self.isAdvanceAllowed(index)
+        self.visibleInputSet(index)

     self.firstName = ko.observable().extend({ required: { message: "Can't be blank" } })
     self.lastName = ko.observable().extend({ required: { message: "Can't be blank" } })


github-resolver: Making branch optional

github-resolver: Making branch optional

--- src/resolvers/github.coffee
+++ src/resolvers/github.coffee
@@ -19,7 +19,7 @@

     @user = match[1]
     @repoRe = new RegExp match[2] #minimatch.makeRe match[2]
-    @branchRe = if match[3] then new RegExp match[3] else /master/ # minimatch.makeRe
+    @branchRe = if match[4] then new RegExp match[4] else /master/ # minimatch.makeRe

     logger.debug "GitHub: #{@user}/#{match[2]}, branch #{@branchRe.source}"

@@ -48,7 +48,7 @@
       ^github\:
       ([a-zA-Z0-9\-_]+)\/ # user
       ([^:]+)             # repository
-      \:(.+)?             # optional branch (master assumed if not set)
+      (\:(.+))?           # optional branch (master assumed if not set)
     ///

     match = expression.match pattern


Fix geolocation code

Fix geolocation code

--- server_setup.coffee
+++ server_setup.coffee
@@ -86,7 +86,6 @@
       ip = req.headers['x-forwarded-for'] or req.connection.remoteAddress
       ip = ip?.split(/,? /)[0]  # If there are two IP addresses, say because of CloudFlare, we just take the first.
       geo = geoip.lookup(ip)
-      geo = country: 'CN'
       #if speaksLanguage or geo?.country is countryCode
       #  log.info("Should we redirect to #{serverID} server? speaksLanguage: #{speaksLanguage}, firstLanguage: #{firstLanguage}, ip: #{ip}, geo: #{geo} -- so redirecting? #{geo?.country is 'CN' and speaksLanguage}")
       return geo?.country is countryCode and speaksLanguage


added another test to reverse-for (see #10)

--- test/blocks.coffee
+++ test/blocks.coffee
@@ -23,5 +23,8 @@
     render('1', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', array: [ 1, 1, 1, 1 ])

   test_reverse: renderTest (render, assert) ->
-    render('321', '{% for item in array reversed %}{{ item }}{% endfor %}', array: [ 1, 2, 3 ])
+    array = [ 1, 2, 3 ]
+    render('321', '{% for item in array reversed %}{{ item }}{% endfor %}', array: array)
+    assert.eql 3, array.length, "Expected array contents to remain the same"
+    assert.eql 1, array[0], "Expected array order to remain the same"