Fix using `core:move-up` and `core:move-down`

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -39,10 +39,10 @@
   registerScrollCommands: ->
     @disposables.add(atom.commands.add(@element, {
       'core:move-up': =>
-        @element.scrollTop -= window.offsetHeight / 20
+        @element.scrollTop -= document.body.offsetHeight / 20
         return
       'core:move-down': =>
-        @element.scrollTop += window.offsetHeight / 20
+        @element.scrollTop += document.body.offsetHeight / 20
         return
       'core:page-up': =>
         @element.scrollTop -= @element.offsetHeight


Fix auth plugin docs

Fix auth plugin docs

--- lib/supplier/plugins/auth.coffee
+++ lib/supplier/plugins/auth.coffee
@@ -10,7 +10,11 @@
 crypto = require "crypto"
 json = require "json-output"

-
+#### Required plugins:
+#
+# * [__Express__](express.html).
+# * [__Mongoose__](mongoose.html).
+#
 #### Can be configured:
 #
 # * __expire__ — Token expiration.
@@ -59,8 +63,7 @@
             callback()

         User = connection.model "users", UserSchema
-
-         # Populate user
+
         app.use (req, res, callback) ->
             authHeader = req.get "Authorization"
             return callback() unless authHeader
@@ -73,12 +76,10 @@
                 return callback() if err or !user
                 return callback() if expires.expired user.tokenExpire

-                req.user =
-                    username = user.username
+                req.user = username: user.username

                 callback()
-
-        # Authenticate
+
         app.post "/auth-token", (req, res) ->
             errorCallback = (err, status) ->
                 res.json json.error(err), status or 500


Fixed #34

Fixed #34

--- lib/views/claim-core-view.coffee
+++ lib/views/claim-core-view.coffee
@@ -39,18 +39,25 @@
       @claimPromise = spark.claimCore deviceID
       @setLoading true
       @claimPromise.done (e) =>
-        if !@claimPromise
-          return
+        @setLoading false
+        if e.ok
+          if !@claimPromise
+            return

-        # Set current core in settings
-        SettingsHelper.setCurrentCore e.id, e.id
+          # Set current core in settings
+          SettingsHelper.setCurrentCore e.id, e.id

-        # Refresh UI
-        atom.workspaceView.trigger 'spark-dev:update-core-status'
-        atom.workspaceView.trigger 'spark-dev:update-menu'
+          # Refresh UI
+          atom.workspaceView.trigger 'spark-dev:update-core-status'
+          atom.workspaceView.trigger 'spark-dev:update-menu'

-        @claimPromise = null
-        @close()
+          @claimPromise = null
+          @close()
+        else
+          @miniEditor.hiddenInput.removeAttr 'disabled'
+          @miniEditor.addClass 'editor-error'
+          @showError e.errors
+          @claimPromise = null

       , (e) =>
         @setLoading false


KodingLogger.test: tests for connect and close

--- workers/social/lib/social/models/kodinglogger.test.coffee
+++ workers/social/lib/social/models/kodinglogger.test.coffee
@@ -112,6 +112,36 @@
       expect restrictedQuery
         .to.exist
         .to.be.equal "(#{identifiers.join ' OR '}) AND #{query}"
+
+      done()
+
+
+  describe '# connect', ->
+
+    it 'should be able to connect to papertrail', (done) ->
+
+      expect(KodingLogger.logger).to.not.exist
+      expect(KodingLogger.pt).to.not.exist
+
+      KodingLogger.connect()
+
+      expect(KodingLogger.logger).to.exist
+      expect(KodingLogger.pt).to.exist
+
+      KodingLogger.pt.on 'connect', -> done()
+
+
+  describe '# close', ->
+
+    it 'should close existing connection from papertrail', (done) ->
+
+      expect(KodingLogger.logger).to.exist
+      expect(KodingLogger.pt).to.exist
+
+      KodingLogger.close()
+
+      expect(KodingLogger.logger).to.not.exist
+      expect(KodingLogger.pt).to.not.exist

       done()


implemented https

implemented https

--- src/private/server.coffee
+++ src/private/server.coffee
@@ -76,7 +76,7 @@
 # Middleware to add render data
 app.use (req, res, next) ->
     res.locals.user = req.user
-    res.locals.baseUrl = req.protocol + '://' + req.get('host')
+    res.locals.baseUrl = 'https://' + req.get('host')
     do next

 app.get '/', (req, res) ->


Minor vimeo fix

Minor vimeo fix

--- src/provider/vimeo.coffee
+++ src/provider/vimeo.coffee
@@ -64,7 +64,7 @@
                     when '270p' then 240
                     else throw new Error("Unrecognized quality #{file.quality}")

-                videos[quality] = source
+                videos[quality].push(source)

             return videos
         catch e


bug fix

bug fix

--- lib/assets/javascripts/webrtc_rails/main.js.coffee
+++ lib/assets/javascripts/webrtc_rails/main.js.coffee
@@ -320,7 +320,7 @@
               @onWebRTCReconnected()

     peer.addStream(@_localStream)
-    peer.onaddstream = (obj) =>
+    peer.onaddstream = (event) =>
       @remoteOutput.src = window.URL.createObjectURL(event.stream)

     peer


Added nvd3 library to common.js file

Added nvd3 library to common.js file

--- frontend/coffeescript/common.coffee
+++ frontend/coffeescript/common.coffee
@@ -16,6 +16,8 @@
 		# Backbone plugins
 		backbone_modal: 'backbone.modal'
 		marionette: 'marionette'
+
+		nvd3: 'nv.d3'

 	shim:
 		jqueryui:
@@ -42,6 +44,10 @@
 		'd3':
 			exports: 'd3'

+		'nvd3':
+			deps: [ 'd3' ]
+			exports: 'nv'
+
 		# Leaflet & associated plugins
 		leaflet_heatmap:
 			deps: [ 'leaflet', 'heatmap' ]


account: change logic for showing update payment method button

--- client/Account/views/billing.coffee
+++ client/Account/views/billing.coffee
@@ -83,24 +83,11 @@
     @paymentMethodWrapper.addSubView new KDHeaderView
       title : 'Payment Method'

-
-
     paymentController.creditCard (err, cc) =>

       card = null  if err?
-
-      # intentional if/else ifs
-      # to denote the edge cases - SY
-      if subscription.provider is 'paypal'
-        card = null

-      else if subscription.provider is 'stripe' and subscription.planTitle is 'free'
-        card = null
-
-      else if subscription.provider is 'koding'
-        card = null
-
-      else
+      if subscription.provider is 'stripe' and subscription.state isnt 'expired'
         card = cc

       @putPaymentMethodView card


Kloud.info: resolveRequestingInfo: check for existence on specific machine id too

--- client/Main/kite/kites/kloud.coffee
+++ client/Main/kite/kites/kloud.coffee
@@ -50,7 +50,7 @@

   resolveRequestingInfos: (machineId, info)->

-    @requestingInfo?[machineId].forEach ({ resolve }) ->
+    @requestingInfo?[machineId]?.forEach ({ resolve }) ->
       resolve info

     @requestingInfo[machineId] = null


Join group after login if groupName exists

--- client/app/MainApp/KD.extend.coffee
+++ client/app/MainApp/KD.extend.coffee
@@ -32,14 +32,16 @@
       # if there is callback and we want to try again
       if callback? and tryAgain
         unless KD.lastFuncCall
+          KD.lastFuncCall = callback
+
           mainController = KD.getSingleton("mainController")
           mainController.once "accountChanged.to.loggedIn", =>
-            if groupName and KD.isLoggedIn()
-              @joinGroup_ groupName, (err)=>
-                return @notify_ "Joining #{groupName} group failed", "error"  if err
-                KD.lastFuncCall?()
-                KD.lastFuncCall = null
-        KD.lastFuncCall = callback
+            if KD.isLoggedIn()
+              KD.lastFuncCall?()
+              KD.lastFuncCall = null
+              if groupName
+                @joinGroup_ groupName, (err) =>
+                  return @notify_ "Joining #{groupName} group failed", "error"  if err
     else if groupName
       @joinGroup_ groupName, (err)=>
         return @notify_ "Joining #{groupName} group failed", "error"  if err


improving import ux (first steps)

improving import ux (first steps)

--- client/app/views/import_view.coffee
+++ client/app/views/import_view.coffee
@@ -93,52 +93,58 @@
         # Amount of elements to import.
         counter = @alarmList.collection.length + @eventList.collection.length

-        # When an element failed to import, an error message is displayed.
         # Finish function is called when there is no more element to save.
         onFaillure = (model) =>
             counter = counter - 1
-            alert t 'some event fail to save'
             finish() if counter is 0
-
-        # When an element is successfully imported, it is added to the current
-        # calendar view.
-        # Finish function is called when there is no more element to save.
-        onSuccess = (model) =>
-            switch model.constructor
-                when Event then app.events.add model
-                when Alarm then app.alarms.add model
-
-            counter = counter - 1
-            finish() if counter is 0
-
-        # When import is finished, the import form is reset and the calendar
-        # view is displayed.
-        finish = =>
-            @$(".confirmation").fadeOut()
-            @$(".results").slideUp =>
-                @$(".import-form").fadeIn()
-                @confirmButton.html t 'confirm import'
-            @alarmList.collection.reset()
-            @eventList.collection.reset()
-            app.router.navigate "calendar", true

         # Show loading spinner.
         @confirmButton.html '        '
         @confirmButton.spin 'tiny'

         # Save every imported alarms to the database.
-        @alarmList.collection.each (alarm) ->
+        async.eachSeries @alarmList.collection.models, (alarm, callback) ->
             alarm.set 'tags', [calendar]
             alarm.save null,
-                success: onSuccess
-                error: onFaillure
+                success: (model) ->
+                    # When an element is successfully imported, it is added to
+                    # the current calendar view.
+                    app.alarms.add model
+                    callback()

-        # Save every imported events to the database.
-        @eventList.collection.each (event) ->
-            event.set 'tags', [calendar]
-            event.save null,
-                success: onSuccess
-                error: onFaillure
+                error: ->
+                    # When an element failed to import, an error message is
+                    # displayed.
+                    alert t 'some alarm failed to save'
+                    callback()
+        , (err) ->
+            # Save every imported events to the database.
+            async.eachSeries @eventList.collection.models, (event, callback) ->
+                event.set 'tags', [calendar]
+                event.save null,
+                    success: (model) ->
+                        # When an element is successfully imported, it is added
+                        # to the current calendar view.
+                        app.events.add model
+                        callback()
+
+                    error: ->
+                        # When an element failed to import, an error message is
+                        # displayed.
+                        alert t 'some event failed to save'
+                        callback()
+
+            , (err) ->
+                # When import is finished, the import form is reset and the
+                # calendar view is displayed.
+                @$(".confirmation").fadeOut()
+                @$(".results").slideUp =>
+                    @$(".import-form").fadeIn()
+                    @confirmButton.html t 'confirm import'
+                @alarmList.collection.reset()
+                @eventList.collection.reset()
+                app.router.navigate "calendar", true
+

     onCancelImportClicked: ->
         @$(".confirmation").fadeOut()


Updating login_defs resource snippet

Updating login_defs resource snippet

--- snippets/login_defs.cson
+++ snippets/login_defs.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
-  'Describe login_def':
-    'prefix': 'login_def'
-    'body': "# http://inspec.io/docs/reference/resources/login_def/\ndescribe login_def('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+  'Describe login_defs':
+    'prefix': 'login_defs'
+    'body': "# http://inspec.io/docs/reference/resources/login_def/\ndescribe login_defs do\n  it('${1:setting}') { should${2:_not} eq '${3:value}' }${4}\nend"


Removed bad js script

Removed bad js script

--- scripts.coffee
+++ scripts.coffee
@@ -1,3 +1,3 @@
-$ ->                                                                                #Load fancybox iframes to browser size
+$ ->
   # Inititate the datatable
   $('.datatable').DataTable()


fixed axis lines for discrete axes

fixed axis lines for discrete axes

--- src/viz/types/helpers/graph/includes/svg.coffee
+++ src/viz/types/helpers/graph/includes/svg.coffee
@@ -326,39 +326,41 @@

       for line in userLines
         d = if validObject(line) then line.position else line
-        unless isNaN(d)
-          d = parseFloat(d)
-          if d > domain[0] and d < domain[1]
-            d = unless validObject(line) then {"position": d} else line
-            d.coords =
-              line: vars[axis].scale.viz(d.position)
-            lineData.push d
-            if d.text
-
-              d.axis    = axis
-              d.padding = vars[axis].lines.font.padding.value * 0.5
-              d.align   = vars[axis].lines.font.align.value
-
-              position = vars[axis].lines.font.position.text
-              textPad  = if position is "middle" then 0 else d.padding * 2
-              textPad  = -textPad if position is "top"
-
-              if axis.indexOf("x") is 0
-                textPos  = if d.align is "left" then vars.axes.height else if d.align is "center" then vars.axes.height/2 else 0
-                textPos -= d.padding * 2 if d.align is "left"
-                textPos += d.padding * 2 if d.align is "right"
-              else
-                textPos  = if d.align is "left" then 0 else if d.align is "center" then vars.axes.width/2 else vars.axes.width
-                textPos -= d.padding * 2 if d.align is "right"
-                textPos += d.padding * 2 if d.align is "left"
-
-              d.coords.text = {}
-              d.coords.text[if axis.indexOf("x") is 0 then "y" else "x"] = textPos
-              d.coords.text[axis] = vars[axis].scale.viz(d.position)+textPad
-
-              d.transform = if axis.indexOf("x") is 0 then "rotate(-90,"+d.coords.text.x+","+d.coords.text.y+")" else null
-
-              textData.push d
+        if axis is vars.axes.discrete
+          valid = domain.indexOf(d) >= 0
+        else
+          valid = d >= domain[0] and d <= domain[1]
+        if valid
+          d = unless validObject(line) then {"position": d} else line
+          d.coords =
+            line: vars[axis].scale.viz(d.position)
+          lineData.push d
+          if d.text
+
+            d.axis    = axis
+            d.padding = vars[axis].lines.font.padding.value * 0.5
+            d.align   = vars[axis].lines.font.align.value
+
+            position = vars[axis].lines.font.position.text
+            textPad  = if position is "middle" then 0 else d.padding * 2
+            textPad  = -textPad if position is "top"
+
+            if axis.indexOf("x") is 0
+              textPos  = if d.align is "left" then vars.axes.height else if d.align is "center" then vars.axes.height/2 else 0
+              textPos -= d.padding * 2 if d.align is "left"
+              textPos += d.padding * 2 if d.align is "right"
+            else
+              textPos  = if d.align is "left" then 0 else if d.align is "center" then vars.axes.width/2 else vars.axes.width
+              textPos -= d.padding * 2 if d.align is "right"
+              textPos += d.padding * 2 if d.align is "left"
+
+            d.coords.text = {}
+            d.coords.text[if axis.indexOf("x") is 0 then "y" else "x"] = textPos
+            d.coords.text[axis] = vars[axis].scale.viz(d.position)+textPad
+
+            d.transform = if axis.indexOf("x") is 0 then "rotate(-90,"+d.coords.text.x+","+d.coords.text.y+")" else null
+
+            textData.push d

       lines = lineGroup.selectAll "line.d3plus_graph_"+axis+"line"
         .data lineData, (d) -> d.position
@@ -413,7 +415,7 @@
           .attr "transform"  , (d) -> d.transform
           .attr "width", (d) -> getText(d).width + (d.padding * 2)
           .attr "height", (d) -> getText(d).height + (d.padding * 2)
-          .attr "fill", vars.axes.background.color
+          .attr "fill",  if vars.axes.background.color isnt "transparent" then vars.axes.background.color else "white"

       rectData = if vars[axis].lines.font.background.value then textData else []


Fix to color loading

Fix to color loading

--- src/goo/loaders/handlers/MeshDataHandler.coffee
+++ src/goo/loaders/handlers/MeshDataHandler.coffee
@@ -142,7 +142,7 @@
 			if data.colors?.length>0
 				if compression?
 					offset = 0
-					scale = 255 / (compression.compressedColorsRange + 1)
+					scale = 1 / (compression.compressedColorsRange + 1)
 					JsonUtils.fillAttributeBufferFromCompressedString(data.colors, meshData, MeshData.COLOR,
 						[scale, scale, scale, scale], [offset,offset, offset, offset])
 				else


Hack to make up for bad cache (don't update meshdata, it's not changed)

--- src/goo/loaders/handlers/MeshDataHandler.coffee
+++ src/goo/loaders/handlers/MeshDataHandler.coffee
@@ -14,77 +14,88 @@
 pu,
 _) ->

+
+	# Perhaps move to utils later
+	_getTypedArray = (bindata, format, range)->
+		if format == 'float32'
+			new Float32Array(bindata.slice(range[0], range[1]))
+		else if format == 'uint16'
+			new Uint16Array(bindata.slice(range[0], range[1]))
+
+
+
 	class MeshDataHandler extends ConfigHandler
 		@_register('mesh')

-
+		constructor: (@world, @getConfig, @updateObject, @options)->
+			@_objects = {}
+
+
 		_create: (meshConfig)->
 			# We do everything in update instead

-			return meshData
-
 		update: (ref, meshConfig)->
+			if not @_objects[ref]
+				if meshConfig.binary_data
+					@getConfig(meshConfig.binary_data).then (bindata)=>
+						if not bindata then throw new Error("Binary mesh data was empty")
+						@_createMeshData(meshConfig, bindata)
+						.then (meshData)=>
+							@_objects[ref] = meshData
+				else
+					@_createMeshData(meshConfig, null)
+					.then (meshData)=>
+						@_objects[ref] = meshData
+			else
+				pu.createDummyPromise(@_objects[ref])
+
+		remove: (ref)->
+			# Do nothing, we didn't save anything
+
+
+		# Returns promise that resolves with the created meshdata object
+		_createMeshData: (meshConfig, bindata)->

 			if meshConfig.compression and meshConfig.compression.compressed
 				compression =
 					compressedVertsRange: meshConfig.compression.compressedVertsRange or (1 << 14) - 1 #int
 					compressedColorsRange: meshConfig.compression.compressedColorsRange or (1 << 8) - 1 #int
 					compressedUnitVectorRange: meshConfig.compression.compressedUnitVectorRange or (1 << 10) - 1 #int
-
-
-			getTypedArray = (bindata, format, range)->
-				if format == 'float32'
-					new Float32Array(bindata.slice(range[0], range[1]))
-				else if format == 'uint16'
-					new Uint16Array(bindata.slice(range[0], range[1]))
-
-			createMeshData = (bindata)=>
-
-				if bindata
-					indices = getTypedArray(bindata, 'uint16', meshConfig.indices)
-					meshConfig.indices = indices
-
-					textureCoords = []
-					for layer, idx in meshConfig.textureCoords
-						textureCoords[idx] = getTypedArray(bindata, 'float32', layer)
-					meshConfig.textureCoords = textureCoords
-
-					meshConfig.vertices = getTypedArray(bindata, 'float32', meshConfig.vertices)
-
-					meshConfig.normals = getTypedArray(bindata, 'float32', meshConfig.normals)
-
-
-				if meshConfig.type == 'SkinnedMesh'
-					meshData = @_parseMeshData(meshConfig.data or meshConfig, 4, 'SkinnedMesh', compression)
-					meshData.type = MeshData.SKINMESH
-				else
-					meshData = @_parseMeshData(meshConfig.data or meshConfig, 0, 'Mesh', compression)
-					meshData.type = MeshData.MESH
-
-				if meshConfig.pose
-					skelRef = meshConfig.pose
-					@getConfig(skelRef).then (skelConfig)=>
-						@updateObject(skelRef, skelConfig).then (skeleton)=>
-							pose = new SkeletonPose(skeleton)
-							pose.setToBindPose()
-							meshData.currentPose = pose
-							return meshData
-				else
-					pu.createDummyPromise(meshData)
-
-
-			if meshConfig.binary_data
-				@getConfig(meshConfig.binary_data).then (bindata)=>
-					if not bindata then throw new Error("Binary mesh data was empty")
-					createMeshData(bindata)
+
+			if bindata
+				# Binary data is stored in a separate file. Experimental format.
+
+				indices = _getTypedArray(bindata, 'uint16', meshConfig.indices)
+				meshConfig.indices = indices
+
+				textureCoords = []
+				for layer, idx in meshConfig.textureCoords
+					textureCoords[idx] = _getTypedArray(bindata, 'float32', layer)
+				meshConfig.textureCoords = textureCoords
+
+				meshConfig.vertices = _getTypedArray(bindata, 'float32', meshConfig.vertices)
+
+				meshConfig.normals = _getTypedArray(bindata, 'float32', meshConfig.normals)
+
+			if meshConfig.type == 'SkinnedMesh'
+				meshData = @_parseMeshData(meshConfig.data or meshConfig, 4, 'SkinnedMesh', compression)
+				meshData.type = MeshData.SKINMESH
 			else
-				createMeshData(null)
-
-		remove: (ref)->
-			# Do nothing, we didn't save anything
-
-
-		# Translated into coffeescript from goo/loaders/MeshLoader.js
+				meshData = @_parseMeshData(meshConfig.data or meshConfig, 0, 'Mesh', compression)
+				meshData.type = MeshData.MESH
+
+			if meshConfig.pose
+				skelRef = meshConfig.pose
+				@getConfig(skelRef).then (skelConfig)=>
+					@updateObject(skelRef, skelConfig).then (skeleton)=>
+						pose = new SkeletonPose(skeleton)
+						pose.setToBindPose()
+						meshData.currentPose = pose
+						return meshData
+			else
+				pu.createDummyPromise(meshData)
+
+
 		# Returns MeshData object
 		_parseMeshData: (data, weightsPerVert, type, compression)->
 			vertexCount = data.vertexCount # int


simpler has_options check

simpler has_options check

--- app/assets/javascripts/models/monitoring_command.js.coffee
+++ app/assets/javascripts/models/monitoring_command.js.coffee
@@ -9,11 +9,7 @@
       cmd.replace(/monitoring\//, "")

     has_options: ->
-      opts = @get("options")
-      if ! opts? || _.keys(opts).length == 0
-        return false
-      return true
-
+      opts? && ! _.isEmpty(@get("options"))

   class exports.MonitoringCommandList extends Stark.Collection
     model: exports.MonitoringCommand


Fiddled with new lines.

Fiddled with new lines.

 * Changed new lines in `bin/tail.coffee`.

--- bin/tail.coffee
+++ bin/tail.coffee
@@ -137,7 +137,7 @@

 writeExceptions = (exceptions, message) ->
   exception = exceptions.shift()
-  process.stdout.write "  #{message} #{new Array((78 - message.length) / 2).join(" -")}\n"
+  process.stdout.write "\n  #{message} #{new Array((78 - message.length) / 2).join(" -")}\n"
   if exception.location
     location = exception.location
     process.stdout.write "  #{location.file}:#{location.line}.\n"
@@ -150,7 +150,6 @@
     process.stdout.write "#{json}\n"
   for element in exception.stack
     process.stdout.write "    at #{element.method} (#{element.file})\n"
-  process.stdout.write "\n"
   if exceptions.length
     writeExceptions(exceptions, "Nested exception")

@@ -167,15 +166,18 @@
           process.stdout.write JSON.stringify(output, null, 2)
           process.stdout.write "\n"
         else
+          separator = ""
           for record in output
+            process.stdout.write separator
+            separator = "\n"
             header = "#{tz("%Y/%m/%d %H:%M:%S", record.date)} on #{record.host} "
             header += new Array(80 - header.length).join("-")
             process.stdout.write "#{header}\n\n"
-            process.stdout.write "#{record.message}\n\n"
+            process.stdout.write "#{record.message}\n"
             if record.json
               if record.exceptions
                 record.json.stderr = "[Uncaught exception: See below.]"
               json = inspect(record.json, false, 1000).replace(/^(\s*\S.*)$/mg, "  $1")
-              process.stdout.write "#{json}\n\n"
+              process.stdout.write "#{json}\n"
             if record.exceptions
               writeExceptions(record.exceptions, "Uncaught exception")


Code improvement

Code improvement

--- packages/rocketchat-lib/client/lib/openRoom.coffee
+++ packages/rocketchat-lib/client/lib/openRoom.coffee
@@ -5,12 +5,13 @@

 	Meteor.defer ->
 		currentTracker = Tracker.autorun (c) ->
+			user = Meteor.user()
+			if (user? and not user.username?) or (not user? and !RocketChat.authz.hasAllPermission('preview-c-room'))
+				BlazeLayout.render 'main'
+				return
+
 			if RoomManager.open(type + name).ready() isnt true
 				BlazeLayout.render 'main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' }
-				return
-
-			user = Meteor.user()
-			if not user?.username and !RocketChat.authz.hasAllPermission('preview-c-room')
 				return

 			currentTracker = undefined


SideBar: managedvms can not be edited, initialized or destroyed

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -92,8 +92,14 @@
     if @getStackUnreadCount()
       menuItems['Update'] = { callback }

-    ['Edit', 'Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) =>
-      menuItems[name] = { callback }
+    managedVM = if @props.stack.get('title').indexOf('Managed VMs') > -1
+    then yes else no
+
+    if managedVM
+    then menuItems['VMs'] = { callback }
+    else
+      ['Edit', 'Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) =>
+        menuItems[name] = { callback }

     { top } = findDOMNode(this).getBoundingClientRect()


sidebarstacksection: update menu options

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -125,49 +125,48 @@
     callback = @bound 'onMenuItemClick'

     menuItems = {}
-
-    remote.api.JStackTemplate.one { _id: @props.stack.get 'baseStackId' }, (err, template) =>
-
-      { originId, accessLevel } = template
-
-      if @getStackUnreadCount()
-        menuItems['Update'] = { callback }
-
-      if @props.stack.getIn ['config', 'remoteDetails', 'originalUrl']
-        menuItems['Open on GitLab'] = { callback }
-
-      managedVM = @props.stack.get('title').indexOf('Managed VMs') > -1
-
-      console.log 'whoami()._id is originId', whoami()._id, originId
-
-      if managedVM
-        menuItems['VMs'] = { callback }
-      else if @props.stack.get 'disabled'
-        # because of disabled stack's baseTemplate came undefined
-        # no need to show Edit, Clone, Reinitialize options
-        ['VMs', 'Destroy VMs'].forEach (name) ->
-          menuItems[name] = { callback }
+    template = @props.template
+
+    { originId, accessLevel } = template.toJS()  if template
+
+    if @getStackUnreadCount()
+      menuItems['Update'] = { callback }
+
+    if @props.stack.getIn ['config', 'remoteDetails', 'originalUrl']
+      menuItems['Open on GitLab'] = { callback }
+
+    managedVM = @props.stack.get('title').indexOf('Managed VMs') > -1
+
+    if managedVM
+      menuItems['VMs'] = { callback }
+    else if @props.stack.get 'disabled'
+      # because of disabled stack's baseTemplate came undefined
+      # no need to show Edit, Clone, Reinitialize options
+      ['VMs', 'Destroy VMs'].forEach (name) ->
+        menuItems[name] = { callback }
+    else
+      if isAdmin() or (template and whoami()._id is originId)
+        menuItems['Edit'] = { callback }
+        menuItems['Clone'] = { callback }  if canCreateStacks()
       else
-        if isAdmin() or @props.stack.get('accessLevel') is 'private'
-          menuItems['Edit'] = { callback }
-          menuItems['Clone'] = { callback }  if canCreateStacks()
-        else
-          menuItems['View Stack'] = { callback }
-        ['Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) ->
-          menuItems[name] = { callback }
-        if isAdmin() and not isDefaultTeamStack @props.stack.get 'baseStackId'
-          menuItems['Make Team Default'] = { callback }
-        if template and whoami()._id is originId
-          menuItems['Share With Team'] = { callback }  if accessLevel is 'private'
-          menuItems['Make Private'] = { callback }  if accessLevel is 'group'
-
-      { top } = findDOMNode(this).getBoundingClientRect()
-
-      menuOptions = { cssClass: 'SidebarMenu', x: 36, y: top + 31 }
-
-      MENU = new kd.ContextMenu menuOptions, menuItems
-
-      MENU.once 'KDObjectWillBeDestroyed', -> kd.utils.wait 50, -> MENU = null
+        menuItems['View Stack'] = { callback }
+      ['Reinitialize', 'VMs', 'Destroy VMs'].forEach (name) ->
+        menuItems[name] = { callback }
+      if isAdmin() and not isDefaultTeamStack @props.stack.get 'baseStackId'
+        menuItems['Make Team Default'] = { callback }
+
+      if template and whoami()._id is originId
+        console.log 'accessLevel', accessLevel
+        menuItems['Share With Team'] = { callback }  if accessLevel is 'private'
+        menuItems['Make Private'] = { callback }  if accessLevel is 'group'
+
+    { top } = findDOMNode(this).getBoundingClientRect()
+
+    menuOptions = { cssClass: 'SidebarMenu', x: 36, y: top + 31 }
+
+    MENU = new kd.ContextMenu menuOptions, menuItems
+
+    MENU.once 'KDObjectWillBeDestroyed', -> kd.utils.wait 50, -> MENU = null

   renderStackUpdatedWidget: ->
@@ -212,14 +211,15 @@
     className  = 'SidebarStackSection'
     className += ' active'  if @state.selectedTemplateId is @props.stack.get 'baseStackId'

-
     <SidebarSection
       ref='sidebarSection'
       className={kd.utils.curry className, @props.className}
       title={@props.stack.get 'title'}
       onTitleClick={@bound 'onTitleClick'}
       secondaryLink=''
+      baseStackId={@props.stack.get 'baseStackId'}
       unreadCount={@getStackUnreadCount()}
+      originalTemplateUpdate={@props.stack.getIn ['config', 'needUpdate']}
       unreadCountClickHandler={@bound 'unreadCountClickHandler'}
       >
       {@renderMachines()}


GroupGeneralSettings: do not set stack template if not provided [#90626732]

--- client/dashboard/lib/views/groupgeneralsettingsview.coffee
+++ client/dashboard/lib/views/groupgeneralsettingsview.coffee
@@ -113,7 +113,9 @@
     group      = @getData()

     if formData.stackTemplates is "none"
-      formData.stackTemplates = []
+      formData.stackTemplates = [ ]
+    else if not formData.stackTemplates?
+      delete formData.stackTemplates
     else
       formData.stackTemplates = [ formData.stackTemplates ]

@@ -169,5 +171,3 @@
           Stack.setDefaultValue "none"

   pistachio:-> "{{> @settingsForm}}"
-
-


Call the given callback correctly

Call the given callback correctly

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -84,8 +84,8 @@
             modal = new VmDangerModalView
               name     : vmInfo.hostnameAlias
               title    : "Destroy '#{hostnameAlias}'"
-              action   : 'Destroy my VM'
-              callback : (callback) =>
+              action   : "Destroy my VM"
+              callback : =>
                 deleteVM vm, callback
                 new KDNotificationView title:'Successfully destroyed!'
                 modal.destroy()


Model isn't required if message is given

--- client/lanes/components/shared/NetworkActivityOverlay.cjsx
+++ client/lanes/components/shared/NetworkActivityOverlay.cjsx
@@ -1,7 +1,7 @@
 class Lanes.Components.NetworkActivityOverlay extends Lanes.React.Component

     propTypes:
-        model:   Lanes.PropTypes.Model.isRequired
+        model:   Lanes.PropTypes.Model
         message: React.PropTypes.string
         timeout: React.PropTypes.number
         visible: React.PropTypes.bool


fix #114

fix #114

--- src/modules/multi-cursor.coffee
+++ src/modules/multi-cursor.coffee
@@ -128,6 +128,7 @@
     this.emit(MultiCursor.events.CURSOR_MOVED, cursor)

   _updateCursor: (cursor) ->
+    @quill.editor.checkUpdate()
     [leaf, offset] = @quill.editor.doc.findLeafAt(cursor.index, true)
     guide = @container.ownerDocument.createElement('span')
     if leaf?
@@ -141,6 +142,7 @@
     DOM.removeNode(guide)
     DOM.normalize(leaf.node.parentNode) if didSplit
     cursor.dirty = false
+    @quill.editor.selection.update('silent')

 module.exports = MultiCursor


Fix triggering twice for `excuse`

Fix triggering twice for `excuse`

Also fixing the mismatch between the documentation and the regex patterns.

--- src/scripts/excuse.coffee
+++ src/scripts/excuse.coffee
@@ -10,6 +10,7 @@
 # Commands:
 #   hubot developer excuse me - Get a random developer excuse
 #   hubot developer excuse - Get a random developer excuse
+#   hubot excuse - Get a random developer excuse
 #
 #   hubot designer excuse me - Get a random designer excuse
 #   hubot designer excuse - Get a random designer excuse
@@ -48,7 +49,7 @@
 ]

 module.exports = (robot) ->
-  robot.respond /developer excuse|excuse(?: me)?/i, (msg) ->
+  robot.respond /(?:developer excuse|excuse)(?: me)?/i, (msg) ->
     robot.http("http://developerexcuses.com")
       .get() (err, res, body) ->
         matches = body.match /<a [^>]+>(.+)<\/a>/i
@@ -56,5 +57,5 @@
         if matches and matches[1]
           msg.send matches[1]

-  robot.respond /designer excuse|excuse(?: me)?/i, (msg) ->
+  robot.respond /designer excuse(?: me)?/i, (msg) ->
     msg.send msg.random(DESIGNER_EXCUSES)


Add support for comments

Add support for comments

--- src/scripts/reddit.coffee
+++ src/scripts/reddit.coffee
@@ -1,4 +1,4 @@
-# reddit (me) <reddit> [top] - Lookup reddit topic
+# reddit (me) <reddit> [limit] - Lookup reddit topic

 # Topic lookup from reddit
 # Enrique Vidal - enrique@cloverinteractive.com
@@ -14,9 +14,10 @@
       message.send text

   lookup_reddit = (message, response_handler)->
-    top       = parseInt message.match[3]
-    reddit    = message.match[2]
-    location  = lookup_site + "r/" + reddit + ".json"
+    top     = parseInt message.match[3]
+    reddit  = "r/" + message.match[2] + ".json"
+
+    location  = lookup_site + reddit

     message.http( location ).get() (error, response, body)->
       return response_handler "Sorry, something went wrong"                   if error
@@ -28,7 +29,7 @@
       for item in list
         count++

-        text = item.data.title + " - " + item.data.url
+        text = ( item.data.title || item.data.link_title ) + " - " + ( item.data.url || item.data.body )
         response_handler text

         break if count == top


replace error with warning for archiving timeout

--- services/track-changes/app/coffee/PackWorker.coffee
+++ services/track-changes/app/coffee/PackWorker.coffee
@@ -87,7 +87,7 @@
 				logger.error {err, result}, "error in pack archive worker"
 				return callback(err)
 			if shutDownRequested
-				logger.error "shutting down pack archive worker"
+				logger.warn "shutting down pack archive worker"
 				return callback(new Error("shutdown"))
 			setTimeout () ->
 				callback(err, result)


Fix PageRoll to pass options down the stack

--- coffee/cilantro/ui/paginator.coffee
+++ coffee/cilantro/ui/paginator.coffee
@@ -85,6 +85,12 @@

         emptyPage: EmptyPage

+        itemViewOptions: (item, index) ->
+            c._.defaults
+                model: item
+                index: index
+            , @options
+

     # Renders multiples pages as requested, but only shows the current
     # page. This is delegated by the paginator-based collection bound to
@@ -116,12 +122,12 @@

         listViewOptions: (item, index) ->
             collection: item.items
-            index: index

         itemViewOptions: (item, index) ->
+            options = model: item, index: index
             if @options.list
-                return @listViewOptions(item, index)
-            return super(item, index)
+                c._.extend options, @listViewOptions(item, index)
+            c._.defaults options, @options

         collectionEvents:
             'change:currentpage': 'showCurentPage'


Fixed race condition in closing this layout

--- core/app/backbone/layouts/tabbed_main_region_layout.coffee
+++ core/app/backbone/layouts/tabbed_main_region_layout.coffee
@@ -1,10 +1,10 @@
 class window.TabbedMainRegionLayout extends Backbone.Marionette.Layout
   template: 'layouts/tabbed_main_region'
   showTitle: (title) ->
-    @titleRegion.show(new TextView(model: new Backbone.Model(text:title), tagName: 'h1'))
+    @titleRegion?.show(new TextView(model: new Backbone.Model(text:title), tagName: 'h1'))

   setTitle: (title) ->
-    @titleRegion.currentView.model.set('text',title)
+    @titleRegion?.currentView.model.set('text',title)

   regions:
     titleRegion:   '.js-title-region'


reactivity: chat list: scroll to bottom once new message is submitted

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -19,6 +19,18 @@
     onLoadMore    : kd.noop
     isParticipant : no
     showItemMenu  : yes
+
+
+  componentWillUpdate: (nextProps, nextState) ->
+
+    ScrollerMixin.componentWillUpdate.call this, nextProps, nextState
+
+    return  unless nextProps?.messages
+
+    { messages } = nextProps
+    fakeMessages = messages.filter (message) -> message.get 'isFake'
+    hasNewMessage = fakeMessages.size > 0
+    @shouldScrollToBottom = yes  if hasNewMessage

   onSubmit: (event) -> @props.onSubmit? event


Client: Activity: Updated handleLoadPopularChannelsSuccess handler to set popular channels without unreadCount data

--- client/activity/lib/flux/stores/channelsstore.coffee
+++ client/activity/lib/flux/stores/channelsstore.coffee
@@ -15,7 +15,7 @@
     @on actions.LOAD_CHANNEL_SUCCESS, @handleLoadChannelSuccess
     @on actions.LOAD_FOLLOWED_PUBLIC_CHANNEL_SUCCESS, @handleLoadFollowedChannelSuccess
     @on actions.LOAD_FOLLOWED_PRIVATE_CHANNEL_SUCCESS, @handleLoadChannelSuccess
-    @on actions.LOAD_POPULAR_CHANNELS_SUCCESS, @handleLoadChannelListSuccess
+    @on actions.LOAD_POPULAR_CHANNELS_SUCCESS, @handleLoadPopularChannelsSuccess

     @on actions.FOLLOW_CHANNEL_BEGIN, @handleFollowChannelBegin
     @on actions.FOLLOW_CHANNEL_SUCCESS, @handleFollowChannelSuccess
@@ -46,10 +46,15 @@
     return channels.set channel.id, toImmutable channel

-  handleLoadChannelListSuccess: (currentChannels, { channels }) ->
+  handleLoadPopularChannelsSuccess: (currentChannels, { channels }) ->

+    # we don't update channel if channel has been already set. Because
+    # fetchPopularTopics action doesn't get the correct unreadCount value.
+    # When BE fix it, we are gonna change this.
     return currentChannels.withMutations (map) ->
-      map.set channel.id, toImmutable channel for channel in  channels
+      for channel in  channels
+        map.set channel.id, toImmutable channel  unless map.get channel.id
+
       return map


activity: embedbox: fix for tests

activity: embedbox: fix for tests

--- client/activity/lib/views/activityinputwidget.coffee
+++ client/activity/lib/views/activityinputwidget.coffee
@@ -158,10 +158,10 @@
     options = { channelId, body, payload, clientRequestId }

     if activity
-    then @update options, @bound 'submissionCallback'
-    else @create options, @bound 'submissionCallback'
-
-    @embedBox.close()
+      @update options, @bound 'submissionCallback'
+    else
+      @create options, @bound 'submissionCallback'
+      @embedBox.close()

   submissionCallback: (err, activity) ->


filefinder: reduce number of key handlers

--- client/ide/lib/views/filefinder/idefilefinder.coffee
+++ client/ide/lib/views/filefinder/idefilefinder.coffee
@@ -9,8 +9,16 @@
 keycode              = require 'keycode'
 _                    = require 'lodash'

+DEBOUNCE_WAIT      = 300
+HAS_TEXT_CLASSNAME = 'has-text'
+KEYDOWN_KEYS       = [ 'enter', 'esc', 'up', 'down' ]

-module.exports = class IDEFileFinder extends KDCustomHTMLView
+isKeyDownKey = _.memoize (key) -> _.indexOf KEYDOWN_KEYS, key
+
+
+module.exports =
+
+class IDEFileFinder extends KDCustomHTMLView

   constructor: (options = {}, data) ->

@@ -22,11 +30,7 @@
       type         : 'text'
       placeholder  : 'Type a file name to search'
       keydown      : _.bind @handleKeyDown, this
-
-    input.on 'keyup', kd.utils.debounce 300, @bound 'handleKeyUp'
-    input.on 'keyup', =>
-      if   input.getValue() is '' then input.unsetClass 'has-text'
-      else input.setClass 'has-text'
+      keyup        : _.bind @handleKeyUp, this

     @addSubView new KDCustomHTMLView cssClass: 'icon'
     @addSubView @content = new KDCustomHTMLView
@@ -37,19 +41,7 @@
     @on 'ReceivedClickElsewhere', @bound 'destroy'

-  handleKeyDown: (e) ->
-
-    code = e.which or e.keyCode
-    key  = keycode code
-
-    switch key
-      when 'esc'   then @destroy()
-      when 'enter' then @handleEnterKey()
-      when 'up'    then @handleNavigation 'up'
-      when 'down'  then @handleNavigation 'down'
-
-
-  search: (text) ->
+  search: _.debounce (text) ->

     return @clearSearch()  if text is ''

@@ -65,6 +57,8 @@
       .catch (err) =>
         @showWarning 'An error occurred, please try again.'
         kd.warn err
+
+  , DEBOUNCE_WAIT

   parseResponse: (machine, res) =>
@@ -108,16 +102,32 @@
     item?.getElement().scrollIntoViewIfNeeded()

-  handleKeyUp: (event) ->
+  handleKeyUp: (e) ->

-    listenedKeys  = [13, 27, 38, 40]
-    isListenedKey = listenedKeys.indexOf(event.which) > -1
-    inputValue    = @input.getValue()
-    isSameText    = inputValue is @lastTerm
+    code = e.which or e.keyCode
+    key  = keycode code
+    term = @input.getValue()

-    return  if isListenedKey or isSameText
+    if   term is '' then @input.unsetClass HAS_TEXT_CLASSNAME
+    else @input.setClass HAS_TEXT_CLASSNAME

-    @search inputValue
+    @search term  if term isnt @lastTerm or not isKeyDownKey key
+
+    return
+
+
+  handleKeyDown: (e) ->
+
+    code = e.which or e.keyCode
+    key  = keycode code
+
+    switch key
+      when 'enter' then @handleEnterKey()
+      when 'esc'   then @destroy()
+      when 'up'    then @handleNavigation 'up'
+      when 'down'  then @handleNavigation 'down'
+
+    return

   handleEnterKey: ->


landing: add all stripe related requests to utils

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -3,6 +3,8 @@
 kookies = require 'kookies'

 RECAPTCHA_JS = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit'
+
+PAYMENT_BACKEND_URI = '/api/social/payment'

 createFormData = (teamData) ->

@@ -484,4 +486,61 @@
         defer    : yes

     recaptchaScript.appendToDomBody()
+
+  makeChargeRequest: (token, email) -> new Promise (resolve, reject) ->
+    options =
+      url: "#{PAYMENT_BACKEND_URI}/creditcard/auth"
+      type: 'POST'
+      contentType: 'application/json'
+      data: JSON.stringify { source: { token }, email }
+      success: resolve
+      error: (err) ->
+        reject JSON.parse err.responseText
+
+    $.ajax options
+
+  loadStripe: -> new Promise (resolve, reject) ->
+
+    return resolve global.Stripe  if global.Stripe
+
+    global.document.head.appendChild (new kd.CustomHTMLView
+      tagName    : 'script'
+      attributes :
+        type     : 'text/javascript'
+        src      : 'https://js.stripe.com/v2/'
+      bind       : 'load'
+      load       : ->
+        Stripe.setPublishableKey kd.config.stripe.token
+        return resolve(global.Stripe)
+    ).getElement()
+
+  createToken: (formData) ->
+
+    utils.loadStripe().then (Stripe) ->
+
+      { number, cvc, exp_month, exp_year } = formData
+
+      cardInfo =
+        number    : number
+        cvc       : cvc
+        exp_month : exp_month
+        exp_year  : exp_year
+
+      return new Promise (resolve, reject) ->
+
+        Stripe.createToken cardInfo, (status, response) ->
+          if response.error
+            return reject response.error
+
+          token = response.id
+          { email } = utils.getTeamData().signup
+
+          utils.makeChargeRequest token, email
+            .then (response) ->
+              utils.savePaymentToken token
+              utils.saveCardInfo cardInfo
+              resolve response
+            .catch (err) ->
+              utils.cleanPayment()
+              reject err
 }


Whoops forgot closing bracket

Whoops forgot closing bracket

--- resources/assets/coffee/_classes/nav2.coffee
+++ resources/assets/coffee/_classes/nav2.coffee
@@ -32,7 +32,7 @@
       continue if popup.dataset.clickMenuId != @currentMenu

       currentPopup = popup
-      link = document.querySelector(".js-click-menu[data-click-menu-target='#{@currentMenu}'")
+      link = document.querySelector(".js-click-menu[data-click-menu-target='#{@currentMenu}']")

     return if !currentPopup?


switch to conjunctive

switch to conjunctive

Former-commit-id: 3e6483bcceb235c21d994740675f3b81912caa36 [formerly 8a285a342299e19ed4e382a2fc827be612bc0054] [formerly 2fd8b7eb8a87ffe6968388c55474f77f6b889ff7 [formerly 3e90e7293591a48a747f629b6f00670a4847e0cd]] [formerly 1652a5c624e6df8079cdc7cdad59c77325d44f56 [formerly 7ef0d725b33e8d85dcfc58e37dd110d8d67f16e5] [formerly f19c8b2bab67f34d1da33ef02bbf698aa3cb0b1f [formerly f19c8b2bab67f34d1da33ef02bbf698aa3cb0b1f [formerly 97f81dfcf29b82bafe860d2eace2c2a0bf9a5814]]]]
Former-commit-id: 08e80950fe34e7623bd1b18217509596f1c57ee3 [formerly ee4d3a8396b3564c7fa9225b2289dce1a4f73593] [formerly 16865584ac46535c2ee6419ec4720eab0656f612 [formerly 4c5139fae0cd90be6ba7e800fecfc79125bed28d]]
Former-commit-id: 0746e638c7ccb261739310ead085aa97fc2cfa4e [formerly 59668f2ab4a768c2bb8e3dfb2fa185ba5c304bb5]
Former-commit-id: 9ee816414ac468f25661c2afd09cb3d14f9db12d
Former-commit-id: d0de1cc04c3c299c6b35b6cf95ba9163ebda8c9f

--- @client/filter.coffee
+++ @client/filter.coffee
@@ -22,9 +22,9 @@

       if filter_funcs.length > 0
         for user in users.users
-          passes = false
+          passes = true
           for func in filter_funcs
-            passes ||= func(user)
+            passes &&= func(user)

           if !passes
             filter_out.users[user.key] = 1
@@ -131,8 +131,8 @@
             DIV style: para,

               """Filters help us understand the opinions of the stakeholder groups. \
-                 Filters are disjunctive: membership to any selected filter will \
-                 pass a user. These are the filters:"""
+                 Filters are conjunctive: only users that pass all active filters are shown.
+                 These are the filters:"""

             DIV style: para,
               SPAN


fix indexing links loop

fix indexing links loop

--- cli/src/commands/index.coffee
+++ cli/src/commands/index.coffee
@@ -64,7 +64,9 @@
     reg = /a\shref=(\"|\')(\/)?([^\'\"]+)/g

     while (matched = (reg.exec src))?
-      return if /^http/m.test matched[3]
+
+      continue if /^http/m.test matched[3]
+
       url = "#{domain}/#{matched[3]}"
       @pages[url] = false unless @pages[url] is true


add @data variable

add @data variable

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -16,6 +16,7 @@
   startUp = (data) ->
     $("#question, #answer").show()
     update_question(data)
+    @data = data

   random_question = (data) ->
     choice = Math.floor(Math.random() * data['words'].length)


Implement simple conversion

Implement simple conversion

--- app._coffee
+++ app._coffee
@@ -1,13 +1,26 @@
 config = require './config'
+deepzoom = require 'deepzoomtools'
 express = require 'express'
+fs = require 'fs'
+path = require 'path'
+request = require 'request'
+
+# Register DZI as XML
+express.static.mime.types.dzi = 'application/xml'
 app = require('streamline-express') express()

+ID = 0
+STATIC_PATH = path.join __dirname, 'public', 'static'
+STATIC_URL = '/static'

 ## MIDDLEWARE:
 # (see http://www.senchalabs.org/connect/ for reference)

 # favicon reqs are very common; respond to those immediately:
 app.use express.favicon()   # will use default favicon since we don't specify
+
+# Ser
+app.use '/static', express.static STATIC_PATH

 # otherwise, log all requests:
 app.use express.logger config.EXPRESS_LOGGER_FORMAT
@@ -39,6 +52,19 @@
     setTimeout _, 500   # mimic async operation
     res.json 'Hello world.'

+app.get '/content', (req, res, _) ->
+    url = req.query?.url
+    if not url?
+        res.json 400, {error: "Missing URL"}
+    id = ++ID
+    contentPath = path.join STATIC_PATH, "#{id}.jpg"
+    writer = request(url).pipe fs.createWriteStream contentPath
+    writer.on 'finish', (_) ->
+        deepzoom.create contentPath, (error) ->
+            if error?
+                console.error error
+        res.json 200, {id, url}
+

 ## MAIN:


Migrated AJAX module to CommonJS spec

Migrated AJAX module to CommonJS spec

--- source/micro.ajax.coffee
+++ source/micro.ajax.coffee
@@ -1,41 +1,40 @@
-do ->
-  'use strict'
+'use strict'

-  u = module.exports.u if typeof module isnt 'undefined'
-  u = window.u if typeof window isnt 'undefined'
-  u = {} if typeof u is 'undefined'
+utils = require './micro.utils.coffee'
+db    = require './micro.localstorage.coffee'

-  _xhrHeaders = (xhr, options) ->
-    options.headers['Content-Type'] = options.contentType if options.contentType
-    options.headers['Accept'] = options.contentType if options.contentType
-    for header of options.headers
-      xhr.setRequestHeader header, options.headers[header]
-    return
+_xhrHeaders = (xhr, options) ->
+  options.headers['Content-Type'] = options.contentType if options.contentType
+  options.headers['Accept'] = options.contentType if options.contentType
+  for header of options.headers
+    xhr.setRequestHeader header, options.headers[header]
+  return

-  _parseResponse = (xhr, options) ->
-    response = xhr
-    if options.contentType is 'application/json'
-      response = JSON.parse xhr.responseText
-    response
+_parseResponse = (xhr, options) ->
+  response = xhr
+  if options.contentType is 'application/json'
+    response = JSON.parse xhr.responseText
+  response

-  _msToMin = (ms) ->
-    ms / 1000 / 60
+_msToMin = (ms) ->
+  ms / 1000 / 60

-  _cacheNotExpired = (req, expireTime) ->
-    true if _msToMin(new Date() - new Date(req.date)) < expireTime
+_cacheNotExpired = (req, expireTime) ->
+  true if _msToMin(new Date() - new Date(req.date)) < expireTime

-  _cacheRequest = (data, options) ->
-    item =
-      url: options.url
-      date: new Date()
-      data: data
-    u.db.update options.cacheDB, 'url', options.url, item
+_cacheRequest = (data, options) ->
+  item =
+    url: options.url
+    date: new Date()
+    data: data
+  db.update options.cacheDB, 'url', options.url, item

-  _checkCache = (options) ->
-    cache = u.db.findOne options.cacheDB, url: options.url
-    cache.data if cache and _cacheNotExpired cache, options.minutesCached
+_checkCache = (options) ->
+  cache = db.findOne options.cacheDB, url: options.url
+  cache.data if cache and _cacheNotExpired cache, options.minutesCached

-  u.ajaxSettings =
+ajax =
+  ajaxSettings:
     type: 'GET'
     async: true
     cache: false
@@ -48,14 +47,14 @@
     crossDomain: false
     timeout: 0

-  u.ajax = (options) ->
-    settings = u.extend {}, u.ajaxSettings
-    options  = u.extend settings, options
+  ajax: (options) ->
+    settings = utils.extend {}, @ajaxSettings
+    options  = utils.extend settings, options

     if options.cache and _checkCache(options)
       return options.success _checkCache(options)

-    options.data = u.serialize(options)
+    options.data = @serialize options

     req = new XMLHttpRequest()

@@ -75,10 +74,10 @@
     req.send options.data
     req

-  u.serialize = (options) ->
+  serialize: (options) ->
     data = options.data
     if options.contentType is 'application/json'
-      data = JSON.stringify(options.data)
+      data = JSON.stringify options.data
     data

-  u+module.exports = ajax


update choice of database nodes

update choice of database nodes

--- salvus/bup_server.coffee
+++ salvus/bup_server.coffee
@@ -2036,6 +2036,8 @@
                                     hosts = ("10.1.#{i}.2" for i in [10..21])
                                 else if a == 3 or a == 4
                                     hosts = ("10.#{a}.#{i}.2" for i in [1..4])
+                                else if a == 5 or a == 6 or a == 7
+                                    hosts = ("smc#{i}dc#{a}" for i in [1..5])
                             winston.debug("database hosts=#{misc.to_json(hosts)}")
                             @database = new cassandra.Salvus
                                 hosts       : hosts


Proper width for description, show hand when truncated, fix auto-close

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -26,7 +26,6 @@
 			height: 33
 			ignoreEvents: false
 			style:
-				fontFamily: "-apple-system, Roboto, Helvetica Neue, Helvetica, Arial, sans-serif"
 				fontWeight: "500"
 				webkitUserSelect: "text"
 				borderRadius: 3
@@ -268,7 +267,6 @@
 				letterSpacing: ".2px"

 	_enableUserSelect: (layer) ->
-
 		if !layer.html then layer.html = ""
 		layer._elementHTML.style["-webkit-user-select"] = "auto"
 		layer._elementHTML.style["cursor"] = "auto"
@@ -292,17 +290,13 @@

 			urlified.replace lineBreakRegex, '<br />'

-		@description = new Layer
-			ignoreEvents: false
+		@description = new ShareLayer
 			parent: @info
 			y: @credentials.maxY + 10
-			backgroundColor: null
 			style:
 				lineHeight: "1.5"
 				wordWrap: "break-word"
 				color: "#111"
-
-		@_enableUserSelect(@description)

 		descriptionStyle =
 			fontSize: "14px"
@@ -316,18 +310,8 @@
 			{width: "#{@description.width}"}
 		)

-		descriptionClickRegister = (e) ->
-			@descriptionStartX = e.x
-			@descriptionStartY = e.y
-
-		descriptionClickCompare = (e) ->
-			if @descriptionStartX is e.x and @descriptionStartY is e.y
-				showFullDescription()
-
 		showFullDescription = =>
-
 			@options.truncated = false
-
 			@description.height = @descriptionSize.height
 			@description.html = parseDescription(@shareInfo.description)

@@ -337,15 +321,9 @@

 			@_updateHeight()
 			@_calculateAvailableSpace()
-
-			@description.onMouseMove =>
-				@description.style =
-					cursor: "default"
-
-			# Remove events when description is shown to make links clickable
-			@description.off Events.TapStart, descriptionClickRegister
-			@description.off Events.TapEnd, descriptionClickCompare
-
+			@_enableUserSelect(@description)
+
+		# Truncate if description is too long
 		if @shareInfo.description.length > @options.maxDescriptionLength

 			@options.truncated = true
@@ -362,16 +340,13 @@
 			@description.height = @descriptionTruncatedSize.height
 			@description.html = parseDescription(@options.shortDescription)

+			@description.onTapEnd -> showFullDescription()
 			@_showPointer(@description)
-
-			# Selecting text also triggers a click event to counter this
-			# we compare the TapStart and TapEnd positions
-			@description.on Events.TapStart, descriptionClickRegister
-			@description.on Events.TapEnd, descriptionClickCompare

 		else
 			@description.height = @descriptionSize.height
 			@description.html = parseDescription(@shareInfo.description)
+			@_enableUserSelect(@description)

 	_renderButtons: ->
 		@buttons = new ShareLayer
@@ -429,22 +404,24 @@
 				backgroundImage: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAWCAYAAADTlvzyAAAABGdBTUEAALGPC/xhBQAAAr5JREFUSA2tlUloU1EUhpuhITFCAnUTR1zVgsQUF1pw4UbETUAq7tTiogUDKkiJSUjaTBvdFCliF5KVazdKigu3BYsjlEIcIOKEEgewTVoz+J2HCa8378UOuXC494z/O+e8e4+lZ4srk8kcq1arVywWyxChvI1Go8g573Q6p8Lh8IdmeOyO1uv1vZamYDN7MpnMABDB16r6A1qGLlut1gIfFOfsHhgYOK4BplKpq6APut3uS+Pj40uqsxGPzyg+M0Y6VQbYS4CHsfc3v+w5RueXl5efEuiw6qDy6XR6P5lNqXIT/hW2c4Dle3t7FzRA0N+JMYp+FE8mJydz1HyXSQCxOwe5zPSK3E+Gw9CFaDRa0AAB2a0zEtkIdS/Qo9tGGQN2Vmff8QjQfX6gQCKRmBNDrYe5XM5ZLBY/w3tNvF/g+BDdPM7zlUplGtDTJrZrxD6fzzE2NvanKbTLoVQqbaesN8k021QoewCAgMjK5bKi6syurKxI6VuAWkkR7iTgGRSPO7tvXEs1tum9tAwdDscXeuZHYdMru3Be9Xg8JX0cLUPu3lcyzOsV3TjT90V9/ySmBqgdrNbr7K2nSGRdWM/UGC1AftsFl8t1BINpvuytarhJ/p7q1wIUBY/tJzbpZVX4La4CSbT9hGsABYDsluhn/xbBJM4NqKHGaQPkPsqo+aUabpB/NDExcdfIpw0wHo+/ttlshzC+A72Bfhs5dpB9t9vtF830HechD/g+7uctnINmAfRyKvMNOknvZPoYLkNAgPbUarVreIzSz3VNBYA+0o4TVGjREOmf0MI0GMT4J7yDtzQIQBB+iH29r04d+xleq1gkEvnRCUx08rStks0Iu4ycA5DMO9n+tyoYzDJU07FYrO2CmzmvKWk2m/XTs1MAymQ4CO2AvGQgr73cUaH38LN9fX0PQqHQRn+onr9RuQfzn9jjeAAAAABJRU5ErkJggg==')"

+	_openIfEnoughSpace: =>
+		# Open or close sheet based on available space
+		if @availableSpace < @threshold
+			'close'
+			@_closeSheet()
+		else
+			@_openSheet()
+
 	_calculateAvailableSpace: =>
 		device = Framer.Device
-		threshold = @options.minAvailableSpaceFullScreen
-		availableSpace = Canvas.width
+		@threshold = @options.minAvailableSpaceFullScreen
+		@availableSpace = Canvas.width

 		# When device is selected, us the device's
 		# position to calculate available space
 		if device.deviceType isnt "fullscreen"
-			threshold = @options.minAvailableSpace
-			availableSpace = Screen.canvasFrame.x
-
-		# Open or close sheet beased on available space
-		if availableSpace < threshold
-			@_closeSheet()
-		else
-			@_openSheet()
+			@threshold = @options.minAvailableSpace
+			@availableSpace = Screen.canvasFrame.x

 		# If verticalSpace is less then sheet height, make sheet scrollable
 		canvasHeight = Canvas.height - 20
@@ -487,6 +464,7 @@
 		# When the window resizes evaluate if the sheet needs to be hidden
 		Canvas.onResize =>
 			@_calculateAvailableSpace()
+			@_openIfEnoughSpace()

 	# Show hand cursor
 	_showPointer: (layer) ->


Fix languageView

Fix languageView

--- BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
@@ -165,7 +165,6 @@
       currentLanguage: @language
       el: this.$el.find('#node-languages')
     )
-#    this.$el.find('#node-languages').append view.render()

   changeLanguage: (event) ->
     Backbone.history.navigate('#node/show/' + @node.get('node_id') + '/' + $(event.currentTarget).data('language'), {trigger: true})


passing tests

passing tests

--- components/article/test/view.coffee
+++ components/article/test/view.coffee
@@ -204,7 +204,7 @@

     it 'calls setupMaxImageHeights on single images', ->
       @view.refreshWindowSize()
-      @setupMaxImageHeights.callCount.should.equal 1
+      @setupMaxImageHeights.callCount.should.be.above 1

   describe '#embedMobileHeight', ->

@@ -225,7 +225,7 @@

     it 'on small screens, resets image sizes for imageset previews', ->
       @view.windowWidth = 600
-      @imgsFillContainer.callCount.should.equal 1
+      @imgsFillContainer.callCount.should.be.above 1

   describe '#imgsFillContainer', ->
     it 'returns true if images are narrower than their container', ->


Adds back the its as a keyword

Adds back the its as a keyword

--- grammars/inspec.cson
+++ grammars/inspec.cson
@@ -12,6 +12,10 @@
     'name': 'keyword.inspec'
   }
   {
+    'match': '(^|;)\\W*(its)\\b'
+    'name': 'keyword.inspec'
+  }
+  {
     'include': 'source.ruby.rspec'
   }
 ]


Played with component build

Played with component build

--- config/build/build_component.coffee
+++ config/build/build_component.coffee
@@ -1,7 +1,23 @@
+path = require 'path'
+_ = require 'underscore'
+es = require 'event-stream'
+
+gulp = require 'gulp'
+compile = require 'gulp-compile-js'
+modules = require 'gulp-module-system'
+header = require 'gulp-header'
+
+HEADER = require './header'
+PLUGINS = require './plugins'
+MODULE_PATHS = _.flatten((["src/#{plugin}/**/*.coffee", "!src/#{plugin}/index.coffee"] for plugin in ['core'].concat(PLUGINS)))
+root_paths = (root_path.replace('/**/*.coffee', '') for root_path in MODULE_PATHS when root_path.indexOf('/**/*.coffee') >= 0)
+
 module.exports = (callback) ->
   return callback()
-  # return gulp.src(library.paths)
-  #   .pipe(es.map (file, callback) -> file.path = file.path.replace("#{path.resolve(dir)}/", '') for dir in root_paths; callback(null, file))
-  #   .pipe(compile({coffee: {bare: true, header: false}}))
-  #   .pipe(modules(library.modules))
-  #   .pipe(es.map((file, callback) -> console.log "Compiled #{library.modules.file_name}"; callback(null, file)))
+  gulp.src(MODULE_PATHS)
+    .pipe(es.map (file, callback) -> file.path = file.path.replace("#{path.resolve(dir)}/", '') for dir in root_paths; callback(null, file))
+    .pipe(compile({coffee: {bare: true, header: false}}))
+    .pipe(modules({type: 'local-shim', file_name: 'knockback-component.js', umd: {symbol: 'kb', dependencies: ['underscore', 'backbone', 'knockout']}}))
+    .pipe(header(HEADER, {pkg: require('../../package.json')}))
+    .pipe(gulp.dest('./lib'))
+    .on('end', callback)


Added bogus property in device-config

Added bogus property in device-config

--- device-config-schema.coffee
+++ device-config-schema.coffee
@@ -3,14 +3,11 @@
   Smartmeterdevice: {
     title: "Smartmeter config options"
     type: "object"
-    #properties:
-      ###ip:
-        description: "Smartmeter IP-ddress"
+    properties:
+      ip:
+        description: "Smartmeter Bogus setting"
         format: String
-        default: "10.0.0.0"
-      timeout:
-        description: "Timeout between requests"
-        format: Number
-        default: "60000"###
+        default: "Dummy"
+
   }
 }


Add UIComponent.active, so we can specify whether to focus the frame

--- content_scripts/ui_component.coffee
+++ content_scripts/ui_component.coffee
@@ -59,10 +59,14 @@
     else
       @hide()

+  activate: (message) ->
+    @postMessage message if message?
+    @show() unless @showing
+    @iframeElement.focus()
+
   show: (message) ->
     @postMessage message if message?
     @iframeElement.setAttribute "style", @showStyle
-    @iframeElement.focus()
     @showing = true

   hide: ->


add a db task to drop database

add a db task to drop database

--- gulp/tasks/db.coffee
+++ gulp/tasks/db.coffee
@@ -21,6 +21,8 @@

 getMongoStr = (db, filepath, type, collectionName) ->
   switch type
+    when 'reset'
+      "mongo #{db.database} --eval 'db.dropDatabase()'"
     when 'import'
       "
         mongoimport --db #{db.database} --collection #{collectionName}
@@ -62,7 +64,7 @@
     )

 # pass collection name as flag arg
-gulp.task 'db:create:seed', ->
+gulp.task 'db:seed:save', ->
   # array of collection names based on model filenames
   collections = _.map fs.readdirSync('./models'), (f) -> f.replace('.coffee', 's')
   unless _.size(argv) == 3
@@ -115,3 +117,12 @@
       )
     )
   )
+
+# drops the database
+gulp.task 'db:reset', ->
+  envName = 'dev'
+  db = mongodbUri.parse config.db[envName]
+  run(getMongoStr(db, null, 'reset'))
+  .exec( ->
+    log.info "Dropped database #{db.database}"
+  )


fixed issue by changing output style to "pre-line" instead of "pre-wrap" for whitespace attribute

--- src/smc-webapp/jupyter/cell-output-message.cjsx
+++ src/smc-webapp/jupyter/cell-output-message.cjsx
@@ -20,7 +20,7 @@
 {javascript_eval} = require('./javascript-eval')

 OUT_STYLE =
-    whiteSpace    : 'pre-wrap'
+    whiteSpace    : 'pre-line'
     wordWrap      : 'break-word'
     fontFamily    : 'monospace'
     paddingTop    : '5px'
@@ -473,4 +473,4 @@
         </div>

 is_ansi = (s) ->
-    return s? and s.indexOf("\u001b") != -1+    return s? and s.indexOf("\u001b") != -1


button_remove(): Remake page instead of trying to remove HTML.

--- client/views-list-edit.coffee
+++ client/views-list-edit.coffee
@@ -38,8 +38,7 @@
       if confirm "Diesen Eintrag wirklich entfernen?"
         xml = (xml.data "referencing_xml") || xml
         xml.remove()
-        e.remove()
-      e.removeClass "selected"
+        menu_reopen()

 @VIEWS_LIST_EDIT =
   button_add:      add_button


Finalizing fixes now that I was actually able to test this against real Gateway

--- implementations/hook.io-smsgw.labyrintti/lib/sender.coffee
+++ implementations/hook.io-smsgw.labyrintti/lib/sender.coffee
@@ -32,7 +32,6 @@
       return cb err if err
       cb null, response

-    #url = @_getServiceUrl msg
     http_options =
       host: @_getServiceHost()
       port: @options.port
@@ -49,13 +48,12 @@
       sendResponse result

     try
-      req = http.request http_options, (res) ->
-
+      req = http.request http_options, (res) =>
         res.setEncoding 'utf8'
         body = ''
         res.on 'data', (chunk) ->
           body += chunk
-        req.on 'end', () ->
+        res.on 'end', () =>
           result =
             code: res.statusCode
             status: 'FAILED'
@@ -67,6 +65,14 @@
             sendResponse null, result
           else
             sendResponse result
+
+      req.on 'error', (err) ->
+        result =
+          code: err.code
+          errno: err.errno
+          msg: err.message
+        return sendResponse result
+
       req.write @_generatePostData(msg)
       req.end()
     catch err
@@ -78,11 +84,12 @@

   _parseResultString: (msg, body, result) ->
     body = decodeURIComponent body
+    lines = body.split("\r\n")

-    lines = body.split("\n")
     result.msg = body
     result.statuses = []
     _.each lines, (line) ->
+      return unless line or line.length
       line_parts = line.match /^(\+[0-9]+) (\w+) (\d+) (.+)$/

       _.each msg.recipients, (recipient) ->
@@ -90,18 +97,19 @@
           rec_status = {
             number: line_parts[1]
             status: line_parts[2]
-            code: line_parts[3]
+            code: parseInt line_parts[3]
             msg: line_parts[4]
           }
-          result.statuses.push rec_status
-
+          result.statuses.push rec_status
     result

   _getServiceHost: () ->
-    url = "http://"
-    if @options.secure
-      url = "https://"
+    #url = "http://"
+    url = ""
+    # if @options.secure
+    #   url = "https://"
     url += @options.host
+
     url

   _getServicePath: (msg) ->


Reorganize outer script

Reorganize outer script

--- js/outer.coffee
+++ js/outer.coffee
@@ -1,9 +1,4 @@
-# Placeholder Polyfill
-# https://github.com/mathiasbynens/jquery-placeholder
-$(window).load ->
-  $("input, textarea").placeholder()
-
-# Sammy
+# Navigation
 (($) ->
   circle = $.sammy("body", ->

@@ -43,3 +38,8 @@
   $ -> circle.run "/"

 ) jQuery
+
+
+# Placeholder Polyfill
+$(window).load ->
+  $("input, textarea").placeholder()


v0 upgrade bugger in /results

v0 upgrade bugger in /results

--- apis/v0.coffee
+++ apis/v0.coffee
@@ -66,24 +66,40 @@
         }
     res.json({success: true, data: finalData})

-
+updateBuggers = {
+  exams: {
+    "course": {
+      "code": "UPD999"
+    },
+    "date": "2015-02-01T23:59:59.999Z",
+    "telecenter": {
+      "name": "UPGRADE NOW! bit.ly/SVUHelper\n(Or use the link in the login screen)"
+    }
+  }
+  results: {
+    "grade": 0,
+    "date": "2015-02-01T23:59:59.999Z",
+    "status": "Archive",
+    "label": "bit.ly/SVUHelper\n(Or use the link in the login screen)",
+    "course": {
+      "program": "SVU",
+      "code": "UPGRADE NOW!"
+    },
+    "term": {
+      "code": "S14"
+    }
+  }
+}
 studentsRouter.get '/:section(exams|results|classes)', (req, res, next)->
   #console.log req.params
   req.studentObject.get req.params.section, {}, (err, data)->
     return next(err) if err
     debug("Got #{data.length} data array for #{req.studentObject.studentId}/#{req.params.section}")
     #data = data.map(dataFixers[req.params.section])
-    if 'exams' == req.params.section
+    #console.log data[0]
+    data.unshift updateBuggers[req.params.section]
+    data.push updateBuggers[req.params.section]

-      data.unshift {
-        "course": {
-          "code": "UPD999"
-        },
-        "date": "2015-02-01T23:59:59.999Z",
-        "telecenter": {
-          "name": "UPGRADE NOW! bit.ly/SVUHelper\n (Or use the link in the login screen)"
-        }
-      }
     res.json({success: true, data: data})

 v0 = etabits.express.Router()
@@ -95,6 +111,7 @@
     console.error(err) if err

     return res.send({success: false, errorMessage: 'Bad Login'}) if err
+
     res.send {
       success: true
       token: data.doc.sessionToken


Disable the trailing throttle event in the editing session heartbeat

Throttle sends an event as soon as the function is called. Then
we wait X minutes, but we only want to send another event if
there's any activity *after* those X minutes.

--- services/web/public/coffee/main/event.coffee
+++ services/web/public/coffee/main/event.coffee
@@ -44,7 +44,7 @@
 						"X-CSRF-Token": window.csrfToken
 					}
 				})
-			, EDIT_SESSION_HEARTBEAT_INTERVAL)
+			, EDIT_SESSION_HEARTBEAT_INTERVAL, trailing: false)

 			sendMB: (key, segmentation = {}) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -27,7 +27,7 @@

   obj2.include(ActiveBounds)

-  obj2DestroySpy = false
+  obestroySpy = false

   obj.bind 'destroy', ->
     destroySpy = true


Fixing param value.

Fixing param value.

--- src/toaster/core/builder.coffee
+++ src/toaster/core/builder.coffee
@@ -319,7 +319,7 @@
     fs.readFileSync rjs_path, 'utf-8'

   # copy vendors to release folder
-  copy_vendors_to_release:( all = true, specific = null, log_time = false )->
+  copy_vendors_to_release:( all = true, specific = null, log_time = true )->

     return unless @config.browser.vendors?


geometry: added a testcase for invalid geometries

--- test/geometry-test.coffee
+++ test/geometry-test.coffee
@@ -216,4 +216,27 @@
       assert.isString geom.getGeometryType()
       assert.equal geom.getGeometryType(), "Point"

+  "A invalid Geometry":
+
+    topic: ->
+      multipolygon = "MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),
+        ((3 3,6 2,6 4,3 3)),
+        ((78 45,65 34,100 54,78 45),(4 65, 54 23, 544 346, 2 1, 4 65)))"
+      (new WKTReader()).read multipolygon
+
+    "should return false on isValid": (geom) ->
+      assert.isFalse geom.isValid()
+      geom.isValid (error, isValid) ->
+        assert.isFalse isValid
+
+    #TODO fix this text
+    #"should throw an error on spatial operations": (geom) ->
+    #  try
+    #    geom.getArea()
+    #  catch e
+    #    console.log "test"
+    #    assert.instanceOf e, Error
+
+
+
 tests.export module


cleanup

cleanup

--- app/assets/javascripts/monitoring/views/add_command.js.coffee
+++ app/assets/javascripts/monitoring/views/add_command.js.coffee
@@ -3,7 +3,8 @@
   class exports.AddCommand extends Stark.View
     el: "div.monitoring_content"
     template: "monitoring/add_command"
-    events: {
+
+    events:
       "click #submit_check": (e) ->
         # show options for selected commands/checks
         opts = []
@@ -15,4 +16,3 @@

         if opts.length > 0
           @transition "mon_hosts_resources_new_opts", { host: @host, opts: opts, commands: @commands }
-    }


Clear promoCode when form submitted

Clear promoCode when form submitted

--- app/assets/javascripts/sprangular/directives/promoForm.coffee
+++ app/assets/javascripts/sprangular/directives/promoForm.coffee
@@ -8,7 +8,6 @@
     $scope.promoCode = ''

     $scope.save = ->
-      Cart.current.promoCode = $scope.promoCode
       Angularytics.trackEvent("Cart", "Coupon added", $scope.promoCode)

       error = (message) ->
@@ -20,6 +19,7 @@
           if response.error
             error(response.error)
           else
+            $scope.promoCode = ''
             $scope.showPromoEntry = false
         .error ->
           error('An error occured')


didn't use

didn't use

--- coffee/services/ticket.coffee
+++ coffee/services/ticket.coffee
@@ -1,8 +1,6 @@
 timeTracker.factory("Ticket", (Project, Analytics, Chrome) ->

   TICKET = "TICKET"
-
-  URL_INDEX_START = 1  # for avoid 0 == false

   TICKET_ID        = 0
   TICKET_TEXT      = 1


Update translation for TIE Advanced.

Update translation for TIE Advanced.

fixes #497

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -45,7 +45,7 @@
         "TIE Fighter Expansion Pack": "TIE-Jäger Erweiterung"
         "TIE Interceptor Expansion Pack": "TIE-Abfangjäger Erweiterung"
         "TIE Bomber Expansion Pack": "TIE-Bomber Erweiterung"
-        "TIE Advanced Expansion Pack": "TIE-Advanced Erweiterung"
+        "TIE Advanced Expansion Pack": "TIE-Turbojäger Erweiterung"
         "Lambda-Class Shuttle Expansion Pack": "Raumfähre der Lambda-Klasse Erweiterung"
         "Slave I Expansion Pack": "Sklave I Erweiterung"
         "Imperial Aces Expansion Pack": "Fliegerasse des Imperiums Erweiterung"
@@ -231,6 +231,7 @@
     exportObj.renameShip 'C-ROC Cruiser', 'C-ROC Kreuzer'
     exportObj.renameShip 'Auzituck Gunship', 'Auzituck-Kanonenboot'
     exportObj.renameShip 'TIE Aggressor', 'TIE-Aggressor'
+    exportObj.renameShip 'TIE Advanced', 'TIE-Turbojäger'
     pilot_translations =
         "Wedge Antilles":
             ship: "X-Flügler"
@@ -293,16 +294,16 @@
             name: '"Kreischläufer"'
             text: """Wenn ein anderes freundliches Schiff in Reichweite 1 mit seinen Primärwaffen angreift, darf es 1 Angriffswürfel neu würfeln."""
         "Maarek Stele":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             text: """Wenn ein Verteidiger durch deinen Angriff eine offene Schadenskarte erhalten würde, ziehst du stattdessen 3 Schadenskarten, wählst eine davon zum Austeilen und legst die restlichen ab."""
         "Tempest Squadron Pilot":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             name: "Pilot der Tornado-Staffel"
         "Storm Squadron Pilot":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             name: "Pilot der Storm-Staffel"
         "Darth Vader":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             text: """Im Schritt "Aktionen durchführen" darfst du 2 Aktionen durchführen."""
         "Alpha Squadron Pilot":
             name: "Pilot der Alpha-Staffel"
@@ -654,13 +655,13 @@
             ship: "TIE-Vergelter"
             text: """Sobald du eine Bombe legst, darfst du die Stopper am Bug deines Schiffs benutzen. Nachdem du eine Bombe gelegt hast, darfst du als freie Aktion eine Fassrolle durchführen."""
         "Juno Eclipse":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             text: """Sobald du dein Manöver aufdeckst, darfst du die Geschwindigkeit um 1 erhöhen oder reduzieren (bis zu einem Minimum von 1)."""
         "Zertik Strom":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             text: """Sobald feindliche Schiffe in Reichweite 1 angreifen, können sie ihren Kampfvorteil durch Reichweite nicht hinzufügen."""
         "Lieutenant Colzet":
-            ship: "TIE Advanced"
+            ship: "TIE-Turbojäger"
             text: """Zu Beginn der Endphase darfst du einen Zielerfassungsmarker , den du auf einem feindlichen Schiff liegen hast, ausgeben, um 1 seiner verdeckten Schadenskarten (zufällig bestimmt) aufzudecken."""
         "Latts Razzi":
             ship: "YV-666"
@@ -1440,7 +1441,7 @@
             text: """%DE_SCUMONLY%%LINEBREAK%Nachdem du ein rotes Manöver ausgeführt hast, darfst du deinem Schiff 1 Fokusmarker zuweisen."""
         "Advanced Targeting Computer":
             name: "Verbesserter Zielcomputer"
-            text: """Nur für TIE Advanced%LINEBREAK%Sobald du mit deiner Primärwaffe angreifst, darfst du deinem Würfelwurf 1 %CRIT% hinzufügen, wenn du den Verteidiger in der Zielerfassung hast. Wenn du das tust, kannst du während dieses Angriffs keine Zielerfassungen ausgeben."""
+            text: """Nur für TIE-Turbojäger%LINEBREAK%Sobald du mit deiner Primärwaffe angreifst, darfst du deinem Würfelwurf 1 %CRIT% hinzufügen, wenn du den Verteidiger in der Zielerfassung hast. Wenn du das tust, kannst du während dieses Angriffs keine Zielerfassungen ausgeben."""
         "Ion Cannon Battery":
             name: "Ionengeschützbatterie"
             text: """Angriff (Energie): Gib 2 Energie von dieser Karte aus, um diesen Angriff durchzuführen. Wenn dieser Angriff trifft, nimmt der Verteidiger 1 kritischen Schaden und erhält 1 Ionenmarker. Dann werden alle Würfelergebnisse negiert."""
@@ -1951,7 +1952,7 @@
         "Andrasta":
             text: '''Nur für Firespray-31.%LINEBREAK%Füge deiner Aufwertungsleiste zwei weitere %BOMB%-Symbole hinzu.'''
         "TIE/x1":
-            text: '''Nur für TIE Advanced.%LINEBREAK%Füge deiner Aufwertungsleiste ein %SYSTEM%-Symbol hinzu.%LINEBREAK%Wenn du eine %SYSTEM%-Aufwertung ausrüstest, sinken deren Kommandopunkte um 4 (Minimum 0).'''
+            text: '''Nur für TIE-Turbojäger.%LINEBREAK%Füge deiner Aufwertungsleiste ein %SYSTEM%-Symbol hinzu.%LINEBREAK%Wenn du eine %SYSTEM%-Aufwertung ausrüstest, sinken deren Kommandopunkte um 4 (Minimum 0).'''
         "Hound's Tooth":
             name: "Reisszahn"
             text: '''Nur für YV-666.%LINEBREAK%Nachdem du zerstörst worden bist, darfst du das Schiff Nashtahwelpe absetzen, bevor du von der Spielfläche entfernt wirst.%LINEBREAK%Es darf diese Runde nicht angreifen.'''


Update manifest.

Update manifest.

--- coffeescripts/manifest.coffee
+++ coffeescripts/manifest.coffee
@@ -3977,6 +3977,192 @@
             name: 'Pulsed Ray Shield'
             type: 'modification'
             count: 5
+        }
+    ]
+    "Auzituck Gunship Expansion Pack": [
+        {
+            name: 'Auzituck Gunship'
+            type: 'ship'
+            count: 1
+        }
+        {
+            name: 'Wullffwarro'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Lowhhrick'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Wookiee Liberator'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Kashyyyk Defender'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Intimidation'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Selflessness'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Wookiee Commandos'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Tactician'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Breach Specialist'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Hull Upgrade'
+            type: 'modification'
+            count: 1
+        }
+    ]
+    "Scurrg H-6 Bomber Expansion Pack": [
+        {
+            name: 'Scurrg H-6 Bomber'
+            type: 'ship'
+            count: 1
+        }
+        {
+            name: 'Captain Nym (Rebel)'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Captain Nym (Scum)'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Sol Sixxa'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Lok Revenant'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Karthakk Pirate'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Lightning Reflexes'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Seismic Torpedo'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Cruise Missiles'
+            type: 'upgrade'
+            count: 2
+        }
+        {
+            name: 'Bomblet Generator'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Minefield Mapper'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Synced Turret'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Cad Bane'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'R4-E1'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Havoc'
+            type: 'title'
+            count: 1
+        }
+    ]
+    "TIE Aggressor Expansion Pack": [
+        {
+            name: 'TIE Aggressor'
+            type: 'ship'
+            count: 1
+        }
+        {
+            name: 'Lieutenant Kestal'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: '"Double Edge"'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Onyx Squadron Escort'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Sienar Specialist'
+            type: 'pilot'
+            count: 1
+        }
+        {
+            name: 'Intensity'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Unguided Rockets'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Twin Laser Turret'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Synced Turret'
+            type: 'upgrade'
+            count: 1
+        }
+        {
+            name: 'Lightweight Frame'
+            type: 'modification'
+            count: 1
         }
     ]


Update nnoi info

Update nnoi info

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -337,6 +337,8 @@

28 мая 2022 года состоится XVIII Нижегородская олимпиада школьников по информатике им. В.Д. Лелюха. Я рекомендую подавать заявку всем школьникам моего курса, которые являются учениками школ города Нижнего Новгорода (не области!), и имеющим уровень как минимум 1В. Можете подать заявку и если у вас уровень ниже, но, возможно, задачи даже отбора вам покажутся сложными.

Для участия в олимпиаде надо подать заявку. Информация про олимпиаду и порядок подачи заявок выставлена на сайте олимпиады в разделе "Новости" и разослана по части школ; если до вашей школы информация не дошла, покажите информацию с сайта своим учителям или завучам.

+
+
Обратите внимание, что срок подачи заявок — до 19 мая включительно!

Отбор на олимпиаду будет проводиться из числа заполнивших заявку по итогам районной и школьной олимпиад, также будет проведен дополнительный заочный отборочный тур 21 мая. Подробная информация есть на сайте олимпиады, а также информация будет разослана после заполнения заявок.


implements proper equation checking to see if unknown is used

--- javascripts/lib/math/expression_traversal.coffee
+++ javascripts/lib/math/expression_traversal.coffee
@@ -21,6 +21,13 @@
       found_equals = true if exp instanceof @expr_classes.equals
     found_equals

+  hasVariableNamed: (name)->
+    found_equals = false
+    @each (exp)=>
+      found_equals = true if exp instanceof @expr_classes.variable and exp.name() == name
+    found_equals
+
+
 ttm.class_mixer(ExpressionTraversal)

 class ExpressionTraversalBuilder


Don't depend on modalOpened events but on factlinkAdded events for hiding the loading button

--- js-library/app/js/jail_iframe/util/create.coffee
+++ js-library/app/js/jail_iframe/util/create.coffee
@@ -10,7 +10,7 @@
 FactlinkJailRoot.createFactFromSelection = ->
   success = ->
     FactlinkJailRoot.createButton.hide()
-    FactlinkJailRoot.off 'modalOpened', success
+    FactlinkJailRoot.off 'factlinkAdded', success

   selInfo = FactlinkJailRoot.getSelectionInfo()

@@ -19,7 +19,7 @@
   siteTitle = selInfo.title
   guided = !!FactlinkConfig.guided

-  FactlinkJailRoot.on 'modalOpened', success
+  FactlinkJailRoot.on 'factlinkAdded', success
   FactlinkJailRoot.factlinkCoreEnvoy 'prepareNewFactlink', text, siteUrl, siteTitle, guided

 # We make this a global function so it can be used for direct adding of facts


webpack: not including smc version, but instead exact timestamp (that will be used to compare with smc version!)

--- src/webpack.config.coffee
+++ src/webpack.config.coffee
@@ -1,3 +1,5 @@
+# SageMathCloud, by SageMath, Inc., (c) 2016 -- License: GPLv3
+
 ###
 # Webpack configuration file

@@ -52,7 +54,16 @@
 information about where the page is getting rendered to, because also the hub.coffee needs to know
 about certain file locations)

-MathJax: It lifes in its own isolated world. This means, don't mess with the MathJax.js ...
+Development vs. Production: There are two variables DEVMODE and PRODMODE.
+* Produmode:
+  * additional compression is enabled (do *not* add the -p switch to webpack, that's done here!)
+  * all output filenames, except for the essential .html files, do have hashes and a flat hierarchy.
+* Devmode:
+  * as little additional plugins as possible (runs faster)
+  * file names have no hashes, or hashes based on the content. This means, when running
+    webpack-watch, you do not end up with thousands of files in the output directory.
+
+MathJax: It lives in its own isolated world. This means, don't mess with the MathJax.js ...
 it needs to know from where it is loaded (the path), to retrieve many additional files on demand.
 That's also the main reason why it is slow, because for each file a new SSL connection has to be setup!
 (even, if the sever just says 302 not modified). Still, we help it a little bit by caching it, too.
@@ -60,7 +71,7 @@
 trigger a reload after an update. The MathjaxVersionedSymlink in combination with misc_node.MATHJAX_LIB
 does extract the MathJax version number, computes the path, and does symlink to it's location.
 Why in misc_node? The problem is, that also the jupyter server (in its isolated iframe),
-needs to know about the URL. That way, the hub can send down the URL to the server (there is no webapp client in between).
+needs to know about the URL. That way, the hub can send down the URL to the jupyter server (there is no webapp client in between).
 ###

 'use strict'
@@ -76,10 +87,6 @@

 git_head      = child_process.execSync("git rev-parse HEAD")
 GIT_REV       = git_head.toString().trim()
-[err, SMC_VERSION] = misc_node.get_smc_version_sync()
-if err?
-    console.error "get_smc_version_sync error: #{err}"
-    process.exit(1)
 TITLE         = 'SageMathCloud'
 SMC_REPO      = 'https://github.com/sagemathinc/smc'
 SMC_LICENSE   = 'GPLv3'
@@ -90,7 +97,9 @@
 NODE_ENV      = process.env.NODE_ENV || DEVEL
 PRODMODE      = NODE_ENV != DEVEL
 DEVMODE       = not PRODMODE
-dateISO       = new Date().toISOString()
+date          = new Date()
+BUILD_DATE    = date.toISOString()
+BUILD_TS      = date.getTime()

 # create a file base_url to set a base url
 BASE_URL      = misc_node.BASE_URL
@@ -102,9 +111,9 @@
 console.log "OUTPUT      ='#{OUTPUT}'"

 # mathjax version → symlink with version info from package.json/version
-MATHJAX_URL     = misc_node.MATHJAX_URL  # from where the files are served
-MATHJAX_ROOT    = misc_node.MATHJAX_ROOT # where the symlink originates
-MATHJAX_LIB     = misc_node.MATHJAX_LIB  # where the symlink points to
+MATHJAX_URL    = misc_node.MATHJAX_URL  # from where the files are served
+MATHJAX_ROOT   = misc_node.MATHJAX_ROOT # where the symlink originates
+MATHJAX_LIB    = misc_node.MATHJAX_LIB  # where the symlink points to
 console.log "MATHJAX_URL  = '#{MATHJAX_URL}'"
 console.log "MATHJAX_ROOT = '#{MATHJAX_ROOT}'"
 console.log "MATHJAX_LIB  = '#{MATHJAX_LIB}'"
@@ -112,7 +121,7 @@
 banner = new webpack.BannerPlugin(
                         """\
                         This file has been created by #{TITLE}.
-                        It was compiled #{dateISO} at revision #{GIT_REV} and version #{SMC_VERSION}.
+                        It was compiled #{BUILD_DATE} at revision #{GIT_REV} and version #{SMC_VERSION}.
                         See #{SMC_REPO} for its #{SMC_LICENSE} code.
                         """)

@@ -150,9 +159,10 @@
                         fullPath   : no
                         prettyPrint: true
                         metadata:
-                            git_ref : GIT_REV
-                            version : SMC_VERSION
-                            date    : dateISO
+                            git_ref   : GIT_REV
+                            version   : SMC_VERSION
+                            built     : BUILD_DATE
+                            timestamp : BUILD_TS

 # https://www.npmjs.com/package/html-webpack-plugin
 HtmlWebpackPlugin = require('html-webpack-plugin')
@@ -176,7 +186,7 @@

 # this is the main indes.html file, which should be served without any caching
 jade2html = new HtmlWebpackPlugin
-                        date     : dateISO
+                        date     : BUILD_DATE
                         title    : TITLE
                         git_rev  : GIT_REV
                         mathjax  : MATHJAX_URL
@@ -274,7 +284,8 @@
                                 'MATHJAX_URL' : JSON.stringify(MATHJAX_URL)
                                 'SMC_VERSION' : JSON.stringify(SMC_VERSION)
                                 'SMC_GIT_REV' : JSON.stringify(GIT_REV)
-                                'BUILD_DATE'  : JSON.stringify(dateISO)
+                                'BUILD_DATE'  : JSON.stringify(BUILD_DATE)
+                                'BUILD_TS'    : JSON.stringify(BUILD_TS)

 {StatsWriterPlugin} = require("webpack-stats-plugin")
 statsWriterPlugin   = new StatsWriterPlugin(filename: "webpack-stats.json")
@@ -426,4 +437,4 @@
         minifyJS : true
         minifyCSS : true
         collapseWhitespace : true
-        conservativeCollapse : true+        conservativeCollapse : true


Make GOG import work from URLs which use a cookie to choose display mode

--- itad_importer.user.coffee
+++ itad_importer.user.coffee
@@ -43,7 +43,7 @@
 // @require https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js
 //
 // @match *://www.dotemu.com/*
-// @match *://secure.gog.com/account/games*
+// @match *://secure.gog.com/account*
 // @match *://www.humblebundle.com/home*
 // @match *://indiegamestand.com/wallet.php
 // @match *://www.shinyloot.com/m/games*
@@ -89,50 +89,49 @@
         .appendTo('.my-games h2.pane-title')

   'secure.gog.com' :
-    # **TODO:** Figure out a way to detect which mode is in use so we can
-    # integrate with the cookie-powered /games URL.
-    'https://secure\.gog\.com/account/games/shelf/?' :
+    'https://secure\.gog\.com/account(/games(/(shelf|list))?)?/?' :
       'source_id': 'gog'
-      'game_list' : -> {
-      # The shelf view mode only sees game IDs and slugs easily
-      id: attr(x, 'data-gameid')
-      url: ('http://www.gog.com/en/game/' +
-          attr(x, 'data-gameindex'))
-      sources: ['gog']
-      } for x in $('[data-gameindex]')
-
-      'insert_button': ->
-        $("</button>")
-        # Use GOG's style but tweak it and force *both* ends round
-        .css({
-        float: 'right'
-        borderRadius: '9px'
-        opacity: 0.7
-        marginTop: '15px'
-        marginRight: '-32px'
-        })
-        .html(BUTTON_LABEL)
-        .prependTo($('.shelf_header').filter(':first'))
-
-    'https://secure\.gog\.com/account/games/list/?' :
-      'source_id': 'gog'
-      'game_list' : -> {
-        id: $(x).closest('.game-item').attr('id').substring(8)
-        # The list view mode only sees game titles easily
-        title: x.textContent.trim()
-        sources: ['gog']
-        } for x in $('.game-title-link')
-
-      'insert_button': ->
-        # Use GOG's style class but force it to be its own tweaked
-        # button group because their style won't round both ends
-        # of the same button
-        $("")
-        .css({ float: 'right', borderRadius: '9px' })
-        .html(BUTTON_LABEL)
-        # Prevent it from throwing off the other group
-        .wrap('')
-        .appendTo('.list_header')
+      'game_list' : ->
+        if $('.shelf_container').length > 0
+          {
+          # The shelf view mode only sees game IDs and slugs easily
+          id: attr(x, 'data-gameid')
+          url: ('http://www.gog.com/en/game/' +
+              attr(x, 'data-gameindex'))
+          sources: ['gog']
+          } for x in $('[data-gameindex]')
+
+        else if $('.games_list').length > 0
+          {
+          id: $(x).closest('.game-item').attr('id').substring(8)
+          # The list view mode only sees game titles easily
+          title: x.textContent.trim()
+          sources: ['gog']
+          } for x in $('.game-title-link')
+
+      'insert_button': ->
+        if $('.shelf_container').length > 0
+          $("</button>")
+          # Use GOG's style but tweak it and force *both* ends round
+          .css({
+          float: 'right'
+          borderRadius: '9px'
+          opacity: 0.7
+          marginTop: '15px'
+          marginRight: '-32px'
+          })
+          .html(BUTTON_LABEL)
+          .prependTo($('.shelf_header').filter(':first'))
+        else if $('.games_list').length > 0
+          # Use GOG's style class but force it to be its own tweaked
+          # button group because their style won't round both ends
+          # of the same button
+          $("")
+          .css({ float: 'right', borderRadius: '9px' })
+          .html(BUTTON_LABEL)
+          # Prevent it from throwing off the other group
+          .wrap('')
+          .appendTo('.list_header')

   'www.humblebundle.com' :
     'https://www\.humblebundle\.com/home/?' :


Cleaned up dead code

Cleaned up dead code

--- src/views/route-settings.coffee
+++ src/views/route-settings.coffee
@@ -144,8 +144,6 @@
             @pendingPosition = @permanentModel.pendingPosition
             @currentUnit = attrs.unit
             @editing = false
-            @$originInput = null
-            @$destinationInput = null
             @_reset()

         _reset: ->
@@ -312,13 +310,6 @@
                 @applyChanges()
                 @render()

-        _getModelFromEvent: (ev) ->
-            switch $(ev.currentTarget).attr 'data-endpoint'
-                when 'origin'
-                    @model.getOrigin()
-                when 'destination'
-                    @model.getDestination()
-
         _setInputValue: (input, value) ->
             input.focus().val value
             # This is for IE 10+


Remove unused require

Remove unused require

--- spec/update-package-dependencies-spec.coffee
+++ spec/update-package-dependencies-spec.coffee
@@ -1,6 +1,5 @@
 os = require 'os'
 path = require 'path'
-UpdatePackageDependencies = require '../lib/update-package-dependencies'

 describe "Update Package Dependencies", ->
   [workspaceElement, mainModule, projectPath] = []


修正cola.findDropDown部分情况下不可用的BUG

修正cola.findDropDown部分情况下不可用的BUG

--- src/widget/edit/dropdown.coffee
+++ src/widget/edit/dropdown.coffee
@@ -2,7 +2,7 @@

 cola.findDropDown = (target)->
 	layer = cola.findWidget(target, cola.AbstractLayer, true)
-	while layer and not layer.hasClass("drop-container")
+	while layer and not layer._dropdown
 		layer = cola.findWidget(layer, cola.AbstractLayer, true)
 	return layer?._dropdown


parse stack items and registers

parse stack items and registers

--- server/import-api.coffee
+++ server/import-api.coffee
@@ -16,19 +16,33 @@
 parse_stack_trace = (tracefile, cb) ->
     console.log "parsing stack trace:#{tracefile.path}" #debug
     stack_trace = []
-    line_num = 0
+    registers   = {}
+    parse_state = "stack" #assumes file begins with stack traces
     lazy_parser = new lazy(fs.createReadStream(tracefile.path))

     lazy_parser.on 'end', () ->
-        cb null,stack_trace
+        cb null,
+            "stack_trace": stack_trace
+            "registers"  : registers

     lazy_parser.lines
                .forEach (line) ->
-                    line_num++
                     stack_line = line.toString()
-                    #console.log "line(#{line_num}):" + stack_line #debug
-                    stack_trace.push(stack_line)
-    return
+
+                    parse_state = "ignore" if stack_line.match(/^\s*$/) && parse_state == "registers"
+                    parse_state = "registers" if stack_line.match /^Registers\:/
+
+                    if parse_state == "stack" && !stack_line.match /^\s*$/ #ignores empty rows between stacks
+                        type = "note"
+                        type = "stack_item" if stack_line.match /^\#\d+\s+.*$/
+                        stack_trace.push
+                            "type": type
+                            "data": stack_line
+
+                    if parse_state == "registers" && stack_line.match /^(\w+)\s+([0-9A-Fa-fx]+)\s+(\d+)$/ # e.g. "r7 0xa8 168"
+                        registers[RegExp.$1] =
+                            "hex": RegExp.$2
+                            "dec": RegExp.$3

 parse_files = (files, cb) ->
     parse_stack_trace files["stack-trace"], (err, trace_arr) ->


Fix missing methods incUnreadForRoomIdExcludingUserId and incUnreadForRoomIdAndUserIds

--- packages/rocketchat-lib/server/models/Subscriptions.coffee
+++ packages/rocketchat-lib/server/models/Subscriptions.coffee
@@ -149,6 +149,35 @@

 		return @update query, update, { multi: true }

+	incUnreadForRoomIdExcludingUserId: (roomId, userId, inc=1) ->
+		query =
+			rid: roomId
+			'u._id':
+				$ne: userId
+
+		update =
+			$set:
+				alert: true
+				open: true
+			$inc:
+				unread: inc
+
+		return @update query, update, { multi: true }
+
+	incUnreadForRoomIdAndUserIds: (roomId, userIds, inc=1) ->
+		query =
+			rid: roomId
+			'u._id':
+				$in: userIds
+
+		update =
+			$set:
+				alert: true
+				open: true
+			$inc:
+				unread: inc
+
+		return @update query, update, { multi: true }

 	setAlertForRoomIdExcludingUserId: (roomId, userId, alert=true) ->
 		query =


Use rule stack returned from tokenizeline

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -464,9 +464,8 @@
     describe "when a line has more tokens than `maxTokensPerLine`", ->
       it "creates a final token with the remaining text and resets the ruleStack to match the begining of the line", ->
         grammar = registry.grammarForScopeName('source.js')
-        grammar.tokenizeLine('')
+        originalRuleStack = grammar.tokenizeLine('').ruleStack
         spyOn(grammar, 'getMaxTokensPerLine').andCallFake -> 5
-        originalRuleStack = [grammar.initialRule, grammar.initialRule, grammar.initialRule]
         {tokens, ruleStack} = grammar.tokenizeLine("var x = /[a-z]/;", originalRuleStack)
         expect(tokens.length).toBe 6
         expect(tokens[5].value).toBe "[a-z]/;"