defiened -> defined

defiened -> defined

--- src/bind.coffee
+++ src/bind.coffee
@@ -5,8 +5,8 @@
   def_data_fn = dvl.const((d) -> [d])
   class_concat = dvl.op((s, d) -> s + ' ' + (d or ''))
   dvl.bind = ({parent, self, data, join, attr, style, property, text, html, on:argsOn, transition, transitionExit}) ->
-    throw "'parent' not defiend" unless parent
-    throw "'self' not defiend" unless typeof self is 'string'
+    throw "'parent' not defined" unless parent
+    throw "'self' not defined" unless typeof self is 'string'
     parts = self.split(id_class_spliter)
     nodeType = parts.shift()
     staticId = null
@@ -148,7 +148,7 @@

   dvl.bindSingle = ({parent, self, datum, attr, style, property, text, html, on:argsOn, transition}) ->
     if typeof self is 'string'
-      throw "'parent' not defiend for string self" unless parent
+      throw "'parent' not defined for string self" unless parent
       parts = self.split(id_class_spliter)
       nodeType = parts.shift()
       staticId = null


Cell#toString must check if the value is defined

--- src/cell.coffee
+++ src/cell.coffee
@@ -11,8 +11,8 @@
     @changed = new Signal(this)

   get: ->
-	  @value
-
+    @value
+
   set: (value) ->
     oldval = @value
     @value = value
@@ -23,4 +23,8 @@
     this.wListen('changed', fn)

   toString: ->
-	  this.get().toString()+    v = this.get()
+    if v?
+      v.toString()
+    else
+      null


add res param

add res param

--- generators/script/templates/src/template.coffee
+++ generators/script/templates/src/template.coffee
@@ -18,5 +18,5 @@
   robot.respond /hello/, (res) ->
     res.reply "hello!"

-  robot.hear /orly/, ->
+  robot.hear /orly/, (res) ->
     res.send "yarly"


Prevent trying to load (probably) nonexistent post

--- resources/assets/coffee/forum-posts-seek.coffee
+++ resources/assets/coffee/forum-posts-seek.coffee
@@ -64,12 +64,16 @@
     e.preventDefault()

     currentPost = @forum.currentPostPosition
+    totalPosts = @forum.totalPosts()
     target = $(e.currentTarget).attr('data-jump-target')

     n = switch target
       when 'first' then 1
-      when 'last' then @forum.totalPosts()
+      when 'last' then totalPosts
       when 'previous' then currentPost - 10
       when 'next' then currentPost + 10

+    n = Math.max(1, n)
+    n = Math.min(totalPosts, n)
+
     @forum.jumpTo n


fixing bug #5

fixing bug #5

--- ui/script/directives/data/processing.coffee
+++ ui/script/directives/data/processing.coffee
@@ -65,10 +65,12 @@
             _.forEach data, (d) -> index[Object.toId d] = d

             _.forEach @scope.processing.inputs, (d) ->
-              d.datum.datum = index[Object.toId d.datum.datum]
+              if d.datum?
+                d.datum.datum = index[Object.toId d.datum.datum]

             _.forEach @scope.processing.outputs, (d) ->
-              d.datum.datum = index[Object.toId d.datum.datum]
+              if d.datum?
+                d.datum.datum = index[Object.toId d.datum.datum]

       childColor: =>
         tinycolor(@scope.color).darken(3).toString()


Fixes for IE10

Fixes for IE10

--- vendor/assets/javascripts/control.js.coffee
+++ vendor/assets/javascripts/control.js.coffee
@@ -186,7 +186,7 @@
     node = document.createElement('span')
     node.className = 'token'
     node.contentEditable = false
-    node.dataset.id = id
+    node.setAttribute('data-id', id)
     node.innerHTML = name
     @range.insertNode(node)


Dont use config

Dont use config

--- views/components/main/parts/task-panel.cjsx
+++ views/components/main/parts/task-panel.cjsx
@@ -204,13 +204,12 @@
       when '/kcsapi/api_req_kousyou/destroyitem2'
         flag = updateQuestRecord('destory_item', null, 1)
     return unless flag
-    if config.get('poi.quest.tracker.enable', true)
-      for task in tasks
-        continue if task.id == 100000
-        if questGoals[task.id]?
-          task.tracking = true
-          task.percent = questRecord[task.id].count / questRecord[task.id].required
-          task.progress = questRecord[task.id].count + ' / ' + questRecord[task.id].required
+    for task in tasks
+      continue if task.id == 100000
+      if questGoals[task.id]?
+        task.tracking = true
+        task.percent = questRecord[task.id].count / questRecord[task.id].required
+        task.progress = questRecord[task.id].count + ' / ' + questRecord[task.id].required
     tasks = _.sortBy tasks, (e) -> e.id
     @setState
       tasks: tasks
@@ -249,7 +248,7 @@
       shipType = window.$ships[shipId].api_stype
       if shipType in [7, 11, 13, 15]
         flag = updateQuestRecord('sinking', {shipType: shipType}, 1) || flag
-    if flag and config.get('poi.quest.tracker.enable', true)
+    if flag
       {tasks} = @state
       for task in tasks
         continue if task.id == 100000


Cloud9 specific code for server running and debugging

--- server/src/ServerListener.coffee
+++ server/src/ServerListener.coffee
@@ -1,6 +1,8 @@
+config = require('./config.js')
 express = require("express")
 http = require('http')
 nowjs = require("now")
+

 app = express()
 server = http.createServer(app)
@@ -10,7 +12,11 @@
   res.sendfile(__dirname + "/views/#{req.params.viewName}")
 )

-server.listen(8080)
+if config.inCloudEditor? and config.inCloudEditor
+  console.log "In C9 environment. port: #{process.env.PORT}; host: #{process.env.IP}"
+  server.listen(process.env.PORT, process.env.IP)
+else
+  server.listen(8080)
 console.log "Listening"

 module.exports.app = app


Implemented some of the tests.

Implemented some of the tests.

--- tests/load-routes-tests.coffee
+++ tests/load-routes-tests.coffee
@@ -1,6 +1,63 @@
 describe 'Knockout single page router route loading', () ->
-	it 'Loads a single route', () -> expect(true).toBe(false)
-	it 'Loads multiple routes', () -> expect(true).toBe(false)
+	it 'Loads a single index route', () ->
+		router = new KnockoutSinglePageRouter [
+			{
+				name: 'default'
+				url: '/'
+			}
+		]
+
+		expect(router.routes.length).toBe(1)
+
+		route = router.routes[0]
+
+		expect(route.component).toBe('default')
+		expect(route.regex.toString()).toBe((/^\/?\/?(\?.*)?$/i).toString())
+
+	it 'Loads a single route with a single path component', () ->
+		router = new KnockoutSinglePageRouter [
+			{
+				name: 'default'
+				url: '/foo'
+			}
+		]
+
+		expect(router.routes.length).toBe(1)
+
+		route = router.routes[0]
+
+		expect(route.component).toBe('default')
+		expect(route.regex.toString()).toBe((/^\/?foo\/?(\?.*)?$/i).toString())
+
+	it 'Loads a single route with a multiple path components', () ->
+		router = new KnockoutSinglePageRouter [
+			{
+				name: 'default'
+				url: '/foo/bar'
+			}
+		]
+
+		expect(router.routes.length).toBe(1)
+
+		route = router.routes[0]
+
+		expect(route.component).toBe('default')
+		expect(route.regex.toString()).toBe((/^\/?foo\/bar\/?(\?.*)?$/i).toString())
+
+	it 'Loads multiple routes', () ->
+		router = new KnockoutSinglePageRouter [
+			{
+				name: 'default'
+				url: '/'
+			}
+			{
+				name: 'default'
+				url: '/foo'
+			}
+		]
+
+		expect(router.routes.length).toBe(2)
+
 	it 'Extracts a parameter from a route', () -> expect(true).toBe(false)
 	it 'Extracts multiple parameters from a route', () -> expect(true).toBe(false)
 	it 'Registers a component if it supplied one', () -> expect(true).toBe(false)


Remove split which isn't in the node package dependencies

--- highlights/highlight-filename-from-stdin.coffee
+++ highlights/highlight-filename-from-stdin.coffee
@@ -3,7 +3,6 @@
 Highlights = require 'highlights'
 fs = require 'fs'
 path = require 'path'
-split = require 'split'
 highlighter = new Highlights()
 highlighter.requireGrammarsSync
   modulePath: require.resolve('./atom-language-perl6/package.json')


no need to round coordinates

no need to round coordinates

--- src/data.coffee
+++ src/data.coffee
@@ -107,9 +107,7 @@

   projection = proj4(albersProj(albers(bbox)))

-  project = (coord) ->
-    [x, y] = projection.forward(coord)
-    return [d3.round(+x), d3.round(+y)]
+  project = (coord) -> [x, y] = projection.forward(coord); [+x, +y]

   projectNode = (node) -> project([node.lon, node.lat])


Added "Pending" instead of -1.-1

Added "Pending" instead of -1.-1

--- app/js/services/wallet.coffee
+++ app/js/services/wallet.coffee
@@ -94,7 +94,7 @@
         blktrx=val.block_num + "." + val.trx_num
         console.log blktrx
         transactions.push
-          block_num: ((if (val.block_num is "-1.-1") then "Pending" else blktrx))
+          block_num: ((if (blktrx is "-1.-1") then "Pending" else blktrx))
           #trx_num: Number(key) + 1
           time: new Date(val.received_time*1000)
           amount: val.amount.amount


fixed timeout not updating problem

fixed timeout not updating problem

--- app/js/services/wallet.coffee
+++ app/js/services/wallet.coffee
@@ -46,10 +46,10 @@
             @wallet_get_info().then (result) =>
                 deferred.resolve()
                 @get_setting('timeout').then (result) =>
-                if result && result.value
-                    @timeout = result.value
-                    @idle._options().idleDuration = @timeout
-                    @idle.watch()
+                    if result && result.value
+                        @timeout = result.value
+                        @idle._options().idleDuration = @timeout
+                        @idle.watch()
                 @get_setting('autocomplete').then (result) =>
                     @autocomplete = result.value if result
                 @get_setting('interface_locale').then (result) =>


Ghost mouse adjustments

Ghost mouse adjustments


fixes #152

fixes #152

--- app/partials/main-footer.cjsx
+++ app/partials/main-footer.cjsx
@@ -107,8 +107,8 @@
         <Markdown>{counterpart "footer.social.content"}</Markdown>

           <Translate component="span" content="footer.social.callToAction" />
-
-
+
+


Speed up unit tests by only generating big fixtures once up front

--- services/track-changes/test/unit/coffee/UpdateCompressor/UpdateCompressorTests.coffee
+++ services/track-changes/test/unit/coffee/UpdateCompressor/UpdateCompressorTests.coffee
@@ -5,14 +5,15 @@
 modulePath = "../../../../app/js/UpdateCompressor.js"
 SandboxedModule = require('sandboxed-module')

+bigstring = ("a" for [0 .. 2*1024*1024]).join("")
+mediumstring = ("a" for [0 .. 1024*1024]).join("")
+
 describe "UpdateCompressor", ->
 	beforeEach ->
 		@UpdateCompressor = SandboxedModule.require modulePath, requires:
 			"../lib/diff_match_patch": require("../../../../app/lib/diff_match_patch")
 		@user_id = "user-id-1"
 		@other_user_id = "user-id-2"
-		@bigstring = ("a" for [0 .. 2*1024*1024]).join("")
-		@mediumstring = ("a" for [0 .. 1024*1024]).join("")
 		@ts1 = Date.now()
 		@ts2 = Date.now() + 1000

@@ -150,56 +151,56 @@
 					meta: ts: @ts1, user_id: @user_id
 					v: 42
 				}, {
-					op: { p: 6, i: @bigstring }
-					meta: ts: @ts2, user_id: @user_id
-					v: 43
-				}])
-				.to.deep.equal [{
-					op: { p: 3, i: "foo" }
-					meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
-					v: 42
-				}, {
-					op: { p: 6, i: @bigstring }
+					op: { p: 6, i: bigstring }
+					meta: ts: @ts2, user_id: @user_id
+					v: 43
+				}])
+				.to.deep.equal [{
+					op: { p: 3, i: "foo" }
+					meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
+					v: 42
+				}, {
+					op: { p: 6, i: bigstring }
 					meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
 					v: 43
 				}]

 			it "should not append inserts that are too big (first op)", ->
 				expect(@UpdateCompressor.compressUpdates [{
-					op: { p: 3, i: @bigstring }
-					meta: ts: @ts1, user_id: @user_id
-					v: 42
-				}, {
-					op: { p: 3 + @bigstring.length, i: "bar" }
-					meta: ts: @ts2, user_id: @user_id
-					v: 43
-				}])
-				.to.deep.equal [{
-					op: { p: 3, i: @bigstring }
-					meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
-					v: 42
-				}, {
-					op: { p: 3 + @bigstring.length, i: "bar" }
+					op: { p: 3, i: bigstring }
+					meta: ts: @ts1, user_id: @user_id
+					v: 42
+				}, {
+					op: { p: 3 + bigstring.length, i: "bar" }
+					meta: ts: @ts2, user_id: @user_id
+					v: 43
+				}])
+				.to.deep.equal [{
+					op: { p: 3, i: bigstring }
+					meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
+					v: 42
+				}, {
+					op: { p: 3 + bigstring.length, i: "bar" }
 					meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
 					v: 43
 				}]

 			it "should not append inserts that are too big (first and second op)", ->
 				expect(@UpdateCompressor.compressUpdates [{
-					op: { p: 3, i: @mediumstring }
-					meta: ts: @ts1, user_id: @user_id
-					v: 42
-				}, {
-					op: { p: 3 + @mediumstring.length, i: @mediumstring }
-					meta: ts: @ts2, user_id: @user_id
-					v: 43
-				}])
-				.to.deep.equal [{
-					op: { p: 3, i: @mediumstring }
-					meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
-					v: 42
-				}, {
-					op: { p: 3 + @mediumstring.length, i: @mediumstring }
+					op: { p: 3, i: mediumstring }
+					meta: ts: @ts1, user_id: @user_id
+					v: 42
+				}, {
+					op: { p: 3 + mediumstring.length, i: mediumstring }
+					meta: ts: @ts2, user_id: @user_id
+					v: 43
+				}])
+				.to.deep.equal [{
+					op: { p: 3, i: mediumstring }
+					meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
+					v: 42
+				}, {
+					op: { p: 3 + mediumstring.length, i: mediumstring }
 					meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
 					v: 43
 				}]


Updated spec

Updated spec

--- spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee
+++ spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee
@@ -28,8 +28,6 @@
       scope.addNewRuleTo(scope.tagGroups[0], "DiscountOrder")

     it "adds a new rule of the specified type to the rules array for the tagGroup", ->
-      expect(scope.enterprise_form.$setDirty).toHaveBeenCalled()
-
       expect(scope.tagGroups[0].rules.length).toEqual 3
       expect(scope.tagGroups[0].rules[2].type).toEqual "TagRule::DiscountOrder"


Fix a undefined var

Fix a undefined var

--- app/assets/javascripts/models/abs_marker_model.coffee
+++ app/assets/javascripts/models/abs_marker_model.coffee
@@ -8,5 +8,5 @@
   markerWithPopup: (htmlData) ->
     m = @marker()
     m.bindPopup(htmlData) if m
-    m.on('mouseover', m.openPopup.bind(m));
+    m.on 'mouseover', m.openPopup.bind(m)  if m
     m


sorts by sales total desc

sorts by sales total desc

--- app/assets/javascripts/spree/backend/filter.js.coffee
+++ app/assets/javascripts/spree/backend/filter.js.coffee
@@ -2,4 +2,5 @@
   options =
     valueNames: [ 'name', 'sku', 'quantity', 'sales' ]

-  new List('sales_total', options)
+  list = new List('sales_total', options)
+  list.sort('sales', { order: 'desc' })


Rescoping the accordion as well

Rescoping the accordion as well

--- app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee
@@ -1,16 +1,11 @@
-Darkswarm.controller "AccordionCtrl", ($scope, storage, $timeout) ->
+Darkswarm.controller "AccordionCtrl", ($scope, storage, $timeout, CurrentHub) ->
   $scope.accordion =
     details: true
     shipping: false
     payment: false
     billing: false
-  storage.bind $scope, "accordion", {storeName: "accordion_#{$scope.order.id}"}
+  storage.bind $scope, "accordion", {storeName: "accordion_#{$scope.order.id}#{CurrentHub.id}#{$scope.order.user_id}"}

   $scope.show = (name)->
     $scope.accordion[name] = true

-  #$timeout =>
-    #if $scope.checkout.$valid
-      #for k, v of $scope.accordion
-        #$scope.accordion[k] = false
-


Call toScroll after any new panel is . Also added an actual delay as it still scrolled to the 'old' location.. 10 ms was too short to allow the location to update after the old panel closes (on Chrome), 50 ms seems ok.

--- app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee
@@ -3,23 +3,22 @@
   value = if localStorageService.get(key) then {} else { details: true, billing: false, shipping: false, payment: false }
   localStorageService.bind $scope, "accordion", value, key
   $scope.accordionSections = ["details", "billing", "shipping", "payment"]
+  # Scrolling is confused by our position:fixed top bar - add an offset to scroll
+  # to the correct location, plus 5px buffer
+  offset_height = $("nav.top-bar").height() + 5

   $scope.show = (section)->
     $scope.accordion[section] = true
+    # If we call scrollTo() directly after show(), when one of the accordions above the
+    # scroll location is closed by show(), scrollTo() will scroll to the old location of
+    # the element. Putting this in a 50 ms timeout is enough delay for the DOM to
+    # have updated.
+    $timeout (->
+      $document.scrollTo $("##{section}"), offset_height, 500), 50

   $scope.$on 'purchaseFormInvalid', (event, form) ->
     # Scroll to first invalid section
     for section in $scope.accordionSections
       if not form[section].$valid
         $scope.show section
-
-        # If we call scrollTo() directly after show(), when one of the accordions above the
-        # scroll location is closed by show(), scrollTo() will scroll to the old location of
-        # the element. Putting this in a zero-length timeout is enough delay for the DOM to
-        # have updated.
-        $timeout ->
-          # Scrolling is confused by our position:fixed top bar - add an offset to scroll
-          # to the correct location, plus 5px buffer
-          offset_height = $("nav.top-bar").height() + 5
-          $document.scrollTo $("##{section}"), offset_height, 500
         break


activity: drop shadow on scrolling, live update only when scrolled to top

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -31,6 +31,14 @@
       cssClass : "lazy-loader"
       partial  : "There is no activity item."

+    @scrollView.on 'scroll', (event) =>
+      if event.delegateTarget.scrollTop > 0
+        @activityHeader.setClass "scrolling-up-outset"
+        @activityHeader.liveUpdateButton.setValue off
+      else
+        @activityHeader.unsetClass "scrolling-up-outset"
+        @activityHeader.liveUpdateButton.setValue on
+
     @noActivityItem.hide()

   loadView:(mainView)->


payment: sentence case

payment: sentence case

--- client/landing/app/Applications/Dashboard.kdapplication/views/productlistitem.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/productlistitem.coffee
@@ -20,7 +20,7 @@
     @embedButton.hide()  unless soldAlone

     @clientsButton = new KDButtonView
-      title    : "View Buyers"
+      title    : "View buyers"
       callback : => @emit 'BuyersReportRequested', product

     @deleteButton = new KDButtonView


change block colour on placement

change block colour on placement

--- public/javascripts/tetris.coffee
+++ public/javascripts/tetris.coffee
@@ -46,7 +46,7 @@
       console.log("BLOCK", @x, @y)

     draw: (c) ->
-      c.fillStyle = "#000"
+      c.fillStyle = "#c00"
       c.fillRect(@x*Tetris.gridSize+1, @y*Tetris.gridSize+1,Tetris.blockSize, Tetris.blockSize)


FIX: autocapitalize and other auto settings work correctly with "false"

--- source/Art/Engine/Forms/TextInputElement.coffee
+++ source/Art/Engine/Forms/TextInputElement.coffee
@@ -33,16 +33,20 @@
       postSetter: (v) -> @domElement?.fontSize    = "#{v || defaultFontSize}px"
     color:        postSetter: (v) -> @domElement?.color       = rgbColor(v || "black").toString()

+  normalizeAuto = (v) ->
+    if v?
+      v || "off"
+
   constructor: (options = {}) ->
-    props =
+    props = merge
       placeholder:    options.placeholder || ""
       type:           options.type
       # NOTE: moving towards using 100% lowerCamelCase in Art.Engine - even if HTML5's name is full-lower-case
       # SO, these full-lower-case options are depricated (e.g. don't use maxlength, use maxLength)
       maxlength:      options.maxLength       || options.maxlength
-      autocapitalize: options.autoCapitalize  || options.autocapitalize
-      autocomplete:   options.autoComplete    || options.autocomplete
-      autocorrect:    options.autoCorrect     || options.autocorrect
+      autocapitalize: normalizeAuto options.autoCapitalize  ? options.autocapitalize
+      autocomplete:   normalizeAuto options.autoComplete    ? options.autocomplete
+      autocorrect:    normalizeAuto options.autoCorrect     ? options.autocorrect

     Factory = if props.type == "textarea"
       delete props.type


FIX: blur event on TextInputElement actually passes an event object

--- source/Art/Engine/Forms/TextInputElement.coffee
+++ source/Art/Engine/Forms/TextInputElement.coffee
@@ -87,11 +87,11 @@

   preprocessEventHandlers: (handlerMap) ->
     merge super,
-      focus: (event)=>
+      focus: (event) =>
         @domElement.focus() unless @domElementFocused
         handlerMap.focus? event
-      blur:  =>
-        @domElement.blur() if     @domElementFocused
+      blur:  (event) =>
+        @domElement.blur()  if     @domElementFocused
         handlerMap.blur? event
       keyPress: (e) =>
         handlerMap.keyPress? e


Remove unneeded path setting of project

Remove unneeded path setting of project

--- spec/bracket-matcher-spec.coffee
+++ spec/bracket-matcher-spec.coffee
@@ -8,8 +8,6 @@
   beforeEach ->
     window.rootView = new RootView
     window.rootView.attachToDom()
-
-    project.setPath(path.join(__dirname, 'fixtures'))

     rootView.openSync('sample.js')


:lipstick: Clean async mess in tests

:lipstick: Clean async mess in tests

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -195,9 +195,8 @@
           editorElement.style.width = '800px'
           editorElement.style.height = '500px'

-          runs ->
-            sleep(150)
-            runs -> nextAnimationFrame()
+          sleep(150)
+          runs -> nextAnimationFrame()

         it 'detect the resize and adjust itself', ->
           expect(minimapElement.offsetWidth).toBeCloseTo(editorElement.offsetWidth / 11, 0)


Remove all items helper for kddiacontainer

--- client/Framework/components/dia/kddiacontainer.coffee
+++ client/Framework/components/dia/kddiacontainer.coffee
@@ -30,3 +30,6 @@
   addItem:(data, options={})->
     itemClass = @getOption 'itemClass'
     @addDia new itemClass options, data
+
+  removeAllItems:->
+    dia.destroy?() for _, dia of @dias


ActivityItemChildView: set options as object instead of null

--- client/Social/Activity/views/activityitemchild.coffee
+++ client/Social/Activity/views/activityitemchild.coffee
@@ -41,7 +41,7 @@
     # -> JOpinion
     #    -> JComment
     if data.bongo_.constructorName in ["JDiscussion","JTutorial"]
-      @commentBox = new OpinionView null, data
+      @commentBox = new OpinionView {}, data
       list        = @commentBox.opinionList
     else
       commentSettings = options.commentSettings or null
@@ -53,7 +53,7 @@
       delegate : list
     , data

-    @settingsButton = new ActivitySettingsView null, data
+    @settingsButton = new ActivitySettingsView {}, data

     super options, data


EnvironmentsModal: Minor code refactor.

EnvironmentsModal: Minor code refactor.

--- client/app/lib/environment/environmentlistitem.coffee
+++ client/app/lib/environment/environmentlistitem.coffee
@@ -102,13 +102,10 @@

   generateMenuCallback: (provider) ->

-    {handleNewMachineRequest} = ComputeHelpers
-    delegate = @getDelegate()
-
     return =>
-      handleNewMachineRequest { provider }
+      ComputeHelpers.handleNewMachineRequest { provider }
       @_menu.destroy()
-      delegate.emit 'ModalDestroyRequested'
+      @getDelegate().emit 'ModalDestroyRequested'

   pistachio: ->


bugfix: reference error defaultCountry => 'US'

--- client/landing/app/MainApp/payment/billingform.coffee
+++ client/landing/app/MainApp/payment/billingform.coffee
@@ -30,7 +30,7 @@
         nextElementFlat   :
           country         :
             itemClass     : KDSelectBox
-            defaultValue  : data.country or defaultCountry
+            defaultValue  : data.country or 'US'

       phone               :
         label             : 'Phone'


Improve spec for D3Bar::generateData

Improve spec for D3Bar::generateData

--- spec/chart-d3-bar-spec.coffee
+++ spec/chart-d3-bar-spec.coffee
@@ -66,9 +66,17 @@
             args.data.datasets
           )
           expect(actual).to.be.an(Array)
+
+        it 'should have same values in properties', ->
+          actual = instance.generateData(
+            args.data.labels,
+            args.data.datasets
+          )
           expect(actual[0].key).to.be('January')
-          expect(actual[0].values[1].value).to.be(28)
-          expect(actual[6].values[1].value).to.be(100)
+          values = actual[0].values[1]
+          expect(values.value).to.be(28)
+          expect(values.fillColor).to.be('rgba(151,187,205,0.5)')
+          expect(values.strokeColor).to.be('rgba(151,187,205,1)')

   describe '::getTransitionElement', ->
     it 'should return an array', ->


:green_heart: Fix missing config in search specs

--- spec/color-search-spec.coffee
+++ spec/color-search-spec.coffee
@@ -5,6 +5,11 @@
   [search, pigments, project] = []

   beforeEach ->
+    atom.config.set 'pigments.sourceNames', [
+      '**/*.styl'
+      '**/*.less'
+    ]
+
     waitsForPromise -> atom.packages.activatePackage('pigments').then (pkg) ->
       pigments = pkg.mainModule
       project = pigments.getProject()
@@ -14,10 +19,7 @@
   describe 'when created with basic options', ->
     beforeEach ->
       search = new ColorSearch
-        sourceNames: [
-          '**/*.styl'
-          '**/*.less'
-        ]
+        sourceNames: atom.config.get 'pigments.sourceNames'
         ignoredNames: [
           'project/vendor/**'
         ]


OMG the specs actually pass

OMG the specs actually pass

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -91,7 +91,7 @@
           expect(rootView.find('.fuzzy-finder')).not.toExist()

     describe "when a path selection is confirmed", ->
-      it "opens the file associated with that path in the editor", ->
+      it "opens the file associated with that path in that split", ->
         rootView.attachToDom()
         editor1 = rootView.getActiveView()
         editor2 = editor1.splitRight()
@@ -101,10 +101,13 @@
         expectedPath = project.resolve('dir/a')
         finderView.confirmed({filePath: expectedPath})

+        editor3 = rootView.getActiveView()
+
         expect(finderView.hasParent()).toBeFalsy()
         expect(editor1.getPath()).not.toBe expectedPath
-        expect(editor2.getPath()).toBe expectedPath
-        expect(editor2.isFocused).toBeTruthy()
+        expect(editor2.getPath()).not.toBe expectedPath
+        expect(editor3.getPath()).toBe expectedPath
+        expect(editor3.isFocused).toBeTruthy()

       describe "when the selected path is a directory", ->
         it "leaves the the tree view open, doesn't open the path in the editor, and displays an error", ->
@@ -122,13 +125,18 @@
     describe "toggling", ->
       describe "when there are pane items with paths", ->
         beforeEach ->
+          rootView.attachToDom()
           rootView.open('sample.txt')

         it "shows the FuzzyFinder if it isn't showing, or hides it and returns focus to the active editor", ->
-          rootView.attachToDom()
           expect(rootView.find('.fuzzy-finder')).not.toExist()
           rootView.getActiveView().splitRight()
-          [editor1, editor2] = rootView.getEditors()
+          [editor1, editor2, editor3] = rootView.getEditors()
+          expect(rootView.getActiveView()).toBe editor3
+
+          expect(editor1.isFocused).toBeFalsy()
+          expect(editor2.isFocused).toBeFalsy()
+          expect(editor3.isFocused).toBeTruthy()

           rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
           expect(rootView.find('.fuzzy-finder')).toExist()
@@ -137,14 +145,14 @@

           rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
           expect(editor1.isFocused).toBeFalsy()
-          expect(editor2.isFocused).toBeTruthy()
+          expect(editor2.isFocused).toBeFalsy()
+          expect(editor3.isFocused).toBeTruthy()
           expect(rootView.find('.fuzzy-finder')).not.toExist()

           rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
           expect(finderView.miniEditor.getText()).toBe ''

         it "lists the paths of the current items, sorted by most recently opened but with the current item last", ->
-          rootView.attachToDom()
           rootView.open 'sample-with-tabs.coffee'
           rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
           expect(_.pluck(finderView.list.find('li > div.file'), 'outerText')).toEqual ['sample.txt', 'sample.js', 'sample-with-tabs.coffee']
@@ -195,14 +203,18 @@
           expect(_.pluck(finderView.list.find('li > div.file'), 'outerText')).toEqual ['sample.js']

     describe "when a path selection is confirmed", ->
-      [editor1, editor2] = []
+      [editor1, editor2, editor3] = []

       beforeEach ->
         rootView.attachToDom()
         editor1 = rootView.getActiveView()
         editor2 = editor1.splitRight()
-        expect(rootView.getActiveView()).toBe editor2
-        rootView.open('sample.txt')
+        editor3 = rootView.open('sample.txt')
+
+        [editor1, editor2, editor3] = rootView.getEditors()
+
+        expect(rootView.getActiveView()).toBe editor3
+
         editor2.trigger 'pane:show-previous-item'
         rootView.trigger 'fuzzy-finder:toggle-buffer-finder'

@@ -213,8 +225,9 @@

           expect(finderView.hasParent()).toBeFalsy()
           expect(editor1.getPath()).not.toBe expectedPath
-          expect(editor2.getPath()).toBe expectedPath
-          expect(editor2.isFocused).toBeTruthy()
+          expect(editor2.getPath()).not.toBe expectedPath
+          expect(editor3.getPath()).toBe expectedPath
+          expect(editor3.isFocused).toBeTruthy()

       describe "when the active pane does not have an item for the selected path", ->
         it "adds a new item to the active pane for the selcted path", ->
@@ -227,9 +240,17 @@
           expectedPath = project.resolve('sample.txt')
           finderView.confirmed({filePath: expectedPath})

+          editor4 = rootView.getActiveView()
+
           expect(finderView.hasParent()).toBeFalsy()
-          expect(editor1.getPath()).toBe expectedPath
-          expect(editor1.isFocused).toBeTruthy()
+          expect(editor1.isFocused).toBeFalsy()
+
+          expect(editor4).not.toBe editor1
+          expect(editor4).not.toBe editor2
+          expect(editor4).not.toBe editor3
+
+          expect(editor4.getPath()).toBe expectedPath
+          expect(editor4.isFocused).toBeTruthy()

   describe "git-status-finder behavior", ->
     [originalText, originalPath, newPath] = []
@@ -527,6 +548,7 @@
     [originalText, originalPath, editor, newPath] = []

     beforeEach ->
+      rootView.attachToDom()
       editor = rootView.getActiveView()
       originalText = editor.getText()
       originalPath = editor.getPath()
@@ -547,10 +569,10 @@
         expect(finderView.find('.status.status-modified').length).toBe 1
         expect(finderView.find('.status.status-modified').closest('li').find('.file').text()).toBe 'sample.js'

-
     describe "when a new file is shown in the list", ->
       it "displays the new icon", ->
         rootView.open('newsample.js')
+        editor = rootView.getActiveView()
         project.getRepo().getPathStatus(editor.getPath())

         rootView.trigger 'fuzzy-finder:toggle-buffer-finder'


exists -> existsSync

exists -> existsSync

--- src/file.coffee
+++ src/file.coffee
@@ -307,7 +307,7 @@
   #
   # Return undefined.
   writeSync: (text) ->
-    previouslyExisted = @exists()
+    previouslyExisted = @existsSync()
     @writeFileWithPrivilegeEscalationSync(@getPath(), text)
     @cachedContents = text
     @subscribeToNativeChangeEvents() if not previouslyExisted and @hasSubscriptions()


Remove old event docs

Remove old event docs

--- src/file.coffee
+++ src/file.coffee
@@ -14,21 +14,6 @@

 # Public: Represents an individual file that can be watched, read from, and
 # written to.
-#
-# ## Events
-#
-# ### contents-changed
-#
-# Public: Fired when the contents of the file has changed.
-#
-# ### moved
-#
-# Public: Fired when the file has been renamed. {::getPath} will reflect the new path.
-#
-# ### removed
-#
-# Public: Fired when the file has been deleted.
-#
 module.exports =
 class File
   EmitterMixin.includeInto(this)


notification priority bumped

notification priority bumped

--- app/scripts/background.coffee
+++ app/scripts/background.coffee
@@ -146,6 +146,7 @@

     chrome.notifications.create tx.hash,
       type: 'basic'
+      priority: 2
       iconUrl: 'images/icon-128.png' # TODO: change this icon

       title: amount.forNotification + ' have ' + whatHappened


dirty yet great image proxy, thank you chris

--- client/app/Applications/Activity.kdapplication/views/embedboxviews.coffee
+++ client/app/Applications/Activity.kdapplication/views/embedboxviews.coffee
@@ -1,3 +1,6 @@
+proxifyUrl =(url)->
+  "https://api.koding.com/1.0/image.php?url="+ encodeURIComponent(url)
+
 class EmbedBoxLinksViewItem extends KDListItemView

   constructor:(options,data)->
@@ -29,13 +32,13 @@
       callback :=>
         @changeEmbed()

-    @favicon = data.favicon_url or ""
+    @favicon = (data.favicon_url) or ""

     @faviconImage = new KDCustomHTMLView
       tagName     : "img"
       cssClass    : "embed-favicon hidden"
       attributes  :
-        src       : @favicon
+        src       : proxifyUrl @favicon
         alt       : data.title

     @faviconImage.show() if @favicon isnt ""
@@ -54,11 +57,12 @@

     @setClass "active"

-  setFavicon:(fav="")->
-    @favicon = fav
-
-    @faviconImage.setDomAttributes src:@favicon
-    @faviconImage.show()
+  setFavicon:(fav)->
+    if fav?
+      @favicon = fav
+
+      @faviconImage.setDomAttributes src:proxifyUrl @favicon
+      @faviconImage.show()

   viewAppended:->
     super()
@@ -255,10 +259,10 @@
       if @getEmbedImageIndex() < @getData().link_embed?.images.length-1
         imgSrc = @getData().link_embed?.images?[@getEmbedImageIndex()]?.url
         if imgSrc
-          @getDelegate().embedImage.setSrc imgSrc
+          @getDelegate().embedImage.setSrc proxifyUrl imgSrc
         else
           # imgSrc is undefined - this would be the place for a default
-          fallBackImgSrc="http://koding.com/images/service_icons/Koding.png"
+          fallBackImgSrc="https://koding.com/images/service_icons/Koding.png"
           @getDelegate().embedImage.setSrc fallBackImgSrc

         # Either way, set the embedImageIndex to the appropriate nr
@@ -329,7 +333,7 @@

-
+

@@ -365,8 +369,8 @@

   # this includes a fallback for when the embedimageindex is out of bounds
   # it will however still request a nonsensical image src
-    @imageLink  = @getData().link_embed?.images?[@getData().link_embed_image_index]?.url or\
-                  @getData().link_embed?.images?[0]?.url or\
+    @imageLink  = proxifyUrl(@getData().link_embed?.images?[@getData().link_embed_image_index]?.url) or\
+                  proxifyUrl(@getData().link_embed?.images?[0]?.url) or\
                   "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" # hardcode a default

     @imageAltText = (@getData().link_embed?.title + \


remove unused code

remove unused code

--- client/landing/app/Applications/Groups.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Groups.kdapplication/AppController.coffee
@@ -444,16 +444,6 @@
         new KDNotificationView {title:"Membership policy has been updated."}
       KD.showError err

-  editPermissions:(group)->
-    group.getData().fetchPermissions (err, permissionSet)->
-      if err
-        new KDNotificationView title: err.message
-      else
-        permissionsModal = new PermissionsModal {
-          privacy: group.getData().privacy
-          permissionSet
-        }, group
-
   loadView:(mainView, firstRun = yes, loadFeed = no)->

     if firstRun
@@ -570,34 +560,3 @@
             title    : 'Dismiss'
             style    : 'modal-cancel'
             callback : -> modal.destroy()
-
-  # old load view
-  # loadView:(mainView, firstRun = yes)->
-
-  #   if firstRun
-  #     mainView.on "searchFilterChanged", (value) =>
-  #       return if value is @_searchValue
-  #       @_searchValue = Encoder.XSSEncode value
-  #       @_lastSubview.destroy?()
-  #       @loadView mainView, no
-
-  #     mainView.createCommons()
-
-  #   KD.whoami().fetchRole? (err, role) =>
-  #     if role is "super-admin"
-  #       @listItemClass = GroupsListItemViewEditable
-  #       if firstRun
-  #         KD.getSingleton('mainController').on "EditPermissionsButtonClicked", (groupItem)=>
-  #           @editPermissions groupItem
-  #         KD.getSingleton('mainController').on "EditGroupButtonClicked", (groupItem)=>
-  #           groupData = groupItem.getData()
-  #           groupData.canEditGroup (err, hasPermission)=>
-  #             unless hasPermission
-  #               new KDNotificationView title: 'Access denied'
-  #             else
-  #               @showGroupSubmissionView groupData
-  #         KD.getSingleton('mainController').on "MyRolesRequested", (groupItem)=>
-  #           groupItem.getData().fetchRoles console.log.bind console
-
-  #     @createFeed mainView
-  #   # mainView.on "AddATopicFormSubmitted",(formData)=> @addATopic formData


fix(message-controls): add accountId to debug values copied to clipboard

--- internal_packages/message-list/lib/message-controls.cjsx
+++ internal_packages/message-list/lib/message-controls.cjsx
@@ -139,10 +139,11 @@

   _onCopyToClipboard: =>
     clipboard = require('electron').clipboard
-    data = "Message ID: #{@props.message.serverId}\n"+
-        "Message Metadata: #{JSON.stringify(@props.message.pluginMetadata, null, '  ')}\n"+
-        "Thread ID: #{@props.thread.serverId}\n"+
-        "Thread Metadata: #{JSON.stringify(@props.thread.pluginMetadata, null, '  ')}\n"
+    data = "AccountID: #{@props.message.accountId}\n"+
+      "Message ID: #{@props.message.serverId}\n"+
+      "Message Metadata: #{JSON.stringify(@props.message.pluginMetadata, null, '  ')}\n"+
+      "Thread ID: #{@props.thread.serverId}\n"+
+      "Thread Metadata: #{JSON.stringify(@props.thread.pluginMetadata, null, '  ')}\n"

     clipboard.writeText(data)


Translated 'settings.cson' to Hebrew

Translated 'settings.cson' to Hebrew

--- def/he/settings.cson
+++ def/he/settings.cson
@@ -1,462 +1,479 @@
 Settings:
-  "tab-title": "Settings"
+  "tab-title": "הגדרות"
   menu: [
     {
       _label: "Core"
-      value: "Core"
+      value: "בסיסי"
     }
     {
       _label: "Editor"
-      value: "Editor"
+      value: "עורך"
     }
     {
       _label: "System"
-      value: "System"
+      value: "מערכת"
     }
     {
       _label: "Keybindings"
-      value: "Keybindings"
+      value: "קיצורי מקלדת"
     }
     {
       _label: "Packages"
-      value: "Packages"
+      value: "חבילות"
     }
     {
       _label: "Themes"
-      value: "Themes"
+      value: "ערכות נושא"
     }
     {
       _label: "Updates"
-      value: "Updates"
+      value: "עדכונים"
     }
     {
       _label: "Install"
-      value: "Install"
+      value: "התקן"
     }
   ]
   "btn-open-config-folder": "Open Config Folder"
   sectionHeadings: [
-    {_label: "Core Settings", value: "Core Settings"}
-    {_label: "Editor Settings", value: "Editor Settings"}
-    {_label: "System Settings", value: "System Settings"}
-    {_label: "Keybindings", value: "Keybindings"}
-    {_label: "Installed Packages", value: "Installed Packages"}
-    {_label: "Choose a Theme", value: "Choose a Theme"}
-    {_label: "Installed Themes", value: "Installed Themes"}
-    {_label: "Install Packages", value: "Install Packages"}
-    {_label: "Featured Packages", value: "Featured Packages"}
-    {_label: "Install Themes", value: "Install Themes"}
-    {_label: "Featured Themes", value: "Featured Themes"}
-    {_label: "Available Updates", value: "Available Updates"}
+    {_label: "Core Settings", value: "הגדרות בסיסיות"}
+    {_label: "Editor Settings", value: "הגדרות עורך"}
+    {_label: "System Settings", value: "הגדרות מערכת"}
+    {_label: "Keybindings", value: "קיצורי מקלדת"}
+    {_label: "Installed Packages", value: "חבילות מותקנות"}
+    {_label: "Choose a Theme", value: "בחר ערכת נושא"}
+    {_label: "Installed Themes", value: "ערכות נושא מותקנות"}
+    {_label: "Install Packages", value: "התקן חבילות"}
+    {_label: "Featured Packages", value: "חבילות מומלצות"}
+    {_label: "Install Themes", value: "התקן ערכות נושא"}
+    {_label: "Featured Themes", value: "ערכות נושא מומלצות"}
+    {_label: "Available Updates", value: "עדכונים זמינים"}
   ]
   subSectionHeadings: [
-    {_label: "Invisible", value: "Invisible"}
-    {_label: "Community Packages", value: "Community Packages"}
-    {_label: "Core Packages", value: "Core Packages"}
-    {_label: "Development Packages", value: "Development Packages"}
-    {_label: "Git Packages", value: "Git Packages"}
-    {_label: "Community Themes", value: "Community Themes"}
-    {_label: "Core Themes", value: "Core Themes"}
-    {_label: "Development Themes", value: "Development Themes"}
-    {_label: "Git Themes", value: "Git Themes"}
+    {_label: "Invisible", value: "בלתי נראה"}
+    {_label: "Community Packages", value: "חבילות מהקהילה"}
+    {_label: "Core Packages", value: "חבילות בסיסיות"}
+    {_label: "Development Packages", value: "חבילות פיתוח"}
+    {_label: "Git Packages", value: "git חבילות"}
+    {_label: "Community Themes", value: "ערכות נושא מהקהילה"}
+    {_label: "Core Themes", value: "ערכות נושא בסיסיות"}
+    {_label: "Development Themes", value: "ערכות נושא פיתוח"}
+    {_label: "Git Themes", value: "gitערכות נושא מ"}
   ]
   settings: {
     notes: [
       {
         _id: 'core-settings-note'
-        html: 'These are Atom\'s core settings which affect behavior
-               unrelated to text editing. Individual packages may have
-               their own additional settings found within their package
-               card in the Packages list.'
+        html: 'אלה הן הגדרות הליבה של אטום אשר משפיעות על כל מה שלא קשור לעריכת טקסט.
+               לחלק מהחבילות המותקנות עשויות להיות הגדרות נוספות,
+               כאשר אותן תוכלו למצוא בכרטיס של כל חבילה
+               בעמוד החבילות'
       }
       {
         _id: 'editor-settings-note'
-        html: 'These settings are related to text editing. Some of these
-               can be overriden on a per-language basis. Check language
-               settings by clicking its package card in the
-               Packages list.'
+        html: 'הגדרות אלו קשורות לעורך הטקסט. חלק מהן נדרסות
+               ומשתנות משפה לשפה. ניתן לבדוק הגדרות לכל שפה על
+               ידי לחיצה על שם השפה בעמוד החבילות'
       }
     ]
     controls: [
       {
         _id: 'core.allowPendingPaneItems'
-        title: "Allow Pending PaneItems"
-        desc: "Allow items to be previewed without adding them to a pane
-               permanently, such as when single clicking files in the tree view."
+        title: "אפשר תצוגה מקדימה של פריטים"
+        desc: "אפשר תצוגה מקדימה של פריטים ללא פתיחה שלהם באופןישיר,
+               כגון בעת לחיצה על קבצים בודדים בתצוגת העץ"
       }
       {
         _id: 'core.audioBeep'
-        title: "Audio Beep"
-        desc: "Trigger the system's beep sound when certain actions cannot be
-               executed or there are no results."
+        title: "צפצוף אודיו"
+        desc: "נגן את צליל הצפצוף של המערכת כאשר פעולות מסויימות אינן יכולות
+               להתבצע"
       }
       {
         _id: 'core.automaticallyUpdate'
-        title: "Automatically Update"
-        desc: "Automatically update Atom when a new release is available."
+        title: "עדכן באופן אוטומטי"
+        desc: "עדכן את אטום באופן אוטומטי כאשר ישנה גרסא חדשה זמינה"
       }
       {
         _id: 'core.autoHideMenuBar'
-        title: "Auto Hide Menu Bar"
-        desc: "Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux."
+        title: "הסתר את סרגל התפריט באופן אוטומטי"
+        desc: " אפשרות זאת נתמכת במערכות ההפעלה 'ווינדוס' ו'לינוקס' בלבד.
+               הסתר את סרגל התפריט באופן אוטומטי, והצג או הסתר אותו על ידי
+               הקשה על Alt"
       }
       {
         _id: 'core.closeDeletedFileTabs'
-        title: "Close Deleted File Tabs"
-        desc: "Close corresponding editors when a file is deleted outside Atom."
+        title: "סגור כרטסיות של קבצים שנמחקו"
+        desc: "סגור כרטיסיות של קבצים שנמחקו מחוץ לאטום"
       }
       {
         _id: 'core.closeEmptyWindows'
-        title: "Close Empty Windows"
-        desc: "When a window with no open tabs or panes is given the 'Close Tab' command, close that window."
+        title: "סגור חלונות ריקים"
+        desc: "כאשר חלון ללא כרטיסיות או פאנלים מקבל את הפקודה לסגירת כרטיסייה,
+               סגור את החלון"
       }
       {
         _id: 'core.destroyEmptyPanes'
-        title: "Destroy Empty Panes"
-        desc: "When the last tab of a pane is closed, remove that pane as well."
+        title: "השמד פאנלים ריקים"
+        desc: "כאשר הכרטיסייה האחרונה בפאנל מסויים נסגרת, השמד את הפאנל"
       }
       {
         _id: 'core.excludeVcsIgnoredPaths'
-        title: "Exclude VCS Ignored Paths"
-        desc: "Files and directories ignored by the current project's VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders."
+        title: "שמוגדרים להתעלם VCS אל תכלול נתיבי"
+        desc: "קבצים ותיקיות המוגדרות ככאלו שניתן להתעלם מהן על ידי תוכנת
+        בקרת הגרסאות של הפרוייקט, יהיו בלתי נראות לחלק מהחבילות שלך.
+        לדוגמא, קבצים המוגדרים ככאלו שניתן להתעלם מהם ב'גיט' יהיו בלתי נראים
+        לחלק גדול מהחבילות שלך. חלק מהחבילות יכולות לדרוס הגדרה זו
+        על ידי הגדרה דומה לזו בדף ההגדרות של החבילה"
       }
       {
         _id: 'core.fileEncoding'
-        title: "File Encoding"
-        desc: "Default character set encoding to use when reading and writing files."
+        title: "קידוד קובץ"
+        desc: "סט ברירת מחדל של סימנים ואותיות אשר ישומש כדי לכתוב ולקרוא קבצים"
       }
       {
         _id: 'core.fileSystemWatcher'
-        title: "File System Watcher"
-        desc: "Choose the underlying implementation used to watch for filesystem changes. Emulating changes will miss any events caused by applications other than Atom, but may help prevent crashes or freezes."
+        title: "צופה מערכת קבצים"
+        desc: "בחר את היישום הבסיסי אשר ישומש כדי לצפות בשינויים במערכת קבצים.
+               חיקוי השינויים יחמיץ כל אירוע שיקרה על ידי כל ישום שהוא לא אטום,
+               אבל ככל הנראה יעזור למנוע קריסות בלתי רצויות או קיפאון של אטום"
         select:
-          native: "Native operating system APIs"
-          atom: "Emulated with Atom events"
+          native: "ממשק תכנות היישומים של מערכת ההפעלה"
+          atom: "חיקוי על ידי אירועים של אטום"
       }
       {
         _id: 'core.followSymlinks'
-        title: "Follow Symlinks"
-        desc: "Follow symbolic links when searching files and when opening files with the fuzzy finder."
+        title: "עקוב אחר סימניות"
+        desc: "Fuzzy finderעקוב אחר קישורי סימניות, בחיפוש קבצים ובפתיחת קבצים בחיפוש ב"
       }
       {
         _id: 'core.ignoredNames'
-        title: "Ignored Names"
-        desc: "List of glob patterns. Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names."
+        title: "התעלם משמות"
+        desc: "רשימה של משתנים. קבצים ותיקיות שיהיו תואמים למשתנים אלו יהיו בלתי נראים
+               לחלק מהחבילות שלך. חלק מהחבילות יכולות לדרוס הגדרה זו
+               על ידי הגדרה דומה בדף ההגדרות של החבילה"
       }
       {
         _id: 'core.openEmptyEditorOnStart'
-        title: "Open Empty Editor On Start"
-        desc: "Automatically open an empty editor on startup."
+        title: "פתח עורך ריק בעת אתחול"
+        desc: "פתח קובץ ריק בכל טעינה חדשה של אטום"
       }
       {
         _id: 'core.packagesWithKeymapsDisabled'
-        title: "Packages With Keymaps Disabled"
+        title: "חבילות עם קיצורי מקשים מושבתות"
       }
       {
         _id: 'core.projectHome'
-        title: "Project Home"
-        desc: "The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default."
+        title: "פרוייקט הבית"
+        desc: "התיקייה שבה אטום מניח שכל הפרוייקטים שלך נמצאים.
+               חבילות שיווצרו בעזרת מחולל החבילות יאוחסנו כאן
+               כברירת מחדל"
       }
       {
         _id: 'core.reopenProjectMenuCount'
-        title: "Reopen Project Menu Count"
-        desc: "How many recent projects to show in the Reopen Project menu."
+        title: "מספר הפרוייקטים ברשימת הפרוייקטים האחרונים"
+        desc: "כמה פרוייקטים אחרונים להציג בתפריט הפרוייקטים האחרונים"
       }
       {
         _id: 'core.restorePreviousWindowsOnStart'
-        title: "Restore Previous Windows On Start"
-        desc: "When checked restores the last state of all Atom windows when started from the icon or atom by itself from the command line; otherwise a blank environment is loaded."
+        title: "שחזור החלונות הקודמים בעת אתחול"
+        desc: "אם אפשרות זאת מופעלת, אטום יפתח את כל החלונות האחרונים שיהיו
+               פתוחים בכל הפעלה חדשה. אם האפשרות מכובה, בכל הפעלה יטען פרוייקט
+               ריק חדש"
         select:
-          no: "no"
-          yes: "yes"
-          always: "always"
+          no: "לא"
+          yes: "כן"
+          always: "תמיד"
       }
       {
         _id: 'core.telemetryConsent'
-        title: "Send Telemetry to the Atom Team"
-        desc: "Allow usage statistics and exception reports to be sent to the Atom team to help improve the product."
+        title: "שלח סטטיסטיקות לצוות הפיתוח של אטום"
+        desc: "אפשר לשלוח סטטיסטיקות בנוגע לשימוש ודוחות חריגים, לצורך שיפור המוצר"
         select:
-          limited: "Allow limited anonymous usage stats, exception and crash reporting"
-          no: "Do not send any telemetry data"
-          undecided: "Undecided (Atom will ask again next time it is launched)"
+          limited: "אפשר ססטיסטיקות מוגבלות וחריגות, כגון קריסות ומידע אנונימי"
+          no: "אל תשלח נתוני טלמטריה כלל"
+          undecided: "אני מתלבט - נשאל אותך בפעם הבאה"
       }
       {
         _id: 'core.titleBar'
-        title: "Title Bar"
-        desc: "Experimental: A custom title bar adapts to theme colors. Choosing custom-inset adds a bit more padding. The title bar can also be completely hidden.
Note: Switching to a custom or hidden title bar will compromise some functionality.
This setting will require a relaunch of Atom to take effect."
+        title: "שורת כותרת"
+        desc: "ניסיוני: שורת כותרת מותאמת אישית אשר מתאימה עצמה לצבעי ערכת הנושא.
+               "
+               "ניסיוני: שורת כותרת מותאמת אישית מתאימה את עצמה
+               לצבעי ערכת הנושא. בחירה באפשרות מותאמת חזק מוסיפה
+               יותר 'ריפוד' וצבע לשורה. שורת הכותרת יכולה להיות גם מוסתרת
+               באופן מלא. הערה: שינוי שורת הכותרת למותאמת אישית או למוסתרת
+               יבטל חלק מהפונקציונליות שלה. הגדרה זו תחייב הפעלה מחדש של אטום"
         select:
-          native: "native"
-          custom: "custom"
-          "custom-inset": "custom-inset"
-          hidden: "hidden"
+          native: "רגיל"
+          custom: "מותאמת אישית"
+          "custom-inset": "מותאמת חזק"
+          hidden: "מוסתרת"
       }
       {
         _id: 'core.useProxySettingsWhenCallingApm'
-        title: "Use Proxy Settings When Calling APM"
-        desc: "Use detected proxy settings when calling the apm command-line tool."
+        title: "השתמש בהגדרות הפרוקסי בכל פנייה למתקין החבילות של אטום"
+        desc: "apm השתמש בהגדרות הפרוקסי שזוהו בעת ההתקשרות לכלי הפקודה"
       }
       {
         _id: 'core.versionPinnedPackages'
-        title: "Version Pinned Packages"
-        desc: "List of names of installed packages which are not automatically updated."
+        title: "נעל גרסא לחבילות"
+        desc: "צור רשימה של שמות של חבילות שלא תרצה שיתעדכנו באופן אוטומטי"
       }
       {
         _id: 'core.useCustomTitleBar'
-        title: "Use Custom Title Bar"
-        desc: "Use custom, theme-aware title bar.
Note: This currently does not include a proxy icon.
This setting will require a relaunch of Atom to take effect."
+        title: "השתמש בשורת כותרת מותאמת אישית"
+        desc: "השתמש בשורת כותרת מותאמת אישית לערכת הנושא

+               הערה: בשלב זה, אין אפשרות זאת כוללת סמל פרוקסי

+               הגדרה זו תחייב הפעלה מחדש של אטום"
       }
       {
         _id: 'core.warnOnLargeFileLimit'
-        title: "Warn On Large File Limit"
-        desc: "Warn before opening files larger than this number of megabytes."
+        title: "קבל אזהרה לגבי קבצים גדולים"
+        desc: "הזהר לפני פתיחת קבצים גדולים ממספר זה של מגה ביטים"
       }
       {
         _id: 'editor.atomicSoftTabs'
-        title: "Atomic Soft Tabs"
-        desc: "Skip over tab-length runs of leading whitespace when moving the cursor."
+        title: "כרטיסיות אטומיות רכות"
+        desc: "דלג על טווחי הלשונית של המרחב הלבן המוביל בעת הזזת הסמן"
       }
       {
         _id: 'editor.autoIndent'
-        title: "Auto Indent"
-        desc: "Automatically indent the cursor when inserting a newline."
+        title: "הזחה אוטומטית"
+        desc: "הזז אוטומטית את הסמן בכל שורה חדשה"
       }
       {
         _id: 'editor.autoIndentOnPaste'
-        title: "Auto Indent On Paste"
-        desc: "Automatically indent pasted text based on the indentation of the previous line."
+        title: "הזחה אוטומטית בכל הדבקה"
+        desc: "הזח אוטומטית טקסט לאחר הדבקה. מבוסס על ההזחה של השורה הקודמת"
       }
       {
         _id: 'editor.backUpBeforeSaving'
-        title: "Back Up Before Saving"
+        title: "גבה לפני שמירה"
         desc: ""
       }
       {
         _id: 'editor.confirmCheckoutHeadRevision'
-        title: "Confirm Checkout HEAD Revision"
-        desc: "Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit."
+        title: "אשר שינוי בענף הראשי"
+        desc: "הצג תיבת דו-שיח לאישור בזמן שינוי הענף הראשי והשלכת שינויים
+               לקובץ הנוכחי מאז ה'קומיט' האחרון"
       }
       {
         _id: 'editor.fontFamily'
-        title: "Font Family"
-        desc: "The name of the font family used for editor text."
+        title: "משפחת גופנים"
+        desc: "שם משפחת הגופנים של טקסט העורך"
       }
       {
         _id: 'editor.fontSize'
-        title: "Font Size"
-        desc: "Height in pixels of editor text."
+        title: "גודל הגופן"
+        desc: "גודל בפיקסלים של טקסט העורך"
       }
       {
         _id: 'editor.invisibles.cr'
-        title: "Invisibles Cr"
-        desc: "Character used to render carriage return characters
-               (for Microsoft-style line endings) when the Show
-               Invisibles setting is enabled."
+        title: "תווים בלתי נראים"
+        desc: "תו אשר משומש להצגת תווים בלתי נראים כאשר האפשרות
+               הצג בלתי נראים מופעלת"
       }
       {
         _id: 'editor.invisibles.eol'
-        title: "Invisibles Eol"
-        desc: "Character used to render newline characters (\n) when the
-               Show Invisibles setting is enabled."
+        title: "שורות בלתי נראות"
+        desc: "תו אשר משומש להצגת שורה חדשה כאשר האפשרות
+               הצג בלתי נראים מופעלת"
       }
       {
         _id: 'editor.invisibles.space'
-        title: "Invisibles Space"
-        desc: "Character used to render leading and trailing space characters
-               when the Show Invisibles setting is enabled."
+        title: "רווחים בלתי נראים"
+        desc: "תו אשר משומש להצגת רווחים כאשר האפשרות
+               הצג בלתי נראים מופעלת"
       }
       {
         _id: 'editor.invisibles.tab'
-        title: "Invisibles Tab"
-        desc: "Character used to render hard tab characters (\t) when the
-               Show Invisibles setting is enabled."
+        title: "טאבים בלתי נראים"
+        desc: "תו אשר משומש להצגת טאבים כאשר האפשרות
+               הצג בלתי נראים מופעלת"
       }
       {
         _id: 'editor.lineHeight'
-        title: "Line Height"
-        desc: "Height of editor lines, as a multiplier of font size."
+        title: "גובה שורה"
+        desc: "גובה שורות העורך, כמכפלה של גודל הגופן"
       }
       {
         _id: 'editor.maxScreenLineLength'
-        title: "Max Screen Line Length"
-        desc: "Defines the maximum width of the editor window before soft wrapping is enforced, in number of characters."
+        title: "אורך מירבי של שורה במסך"
+        desc: "מגדיר את הרוחב המירבי של חלון העורך לפני שהעורך מפצל את השורות
+               באופן אוטומטי. ההגדרה נספרת במספרי תווים"
       }
       {
         _id: 'editor.nonWordCharacters'
-        title: "Non Word Characters"
-        desc: "A string of non-word characters to define word boundaries."
+        title: "תווים שלא מוגדרים כמילים"
+        desc: "מחרוזת של תווים שאינם מוגדרים כמילים, כדי להגדיר גבולות
+               של כל מילה"
       }
       {
         _id: 'editor.preferredLineLength'
-        title: "Preferred Line Length"
-        desc: "Identifies the length of a line which is used when wrapping text
-               with the Soft Wrap At Preferred Line Length setting
-               enabled, in number of characters."
+        title: "אורך מירבי מועדף של שורה"
+        desc: "מזהה את אורך השורה המשומש בעת גלישת טקסט עם ההגדרה
+               חצה שורה באורך מועדף. ההגדרה נספרת במספרי תווים"
       }
       {
         _id: 'editor.scrollPastEnd'
-        title: "Scroll Past End"
-        desc: "Allow the editor to be scrolled past the end of the last line."
+        title: "גלול לאחר סיום"
+        desc: "אפשר לעורך לגלול מטה מעבר לנקודת הסיום בשורה האחרונה בקובץ"
       }
       {
         _id: 'editor.scrollSensitivity'
-        title: "Scroll Sensitivity"
-        desc: "Determines how fast the editor scrolls when using a mouse or
-               trackpad."
+        title: "רגישות גלילה"
+        desc: "קובע את מהירות הגלילה של העורך בעת שימוש בעכבר או במשטח מגע"
       }
       {
         _id: 'editor.showCursorOnSelection'
-        title: "Show Cursor On Selection"
-        desc: "Show cursor while there is a selection."
+        title: "הצג סמן על בחירה"
+        desc: "הצג סמן גם כאשר ישנה בחירה"
       }
       {
         _id: 'editor.showIndentGuide'
-        title: "Show Indent Guide"
-        desc: "Show indentation indicators in the editor."
+        title: "הצג מדריך הזחה"
+        desc: "הצג מחווני הזחה בעורך"
       }
       {
         _id: 'editor.showInvisibles'
-        title: "Show Invisibles"
-        desc: "Render placeholders for invisible characters, such as tabs,
-               spaces and newlines."
+        title: "הצג בלתי נראים"
+        desc: "הצג 'מחזיקי מקום' עבור תווים בלתי נראים כמו 'טאבים', רווחים ושורות חדשות"
       }
       {
         _id: 'editor.showLineNumbers'
-        title: "Show Line Numbers"
-        desc: "Show line numbers in the editor's gutter."
+        title: "הצג מספרי שורות"
+        desc: "הצג את המספר של כל שורה בעורך"
       }
       {
         _id: 'editor.softTabs'
-        title: "Soft Tabs"
-        desc: "If the Tab Type config setting is set to \"auto\"
-               and autodetection of tab type from buffer content fails, then
-               this config setting determines whether a soft tab or a hard tab
-               will be inserted when the Tab key is pressed."
+        title: "הזחות טאב רכות"
+        desc: "אם ההגדרה סוג ההזחה בלחיצה על טאב
+               מוגדרת כאוטומטית והזיהוי האוטומטי של סוג הטאב נכשל, הגדרת
+               תצורה זו קובעת האם הטאב יהיה רך או קשה. יוכנס בעת
+               לחיצה על מקש הטאב במקלדת"
       }
       {
         _id: 'editor.softWrap'
-        title: "Soft Wrap"
-        desc: "Wraps lines that exceed the width of the window. When
-               Soft Wrap At Preferred Line Length is set, it will
-               wrap to the number of characters defined by the Preferred
-               Line Length setting."
+        title: "גלישת  טקסט רכה"
+        desc: "גולש שורות החורגות מרוחב החלון. כאשר ההגדרה
+               חצה שורה באורך מועדף מוגדרת, ההגדרה הזו
+               תגליש שורות לפי הגדרת אורך מירבי מועדף של שורה"
       }
       {
         _id: 'editor.softWrapAtPreferredLineLength'
-        title: "Soft Wrap At Preferred Line Length"
-        desc: "Instead of wrapping lines to the window's width, wrap lines to
-               the number of characters defined by the Preferred Line
-               Length setting. This will only take effect when the soft
-               wrap config setting is enabled globally or for the current
-               language. Note: If you want to hide the wrap
-               guide (the vertical line) you can disable the
-               wrap-guide package."
+        title: "חצה שורה באורך מועדף"
+        desc: "במקום גלישת שורות לרוחב החלון, גלוש שורות למספר התווים
+               המוגדר באורך מירבי מועדף של שורה. אפשרות זו
+               תצא לפועל רק אם גלישת טקסט רכה היא אפשרות מופעלת
+               באופן גלובלי או שהיא מופעלת לשפה שבה אתה כותב.
+               הערה: אם תרצה להשבית את מדריך הגלישה (הקו האנכי),
+               תוכל להשבית חבילה בשם warp-guide"
       }
       {
         _id: 'editor.softWrapHangingIndent'
-        title: "Soft Wrap Hanging Indent"
-        desc: "When soft wrap is enabled, defines length of additional
-               indentation applied to wrapped lines, in number of characters."
+        title: "הזחה נוספת של גלישת טקסט רכה"
+        desc: "כאשר אפשרות הגלישת טקסט רכה מופעלת, אפשרות זו
+               תגדיר את האורך של הזחות נוספות הנוספות לשורות גולשות.
+               הגדרה זו נספרת במספרי תווים"
       }
       {
         _id: 'editor.tabLength'
-        title: "Tab Length"
-        desc: "Number of spaces used to represent a tab."
+        title: "אורך טאב"
+        desc: "מספר הרווחים המייצג לחיצה על מקש הטאב"
       }
       {
         _id: 'editor.tabType'
-        title: "Tab Type"
-        desc: "Determine character inserted when Tab key is pressed. Possible
-               values: \"auto\", \"soft\" and \"hard\". When set to \"soft\" or
-               \"hard\", soft tabs (spaces) or hard tabs (tab characters) are
-               used. When set to \"auto\", the editor auto-detects the tab type
-               based on the contents of the buffer (it uses the first leading
-               whitespace on a non-comment line), or uses the value of the Soft
-               Tabs config setting if auto-detection fails."
+        title: "סוג ההזחה בלחיצה על טאב"
+        desc: "קובע את התו שיהיה מוכנס כשהמקש טאב נלחץ"
       }
       {
         _id: 'editor.undoGroupingInterval'
-        title: "Undo Grouping Interval"
-        desc: "Time interval in milliseconds within which text editing
-               operations will be grouped together in the undo history."
+        title: "זמן לקיבוץ פעולות לביטול"
+        desc: "זמן (במילישניות) שבו כל מספר פעולות של עריכת טקסט יחשבו
+               כפעולה אחת. כאשר תרצה לבטל את הפעולות כל קבוצה תתבטל
+               בלחיצה אחת על כפתור הביטול"
       }
       {
         _id: 'editor.zoomFontWhenCtrlScrolling'
-        title: "Zoom Font When Ctrl Scrolling"
-        desc: "Change the editor font size when pressing the Ctrl key and
-               scrolling the mouse up/down."
+        title: "שנה גודל גופן בזמן גלילה ולחיצה על קונטרול"
+        desc: "שנה את גודל הגופן של העורך בזמן לחיצה על מקש ה'קונטרול' במקלדת
+               וגלילה של העכבר למעלה או למטה"
       }
       {
         _id: 'system.windows.file-handler'
-        title: "Register as file handler"
-        desc: "Show Atom in the \"Open with\" application list for easy association with file types."
+        title: "רשום את אטום כמטפל בקבצים במערכת ההפעלה"
+        desc: "הצג את אטום בתפריט ה\"פתח עם\" לשימוש נוח בכל הקבצים במחשב שלך"
       }
       {
         _id: 'system.windows.shell-menu-files'
-        title: "Show in file context menus"
-        desc: "Add \"Open with Atom\" to the File Explorer context menu for files."
+        title: "הצג את אטום בתפריט הקשר של הקבצים"
+        desc: "הוסף \"פתח עם אטום\" לתפריט הקישור של הקבצים במנהל הקבצים
+              של מערכת ההפעלה שלך"
       }
       {
         _id: 'system.windows.shell-menu-folders'
-        title: "Show in folder context menus"
-        desc: "Add \"Open with Atom\" to the File Explorer context menu for folders."
+        title: "הצג את אטום בתפריט הקשר של התיקיות"
+        desc: "הוסף \"פתח עם אטום\" לתפריט הקישור של התיקיות במנהל הקבצים
+              של מערכת ההפעלה שלך"
       }
     ]
   }
   system: {
-    notes: "These settings determine how Atom integrates with your operating system."
+    notes: "הגדרות אלו קובעות כיצד אטום ישתלב ויעבוד עם מערכת ההפעלה שלך"
   }
   keymaps: {
     notes: {
-      text1: "You can override these keybindings by copying "
-      text2: "and pasting them into "
-      text3: "your keymap file"
+      text1: "תוכל לעקוף קיצורי מקשים ברירת מחדל אלו על ידי העתקה"
+      text2: " של קיצורים, והדבקה שלהם ב"
+      text3: "קובץ קיצורי המקלדת שלך"
       text4: ""   # optional
       _template: "${text1} <icon /> ${text2} ${text3} ${text4}"
     }
-    "search-bar-placeholder": "Search keybindings"
-    "table-header-keystroke": "Keystroke"
-    "table-header-command": "Command"
-    "table-header-source": "Source"
-    "table-header-selector": "Selector"
+    "search-bar-placeholder": "חפש קיצור מקשים"
+    "table-header-keystroke": "הקשה"
+    "table-header-command": "פקודה"
+    "table-header-source": "מקור"
+    "table-header-selector": "בורר"
   }
   packages: {
-    searchBarText: 'Filter packages by name'
+    searchBarText: 'סינון חבילות לפי שם'
   }
   themes: {
     notes: {
-      text1: "You can also style Atom by editing "
-      text2: "your stylesheet"
-      text3: ""   # optional
+      text1: "תוכל גם לעצב את אטום על ידי עריכת "
+      text2: "גליון הסגנונות שלך"
+      text3: ""
       _template: "${text1} ${text2} ${text3}"
     }
-    title1: "UI Theme"
-    description1: "This styles the tabs, status bar, tree view, and dropdowns"
-    title2: "Syntax Theme"
-    description2: "This styles the text inside the editor"
-    searchBarText: 'Filter themes by name'
+    title1: "ערכת נושא ממשק המשתמש"
+    description1: "ערכות נושא אלו כוללות את הכרטיסיות, שורת המצב, תצוגת העץ והתפריטים הנפתחים"
+    title2: "ערכת נושא התחביר"
+    description2: "ערכות נושא אלו כוללות את העיצוב של הטקסט בתוך העורך"
+    searchBarText: 'סינון ערכות נושא לפי שם'
   }
   updates: {
-    "check-updates": "Check for Updates"
-    "update-all": "Update All"
-    "checking-updates": "Checking for updates..."
-    "all-updated-message": "All of your installed packages are up to date!"
+    "check-updates": "סרוק עבור עדכונים"
+    "update-all": "עדכן הכל"
+    "checking-updates": "...סורק עבור עדכונים"
+    "all-updated-message": "!כל החבילות שלך מעודכנות"
   }
   install: {
     notes: {
-      text1: "Packages are published to "
-      text2: " and are installed to "
-      text3: ""   # optional
+      text1: ""
+      text2: "הוא הנתיב שבו מותקנות החבילות, כשהן מתפרסמות ב "
+      text3: ""
       _template: "${text1}atom.io${text2} /path/to/pkg ${text3}"
     }
-    "searchbar-placeholder": "Search packages"
-    "btn-packages": "Packages"
-    "btn-themes": "Themes"
+    "searchbar-placeholder": "חפש חבילות"
+    "btn-packages": "חבילות"
+    "btn-themes": "ערכות נושא"
   }
   "control-btns":{
-    install: "Install"
-    update: "Update to"
-    setting: "Settings"
-    uninstall: "Uninstall"
-    disable: "Disable"
-    enable: "Enable"
-  }
+    install: "התקן"
+    update: "עדכן"
+    setting: "הגדרות"
+    uninstall: "הסר התקנה"
+    disable: "השבת"
+    enable: "הפעל"
+  }


green if stricty ahead, red if strictly behind, no color otherwise

--- src/daily-report/services/report-builder.coffee
+++ src/daily-report/services/report-builder.coffee
@@ -14,11 +14,19 @@
     promise.then ->
       index = getCurrentDayIndex sprint.bdcData
       diff = sprint.bdcData[index].done - sprint.bdcData[index].standard
-      if diff > 0 then true else false
+      if diff > 0
+        true
+      if diff < 0
+       false

   renderBehindAhead = (message) ->
     isAhead().then (ahead) ->
-      label = if ahead then message.aheadLabel else message.behindLabel
+      if ahead
+        label = message.aheadLabel
+      else if ahead?
+        label = message.aheadLabel
+      else
+        label = message.behindLabel
       message.body = message.body.replace '{behind/ahead}', label
       message.subject = message.subject.replace '{behind/ahead}', label
       message
@@ -46,7 +54,12 @@
       message.body = message.body.replace />(.*(\{color=(.+?)\}).*)</g, (match, line, toRemove, color) ->
         line = line.replace toRemove, ""
         if color is 'smart'
-          color = if ahead then 'green' else 'red'
+          if ahead
+            color = 'green'
+          else if ahead?
+            color = 'red'
+          else
+            color = 'none'
         ">#{line}<"
       message


Remove name from mobile mention

Remove name from mobile mention

--- scripts/mobile-mention.coffee
+++ scripts/mobile-mention.coffee
@@ -8,20 +8,25 @@
 module.exports = (robot) ->

   mention = (user, message) ->
-    nameWithDot = user.match(/(.+)@inin.com/i)[1]
-    name = nameWithDot.split('.').join(' ')
-    nameCap = name.replace(/(^|\s)[a-z]/g, (f) -> f.toUpperCase())
-    cleanMessage = message.replace /@mobile/gi, ''
+    # TODO: Purecloud adapter needs updating to return actual user instead of group
+    # nameWithDot = user.match(/(.+)@inin.com/i)[1]
+    # name = nameWithDot.split('.').join(' ')
+    # nameCap = name.replace(/(^|\s)[a-z]/g, (f) -> f.toUpperCase())

     str = """
-          > ###### [@#{nameCap}](#/person/abcdefghijklmnopqrstuv) said
-          > #{cleanMessage}
+          > ###### [@Your Mobile Team Member](#/person/abcdefghijklmnopqrstuv) said
+          > #{message}

-          [  ](#/person/53a2ecf39cfde35e232982a9) [  ](#/person/50c26f9b3711784291c6807a) [  ](#/person/54f07909d96de71e0a552740) [  ](#/person/53a2eceb9cfde35e2329822a) [  ](#/person/53a2ed209cfde35e23298300) [  ](#/person/53a2ed4a9cfde35e23298437) [  ](#/person/50c26fa43711784290c68026) [  ](#/person/561fcf4e3e0d3317a49d846f) [  ](#/person/5361a63e64a211297b74a391) [  ](#/person/53a2ecec9cfde35e23298236) [  ](#/person/53a2ed3e9cfde35e2329840e)
-          Yo mobile team! #{nameCap} said something that you all should read.
+          [   ](#/person/53a2ecf39cfde35e232982a9) [   ](#/person/50c26f9b3711784291c6807a) [   ](#/person/54f07909d96de71e0a552740) [   ](#/person/53a2eceb9cfde35e2329822a) [   ](#/person/53a2ed209cfde35e23298300) [   ](#/person/53a2ed4a9cfde35e23298437) [   ](#/person/50c26fa43711784290c68026) [   ](#/person/561fcf4e3e0d3317a49d846f) [   ](#/person/5361a63e64a211297b74a391) [   ](#/person/53a2ecec9cfde35e23298236) [   ](#/person/53a2ed3e9cfde35e2329840e)
+          Yo mobile team! This is important!
           """

     return str

   robot.hear /@mobile/i, (msg) ->
+    console.log 'msg', msg
+    console.log 'msg.message.text', msg.message.text
+    console.log 'msg.message.user.name', msg.message.user.name
+    console.log 'msg.message.user', msg.message.user
     msg.send mention(msg.message.user.name, msg.message.text)
+


Removed not needed controller. Improved App.ControllerTabs and App.ControllerNavSidbar.

--- app/assets/javascripts/app/controllers/_application_controller_generic.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_generic.coffee
@@ -170,7 +170,7 @@
     # append content table
     params = _.extend(
       {
-        el:         @el.find('.table-overview')
+        el:         @$('.table-overview')
         model:      App[ @genericObject ]
         objects:    objects
         bindRow:
@@ -301,59 +301,15 @@
   inline: (data) ->
     @html App.view('generic/drox')(data)
     if data.text
-      @el.find('.drox-body').text(data.text)
+      @$('.drox-body').text(data.text)
     if data.html
-      @el.find('.drox-body').html(data.html)
+      @$('.drox-body').html(data.html)

   template: (data) ->
     drox = $( App.view('generic/drox')(data) )
     content = App.view(data.file)(data.params)
     drox.find('.drox-body').append(content)
     drox
-
-class App.ControllerLevel2 extends App.ControllerContent
-  events:
-    'click [data-toggle="tabnav"]': 'toggle',
-
-  constructor: ->
-    super
-
-  render: ->
-
-    # set title
-    @title @page.title
-    @navupdate @page.nav
-
-    @html App.view('generic/admin_level2/index')(
-      page:     @page
-      menus:    @menu
-      type:     @type
-      target:   @target
-    )
-
-    if !@target
-      @target = @menu[0]['target']
-
-    for menu in @menu
-      @el.find('.nav-tab-content').append('

')
-      if menu.controller && ( @toggleable is true || ( @toggleable is false && menu.target is @target ) )
-        params    = menu.params || {}
-        params.el = @el.find( '#' + menu.target )
-        new menu.controller( params )
-
-    @el.find('.tabbable').addClass('hide')
-    @el.find( '#' + @target ).removeClass('hide')
-    @el.find('[data-toggle="tabnav"][href*="/' + @target + '"]').parent().addClass('active')
-
-  toggle: (e) ->
-    return true if @toggleable is false
-    e.preventDefault()
-    target = $(e.target).data('target')
-    $(e.target).parents('ul').find('li').removeClass('active')
-    $(e.target).parents('li').addClass('active')
-    @el.find('.tabbable').addClass('hide')
-    @el.find('#' + target).removeClass('hide')
-#    window.scrollTo(0,0)

 class App.ControllerTabs extends App.Controller
   events:
@@ -372,24 +328,25 @@

     # insert content
     for tab in @tabs
-      @el.find('.tab-content').append("

")
+      @$('.tab-content').append("

")
       if tab.controller
         params = tab.params || {}
         params.name = tab.name
         params.target = tab.target
-        params.el = @el.find( "##{tab.target}" )
+        params.el = @$( "##{tab.target}" )
         new tab.controller( params )

-    # check if tabs need to be hidden
-    # if @tabs.length <= 1
-    #   @el.find('.page-tabs').addClass('hide')
+    # check if tabs need to be show / cant' use .tab(), because tabs are note shown (only one tab exists)
+    if @tabs.length <= 1
+      @$('.tab-pane').addClass('active')
+      return

     # set last or first tab to active
     @lastActiveTab = @Config.get('lastTab')
-    if @lastActiveTab &&  @el.find(".nav-tabs li a[href=#{@lastActiveTab}]")[0]
-      @el.find(".nav-tabs li a[href=#{@lastActiveTab}]").tab('show')
+    if @lastActiveTab &&  @$(".nav-tabs li a[href=#{@lastActiveTab}]")[0]
+      @$(".nav-tabs li a[href=#{@lastActiveTab}]").tab('show')
     else
-      @el.find('.nav-tabs li:first a').tab('show')
+      @$('.nav-tabs li:first a').tab('show')

   tabRemember: (e) =>
     @lastActiveTab = $(e.target).attr('href')
@@ -439,15 +396,22 @@
           else
             item.active = false

-    @render(selectedItem)
+    @renderContainer(selectedItem)
+    @renderNavBar(selectedItem)

     @bind(
       'ui:rerender'
       =>
-        @render(selectedItem, true)
-    )
-
-  render: (selectedItem, force) =>
+        @renderNavBar(selectedItem)
+    )
+
+  renderContainer: =>
+    return if $( ".#{@configKey}" )[0]
+    @html App.view('generic/navbar_level2/index')(
+      className:  @configKey
+    )
+
+  renderNavBar: (selectedItem) =>

     # remember latest selected item
     selectedItemMeta =
@@ -455,17 +419,24 @@
       date: new Date
     App.Config.set("Runtime::#{@configKey}", selectedItemMeta)

-    if !$( ".#{@configKey}" )[0] || force
-      @html App.view('generic/navbar_l2')(
-        groups:     @groupsSorted
-        className:  @configKey
-      )
+    @$('.sidebar').html App.view('generic/navbar_level2/navbar')(
+      groups:     @groupsSorted
+      className:  @configKey
+    )
     if selectedItem
-      @el.find('li').removeClass('active')
-      @el.find("a[href=\"#{selectedItem.target}\"]").parent().addClass('active')
-
-      new selectedItem.controller(
-        el: @el.find('.main')
+      @$('li').removeClass('active')
+      @$("a[href=\"#{selectedItem.target}\"]").parent().addClass('active')
+      @executeController(selectedItem)
+
+  executeController: (selectedItem) =>
+
+      # in case of rerendering
+      if @activeController && @activeController.render
+        @activeController.render()
+        return
+
+      @activeController = new selectedItem.controller(
+        el: @$('.main')
       )

 class App.GenericHistory extends App.ControllerModal
@@ -580,7 +551,7 @@

     for item in @items
       do (item) =>
-        @el.find('[data-type="' + item.name + '"]').on(
+        @$('[data-type="' + item.name + '"]').on(
           'click',
           (e) ->
             e.preventDefault()
@@ -624,13 +595,13 @@
     # init content callback
     for item in @items
       if item.callback
-        item.callback( @el.find( '.sidebar[data-tab="' + item.name + '"] .sidebar-content' ) )
+        item.callback( @$( '.sidebar[data-tab="' + item.name + '"] .sidebar-content' ) )

     # add item acctions
     for item in @items
       if item.actions
         new App.ActionRow(
-          el:    @el.find('.sidebar[data-tab="' + item.name + '"] .js-actions')
+          el:    @$('.sidebar[data-tab="' + item.name + '"] .js-actions')
           items: item.actions
           type:  'small'
         )
@@ -675,13 +646,13 @@
     @tabs.removeClass('active')

     # add active state
-    @el.find('.tabsSidebar-tab[data-tab=' + name + ']').addClass('active')
+    @$('.tabsSidebar-tab[data-tab=' + name + ']').addClass('active')

     # hide all content tabs
     @sidebars.addClass('hide')

     # show active tab content
-    tabContent = @el.find('.sidebar[data-tab=' + name + ']')
+    tabContent = @$('.sidebar[data-tab=' + name + ']')
     tabContent.removeClass('hide')

     # remember current tab


Don't need all that console.info.

Don't need all that console.info.

--- angular-pouchdb.coffee
+++ angular-pouchdb.coffee
@@ -56,13 +56,12 @@
     transclude: 'element'
     compile: (elem, attrs, transclude) ->
       ($scope, $element, $attr) ->
-        console.info '$transclude', transclude
         parent = $element.parent()
-        console.info $attr.pouchRepeat
         [cursor, collection] = /^\s*([a-zA-Z0-9]+)\s*in\s*([a-zA-Z0-9]+)\s*$/.exec($attr.pouchRepeat).splice(1)

         $scope.$watch collection
           , ->
+
             displayRow = (doc) ->
               childScope = $scope.$new()
               childScope[cursor] = doc


Add a class for tracking current location

--- src/maps.coffee
+++ src/maps.coffee
@@ -1,10 +1,11 @@
-Q = require('q')
+$ = require('jquery')
+Bacon = require('bacon')

 class GeoCoder
   constructor: () ->
     @geocoder = new google.maps.Geocoder()
   code: (request) ->
-    deferred = Q.defer()
+    deferred = $.Deferred()
     @geocoder.geocode request, (results, status) ->
       if status is google.maps.GeocoderStatus.OK
         deferred.resolve results.map (result) ->
@@ -14,10 +15,18 @@
             latitude:  result.geometry.loaction.lat()
       else
         deferred.reject status
-    deferred.promise
+    Bacon.fromPromise deferred
   codeAddress: (address) ->
     @code address: address
   codeLocation: (location) ->
     @code location: google.maps.LatLng(location.latitude, location.longitude)

-exports = {GeoCoder}
+class CurrentLocation
+  constructor: ->
+    stream = Bacon.fromBinder (sink) ->
+      navigator.geolocation.watchPosition(
+        (coords:{longitude, latitude}) -> sink {longitude, lataiude}
+        (e) -> sink new Bacon.Error(e)
+    @location = stream.toProperty()
+
+module.exports = {GeoCoder, CurrentLocation}


atom: custom keybinding for moving tabs

atom: custom keybinding for moving tabs

--- atom/atom.symlink/keymap.cson
+++ atom/atom.symlink/keymap.cson
@@ -10,3 +10,7 @@
 # Focus on tree view like in Linux
 '.platform-darwin':
   'alt-\\': 'tree-view:toggle-focus'
+
+'body.platform-darwin':
+  'alt-shift-cmd-left': 'pane:move-item-left'
+  'alt-shift-cmd-right': 'pane:move-item-right'


Explicitly add Buffer in tests

Explicitly add Buffer in tests

--- test/helpers.coffee
+++ test/helpers.coffee
@@ -13,6 +13,7 @@

 AWS = require('../lib/aws')
 EventEmitter = require('events').EventEmitter
+Buffer = require('buffer').Buffer
 semver = require('semver')

 require('util').print = (data) ->


Added one more ModalView test for destroyOnHidden.

--- test/app/frimfram/ModalView.spec.coffee
+++ test/app/frimfram/ModalView.spec.coffee
@@ -84,4 +84,14 @@
       modal.show(true)
       modal.hide()
       modal.once 'hidden', -> done()
- +
+
+  describe '.destroyOnHidden', ->
+    it 'can be used to keep a modal from destroying itself on hidden', ->
+      Modal = FrimFram.ModalView.extend({
+        destroyOnHidden: false
+      })
+      modal = new Modal()
+      modal.show(true)
+      modal.hide(true)
+      expect(modal.destroyed).toBeFalsy()


Add kubernetes dashboard link if available

--- plugins/kubernetes/app/assets/javascripts/kubernetes/components/clusters/item.coffee
+++ plugins/kubernetes/app/assets/javascripts/kubernetes/components/clusters/item.coffee
@@ -134,6 +134,11 @@
             i className: 'fa fa-fw fa-wrench'
             'Setup'

+          cluster.status.dashboard &&
+            a className: 'btn btn-sm btn-default btn-icon-text', disabled: disabled, href: "#{cluster.status.dashboard}", target: "_blank",
+              i className: 'fa fa-fw fa-dashboard'
+              'Kubernetes Dashboard'
+

       React.createElement ClusterEvents, cluster: cluster


add status property to round model

add status property to round model

--- src/node_modules/round-model/round-model.coffee
+++ src/node_modules/round-model/round-model.coffee
@@ -17,6 +17,39 @@
         new BucketModel(bucket)
       @fixedCosts = _.map data.fixed_costs, (fixedCost) ->
         new FixedCostModel(fixedCost)
+
+      Object.defineProperties @,
+        status:
+          get: ->
+            now = Moment()
+            if (@startsAt.isAfter(now))
+              changeLabel = "starts"
+              changeTime = @startsAt.from(now)
+              return {
+                name: "pending"
+                class: "info"
+                label: changeLabel + " " + changeTime
+                changeLabel: changeLabel
+                changeTime: changeTime
+              }
+            else if (@startsAt.isBefore(now) and @endsAt.isAfter(now))
+              return {
+                name: "open"
+                class: "success"
+                label: "open now, ends " + @endsAt.from(now)
+                changeLabel: "time left"
+                changeTime: @endsAt.from(now, true)
+              }
+            else
+              changeLabel = "ended"
+              changeTime = @endsAt.from(now)
+              return {
+                name: "closed"
+                class: "warning"
+                label: changeLabel + " " + changeTime
+                changeLabel: changeLabel
+                changeTime: changeTime
+              }

     getMyContributions: ->
       @myContributions = _.map @buckets, (bucket) ->


Dropped makeEditable from Choices, used input instead


Fixed bug in account.coffee

Fixed bug in account.coffee

"id" was on line 10 instead of "gid", preventing users from leaving groups. This bug was also fixed in the javascript generated from this coffeescript.

--- web/coffee/account.coffee
+++ web/coffee/account.coffee
@@ -7,7 +7,7 @@
     else
       for g in data
         if g['owner'] == true then permission = 'owner' else permission = 'member'
-        html += """
#{g.name}
×

"""
+        html += """
#{g.name}
×

"""
     html += "
"
     $('#group_membership_table').html html
     $('.remove-group-button').click (event) -> leave_group $(this).parent()
@@ -63,4 +63,4 @@
   $('#update-password-button').click (event) ->
     event.preventDefault()
     submit_new_password()
-    return false+    return false


Revert "Disable flow prioritizeFirstAndLastChunk to test upload order"

This reverts commit fad41ce5b253dbcb26e539df44c87969d7e5d5e6.

--- web/service/upload.coffee
+++ web/service/upload.coffee
@@ -15,6 +15,6 @@
       permanentErrors: [400, 403, 404, 409, 415, 500, 501]
       successStatuses: [200, 201, 202, 204],
       progressCallbacksInterval: 500
-      prioritizeFirstAndLastChunk: false
+      prioritizeFirstAndLastChunk: true
       headers: {'x-csverf': router.http.csverf}
 ])


Set chunksize back to a normal setting, slightly more cautious

--- web/service/upload.coffee
+++ web/service/upload.coffee
@@ -8,9 +8,15 @@
     flowOptions: () ->
       target: router.controllers.uploadChunk.route()
       method: 'octet'
+      # this setting was used for a long time
       # chunkSize: 4194304
-      chunkSize: 500
+      # use this setting to be more cautious with restricted networks
+      chunkSize: 1048576
+      # this setting allows gets around VPN problems
+      # chunkSize: 500
+      # this setting was used for a long time
       # forceChunkSize: false
+      # use this this setting to be more cautious
       forceChunkSize: true
       simultaneousUploads: 3
       testChunks: false


fix https://github.com/sagemathinc/smc/issues/1958 -- "copy/paste from jupyter output in firefox loses newlines"

--- src/smc-webapp/jupyter/cell-output-message.cjsx
+++ src/smc-webapp/jupyter/cell-output-message.cjsx
@@ -38,7 +38,9 @@

         else

-                {value}
+                {# This span below is solely to workaround an **ancient** Firefox bug }
+                {# See https://github.com/sagemathinc/smc/issues/1958    }
+                {value}

 Stderr = rclass
@@ -48,8 +50,8 @@
     mixins: [ImmutablePureRenderMixin]

     render: ->
-

-            {@props.message.get('text')}
+        # span below?  what? -- See https://github.com/sagemathinc/smc/issues/1958
+
{@props.message.get('text')}

 Image = rclass
@@ -109,7 +111,9 @@

     render: ->

-            {@props.value}
+              {# span?  what? -- See https://github.com/sagemathinc/smc/issues/1958 }
+                {@props.value}
+

 UntrustedJavascript = rclass


Enable sourcemaps on dev and prod environments (both minified and normal).

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -28,8 +28,10 @@
 		exec:
 			run:
 				command:"node app.js | ./node_modules/logger-sharelatex/node_modules/bunyan/bin/bunyan --color"
-			cssmin:
-				command:"node_modules/clean-css/bin/cleancss --s0 -o public/stylesheets/style.css public/stylesheets/style.css"
+			cssmin_sl:
+				command:"node_modules/clean-css/bin/cleancss --s0 --source-map -o public/stylesheets/style.css public/stylesheets/style.css"
+			cssmin_ol:
+				command:"node_modules/clean-css/bin/cleancss --s0 --source-map -o public/stylesheets/ol-style.css public/stylesheets/ol-style.css"

 		watch:
@@ -139,26 +141,29 @@
 			app:
 				options:
 					sourceMap: true
-					sourceMapFilename: "public/stylesheets/style.map"
+					sourceMapFilename: "public/stylesheets/style.css.map"
 					sourceMapBasepath: "public/stylesheets"
 				files:
 					"public/stylesheets/style.css": "public/stylesheets/style.less"
 			ol:
 				options:
 					sourceMap: true
-					sourceMapFilename: "public/stylesheets/ol-style.map"
+					sourceMapFilename: "public/stylesheets/ol-style.css.map"
 					sourceMapBasepath: "public/stylesheets"
 				files:
 					"public/stylesheets/ol-style.css": "public/stylesheets/ol-style.less"

 		postcss:
 			options:
-				map: true,
+				map:
+					prev: "public/stylesheets/"
+					inline: false
+					sourcesContent: true
 				processors: [
 					require('autoprefixer')({browsers: [ 'last 2 versions', 'ie >= 10' ]})
 				]
 			dist:
-				src: 'public/stylesheets/style.css'
+				src: [ "public/stylesheets/style.css", "public/stylesheets/ol-style.css" ]

 		env:
 			run:
@@ -389,7 +394,7 @@
 	grunt.registerTask 'compile:server', 'Compile the server side coffee script', ['clean:app', 'coffee:app', 'coffee:app_dir', 'compile:modules:server']
 	grunt.registerTask 'compile:client', 'Compile the client side coffee script', ['coffee:client', 'coffee:sharejs', 'wrap_sharejs', "compile:modules:client", 'compile:modules:inject_clientside_includes']
 	grunt.registerTask 'compile:css', 'Compile the less files to css', ['less', 'postcss:dist']
-	grunt.registerTask 'compile:minify', 'Concat and minify the client side js', ['requirejs', "file_append", "exec:cssmin",]
+	grunt.registerTask 'compile:minify', 'Concat and minify the client side js and css', ['requirejs', "file_append", "exec:cssmin_sl", "exec:cssmin_ol"]
 	grunt.registerTask 'compile:unit_tests', 'Compile the unit tests', ['clean:unit_tests', 'coffee:unit_tests']
 	grunt.registerTask 'compile:acceptance_tests', 'Compile the acceptance tests', ['clean:acceptance_tests', 'coffee:acceptance_tests']
 	grunt.registerTask 'compile:smoke_tests', 'Compile the smoke tests', ['coffee:smoke_tests']


mqtt: Fix error handling with mqtt>1.4.x

--- src/mqtt.coffee
+++ src/mqtt.coffee
@@ -19,17 +19,27 @@
       return callback mqtt

     @client = mqtt.connect @address
-    @client.on 'error', (err) =>
-      debug 'error', err
+
+    # debug
     @client.on 'reconnect', () =>
       debug 'reconnect'
     @client.on 'offline', () =>
       debug 'offline'
-    onConnected = (err) =>
+
+    @client.on 'error', (err) =>
+      debug 'error', err
+      if callback
+        callback err
+        callback = null
+        return
+    onConnected = (connack) =>
       debug 'connected'
       @client.on 'message', (topic, message) =>
         @_onMessage topic, message
-      return callback err
+      if callback
+        callback null
+        callback = null
+        return
     @client.once 'connect', onConnected

   disconnect: (callback) ->


Enable loading plugins by a string name, searching first in the local plugins directory and then trying the global require before giving up.

--- src/nesh.coffee
+++ src/nesh.coffee
@@ -87,6 +87,16 @@

 # Add a new plugin. Callback is passed an error object if one occurs.
 nesh.loadPlugin = (plugin, callback) ->
+    if typeof(plugin) is 'string'
+        # Find the right place to import this plugin
+        try
+            plugin = require "./plugins/#{plugin}"
+        catch e
+            try
+                plugin = require plugin
+            catch e
+                callback "Could not find plugin '#{plugin}'!"
+
     nesh.plugins.push plugin
     if plugin.setup
         callPluginMethod plugin.setup, nesh.defaults, callback


Reduce the large text size from 5 to 0.5 MB.

--- test/integration/parameterised-statements-test.coffee
+++ test/integration/parameterised-statements-test.coffee
@@ -166,7 +166,7 @@
   execSql(test, TYPES.Text, 'small')

 exports.textLarge = (test) ->
-  dBuf = new Buffer(5000000)
+  dBuf = new Buffer(500000)
   dBuf.fill('x')
   execSql(test, TYPES.Text, dBuf.toString())


fixed a bug accidentally introduced where turn no longer accepted numbers

--- web/scripts/gameManager.coffee
+++ web/scripts/gameManager.coffee
@@ -267,7 +267,7 @@
             d = $.inArray(dir, ['North','East','South','West'])
             if d >= 0
                 @gameState.turn d
-            else if d != - 1
+            else if !isNaN d
                 @gameState.turn (4 + dir % 4) % 4
         return


Fixes #16

Fixes #16

--- web/scripts/gameManager.coffee
+++ web/scripts/gameManager.coffee
@@ -186,6 +186,8 @@
         @protagonist = @gameConfig.characters.protagonist
         @target = @gameConfig.characters.gflag
         @tick = 0
+        @tock = 0
+        @waitTime = 8

         for name, character of @gameConfig.characters
             if character.AI? and character.moves?
@@ -222,10 +224,11 @@
                     for name, character of @gameConfig.characters
                         @visual.changeState character.index, 4
                     @waiting = false
-            if not @waiting and (@tick - 10) % 30 == 0
-                @tick -= 11
-        @visual.getFrame @gameManager.config.visual, @tick
+            if not @waiting and (@tick - @waitTime) % 30 == 0
+                @tick -= @waitTime + 1
+        @visual.getFrame @gameManager.config.visual, @tock
         @tick++
+        @tock++
         return

     runCharacterCommand: (character) ->


Improve asset load

Improve asset load

--- packages/rocketchat-assets/server/assets.coffee
+++ packages/rocketchat-assets/server/assets.coffee
@@ -102,10 +102,13 @@
 		ws = RocketChatAssetsInstance.createWriteStream asset, contentType
 		ws.on 'end', Meteor.bindEnvironment ->
 			Meteor.setTimeout ->
-				RocketChat.settings.updateById "Assets_#{asset}", {
+				key = "Assets_#{asset}"
+				value = {
 					url: "assets/#{asset}.#{extension}"
 					defaultUrl: assets[asset].defaultUrl
 				}
+				RocketChat.settings.updateById key, value
+				RocketChat.Assets.processAsset key, value
 			, 200

 		rs.pipe ws
@@ -117,11 +120,51 @@

 		RocketChatAssetsInstance.deleteFile asset

-		RocketChat.settings.updateById "Assets_#{asset}", {defaultUrl: assets[asset].defaultUrl}
+		key = "Assets_#{asset}"
+		value = {
+			defaultUrl: assets[asset].defaultUrl
+		}
+		RocketChat.settings.updateById key, value
+		RocketChat.Assets.processAsset key, value
 		return

 	refreshClients: ->
 		process.emit('message', {refresh: 'client'})
+
+	processAsset: (settingKey, settingValue) ->
+		if settingKey.indexOf('Assets_') isnt 0
+			return
+
+		assetKey = settingKey.replace /^Assets_/, ''
+		assetValue = assets[assetKey]
+
+		if not assetValue?
+			return
+
+		if not settingValue?.url?
+			assetValue.cache = undefined
+			return
+
+		file = RocketChatAssetsInstance.getFileSync assetKey
+		if not file
+			assetValue.cache = undefined
+			return
+
+		hash = crypto.createHash('sha1').update(file.buffer).digest('hex')
+		extension = settingValue.url.split('.').pop()
+		assetValue.cache =
+			path: "assets/#{assetKey}.#{extension}"
+			cacheable: false
+			sourceMapUrl: undefined
+			where: 'client'
+			type: 'asset'
+			content: file.buffer
+			extension: extension
+			url: "/assets/#{assetKey}.#{extension}?#{hash}"
+			size: file.length
+			uploadDate: file.uploadDate
+			contentType: file.contentType
+			hash: hash

 RocketChat.settings.addGroup 'Assets'
@@ -130,50 +173,24 @@
 		RocketChat.settings.add "Assets_#{key}", {defaultUrl: value.defaultUrl}, { type: 'asset', group: 'Assets', fileConstraints: value.constraints, i18nLabel: value.label, asset: key, public: true }

-forEachAsset = (key, value) ->
-	RocketChat.settings.get "Assets_#{key}", (settingKey, settingValue) ->
-		if not settingValue?.url?
-			value.cache = undefined
-			return
-
-		file = RocketChatAssetsInstance.getFileWithReadStream key
-		if not file
-			value.cache = undefined
-			return
-
-		data = []
-		file.readStream.on 'data', Meteor.bindEnvironment (chunk) ->
-			data.push chunk
-
-		file.readStream.on 'end', Meteor.bindEnvironment ->
-			data = Buffer.concat(data)
-			hash = crypto.createHash('sha1').update(data).digest('hex')
-			extension = settingValue.url.split('.').pop()
-			value.cache =
-				path: "assets/#{key}.#{extension}"
-				cacheable: false
-				sourceMapUrl: undefined
-				where: 'client'
-				type: 'asset'
-				content: data
-				extension: extension
-				url: "/assets/#{key}.#{extension}?#{hash}"
-				size: file.length
-				uploadDate: file.uploadDate
-				contentType: file.contentType
-				hash: hash
-
-
-forEachAsset(key, value) for key, value of assets
+RocketChat.models.Settings.find().observe
+	added: (record) ->
+		RocketChat.Assets.processAsset record._id, record.value
+	changed: (record) ->
+		RocketChat.Assets.processAsset record._id, record.value
+	removed: (record) ->
+		RocketChat.Assets.processAsset record._id, undefined
+
+Meteor.startup ->
+	Meteor.setTimeout ->
+		process.emit('message', {refresh: 'client'})
+	, 200

 calculateClientHash = WebAppHashing.calculateClientHash
 WebAppHashing.calculateClientHash = (manifest, includeFilter, runtimeConfigOverride) ->
 	for key, value of assets
 		if not value.cache? && not value.defaultUrl?
 			continue
-
-		manifestItem = _.find manifest, (item) ->
-			return item.path is key

 		cache = {}
 		if value.cache
@@ -204,6 +221,7 @@
 			WebAppInternals.staticFiles["/__cordova/assets/#{key}"] = WebAppInternals.staticFiles["/__cordova/#{value.defaultUrl}"]
 			WebAppInternals.staticFiles["/__cordova/assets/#{key}.#{extension}"] = WebAppInternals.staticFiles["/__cordova/#{value.defaultUrl}"]

+		manifestItem = _.findWhere manifest, {path: key}

 		if manifestItem?
 			index = manifest.indexOf(manifestItem)
@@ -224,7 +242,7 @@
 		unless hasPermission
 			throw new Meteor.Error 'error-action-now-allowed', 'Managing assets not allowed', { method: 'refreshClients', action: 'Managing_assets' }

-		RocketChat.Assets.refreshClients
+		RocketChat.Assets.refreshClients()

 	unsetAsset: (asset) ->


Log connection when not auth

Log connection when not auth

--- server/stream/streamBroadcast.coffee
+++ server/stream/streamBroadcast.coffee
@@ -62,7 +62,7 @@
 		stream: (streamName, args) ->
 			# Prevent call from self and client
 			if not @connection? or @connection.clientAddress?
-				console.log "Stream for broadcast with name #{streamName} from client or self is not authorized".red
+				console.log "Stream for broadcast with name #{streamName} from client or self is not authorized".red, @connection
 				return

 			# Prevent call from unauthrorized connections


clean up wait until loaded on test helper

--- test-integration/helpers/ui/test-element.coffee
+++ test-integration/helpers/ui/test-element.coffee
@@ -107,12 +107,11 @@
     _.each commonElements, @setCommonElement
     @

-  waitUntilLoaded: () =>
+  waitUntilLoaded: =>
     # Adjust the test timeout *and* tell selenium to wait up to the same amount of time. Maybe this is redundant?
     @test.utils.wait.giveTime @options.defaultWaitTime, =>
-      @test.utils.verboseWrap 'Waiting until Loadable .is-loading is gone', => @test.driver.wait(=>
+      @test.utils.wait.until "Waiting until Loadable #{JSON.stringify(@el.loadingState().getLocator())} is gone", =>
         @el.loadingState().isPresent().then (isPresent) -> not isPresent
-      , @options.defaultWaitTime)

   setCommonHelper: (name, helper) =>
     @el[name] = helper.fn or helper