No need to resetDecoration, ohf.

No need to resetDecoration, ohf.

--- client/app/CommonViews/reviews/reviewview.coffee
+++ client/app/CommonViews/reviews/reviewview.coffee
@@ -6,7 +6,8 @@

     @setClass "review-container"
     @createSubViews data
-    @resetDecoration()
+    # @resetDecoration()
+    @decorateCommentedState()
     @attachListeners()

   render:->


use component react-spinner

use component react-spinner

--- client/app/lib/components/codeblock/index.coffee
+++ client/app/lib/components/codeblock/index.coffee
@@ -3,7 +3,7 @@
 globals = require 'globals'
 Tracker = require 'app/util/tracker'
 copyToClipboard = require 'app/util/copyToClipboard'
-Spinner = require 'react-spinner'
+Spinner = require 'app/components/reactspinner'

 module.exports = class CodeBlock extends React.Component


fix tests env

fix tests env

--- tests/no-regression.coffee
+++ tests/no-regression.coffee
@@ -554,7 +554,7 @@
                         body.query.should.equal "dragons"
                         200
                     else 204
-                @indexer.listen 9102
+                @indexer.listen 9092
                 setTimeout done, 500


coffee doesn't have semicolons

coffee doesn't have semicolons

--- tasks/server.coffee
+++ tasks/server.coffee
@@ -18,7 +18,7 @@
   express = require("express")
   httpProxy = require("http-proxy")
   fileUtils = require("./../lib/file-utils")
-  watchr = require('watch_r-structr-lock');
+  watchr = require('watch_r-structr-lock')

   grunt.registerTask "server", "static file & api proxy development server", ->
     apiPort = process.env.API_PORT || grunt.config.get("server.apiProxy.port") || 3000


Never try to hit API when network is unavailable

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -63,7 +63,7 @@
         data = JSON.parse(body)
       catch error
         return callback(error)
-
+
       delete data['versions'] if data['versions']
       cached =
         data: data
@@ -77,6 +77,8 @@
   online: ->
     navigator.onLine

+  # This could use a better name, since it checks whether it's appropriate to return
+  # the cached data and pretends it's null if it's stale and we're online
   fetchFromCache: (packagePath, options, callback) ->
     unless callback
       callback = options
@@ -88,8 +90,7 @@

     cached = localStorage.getItem(@cacheKeyForPath(packagePath))
     cached = if cached then JSON.parse(cached)
-    # TODO - Needs to always return cached data when api is unreachable
-    if cached? and (options.force or (Date.now() - cached.createdOn < @expiry))
+    if cached? and (not @online() or options.force or (Date.now() - cached.createdOn < @expiry))
       cached ?=  {data: {}}
       callback(null, cached.data)
     else


Workspace: Fixed creating multiple editor tabs in EditorPane.

--- client/landing/Workspace/panes/editorpane.coffee
+++ client/landing/Workspace/panes/editorpane.coffee
@@ -25,21 +25,27 @@
       @ace.editor.setValue content  if content

   createEditorTabs: ->
-    @tabHandleContainer = new ApplicationTabHandleHolder
-      delegate      : this
-      addPlusHandle : no
+    @editors                  = {}
+    @tabHandleContainer       = new ApplicationTabHandleHolder
+      delegate                : this
+      addPlusHandle           : no

-    @tabView = new ApplicationTabView
-      delegate           : this
-      tabHandleContainer : @tabHandleContainer
+    @tabView                  = new ApplicationTabView
+      delegate                : this
+      tabHandleContainer      : @tabHandleContainer
+      removePaneOnTabChange   : no

-    for fileOptions in @files
-      file   = FSHelper.createFileFromPath fileOptions.path
-      pane   = new KDTabPaneView
-        name : file.name or "Untitled.txt"
+    @files.forEach (options) =>
+      {name, path, content}   = options
+      file                    = FSHelper.createFileFromPath path
+      pane                    = new KDTabPaneView
+        name                  : file.name or "Untitled.txt"
+        removePaneOnTabChange : no

-      pane.addSubView @createEditorInstance file
+      editor = @createEditorInstance file, content
+      pane.addSubView editor
       @tabView.addPane pane
+      @editors[name] = editor  if name

   getValue: ->
     return  @ace.editor.getSession().getValue()


define noop for JGuest#fetchNonce

define noop for JGuest#fetchNonce

--- client/landing/app/MainApp/account-mixins.coffee
+++ client/landing/app/MainApp/account-mixins.coffee
@@ -1,7 +1,9 @@
 AccountMixin = do ->

   init:(api)->
-    {JAccount} = api
+    {JAccount, JGuest} = api
+
+    JGuest::fetchNonce = ->

     nonces = []


Use KD instead @ since its static now

Use KD instead @ since its static now

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


sometimes word won't exist

sometimes word won't exist

--- lib/jsMegaHal.coffee
+++ lib/jsMegaHal.coffee
@@ -118,7 +118,7 @@
 	@word the seed word, can be null/undefined
 	###
 	getReply: (word) ->
-		word = word.trim()
+		word = word?.trim()
 		quads = []

 		#if we don't have a specific word, everything is possible to choose from


Updating command.target to be an Object instead of String

see https://github.com/atom/notifications/pull/173

--- lib/command-logger.coffee
+++ lib/command-logger.coffee
@@ -67,7 +67,7 @@
   #
   # * `command` Command {Object} to be logged
   #   * `type` Name {String} of the command
-  #   * `target` {String} describing where the command was triggered
+  #   * `target` {Object} Node describing where the command was triggered
   logCommand: (command) ->
     {type: name, target, time} = command
     return if command.detail?.jQueryTrigger


Standard API, okay

Standard API, okay

--- main.coffee
+++ main.coffee
@@ -395,7 +395,7 @@
 	(request, response) ->
 		nameIsValid = true
 		if nameIsValid
-			charid = lib.game.createCharacter(dbConnection, request.uonline.user.id, request.body.character_name)
+			charid = lib.game.createCharacter.sync(null, dbConnection, request.uonline.user.id, request.body.character_name)
 			response.redirect '/character/'
 		else
 			response.render 'new_character', request.uonline
@@ -492,7 +492,7 @@
 app.get '/action/switchCharacter/:id',
 	mustBeAuthed,
 	(request, response) ->
-		lib.game.switchCharacter dbConnection, request.uonline.user.id, request.params.id
+		lib.game.switchCharacter.sync null, dbConnection, request.uonline.user.id, request.params.id
 		response.redirect 'back'


Code clean up

Code clean up

--- views/battle-detail-area.cjsx
+++ views/battle-detail-area.cjsx
@@ -10,70 +10,73 @@
 # battleType = carrier   : Carrier Task Force (空母機動部隊)
 # battleType = surface   : Surface Task Force (水上打撃部隊)
 simualteBattlePacket = (packet) ->
-  if not packet?
-    return result =
-      battleType: null
-      battleFlow: []
+  return unless packet?

   isCombined = packet.poi_is_combined
   isCarrier = packet.poi_is_carrier
-  uri = packet.poi_uri
-
   # TODO: Keep compatibility with version 1.0.0
   #       Please remove these after 2016 autumn event.
   if packet.poi_is_water? and not packet.poi_is_carrier?
     isCarrier = !packet.poi_is_water

-  battleType = null
+  stageFlow = null
   # Normal Fleet
-  if not isCombined
-    switch uri
-      # Battle, Air battle
-      when '/kcsapi/api_req_sortie/battle', '/kcsapi/api_req_practice/battle', '/kcsapi/api_req_sortie/airbattle'
-        battleType = 'normal'
-        stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling]
-      # Night battle
-      when '/kcsapi/api_req_battle_midnight/battle', '/kcsapi/api_req_practice/midnight_battle', '/kcsapi/api_req_battle_midnight/sp_midnight'
-        battleType = 'night'
-        stageFlow = [StageType.Shelling]
+  if isCombined is false
+    stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling]
+    stageTitle = [
+      "#{__ "Aerial Combat"} - Stage 3",
+      "#{__ "Aerial Combat"} - Stage 3",
+      "#{__ 'Expedition Supporting Fire'}",
+      "#{__ 'Opening Torpedo Salvo'}",
+      "#{__ 'Shelling'}",
+      "#{__ 'Shelling'}",
+      "#{__ 'Torpedo Salvo'}",
+      "#{__ 'Night Combat'}"
+    ]
   # Carrier Task Force
-  if isCombined and isCarrier
-    switch uri
-      # Battle, Air battle
-      when '/kcsapi/api_req_combined_battle/battle', '/kcsapi/api_req_combined_battle/airbattle'
-        battleType = 'carrier'
-        stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.Shelling]
-      # Night battle
-      when '/kcsapi/api_req_combined_battle/midnight_battle'
-        battleType = 'night'
-        stageFlow = [StageType.Shelling]
+  if isCombined is true and isCarrier true
+    stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.Shelling]
+    stageTitle = [
+      "#{__ "Aerial Combat"} - Stage 3",
+      "#{__ "Aerial Combat"} - Stage 3",
+      "#{__ 'Expedition Supporting Fire'}",
+      "#{__ 'Opening Torpedo Salvo'}",
+      "#{__ 'Shelling'} - #{__ 'Escort Fleet'}",
+      "#{__ 'Torpedo Salvo'}",
+      "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
+      "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
+      "#{__ 'Night Combat'}"
+    ]
   # Surface Task Force
-  if isCombined and not isCarrier
-    switch uri
-      # Battle, Air battle
-      when '/kcsapi/api_req_combined_battle/battle_water', '/kcsapi/api_req_combined_battle/airbattle'
-        battleType = 'surface'
-        stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling]
-      # Night battle
-      when '/kcsapi/api_req_combined_battle/midnight_battle'
-        battleType = 'night'
-        stageFlow = [StageType.Shelling]
+  if isCombined is true and isCarrier is false
+    stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling]
+    stageTitle = [
+      "#{__ "Aerial Combat"} - Stage 3",
+      "#{__ "Aerial Combat"} - Stage 3",
+      "#{__ 'Expedition Supporting Fire'}",
+      "#{__ 'Opening Torpedo Salvo'}",
+      "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
+      "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
+      "#{__ 'Shelling'} - #{__ 'Escort Fleet'}",
+      "#{__ 'Torpedo Salvo'}",
+      "#{__ 'Night Combat'}"
+    ]

   formedFlow = []
-  if battleType
+  if stageFlow
+    console.assert(stageFlow.length == stageTitle.length, "`stageFlow` and `stageTitle` have different length!")
     try
       battleFlow = simulator.simulate(packet)
-      for stage in stageFlow
-        if battleFlow.length > 0 and battleFlow[0].type is stage
-          formedFlow.push battleFlow.shift()
-        else
-          formedFlow.push null
+      for battle in battleFlow
+        if stageFlow.length > 0 and stageFlow[0] == battle.type
+          stageFlow.shift()
+          battle.title = stageTitle.shift()
+          formedFlow.push battle
     catch err
       console.error(err)
-
-  return result =
-    battleType: battleType
-    battleFlow: formedFlow
+      return
+
+  return formedFlow

 getHpStyle = (percent) ->
@@ -212,57 +215,15 @@
     return true

   render: ->
-    {battleType, battleFlow} = simualteBattlePacket @props.battlePacket
-    switch battleType
-      when 'normal'
-        titles = [
-          "#{__ "Aerial Combat"} - Stage 3",
-          "#{__ "Aerial Combat"} - Stage 3",
-          "#{__ 'Expedition Supporting Fire'}",
-          "#{__ 'Opening Torpedo Salvo'}",
-          "#{__ 'Shelling'}",
-          "#{__ 'Shelling'}",
-          "#{__ 'Torpedo Salvo'}",
-          "#{__ 'Night Combat'}"
-        ]
-      when 'carrier'
-        titles = [
-          "#{__ "Aerial Combat"} - Stage 3",
-          "#{__ "Aerial Combat"} - Stage 3",
-          "#{__ 'Expedition Supporting Fire'}",
-          "#{__ 'Opening Torpedo Salvo'}",
-          "#{__ 'Shelling'} - #{__ 'Escort Fleet'}",
-          "#{__ 'Torpedo Salvo'}",
-          "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
-          "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
-          "#{__ 'Night Combat'}"
-        ]
-      when 'surface'
-        titles = [
-          "#{__ "Aerial Combat"} - Stage 3",
-          "#{__ "Aerial Combat"} - Stage 3",
-          "#{__ 'Expedition Supporting Fire'}",
-          "#{__ 'Opening Torpedo Salvo'}",
-          "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
-          "#{__ 'Shelling'} - #{__ 'Main Fleet'}",
-          "#{__ 'Shelling'} - #{__ 'Escort Fleet'}",
-          "#{__ 'Torpedo Salvo'}",
-          "#{__ 'Night Combat'}"
-        ]
-      when 'night'
-        titles = [
-          "#{__ 'Night Combat'}"
-        ]
-      else
-        titles = null
+    stages = simualteBattlePacket @props.battlePacket

     <div className="battle-detail-area">
       <Panel header={__ "Battle Detail"}>
       {
-        if titles
+        if stages
           tables = []
-          for title, i in titles
-            tables.push <AttackTable key={i} title={title} attacks={battleFlow[i]?.detail} />
+          for stage, i in stages
+            tables.push <AttackTable key={i} title={stage.title} attacks={stage.detail} />
           tables
         else
           __ "No battle"


angle bouncing is much more conservative

--- src/script/GameplayState.coffee
+++ src/script/GameplayState.coffee
@@ -83,8 +83,8 @@
     @square.body.position.x = Math.max(Math.min(game.input.activePointer.x - @square.body.width / 2, @wallRight.body.position.x - @square.body.width), @wallLeft.body.position.x + @wallLeft.body.width)

     game.physics.arcade.overlap(@ball, @square, (ball, paddle) ->
-      ball.body.velocity.x = 400 * Math.cos(Math.PI + (Math.PI * (ball.body.center.x - paddle.body.position.x) / paddle.body.width))
-      ball.body.velocity.y = 400 * Math.sin(Math.PI + (Math.PI * (ball.body.center.x - paddle.body.position.x) / paddle.body.width)))
+      ball.body.velocity.x = 400 * Math.cos(Math.PI + (Math.PI / 4) + ((Math.PI / 2) * (ball.body.center.x - paddle.body.position.x) / paddle.body.width))
+      ball.body.velocity.y = 400 * Math.sin(Math.PI + (Math.PI / 4) + ((Math.PI / 2) * (ball.body.center.x - paddle.body.position.x) / paddle.body.width)))
     game.physics.arcade.overlap(@ball, @wallTop, @collideY)
     game.physics.arcade.overlap(@ball, @wallLeft, @collideX)
     game.physics.arcade.overlap(@ball, @wallRight, @collideX)


fix: fix worker script selector (#11)

fix: fix worker script selector (#11)

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -96,7 +96,7 @@

         _uploadWorker: (file) ->
             defer = $q.defer()
-            script = document.querySelectorAll('[src*="file-upload"]')[0]
+            script = document.querySelectorAll('[src*="cg-file-upload.js"]')[0]
             workerUrl = new URL script.src.replace 'file-upload.js', 'file-upload-worker.js'
             worker = new Worker workerUrl.pathname
             worker.onmessage = (e) ->


Added required configuration parameter api key

--- src/scripts/wunderground.coffee
+++ src/scripts/wunderground.coffee
@@ -5,7 +5,8 @@
 #   None
 #
 # Configuration:
-#   None
+#  HUBOT_WUNDERGROUND_API_KEY Sign up at http://www.wunderground.com/weather/api/.
+#
 #
 # Commands:
 #   hubot weather me <location> - short-term forecast


marginally fault tolerant and untested job watcher

--- src/serverjs/job_watcher.coffee
+++ src/serverjs/job_watcher.coffee
@@ -11,6 +11,8 @@
     @mapStatus = {}
     @reduceStatus = {}
     @initFromFirebase()
+
+    @doneMappers = []

   initFromFirebase: () ->
@@ -50,16 +52,19 @@

     switch @status
       when "STARTING"
-        @start()
+        @allocateMappers()
+        @startMap()
       when "MAPPING_STARTED"
         @checkMappers()
       when "MAPPING_ENDING_FIRST"
         @allocateReducers()
+        @finishMappers()
         @status = "MAPPING_ENDING"
       when "MAPPING_ENDING"
-        @checkMappers
+        @checkMappers()
+        @finishMappers()
       when "REDUCE_START"
-        # TODO init stuff
+        @startReduce()
         @status = "REDUCE_STARTED"
       when "REDUCE_STARTED"
         @checkMappers()  # so that if a reducer fails we have the data to send it?
@@ -71,71 +76,141 @@
       setTimeout () =>
         @loop()
       , 1
-
-
-  start: () ->
-    @allocateMappers()
-    for child_id of @mappers
-      @send child_id, { name: 'MAP_START', job_id: @jobid, url: @mapStatus[child_id].url }
-
-  checkMappers: () ->
-    # read through messages to see if anyone finished, if all finished, or if anyone is dead
-
-    for message_id, message of @queue
-      if message.name is 'MAP_DONE'
-        node = message.worker_id
-        @mapStatus[node].done = true
-        @numMappersLeft--
-        delete @queue[message_id]
-        if @status is "MAPPING_STARTED"
-          @status = "MAPPING_ENDING_FIRST"
-
-    toRetry = []
-    for worker_id of @mappers
-      now = new Date().now()
-      if (now - @clientMap[worker_id].last_update > 4 * constants.HEARTBEAT_INTERVAL)
-        # he's dead jim
-        toRetry.push worker_id
-
-    for failed_worker in toRetry
-      @retryMap failed_worker
-
-    if @numMappersLeft == 0 and @status is not "REDUCE_STARTED"
-      @status = "REDUCE_START"
-
-  checkReducers: () ->
+    else
+      @finish()

   allocateMappers: () ->
     numMappers = @urls.length
     @mappers = @get_active_clients numMappers
     index = 0
     for child_id, child in @mappers
-      @mapStatus[child_id] = { done: false, url: @urls[index++] }
+      @mapStatus[child_id] = { done: false, index: index }
+      index++
     @numMappersLeft = numMappers

+  startMap: () ->
+    index = 0
+    for child_id of @mappers
+      startMapper child_id, index++
+
+  startMapper: (mapper, index) ->
+    @send mapper, { name: 'MAP_START', index: index, job_id: @jobid, url: @urls[index]}
+
+  checkMappers: () ->
+    # read through messages to see if anyone finished, if all finished, or if anyone is dead
+
+    for message_id, message of @queue
+      if message.name is 'MAP_DONE'
+        node = message.worker_id
+        @mapStatus[node].done = true
+        @doneMappers.push node
+        @numMappersLeft--
+        delete @queue[message_id]
+        if @status is "MAPPING_STARTED"
+          @status = "MAPPING_ENDING_FIRST"
+
+    toRetry = []
+    for worker_id of @mappers
+      now = new Date().now()
+      if now - @clientMap[worker_id].last_update > 4 * constants.HEARTBEAT_INTERVAL
+        # he's dead jim
+        toRetry.push worker_id
+
+    for failed_worker in toRetry
+      @retryMap failed_worker
+
+    if @numMappersLeft == 0 and @status is not "REDUCE_STARTED"
+      @status = "REDUCE_START"
+
   allocateReducers: () ->
-    numReducers = @urls.length
+    @numReducers = @urls.length
     @reducers = @get_active_clients numReducers
+    index = 0
     for child_id of @reducers
-      @reduceStatus[child_id] = { done: false }
+      @reduceStatus[child_id] = { done: false, index: index++ }
     @numReducersLeft = numReducers

+
+  finishMappers: () ->
+    for mapper in @doneMappers
+      @sendReduceNodes mapper
+    @doneMappers = []
+
+  startReduce: () ->
+    for reducer of @reducers
+      @startReducer reducer
+
+  startReducer: (reducer) ->
+    @send reducer { name: 'START_REDUCE', nummappers: @mappers.length }
+
+  checkReducers: () ->
+    # see if anyone finished
+    for message_id, message of @queue
+      if message.name is 'REDUCE_DONE'
+        node = message.worker_id
+        @reduceStatus[node].done = true
+        @numReducersLeft--
+        delete @queue[message_id]
+
+    # see if anyone failed
+    failed_reducers = []
+    now = new Date().now()
+    for reducer in @reducers
+      if now - @clientMap[reducer].last_update > 4 * constants.HEARTBEAT_INTERVAL
+        #dis fucker dead
+        failed_reducers.push reducer
+
+    if reducer.length > 0
+      @retryReducers failed_reducers
+    else if @numReducersLeft == 0
+      state = "DONE"
+
+
+  sendReduceNodes: (mapper) ->
+    send mapper { name: 'REDUCE_NODES', reducers: @reducers }
+
+  # whether mapper fails before or after it finishes, it needs to be alive the whole time,
+  # so that it can send the data to failed reducers
   retryMap: (failed_id) ->
     new_node = get_active_clients(1)[0]
-    url = @mapStatus[failed_id].url
-    @send new_node, { name: 'MAP_START', job_id: @jobid, url: url}
-    @mappers[new_node] = { done: false, url: url}
+    index = @mapStatus[failed_id].index
+    startMapper new_node, index
+    @mappers[index] = new_node
+    @mapStatus[new_node] = { done: false, index: index}
     if @mapStatus[failed_id].done
       @numMappersLeft++

     delete @mappers[failed_id]
-
-
+    delete @mapStatus[failed_id]
+
+
+  retryReducers: (failed_reducers) ->
+    for failed_reducer in failed_reducers
+      @retryReduce failed_reducer
+
+    # all done mappers need to resend
+    for mapper_id, mapper_status of @mapStatus
+      if mapper_status.done
+        @doneMappers.push mapper_id
+
+    # have all done mappers re-send the data
+    @finishMappers()
+
+
+  #don't call if reducer has already finished, data is written to firebase
   retryReduce: (failed_id) ->
+    new_node = get_active_clients(1)[0]
+    index = @reduceStatus[failed_id].index
+
+    @reducers[index] = new_node
+    @reduceStatus[new_node] = { done: false, index: index }

   # abstraction
-  send: (node, message) ->
+  send: (node, jsonMessage) ->
     #todo add to node's firebase message queue

+  finish: () ->
+    # TODO? does this need to exist?
+
 module.exports = {JobWatcher}


jupyter: improve scroll tate restore

jupyter: improve scroll tate restore

--- src/smc-webapp/jupyter/cell-list.cjsx
+++ src/smc-webapp/jupyter/cell-list.cjsx
@@ -46,9 +46,21 @@
             @props.actions.disable_key_handler()

     componentDidMount: ->
-        # restore scroll state
+
         if @props.scrollTop?
-            ReactDOM.findDOMNode(@refs.cell_list)?.scrollTop = @props.scrollTop
+            # restore scroll state -- as rendering happens dynamically and asynchronously, and I have no idea how to know
+            # when we are done, we can't just do this once.  Instead, we keep resetting scrollTop until scrollHeight
+            # stops changing or 2s elapses.
+            locals =
+                scrollTop    : @props.scrollTop
+                scrollHeight : 0
+            f = =>
+                elt = ReactDOM.findDOMNode(@refs?.cell_list)
+                if elt? and elt.scrollHeight != locals.scrollHeight  # dynamically rendering actually changed something
+                    elt.scrollTop = locals.scrollTop
+                    locals.scrollHeight = elt.scrollHeight
+            for tm in [0, 250, 750, 1500, 2000]
+                setTimeout(f, tm)

         if @props.actions?
             # Enable keyboard handler if necessary


working implementation of drag and drop across multiple panels

--- src/tabs-view.coffee
+++ src/tabs-view.coffee
@@ -45,15 +45,39 @@
   removeTabAtIndex: (index) ->
     @find(".tab:eq(#{index})").remove()

+  onDragStart: (event) =>
+    super
+    pane = $(event.target).closest('.pane')
+    event.originalEvent.dataTransfer.setData 'from-pane-index', pane.index()
+
   onDrop: (event) =>
     super
-    sessions = @editor.editSessions
-    el = @sortableElement(event)
-    previousIndex = event.originalEvent.dataTransfer.getData('index')
-    currentIndex  = el.index() - 1
+    transfer = event.originalEvent.dataTransfer
+    previousDraggedTabIndex = transfer.getData 'sortable-index'

-    sessions.splice(currentIndex, 0, sessions.splice(previousIndex, 1)[0])
+    fromPaneIndex = ~~transfer.getData 'from-pane-index'
+    toPaneIndex   = ~~$(event.target).closest('.pane').index()
+    fromPane      = rootView.find ".pane:nth-child(#{fromPaneIndex + 1})"
+    fromEditor    = fromPane.find('.editor').view()

-    @setActiveTab(currentIndex)
-    @editor.setActiveEditSessionIndex(currentIndex)
-    @editor.focus()
+    if fromPaneIndex == toPaneIndex
+      toPane   = fromPane
+      toEditor = fromEditor
+    else
+      toPane = rootView.find ".pane:nth-child(#{toPaneIndex + 1})"
+      toEditor = toPane.find('.editor').view()
+
+    droppedNearTab = @getSortableElement(event)
+    draggedTab     = fromPane.find(".#{Tabs.viewClass()} .sortable:eq(#{previousDraggedTabIndex})")
+
+    draggedTab.remove()
+    draggedTab.insertBefore(droppedNearTab)
+
+    currentDraggedTabIndex = draggedTab.index()
+
+    toEditor.editSessions.splice(currentDraggedTabIndex, 0, fromEditor.editSessions.splice(previousDraggedTabIndex, 1)[0])
+
+    @setActiveTab(currentDraggedTabIndex)
+    fromEditor.setActiveEditSessionIndex(0)
+    toEditor.setActiveEditSessionIndex(currentDraggedTabIndex)
+    toEditor.focus()


kite: nodejs kite: go style

kite: nodejs kite: go style

--- node_modules_koding/koding-kite/examples/exp2.coffee
+++ node_modules_koding/koding-kite/examples/exp2.coffee
@@ -1,24 +1,20 @@
 # this is just an example for a client
 {kite, KiteEventListener} = require 'koding-kite'
+sleep = kite.utils.sleep

-eventListener = new KiteEventListener
-eventListener.on "REGISTER", (kite)->
-  console.log "received kite event register", kite
+kite.main ()->

-eventListener.on "DEREGISTER", (kite)->
-  console.log "received kite event deregister", kite
+  options = new kite.Options {
+                  Kitename:    "application",
+                  Version:     "0.0.1",
+                  Port:        9998,
+                  Region:      "localhost",
+                  Environment: "development",
+                  Username:    "devrim",
+          }

-options = new kite.Options {
-                Kitename:    "application",
-                Version:     "0.0.1",
-                Port:        9998,
-                Region:      "localhost",
-                Environment: "development",
-                Username:    "devrim",
-        }
-
-k = kite.New options
-k.Start ()->
+  k = kite.New options
+  k.Run()

   query = new kite.KontrolQuery {
     Username:    "devrim",
@@ -26,39 +22,30 @@
     Name:        "mathworker",
   }

-  k.Kontrol.GetKites query, eventListener, (err, kites)->
-      throw "Error while getting kite list #{err}" if err
-      throw "no available kite worker" if not Array.isArray(kites) or kites.length is 0
-      # connecting to first avail kite
-      mathWorker = kites[0]
-      mathWorker.Dial ()->
+  # To demonstrate we can receive notifications matcing to our query.
+  onEvent = ()->
+    console.log "--- kite event: ", arguments

-        # you can call a remote function in async fashion
-        value = 345
-        mathWorker.Call "square", value, (r)->
-          [err, data] = r.Args
-          if err
-            console.log "received error from kite ", err
-            return
-          console.log "square of #{value} is #{data}"
+  [err, kites] = k.Kontrol.GetKites query, onEvent

-        # you can use sync style as in Go kites, just wrap your block
-        # with kite.sync helper
-        kite.sync ()->
-          squareOf = (i)->
-            [r, err] = mathWorker.Call "square", i
-            if err
-              console.error "we received an error from mathWorker #{err}"
-            else
-              console.log "square of #{i} is #{r.Args[1]}"
-          squareOf(1234)
+  if err isnt null
+    console.error err
+    return

-        # for sake of completeness, we will call sync style functions in kite
-        mathWorker.Call "doubleSquare", 12, (r)-> console.log "double square returned ", r.Args
+  if kites.length is 0
+    console.error "No mathworker available"
+    return

-        # throwing error in sync style
-        mathWorker.Call "squareWithError", 12, (r)-> console.log "squareWithError returned ", r.Args
+  mathWorker = kites[0]
+  err = mathWorker.Dial()

-        # simple sync style
-        mathWorker.Call "squareSync", 12, (r)-> console.log "squareSync returned ", r.Args
+  squareOf = (i)->
+    [r, err] = mathWorker.Call "square", i
+    if err
+      console.error "we received an error from mathWorker #{err}"
+    else
+      console.log "square of #{i} is #{r.Args[1]}"

+  loop
+    squareOf(1234)
+    sleep(1000)


Only show drop error if dragging a file

Only show drop error if dragging a file

--- src/smc-webapp/desktop_app.cjsx
+++ src/smc-webapp/desktop_app.cjsx
@@ -152,11 +152,12 @@
             console.log "react desktop_app.drop", e
         e.preventDefault()
         e.stopPropagation()
-        {alert_message} = require('./alerts')
-        alert_message
-            type     : 'info'
-            title    : 'File drop disabled'
-            message  : 'To upload a file, drop it onto the files listing or the "Drop files to upload" area in the +New tab.'
+        if e.dataTransfer.files.length > 0
+            {alert_message} = require('./alerts')
+            alert_message
+                type     : 'info'
+                title    : 'File Drop Rejected'
+                message  : 'To upload a file, drop it onto the files listing'

     render: ->
         style =


project +new: fix bug where button always says network access is disabled

--- src/smc-webapp/project_new.cjsx
+++ src/smc-webapp/project_new.cjsx
@@ -141,18 +141,16 @@
 ProjectNew = (name) -> rclass
     displayName : 'ProjectNew'

-    mixins : [ImmutablePureRenderMixin]
-
     reduxProps :
         "#{name}" :
             current_path     : rtypes.string
             default_filename : rtypes.string
-            project_id       : rtypes.string
         projects :
             project_map      : rtypes.immutable

     propTypes :
-        actions : rtypes.object.isRequired
+        actions        : rtypes.object.isRequired
+        projects_store : rtypes.object.isRequired

     getInitialState : ->
         return filename : @props.default_filename ? @default_filename()
@@ -197,7 +195,7 @@
     blocked: ->
         if not @props.project_map?
             return ''
-        if @props.project_map.get(@props.project_id)?.get('settings')?.get('network')
+        if @props.projects_store.get_total_project_quotas(@props.project_id)?.network
             return ''
         else
             return ' (internet access blocked -- see project settings)'
@@ -347,7 +345,7 @@
     FileUpload_connected  = FileUpload(store.name)

         <Redux redux={redux}>
-            <ProjectNew_connnected actions={actions} />
+            <ProjectNew_connnected project_id={project_id} actions={actions} projects_store={redux.getStore('projects')}/>
         </Redux>

         <Redux redux={redux}>


Hide progress element when value >= 100

Hide progress element when value >= 100

--- src/trix/views/attachment_view.coffee
+++ src/trix/views/attachment_view.coffee
@@ -109,8 +109,12 @@
   # Attachment delegate

   attachmentDidChangeUploadProgress: ->
-    value = @attachment.getUploadProgress()
-    @findProgressElement()?.value = value
+    if element = @findProgressElement()
+      value = @attachment.getUploadProgress()
+      if value < 100
+        element.value = value
+      else
+        element.style.display = "none"

 htmlContainsTagName = (html, tagName) ->
   div = makeElement("div")


Add @-ms-keyframes to common mistakes list because of Font Awesome

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -58,6 +58,7 @@
          'animation-delay', 'animation-direction', 'animation-fill-mode',
          'animation-iteration-count', 'animation-play-state',
          'animation-timing-function', '@keyframes',
+          #mistakes: ['-ms-']
           browsers: browsers

 # Transition


thx lodash

thx lodash

At some point _.each added an early termination feature if you return
false. Problem is Coffeescript always returns the last line so if the
last line is falsy (like assigning a value to false) we will
unintentionally return early.

--- src/modules/multi-cursor.coffee
+++ src/modules/multi-cursor.coffee
@@ -91,6 +91,7 @@
     _.each(@cursors, (cursor, id) =>
       return unless cursor?
       this._updateCursor(cursor) if cursor.dirty or force
+      return true
     )

   _applyDelta: (delta) ->


reuse classes for symbol list

reuse classes for symbol list

--- src/packages/symbols-view/src/symbols-view.coffee
+++ src/packages/symbols-view/src/symbols-view.coffee
@@ -12,7 +12,7 @@
   @activate: (rootView) ->
     @instance = new SymbolsView(rootView)

-  @viewClass: -> "#{super} symbols-view"
+  @viewClass: -> "#{super} symbols-view overlay from-top"

   filterKey: 'name'

@@ -22,7 +22,7 @@
   itemForElement: ({position, name, file}) ->
     $$ ->
       @li =>
-        @div name, class: 'function-name'
+        @div name, class: 'label'
         @div class: 'right', =>
           if position
             text = "Line #{position.row + 1}"


using this instead of window

using this instead of window

--- src/scripts/bootstrap.coffee
+++ src/scripts/bootstrap.coffee
@@ -1 +1 @@
-angular.bootstrap window.document, ['app']+angular.bootstrap @.document, ['app']


Return false from event handlers

Return false from event handlers

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -278,8 +278,10 @@
     dialog.on 'directory-created', (event, createdPath) =>
       @entryForPath(createdPath).reload()
       @selectEntryForPath(createdPath)
+      false
     dialog.on 'file-created', (event, createdPath) ->
       atom.workspaceView.open(createdPath)
+      false

     dialog.attach()


fix changing selection when dragging from other window

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -47,7 +47,6 @@
     @editorsToDestroy = []

     @dragEventCounts = new WeakMap
-    @dragPaths = []
     @rootDragAndDrop = new RootDragAndDrop(this)

     @handleEvents()
@@ -1032,7 +1031,9 @@

       entry = header.parentNode
       @dragEventCounts.set(entry, 0) unless @dragEventCounts.get(entry)
-      entry.classList.add('selected') if @dragEventCounts.get(entry) is 0
+      unless @dragEventCounts.get(entry) isnt 0 or entry.classList.contains('selected')
+        entry.classList.add('drag-over', 'selected')
+
       @dragEventCounts.set(entry, @dragEventCounts.get(entry) + 1)

   onDragLeave: (e) =>
@@ -1042,16 +1043,13 @@
       e.stopPropagation()

       entry = header.parentNode
-      directoryPath = entry.querySelector('.name')?.dataset.path
-      directorySelected = directoryPath? and @dragPaths.includes(directoryPath)
-
       @dragEventCounts.set(entry, @dragEventCounts.get(entry) - 1)
-      entry.classList.remove('selected') if @dragEventCounts.get(entry) is 0 and not directorySelected
+      if @dragEventCounts.get(entry) is 0 and entry.classList.contains('drag-over')
+        entry.classList.remove('drag-over', 'selected')

   # Handle entry name object dragstart event
   onDragStart: (e) ->
     @dragEventCounts = new WeakMap
-    @dragPaths = []
     @selectOnMouseUp = null
     if entry = e.target.closest('.entry')
       e.stopPropagation()
@@ -1073,7 +1071,6 @@
       for target in @getSelectedEntries()
         entryPath = target.querySelector(".name").dataset.path
         parentSelected = target.parentNode.closest(".entry.selected")
-        @dragPaths.push(entryPath)
         unless parentSelected
           initialPaths.push(entryPath)
           newElement = target.cloneNode(true)
@@ -1104,12 +1101,11 @@
       e.stopPropagation()

       if @dragEventCounts.get(entry) > 0 and not entry.classList.contains('selected')
-        entry.classList.add('selected')
+        entry.classList.add('drag-over', 'selected')

   # Handle entry drop event
   onDrop: (e) ->
     @dragEventCounts = new WeakMap
-    @dragPaths = []
     if entry = e.target.closest('.entry')
       return if @rootDragAndDrop.isDragging(e)

@@ -1128,7 +1124,7 @@
         initialPaths = initialPaths.split(',')
         return if initialPaths.includes(newDirectoryPath)

-        entry.classList.remove('selected')
+        entry.classList.remove('drag-over', 'selected')
         parentSelected = entry.parentNode.closest('.entry.selected')
         return if parentSelected


:fire: Remove unused toString() calls

:fire: Remove unused toString() calls

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -608,11 +608,11 @@
           originalNewPath = newPath
           while fs.existsSync(newPath)
             if initialPathIsDirectory
-              newPath = "#{originalNewPath}#{fileCounter.toString()}"
+              newPath = "#{originalNewPath}#{fileCounter}"
             else
               extension = getFullExtension(originalNewPath)
               filePath = path.join(path.dirname(originalNewPath), path.basename(originalNewPath, extension))
-              newPath = "#{filePath}#{fileCounter.toString()}#{extension}"
+              newPath = "#{filePath}#{fileCounter}#{extension}"
             fileCounter += 1

           if fs.isDirectorySync(initialPath)


Add pending option for ::openSelectedEntry

If file is single-clicked, it is opened in a pending state and will get destroyed when it loses focus.

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -113,7 +113,7 @@
      'tree-view:recursive-expand-directory': => @expandDirectory(true)
      'tree-view:collapse-directory': => @collapseDirectory()
      'tree-view:recursive-collapse-directory': => @collapseDirectory(true)
-     'tree-view:open-selected-entry': => @openSelectedEntry(true)
+     'tree-view:open-selected-entry': => @openSelectedEntry()
      'tree-view:open-selected-entry-right': => @openSelectedEntryRight()
      'tree-view:open-selected-entry-left': => @openSelectedEntryLeft()
      'tree-view:open-selected-entry-up': => @openSelectedEntryUp()
@@ -204,11 +204,13 @@
     switch e.originalEvent?.detail ? 1
       when 1
         @selectEntry(entry)
-        @openSelectedEntry(false) if entry instanceof FileView
-        entry.toggleExpansion(isRecursive) if entry instanceof DirectoryView
+        if entry instanceof FileView
+          @openSelectedEntry(pending: true)
+        else if entry instanceof DirectoryView
+          entry.toggleExpansion(isRecursive)
       when 2
         if entry instanceof FileView
-          @unfocus()
+          atom.workspace.getActivePaneItem().confirmPendingState?()
         else if entry instanceof DirectoryView
           entry.toggleExpansion(isRecursive)

@@ -375,12 +377,12 @@
       directory.collapse(isRecursive)
       @selectEntry(directory)

-  openSelectedEntry: (activatePane) ->
+  openSelectedEntry: (options) ->
     selectedEntry = @selectedEntry()
     if selectedEntry instanceof DirectoryView
       selectedEntry.toggleExpansion()
     else if selectedEntry instanceof FileView
-      atom.workspace.open(selectedEntry.getPath(), {activatePane})
+      atom.workspace.open(selectedEntry.getPath(), options)

   openSelectedEntrySplit: (orientation, side) ->
     selectedEntry = @selectedEntry()


Use event.key (not event.keyIdentifier).

event.keyIdentifier is depricated and will be removed soon.  It is being
replaced by event.key.  Unfortunatelty, event.key is not yet available
in the stable Chrome version.

Here, we use whichever API is available.  In due course, we can remove
the event.keyIdentifier implementation (and a considerable amount of
machinery surrounding it).

For the time being, if both APIs are available, then we verify one
against the other and show a warning message of they do not match.  This
should help us track down any issues which arise.

Using event.key has the additional benefit of correctly detecting
shifted characters on the numbers row on keydown, which has been a
problem for some users.

Fixes #2128.

Note:  We have a problem with the tests.  phantomjs does not currently
support event.key, and possibly never will.

--- lib/keyboard_utils.coffee
+++ lib/keyboard_utils.coffee
@@ -4,7 +4,7 @@
     f12: 123, tab: 9, downArrow: 40, upArrow: 38 }

   keyNames:
-    { 37: "left", 38: "up", 39: "right", 40: "down" }
+    { 37: "left", 38: "up", 39: "right", 40: "down", 32: "space" }

   # This is a mapping of the incorrect keyIdentifiers generated by Webkit on Windows during keydown events to
   # the correct identifiers, which are correctly generated on Mac. We require this mapping to properly handle
@@ -30,7 +30,27 @@
     else
       @platform = "Windows"

+  # We are migrating from using event.keyIdentifier to using event.key.  For some period of time, we must
+  # support both.  This wrapper can be removed once Chrome 52 is considered too old to support.
   getKeyChar: (event) ->
+    if event.key?
+      @getKeyCharUsingKey event
+    else
+      @getKeyCharUsingKeyIdentifier event
+
+  getKeyCharUsingKey: (event) ->
+    if event.keyCode of @keyNames
+      @keyNames[event.keyCode]
+    else if event.key.length == 1
+      event.key
+    else if event.key.length == 2 and "F1" <= event.key <= "F9"
+      event.key.toLowerCase() # F1 to F9.
+    else if event.key.length == 3 and "F10" <= event.key <= "F12"
+      event.key.toLowerCase() # F10 to F12.
+    else
+      ""
+
+  getKeyCharUsingKeyIdentifier: (event) ->
     # Not a letter
     if (event.keyIdentifier.slice(0, 2) != "U+")
       return @keyNames[event.keyCode] if (@keyNames[event.keyCode])


Bug with MyDate as param

Bug with MyDate as param

--- lib/workspace.coffee
+++ lib/workspace.coffee
@@ -48,7 +48,7 @@
   toString: ->
     "<Workspace: #{@worker().prefix}>"

-opt_names = ['sticky', 'as', 'namespace']
+opt_names = ['sticky', 'as', 'namespace', 'str'] # str is for MyDate...

 parse_args = (args) ->
   callback = _.callback args


Major simplification.

Major simplification.

Seems to still have some double speak and formatting issues. Will fix.

--- lib/linter-crystal.coffee
+++ lib/linter-crystal.coffee
@@ -1,77 +1,27 @@
-{BufferedProcess, BufferedNodeProcess} = require 'atom'
+require 'atom'
 linterPath = atom.packages.getLoadedPackage("linter").path
 Linter = require "#{linterPath}/lib/linter"
-path = require 'path'

 class LinterCrystal extends Linter
   # The syntax that the linter handles. May be a string or
   # list/tuple of strings. Names should be all lowercase.
   @syntax: ['source.crystal']

-  # A string, list, tuple or callable that returns a string, list or tuple,
+   # A string, list, tuple or callable that returns a string, list or tuple,
   # containing the command line (with arguments) used to lint.
-  cmd: ''
-  crystal: ''
+  cmd: 'crystal'

   executablePath: null

+  errorStream: 'stdout'
+
   linterName: 'crystal'

-  errorStream: 'stderr'
+  # A regex pattern used to extract information from the executable's output.
+  regex:
+    '.+:(?<number>\\d+): (\\[1m)?(?<message>.+)(\\[0m)?'

-  # A regex pattern used to extract information from the executable's output.
-  regex: ':(?<line>\d+): (?<message>.*)'
-
-  constructor: (editor) ->
+  constructor: (editor)->
     super(editor)

-  lintFile: (filePath, callback) ->
-     # save cmd to tmp
-    tmp = @cmd
-
-    @crystal = atom.config.get 'linter-crystal.crystalCommandName'
-    if atom.inDevMode()
-      console.log 'crystal-command: ' + @crystal
-
-    @cmd = "#{@crystal} #{@cmd}"
-
-    # build the command with arguments to lint the file
-    {command, args} = @getCmdAndArgs(filePath)
-
-    if atom.inDevMode()
-      console.log 'is node executable: ' + @isNodeExecutable
-
-    # use BufferedNodeProcess if the linter is node executable
-    if @isNodeExecutable
-      Process = BufferedNodeProcess
-    else
-      Process = BufferedProcess
-
-    # options for BufferedProcess, same syntax with child_process.spawn
-    options = {cwd: @cwd}
-
-    stdout = (output) =>
-      if atom.inDevMode()
-        console.log 'stdout', output
-      if @errorStream == 'stdout'
-        @processMessage(output, callback)
-
-    stderr = (output) =>
-      if atom.inDevMode()
-        console.warn 'stderr', output
-      if @errorStream == 'stderr'
-        @processMessage(output, callback)
-
-    new Process({command, args, options, stdout, stderr})
-
-    #restore cmd
-    @cmd = tmp;
-
-  createMessage: (match) ->
-    # message might be empty, we have to supply a value
-    if match and match.type == 'parse' and not match.message
-      message = 'error'
-
-    super(match)
-
 module.exports = LinterCrystal


Give hull-remote a little more time to start

--- src/client/channel.coffee
+++ src/client/channel.coffee
@@ -54,7 +54,7 @@
     domready(@onDomReady)

   onDomReady : ()=>
-    @timeout = setTimeout(@loadingFailed, 30000);
+    @timeout = setTimeout(@loadingFailed, 60000);
     @rpc = new xdm.Rpc
       remote    : @currentConfig.getRemoteUrl()
       container : document.body


fixes #3

fixes #3

--- mbta.coffee
+++ mbta.coffee
@@ -70,7 +70,8 @@
       route

     routeList = _.flatten routeArray
-    filteredList = routeList.filter (r) -> routeKey in r.route_name
+    filteredList = routeList.filter (r) ->
+      r.route_name.indexOf(routeKey) isnt -1

     deferred.resolve filteredList


Removing old commented code

Removing old commented code

--- src/polvo/core/optimizer.coffee
+++ src/polvo/core/optimizer.coffee
@@ -238,16 +238,4 @@
              "in file ".yellow +
              file.id.bold.grey

-    # makes sure the main file goes in first place
-    # if cycling is false
-
-    #   # but only in case amd/cjs isnt in use
-    #   if @config.browser.module_system is 'none'
-    #     main = @config.browser.main_module + '.coffee'
-
-    #     index = (ArrayUtil.find files, 'id': main)?.index
-
-    #     if index?
-    #       files.splice 0, 0, (files.splice index, 1 )[0]
-
     return files


new(gui): use mapnik tiles. closes #121

new(gui): use mapnik tiles. closes #121

--- src/components/Map.coffee
+++ src/components/Map.coffee
@@ -100,7 +100,7 @@
       onLeafletZoomend : (e) => onZoomend @getMapCoordinates()
       onLeafletClick   : (e) -> onClick e.latlng
       React.createElement TileLayer,
-        url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png"
+        url: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
         attribution: OSM_ATTR
       markers
       if (p = @props.marker)?


Remove h1

Remove h1

--- src/components/app.coffee
+++ src/components/app.coffee
@@ -10,7 +10,7 @@
 Scoreboard = React.createFactory require('./scoreboard')
 PlayerMPRTable = React.createFactory require('./player_mpr_table')

-{ div, h1 } = React.DOM
+{ div } = React.DOM

 DartsApp = React.createClass
   displayName: 'Darts'
@@ -26,7 +26,6 @@

   render: ->
     div {},
-      h1 {className: 'middle'}, 'Playing Game'
       div {id: 'playing-container'},
         div {id: 'playing'},
           PlayerOrderList {}


s/@./@/

s/@./@/

--- mock.coffee
+++ mock.coffee
@@ -38,8 +38,8 @@
   sandbox = sinon.sandbox.create()

   sandbox.stub clever.Query.prototype, 'exec', (cb) ->
-    resource = _.strRightBack(@._url, '/')
-    conditions = @._conditions
+    resource = _.strRightBack(@_url, '/')
+    conditions = @_conditions
     cb null, _(clever.db[resource]).chain()
       .filter((obj) ->
         for key, val of conditions


Use packages url from config

Use packages url from config

--- src/unpublish.coffee
+++ src/unpublish.coffee
@@ -5,6 +5,7 @@

 auth = require './auth'
 Command = require './command'
+config = require './config'
 fs = require './fs'
 request = require './request'

@@ -41,7 +42,7 @@
         return

       options =
-        uri: "https://atom.io/api/packages/#{packageName}"
+        uri: "#{config.getAtomPackagesUrl()}/#{packageName}"
         headers:
           authorization: token
         json: true
@@ -75,7 +76,6 @@
   run: (options) ->
     {callback} = options
     options = @parseOptions(options.commandArgs)
-
     [name] = options.argv._

     atIndex = name?.indexOf('@')


use timer

use timer

--- coffee/timerCtrl.coffee
+++ coffee/timerCtrl.coffee
@@ -1,18 +1,15 @@
-timeTracker.controller('TimerCtrl', ['$scope', 'timer', '$account', '$redmine', '$ticket', '$message', ($scope, timer, $account, $redmine, $ticket, $message) ->
+timeTracker.controller('TimerCtrl', ['$scope', '$account', '$redmine', '$ticket', '$message', ($scope, $account, $redmine, $ticket, $message) ->

-  # ONE_MINUTE = 1000 * 60
-  ONE_MINUTE = 1000 * 5 # for develop
+  # ONE_MINUTE = 1
+  ONE_MINUTE = 0 # for develop
   COMMENT_MAX = 255
-
-  hours = 0
-  start = null

   $scope.isTracking = false
   $scope.comment = ""
   $scope.commentMaxLength = COMMENT_MAX
   $scope.commentRemain = COMMENT_MAX
-  $scope.clickSubmitButton = ->

+  _redmine = null

   ###
    get data from sync Storage, then init.
@@ -23,8 +20,8 @@
       url    = accounts[0].url
       apiKey = accounts[0].apiKey
       userId = accounts[0].userId
-      $scope.clickSubmitButton = -> onClickSubmit(url, apiKey, userId)
-      $redmine(url, apiKey).issues.getOnUser(userId, successGetIssues)
+      _redmine = $redmine(url, apiKey, userId)
+      _redmine.issues.getOnUser(successGetIssues)

   ###
@@ -41,22 +38,22 @@
   ###
    Start or End Time tracking
   ###
-  onClickSubmit = (url, apiKey, userId) ->
+  $scope.clickSubmitButton = () ->
     if $scope.isTracking
       $scope.isTracking = false
-      timer.stop()
+      $scope.$broadcast 'timer-stop'
     else
       $scope.isTracking = true
-      timer.start()
+      $scope.$broadcast 'timer-start'

   ###
    on timer stopped, send time entry.
   ###
-  $scope.$on 'timer-stopped', (e, millisec) ->
-    if millisec > ONE_MINUTE
-      hours = millisec / 1000 / 60 / 60
-      $redmine(url, apiKey).issues.submitTime(userId, $scope.comment, hours, submitSuccess, submitError)
+  $scope.$on 'timer-stopped', (e, time) ->
+    if _redmine? and time.minutes >= ONE_MINUTE
+      hours = time.minutes / 60
+      _redmine.issues.submitTime($scope.comment, hours, submitSuccess, submitError)
       $message.toast "Submitting #{$scope.selectedTicket[0].subject}: #{hours} hr"
     else
       $message.toast 'Too short time entry.'


Fix an issue where the login failed message doesn't appear when it should

--- web/lib/coffeescripts/session.coffee
+++ web/lib/coffeescripts/session.coffee
@@ -11,7 +11,7 @@
   connect: (jid, password, callback) ->
     @xmpp.connect jid, password, (status) =>
       switch status
-        when Strophe.Status.CONNFAIL
+        when Strophe.Status.AUTHFAIL or Strophe.Status.CONNFAIL
           callback false
         when Strophe.Status.CONNECTED
           @xmpp.addHandler ((el) => this.handleIq(el)), null, 'iq'


为TemplateWidget提供外部<template>节点的支持

为TemplateWidget提供外部<template>节点的支持


Check for event object before stopping its propagation

--- packages/emberella/lib/mixins/focusable_mixin.coffee
+++ packages/emberella/lib/mixins/focusable_mixin.coffee
@@ -21,9 +21,9 @@
   hasFocus: false

   focusIn: (e) ->
-    e.stopPropagation()
+    e?.stopPropagation()
     set @, 'hasFocus', true

   focusOut: (e) ->
-    e.stopPropagation()
+    e?.stopPropagation()
     set @, 'hasFocus', false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -73,7 +73,7 @@
 ###*
 Get the file extension of a string.

-    "README"
+    "README.md".extension()

 @name extension
 @methodOf String#


Push even if location is the same

Push even if location is the same

--- vendor/assets/javascripts/views_controller.js.coffee
+++ vendor/assets/javascripts/views_controller.js.coffee
@@ -12,7 +12,7 @@
     @triggerChange @currentView(), direction: NavigationView.directionBack

   push: (view) ->
-    return if @currentLocation() == window.location.hash
+    # return if @currentLocation() == window.location.hash
     return @pop() if @previousLocation() == window.location.hash
     @saveScrollPosition()
     @views.push @setView(view)


Add more descriptive line numbers to node toString()

--- nodes.coffee
+++ nodes.coffee
@@ -108,7 +108,9 @@
   # `toString` representation of the node, for inspecting the parse tree.
   # This is what `coffee --nodes` prints out.
   toString: (idt = '', name = @constructor.name) ->
-    location = (@locationData?.first_line || "unknown") + ": "
+    firstLine = @locationData?.first_line || "unknown"
+    lastLine = @locationData?.last_line || "unknown"
+    location = firstLine  + (if lastLine != firstLine then "-" + lastLine else "") + ": "
     tree = '\n' + idt + location + name
     tree += '?' if @soak
     @eachChild (node) -> tree += node.toString idt + TAB


yet another small cleanup and obscure bugfix related to #2333


search: adapt to find datasets for publications

--- src/routes/simple-search.coffee
+++ src/routes/simple-search.coffee
@@ -14,37 +14,90 @@
 	log.debug("_get_id: #{s} -> #{snew}")
 	return snew

+#publication -> entityLink -> dataset
+#publication -> entityLink -> citedData -> entityLink -> dataset
+#
+
+# dataset -> publication
+# ignore entities of type dataset
+
+#        (refererences)  (xy_temporal)
+#           LINKPC1        LINKCD
+#        from|    |to  from|    |to
+#            v    v        v    v
+# publication1    citedData1    dataset1
+#                     ^
+#                   to|from
+#                   LINKSA (same_as)
+#                 from|to
+#                     v
+# publication2    citedData2
+#            ^    ^
+#        from|    |to
+#            LINKPC2
+#         (references)
+#
+# Query#1: id for dataset1 -> LINKCD, possibly LINKSA (citedData1/2), LINKPC1 -> publication
+# Query#2: id for publication1 -> LINKCD, possibly same_as
+
 module.exports = (app, done) ->
+	{Entity, EntityLink} = app.schemo.models
+
+	_find_links_from_publication = (fromEntity, cb) ->
+		FOUND_LINKS = []
+		#
+		# publication -> LINKPC
+		#
+		EntityLink.find({fromEntity: fromEntity.uri()}).limit(100).exec (err, links1) ->
+			return cb err if err
+			Async.eachSeries links1, (link, cbLinks1) ->
+				#
+				# LINKPC -> citedData
+				#
+				Entity.findOne {_id: _get_id link.toEntity}, (err, citedData) ->
+					return cbLinks1 err if err or not citedData
+					# TODO same_as
+					# TODO same_as
+					# TODO same_as
+					#
+					# citedData -> LINKCD
+					#
+					EntityLink.findOne {fromEntity: citedData.uri()}, (err, link2) ->
+						return cbLinks1 err if err or not link2
+						#
+						# LINKCD -> dataset
+						#
+						Entity.findOne {_id: _get_id link2.toEntity}, (err, toEntity) ->
+							return cbLinks1 err if err or not toEntity
+							# jsonify and replace fromEntity/toEntity with populated documents
+							toPush = link2.toJSON()
+							toPush.fromEntity = fromEntity.toJSON()
+							toPush.toEntity = toEntity.toJSON()
+							FOUND_LINKS.push toPush
+							return cbLinks1()
+			, (err) ->
+				return cb err if err
+				return cb null, FOUND_LINKS

 	_find_links = (needleQuery, cb) ->
-		log.debug("Entity query: #{needleQuery}")
-		{Entity, EntityLink} = app.schemo.models
-		Entity.find(needleQuery).limit(100)
-			.exec (err, entities) ->
+		log.debug("Entity query:". needleQuery)
+		FOUND_LINKS = []
+		Entity.find(needleQuery).limit(1000).exec (err, entities) ->
+			return cb err if err
+			return cb null, [] if entities.length == 0
+			Async.eachSeries entities, (fromEntity, cbEntities1) ->
+				#
+				# Query1 (publication -> dataset)
+				#
+				# log.debug "fromEntity", fromEntity.uri()
+				if fromEntity.entityType is 'publication'
+					_find_links_from_publication fromEntity, (err, links) ->
+						return cbEntities1 err if err
+						FOUND_LINKS.push x for x in links
+						return cbEntities1()
+			, (err) ->
 				return cb err if err
-				return cb null, [] if entities.length == 0
-				linkQuery = $or: []
-				for tofrom in ['to', 'from']
-					for entity in entities
-						linkQuery.$or.push "#{tofrom}Entity": "#{entity.uri()}"
-				log.debug("Link query: ", linkQuery)
-				EntityLink.find(linkQuery).limit(100).exec (err, links) ->
-					return cb err if err
-					log.debug("Links:", x.uri() for x in links)
-					Async.map links, (link, linkDone) ->
-						link = link.toJSON()
-						return linkDone "Bad link" if not link.fromEntity or not link.toEntity
-						Async.each ['from', 'to'], (tofrom, tofromDone) ->
-							Entity.findOne(_id: _get_id(link["#{tofrom}Entity"])).exec (err, tofromEntity) ->
-								return tofromDone "Error" + err if err or not tofromEntity
-								link["#{tofrom}Entity"] = tofromEntity.toJSON()
-								return tofromDone()
-						, (err) ->
-							return linkDone err, link
-					, (err, newLinks) ->
-						return cb err if err
-						log.debug(newLinks)
-						cb null, newLinks
+				return cb null, FOUND_LINKS

 	# Swagger interface
 	app.get '/search', (req, res, next) ->
@@ -52,6 +105,7 @@
 		if not req.query.id
 			return res.render 'simple-search', locals
 		req.query.id = _denoise_id(req.query.id)
+		# Enable regex search with "?regex=on"
 		if not req.query.regex
 			needle = req.query.id
 		else
@@ -60,6 +114,9 @@
 		needleQuery.$or.push _id: needle
 		needleQuery.$or.push identifier: needle
 		needleQuery.$or.push identifiers: needle
+		needleQuery.entityType = req.query.from_type || 'publication'
+		unless needleQuery.entityType in ['dataset', 'publication']
+			return next "from_type must be dataset or publication"
 		_find_links needleQuery, (err, links) ->
 			if err
 				next err


reduce delay from 3000 to 500

reduce delay from 3000 to 500

--- packages/rocketchat-ui/lib/RoomHistoryManager.coffee
+++ packages/rocketchat-ui/lib/RoomHistoryManager.coffee
@@ -169,7 +169,7 @@

 					setTimeout ->
 						msgElement.removeClass('highlight')
-					, 3000
+					, 500
 				room.loaded += result.messages.length
 				room.hasMore.set result.moreBefore
 				room.hasMoreNext.set result.moreAfter


Ensure modified unit listing is re-rendered.

--- src/views/search-results.coffee
+++ src/views/search-results.coffee
@@ -268,6 +268,8 @@
             @resultLayoutView.tryNextPage()
         initialize: (opts, rest...) ->
             @resultLayoutView = new SearchResultsLayoutView opts, rest...
+            @listenTo opts.fullCollection, 'reset', =>
+                @render()
         onRender: ->
             @unitRegion.show @resultLayoutView
             super()


made scoreboard public

made scoreboard public

--- web/coffee/scoreboard.coffee
+++ web/coffee/scoreboard.coffee
@@ -25,7 +25,8 @@

         window.drawTopTeamsProgressionGraph "#top-team-score-progression-graph"
       when 0
-        apiNotify(data)
+        0
+        #apiNotify(data)

 $ ->
   load_scoreboard()


fix analytics grade distribution percentages

test plan:
* with analytics enabled, hovering over the grade distribution
 graph points should have the correct information

closes #CNVS-22865

Change-Id: I2ae37947c1e0b903831159e2657479dfd420fb43
Reviewed-on: https://gerrit.instructure.com/66210
Tested-by: Jenkins
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
QA-Review: August Thornton <9adc7a1161ddf32ff608de792a7e50179545f026@instructure.com>
Product-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/Department/GradeDistributionData.coffee
+++ app/coffeescripts/Department/GradeDistributionData.coffee
@@ -15,6 +15,6 @@
     populate: (data) ->
       cumulative = 0
       for i in [0..100]
-        cumulative += data[i]
+        cumulative += parseInt(data[i], 10)
       cumulative = 1 if cumulative is 0
       @values = _.map [0..100], (i) -> data[i] / cumulative


fix GroupModel#isAdmin, close #100.

fix GroupModel#isAdmin, close #100.

--- src/node_modules/group-model/group-model.coffee
+++ src/node_modules/group-model/group-model.coffee
@@ -10,7 +10,14 @@
         new RoundModel(round)

     isAdmin: (user) ->
-      if process.env.NODE_ENV == 'production'
-        return user.id == 28
-      else
-        return user.id == 1
+      if typeof @memberships == 'undefined'
+        throw new Error("""
+          Unable to check if user #{user.id}
+          is admin of group #{@id}, do not
+          have access to memberships
+        """)
+
+      membership = _.find @memberships, (m) ->
+        m.user.id == user.id
+
+      membership.isAdmin


Social: fix adding new members to group channels

--- workers/social/lib/social/models/socialapi/socialaccount.coffee
+++ workers/social/lib/social/models/socialapi/socialaccount.coffee
@@ -1,7 +1,4 @@
-Bongo          = require "bongo"
-
-{Base} = Bongo
-
+{Base} = require "bongo"

 # this file named as socialaccount while it is under social folder, because i
 # dont want it to be listed first item while searching for account.coffe in
@@ -90,9 +87,8 @@
               return console.error "couldnt create socialapi id", err  if err

               # add account into public channel
-              options      =
-                channelId  : socialApiChannelId
-                accountIds : [socialApiId]
+              options = [{ channelId: socialApiChannelId, accountId: socialApiId }]
+              options.channelId = socialApiChannelId

               SocialChannel[funcName] client, options, (err, participants) ->
                 return console.error "couldnt add user into group socialapi chan", err, options  if err
@@ -101,9 +97,13 @@
                 return if group.slug isnt "koding"

                 # add account into announcement channel
-                options      =
-                  channelId  : socialApiChannelId
-                  accountIds : [socialApiId]
+                # options      =
+                #   channelId  : socialApiChannelId
+                #   accountIds : [socialApiId]
+
+                # add account into public channel
+                options = [{ channelId: socialApiAnnouncementChannelId, accountId: socialApiId }]
+                options.channelId = socialApiAnnouncementChannelId

                 SocialChannel[funcName] client, options, (err) ->
                   return console.error "couldnt add user into group socialapi chan", err, options  if err


Correctly instantiate default models [skip ci]

--- bokehjs/src/coffee/models/graphs/graph_data_source.coffee
+++ bokehjs/src/coffee/models/graphs/graph_data_source.coffee
@@ -7,6 +7,6 @@
   type: 'GraphDataSource'

   @define {
-    nodes: [ p.Instance, new ColumnDataSource({data: {index: []}})          ]
-    edges: [ p.Instance, new ColumnDataSource({data: {start: [], end: []}}) ]
+    nodes: [ p.Instance, () -> new ColumnDataSource({data: {index: []}})          ]
+    edges: [ p.Instance, () -> new ColumnDataSource({data: {start: [], end: []}}) ]
   }


refactor(slimerjs): make slimerjs works again with latest reporters

--- src/spectacular_slimerjs.coffee
+++ src/spectacular_slimerjs.coffee
@@ -38,14 +38,13 @@
           yellow: ['\x1B[33m', '\x1B[39m']
         }

-        spectacular.StackReporter::colorize =
-        spectacular.ConsoleReporter::colorize = (str, color) ->
-          if @options.colors
+        window.spectacular.utils.colorize = (str, color, colors) ->
+          if colors
             styles[color][0] + str + styles[color][1]
           else
             str

-        reporter = new window.spectacular.ConsoleReporter(spectacular.options)
+        reporter = window.spectacular.ConsoleReporter.getDefault(spectacular.options)
         window.env.runner.on 'result', reporter.onResult
         window.env.runner.on 'end', reporter.onEnd
         reporter.on 'report', (msg) -> window.consoleResults = msg.target
@@ -69,7 +68,7 @@
       slimer.exit()

 specsReady = ->
-  page.evaluate -> window.resultReceived and window.consoleResults?
+  page.evaluate -> window.consoleResults?

 # Wait until the test condition is true or a timeout occurs.
 #


Requirements parser: code documentation

Requirements parser: code documentation

--- client/stacks/lib/views/stacks/requirementsparser.coffee
+++ client/stacks/lib/views/stacks/requirementsparser.coffee
@@ -99,7 +99,13 @@

     return requirements

-
+# This function searches for config section with user input types
+# and adds those types to userInput data.
+# For example, if you define studentNumber variable in stack template
+# and want to make it textarea, you need to add the following in your template:
+# koding:
+#   userInput:
+#     studentNumber: 'textarea'
 addUserInputTypes = (content, userVars) ->

   return  unless userVars


Enhance daemon code, tests and add .nvmrc support

--- src/daemon/app/util.coffee
+++ src/daemon/app/util.coffee
@@ -34,37 +34,6 @@

     'static --port $PORT'

-  getCommand: (commandLine) ->
-    commandLine.split(' ')[0]
-
-  getArgs: (commandLine) ->
-    args = commandLine.split ' '
-    args.shift()
-    args
-
-  replacePort: (str, port) ->
-    str.replace /\$PORT/g, port
-
-  getSpawnArgs: (path, port) ->
-    env = clone process.env
-    env.PORT = port
-
-    nodePath = @getNodePath path
-    env.PATH = "#{nodePath}:#{env.PATH}"
-
-    commandLine = @getCommandLine path
-    commandLine = @replacePort commandLine, port
-
-    command = @getCommand commandLine
-    command = command.replace 'static', config.staticPath
-    command = command.replace 'nodemon', config.nodemonPath
-
-    args = @getArgs commandLine
-
-    cwd = path
-
-    [command, args, cwd: path, env: env]
-
   getRespawnArgs: (path, port) ->
     env = clone process.env
     env.PORT = port
@@ -72,11 +41,11 @@
     nodePath = @getNodePath path
     env.PATH = "#{nodePath}:#{env.PATH}"

-    commandLine = @getCommandLine path
-    commandLine = @replacePort commandLine, port
-    commandLine = commandLine.replace 'static', config.staticPath
-    commandLine = commandLine.replace 'nodemon', config.nodemonPath
-
+    commandLine = @getCommandLine(path)
+      .replace(/\$PORT/g, port)
+      .replace('nodemon', config.nodemonPath)
+      .replace('static', config.staticPath)
+
     cwd = path

     command: commandLine.split ' '


Changed wrapper function to rebind 'this' to 'self'

--- tasks/coffee_build.coffee
+++ tasks/coffee_build.coffee
@@ -274,7 +274,7 @@
     lineOffset += js.split('\n').length - 1
     output += js
   if options.wrap
-    output = '(function() {\n' + output + '\n})();'
+    output = '(function(self) {\n' + output + '\n}).call({}, this);'
   if options.sourceMap
     sourceMapDest = path.basename(outFile) + '.map'
     output += "\n\n//@ sourceMappingURL=#{sourceMapDest}"


Go to compilation-help tab on compilation error

--- client/main/main.ng.coffee
+++ client/main/main.ng.coffee
@@ -546,6 +546,10 @@
         $scope.compilerHelpString = description ? ""
         $scope.runningStatus = STATUS.COMPILATION_ERROR

+        if $scope.compilerHelpString.length > 0
+            $state.go('compilation-help')
+            $timeout(->$scope.selectedIndex = 1)
+
         term = terminal()
         term.error message

@@ -578,6 +582,10 @@
         currentLine = editor.getSession().addMarker(range, "current-line", "fullLine", true)

     listen.compilationSuccessful = ({ ast, instructions, goingToRun }) ->
+        if $scope.compileError
+            $state.go('instructions')
+            $timeout(-> $scope.selectedIndex = 2)
+
         $scope.compiled = yes
         $scope.compileError = no


errors

errors

--- app/adapters/application.coffee
+++ app/adapters/application.coffee
@@ -35,8 +35,10 @@
             reject error

     findAll: (store, type, sinceToken) ->
+        throw "I DIDN'T IMPLEMENT THIS SHIT"

     findQuery: (store, type, query) ->
+        throw "I DIDN'T IMPLEMENT THIS SHIT"

     createRecord: (store, type, record) -> new Promise (resolve) ->
         record.set 'id', guid()
@@ -58,5 +60,6 @@
         resolve()

     deleteRecord: (store, type, record) ->
+        throw "I DIDN'T IMPLEMENT THIS SHIT"

 `export default ApplicationAdapter`


InvitedItemView: Use modifiedAt or createdAt.

--- client/admin/lib/views/invitations/inviteditemview.coffee
+++ client/admin/lib/views/invitations/inviteditemview.coffee
@@ -69,7 +69,7 @@

   createViews: ->

-    { hash, createdAt } = @getData()
+    { hash, createdAt, modifiedAt } = @getData()
     { statusType }      = @getOptions()
     size                = 40
     defaultAvatarUri    = "https://koding-cdn.s3.amazonaws.com/square-avatars/default.avatar.#{size}.png"
@@ -80,7 +80,7 @@
       attributes :
         src      : "//gravatar.com/avatar/#{hash}?s=#{size}&d=#{defaultAvatarUri}"

-    @timeAgoView  = new KDTimeAgoView { click: @bound 'showSettings' }, createdAt
+    @timeAgoView  = new KDTimeAgoView { click: @bound 'showSettings' }, modifiedAt or createdAt

     if statusType is 'pending'
       @settingsIcon = new KDCustomHTMLView


Use #sslUrl in the overwritten #imageUrl method

--- models/auction_lot.coffee
+++ models/auction_lot.coffee
@@ -15,5 +15,6 @@
   estimateText: (string) ->
     string.replace /\ -\ /, ' –<br>'

+  # Overwrite #imageUrl because we need to replace thumbnail
   imageUrl: (version) ->
-    @get('image_url').replace 'thumbnail', version
+    @sslUrl @get('image_url').replace 'thumbnail', version


Remove selected message with toolbar

Remove selected message with toolbar

--- client/app/components/toolbar_messageslist_actions.coffee
+++ client/app/components/toolbar_messageslist_actions.coffee
@@ -44,7 +44,9 @@

         else
             conversationIDs = selected.map (id) =>
-                @props.messages.get(id).get('conversationID')
+                isMessage = (message) -> message.get('id') is id
+                if (message = @props.messages.find isMessage)
+                    return message.get('conversationID')

             return {count, conversationIDs, applyToConversation}


newmembergroupbucketview fix for four registered members

--- client/landing/app/CommonViews/linkviews/linkgroup.coffee
+++ client/landing/app/CommonViews/linkviews/linkgroup.coffee
@@ -62,17 +62,19 @@
   pistachio:->

     participants = @getData()
-    {hasMore, totalCount, group, separator} = @getOptions()
+    {suffix, hasMore, totalCount, group, separator} = @getOptions()

     @createMoreLink()
-
-    switch totalCount
+    # fix for old cache instances
+    count = totalCount
+    count = 20 if count is 4 and not @participant2
+    switch count
       when 0 then ""
-      when 1 then "{{> @participant0}}#{@getOptions().suffix}"
-      when 2 then "{{> @participant0}} and {{> @participant1}}#{@getOptions().suffix}"
-      when 3 then "{{> @participant0}}#{separator}{{> @participant1}} and {{> @participant2}}#{@getOptions().suffix}"
-      when 4 then "{{> @participant0}}#{separator}{{> @participant1}}#{separator}{{> @participant2}} and {{> @participant3}}#{@getOptions().suffix}"
-      else "{{> @participant0}}#{separator}{{> @participant1}}#{separator}{{> @participant2}} and {{> @more}}#{@getOptions().suffix}"
+      when 1 then "{{> @participant0}}#{suffix}"
+      when 2 then "{{> @participant0}} and {{> @participant1}}#{suffix}"
+      when 3 then "{{> @participant0}}#{separator}{{> @participant1}} and {{> @participant2}}#{suffix}"
+      when 4 then "{{> @participant0}}#{separator}{{> @participant1}}#{separator}{{> @participant2}} and {{> @participant3}}#{suffix}"
+      else "{{> @participant0}}#{separator}{{> @participant1}}#{separator}{{> @participant2}} and {{> @more}}#{suffix}"

   render:->


Remove unnecessary existential check

Remove unnecessary existential check

--- nodes.coffee
+++ nodes.coffee
@@ -1274,8 +1274,8 @@
     else
       fromDecl = fromRef = '0'
     if to
-      if from and from instanceof Value and from?.isSimpleNumber() and
-                    to instanceof Value and to.isSimpleNumber()
+      if from instanceof Value and from.isSimpleNumber() and
+         to instanceof Value and to.isSimpleNumber()
         to = +to.compile(o) - +fromRef
         to += 1 unless exclusive
       else


:new: Implement EditorRegistry::ofPath

:new: Implement EditorRegistry::ofPath

--- lib/editor-registry.coffee
+++ lib/editor-registry.coffee
@@ -7,9 +7,17 @@
     @subscriptions = new CompositeDisposable
     @subscriptions.add @emitter
     @editorLinters = new Map()
+    @editorLintersByPath = new Map()

   create: (textEditor) ->
-    @editorLinters.set(textEditor, editorLinter = new EditorLinter(textEditor))
+    editorLinter = new EditorLinter(textEditor)
+    if currentPath = textEditor.getPath()
+      @editorLintersByPath.set(currentPath, editorLinter)
+    textEditor.onDidChangePath (path) =>
+      @editorLintersByPath.delete(currentPath)
+      @editorLintersByPath.set(currentPath = path, editorLinter)
+
+    @editorLinters.set(textEditor, editorLinter)
     editorLinter.onDidDestroy =>
       @editorLinters.delete(textEditor)
       editorLinter.dispose()
@@ -18,6 +26,9 @@

   forEach: (callback) ->
     @editorLinters.forEach(callback)
+
+  ofPath: (path) ->
+    return @editorLintersByPath.get(path)

   ofTextEditor: (editor) ->
     return @editorLinters.get(editor)


fixing comprehensions to also close over the index variable, if used in an inner function...

--- nodes.coffee
+++ nodes.coffee
@@ -1196,9 +1196,9 @@
     name:           @name and @name.compile o
     index:          @index and @index.compile o
     scope.find name  if name and not @pattern and not codeInBody
-    scope.find index if index and not codeInBody
+    scope.find index if index
     rvar:           scope.freeVariable() unless topLevel
-    ivar:           if range then name else index or scope.freeVariable()
+    ivar:           if range then name else if codeInBody then scope.freeVariable() else index or scope.freeVariable()
     varPart:        ''
     body:           Expressions.wrap([@body])
     if range
@@ -1224,6 +1224,7 @@
       body:         Expressions.wrap([new IfNode(@guard, body)])
     if codeInBody
       body.unshift  literal "var $namePart" if namePart
+      body.unshift  literal "var $index = $ivar" if index
       body:         ClosureNode.wrap(body, true)
     else
       varPart:      "${@idt(1)}$namePart;\n" if namePart


Changes to completion API

Changes to completion API

* added hiding clause support
* listImportedSymbols returns a flat list

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -136,6 +136,7 @@
           ^import
           \s+(qualified\s+)? #qualified
           ([\w.]+) #name
+          (?:\s+(hiding))?
           (?:\s+\(([^)]+)\))? #import list
           (?:\s+as\s+([\w.]+))? #alias
           ///gm
@@ -143,8 +144,9 @@
           modules.push
             qualified: match[1]?
             name: match[2]
-            importList: match[3]?.split(',')?.map (s) -> s.trim()
-            alias: match[4]
+            hiding: match[3]?
+            importList: match[4]?.split(',')?.map (s) -> s.trim()
+            alias: match[5]
         return modules
       listImportedSymbols: (buffer, modules) =>
         modules ?= @provideCompletionBackend_0_1_0().getImportedModules(buffer)
@@ -153,13 +155,28 @@
             rd = @process.getRootDir(buffer)
             @process.runBrowse rd, [m.name], (symbols) ->
               s = symbols.map (s) ->
-                [name, type] = s.split('::').map (s) -> s.trim()
-                {name: name, type: type}
+                [name, typeSignature] = s.split('::').map (s) -> s.trim()
+                if /^(?:type|data|newtype)/.test(typeSignature)
+                  symbolType='type'
+                else if /^(?:class)/.test(typeSignature)
+                  symbolType='class'
+                else
+                  symbolType='function'
+                {name, typeSignature, symbolType}
               if m.importList?
                 s = s.filter (s) ->
-                  m.importList.indexOf(s.name) >= 0
+                  if m.hiding
+                    m.importList.indexOf(s.name) < 0
+                  else
+                    m.importList.indexOf(s.name) >= 0
               resolve
-                module: m.name
-                alias: m.alias
-                qualified: m.qualified
+                module: m
                 symbols: s
+        .then (modules) ->
+          [].concat (modules.map ({module,symbols})->
+            symbols.map (s) ->
+              s.module=module
+              if s.module.qualified
+                s.name=(s.module.alias ? s.module.name)+"."+s.name
+              return s
+            )...


Added Leo Quote

Added Leo Quote

--- community/browser/app/scripts/services/motdService.coffee
+++ community/browser/app/scripts/services/motdService.coffee
@@ -36,6 +36,7 @@
             { 'text':'We think your graph is a special snowflake.', 'author':'Neo4j'}
             { 'text':'Still he\'d see the matrix in his sleep, bright lattices of logic unfolding across that colorless void.', 'author':'William Gibson'}
             { 'text':'Eventually everything connects.', 'author':'Charles Eames'}
+            { 'text':'Realize that everything connects to everything else.', 'author':'Leonardo da Vinci'}
             { 'text':'To develop a complete mind: study the science of art. Study the art of science. Develop your senses - especially learn how to see. Realize that everything connects to everything else.', 'author':'Leonardo da Vinci'}
           ],
           tips: [


fix pdf preview error

fix pdf preview error

--- app/assets/javascripts/angular/v2/controllers/pdf_ctrl.js.coffee
+++ app/assets/javascripts/angular/v2/controllers/pdf_ctrl.js.coffee
@@ -1,7 +1,7 @@
 App.controller 'PDFCtrl', ['$scope', '$stateParams', 'Instrument', 'Display', 'FileSaver',
-'InstrumentQuestion', 'InstrumentOption', 'InstrumentOptionInOptionSet', 'Setting',
-($scope, $stateParams, Instrument, Display, FileSaver, InstrumentQuestion, InstrumentOption,
-InstrumentOptionInOptionSet, Setting) ->
+'InstrumentQuestion', 'Options', 'OptionInOptionSet', 'Setting',
+($scope, $stateParams, Instrument, Display, FileSaver, InstrumentQuestion, Options,
+OptionInOptionSet, Setting) ->

   $scope.instrument_id = if $stateParams.instrument_id then $stateParams.instrument_id else $stateParams.id
   $scope.project_id = $stateParams.project_id
@@ -17,12 +17,10 @@
     'project_id': $scope.project_id,
     'instrument_id': $scope.instrument_id
   })
-  $scope.instrumentOptions = InstrumentOption.query({
-    'project_id': $scope.project_id,
+  $scope.instrumentOptions = Options.query({
     'instrument_id': $scope.instrument_id
   })
-  $scope.instrumentOptionInOptionSets = InstrumentOptionInOptionSet.query({
-    'project_id': $scope.project_id,
+  $scope.instrumentOptionInOptionSets = OptionInOptionSet.query({
     'instrument_id': $scope.instrument_id
   })
   $scope.settings = Setting.get({})


If mouseover highlighting has been disabled because keyboard shortcuts have been used, disable it for the sidebar links as well.

It was already implemented for entries list, but not for the sidebar.

A handler is bound to the mousemove event so that if the user actually moves the mouse, mouseover highlighting will be reenabled.

--- app/assets/javascripts/ng-controllers/ng-FeedbunchCtrl.js.coffee
+++ app/assets/javascripts/ng-controllers/ng-FeedbunchCtrl.js.coffee
@@ -198,21 +198,21 @@
   # Highlight the start link in the sidebar when the mouse hovers over it
   #--------------------------------------------
   $scope.highlight_start_link = ()->
-    highlightedSidebarLinkSvc.reset()
+    highlightedSidebarLinkSvc.reset() unless $rootScope.mouseover_highlight_disabled
     return

   #--------------------------------------------
   # Highlight a folder link in the sidebar when the mouse hovers over it
   #--------------------------------------------
   $scope.highlight_folder_link = (folder)->
-    highlightedSidebarLinkSvc.set_folder folder
+    highlightedSidebarLinkSvc.set_folder folder unless $rootScope.mouseover_highlight_disabled
     return

   #--------------------------------------------
   # Highlight a feed link in the sidebar when the mouse hovers over it
   #--------------------------------------------
   $scope.highlight_feed_link = (feed)->
-    highlightedSidebarLinkSvc.set_feed feed
+    highlightedSidebarLinkSvc.set_feed feed unless $rootScope.mouseover_highlight_disabled
     return

   #--------------------------------------------


Remove console log on furatto demo

Remove console log on furatto demo

--- coffee/furatto-demo.coffee
+++ coffee/furatto-demo.coffee
@@ -10,7 +10,6 @@
   $('.panel-content').scroll ->
     sidebar = $('.docs-sidebar')
     if $(window).width() > 767
-      console.log $(window).width()
       if $('.panel-content').scrollTop() >= 320
         sidebar.addClass("affix")
       if $('.panel-content').scrollTop() < 320


Don't add upgrades key if it's not there.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -2132,18 +2132,22 @@
             name: @pilot.canonical_name
             points: @getPoints()
             ship: @data.canonical_name
-            upgrades: {}
+
+        upgrade_obj = {}

         for upgrade in @upgrades
             if upgrade?.data?
-                upgrade.toXWS xws.upgrades
+                upgrade.toXWS upgrade_obj

         for modification in @modifications
             if modification?.data?
-                modification.toXWS xws.upgrades
+                modification.toXWS upgrade_obj

         if @title?.data?
-            @title.toXWS xws.upgrades
+            @title.toXWS upgrade_obj
+
+        if Object.keys(upgrade_obj).length > 0
+            xws.upgrades = upgrade_obj

         xws


tests for 1840

tests for 1840

--- test/function_invocation.coffee
+++ test/function_invocation.coffee
@@ -506,3 +506,15 @@
   eq obj.prop, nonce
   eq obj.a, argNonceA
   eq obj.b, argNonceB
+
+test "#1840: accessing the `prototype` after function invocation should compile", ->
+  doesNotThrow -> CoffeeScript.compile 'fn()::prop'
+
+  nonce = {}
+  class Test then id: nonce
+
+  dotAccess = -> Test::
+  protoAccess = -> Test
+
+  eq dotAccess().id, nonce
+  eq protoAccess()::id, nonce


:fire: logs

:fire: logs

--- lib/palette-element.coffee
+++ lib/palette-element.coffee
@@ -127,7 +127,6 @@

   buildList: (container, paletteColors) ->
     paletteColors = @checkForDuplicates(paletteColors)
-    console.log paletteColors
     for [names, color] in paletteColors
       li = document.createElement('li')
       li.className = 'color-item'


fix time bug

fix time bug

--- src/github-calendar.coffee
+++ src/github-calendar.coffee
@@ -20,13 +20,13 @@

 daysBetween = (date1, date2)->
 	d1 = new Date( date1.getTime() )
-	d1.setHours 0
-	d1.setMinutes 0
-	d1.setSeconds 0
+	d1.setUTCHours 0
+	d1.setUTCMinutes 0
+	d1.setUTCSeconds 0
 	d2 = new Date( date2.getTime() )
-	d2.setHours 0
-	d2.setMinutes 0
-	d2.setSeconds 0
+	d2.setUTCHours 0
+	d2.setUTCMinutes 0
+	d2.setUTCSeconds 0
 	ms = Math.abs( d1 - d2 )
 	floor ms / 1000 / 60 / 60 / 24

@@ -67,11 +67,17 @@
 eventsHandler = (events)->
 	events.forEach (e)->
 		# console.log e
+		e.payload.commits ||= []
 		commitsLength = if e.payload.commits then e.payload.commits.length else 0
 		created_at = new Date(e.created_at)

-		# model.push { created_at: created_at, commitsLength: commitsLength }
-		eventMap[ created_at.format('yyyy-mm-dd') ] = e
+		key = created_at.format('yyyy-mm-dd')
+		if !!eventMap[key]
+			e.payload.commits.forEach (c)->
+				eventMap[key].payload.commits.push c if !!eventMap[key].payload.commits
+		else
+			eventMap[key] = e
+		eventMap[key].payload.commits ||= []

 		# grid = $('<div>').html( e.type+' '+e.created_at+' '+e.repo.name+' commits:'+commitsLength)
 		# $('#github-calendar > .content').append grid


re-add single events

re-add single events

--- src/google-calendar.coffee
+++ src/google-calendar.coffee
@@ -83,6 +83,7 @@
       params:
         timeMin: now
         timeMax: in24
+        singleEvents: true
         calendarId: gcal[userId].calendarId
       callback: (err, data)->
         return console.log(err) if err


Update host attributes when dataVar changes.

--- lib/registerElement.coffee
+++ lib/registerElement.coffee
@@ -33,6 +33,9 @@
     @childRoot = shadow.querySelector 'div'
     @blazeData = {}
     @blazeView = Blaze.renderWithData blazeTemplate, @blazeData, @childRoot
+    @blazeView.autorun =>
+      _.each @blazeView.dataVar.get(), (name, attr) =>
+        @childRoot.parentNode.host.setAttribute attr, name
   newPrototype.attributeChangedCallback = (name, oldValue, newValue) ->
     @blazeData = @blazeView.dataVar.get()
     @blazeData[name] = newValue


Revert "replace wrapping if by guard"

Revert "replace wrapping if by guard"

This reverts commit 9142179eea8c17cb50788e8db2690b41daa2fab1.

Conflicts:
	app/backbone/views/wheel/base_fact_wheel_view.coffee

--- core/app/backbone/views/wheel/base_fact_wheel_view.coffee
+++ core/app/backbone/views/wheel/base_fact_wheel_view.coffee
@@ -169,14 +169,13 @@
   clickOpinionType: ->

   bindTooltips: ->
-    return unless @options.showsTooltips
+    if @options.showsTooltips
+      @$("div.tooltip").remove()
+      @$(".authority").tooltip title: "This number represents the amount of thinking " + "spent by people on this Factlink"

-    @$("div.tooltip").remove()
-    @$(".authority").tooltip title: "This number represents the amount of thinking " + "spent by people on this Factlink"
-
-    # Create tooltips for each opinionType (believe, disbelieve etc)
-    for key, opinionType of @model.get('opinion_types')
-      raphaelObject = @opinionTypeRaphaels[opinionType.type]
-      $(raphaelObject.node).tooltip
-        title: @options.opinionStyles[opinionType.type].groupname + ": " + opinionType.percentage + "%"
-        placement: "left"
+      # Create tooltips for each opinionType (believe, disbelieve etc)
+      for key, opinionType of @model.get('opinion_types')
+        raphaelObject = @opinionTypeRaphaels[opinionType.type]
+        $(raphaelObject.node).tooltip
+          title: @options.opinionStyles[opinionType.type].groupname + ": " + opinionType.percentage + "%"
+          placement: "left"


sosh

sosh

Former-commit-id: d1943046a1722b081c5ceaddb079684a701c878e [formerly 1cae751d032973c76d867bd6661fd5e33e24c8db] [formerly e6c3141849d0cf92488c8a696fa437539a6ef012 [formerly 22a0d0fc63715f24c60b55dea55b21d4950fb775]] [formerly 09c74b11f1522599836014c08ba0c2ad318bc7de [formerly 809c82d03cad248a3e580be0c00cf2d5c745d569] [formerly d68c68780c898538f1a4816215fee97138d14771 [formerly d68c68780c898538f1a4816215fee97138d14771 [formerly 747d3de2db86cdc2841f7662194be3dd51b46390]]]]
Former-commit-id: 4a77f44269e1eb92d0b48b146b90857d7bd50f7a [formerly 863b382f3be26ddfd26fd17cdc926035dd0ef53e] [formerly 17a1888a189a70e1803c7cfc509cba87e5b28a2a [formerly 5d8880db485932cd1d2f784acfdea0723a3a5f94]]
Former-commit-id: ccc86b0ee1eaf7e676776e1206c0b9ac3f75d802 [formerly 4f4473cedf513a0025b2afb5373f90871c482ed7]
Former-commit-id: b6765ea95820152d3acfbd01567ee88563a03683
Former-commit-id: 0c12e9215fff7cf113d0c1b2b71c0a95e7dfad93

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -136,6 +136,27 @@

 ##########################
 # SUBDOMAIN CONFIGURATIONS
+
+################
+# sosh
+customizations['sosh'] =
+  point_labels :
+    pro: 'strength'
+    pros: 'strengths'
+    con: 'weakness'
+    cons: 'weaknesses'
+    your_header: "--valences-- you observe"
+    other_header: "--valences-- observed"
+    top_header: "Foremost --valences--"
+
+  slider_pole_labels :
+    individual:
+      support: 'Yes'
+      oppose: 'No'
+    group:
+      support: 'Yes'
+      oppose: 'No'
+

 ################
 # schools


Fix method call

Fix method call

--- lib/supported-files.coffee
+++ lib/supported-files.coffee
@@ -8,7 +8,7 @@
           # or
           |
         # matches function in class 'REPLACE('
-        REPLACE \s* \(
+        [ \t\n]REPLACE \s* \(
           # or
           |
         # matches 'REPLACE = function (' or 'REPLACE: function ('
@@ -24,7 +24,7 @@
           # or
           |
         # matches function in class 'REPLACE('
-        REPLACE \s* \(
+        [ \t\n]REPLACE \s* \(
           # or
           |
         # matches 'REPLACE = function (' or 'REPLACE: function ('


:art: Move the key out

:art: Move the key out

--- lib/symbol-provider.coffee
+++ lib/symbol-provider.coffee
@@ -208,20 +208,16 @@
     # ```
     # `class` types are higher priority than `variables`
     cacheSymbol = (word, type, bufferRow, scopes) =>
-      wordKey = getWordKey(word)
-      cachedSymbol = symbols[wordKey]
+      key = @getSymbolKey(word)
+      cachedSymbol = symbols[key]
       if cachedSymbol?
         currentTypePriority = @config[type].priority
         cachedTypePriority = @config[cachedSymbol.type].priority
         cachedSymbol.type = type if currentTypePriority > cachedTypePriority
-        console.log cachedSymbol
         cachedSymbol.bufferRows.push(bufferRow)
         cachedSymbol.scopes.push(scopes)
       else
-        symbols[wordKey] = {word, type, bufferRows: [bufferRow], scopes: [scopes]}
-
-     # some words are reserved, like 'constructor' :/
-    getWordKey = (word) -> word + '$$'
+        symbols[key] = {word, type, bufferRows: [bufferRow], scopes: [scopes]}

     for {tokens}, bufferRow in tokenizedLines
       for token in tokens
@@ -234,7 +230,10 @@
                   cacheSymbol(word, type, bufferRow, scopes)
               break

-    (symbol for wordKey, symbol of symbols)
+    (symbol for key, symbol of symbols)
+
+  # some words are reserved, like 'constructor' :/
+  getSymbolKey: (word) -> word + '$$'

   cssSelectorFromScopes: (scopes) ->
     selector = ''


fix(about): avoids initializing slideshow if selector is missing (#8184)

--- src/desktop/apps/about/client/view.coffee
+++ src/desktop/apps/about/client/view.coffee
@@ -93,6 +93,7 @@
       , offset: -> -($(this).height() - 1)

   setupHeroUnitSlideshow: ->
+    return unless @$('.about-hero-unit-bgs').length
     @heroUnitCycle = new Cycle $el: @$('.about-hero-unit-bgs'), selector: '.about-hero-unit-bg'
     @heroUnitCycle.start()


Make the method more generic to fransform all and not just Zapier

--- oauth.coffee
+++ oauth.coffee
@@ -45,15 +45,17 @@
 				console.log '[OAuth2Server]', req.method, req.url
 			next()

-		fixZapierNotUsingFormUrlencodedType = (req, res, next) ->
-			if not req.is('application/x-www-form-urlencoded') and req.method is 'POST' and req.get('user-agent') is 'Zapier'
+		# Transforms requests which are POST and aren't "x-www-form-urlencoded" content type
+		# and they pass the required information as query strings
+		transformRequestsNotUsingFormUrlencodedType = (req, res, next) ->
+			if not req.is('application/x-www-form-urlencoded') and req.method is 'POST'
 				if self.config.debug is true
-					console.log '[OAuth2Server]', 'Transforming a request for Zapier. To form-urlencoded and query to body.'
+					console.log '[OAuth2Server]', 'Transforming a request to form-urlencoded with the query going to the body.'
 				req.headers['content-type'] = 'application/x-www-form-urlencoded'
 				req.body = req.query
 			next()

-		@app.all '/oauth/token', debugMiddleware, fixZapierNotUsingFormUrlencodedType, @oauth.grant()
+		@app.all '/oauth/token', debugMiddleware, transformRequestsNotUsingFormUrlencodedType, @oauth.grant()

 		@app.get '/oauth/authorize', debugMiddleware, Meteor.bindEnvironment (req, res, next) ->
 			client = self.model.Clients.findOne({ active: true, clientId: req.query.client_id })


more tests

more tests

--- src/experiments/parse-htmlish-tags.coffee
+++ src/experiments/parse-htmlish-tags.coffee
@@ -48,7 +48,7 @@
   @ontext = ( text ) ->
     start = @parser.startIndex
     end   = @parser.endIndex
-    handler null, { mark: '.', value: text, start, end, }
+    handler null, { mark: '.', name: 'text', value: text, start, end, }

   #---------------------------------------------------------------------------------------------------------
   @onclosetag = ( name ) ->
@@ -90,6 +90,9 @@
     "helo <いきましょうか/>world"
     "<div>just a </div> that is closed"
     "just a </div> that is closed"
+    "some < lonely > brackets"
+    "some < lonely brackets"
+    "some lonely > brackets"
     # """<?xml-stylesheet type="text/xsl" href="style.xsl"?>foobar"""
     # "helo <x:b>world</x:b>"
     # "helo <b><i>world"


pointless refactoring

pointless refactoring

--- src/form_components/response_field.coffee
+++ src/form_components/response_field.coffee
@@ -1,3 +1,6 @@
+_isPageButton = (el) ->
+  el && (el.hasAttribute('data-fr-next-page') || el.hasAttribute('data-fr-previous-page'))
+
 FormRenderer.Models.ResponseField = FormRenderer.Models.BaseFormComponent.extend
   input_field: true
   field_type: undefined
@@ -136,14 +139,11 @@
         newActive = document.activeElement

         unless $.contains(@el, newActive)
-          if @_isPageButton(newActive)
+          if _isPageButton(newActive)
             $(document).one 'mouseup', => @model.validate()
           else
             @model.validate()
       , 1
-
-  _isPageButton: (el) ->
-    el && (el.hasAttribute('data-fr-next-page') || el.hasAttribute('data-fr-previous-page'))

   # Run validations on change if there are errors
   _onInput: ->


Remove black-magic from set method

Remove black-magic from set method

--- src/intermediate/IntermediateModel.coffee
+++ src/intermediate/IntermediateModel.coffee
@@ -8,21 +8,25 @@
   get: (key) ->
     @[key]

-  set: (key, value, silent) ->
+  set: (key, value) ->
     @[key] = value
-    if not @_events[key] or silent
-      return
-    for callback in @_events[key]
-      (callback)()
+
+  on: (eventName, callback) ->
+    @_events[eventName] = @_events[eventName] or []
+    @_events[eventName].push(callback)

   onChange: (key, callback) ->
-    @_events[key] = @_events[key] or []
-    @_events[key].push(callback)
+    @on('change_' + key, callback)
+
+  emit: (eventName) ->
+    for callback in @_events[eventName]
+      callback()

   fetch: ->
     setTimeout( =>
       res = @genData()
       @set('data', res)
+      @emit('change_data')
     , 300)

   genData: ->


Use skinny arrow for rootView.eachEditor callback

--- lib/wrap-guide-view.coffee
+++ lib/wrap-guide-view.coffee
@@ -5,7 +5,7 @@
 module.exports =
 class WrapGuideView extends View
   @activate: ->
-    rootView.eachEditor (editor) =>
+    rootView.eachEditor (editor) ->
       editor.underlayer.append(new WrapGuideView(editor)) if editor.attached

   @content: ->


fixing tests

fixing tests

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -158,7 +158,6 @@

         result_exp = _ExpressionManipulator.build(expression_position.expression()).clone().
           withComponent(expression_position, (component)=>
-
             _ImplicitMultiplication.build(@comps).invokeD(component)
             component.appendD(@comps.build_exponentiation(base: base, power: power))
           ).value()
@@ -174,15 +173,18 @@
         @position = @pos.build(new_exp.last().id())
         new_exp

-      perform: (expression, position)->
-        if expression.isEmpty()
-          expression: expression
-          position: position
-        else
-          expr = _FinalOpenSubExpressionApplication.build(expr: expression, comps: @comps).
-            performOrDefault((expr)=> @appendAction(expr))
-          expression: expr
-          position: @position
+      perform: (expression_position)->
+        expr = expression_position.expression()
+        return expression_position if expr.isEmpty()
+
+        result_exp = _ExpressionManipulator.build(expr).clone().
+          withComponent(expression_position, (component)=>
+            _OverrideIfOperatorOrAppend.build(@comps, component).withD(
+              @comps.build_multiplication()
+            )
+          ).value()
+
+        expression_position.clone(expression: result_exp)

     class_mixer(AppendMultiplication)

@@ -398,18 +400,30 @@

     class _OverrideIfOperatorOrAppend
       initialize: (@comps, @expression)->
-      with: (operator)->
+      with: (operator, append_method="append", replace_method="replaceLast")->
         last = @expression.last()
-        if last && last.isOperator()
-          if last instanceof @comps.classes.exponentiation
-            if last.power().isBlank()
-              @expression.replaceLast(operator)
+
+        action =
+          if last
+            if last.isOperator()
+              if last instanceof @comps.classes.exponentiation
+                "append"
+              else
+                "replace"
             else
-              @expression.append(operator)
+              "append"
           else
-            @expression.replaceLast(operator)
-        else
-          @expression.append(operator)
+            "append"
+
+        if action == "append"
+          @expression[append_method](operator)
+        else if action == "replace"
+          @expression[replace_method](operator)
+
+      # @destructive
+      withD: (operator)->
+        @with(operator, "appendD", "replaceLastD")
+
     class_mixer(_OverrideIfOperatorOrAppend)


update

update

--- vendor/assets/javascripts/maj-text-counter.coffee
+++ vendor/assets/javascripts/maj-text-counter.coffee
@@ -1,10 +1,10 @@
-/*!
-* jQuery Maj Text Counter Plugin v0.1
-* https://github.com/supernini/majTextCounter
-*
-* Copyright 2014 miseajour.net
-* Released under the MIT license
-*/
+# /*!
+# * jQuery Maj Text Counter Plugin v0.1
+# * https://github.com/supernini/majTextCounter
+# *
+# * Copyright 2014 miseajour.net
+# * Released under the MIT license
+# */

 $.fn.extend
   majTextCounter: (options) ->