spec: Suppress crash-reporter test on 64bit Windows

--- spec/api-crash-reporter-spec.coffee
+++ spec/api-crash-reporter-spec.coffee
@@ -16,6 +16,9 @@
   w = null
   beforeEach -> w = new BrowserWindow(show: false)
   afterEach -> w.destroy()
+
+  # It is not working on 64bit Windows.
+  return if process.platform is 'win32' and process.arch is 'x64

   it 'should send minidump when renderer crashes', (done) ->
     @timeout 60000


Simplify os download a bit with _.unary

Simplify os download a bit with _.unary

--- lib/actions/os.coffee
+++ lib/actions/os.coffee
@@ -1,4 +1,4 @@
-_ = require('lodash')
+_ = require('lodash-contrib')
 async = require('async')
 path = require('path')
 mkdirp = require('mkdirp')
@@ -72,8 +72,7 @@
 			(callback) ->

 				# We need to ensure this directory exists
-				mkdirp path.dirname(outputFile), (error) ->
-					return callback(error)
+				mkdirp(path.dirname(outputFile), _.unary(callback))

 			(callback) ->
 				console.info("Destination file: #{outputFile}\n")


Added support for a basic context menu when editing. Resolves #69

But see #88, which was already an issue, but this makes it worse.

--- src/main/client/grid/grid-directives-tables.coffee
+++ src/main/client/grid/grid-directives-tables.coffee
@@ -397,42 +397,6 @@
             handsonTable.addHook 'beforeValidate', (value, row, fieldFunction, source) ->
               {"$value": value, "$source": source}

-            ## If we have a "status" attribute, we should add a context handler, with options
-            ## for the different values, all of which modify the record value with that particular
-            ## value. We need to poke around in the data for that.
-
-            addStatusContext = undefined
-            for attribute in attributes
-              if attribute.name == 'status' and attribute.type == 'option'
-                addStatusContext = attribute
-                break
-
-            handleStatusChange = (instance, row, status) ->
-              instance.setDataAtCell row, attributeColumnTable['status'], status
-
-            updateContextMenu = () ->
-              attribute = attributeColumnTable?['status']?
-              if attribute?
-                attribute = attributes[attributeColumnTable?['status']]
-              if attribute and attribute.type == 'option'
-
-                options = {}
-                for value in attribute.options.values
-                  options[value] = "Mark record as #{value}"
-
-                contextMenu =  {
-                  callback: (key, options) ->
-                    self = @
-                    handler = () -> handleStatusChange self, options.start.row, key
-                    setTimeout handler, 100
-                  items: options
-                }
-
-                handsonTable.updateSettings { contextMenu: (if scope.editing then contextMenu else false) }
-
-              else
-                handsonTable.updateSettings { contextMenu: false }
-
             # Can actually cancel the change by returning false, or true to accept it
             # Of course, this doesn't use a callback, so it's somewhat less helpful for
             # asynchronous validation.
@@ -456,15 +420,16 @@
                 for attribute, i in response.attributes
                   attributeColumnTable[attribute.name] = i

-                updateContextMenu()
-
                 ## This is where we have the initial load. Let's initiate a scroll down, but carefully
                 scope.$emit 'table:positionAtEnd'

         scope.$watch 'trackerEditingStatus', (editing, old) ->
           if handsonTable
-            handsonTable.updateSettings {readOnly: ! editing, contextMenu: (if editing then contextMenu else false)}
+            handsonTable.updateSettings {
+              readOnly: ! editing,
+              contextMenu: if editing then ['row_above', 'row_below'] else false
+            }

         rtime = undefined


Cheat on the remote object's constructor name.

--- renderer/api/lib/remote.coffee
+++ renderer/api/lib/remote.coffee
@@ -1,4 +1,5 @@
 ipc = require 'ipc'
+v8_util = process.atom_binding 'v8_util'

 generateFromPainObject = (plain) ->
   switch plain.type
@@ -6,13 +7,12 @@
     when 'value' then plain.value
     when 'array' then (generateFromPainObject(el) for el in plain.members)
     else
-      ret = {}
       if plain.type is 'function'
         # A shadow class to represent the remote function object.
         ret =
-        class RemoteObject
+        class RemoteFunction
           constructor: ->
-            if @constructor == RemoteObject
+            if @constructor == RemoteFunction
               # Constructor call.
               obj = ipc.sendChannelSync 'ATOM_INTERNAL_CONSTRUCTOR', plain.id, Array::slice.call(arguments)

@@ -24,6 +24,8 @@
               # Function call.
               ret = ipc.sendChannelSync 'ATOM_INTERNAL_FUNCTION_CALL', plain.id, Array::slice.call(arguments)
               return generateFromPainObject ret
+      else
+        ret = v8_util.createObjectWithName plain.name

       # Polulate delegate members.
       for member in plain.members


Comment server code

Comment server code

--- server/server.coffee
+++ server/server.coffee
@@ -0,0 +1,9 @@
+# Meteor.methods
+# 	setTheme: (theme) ->
+# 		# console.log this.userId
+# 		# if !this.userId
+# 		# 	id = Meteor.users.insert
+# 		# 		username: 'Pomidore',
+# 		# 		emails: [{address:'sunify@gmail.com', verified: true}]
+# 		# 	this.setUserId id
+# 		console.log this.userId


workaround stefon bug

workaround stefon bug

--- js/setup-frontend.coffee
+++ js/setup-frontend.coffee
@@ -1 +1,5 @@
+###
+stefon doesn't like comments at the end of the previous file
+###
+
 window.onload = frontend.core.setup_BANG_


I may have done this right..

I may have done this right..

--- lib/app/routes.coffee
+++ lib/app/routes.coffee
@@ -7,14 +7,13 @@

 module.exports.init = (app) ->

-    assetsUrl = if process.env.PRODUCTION?
-        console.log 'Using the production assetsUrl'
-        'https://songdown.herokuapp.com/static'
-    else
-        console.log 'Using the development assetsUrl'
-        "https://localhost:#{app.get 'port'}/static"
+    songDir = app.get 'songDir'

-    songDir = app.get 'songDir'
+    getAssetsUrl = (req) ->
+        if process.env.PRODUCTION?
+            "#{req.protocol}://songdown.herokuapp.com/static"
+        else
+            "#{req.protocol}://localhost:#{app.get 'port'}/static"

     # Index
@@ -22,7 +21,7 @@
         .get (req, res) ->
             songs = loadSongs songDir
             crshUrl = req.crsh.libs.name
-            res.end templates.index {songs, assetsUrl, crshUrl}
+            res.end templates.index {songs, assetsUrl: getAssetsUrl(req), crshUrl}

     # Song view
     app.route '/song/:fname'
@@ -33,7 +32,7 @@

             res.send templates.song
                 artist: song.artist
-                assetsUrl: assetsUrl
+                assetsUrl: getAssetsUrl(req)
                 html: song.toHtml()
                 track: song.track
                 fname: song.fname


bad idea to put max-parallelism on billing

--- js/inner/billing.coffee
+++ js/inner/billing.coffee
@@ -47,7 +47,6 @@
     billing_name: null
     billing_email: null
     extra_data: null
-    max_parallelism: 1

     # make it work
     current_org_name: null # organization that instantiated the billing class


Teamwork: Fixes for import modal.

Teamwork: Fixes for import modal.

--- client/Teamwork/Views/teamworkworkspace.coffee
+++ client/Teamwork/Views/teamworkworkspace.coffee
@@ -177,21 +177,26 @@
   showImportModal: ->
     modal          = new KDModalView
       title        : "Import Content to your VM"
-      content      : "
Enter the URL of a git repository or zip archive.
"
-      cssClass     : "tw-modal"
+      cssClass     : "tw-modal tw-import-modal"
       overlay      : yes
       width        : 600
       buttons      :
         Import     :
           title    : "Import"
           cssClass : "modal-clean-green"
+          icon     : yes
+          iconClass: "tw-import-icon"
           callback : => @getDelegate().emit "ImportRequested", importUrlInput.getValue()
         Close      :
-          title    : "Close"
+          title    : "Cancel"
           cssClass : "modal-cancel"
           callback : -> modal.destroy()

     modal.addSubView importUrlInput = new KDHitEnterInputView
-      type         : "text"
-      placeholder  : "Import Url"
-      callback     : => @handleJoinASessionFromModal sessionKeyInput.getValue(), modal
+      type         : "text tw-import-url"
+      placeholder  : "Enter the URL of a git repository or zip archive."
+      callback     : => @getDelegate().emit "ImportRequested", importUrlInput.getValue()
+
+    modal.addSubView new KDCustomHTMLView
+      tagName      : "span"
+      cssClass     : "input-icon"


a small note

a small note

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -275,6 +275,8 @@
                     Check Console for more details.

                   """
+                  # We may after put a full stck to the output
+                  # It looks weird for now.
                   #
#{error.stack}

       console.warn error.message, error


video: fix stopped talking events for publisher/subscriber

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -184,7 +184,7 @@
       @emit 'ParticipantStartedTalking', nick
       @changeActiveParticipant nick  unless @state.selectedParticipant

-     _subscriber.on 'TalkingDidStop', =>
+    _subscriber.on 'TalkingDidStop', =>
       @emit 'ParticipantStoppedTalking', nick

     return _subscriber
@@ -217,6 +217,7 @@
       @emit 'ParticipantStartedTalking', getNick()

     _publisher.on 'TalkingDidStop', =>
+      return  unless @state.active
       @emit 'ParticipantStoppedTalking', getNick()

     return _publisher


video/model: add a method to unpublish from session

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -461,6 +461,23 @@

   ###*
+   * Unpublishes current publisher from session, after that is destroyed, it
+   * calls the callback. Tokbox, for some reason, requires a little time to be
+   * passed doing something publisher related after current publisher is
+   * destroyed. Magic number handles that. This method is necessary to work
+   * with new publishers after destroying a publisher.
+   *
+   * @param {function} callback
+  ###
+  _unpublishFromSession: (callback) ->
+
+    @publisher.videoData.once 'streamDestroyed', ->
+      kd.utils.wait constants.NUMBER_THAT_MAKES_TOKBOX_WORK, callback
+
+    @session.unpublish @publisher.videoData
+
+
+  ###*
    * Unregister publisher and stream.
   ###
   unregisterPublisher: ->


Use ::getActiveTextEditor instead of ::getActiveEditor. Fixes #57

--- lib/atom-react.coffee
+++ lib/atom-react.coffee
@@ -125,7 +125,7 @@
     HTMLtoJSX = require './htmltojsx'
     converter = new HTMLtoJSX(createClass: false)

-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()

     return if not @isReactEnabledForEditor editor

@@ -149,7 +149,7 @@
     jsxformat = require 'jsxformat'
     _ = require 'lodash'

-    editor = atom.workspace.getActiveEditor()
+    editor = atom.workspace.getActiveTextEditor()

     return if not @isReactEnabledForEditor editor


Fix path to htmltojsx

Fix path to htmltojsx

--- lib/atom-react.coffee
+++ lib/atom-react.coffee
@@ -81,7 +81,7 @@

   onHTMLToJSX: ->
     jsxformat = require 'jsxformat'
-    HTMLtoJSX = require './lib/htmltojsx'
+    HTMLtoJSX = require './htmltojsx'
     converter = new HTMLtoJSX(createClass: false)

     editor = atom.workspace.getActiveEditor()


get -> set

get -> set

--- src/trix/lib/installer.coffee
+++ src/trix/lib/installer.coffee
@@ -26,7 +26,7 @@

   createEditor: ->
     if @config.mode in @supportedModes
-      @getConfigElements()
+      @setConfigElements()
       @config.textElement = @createTextElement()
       @config.autofocus ?= @config.textareaElement.hasAttribute("autofocus")
       @createStyleSheet()
@@ -36,8 +36,8 @@
       else
         new Trix.EditorController @config

-  getConfigElements: ->
-    for key in "textarea toolbar input debug".split(" ")
+  setConfigElements: ->
+    for key in "textarea toolbar input".split(" ")
       @config["#{key}Element"] = getElement(@config[key])
       delete @config[key]


Tests: Messages: Improve refreshPageAndSeeTheConversationInSidebar test.

--- client/test/lib/messages/privatemessage.coffee
+++ client/test/lib/messages/privatemessage.coffee
@@ -42,15 +42,14 @@

     helpers.beginTest(browser)

-    isStarted = messagesHelpers.startConversation(browser, testUsers)
+    messagesHelpers.startConversation(browser, testUsers)

-    if isStarted then browser.end()
-    else
-      browser
-        .refresh()
-        .waitForElementVisible  '.activity-sidebar .messages', 20000
-        .assert.containsText    '.activity-sidebar .messages', testUsers[0].fullName  # Assertion
-        .end()
+    browser
+      .refresh()
+      .waitForElementVisible    '.activity-sidebar .messages', 20000
+      .waitForElementNotVisible '[testpath=main-sidebar] section.messages .kdloader', 20000
+      .assert.containsText      '.activity-sidebar .messages', testUsers[0].username  # Assertion
+      .end()

   leaveConversation: (browser) ->


Existend -> Existent

Existend -> Existent

--- _src/lib/node_cache.coffee
+++ _src/lib/node_cache.coffee
@@ -148,7 +148,7 @@
 	#
 	set: ( key, value, ttl, cb )=>
 		# internal helper variables
-		existend = false
+		existent = false

 		# force the data to string
 		if @options.forceString and not _isString( value )
@@ -161,7 +161,7 @@

 		# remove existing data from stats
 		if @data[ key ]
-			existend = true
+			existent = true
 			@stats.vsize -= @_getValLength( @_unwrap( @data[ key ], false ) )

 		# set the value
@@ -169,7 +169,7 @@
 		@stats.vsize += @_getValLength( value )

 		# only add the keys and key-size if the key is new
-		if not existend
+		if not existent
 			@stats.ksize += @_getKeyLength( key )
 			@stats.keys++

@@ -206,7 +206,7 @@

 		delCount = 0
 		for key in keys
-			# only delete if existend
+			# only delete if existent
 			if @data[ key ]?
 				# calc the stats
 				@stats.vsize -= @_getValLength( @_unwrap( @data[ key ], false ) )
@@ -260,7 +260,7 @@
 			cb( null, false ) if cb?
 			return false

-		# check for existend data and update the ttl value
+		# check for existent data and update the ttl value
 		if @data[ key ]? and @_check( key, @data[ key ] )
 			# on ttl = 0  delete the key. otherwise reset the value
 			if ttl > 0
@@ -302,7 +302,7 @@
 			cb( null, undefined ) if cb?
 			return undefined

-		# check for existend data and update the ttl value
+		# check for existent data and update the ttl value
 		if @data[ key ]? and @_check( key, @data[ key ] )
 			_ttl = @data[ key ].t
 			cb( null, _ttl ) if cb?


Only select last operation if path is expanded

--- src/packages/command-panel/lib/preview-list.coffee
+++ src/packages/command-panel/lib/preview-list.coffee
@@ -107,7 +107,11 @@
     super()

     @find('.selected').removeClass('selected')
-    @find('.operation:last').addClass('selected')
+    lastPath = @find('.path:last')
+    if lastPath.hasClass('is-collapsed')
+      lastPath.addClass('selected')
+    else
+      lastPath.find('.operation:last').addClass('selected')

   scrollToTop: ->
     super()


Convert values always to decimal notation

Fixes https://github.com/motif/company/issues/2038
Fixes https://github.com/motif/company/issues/1047

--- framer/LayerStyle.coffee
+++ framer/LayerStyle.coffee
@@ -103,21 +103,21 @@

 		"
 		translate3d(
-			#{layer._properties.x}px,
-			#{layer._properties.y}px,
-			#{layer._properties.z}px)
+			#{layer._properties.x?.toFixed(20)}px,
+			#{layer._properties.y?.toFixed(20)}px,
+			#{layer._properties.z?.toFixed(20)}px)
 		scale3d(
-			#{layer._properties.scaleX * layer._properties.scale},
-			#{layer._properties.scaleY * layer._properties.scale},
-			#{layer._properties.scaleZ})
-		skew(#{layer._properties.skew}deg,#{layer._properties.skew}deg)
-		skewX(#{layer._properties.skewX}deg)
-		skewY(#{layer._properties.skewY}deg)
-		translateZ(#{layer._properties.originZ}px)
-		rotateX(#{layer._properties.rotationX}deg)
-		rotateY(#{layer._properties.rotationY}deg)
-		rotateZ(#{layer._properties.rotationZ}deg)
-		translateZ(#{-layer._properties.originZ}px)
+			#{(layer._properties.scaleX * layer._properties.scale).toFixed(20)},
+			#{(layer._properties.scaleY * layer._properties.scale).toFixed(20)},
+			#{layer._properties.scaleZ?.toFixed(20)})
+		skew(#{layer._properties.skew?.toFixed(20)}deg,#{layer._properties.skew?.toFixed(20)}deg)
+		skewX(#{layer._properties.skewX?.toFixed(20)}deg)
+		skewY(#{layer._properties.skewY?.toFixed(20)}deg)
+		translateZ(#{layer._properties.originZ?.toFixed(20)}px)
+		rotateX(#{layer._properties.rotationX?.toFixed(20)}deg)
+		rotateY(#{layer._properties.rotationY?.toFixed(20)}deg)
+		rotateZ(#{layer._properties.rotationZ?.toFixed(20)}deg)
+		translateZ(#{-layer._properties.originZ?.toFixed(20)}px)
 		"

 	webkitTransformForce2d: (layer) ->
@@ -131,10 +131,10 @@
 			if layer._properties[p] isnt v
 				console.warn "Layer property '#{p}'' will be ignored with force2d enabled"

-		css.push "translate(#{layer._properties.x}px,#{layer._properties.y}px)"
-		css.push "scale(#{layer._properties.scale})"
-		css.push "skew(#{layer._properties.skew}deg,#{layer._properties.skew}deg)"
-		css.push "rotate(#{layer._properties.rotationZ}deg)"
+		css.push "translate(#{layer._properties.x?.toFixed(20)}px,#{layer._properties.y?.toFixed(20)}px)"
+		css.push "scale(#{layer._properties.scale?.toFixed(20)})"
+		css.push "skew(#{layer._properties.skew?.toFixed(20)}deg,#{layer._properties.skew?.toFixed(20)}deg)"
+		css.push "rotate(#{layer._properties.rotationZ?.toFixed(20)}deg)"

 		return css.join(" ")


specs: add tests for videoLinkShell controlView

--- coffee/test/shells/videolink.shell.spec.coffee
+++ coffee/test/shells/videolink.shell.spec.coffee
@@ -107,6 +107,108 @@
         expect(mv.playerView.seekOffset).not.toHaveBeenCalled()
         mv.seekOffset()
         expect(mv.playerView.seekOffset).toHaveBeenCalled()
+
+
+      describe 'MediaView::controlsView', ->
+
+        it 'should have a play button', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          playControl = view.controlsView.$ '.control-view.play'
+          expect(playControl.length).toBe 1
+
+        it 'should have a play button that is initially hidden', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          playControl = view.controlsView.$ '.control-view.play'
+          expect(playControl.length).toBe 1
+          expect(playControl.hasClass 'hidden').toBe true
+
+        it 'should have a pause button', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          pauseControl = view.controlsView.$ '.control-view.pause'
+          expect(pauseControl.length).toBe 1
+
+        it 'should have a pause button that is not initially hidden', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          pauseControl = view.controlsView.$ '.control-view.pause'
+          expect(pauseControl.length).toBe 1
+          expect(pauseControl.hasClass 'hidden').toBe false
+
+        it 'should show play button when paused', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          view.pause()
+          expect(view.isPaused()).toBe true
+
+          playControl = view.controlsView.$ '.control-view.play'
+          expect(playControl.hasClass 'hidden').toBe false
+
+        it 'should hide pause button when paused', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          view.pause()
+          expect(view.isPaused()).toBe true
+
+          pauseControl = view.controlsView.$ '.control-view.pause'
+          expect(pauseControl.hasClass 'hidden').toBe true
+
+        it 'should hide play button when playing', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          expect(view.isPlaying()).toBe true
+
+          playControl = view.controlsView.$ '.control-view.play'
+          expect(playControl.hasClass 'hidden').toBe true
+
+        it 'should show pause button when playing', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          expect(view.isPlaying()).toBe true
+
+          pauseControl = view.controlsView.$ '.control-view.pause'
+          expect(pauseControl.hasClass 'hidden').toBe false
+
+        it 'should play when play button is clicked', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          playControl = view.controlsView.$ '.control-view.play'
+          view.pause()
+
+          spyOn view, 'play'
+          playControl.click()
+          expect(view.play).toHaveBeenCalled()
+
+        it 'should pause when pause button is clicked', ->
+          view = new MediaView viewOptions()
+          view.controlsView.render()
+          view.render()
+          view.play()
+          pauseControl = view.controlsView.$ '.control-view.pause'
+
+          spyOn view, 'pause'
+          pauseControl.click()
+          expect(view.pause).toHaveBeenCalled()
+

       # TODO: test onPlaybackTick. waiting on integration with video start,
       # pause, and seek calls
@@ -299,6 +401,107 @@
           expect(rv._playerView.elapsedLoops()).toBe 0

+      describe 'RemixView::_controlsView', ->
+
+        it 'should have a play button', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          playControl = view._controlsView.$ '.control-view.play'
+          expect(playControl.length).toBe 1
+
+        it 'should have a play button that is initially hidden', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          playControl = view._controlsView.$ '.control-view.play'
+          expect(playControl.length).toBe 1
+          expect(playControl.hasClass 'hidden').toBe true
+
+        it 'should have a pause button', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          pauseControl = view._controlsView.$ '.control-view.pause'
+          expect(pauseControl.length).toBe 1
+
+        it 'should have a pause button that is not initially hidden', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          pauseControl = view._controlsView.$ '.control-view.pause'
+          expect(pauseControl.length).toBe 1
+          expect(pauseControl.hasClass 'hidden').toBe false
+
+        it 'should show play button when paused', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          view._playerView.pause()
+          expect(view._playerView.isPaused()).toBe true
+
+          playControl = view._controlsView.$ '.control-view.play'
+          expect(playControl.hasClass 'hidden').toBe false
+
+        it 'should hide pause button when paused', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          view._playerView.pause()
+          expect(view._playerView.isPaused()).toBe true
+
+          pauseControl = view._controlsView.$ '.control-view.pause'
+          expect(pauseControl.hasClass 'hidden').toBe true
+
+        it 'should hide play button when playing', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          expect(view._playerView.isPlaying()).toBe true
+
+          playControl = view._controlsView.$ '.control-view.play'
+          expect(playControl.hasClass 'hidden').toBe true
+
+        it 'should show pause button when playing', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          expect(view._playerView.isPlaying()).toBe true
+
+          pauseControl = view._controlsView.$ '.control-view.pause'
+          expect(pauseControl.hasClass 'hidden').toBe false
+
+        it 'should play when play button is clicked', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          playControl = view._controlsView.$ '.control-view.play'
+          view._playerView.pause()
+
+          spyOn view._playerView, 'play'
+          playControl.click()
+          expect(view._playerView.play).toHaveBeenCalled()
+
+        it 'should pause when pause button is clicked', ->
+          view = new RemixView viewOptions()
+          view._controlsView.render()
+          view.render()
+          view._playerView.play()
+          pauseControl = view._controlsView.$ '.control-view.pause'
+
+          spyOn view._playerView, 'pause'
+          pauseControl.click()
+          expect(view._playerView.pause).toHaveBeenCalled()
+
+
       it 'should look good', ->
         # setup DOM
         acorn.util.appendCss()


Refactor Legacy internals to use upstream subtree code

--- packages/redux-components-legacy/src/subtree.coffee
+++ packages/redux-components-legacy/src/subtree.coffee
@@ -3,70 +3,38 @@
 import ReduxComponent from 'redux-components/lib/ReduxComponent'
 import { willMountComponent, didMountComponent, willUnmountComponent } from 'redux-components/lib/mountComponent'
 import createClass from './createClass'
+import upstreamCreateComponent from 'redux-components/lib/createComponent'
+import createSubtreeLifecycleMethods from 'redux-components/lib/util/createSubtreeLifecycleMethods'

 ##### SubtreeMixin
-attachComponent = (parentComponent, key, component) ->
-	# Development invariant: prevent shadowing of parent keys
-	if process.env.NODE_ENV isnt 'production'
-		invariant(not parentComponent[key]?, "redux-component of type #{parentComponent.displayName} (mounted at location #{parentComponent.path}) is using SubtreeMixin, and one of its children would shadow the key `#{key}` on the parent component.")
-	parentComponent[key] = component
-	childPath = parentComponent.path.concat( [ key ] )
-	component.__willMount(parentComponent.store, childPath, parentComponent)
-	component
-
-applyDescriptor = (parentComponent, key, descriptor) ->
-	attachComponent(parentComponent, key, createComponent(descriptor))
-
 export SubtreeMixin = {
-	getReducer: -> @subtreeReducer
+	getReducer: -> @__subtreeReducer

 	componentWillMount: ->
 		# Sanity check that our component supports subtrees
 		if process.env.NODE_ENV isnt 'production'
 			invariant(typeof @getSubtree is 'function', "redux-component of type #{@displayName} (mounted at location #{@path}) is using SubtreeMixin, but does not have a getSubtree() method.")

-		# Create subcomponents
-		@__subtree = {}
-		for key, descriptor of @getSubtree()
-			@__subtree[key] = applyDescriptor(@, key, descriptor)
+		# Delegate to redux-components internal methods
+		lifecycle = createSubtreeLifecycleMethods(@getSubtree, null, null, () => this.__originalUnmount())
+		@__subtreeDidMount = lifecycle.componentDidMount

-		# Create reducer
-		reducerMap = {}
-		for key, component of @__subtree
-			reducerMap[key] = component.reducer
-		@subtreeReducer = combineReducers(reducerMap)
+		# Rework the unmount method to honor the lifecycle contract
+		@__originalUnmount = this.componentWillUnmount
+		actualUnmount = lifecycle.componentWillUnmount
+		this.componentWillUnmount = =>
+			actualUnmount.call(this)
+			this.componentWillUnmount = @__originalUnmount
+			delete @__subtreeDidMount
+			delete @__originalUnmount
+
+		# Call new willMount
+		lifecycle.componentWillMount.call(this)

 	componentDidMount: ->
-		didMountComponent(component) for key, component of @__subtree
-		undefined
-
-	componentWillUnmount: ->
-		willUnmountComponent(component) for key, component of @__subtree
-		undefined
+		@__subtreeDidMount?()
+		return
 }

 ##### createComponent
-SubtreeNonce = createClass {
-	displayName: '(subtree)'
-	mixins: [ SubtreeMixin ]
-}
-
-ReducerNonce = createClass {
-	displayName: '(reducer)'
-}
-
-export createComponent = (descriptor) ->
-	if descriptor instanceof ReduxComponent
-		descriptor
-	else if descriptor?.prototype and (descriptor.prototype instanceof ReduxComponent)
-		new descriptor()
-	else if typeof(descriptor) is 'object' and (not Array.isArray(descriptor))
-		component = new SubtreeNonce()
-		component.getSubtree = (-> descriptor)
-		component
-	else if typeof(descriptor) is 'function'
-		component = new ReducerNonce()
-		component.getReducer = (-> descriptor)
-		component
-	else
-		throw new Error("invalid component descriptor")
+export createComponent = upstreamCreateComponent


fix line mix up

fix line mix up

--- packages/rocketchat-slashcommands-msg/server.coffee
+++ packages/rocketchat-slashcommands-msg/server.coffee
@@ -12,10 +12,10 @@
 		usernameOrig = trimmedParams.slice(0, trimmedParams.indexOf(' '))
 		message = trimmedParams.slice(trimmedParams.indexOf(' ') + 1)

+		username = usernameOrig.replace('@', '')
+
 		if username is ''
 			return
-
-		username = usernameOrig.replace('@', '')

 		user = Meteor.users.findOne Meteor.userId()
 		msgUser = RocketChat.models.Users.findOneByUsername username


breadcrumbs fix.

breadcrumbs fix.

--- public/js/app/views/common/form.coffee
+++ public/js/app/views/common/form.coffee
@@ -10,9 +10,9 @@

   render: =>
     if @model.isNew()
-      @settings.breadcrumbs.push({title: 'Add', url: '#'})
+      @settings.breadcrumbs[2] = title: 'Add', url: '#'
     else
-      @settings.breadcrumbs.push({title: 'Edit', url: '#'})
+      @settings.breadcrumbs[2] = title: 'Edit', url: '#'

     $(@el).html @template()
     @


corrected object naming

corrected object naming

--- src/scripts/githooks/coreos_restart/githook.coffee
+++ src/scripts/githooks/coreos_restart/githook.coffee
@@ -56,8 +56,8 @@
     # Grab configuration for specific service.
     services[service].name = service
     config =
-      app
-      cluster
+      app: app
+      cluster: cluster
       service: services[service]

     # Render Service File


1.0.0

--- def/menu_win32.cson
+++ def/menu_win32.cson
@@ -271,8 +271,6 @@
 			  value: "檢視使用條款"
 			"View &License":
 			  value: "檢視授權"
-			"Check for Update":
-			  value: "檢查更新"
 			"&Documentation":
 				value: "說明文件(&D)"
 			Roadmap:


automatically upcasing lowercase operators

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -70,9 +70,9 @@
           if @options.operators
             @range = @getRange()
             operator = @range.startContainer.data.trim() if @range.startContainer.data
-            if operator && @options.operators.includes(operator)
+            if operator && @options.operators.map((op) -> op.toLowerCase()).includes(operator.toLowerCase())
               @word = [operator] #TODO: Make this not a hack
-              @addItem($(''+operator+''), true)
+              @addItem($(''+operator.toUpperCase()+''), true)

         when 40 # Down arrow
           if @resultList.find("li").length > 0


increased log scale axis buffer

increased log scale axis buffer

--- src/viz/types/helpers/graph/includes/buffer.coffee
+++ src/viz/types/helpers/graph/includes/buffer.coffee
@@ -60,7 +60,9 @@
       allPositive = domain[0] >= 0 and domain[1] >= 0
       allNegative = domain[0] <= 0 and domain[1] <= 0

-      additional = Math.abs(domain[1] - domain[0]) * 0.05 or 1
+      mod = if vars[axis].scale.value is "log" then .5 else 0.05
+
+      additional = Math.abs(domain[1] - domain[0]) * mod or 1

       if vars[axis].scale.value is "log"
         if allPositive


Set canvas dimensions to the document dimensions

--- index.coffee
+++ index.coffee
@@ -6,6 +6,11 @@
 })

 define ['game', 'synchronizedtime', 'position', 'lib/fabric', 'lib/jquery'], (Game, SynchronizedTime, Position) -> # Fabric is deliberately not set as an argument
+
+  canvas = $("#canvas")
+  canvas.width = document.body.clientWidth
+  canvas.height = document.body.clientHeight
+
   console.log "Fabric: ", fabric
   console.log "Position: ", new Position([1,2], 0, 5)
   canvas = new fabric.Canvas('canvas', {renderOnAddRemove: false})


Disable/enable voxel-fly plugin in survival/creative mode

Requires https://github.com/deathcap/voxel-fly/commit/3a57780cfc1fc79ba8877542c67eadb4dbc313a3

--- index.coffee
+++ index.coffee
@@ -111,7 +111,8 @@
   game.mode = GAME_MODE_SURVIVAL
   controlsTarget = game.controls.target()

-  game.flyer = game.plugins.load 'fly', {physical: controlsTarget, flySpeed: 0.8, enabled: false}
+  game.plugins.load 'fly', {physical: controlsTarget, flySpeed: 0.8}
+  game.plugins.disable 'fly'

   game.plugins.load 'walk', {
     skin: controlsTarget.playerSkin
@@ -160,14 +161,12 @@
       # TODO: add gamemode event? for plugins to handle instead of us
       if game.mode == GAME_MODE_SURVIVAL
         game.mode = GAME_MODE_CREATIVE
-        game.flyer.enabled = true
+        game.plugins.enable 'fly'
         mine.instaMine = true
         console.log("creative mode")
       else
         game.mode = GAME_MODE_SURVIVAL
-        if game.flyer.flying
-          game.flyer.stopFlying()
-        game.flyer.enabled = false
+        game.plugins.disable 'fly'
         mine.instaMine = false
         console.log("survival mode")


Use Mono on non-Windows

Use Mono on non-Windows

--- index.coffee
+++ index.coffee
@@ -12,9 +12,20 @@
     ChildProcess.execFile options.cmd, options.args, (error, stdout, stderr) ->
       grunt.log.error(stderr) if stderr
       callback(error)
+
+  locateExecutableInPath = (name) ->
+    haystack = _.map process.env.PATH.split(/[:;]/), (x) -> path.join(x, name)
+    _.find haystack, (needle) -> fs.existsSync(needle)

   grunt.registerMultiTask 'create-windows-installer', 'Create the Windows installer', ->
     @requiresConfig("#{@name}.#{@target}.appDirectory")
+
+    useMono = false
+    [monoExe, wineExe] = _.map(['mono', 'wine'], locateExecutableInPath)
+
+    unless process.platform is 'win32'
+      useMono = true
+      throw new Error("You must install both Mono and Wine on non-Windows") unless wineExe and monoExe

     done = @async()

@@ -71,11 +82,20 @@
       nugetOutput
       '-NoDefaultExcludes'
     ]
+
+    if useMono
+      args.unshift(cmd)
+      cmd = monoExe

     syncReleases = (cb) ->
       if remoteReleases?
         cmd = path.resolve(__dirname, '..', 'vendor', 'SyncReleases.exe')
         args = ['-u', remoteReleases, '-r', outputDirectory]
+
+        if useMono
+          args.unshift(cmd)
+          cmd = monoExe
+
         exec {cmd, args}, cb
       else
         process.nextTick -> cb()
@@ -97,6 +117,10 @@
           '--loadingGif'
           loadingGif
         ]
+
+        if useMono
+          args.unshift(path.resolve(__dirname, '..', 'vendor', 'Update-Mono.exe'))
+          cmd = monoExe

         if signWithParams?
           args.push '--signWithParams'


:bug: delay loading MathJax until configured

--- index.coffee
+++ index.coffee
@@ -1,5 +1,7 @@
 require 'jquery'
 require 'bootstrap' # Attach bootstrap to jQuery
+
+MathJax = window.MathJax

 window._STORES =
   APP_CONFIG: require './src/flux/app-config'
@@ -16,7 +18,7 @@
   TIME: require './src/flux/time'
   TOC: require './src/flux/toc'

-window.MathJax?.Hub.Config(
+MATHJAX_CONFIG =
   showProcessingMessages: false
   tex2jax:
     displayMath: [['\u200c\u200c\u200c', '\u200c\u200c\u200c']] # zero-width non-joiner
@@ -24,7 +26,16 @@
   styles:
     "#MathJax_Message":    visibility: "hidden", left: "", right: 0
     "#MathJax_MSIE_Frame": visibility: "hidden", left: "", right: 0
-)
+
+if MathJax?.Hub
+  MathJax.Hub.Config(MATHJAX_CONFIG)
+  MathJax.Hub.Configured()
+else
+  MATHJAX_CONFIG.AuthorInit = ->
+    MathJax.Hub.Configured()
+
+  window.MathJax = MATHJAX_CONFIG
+
 api = require './src/api'
 api.start()
 router = require './src/router'


`__hash__` property added

`__hash__` property added

--- index.coffee
+++ index.coffee
@@ -20,6 +20,7 @@
 			return new Dict obj

 		# properties
+		utils.defProp @, '__hash__', '', utils.uid()
 		utils.defProp @, '_data', 'e', obj
 		utils.defProp @, '_keys', 'w', null
 		utils.defProp @, '_values', 'w', null


Basic template generation functionality is working

--- src/scripts/yara.coffee
+++ src/scripts/yara.coffee
@@ -5,7 +5,7 @@
 #   None
 #
 # Configuration:
-#   None
+#   None…
 #
 # Commands:
 #   hubot yara - Generates default rule template
@@ -16,6 +16,37 @@

 module.exports = (robot) ->
   robot.respond /yara/i, (msg) ->
+    # Default Values
+    yara_rule_name = "rule_name"
+    yara_rule_category = "category"
+    yara_meta_author = "Hubot"
+    yara_meta_date = "2013-01-01"
+    yara_meta_description = "Default Rule Template"
+    yara_strings_list = ['foo', 'bar', 'baz']
+
     # If there's strings generate rule with strings

-    # If there aren't then just generate a template
+    # Generate Strings Section
+    # yara_conditions = 0
+    # yara_strings_parsed = ""
+    #
+    # for yara_string in yara_strings_list
+    #   yara_strings_parsed += "    string#{yara_conditions} '#{yara_string}'\n"
+    #   yara_conditions++
+
+    new_rule = """
+    rule #{yara_rule_name} : #{yara_rule_category}
+    {
+      meta:
+        author = '#{yara_meta_author}'
+        date = '#{yara_meta_date}'
+        description = '#{yara_meta_description}'
+      strings:
+        $string0 = "foo"
+        $string1 = "bar"
+        $string2 = "baz" wide
+    condition:
+        3 of them
+    }
+    """
+    msg.send new_rule


Update modal.coffee

Update modal.coffee

--- vendor/assets/javascripts/mice/modal.coffee
+++ vendor/assets/javascripts/mice/modal.coffee
@@ -14,195 +14,242 @@
       @isShown        = null
       @scrollbarWidth = 0

-      if @options.remote then @$element.find('.modal-content').load(@options.remote, $.proxy( (-> @$element.trigger('loaded.bs.modal')), @))
-
-    toggle: (_relatedTarget) ->
-      if @isShown then @hide() else @show(_relatedTarget)
-
-    show: (_relatedTarget) ->
-      e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
-
-      @$element.trigger(e)
-
-      return if @isShown or e.isDefaultPrevented()
-
-      @isShown = true
-
-      @checkScrollbar()
-      @$body.addClass('modal-open')
-
-      @setScrollbar()
-      @escape()
-
-      @$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(@hide, @))
-
-      @backdrop =>
-        transition = $.support.transition and @$element.hasClass('fade')
-
-        if !@$element.parent().length then @$element.appendTo(@$body) # don't move modals dom position
-
-        @$element.show().scrollTop(0)
-
-        if transition then @$element[0].offsetWidth # force reflow
-
-        @$element.addClass('in').attr('aria-hidden', false)
-
-        @enforceFocus()
-
-        e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
-
-        if transition
-          @$element.find('.modal-dialog') # wait for modal to slide in
-            .one('bsTransitionEnd', -> @$element.trigger('focus').trigger(e))
-            .emulateTransitionEnd(300)
-        else
-          @$element.trigger('focus').trigger(e)
-
-    hide: (e) ->
-      e.preventDefault() if e
-
-      e = $.Event('hide.bs.modal')
-
-      @$element.trigger(e)
-
-      return if !@isShown or e.isDefaultPrevented()
-
-      @isShown = false
-
-      @$body.removeClass('modal-open')
-
-      @resetScrollbar()
-      @escape()
-
-      $(document).off('focusin.bs.modal')
-
-      @$element.removeClass('in').attr('aria-hidden', true).off('click.dismiss.bs.modal')
-
-      if $.support.transition and @$element.hasClass('fade')
-        @$element.one('bsTransitionEnd', $.proxy(@hideModal, @)).emulateTransitionEnd(300)
-      else
-        @hideModal()
-
-    enforceFocus: ->
-      $(document).off('focusin.bs.modal').on('focusin.bs.modal', $.proxy( ((e) -> @$element.trigger('focus') if @$element[0] != e.target and !@$element.has(e.target).length), @))
-
-    escape: ->
-      if @isShown and @options.keyboard
-        @$element.on('keyup.dismiss.bs.modal', $.proxy( ((e) -> e.which == 27 and @hide()), @))
-      else if !@isShown
-        @$element.off('keyup.dismiss.bs.modal')
-
-    hideModal: ->
-      @$element.hide()
-      @backdrop => @$element.trigger('hidden.bs.modal')
-
-    removeBackdrop: ->
-      @$backdrop and @$backdrop.remove()
-      @$backdrop = null
-
-    backdrop: (callback) ->
-      that = this
-      animate = if @$element.hasClass('fade') then 'fade' else ''
-
-      if @isShown and @options.backdrop
-        doAnimate = $.support.transition and animate
-
-        @$backdrop = $('
').appendTo(@$body)
-
-        proxy = (e) ->
-          return if e.target != e.currentTarget
-          if @options.backdrop == 'static' then @$element[0].focus.call(@$element[0]) else @hide.call(@)
-
-        @$element.on 'click.dismiss.bs.modal', $.proxy(proxy, @)
-
-        if doAnimate then @$backdrop[0].offsetWidth # force reflow
-
-        @$backdrop.addClass('in')
-
-        return unless callback
-
-        if doAnimate then @$backdrop.one('bsTransitionEnd', callback).emulateTransitionEnd(150) else callback()
-
-      else if !@isShown and @$backdrop
-        @$backdrop.removeClass('in')
-
-        callbackRemove = =>
-          @removeBackdrop()
-          callback and callback()
-
-        if $.support.transition and @$element.hasClass('fade') then @$backdrop.one('bsTransitionEnd', callbackRemove).emulateTransitionEnd(150) else callbackRemove()
-
-      else if callback
-        callback()
-
-
-    checkScrollbar: ->
-      return if document.body.clientWidth >= window.innerWidth
-      @scrollbarWidth = @scrollbarWidth or @measureScrollbar()
-
-    setScrollbar: ->
-      bodyPad = parseInt((@$body.css('padding-right') or 0), 10)
-      if @scrollbarWidth then @$body.css('padding-right', bodyPad + @scrollbarWidth)
-
-    resetScrollbar: ->
-      @$body.css('padding-right', '')
-
-    measureScrollbar: -> # thx walsh
-      scrollDiv = document.createElement('div')
-      scrollDiv.className = 'modal-scrollbar-measure'
-      @$body.append(scrollDiv)
-      scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
-      @$body[0].removeChild(scrollDiv)
-      return scrollbarWidth
-
-
-  # MODAL PLUGIN DEFINITION
-  # =======================
-
-  $.fn.modal = (option, _relatedTarget) ->
-    @.each ->
-      $this   = $(@)
-      data    = $this.data('bs.modal')
-      options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' and option)
-
-      if !data
-        $this.data('bs.modal', (data = new Modal(@, options)))
-
-      if typeof option == 'string'
-        data[option](_relatedTarget)
-
-      else if options.show
-        data.show(_relatedTarget)
-
-
-  $.fn.modal.Constructor = Modal
-
-  # TOOLTIP PLUGIN DEFAULT OPTIONS
-  # =========================
-  $.fn.modal.defaults =
+      if @options.remote
+        @$element.find('.modal-content').load(@options.remote, $.proxy( (-> @$element.trigger('loaded.bs.modal')) , @))
+
+  Modal.VERSION  = '3.2.0'
+
+  Modal.DEFAULTS =
     backdrop: true
     keyboard: true
     show: true

+  Modal::toggle = (_relatedTarget) ->
+    if @isShown then @hide() else @show(_relatedTarget)
+
+
+  Modal::show = (_relatedTarget) ->
+    that = @
+    e    = $.Event('show.bs.modal', relatedTarget: _relatedTarget)
+
+    @$element.trigger(e)
+
+    return if (@isShown || e.isDefaultPrevented())
+
+    @isShown = true
+
+    @checkScrollbar()
+    @$body.addClass('modal-open')
+
+    @setScrollbar()
+    @escape()
+
+    @$element.on 'click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(@hide, @)
+
+    @backdrop =>
+      transition = $.support.transition && that.$element.hasClass('fade')
+
+      @$element.appendTo(@$body) unless @$element.parent().length # don't move modals dom position
+
+      @$element.show().scrollTop(0)
+
+      @$element[0].offsetWidth if transition # force reflow
+
+      @$element.addClass('in').attr('aria-hidden', false)
+
+      @enforceFocus()
+
+      e = $.Event('shown.bs.modal', relatedTarget: _relatedTarget)
+
+      if transition
+        @$element.find('.modal-dialog') # wait for modal to slide in
+          .one 'bsTransitionEnd', -> @$element.trigger('focus').trigger(e)
+          .emulateTransitionEnd(300)
+      else
+        @$element.trigger('focus').trigger(e)
+
+
+  Modal::hide = (e) ->
+    if (e) then e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    @$element.trigger(e)
+
+    return if (!@isShown || e.isDefaultPrevented())
+
+    @isShown = false
+
+    @$body.removeClass('modal-open')
+
+    @resetScrollbar()
+    @escape()
+
+    $(document).off('focusin.bs.modal')
+
+    @$element
+      .removeClass('in')
+      .attr('aria-hidden', true)
+      .off('click.dismiss.bs.modal')
+
+    if $.support.transition && @$element.hasClass('fade')
+      @$element
+        .one('bsTransitionEnd', $.proxy(@hideModal, @))
+        .emulateTransitionEnd(300)
+    else
+      @hideModal()
+
+
+  Modal::enforceFocus = ->
+    $(document)
+      .off 'focusin.bs.modal' # guard against infinite focus loop
+      .on 'focusin.bs.modal',
+        $.proxy (e) ->
+          if @$element[0] != e.target && !@$element.has(e.target).length
+            @$element.trigger('focus')
+        , @
+
+
+  Modal::escape = ->
+    if @isShown && @options.keyboard
+      @$element.on 'keyup.dismiss.bs.modal',
+        $.proxy (e) ->
+          e.which == 27 && @hide()
+        , @
+    else if !@isShown
+      @$element.off('keyup.dismiss.bs.modal')
+
+
+  Modal::hideModal = ->
+    that = @
+    @$element.hide()
+    @backdrop -> that.$element.trigger('hidden.bs.modal')
+
+
+  Modal::removeBackdrop = ->
+    @$backdrop && @$backdrop.remove()
+    @$backdrop = null
+
+
+  Modal::backdrop = (callback) ->
+    that = this
+    animate = if @$element.hasClass('fade') then 'fade' else ''
+
+    if @isShown && @options.backdrop
+      doAnimate = $.support.transition && animate
+
+      @$backdrop = $('
').appendTo(@$body)
+
+      @$element.on 'click.dismiss.bs.modal',
+        $.proxy (e) ->
+          return if e.target != e.currentTarget
+          if @options.backdrop == 'static' then @$element[0].focus.call(@$element[0]) else @hide.call(this)
+        , this
+
+      @$backdrop[0].offsetWidth if doAnimate # force reflow
+
+      @$backdrop.addClass('in')
+
+      return if !callback
+
+      if doAnimate
+        @$backdrop.one('bsTransitionEnd', callback).emulateTransitionEnd(150)
+      else
+        callback()
+
+    else if !@isShown && @$backdrop
+      @$backdrop.removeClass('in')
+
+      callbackRemove = ->
+        that.removeBackdrop()
+        callback && callback()
+
+      if $.support.transition && @$element.hasClass('fade')
+        @$backdrop
+          .one('bsTransitionEnd', callbackRemove)
+          .emulateTransitionEnd(150)
+      else
+        callbackRemove()
+
+    else if callback
+      callback()
+
+
+  Modal::checkScrollbar = ->
+    return if document.body.clientWidth >= window.innerWidth
+    @scrollbarWidth = @scrollbarWidth || @measureScrollbar()
+
+
+  Modal::setScrollbar = ->
+    bodyPad = parseInt @$body.css('padding-right') || 0, 10
+    @$body.css('padding-right', bodyPad + @scrollbarWidth) if @scrollbarWidth
+
+
+  Modal::resetScrollbar = ->
+    @$body.css 'padding-right', ''
+
+
+  Modal::measureScrollbar = -> # thx walsh
+    scrollDiv = document.createElement('div')
+    scrollDiv.className = 'modal-scrollbar-measure'
+    @$body.append(scrollDiv)
+    scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+    @$body[0].removeChild(scrollDiv)
+    scrollbarWidth
+
+
+  # MODAL PLUGIN DEFINITION
+  # =======================
+
+  Plugin = (option, _relatedTarget) ->
+    @each ->
+      $element   = $(@)
+      data    = $element.data('bs.modal')
+      options = $.extend({}, Modal.DEFAULTS, $element.data(), typeof option == 'object' && option)
+
+      if (!data)
+        $element.data 'bs.modal', (data = new Modal(@, options))
+
+      if typeof option == 'string'
+        data[option](_relatedTarget)
+
+      else if options.show
+        data.show(_relatedTarget)
+
+  old = $.fn.modal
+
+  $.fn.modal             = Plugin
+  $.fn.modal.Constructor = Modal
+
+
+  # MODAL NO CONFLICT
+  # =================
+
+  $.fn.modal.noConflict = ->
+    $.fn.modal = old
+    this
+
+
   # MODAL DATA-API
   # ==============

-  $(document).on 'click.bs.modal.data-api', '[data-toggle="modal"]', (e) ->
-    $this   = $(@)
-    href    = $this.attr('href')
-    $target = $($this.attr('data-target') or (href and href.replace(/.*(?=#[^\s]+$)/, ''))) # strip for ie7
-    option  = if $target.data('bs.modal') then 'toggle' else $.extend({ remote: !/#/.test(href) and href }, $target.data(), $this.data())
-
-    if $this.is('a') then e.preventDefault()
-
-    $target.one 'show.bs.modal', (showEvent) ->
-        return if showEvent.isDefaultPrevented() # only register focus restorer if modal will actually get shown
-        $target.one 'hidden.bs.modal', -> $this.is(':visible') and $this.trigger('focus')
-
-    # Plugin.call($target, option, @)
-
-    $target.modal option, @
+  $(document).on 'click.bs.modal.data-api',
+    '[data-toggle="modal"]',
+    (e) ->
+      $this   = $(@)
+      href    = $this.attr('href')
+      $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) # strip for ie7
+      option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+      e.preventDefault() if $this.is('a')
+
+      $target.one 'show.bs.modal', (showEvent) ->
+        return if (showEvent.isDefaultPrevented()) # only register focus restorer if modal will actually get shown
+        $target.one 'hidden.bs.modal', ->
+          $this.is(':visible') && $this.trigger('focus')
+
+      Plugin.call($target, option, @)

   return


Add support for IntEditor and NumberEditor

--- bokehjs/src/coffee/widget/cell_editors.coffee
+++ bokehjs/src/coffee/widget/cell_editors.coffee
@@ -4,7 +4,8 @@
   "common/has_properties"
   "common/collection"
   "common/continuum_view"
-], (_, $, HasProperties, Collection, ContinuumView) ->
+  "jquery_ui/spinner"
+], (_, $, HasProperties, Collection, ContinuumView, $1) ->

   class CellEditor extends HasProperties
   class CellEditorCollection extends Collection
@@ -16,6 +17,7 @@
     initialize: (args) ->
       super({})
       @args = args
+      @model = @args.column.editorModel
       @render()
       @$el.appendTo(@args.container)

@@ -46,26 +48,36 @@
       @defaultValue = if value? then value else @emptyValue
       @setValue(@defaultValue)

-    validate: () ->
+    validateValue: (value) ->
       if @args.column.validator
-        result = @args.column.validator(@getValue())
+        result = @args.column.validator(value)
         if !result.valid
           return result

       return { valid: true, msg: null }

-  class StringEditorView extends CellEditorView
-
-    emptyValue: ""
-
-    el: '<input type="text" class="bk-cell-editor bk-cell-editor-string" />'
-
-    render: () ->
-      @$el.bind "keydown.nav", (event) =>
+    validate: () -> return @validateValue(@getValue())
+
+    disable_horizontal_navigation: () ->
+      @$el.bind "keydown", (event) =>
         if event.keyCode == $.ui.keyCode.LEFT or event.keyCode == $.ui.keyCode.RIGHT
           event.stopImmediatePropagation()
-      @focus()
-      @$el.select()
+
+    disable_vertical_navigation: () ->
+      @$el.bind "keydown", (event) =>
+        if event.keyCode == $.ui.keyCode.UP   or event.keyCode == $.ui.keyCode.PAGE_UP   or
+           event.keyCode == $.ui.keyCode.DOWN or event.keyCode == $.ui.keyCode.PAGE_DOWN
+          event.stopImmediatePropagation()
+
+  class StringEditorView extends CellEditorView
+
+    emptyValue: ""
+
+    el: '<input type="text" class="bk-cell-editor bk-cell-editor-string" />'
+
+    render: () ->
+      @disable_horizontal_navigation()
+      @$el.focus().select()

     loadValue: (item) ->
       super(item)
@@ -93,7 +105,8 @@
     el: '<select tabIndex="0" class="bk-cell-editor bk-cell-editor-select" />'

     render: () ->
-      for option in @args.column.editorModel.get("options")
+      @disable_vertical_navigation()
+      for option in @model.get("options")
         @$el.append($('<option>').attr(value: option).text(option))
       @focus()

@@ -128,6 +141,32 @@

   class IntEditorView extends CellEditorView

+    el: '<input type="text" class="bk-cell-editor bk-cell-editor-int" />'
+
+    render: () ->
+      @$el.spinner(step: @model.get("step"))
+      @disable_horizontal_navigation()
+      @disable_vertical_navigation()
+      @$el.focus().select()
+
+    remove: () ->
+      @$el.spinner("destroy")
+      super()
+
+    serializeValue: () ->
+      return parseInt(@getValue(), 10) || 0
+
+    loadValue: (item) ->
+      super(item)
+      @$el[0].defaultValue = @defaultValue
+      @$el.select()
+
+    validateValue: (value) ->
+      if isNaN(value)
+        return { valid: false, msg: "Please enter a valid integer"}
+      else
+        return super(value)
+
   class IntEditor extends CellEditor
     type: 'IntEditor'
     default_view: IntEditorView
@@ -136,6 +175,32 @@
     model: IntEditor

   class NumberEditorView extends CellEditorView
+
+    el: '<input type="text" class="bk-cell-editor bk-cell-editor-number" />'
+
+    render: () ->
+      @$el.spinner(step: @model.get("step"))
+      @disable_horizontal_navigation()
+      @disable_vertical_navigation()
+      @$el.focus().select()
+
+    remove: () ->
+      @$el.spinner("destroy")
+      super()
+
+    serializeValue: () ->
+      return parseFloat(@getValue()) || 0.0
+
+    loadValue: (item) ->
+      super(item)
+      @$el[0].defaultValue = @defaultValue
+      @$el.select()
+
+    validateValue: (value) ->
+      if isNaN(value)
+        return { valid: false, msg: "Please enter a valid number"}
+      else
+        return super(value)

   class NumberEditor extends CellEditor
     type: 'NumberEditor'


use 'delete' rather 'deactivate' to talk about delete user account

--- vue/src/shared/services/user_service.coffee
+++ vue/src/shared/services/user_service.coffee
@@ -32,7 +32,7 @@

     deactivate_user:
       icon: 'mdi-exit-run'
-      name: 'deactivation_modal.aria_label'
+      name: 'profile_page.delete_user_link'
       canPerform: -> !user.deactivatedAt
       perform: ->
         EventBus.$emit 'openModal',
@@ -40,7 +40,7 @@
           props:
             confirm:
               text:
-                title: 'deactivation_modal.aria_label'
+                title: 'profile_page.delete_account'
                 raw_helptext: "
                   <p>#{vm.$t('deactivation_modal.introduction')}</p>
                   <ul>


keep zebra stripes for file listing highlight

--- salvus/page/project_files.cjsx
+++ salvus/page/project_files.cjsx
@@ -381,7 +381,10 @@
         checked = @props.checked_files.has(misc.path_to_file(@props.current_path, name))
         is_public = @props.file_map[name].is_public
         if checked
-            color = 'rgb(255, 255, 204)'
+            if index % 2 == 0
+                color = 'rgb(250, 250, 209)'
+            else
+                color = 'rgb(255, 255, 220)'
         else if index % 2 == 0
             color = '#eee'
         else


fixed issues with copying files to a different project in public view when signed in

--- salvus/page/project_files.cjsx
+++ salvus/page/project_files.cjsx
@@ -761,7 +761,7 @@

     getInitialState : ->
         copy_destination_directory  : ''
-        copy_destination_project_id : @props.project_id
+        copy_destination_project_id : if @props.public_view then '' else @props.project_id
         move_destination            : ''
         new_name                    : misc.path_split(@props.checked_files?.first()).tail
         show_different_project      : @props.public_view
@@ -984,11 +984,12 @@
             <Col sm=4 style={color:'#666'}>

In the project

                 <Combobox
-                    valueField   = {'id'}
-                    textField    = {'title'}
+                    valueField   = 'id'
+                    textField    = 'title'
                     data         = {@props.flux.getStore('projects').get_project_select_list(@props.project_id)}
-                    filter       = {'contains'}
+                    filter       = 'contains'
                     defaultValue = {if not @props.public_view then @props.project_id}
+                    placeholder  = 'Select a project...'
                     onSelect     = {(value) => @setState(copy_destination_project_id : value.id)}
                     messages     = {emptyFilter : '', emptyList : ''}
                     />
@@ -1034,12 +1035,13 @@

     valid_copy_input : ->
         input = @state.copy_destination_directory
+        if @state.copy_destination_project_id is ''
+            return false
         if input is @props.current_directory
             return false
         if misc.startswith(input, '/') # TODO: make this smarter
             return false
-        else
-            return true
+        return true

     create_account_click : (e) ->
         e.preventDefault()


correct systems tab view test describe

correct systems tab view test describe

--- test/app/views/editor/systems_tab_view.spec.coffee
+++ test/app/views/editor/systems_tab_view.spec.coffee
@@ -1,4 +1,4 @@
-describe 'editor/level/thangs_tab', ->
+describe 'editor/level/systems_tab', ->
   SystemsTabView = require 'views/editor/level/systems_tab_view'

   it 'does stuff', ->


OX-155 removed z-axis repitition

OX-155 removed z-axis repitition

--- app/assets/javascripts/model/binary/renderer.coffee
+++ app/assets/javascripts/model/binary/renderer.coffee
@@ -21,9 +21,7 @@
   TEXTURE_SIZE : 512
   MAP_SIZE : 85 # 4⁰ + 4¹ + 4² + 4³
   DELTA : [9, 4, 0]
-  REPEAT : [0, 0, 1]
-
-  SPECIALbucketData : null
+  REPEAT : [0, 0, 0]

   RECURSION_PLACEHOLDER : {}


add network attribute to sharing

add network attribute to sharing

--- app/assets/javascripts/sharing_tags/share.js.coffee
+++ app/assets/javascripts/sharing_tags/share.js.coffee
@@ -5,6 +5,7 @@
   @debug: false

   @share: (network, attributes)->
+    attributes.network = network
     SharingTags.Share[network]?(attributes)

   class @Error extends Error


fixing development/prod deployment issue

--- app/assets/javascripts/workspace/cesium_view.coffee
+++ app/assets/javascripts/workspace/cesium_view.coffee
@@ -1,4 +1,4 @@
-@CESIUM_BASE_URL = 'http://nasa-ace-web.dev/cesium'
+@CESIUM_BASE_URL = "http://#{document.location.host}/cesium"

 class @Workspace.CesiumView
   BASE_HEIGHT: 554678932


Nginx: increase hubspot timeout

Nginx: increase hubspot timeout

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -145,7 +145,7 @@
           proxy_set_header      X-Real-IP       $remote_addr;
           proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_next_upstream   error timeout   invalid_header http_500;
-          proxy_connect_timeout 3;
+          proxy_connect_timeout 10;

           proxy_pass #{proxy};


nginx: improve /?/kd routes

nginx: improve /?/kd routes

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -313,7 +313,7 @@
       }

       # redirect /d/kd to KD installer for development channel
-      location /d/kd {
+      location ~ ^/d/kd$ {
         proxy_pass            "https://s3.amazonaws.com/koding-kd/development/install-kd.sh";
         proxy_set_header      X-Host          $host;
         proxy_set_header      X-Real-IP       $remote_addr;
@@ -324,7 +324,7 @@
       }

       # redirect /p/kd to KD installer for production channel
-      location /p/kd {
+      location ~ ^/p/kd$ {
         proxy_pass            "https://s3.amazonaws.com/koding-kd/production/install-kd.sh";
         proxy_set_header      X-Host          $host;
         proxy_set_header      X-Real-IP       $remote_addr;


added additional images, minor code cleanup

--- src/scripts/scotch.coffee
+++ src/scripts/scotch.coffee
@@ -17,11 +17,8 @@
   "https://s3.amazonaws.com/scotch-assets/scotch_1.jpg",
   "https://s3.amazonaws.com/scotch-assets/scotch_2.jpg",
   "https://s3.amazonaws.com/scotch-assets/scotch_3.jpg",
-  "https://s3.amazonaws.com/scotch-assets/scotch_4.jpg"
-]
-
-message =[
-  "You're Welcome"
+  "https://s3.amazonaws.com/scotch-assets/scotch_4.jpg",
+  "https://s3.amazonaws.com/scotch-assets/scotch_5.jpg"
 ]

 module.exports = (robot) ->


Some goe indeces were missing

Some goe indeces were missing

--- src/server/install.coffee
+++ src/server/install.coffee
@@ -2,3 +2,4 @@
   Stops._ensureIndex({"loc": "2d" });
   Trips._ensureIndex({"fromLoc": "2d" });
   Trips._ensureIndex({"toLoc": "2d" });
+  Trips._ensureIndex({"stops.loc": "2d" });


setMaxListeners to remove warnings

setMaxListeners to remove warnings

--- src/server/session.coffee
+++ src/server/session.coffee
@@ -43,6 +43,10 @@

   lastReceivedCollection = null
   lastReceivedDoc = null
+
+  # Subscriptions care about the stream being destroyed. We end up with a
+  # listener per subscribed document for the client, which can be a lot.
+  stream.setMaxListeners 0

   seq = 1


rewritten some parts

rewritten some parts

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -1,15 +1,14 @@
 'use strict'
-{CompositeDisposable} = require 'atom'
+{Disposable, CompositeDisposable} = require 'atom'

 class BlockCursor
-  cursorStyle = null
+  primarySelector = 'atom-text-editor::shadow .cursors .cursor'
+  secondarySelector = 'atom-text-editor::shadow .cursors.blink-off .cursor'
   cursorTypeMap =
     '\u25AE - Block': 'block' # ▮
     '\u25AF - Bordered box': 'bordered-box' # ▯
     '| - I-beam': 'i-beam'
     '_ - Underline': 'underline'
-  primarySelector = 'atom-text-editor::shadow .cursors .cursor'
-  secondarySelector = 'atom-text-editor::shadow .cursors.blink-off .cursor'

   config:
     cursorType:
@@ -72,39 +71,33 @@

   activate: ->
     @subs = new CompositeDisposable()
-    @observeConfigs
-      'cursorType': @applyCursorType
-      'primaryColor': @applyPrimaryColor
-      'primaryColorAlpha': @applyPrimaryColor
-      'secondaryColor': @applySecondaryColor
-      'secondaryColorAlpha': @applySecondaryColor
-      'blinkInterval': @applyBlinkInterval.bind @
-      'pulseDuration': @applyPulseDuration
-      'cursorThickness': @applyCursorThickness
-      'cursorLineFix': @applyCursorLineFix
-    @setConfig 'preview', 'The quick brown fox jumps over the lazy dog'
-    atom.config.unset 'block-cursor.zzzpreview' # renamed to 'preview', so leave this here for few versions
+    @subs.add [
+      atom.config.observe 'block-cursor.cursorType', @applyCursorType
+      atom.config.observe 'block-cursor.primaryColor', @applyPrimaryColor
+      atom.config.observe 'block-cursor.primaryColorAlpha', @applyPrimaryColor
+      atom.config.observe 'block-cursor.secondaryColor', @applySecondaryColor
+      atom.config.observe 'block-cursor.secondaryColorAlpha', @applySecondaryColor
+      atom.config.observe 'block-cursor.blinkInterval', @applyBlinkInterval.bind @
+      atom.config.observe 'block-cursor.pulseDuration', @applyPulseDuration
+      atom.config.observe 'block-cursor.cursorThickness', @applyCursorThickness
+      atom.config.observe 'block-cursor.cursorLineFix', @applyCursorLineFix
+    ]
+    # set some text in the preview field
+    atom.config.set 'block-cursor.preview', 'The quick brown fox jumps over the lazy dog'
+    # renamed to 'preview', so leave this here for few versions. preview was zzzpreview
+    # as a hack to force it to show at the bottom of the list in the settings view
+    atom.config.unset 'block-cursor.zzzpreview'

   deactivate: ->
     @subs.dispose()
-    if cursorStyle?
-      cursorStyle.parentNode.removeChild cursorStyle
-      cursorStyle = null
-
-  getConfig: (key) ->
-    atom.config.get "block-cursor.#{key}"
-
-  setConfig: (key, value) ->
-    atom.config.set "block-cursor.#{key}", value
-
-  observeConfigs: (obj) ->
-    for own key, cb of obj
-      @subs.add atom.config.observe "block-cursor.#{key}", cb

   getColor: (which) ->
-    color = @getConfig "#{which}Color"
-    color.alpha = @getConfig "#{which}ColorAlpha"
+    color = atom.config.get "block-cursor.#{which}Color"
+    color.alpha = atom.config.get "block-cursor.#{which}ColorAlpha"
     color.toRGBAString()
+
+  getBlinkInterval: ->
+    atom.config.get 'block-cursor.blinkInterval'

   applyCursorType: (cursorTypeName) ->
     cursorType = cursorTypeMap[cursorTypeName] ? cursorTypeName
@@ -117,13 +110,13 @@
     @updateStylesheet primarySelector, 'background-color', color
     @updateStylesheet primarySelector, 'border-color', color
     # also apply to blink-off state if cursor blink is disabled
-    if 0 is @getConfig 'blinkInterval'
+    if 0 is @getBlinkInterval()
       @updateStylesheet secondarySelector, 'background-color', color
       @updateStylesheet secondarySelector, 'border-color', color

   applySecondaryColor: =>
     # use primaryColor if cursor blink is disabled
-    color = @getColor (if 0 is @getConfig 'blinkInterval' then 'primary' else 'secondary')
+    color = @getColor (if 0 is @getBlinkInterval() then 'primary' else 'secondary')
     @updateStylesheet secondarySelector, 'background-color', color
     @updateStylesheet secondarySelector, 'border-color', color

@@ -153,15 +146,21 @@
     workspaceView = atom.views.getView atom.workspace
     workspaceView.classList[if doFix then 'add' else 'remove'] 'block-cursor-cursor-line-fix'

-  getCursorStyle: ->
-    return cursorStyle if cursorStyle?
-    cursorStyle = document.createElement 'style'
-    cursorStyle.type = 'text/css'
-    document.querySelector('head atom-styles').appendChild cursorStyle
-    cursorStyle
+  updateStylesheet: do ->
+    sub = null
+    style = null

-  updateStylesheet: (selector, property, value) ->
-    sheet = @getCursorStyle().sheet
-    sheet.insertRule "#{selector} { #{property}: #{value}; }", sheet.cssRules.length
+    (selector, property, value) ->
+      unless sub?
+        sub = new Disposable ->
+          style.parentNode.removeChild style
+          style = null
+          sub = null
+        @subs.add sub
+      unless style?
+        style = document.createElement 'style'
+        style.type = 'text/css'
+        document.querySelector('head atom-styles').appendChild style
+      style.sheet.insertRule "#{selector} { #{property}: #{value}; }", style.sheet.cssRules.length

 module.exports = new BlockCursor()


change project.eachEditor to workspace.eachEditor

--- lib/clang-format.coffee
+++ lib/clang-format.coffee
@@ -3,7 +3,7 @@
 module.exports =
 class ClangFormat
   constructor: (state) ->
-    atom.project.eachEditor (editor) =>
+    atom.workspace.eachEditor (editor) =>
       @handleBufferEvents(editor)

     atom.workspaceView.command 'clang-format:format', =>


cli generate Pool.destroyEntity

cli generate Pool.destroyEntity

--- lib/cli/generate.coffee
+++ lib/cli/generate.coffee
@@ -322,7 +322,7 @@
           js.push "   * @returns {entitas.Entity}"
           js.push "   */"
           js.push "  Pool.prototype.remove#{Name} = function() {"
-          js.push "    this.destroyEntity(#{name}Entity);"
+          js.push "    this.destroyEntity(this.#{name}Entity);"
           js.push "  };"

           d3.push "        #{name}Entity: Entity;"


fixes #315

fixes #315

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -281,13 +281,13 @@

 Reference::enc_verbatim = (f) ->
   if Translator.BetterBibTeX
-    href = ('' + f.value).replace(/([#\\%&{}])/g, '\\$1')
+    value = ('' + f.value).replace(/([#\\%&{}])/g, '\\$1')
   else
-    href = ('' + f.value).replace(/([\\{}])/g, '\\$1')
-  href = href.replace(/[^\x21-\x7E]/g, ((chr) -> '\\%' + ('00' + chr.charCodeAt(0).toString(16).slice(-2)))) if not Translator.unicode
-
-  return "\\href{#{href}}{#{LaTeX.text2latex(href)}}" if f.name == 'url' && Translator.fancyURLs
-  return href
+    value = ('' + f.value).replace(/([\\{}])/g, '\\$1')
+  value = value.replace(/[^\x21-\x7E]/g, ((chr) -> '\\%' + ('00' + chr.charCodeAt(0).toString(16).slice(-2)))) if not Translator.unicode
+
+  return "\\href{#{value}}{#{LaTeX.text2latex(value)}}" if f.name == 'url' && Translator.fancyURLs
+  return value

 Reference::nonLetters = new XRegExp("[^\\p{Letter}]", 'g')
 Reference::punctuationAtEnd = new XRegExp("[\\p{Punctuation}]$")
@@ -383,15 +383,14 @@
   return value

 Reference::enc_tags = (f) ->
-  return null if not f.value || f.value.length == 0
-  tags = (tag.tag for tag in f.value when tag.tag != Translator.rawLaTag)
+  tags = (tag.tag for tag in f.value || [] when tag?.tag && tag.tag != Translator.rawLaTag)
+  return null tags.length == 0

   # sort tags for stable tests
   tags.sort() if Translator.testing

-  f.value = tags
-  f.sep = ','
-  return @enc_latex(f)
+  tags = ((if Translator.BetterBibTeX then tag.replace(/([#\\%&{}])/g, '\\$1') else tag.replace(/([#%\\{}])/g, '\\$1')).replace(/,/g, '{,}') for tag in tag)
+  return tags.join(',')

 Reference::enc_attachments = (f) ->
   return null if not f.value || f.value.length == 0


Added ability to fetch user summary using custom URL

--- steam.coffee
+++ steam.coffee
@@ -61,13 +61,20 @@
             msg.match[2] += if msg.match[2].slice(-1) is "s" then "'" else "'s"
             msg.reply "#{msg.match[2]} Steam ID is: #{id}"

-    robot.respond /steam status (\d+)/i, (msg) ->
-        steam_request msg, "/ISteamUser/GetPlayerSummaries", steamids: msg.match[1], (object) ->
-            player = object.response.players[0]
-            status = if player.communityvisibilitystate is 1 then 'Unavailable (Private)' else personaStates[player.personastate]
-            lastOnline = moment.unix(player.lastlogoff).fromNow()
-            msg.reply "#{msg.match[1]} belongs to #{player.personaname} who is currently #{status} and was last online #{lastOnline}."
-        , 2
+    robot.respond /steam status (.*)/i, (msg) ->
+        summary = (steamID) ->
+            steam_request msg, "/ISteamUser/GetPlayerSummaries", steamids: steamID, (object) ->
+                player = object.response.players[0]
+                status = if player.communityvisibilitystate is 1 then 'Unavailable (Private)' else personaStates[player.personastate]
+                lastOnline = moment.unix(player.lastlogoff).fromNow()
+                msg.reply "#{msg.match[1]} belongs to #{player.personaname} who is currently #{status} and was last online #{lastOnline}."
+            , 2
+
+        if msg.match[1].match /\d{17}/
+            summary msg.match[1]
+        else
+            getSteamID msg, msg.match[1], (steamID) ->
+                summary steamID

     robot.respond /dota history (.*)/i, (msg) ->
         [command, value] = msg.match
@@ -133,8 +140,8 @@
                 if msg.match[2].match /\d{17}/
                     playerInfo getCommunityID(msg.match[2])
                 else
-                    getSteamID msg, msg.match[2], (id) ->
-                        playerInfo getCommunityID(id)
+                    getSteamID msg, msg.match[2], (steamID) ->
+                        playerInfo getCommunityID(steamID)

 getSteamID = (msg, customURL, handler) ->
     steam_request msg, "/ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->


Text now are not set if tag contains styled children

--- style.coffee
+++ style.coffee
@@ -360,17 +360,18 @@
 		obj = @getTextObject()
 		if obj
 			# break if already has a parent with text
-			hasParentWithText = false
+			shouldSetText = true
 			parent = node
 			while parent = parent.parent
 				if parent._documentStyle?.isTextSet
-					hasParentWithText = true
+					shouldSetText = false
 					break

-			if hasParentWithText
-				if isText
-					@item.visible = false
-			else
+			# break if has a styles children
+			if shouldSetText and node instanceof Tag and node.query('[neft:style]')
+				shouldSetText = false
+
+			if shouldSetText
 				if isText
 					text = node.text
 					@item.visible = text.length > 0
@@ -380,6 +381,9 @@
 				if text.length > 0 or @isTextSet
 					@isTextSet = true
 					obj.text = text
+			else
+				if isText
+					@item.visible = false
 		return

 	getVisibility: ->


More spaces between functions

More spaces between functions

--- resources/assets/coffee/react/beatmapset-page/hype.coffee
+++ resources/assets/coffee/react/beatmapset-page/hype.coffee
@@ -99,8 +99,10 @@
         props:
           href: @reportUrl()

+
   reportUrl: =>
     "#{route('beatmapsets.discussion', beatmapset: @props.beatmapset.id, beatmap: '-', mode: 'generalAll')}#new"

+
   userCanDisqualify: =>
     @props.currentUser? && (@props.currentUser.is_moderator || @props.currentUser.is_admin || @props.currentUser.is_full_bn)


Revert 2b747c8e67afd5665d3449128cf20be2aa81b2ca

inline-block breaks text wrapping

--- src/core/renderer.coffee
+++ src/core/renderer.coffee
@@ -32,13 +32,6 @@
   '.editor-container blockquote' : { 'margin': '0 0 0 2em', 'padding': '0' }
   '.editor-container ol'         : { 'margin': '0 0 0 2em', 'padding': '0', 'list-style-type': 'decimal' }
   '.editor-container ul'         : { 'margin': '0 0 0 2em', 'padding': '0', 'list-style-type': 'disc' }
-
-# Make inline tags inline-block
-tags = _.reduce(Normalizer.TAGS, (tags, tag) ->
-  tags.push(".editor-container #{tag.toLowerCase()}") unless dom.BLOCK_TAGS[tag]? or dom.VOID_TAGS[tag]?
-  return tags
-, [])
-DEFAULT_STYLES[tags.join(', ')] = { 'display': 'inline-block' } unless dom.isIE(11)

 LIST_STYLES = ['decimal', 'lower-alpha', 'lower-roman']
 rule = '.editor-container ol > li'


Various fixes, cleanups. Fixing clashes between zoom and brush. Disallowing to move/zoom out of the domain.

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -66,13 +66,26 @@
             @options.width = @svg[0][0].clientWidth
             @options.height = @svg[0][0].clientHeight

+        @options.selectionLimit ||= null
         @options.brush ||= {}
         @options.brush.start ||= @options.start
-        @options.brush.end ||= new Date(new Date(@options.brush.start).setDate(@options.brush.start.getDate() + 3))
+        if @options.selectionLimit
+            @options.brush.end ||= new Date(@options.brush.start.getTime() + @options.selectionLimit * 1000)
+        else
+            @options.brush.end ||= new Date(new Date(@options.brush.start).setDate(@options.brush.start.getDate() + 3))
+
+        @selectionConstraint = [
+            new Date(@options.brush.start.getTime() - @options.selectionLimit * 1000),
+            new Date(@options.brush.end.getTime() + @options.selectionLimit * 1000)
+        ]
+
+        @options.display ||= {}
+        @options.display.start ||= @options.domain.start
+        @options.display.end ||= @options.domain.end
+
         @options.debounce ||= 50
         @options.ticksize ||= 3
         @options.datasets ||= []
-        @options.selectionLimit ||= -1

         @recordFilter = @options.recordFilter

@@ -97,7 +110,7 @@
         # scales
         @scales = {
             x: d3.time.scale.utc()
-                .domain([ @options.domain.start, @options.domain.end ])
+                .domain([ @options.display.start, @options.display.end ])
                 .range([0, @options.width])
             y: d3.scale.linear()
                 .range([@options.height-29, 0])
@@ -132,32 +145,32 @@
         @brush = d3.svg.brush()
             .x(@scales.x)
             .on('brushstart', =>
-                @options.lastZoom = {
-                    scale: @options.zoom.scale(),
-                    translate: [
-                        @options.zoom.translate()[0],
-                        @options.zoom.translate()[1],
-                    ]
-                }
-
-                @options.zoom.on('zoom', null)
+                # deactivate zoom behavior
+                @brushing = true
+                @prevTranslate = @options.zoom.translate()
+                @prevScale = @options.zoom.scale()
+                @selectionConstraint = null
+
+                # show the brush tooltips
+                if @brushTooltip
+                    @tooltipBrushMin.transition()
+                        .duration(100)
+                        .style("opacity", .9)
+                    @tooltipBrushMax.transition()
+                        .duration(100)
+                        .style("opacity", .9)
             )
             .on('brushend', =>
-                @options.zoom
-                    .scale(@options.lastZoom.scale)
-                    .translate(@options.lastZoom.translate)
-                    .on('zoom', => @redraw)
-
-                # Check for selection limit and reduce to correct size
-                if @options.selectionLimit > 0
-                    @svg.selectAll('.brush')
-                        .attr({fill: "#333"})
-                    s = @brush.extent()
-                    if (s[1]-s[0])/1000 >= @options.selectionLimit
-                        tmpdate = new Date(@brush.extent()[0].getTime() + @options.selectionLimit * 1000)
-                        @brush.extent([@brush.extent()[0], tmpdate])
-                        d3.select(@element).select('g.brush').call(@brush)
-
+                @brushing = false
+                @options.zoom.translate(@prevTranslate)
+                @options.zoom.scale(@prevScale)
+
+                @checkBrush()
+                @redraw()
+
+                @selectionConstraint = null
+
+                # dispatch the events
                 @element.dispatchEvent(
                     new CustomEvent('selectionChanged', {
                         detail: {
@@ -169,53 +182,33 @@
                     })
                 )

-                if (@brushTooltip)
+                # hide the brush tooltips
+                if @brushTooltip
                     @tooltipBrushMin.transition()
                         .duration(100)
                         .style("opacity", 0)
-
                     @tooltipBrushMax.transition()
                         .duration(100)
                         .style("opacity", 0)
-
             )
             .on('brush', =>
-                # Check for selection limit, warn in red if selection is to big
-                if @options.selectionLimit > 0
-                    s = @brush.extent()
-                    if (s[1]-s[0]) / 1000 > @options.selectionLimit
-                        @svg.selectAll('.brush')
-                            .attr({fill: "red"})
-
+                if @options.selectionLimit != null
+                    if @selectionConstraint == null
+                        [low, high] = @brush.extent()
+                        @selectionConstraint = [
+                            new Date(high.getTime() - @options.selectionLimit * 1000),
+                            new Date(low.getTime() + @options.selectionLimit * 1000)
+                        ]
                     else
-                        @svg.selectAll('.brush')
-                            .attr({fill: "#333"})
-
-                if @brushTooltip
-                    offheight = 0
-                    if @svg[0][0].parentElement?
-                        offheight = @svg[0][0].parentElement.offsetHeight
-                    else
-                        offheight = @svg[0][0].parentNode.offsetHeight
-                    @options.zoom
-                        .scale(@options.lastZoom.scale)
-                        .translate(@options.lastZoom.translate)
-
-                    @tooltipBrushMin.transition()
-                        .duration(100)
-                        .style("opacity", .9)
-                    @tooltipBrushMin.html(@simplifyDate(@brush.extent()[0]))
-                        .style("left", (@scales.x(@brush.extent()[0])+@brushTooltipOffset[0]) + "px")
-                        .style("top", (offheight + @brushTooltipOffset[1]) + "px")
-
-
-                    @tooltipBrushMax.transition()
-                        .duration(100)
-                        .style("opacity", .9)
-                    @tooltipBrushMax.html(@simplifyDate(@brush.extent()[1]))
-                        .style("left", (@scales.x(@brush.extent()[1])+@brushTooltipOffset[0]) + "px")
-                        .style("top", (offheight + @brushTooltipOffset[1] + 20) + "px")
-
+                        if d3.event.mode == "move"
+                            [low, high] = @brush.extent()
+                            @selectionConstraint = [
+                                new Date(high.getTime() - @options.selectionLimit * 1000),
+                                new Date(low.getTime() + @options.selectionLimit * 1000)
+                            ]
+                        @checkBrush()
+
+                @redraw()
             )
             .extent([@options.brush.start, @options.brush.end])

@@ -246,11 +239,20 @@
             )

         # create the zoom behavior
+        minScale = (@options.display.start - @options.display.end) / (@options.domain.start - @options.domain.end)
+
         @options.zoom = d3.behavior.zoom()
             .x(@scales.x)
             .size([@options.width, @options.height])
-            .scaleExtent([1, Infinity])
-            .on('zoom', => @redraw())
+            .scaleExtent([minScale, Infinity])
+            .on('zoom', =>
+                if @brushing
+                    @options.zoom.scale(@prevScale)
+                    @options.zoom.translate(@prevTranslate)
+                else
+                    [start, end] = @scales.x.domain();
+                    @center(start, end)
+            )
         @svg.call(@options.zoom)

         # initialize all datasets
@@ -265,6 +267,18 @@
     ## Private API
     ###

+    checkBrush: ->
+        if @selectionConstraint
+            [a, b] = @selectionConstraint
+            [x, y] = @brush.extent()
+
+            if x < a
+                x = a
+            if y > b
+                y = b
+
+            @brush.extent([x, y])
+
     redraw: ->
         # update brush
         @brush.x(@scales.x).extent(@brush.extent())
@@ -272,6 +286,23 @@
         # repaint the axis and the brush
         d3.select(@element).select('g.mainaxis').call(@axis.x)
         d3.select(@element).select('g.brush').call(@brush)
+
+        # redraw brushes
+        if @brushTooltip
+            offheight = 0
+            if @svg[0][0].parentElement?
+                offheight = @svg[0][0].parentElement.offsetHeight
+            else
+                offheight = @svg[0][0].parentNode.offsetHeight
+
+            @tooltipBrushMin.html(@simplifyDate(@brush.extent()[0]))
+                .style("left", (@scales.x(@brush.extent()[0])+@brushTooltipOffset[0]) + "px")
+                .style("top", (offheight + @brushTooltipOffset[1]) + "px")
+
+            @tooltipBrushMax.html(@simplifyDate(@brush.extent()[1]))
+                .style("left", (@scales.x(@brush.extent()[1])+@brushTooltipOffset[0]) + "px")
+                .style("top", (offheight + @brushTooltipOffset[1] + 20) + "px")
+

         # repaint the datasets
         # First paint lines and ticks
@@ -519,30 +550,7 @@
                     else if not (record[1] instanceof Date)
                         record = [ record[0], record[0] ].concat(record[1..])

-                    # TODO: implement other data conversions
-
                     finalRecords.push(record)
-
-                    # if record[0] instanceof Date and record[1] instanceof Date
-                    #     ranges.push(record)
-                    # else
-                    #     points.push(record)
-
-                    # TODO: implement paths
-
-                    # if Array.isArray(record)
-                    #     if record.length == 3
-                    #         paths.push(record)
-                    #     else
-                    #         ranges.push(record)
-                    # else if record instanceof Date
-                    #     points.push(record)
-
-                    # # TODO: keep this?
-                    # else if record.split("/").length > 1
-                    #     elements = record.split("/")
-                    #     elements.pop()
-                    #     ranges.push(elements)
             else
                 # TODO: perform check of records
                 finalPaths = records
@@ -678,15 +686,24 @@
     hasDataset: (id) ->
         return false unless @datasets[id]?

-    # TODO redraws.
+    # redraws.
     center: (params...) ->
         start = new Date(params[0])
         end = new Date(params[1])
         [ start, end ] = [ end, start ] if end < start

-        @options.zoom.scale((@options.domain.end - @options.domain.start) / (end - start))
+        diff = end - start
+        if start < @options.domain.start
+            start = @options.domain.start
+            newEnd = new Date(start.getTime() + diff)
+            end = if newEnd < @options.domain.end then newEnd else @options.domain.end
+        if end > @options.domain.end
+            end = @options.domain.end
+            newStart = new Date(end.getTime() - diff)
+            start = if newStart > @options.domain.start then newStart else @options.domain.start
+
+        @options.zoom.scale((@options.display.end - @options.display.start) / (end - start))
         @options.zoom.translate([ @options.zoom.translate()[0] - @scales.x(start), 0 ])
-
         @redraw()

         true


removed unnecessary (since the last commit) initialisers for d3 events

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -71,7 +71,6 @@
             .call(@grid.x)

         # brush
-        element.dispatch = d3.dispatch('brushStart', 'brushEnd')
         @brush = d3.svg.brush()
             .x(@scales.x)
             .on('brushend', =>


Ensures line numbers match up with required javascript.

--- src/stdlib/require.coffee
+++ src/stdlib/require.coffee
@@ -51,9 +51,7 @@

     if not /define\(/.test code
       code = """
-        define(function(require, exports, module) {
-          'use strict';
-          #{code};
+        define(function(require, exports, module) { 'use strict'; #{code};
         });
       """
     __jsc__.evalJSString_withScriptPath code, file


Added support for Turbolinks 5

Added support for Turbolinks 5

Turbolinks 5 has new event names. This change adds an event listener for the new event, which functions similarly to the `page:change` event in previous versions.

--- app/assets/javascripts/plugins/camaleon_google_analytic/google_analytics.js.coffee
+++ app/assets/javascripts/plugins/camaleon_google_analytic/google_analytics.js.coffee
@@ -21,6 +21,7 @@

     if typeof Turbolinks isnt 'undefined' and Turbolinks.supported
       document.addEventListener "page:change", GoogleAnalytics.trackPageview, true
+      document.addEventListener "turbolinks:load", GoogleAnalytics.trackPageview, true
     else
       GoogleAnalytics.trackPageview()


Setting cartId on construct

Setting cartId on construct

--- src/features/cart.coffee
+++ src/features/cart.coffee
@@ -2,7 +2,7 @@

 		constructor: (@m) ->

-			@Identifier()
+			@cartId = @Identifier()

 		Identifier: (reset = false, id = false) ->


add warnings for non-standard FS options

--- src/gerber-parser.coffee
+++ src/gerber-parser.coffee
@@ -30,8 +30,14 @@
     if not (m = p.match reFS)
       throw new Error 'invalid format specification'
     [_, zero, nota, pN, pM] = m
-    if zero isnt 'L' and zero isnt 'T' then zero = 'L' # defaults to leading
-    if nota isnt 'A' and nota isnt 'I' then nota = 'A' # defaults to absolute
+    if zero isnt 'L' and zero isnt 'T'
+      console.warn 'gerber zero suppression is not specified. assuming
+        leading zero suppression (same as no zero suppression)'
+      zero = 'L'
+    if nota isnt 'A' and nota isnt 'I'
+      console.warn 'gerber coordinate notation is not specified. assuming
+        absolute notation'
+      nota = 'A'
     @format.zero ?= zero
     @format.places ?= [Number(pN), Number(pM)]
     c.set ?= {}


status widget, input was missing event parameter

--- client/landing/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
@@ -54,7 +54,7 @@
         @utils.wait 1000, =>
           @requestEmbed()

-      keyup:=>
+      keyup:(event)=>
         # this needs to be refactored, this will only capture URLS when the user
         # adds a space after them or tabs out
         if ($(event.which)[0] is 32) or ($(event.which)[0] is 9) # when space key is hit, URL is usually complete


Don't need to add a new Layer, Sinan can u check this.

--- client/landing/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
@@ -77,7 +77,7 @@

   switchToLargeView:->

-    @parent.unsetClass "no-shadow"
+    @parent.unsetClass "no-shadow" if @parent # monkeypatch when loggedout this was giving an error
     @smallInput.hide()
     @$('>div.large-input, >div.formline').show()

@@ -85,8 +85,9 @@
       @largeInput.$().trigger "focus"
       @largeInput.setHeight 72

-    tabView = @parent.getDelegate()
-    @getSingleton("windowController").addLayer tabView
+    # Do we really need this? Without that it works great.
+    # tabView = @parent.getDelegate()
+    # @getSingleton("windowController").addLayer tabView

   switchToEditView:(activity)->
     {tags, body} = activity
@@ -109,7 +110,6 @@
     super

   viewAppended:->
-
     @setTemplate @pistachio()
     @template.update()
     @switchToSmallView()


Enable multiple users

Enable multiple users

Enable atom for multiple users on same linux computer.

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -18,7 +18,7 @@
   if process.platform is 'win32'
     '\\\\.\\pipe\\atom-sock'
   else
-    path.join(os.tmpdir(), "atom_#{process.env['USER']}.sock")
+    path.join(os.tmpdir(), "atom_#{process.env.USER}.sock")

 # The application's singleton class.
 #


Better feedback on unsupported country.

Better feedback on unsupported country.

--- src/coffee/customerxmlimport.coffee
+++ src/coffee/customerxmlimport.coffee
@@ -215,13 +215,13 @@
     for k, xml of xmljs.Customer
       customerNumber = xmlHelpers.xmlVal xml, 'CustomerNr'

-      country = xmlHelpers.xmlVal xml, 'country'
-      country = switch country
+      rawCountry = xmlHelpers.xmlVal xml, 'country'
+      country = switch rawCountry
         when 'D' then 'DE'
         when 'A' then 'AT'

       unless country?
-        console.error "Unsupported country '#{country}'"
+        console.error "Unsupported country '#{rawCountry}'"
         continue

       customerGroup = xmlHelpers.xmlVal xml, 'group', NO_CUSTOMER_GROUP


add error handler to each stage of download pipeline

--- app/coffee/MongoAWS.coffee
+++ app/coffee/MongoAWS.coffee
@@ -41,7 +41,11 @@
 			.on 'finish', () ->
 				return callback(null)

-	unArchiveDocHistory: (project_id, doc_id, callback = (error) ->) ->
+	unArchiveDocHistory: (project_id, doc_id, _callback = (error) ->) ->
+
+		callback = (args...) ->
+			_callback(args...)
+			_callback = () ->

 		AWS.config.update {
 			accessKeyId: settings.filestore.s3.key
@@ -61,6 +65,8 @@
 		download
 			.on 'open', (obj) ->
 				return 1
+			.on 'error', (err) ->
+				callback(err)
 			.pipe lineStream
 			.on 'data', (line) ->
 				if line.length > 2


jQuery variables should start with $

jQuery variables should start with $

--- src/controllers/expensesCtrl.coffee
+++ src/controllers/expensesCtrl.coffee
@@ -26,16 +26,16 @@
     e.val val.toFixed 2

 toggleAddItemDialog = (show) ->
-  addItemDialog = $('#addItemDialog')
-  visible = addItemDialog.is ':visible'
+  $addItemDialog = $('#addItemDialog')
+  visible = $addItemDialog.is ':visible'
   if show? and show and visible then return
   if show? and not show and not visible then return
-  addItemDialog.show() if not visible
+  $addItemDialog.show() if not visible
   onComplete = ->
-    addItemDialog.hide() if visible
-  bottomPixels = addItemDialog.outerHeight()
-  addItemDialog.animate(
-    { bottom: if visible then "-#{bottomPixels}px" else "-#{addItemDialog.css 'border-bottom-width'}" },
+    $addItemDialog.hide() if visible
+  bottomPixels = $addItemDialog.outerHeight()
+  $addItemDialog.animate(
+    { bottom: if visible then "-#{bottomPixels}px" else "-#{$addItemDialog.css 'border-bottom-width'}" },
     { duration: 250, complete: onComplete }
   )
 $('#addItemButton').click -> toggleAddItemDialog()


Made `debug()` and `addSource()` chainable

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -14,6 +14,7 @@
     error = console.error.bind console
   error = errFunc if typeof errFunc is 'function'
   log 'Debugging Flash Loader'
+  @

 PLATFORM = process.platform
 FILENAME = switch PLATFORM
@@ -107,6 +108,7 @@
     else
       error "Invalid version string for #{location}: #{version}"
   flashSources.push o
+  @

 getPath = (loc) ->
   if loc?
@@ -122,7 +124,8 @@
         errMsg = "Could not load '#{FILENAME}' from: \n#{loc}"
   else
     if flashSources.length is 0
-      errMsg = 'No source has been added. Please call `addSource()` to add the location where Flash Player can be found.'
+      errMsg = 'No source has been added. Please call `addSource()` \
+                to add the location where Flash Player can be found.'
     for src, i in flashSources
       flashPath = getPath src.loc
       usingIndex = i
@@ -133,7 +136,7 @@
 load = ->
   flashPath = getPath()
   if flashPath?
-    log "Loading Pepper Flash Player from: \n#{flashPath}"
+    log "Loading Pepper Flash Player: \n#{flashPath}"
     app.commandLine.appendSwitch 'ppapi-flash-path', flashPath
     # Note: the 'ppapi-flash-version' switch is used by Google Chrome to decide
     # which flash player to load (it'll choose the newest one), and display in


Added some new events for ga tracks

Added some new events for ga tracks

--- app/assets/javascripts/classes/player.js.coffee
+++ app/assets/javascripts/classes/player.js.coffee
@@ -139,16 +139,37 @@

     Youtube.pauseVideo()

+    _gaq.push [
+      '_trackEvent'
+      'Tracks'
+      'Pause'
+      track.get('artist') + ' - ' + track.get('title')
+    ]
+
   resume: ->
     @playing = on

     Youtube.playVideo()
+
+    _gaq.push [
+      '_trackEvent'
+      'Tracks'
+      'Resume'
+      track.get('artist') + ' - ' + track.get('title')
+    ]

   stop: ->
     @playing = off

+    _gaq.push [
+      '_trackEvent'
+      'Tracks'
+      'Stop'
+      track.get('artist') + ' - ' + track.get('title')
+    ]
+
   next: ->
-    if @history.size() >= 1
+    if @history.size() > 0
       currentTrackResults = @tracks.where
         mbid: @history.last().get('mbid')

@@ -162,6 +183,13 @@
       track = @tracks.at(0)

     @play(track)
+
+    _gaq.push [
+      '_trackEvent'
+      'Tracks'
+      'Next'
+      track.get('artist') + ' - ' + track.get('title')
+    ]

   previous: ->
     #console.log @history
@@ -172,6 +200,13 @@
       track = @tracks.at(0)

     @play track, off
+
+    _gaq.push [
+      '_trackEvent'
+      'Tracks'
+      'Previous'
+      track.get('artist') + ' - ' + track.get('title')
+    ]

   doScrobble: (artist, track, image) ->
     $.ajax


Add a space between value and unit.

Add a space between value and unit.

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -64,13 +64,13 @@
       when '%'
         return @percentage_to_string(x)
       when 'MJ'
-        return "#{value}#{@scaling_in_words(pow, 'joules')}"
+        return "#{value} #{@scaling_in_words(pow, 'joules')}"
       when 'PJ'
-        return "#{value}#{@scaling_in_words(pow + 3, 'joules')}"
+        return "#{value} #{@scaling_in_words(pow + 3, 'joules')}"
       when 'MW'
-        return "#{value}#{@scaling_in_words(pow, 'watt')}"
+        return "#{value} #{@scaling_in_words(pow, 'watt')}"
       when 'MT'
-        return "#{value}#{@scaling_in_words(pow + 2, 'ton')}"
+        return "#{value} #{@scaling_in_words(pow + 2, 'ton')}"
       when 'euro'
         return @euros_to_string x, true
       when 'bln_euro'


fixing product row tap handler - removed fallback for desktop

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -14,20 +14,9 @@

         if gon.page is 'stores_show'
             header_fix()
-            navigate_to = (store_slug, product_slug) ->
-                destination = "#{store_slug}/#{product_slug}"
-                # add mobile layout param for desktop
-                destination += '?layout=mobile' if gon.layout is 'mobile'
-                location.href = destination

-            navigate_to_store = (product_slug) ->
-                navigate_to(gon.store_slug, product_slug)
-
-            ($ '.products')
-                .on('tap', '.product', ->
-                    navigate_to_store(($ @).data('slug')))
-                .on('click', '.product', ->
-                    navigate_to_store(($ @).data('slug'))) if gon.layout is 'mobile'
+            ($ '.products').on('tap', '.product', ->
+                location.href = "#{gon.store_slug}/#{($ @).data('slug')}")

         if gon.page is 'products_show'
             header_fix()


ignore quiz dates

ignore quiz dates

--- app/lib/router.coffee
+++ app/lib/router.coffee
@@ -123,13 +123,14 @@
         quiz = @data()
         return unless quiz?

+        # TODO: we're not doing date-based quizzes anymore, delete this?
         # Check that the quiz has started and hasn't run out
-        now = new Date()
-        unless (quiz.startDate < now and now < quiz.endDate)
-          @redirect 'lobby'
-          FlashMessages.sendError 'Denne quiz er ikke tilgængelig'
-          pause()
-          return
+        # now = new Date()
+        # unless (quiz.startDate < now and now < quiz.endDate)
+        #   @redirect 'lobby'
+        #   FlashMessages.sendError 'Denne quiz er ikke tilgængelig'
+        #   pause()
+        #   return

         unless Session.get 'currentQuestion'
           Session.set 'currentQuestion', 0