MachineSettingsDomainsView: Error and warning handling.

--- client/app/lib/providers/machinesettingsdomainsview.coffee
+++ client/app/lib/providers/machinesettingsdomainsview.coffee
@@ -67,17 +67,16 @@
     machineId = @machine._id

     if @listController.getItemCount() >= 5
-      # @warning.setTooltip
-      #   title: "It's not allowed to create more than 5 domains."
-      # @warning.show()
-      kd.warn "It's not allowed to create more than 5 domains."
-      return
+      warning = "It's not allowed to create more than 5 domains."
+      @showNotification warning, 'warning'
+      @addNewButton.hideLoader()
+      return kd.warn warning

     @isInProgress = yes
     @addInputView.makeDisabled()
     @addNewButton.showLoader()

-    # @warning.hide()
+    @notificationView.hide()

     computeController.getKloud()

@@ -93,8 +92,7 @@

       .catch (err)=>
         kd.warn 'Failed to create domain:', err
-        # @warning.setTooltip title: err.message
-        # @warning.show()
+        @showNotification err.message
         @addInputView.makeEnabled()
         @addInputView.setFocus()
         @addNewButton.hideLoader()
@@ -112,7 +110,7 @@
     { domain }  = domainItem.getData()
     machineId   = @machine._id

-    # @warning.hide()
+    @notificationView.hide()
     domainItem.setLoadingMode yes

     computeController.getKloud()
@@ -126,15 +124,14 @@
       .catch (err) =>
         kd.warn 'Failed to remove domain:', err
         domainItem.setLoadingMode no
-        # @warning.setTooltip title: err.message
-        # @warning.show()
+        @showNotification err.message

   handleStateChange: (domainItem, state) ->

     domainItem.setLoadingMode yes

-    # @warning.hide()
+    @notificationView.hide()

     @askForPermission domainItem, state, (approved) =>
       if approved
@@ -159,8 +156,7 @@

       .catch (err) =>
         kd.warn "Failed to change domain state:", err
-        # @warning.setTooltip title: err.message
-        # @warning.show()
+        @showNotification err.message

         @revertToggle domainItem, state
         domainItem.setLoadingMode no


home/billing: show form values if there is no card

--- client/home/lib/billing/flux/cardformvalues/getters.coffee
+++ client/home/lib/billing/flux/cardformvalues/getters.coffee
@@ -8,13 +8,16 @@
   plainValues
   PaymentFlux.getters.paymentValues
   (formValues, paymentValues) ->
-    card = paymentValues.get 'groupCreditCard'

     if isEdited(formValues)
       return formValues

     # groupPlan = paymentValues.get 'groupPlan'
     # return defaultValues()  unless groupPlan
+
+    card = paymentValues.get 'groupCreditCard'
+
+    return formValues  unless card

     formValues.withMutations (formValues) ->
       formValues


UI Copy: "Still waiting... This VM is booting slowly."

If you guys don't like this, let me know and I will think of something else

--- client/ide/filetree/itemsubviews/vmtogglebuttonview.coffee
+++ client/ide/filetree/itemsubviews/vmtogglebuttonview.coffee
@@ -23,7 +23,7 @@
         type    : "mini"
         cssClass: "error"
         duration: 5000
-        title   : "I cannot turn this machine on, please give it a few seconds."
+        title   : "Still waiting... This VM is booting slowly."

       @toggle.setDefaultValue no
       # KD.utils.notifyAndEmailVMTurnOnFailureToSysAdmin vm, err.message


kite/stats: make DiskUsage to use sigar instead of custom shell command

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -270,14 +270,23 @@
       @info vm, callback? rest...

   fetchDiskUsage:(vmName, callback = noop)->
-    command = """df | grep aufs | awk '{print $2, $3}'"""
-    @run { vmName, withArgs:command }, (err, res)->
-      if err or not res then [max, current] = [0, 0]
-      else [max, current] = res.trim().split " "
-      warn err  if err
-      callback
-        max     : 1024 * parseInt max, 10
-        current : 1024 * parseInt current, 10
+    if vmName is "local-#{KD.whoami().profile.nickname}"
+      @info vmName, (err, vm, info)->
+        if err or info.state isnt "RUNNING" then [max, current] = [0, 0]
+        else [max, current] = [info.diskTotal, info.diskUsage]
+        warn err  if err
+        callback
+          max     : 1024 * parseInt max, 10
+          current : 1024 * parseInt current, 10
+    else
+      command = """df | grep aufs | awk '{print $2, $3}'"""
+      @run { vmName, withArgs:command }, (err, res)->
+        if err or not res then [max, current] = [0, 0]
+        else [max, current] = res.trim().split " "
+        warn err  if err
+        callback
+          max     : 1024 * parseInt max, 10
+          current : 1024 * parseInt current, 10

   fetchRamUsage:(vmName, callback = noop)->
     @info vmName, (err, vm, info)->


Add askToTurnOn helper method to turn VM on globally

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -37,3 +37,30 @@
     return (rest...)=>
       @info callback? rest...

+  askToTurnOn:(appName='', callback)->
+
+    content = """To #{if appName then 'run' else 'do this'} #{appName}
+                 you need to turn on your VM first, you can do that by
+                 clicking 'Turn ON VM' button below."""
+
+    modal = new KDModalView
+      title          : "Your VM is turned off"
+      content        : "

#{content}

"
+      height         : "auto"
+      overlay        : yes
+      buttons        :
+        'Turn ON VM' :
+          style      : "modal-clean-green"
+          callback   : =>
+            @start =>
+              modal.destroy()
+              if appName
+                @once 'StateChanged', ->
+                  appManager.open appName
+        Cancel       :
+          style      : "modal-clean-gray"
+          callback   : ->
+            modal.destroy()
+            callback?()
+
+    modal.once 'KDModalViewDestroyed', -> callback?()


No need to fetchDefaultVmName for info, kudos to Chris T.

Conflicts:

	client/app/MainApp/VirtualizationController.coffee

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -98,17 +98,11 @@
           callback message: "No such VM!"

   info:(vm, callback)->
-    [callback, vm] = [vm, callback]  if typeof vm is 'function'
-    @fetchDefaultVmName (defaultVm)=>
-      vm or= defaultVm
-      @_runWrapper 'vm.info', vm, (err, info)=>
-        warn "[VM-#{vm}]", err  if err
-        if err?.name is "UnderMaintenanceError"
-          info = state: "MAINTENANCE"
-          err  = null
-        @emit 'StateChanged', err, vm, info
-        callback? err, vm, info
-      , no
+    [callback, vm] = [vm, callback]  unless callback
+    @_runWrapper 'vm.info', vm, (err, info)=>
+      warn "[VM-#{vm}]", err  if err
+      @emit 'StateChanged', err, vm, info
+      callback? err, vm, info

   fetchRegion: (vmName, callback)->
     if region = @vmRegions[vmName]


Angular 1.3 jqLite does not provide .get().

--- app/directives.coffee
+++ app/directives.coffee
@@ -21,7 +21,7 @@
   .directive 'xoClick', ($parse) ->
     ($scope, $element, attrs) ->
       fn = $parse attrs.xoClick
-      current = $element.get(0)
+      current = $element[0]
       current.addEventListener(
         'click'
         (event) ->
@@ -57,7 +57,7 @@
   # TODO: Mutualize code with `xoClick`.
   .directive 'xoSref', ($state, $window) ->
     ($scope, $element, attrs) ->
-      current = $element.get(0)
+      current = $element[0]
       current.addEventListener(
         'mouseup'
         (event) ->


ensure we pass a delay argument thru to the tranport from the client

--- lib/assets/javascripts/firehose/consumer.js.coffee
+++ lib/assets/javascripts/firehose/consumer.js.coffee
@@ -33,7 +33,8 @@
     # Make sure we return ourself out of the constructor so we can chain.
     this

-  connect: =>
+  connect: (delay=0) =>
+    console?.log "Consumer#connect", delay
     # Get a list of transports that the browser supports
     supportedTransports = (transport for transport in @config.transports when transport.supported())
     # Mmmkay, we've got transports supported by the browser, now lets try connecting
@@ -46,8 +47,8 @@
       @config.failed = =>
         # Map the next transport to connect inside of the current transport failures
         if nextTransport = supportedTransports.pop()
-          new nextTransport(@config).connect()
+          new nextTransport(@config).connect delay
         else originalFailFun?()
       new transport(@config)
     # Fire off the first connection attempt.
-    transports[0].connect()
+    transports[0].connect delay


Log connection error instead of throwing exception if console is open - Do not completely die when console is open and websocket connection fails - See https://github.com/polleverywhere/firehose/issues/38

--- lib/assets/javascripts/firehose/consumer.js.coffee
+++ lib/assets/javascripts/firehose/consumer.js.coffee
@@ -12,7 +12,10 @@
     # transport, like WebSockets is supported by the browser, but for whatever
     # reason it can't connect (probably a firewall)
     @config.failed       ||= ->
-      throw "Could not connect"
+      if console?
+        console.log "Could not connect"
+      else
+        throw "Could not connect"
     # Params that we'll tack on to the URL.
     @config.params       ||= {}
     # Do stuff before we send the message into config.message. The sensible


Use embedded Python v3.8.1

Use embedded Python v3.8.1

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -343,7 +343,7 @@
   return false

 installPython = (callback) ->
-  pyVer = '3.5.2'
+  pyVer = '3.8.1'
   arch = 'win32'
   if os.arch().indexOf('x64') > -1
     arch = 'amd64'


save api key to config file if set in Atom on startup

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -77,7 +77,7 @@
     setupConfigs()
     statusBarTileView?.setTitle('WakaTime initialized')
     statusBarTileView?.setStatus()
-    @settingChangedObserver = atom.config.observe 'wakatime', @settingChangedHandler
+    @settingChangedObserver = atom.config.observe 'wakatime', settingChangedHandler

   consumeStatusBar: (statusBar) ->
     statusBarTileView = new StatusBarTileView()
@@ -94,47 +94,47 @@
     statusBarTileView?.destroy()
     @settingChangedObserver?.dispose()

-  settingChangedHandler: (settings) ->
-    if settings.showStatusBarIcon
-      statusBarTileView?.show()
-    else
-      statusBarTileView?.hide()
-    if pluginReady
-      apiKey = settings.apikey
-      if isValidApiKey(apiKey)
-        atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
-        atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'
-        saveApiKey apiKey
+settingChangedHandler = (settings) ->
+  if settings.showStatusBarIcon
+    statusBarTileView?.show()
+  else
+    statusBarTileView?.hide()
+  if pluginReady
+    apiKey = settings.apikey
+    if isValidApiKey(apiKey)
+      atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
+      atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'
+      saveApiKey apiKey

 saveApiKey = (apiKey) ->
   configFile = path.join getUserHome(), '.wakatime.cfg'
   fs.readFile configFile, 'utf-8', (err, inp) ->
     if err?
-      console.warn 'Error: could not read wakatime config file'
-      inp = ''
+      console.log 'Error: could not read wakatime config file'
     String::startsWith ?= (s) -> @slice(0, s.length) == s
     String::endsWith   ?= (s) -> s == '' or @slice(-s.length) == s
     contents = []
     currentSection = ''
     found = false
-    for line in inp.split('\n')
-      if line.trim().startsWith('[') and line.trim().endsWith(']')
-        if currentSection == 'settings' and not found
-          contents.push('api_key = ' + apiKey)
-          found = true
-        currentSection = line.trim().substring(1, line.trim().length - 1).toLowerCase()
-        contents.push(line)
-      else if currentSection == 'settings'
-        parts = line.split('=')
-        currentKey = parts[0].trim()
-        if currentKey == 'api_key'
-          if not found
+    if inp?
+      for line in inp.split('\n')
+        if line.trim().startsWith('[') and line.trim().endsWith(']')
+          if currentSection == 'settings' and not found
             contents.push('api_key = ' + apiKey)
             found = true
+          currentSection = line.trim().substring(1, line.trim().length - 1).toLowerCase()
+          contents.push(line)
+        else if currentSection == 'settings'
+          parts = line.split('=')
+          currentKey = parts[0].trim()
+          if currentKey == 'api_key'
+            if not found
+              contents.push('api_key = ' + apiKey)
+              found = true
+          else
+            contents.push(line)
         else
           contents.push(line)
-      else
-        contents.push(line)

     if not found
       if currentSection != 'settings'
@@ -157,11 +157,14 @@
     pluginReady = true
     if err?
       console.log 'Error: could not read wakatime config file'
+      settingChangedHandler atom.config.get('wakatime')
       return
     commonConfigs = ini.decode configContent
     if commonConfigs? and commonConfigs.settings? and isValidApiKey(commonConfigs.settings.api_key)
       atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
       atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'
+    else
+      settingChangedHandler atom.config.get('wakatime')

 isValidApiKey = (key) ->
   if not key?


Fix perf issue

Fix perf issue

Reset loop updates when we destroy a context.

--- framer/Context.coffee
+++ framer/Context.coffee
@@ -1,122 +1,598 @@
-Utils = require "./Utils"
-
-{_} = require "./Underscore"
-{BaseClass} = require "./BaseClass"
-{Config} = require "./Config"
-{EventManager} = require "./EventManager"
-
-Counter = 1
-
-class exports.Context extends BaseClass
-
-	constructor: (options={}) ->
+"""
+
+VRComponent class
+
+properties
+- front (set: imagePath <string>, get: layer)
+- right
+- back
+- left
+- top
+- bottom
+- heading <number>
+- elevation <number>
+- tilt <number> readonly
+
+- orientationLayer <bool>
+- arrowKeys <bool>
+- lookAtLatestProjectedLayer <bool>
+
+methods
+- projectLayer(layer) # heading and elevation are set as properties on the layer
+- hideEnviroment()
+
+events
+- Events.OrientationDidChange, (data {heading, elevation, tilt})
+
+--------------------------------------------------------------------------------
+
+VRLayer class
+
+properties
+- heading <number> (from 0 up to 360)
+- elevation <number> (from -90 down to 90 up)
+
+"""
+
+SIDES = [
+	"north",
+	"front",
+	"east",
+	"right",
+	"south",
+	"back",
+	"west",
+	"left",
+	"top",
+	"bottom",
+]
+
+KEYS = {
+	LeftArrow: 37
+	UpArrow: 38
+	RightArrow: 39
+	DownArrow: 40
+}
+
+KEYSDOWN = {
+	left: false
+	up: false
+	right: false
+	down: false
+}
+
+Events.OrientationDidChange = "orientationdidchange"
+
+class VRAnchorLayer extends Layer
+
+	constructor: (layer, cubeSide) ->
+		super undefined
+		@width = 0
+		@height = 0
+		@clip = false
+		@name = "anchor"
+		@cubeSide = cubeSide
+
+		@layer = layer
+		layer.superLayer = @
+		layer.center()
+
+		layer.on "change:orientation", (newValue, layer) =>
+			@updatePosition(layer)
+		@updatePosition(layer)
+
+		layer._context.on "layer:destroy", (layer) =>
+			if layer == @layer
+				@destroy()
+
+	updatePosition: (layer) ->
+		halfCubSide = @cubeSide/2
+		@style["webkitTransform"] = "translateX(#{(@cubeSide - @width)/2}px) translateY(#{(@cubeSide - @height)/2}px) rotateZ(#{layer.heading}deg) rotateX(#{90-layer.elevation}deg) translateZ(#{halfCubSide*.9}px) rotateX(180deg)"
+
+class exports.VRLayer extends Layer
+
+	constructor: (options = {}) ->
+		options = _.defaults options,
+			heading: 0
+			elevation: 0
+		super options
+
+	@define "heading",
+		get: -> @_heading
+		set: (value) ->
+			if value >= 360
+				value = value % 360
+			else if value < 0
+				rest = Math.abs(value) % 360
+				value = 360 - rest
+			if @_heading != value
+				@_heading = value
+				@emit("change:heading", value)
+				@emit("change:orientation", value)
+
+	@define "elevation",
+		get: -> @_elevation
+		set: (value) ->
+			value = Utils.clamp(value, -90, 90)
+			if value != @_elevation
+				@_elevation = value
+				@emit("change:elevation", value)
+				@emit("change:orientation", value)
+
+class exports.VRComponent extends Layer
+
+	constructor: (options = {}) ->
+		options = _.defaults options,
+			cubeSide: 3000
+			perspective: 1200
+			lookAtLatestProjectedLayer: false
+			width: Screen.width
+			height: Screen.height
+			orientationLayer: true
+			arrowKeys: true
+		super options
+		@perspective = options.perspective
+		@backgroundColor = null
+		@createCube(options.cubeSide)
+		@degToRad = Math.PI / 180
+		@layersToKeepLevel = []
+		@lookAtLatestProjectedLayer = options.lookAtLatestProjectedLayer
+		@arrowKeys = options.arrowKeys
+		@_keys()
+
+		@_heading = 0
+		@_elevation = 0
+		@_tilt = 0
+
+		@_headingOffset = 0
+		@_elevationOffset = 0
+		@_deviceHeading = 0
+		@_deviceElevation = 0
+
+		if options.heading
+			@heading = options.heading
+		if options.elevation
+			@elevation = options.elevation
+
+		@orientationLayer = options.orientationLayer
+
+		@desktopPan(0, 0)
+
+		# tilting and panning
+		if Utils.isMobile()
+			window.addEventListener "deviceorientation", (event) =>
+				@orientationData = event
+
+		Framer.Loop.on("update", @deviceOrientationUpdate)
+
+		# Make sure we remove the update from the loop when we destroy the context
+		Framer.CurrentContext.on "reset", ->
+			Framer.Loop.off("update", @deviceOrientationUpdate)
+
+		@on "change:frame", ->
+			@desktopPan(0,0)
+
+
+	_keys: ->
+		document.addEventListener "keydown", (event) =>
+			if @arrowKeys
+				switch event.which
+					when KEYS.UpArrow
+						KEYSDOWN.up = true
+						event.preventDefault()
+					when KEYS.DownArrow
+						KEYSDOWN.down = true
+						event.preventDefault()
+					when KEYS.LeftArrow
+						KEYSDOWN.left = true
+						event.preventDefault()
+					when KEYS.RightArrow
+						KEYSDOWN.right = true
+						event.preventDefault()
+
+		document.addEventListener "keyup", (event) =>
+			if @arrowKeys
+				switch event.which
+					when KEYS.UpArrow
+						KEYSDOWN.up = false
+						event.preventDefault()
+					when KEYS.DownArrow
+						KEYSDOWN.down = false
+						event.preventDefault()
+					when KEYS.LeftArrow
+						KEYSDOWN.left = false
+						event.preventDefault()
+					when KEYS.RightArrow
+						KEYSDOWN.right = false
+						event.preventDefault()
+
+		window.onblur = ->
+			KEYSDOWN.up = false
+			KEYSDOWN.down = false
+			KEYSDOWN.left = false
+			KEYSDOWN.right = false
+
+	@define "orientationLayer",
+		get: -> return @desktopOrientationLayer != null && @desktopOrientationLayer != undefined
+		set: (value) ->
+			if @world != undefined
+				if Utils.isDesktop()
+					if value == true
+						@addDesktopPanLayer()
+					else if value == false
+						@removeDesktopPanLayer()
+
+	@define "heading",
+		get: ->
+			heading = @_heading + @_headingOffset
+			if heading > 360
+				heading = heading % 360
+			else if heading < 0
+				rest = Math.abs(heading) % 360
+				heading = 360 - rest
+			return heading
+		set: (value) ->
+			@lookAt(value, @_elevation)
+
+	@define "elevation",
+		get: -> @_elevation
+		set: (value) -> @lookAt(@_heading, value)
+
+	@define "tilt",
+		get: -> @_tilt
+		set: (value) -> throw "Tilt is readonly"
+
+	SIDES.map (face) =>
+		@define face,
+			get: -> @layerFromFace(face) # @getImage(face)
+			set: (value) -> @setImage(face, value)
+
+	createCube: (cubeSide = @cubeSide) =>
+		@cubeSide = cubeSide
+
+		@world?.destroy()
+		@world = new Layer
+			name: "world"
+			superLayer: @
+			width: cubeSide, height: cubeSide
+			backgroundColor: null
+			clip: false
+		@world.style.webkitTransformStyle = "preserve-3d"
+		@world.center()
+
+		halfCubSide = @cubeSide/2
+
+		@side0 = new Layer
+		@side0.style["webkitTransform"] = "rotateX(-90deg) translateZ(-#{halfCubSide}px)"
+		@side1 = new Layer
+		@side1.style["webkitTransform"] = "rotateY(-90deg) translateZ(-#{halfCubSide}px) rotateZ(90deg)"
+		@side2 = new Layer
+		@side2.style["webkitTransform"] = "rotateX(90deg) translateZ(-#{halfCubSide}px) rotateZ(180deg)"
+		@side3 = new Layer
+		@side3.style["webkitTransform"] = "rotateY(90deg) translateZ(-#{halfCubSide}px) rotateZ(-90deg)"
+		@side4 = new Layer
+		@side4.style["webkitTransform"] = "rotateY(-180deg) translateZ(-#{halfCubSide}px) rotateZ(180deg)"
+		@side5 = new Layer
+		@side5.style["webkitTransform"] = "translateZ(-#{halfCubSide}px)"
+
+		@sides = [@side0, @side1, @side2, @side3, @side4, @side5]
+		colors = ["#866ccc", "#28affa", "#2dd7aa", "#ffc22c", "#7ddd11", "#f95faa"]
+		sideNames = ["front", "right", "back", "left", "top", "bottom"]
+
+		index = 0
+		for side in @sides
+			side.name = sideNames[index]
+			side.width = side.height = cubeSide
+			side.superLayer = @world
+			side.html = sideNames[index]
+			side.color = "white"
+			side._backgroundColor = colors[index]
+			side.backgroundColor = colors[index]
+			side.style =
+				lineHeight: "#{cubeSide}px"
+				textAlign: "center"
+				fontSize: "#{cubeSide / 10}px"
+				fontWeight: "100"
+				fontFamily: "Helvetica Neue"
+			index++
+
+		if @sideImages
+			for key of @sideImages
+				@setImage key, @sideImages[key]
+
+	hideEnviroment: ->
+		for side in @sides
+			side.destroy()
+
+	layerFromFace: (face) ->
+		map =
+			north: @side0
+			front: @side0
+			east:  @side1
+			right: @side1
+			south: @side2
+			back:  @side2
+			west:  @side3
+			left:  @side3
+			top:   @side4
+			bottom:@side5
+		return map[face]
+
+	setImage: (face, imagePath) ->

-		super
-
-		Counter++
-
-		options = _.defaults options,
-			contextName: null
-			parentLayer: null
-			name: null
-
-		if not options.name
-			throw Error("Contexts need a name")
-
-		@_parentLayer = options.parentLayer
-		@_name = options.name
+		if not face in SIDES
+			throw Error "VRComponent setImage, wrong name for face: " + face + ", valid options: front, right, back, left, top, bottom, north, east, south, west"
+
+		if not @sideImages
+			@sideImages = {}
+		@sideImages[face] = imagePath
+
+		layer = @layerFromFace(face)

-		@reset()
-
-	reset: ->
-
-		@eventManager?.reset()
-		@eventManager = new EventManager
-
-		if @_rootElement
-			# Clean up the current root element:
-			if @_rootElement.parentNode
-				# Already attached to the DOM - remove it:
-				@_rootElement.parentNode.removeChild(@_rootElement)
-			else
-				# Not on the DOM yet. Prevent it from being added (for this happens
-				# async):
-				@_rootElement.__cancelAppendChild = true
-
-		# Create a fresh root element:
-		@_createRootElement()
-
-		@_delayTimers?.map (timer) -> window.clearTimeout(timer)
-		@_delayIntervals?.map (timer) -> window.clearInterval(timer)
-
-		if @_animationList
-			for animation in @_animationList
-				animation.stop(false)
-
-		@_layerList = []
-		@_animationList = []
-		@_delayTimers = []
-		@_delayIntervals = []
-		@_layerIdCounter = 1
-
-		@emit("reset", @)
-
-	destroy: ->
-		@reset()
-		if @_rootElement.parentNode
-			@_rootElement.parentNode.removeChild(@_rootElement)
-		Utils.domCompleteCancel(@_appendRootElement)
-
-	getRootElement: ->
-		@_rootElement
-
-	getLayers: ->
-		_.clone(@_layerList)
-
-	addLayer: (layer) ->
-		return if layer in @_layerList
-		@_layerList.push(layer)
-		return null
-
-	removeLayer: (layer) ->
-		@_layerList = _.without(@_layerList, layer)
-		return null
-
-	layerCount: ->
-		return @_layerList.length
-
-	nextLayerId: ->
-		@_layerIdCounter++
-
-	_createRootElement: ->
-
-		@_rootElement = document.createElement("div")
-		@_rootElement.id = "FramerContextRoot-#{@_name}"
-		@_rootElement.classList.add("framerContext")
-
-		if @_parentLayer
-			@_appendRootElement()
+		if imagePath
+			layer?.html = ""
+			layer?.image = imagePath
 		else
-			Utils.domComplete(@_appendRootElement)
-
-	_appendRootElement: =>
-		parentElement = @_parentLayer?._element
-		parentElement ?= document.body
-		parentElement.appendChild(@_rootElement)
-
-	run: (f) ->
-		previousContext = Framer.CurrentContext
-		Framer.CurrentContext = @
-		f()
-		Framer.CurrentContext = previousContext
-
-	@define "width",
-		get: ->
-			return @_parentLayer.width if @_parentLayer
-			return window.innerWidth
-
-	@define "height",
-		get: ->
-			return @_parentLayer.height if @_parentLayer
-			return window.innerHeight
-
+			layer?.html = layer?.name
+			layer?.backgroundColor = layer?._backgroundColor
+
+	getImage: (face) ->
+
+		if not face in SIDES
+			throw Error "VRComponent getImage, wrong name for face: " + face + ", valid options: front, right, back, left, top, bottom, north, east, south, west"
+
+		layer = @layerFromFace(face)
+		if layer
+			layer.image
+
+	projectLayer: (insertLayer) ->
+
+		heading = insertLayer.heading
+		if heading == undefined
+			heading = 0
+		elevation = insertLayer.elevation
+		if elevation == undefined
+			elevation = 0
+
+		if heading >= 360
+			heading = value % 360
+		else if heading < 0
+			rest = Math.abs(heading) % 360
+			heading = 360 - rest
+
+		elevation = Utils.clamp(elevation, -90, 90)
+
+		insertLayer.heading = heading
+		insertLayer.elevation = elevation
+
+		anchor = new VRAnchorLayer(insertLayer, @cubeSide)
+		anchor.superLayer = @world
+
+		if @lookAtLatestProjectedLayer
+			@lookAt(heading, elevation)
+
+	# Mobile device orientation
+
+	deviceOrientationUpdate: =>
+
+		if Utils.isDesktop()
+			if @arrowKeys
+				if @_lastCallHorizontal == undefined
+					@_lastCallHorizontal = 0
+					@_lastCallVertical = 0
+					@_accelerationHorizontal = 1
+					@_accelerationVertical = 1
+					@_goingUp = false
+					@_goingLeft = false
+
+				date = new Date()
+				x = .1
+				if KEYSDOWN.up || KEYSDOWN.down
+					diff = date - @_lastCallVertical
+					if diff < 30
+						if @_accelerationVertical < 30
+							@_accelerationVertical += 0.18
+					if KEYSDOWN.up
+						if @_goingUp == false
+							@_accelerationVertical = 1
+							@_goingUp = true
+						@desktopPan(0, 1 * @_accelerationVertical * x)
+					else
+						if @_goingUp == true
+							@_accelerationVertical = 1
+							@_goingUp = false
+
+						@desktopPan(0, -1 * @_accelerationVertical * x)
+					@_lastCallVertical = date
+
+				else
+					@_accelerationVertical = 1
+
+				if KEYSDOWN.left || KEYSDOWN.right
+					diff = date - @_lastCallHorizontal
+					if diff < 30
+						if @_accelerationHorizontal < 25
+							@_accelerationHorizontal += 0.18
+					if KEYSDOWN.left
+						if @_goingLeft == false
+							@_accelerationHorizontal = 1
+							@_goingLeft = true
+						@desktopPan(1 * @_accelerationHorizontal * x, 0)
+					else
+						if @_goingLeft == true
+							@_accelerationHorizontal = 1
+							@_goingLeft = false
+						@desktopPan(-1 * @_accelerationHorizontal * x, 0)
+					@_lastCallHorizontal = date
+				else
+					@_accelerationHorizontal = 1
+
+		else if @orientationData
+
+			alpha = @orientationData.alpha
+			beta = @orientationData.beta
+			gamma = @orientationData.gamma
+
+			if alpha != 0 && beta != 0 && gamma != 0
+				@directionParams(alpha, beta, gamma)
+
+			xAngle = beta
+			yAngle = -gamma
+			zAngle = alpha
+
+			halfCubSide = @cubeSide/2
+			orientation = "rotate(#{window.orientation * -1}deg) "
+			translationX = "translateX(#{(@width / 2) - halfCubSide}px)"
+			translationY = " translateY(#{(@height / 2) - halfCubSide}px)"
+			translationZ = " translateZ(#{@perspective}px)"
+			rotation = translationZ + translationX + translationY + orientation + " rotateY(#{yAngle}deg) rotateX(#{xAngle}deg) rotateZ(#{zAngle}deg)" + " rotateZ(#{-@_headingOffset}deg)"
+			@world.style["webkitTransform"] = rotation
+
+	directionParams: (alpha, beta, gamma) ->
+
+		alphaRad = alpha * @degToRad
+		betaRad = beta * @degToRad
+		gammaRad = gamma * @degToRad
+
+		# Calculate equation components
+		cA = Math.cos(alphaRad)
+		sA = Math.sin(alphaRad)
+		cB = Math.cos(betaRad)
+		sB = Math.sin(betaRad)
+		cG = Math.cos(gammaRad)
+		sG = Math.sin(gammaRad)
+
+		# x unitvector
+		xrA = -sA * sB * sG + cA * cG
+		xrB = cA * sB * sG + sA * cG
+		xrC = cB * sG
+
+		# y unitvector
+		yrA = -sA * cB
+		yrB = cA * cB
+		yrC = -sB
+
+		# -z unitvector
+		zrA = -sA * sB * cG - cA * sG
+		zrB = cA * sB * cG - sA * sG
+		zrC = cB * cG
+
+		# Calculate heading
+		heading = Math.atan(zrA / zrB)
+
+		# Convert from half unit circle to whole unit circle
+		if zrB < 0
+			heading += Math.PI
+		else if zrA < 0
+			heading += 2 * Math.PI
+
+		# # Calculate Altitude (in degrees)
+		elevation = Math.PI / 2 - Math.acos(-zrC)
+
+		cH = Math.sqrt(1 - (zrC * zrC))
+		tilt = Math.acos(-xrC / cH) * Math.sign(yrC)
+
+		# Convert radians to degrees
+		heading *= 180 / Math.PI
+		elevation *= 180 / Math.PI
+		tilt *= 180 / Math.PI
+
+		@_heading = Math.round(heading * 1000) / 1000
+		@_elevation = Math.round(elevation * 1000) / 1000
+
+		tilt = Math.round(tilt * 1000) / 1000
+		orientationTiltOffset = (window.orientation * -1) + 90
+		tilt += orientationTiltOffset
+		if tilt > 180
+			diff = tilt - 180
+			tilt = -180 + diff
+		@_tilt = tilt
+
+		@_deviceHeading = @_heading
+		@_deviceElevation = @_elevation
+
+		@_emitOrientationDidChangeEvent()
+
+	# Desktop tilt
+
+	removeDesktopPanLayer: =>
+		@desktopOrientationLayer?.destroy()
+
+	addDesktopPanLayer: =>
+		@desktopOrientationLayer?.destroy()
+		@desktopOrientationLayer = new Layer
+			width: 100000, height: 10000
+			backgroundColor: null
+			superLayer:@
+			name: "desktopOrientationLayer"
+		@desktopOrientationLayer.center()
+		@desktopOrientationLayer.draggable.enabled = true
+
+		@prevDesktopDir = @desktopOrientationLayer.x
+		@prevDesktopHeight = @desktopOrientationLayer.y
+
+		@desktopOrientationLayer.on Events.DragStart, =>
+			@prevDesktopDir = @desktopOrientationLayer.x
+			@prevDesktopHeight = @desktopOrientationLayer.y
+			@desktopDraggableActive = true
+
+		@desktopOrientationLayer.on Events.Move, =>
+			if @desktopDraggableActive
+				strength = Utils.modulate(@perspective, [1200, 900], [22, 17.5])
+				deltaDir = (@desktopOrientationLayer.x - @prevDesktopDir) / strength
+				deltaHeight = (@desktopOrientationLayer.y - @prevDesktopHeight) / strength
+				@desktopPan(deltaDir, deltaHeight)
+				@prevDesktopDir = @desktopOrientationLayer.x
+				@prevDesktopHeight = @desktopOrientationLayer.y
+
+		@desktopOrientationLayer.on Events.AnimationEnd, =>
+			@desktopDraggableActive = false
+			@desktopOrientationLayer?.center()
+
+	desktopPan: (deltaDir, deltaHeight) ->
+		halfCubSide = @cubeSide/2
+		translationX = "translateX(#{(@width / 2) - halfCubSide}px)"
+		translationY = " translateY(#{(@height / 2) - halfCubSide}px)"
+		translationZ = " translateZ(#{@perspective}px)"
+		@_heading -= deltaDir
+
+		if @_heading > 360
+			@_heading -= 360
+		else if @_heading < 0
+			@_heading += 360
+
+		@_elevation += deltaHeight
+		@_elevation = Utils.clamp(@_elevation, -90, 90)
+
+		rotation = translationZ + translationX + translationY + " rotateX(#{@_elevation + 90}deg) rotateZ(#{360 - @_heading}deg)" + " rotateZ(#{-@_headingOffset}deg)"
+		@world.style["webkitTransform"] = rotation
+
+		@_heading = Math.round(@_heading * 1000) / 1000
+		@_tilt = 0
+		@_emitOrientationDidChangeEvent()
+
+	lookAt: (heading, elevation) ->
+		halfCubSide = @cubeSide/2
+		translationX = "translateX(#{(@width / 2) - halfCubSide}px)"
+		translationY = " translateY(#{(@height / 2) - halfCubSide}px)"
+		translationZ = " translateZ(#{@perspective}px)"
+		rotation = translationZ + translationX + translationY + " rotateZ(#{@_tilt}deg) rotateX(#{elevation + 90}deg) rotateZ(#{-heading}deg)"
+
+		@world?.style["webkitTransform"] = rotation
+		@_heading = heading
+		@_elevation = elevation
+		if Utils.isMobile()
+			@_headingOffset = @_heading - @_deviceHeading
+
+		@_elevationOffset = @_elevation - @_deviceElevation
+
+		heading = @_heading
+		if heading < 0
+			heading += 360
+		else if heading > 360
+			heading -= 360
+
+		@emit(Events.OrientationDidChange, {heading: heading, elevation: @_elevation, tilt: @_tilt})
+
+	_emitOrientationDidChangeEvent: ->
+		@emit(Events.OrientationDidChange, {heading: @heading, elevation: @_elevation, tilt: @_tilt})


Fix clipped point trimming

Fix clipped point trimming

The ref normal didn't need to be flipped because the reference was already
defined as being from B's edge pointing toward A.

--- app/js/physics.coffee
+++ app/js/physics.coffee
@@ -7,7 +7,7 @@
   controls = new KeyboardControls

   # dot = new Particle [100, ctx.height - 100]
-  window.rect = new Rectangle 120, 100, [110, 50], 0, "#F00"
+  window.rect = new Rectangle 120, 100, [100, 80], 0.3, "#F00"
   rect2 = new Rectangle 120, 100, [0, 0], 0, "#00F"

   _.extend ctx,
@@ -69,29 +69,24 @@
     console.log "found minimum separation axis of #{minAxis}"
     return [] unless minAxis

-    @debug.myBest = e1 = @bestEdge minAxis
-    @debug.theirBest = e2 = other.bestEdge Vec.invert minAxis # A->B always
-
-    # Now, clip the edges. Do a series of line/plane clips to get the contact
-    # manifold. Identify the reference edge and incident edge. Reference is most
-    # perpendicular to contact axis, and will be used to clip the incident edge
-    # vertices to generate the manifold.
-
+    e1 = @bestEdge minAxis
+    e2 = other.bestEdge Vec.invert minAxis # A->B always
+
+    # Now, clip the edges. The reference edge is most perpendicular to contact
+    # axis, and will be used to clip the incident edge vertices to generate the
+    # contact points.
     if e1.dot(minAxis) <= e2.dot(minAxis)
       reference = e1
       incident  = e2
-      flipped   = false
     else
       reference = e2
       incident  = e1
-      flipped   = true

     @debug.reference = reference
     @debug.incident = incident

     console.log "reference: #{reference.deepest} #{reference.from} -> #{reference.to} (vec #{reference.vec})"
     console.log "incident: #{incident.deepest} #{incident.from} -> #{incident.to} (vec #{incident.vec})"
-    console.log "flipped", flipped

     reference.normalize()

@@ -116,31 +111,28 @@
     # get the reference edge normal
     refNorm = Vec.perpendicular reference.vec

-    # if incident/reference were flipped, then flip the normal to clip properly
-    refNorm = Vec.invert refNorm if flipped
-
-    # get the largest depth
-    max = Vec.dotProduct refNorm, reference.deepest
-
+    # find the largest depth
+    maxDepth = Vec.dotProduct refNorm, reference.deepest
     trimmed = []
-    # make sure the final points are not past this maximum
-    unless Vec.dotProduct(refNorm, clipped[0]) - max < 0
+
+    # make sure the final points are not past this maximum depth
+    unless Vec.dotProduct(refNorm, clipped[0]) - maxDepth < 0
       trimmed.push clipped[0]
-    unless Vec.dotProduct(refNorm, clipped[1]) - max < 0
+    unless Vec.dotProduct(refNorm, clipped[1]) - maxDepth < 0
       trimmed.push clipped[1]

-    console.log "trimmed", trimmed[0], trimmed[1]
+    console.log "trimmed", trimmed...
     @debug.trimmed = trimmed

     trimmed

   # clip the line segment from v1 to v2 if they are beyond offset along normal
   clip: (v1, v2, normal, offset) ->
-    console.log "clipping #{v1} -> #{v2} against #{normal.vec} with #{offset}"
+    console.log "clipping", v1, "->", v2, "against", normal.vec, "with", offset
     points = []
     d1 = normal.dot(v1) - offset
     d2 = normal.dot(v2) - offset
-    console.log "d1 #{d1}, d2 #{d2}"
+    console.log "d1", d1, "d2", d2
     points.push v1 if d1 >= 0
     points.push v2 if d2 >= 0


Dev host

Dev host

--- app/lib/config.coffee
+++ app/lib/config.coffee
@@ -14,7 +14,7 @@
       workflowId: '502a701e516bcb0001000002'

   development:
-    apiHost: 'https://api.zooniverse.org'
+    apiHost: 'http://localhost:3000'
     surveys:
       candels:
         id: '50251c3b516bcb6ecb000001'


added missing logger configuration for process

--- app/lib/logger.coffee
+++ app/lib/logger.coffee
@@ -7,4 +7,5 @@
     { type: 'file', filename: 'log/server.log'}
   ]

-log4js.getLogger('server').setLevel config.logLevel.server+log4js.getLogger('server').setLevel  config.logLevel.server
+log4js.getLogger('process').setLevel config.logLevel.process


added missing require

added missing require

--- app/lib/routes.coffee
+++ app/lib/routes.coffee
@@ -1,4 +1,5 @@
 require('routers/index_route')
+require('routers/tags_index_route')

 App.Router.map (match) ->
   match('/').to 'index'


Include tips for the Git and GitHub package

--- lib/tips.coffee
+++ lib/tips.coffee
@@ -4,6 +4,10 @@
   'You can quickly open files with the Fuzzy Finder. Try it by using {fuzzy-finder:toggle-file-finder}'
   'You can toggle the Tree View with {tree-view:toggle}'
   'You can focus the Tree View with {tree-view:toggle-focus}'
+  'You can toggle the Git tab with {github:toggle-git-tab}'
+  'You can focus the Git tab with {github:toggle-git-tab-focus}'
+  'You can toggle the GitHub tab with {github:toggle-github-tab}'
+  'You can focus the GitHub tab with {github:toggle-github-tab-focus}'
   'You can split a pane with {pane:split-right}'
   'You can jump to a method in the editor using {symbols-view:toggle-file-symbols}'
   'You can install packages and themes from the Settings View {settings-view:open}'


Add permanent/temporary virtuals

Add permanent/temporary virtuals

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- app/models/ban.coffee
+++ app/models/ban.coffee
@@ -18,8 +18,14 @@
   date:
     type: Date
     required: yes
-  end: Date
+  end:
+    type: Date
+    default: null

 schema.virtual('link').get -> "/bans/#{@_id}"

+schema.virtual('permanent').get -> not @temporary
+
+schema.virtual('temporary').get -> @date isnt null
+
 module.exports = mongoose.model 'Ban', schema


Tests: Helpers: Use testpath for activity input widget.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -223,6 +223,7 @@

   doPostActivity: (browser, post, shouldAssert = yes, hasEmbeddable = no) ->
+
     browser
       .pause                    5000 # wait for IDE open
       .click                    '[testpath="public-feed-link/Activity/Topic/public"]'
@@ -235,7 +236,7 @@

     if hasEmbeddable
       browser
-        .waitForElementVisible  '.activity-input-widget .link-embed-box', 20000
+        .waitForElementVisible  '[testpath=ActivityInputWidget] .link-embed-box', 20000

     browser
       .click                    '[testpath=post-activity-button]'


replace parseInts with +

replace parseInts with +


allow empty message body

allow empty message body

--- api/models/Msg.coffee
+++ api/models/Msg.coffee
@@ -29,8 +29,6 @@
 			defaultsTo: 'chat'
 		body:
 			type: 		'string'
-			required:	true
-			defaultsTo:	'file'
 		file:
 			type:		'string'
 		file_inode:


disable book

disable book

--- client/app/CommonViews/HelpBox.coffee
+++ client/app/CommonViews/HelpBox.coffee
@@ -11,7 +11,7 @@
     @setPartial @partial()

   click:->
-    @getSingleton('mainController').emit "ShowInstructionsBook"
+    #@getSingleton('mainController').emit "ShowInstructionsBook"

   partial:()->
     """


Remove unecessary code

Remove unecessary code

--- server/publications/subscription.coffee
+++ server/publications/subscription.coffee
@@ -31,9 +31,6 @@

 RocketChat.models.Subscriptions.on 'change', (type, args...) ->
-	if type is 'update' and args[0]?.alert?.$ne?
-		delete args[0].alert
-
 	records = RocketChat.models.Subscriptions.getChangedRecords type, args[0], fields

 	for record in records


Add confirm to user delete

Add confirm to user delete

--- client/source/views/users/edit.coffee
+++ client/source/views/users/edit.coffee
@@ -26,6 +26,7 @@

   clickRemove: (e) ->
     e.preventDefault()
-    @model.destroy(wait: yes).done =>
-      toastr.success 'User has been removed.'
-      @dispose()+    if confirm 'Are you sure?'
+      @model.destroy(wait: yes).done =>
+        toastr.success 'User has been removed.'
+        @dispose()


Add .href for location

Add .href for location

--- src/scripts/modules/media/title/title.coffee
+++ src/scripts/modules/media/title/title.coffee
@@ -14,7 +14,7 @@

       # Set information used for social media links
       share =
-        url: window.location
+        url: window.location.href
         source: 'Connexions'
         summary: @model.get('abstract') or 'An OpenStax College book.'
         title: title or 'Untitled'


Pagination urls now work for deep linking, although the discussion isn't automatically shown yet.

--- lms/static/coffee/src/discussion/discussion_module_view.coffee
+++ lms/static/coffee/src/discussion/discussion_module_view.coffee
@@ -7,9 +7,15 @@
       "click .discussion-paginator a": "navigateToPage"

     paginationTemplate: -> DiscussionUtil.getTemplate("_pagination")
+    page_re: /\?discussion_page=(\d+)/
+    initialize: ->
+      # Set the page if it was set in the URL. This is used to allow deep linking to pages
+      match = @page_re.exec(window.location.href)
+      if match
+        @page = parseInt(match[1])
+      else
+        @page = 1

-    initialize: ->
-      @page = 1

     toggleNewPost: (event) ->
       if @newPostForm.is(':hidden')
@@ -67,7 +73,7 @@
       @renderPagination(2, response.num_pages)

     addThread: (thread, collection, options) =>
-      # TODO: When doing pagination, this will need to repaginate
+      # TODO: When doing pagination, this will need to repaginate. Perhaps just reload page 1?
       article = $("

")
       @$('section.discussion > .threads').prepend(article)
       threadView = new DiscussionThreadInlineView el: article, model: thread
@@ -77,8 +83,6 @@
     renderPagination: (delta, numPages) =>
       minPage = Math.max(@page - delta, 1)
       maxPage = Math.min(@page + delta, numPages)
-      console.log minPage
-      console.log maxPage
       pageUrl = (number) ->
         "?discussion_page=#{number}"
       params =


clicking credits from title screen works

--- canvas/game.coffee
+++ canvas/game.coffee
@@ -1198,24 +1198,22 @@

   class Credits
-    constructor: (@gw, @window) ->
-      @img = pyglet.resource.image("data/credits.png")
-      @window.set_handler('on_draw', @on_draw)
-      @window.set_handler('on_mouse_press', @on_mouse_press)
-      pyglet.clock.schedule_interval(@update, 1/game_fps)
-
-    update: (dt) ->
-
-    on_draw: ->
-      @window.clear()
-      @img.blit(0, 0)
+    constructor: (@gw, @engine) ->
+      @batch = new Engine.Batch()
+      @img = new Engine.Sprite("credits", batch: @batch)
+      @engine.on('draw', @draw)
+      @engine.on('mousedown', @onMouseDown)
+
+    draw: =>
+      @engine.clear()
+      @engine.draw @batch

     end: ->
-      @window.remove_handler('on_draw', @on_draw)
-      @window.remove_handler('on_mouse_press', @on_mouse_press)
-      pyglet.clock.unschedule(@update)
-
-    on_mouse_press: (x, y, button, modifiers) ->
+      @engine.removeListener('draw', @draw)
+      @engine.removeListener('mousedown', @onMouseDown)
+      @engine.removeListener('update', @update)
+
+    onMouseDown: (pos) =>
       @gw.title()

@@ -1228,12 +1226,12 @@
       @batch = new Engine.Batch()
       @img = new Engine.Sprite("title", batch: @batch)

-      @start_pos = new Vec2d(409, 305)
-      @credits_pos = new Vec2d(360, 229)
-      @controls_pos = new Vec2d(525, 242)
+      @start_pos = new Vec2d(409, 600-305)
+      @credits_pos = new Vec2d(360, 600-229)
+      @controls_pos = new Vec2d(525, 600-242)
       @click_radius = 50

-      @lobby_pos = new Vec2d(746, 203)
+      @lobby_pos = new Vec2d(746, 600-203)
       @lobby_size = new Vec2d(993.0 - @lobby_pos.x, 522.0 - @lobby_pos.y)

       if @server?
@@ -1255,7 +1253,7 @@
       @nick_label = {}
       @nick_user = {}
       h = 18
-      next_pos = @lobby_pos + new Vec2d(0, @lobby_size.y - h)
+      next_pos = @lobby_pos.offset(0, @lobby_size.y - h)
       for user in @users
         nick = user['nick']
         if nick is @nick
@@ -1298,15 +1296,14 @@
       @engine.removeListener 'mousedown', @onMouseDown
       @engine.removeListener 'update', @update

-    onMouseDown: (x, y, button, modifiers) =>
-      click_pos = new Vec2d(x, y)
-      if click_pos.get_distance(@start_pos) < @click_radius
+    onMouseDown: (click_pos) =>
+      if click_pos.distanceTo(@start_pos) < @click_radius
         @gw.play(server_on=false)
         return
-      else if click_pos.get_distance(@credits_pos) < @click_radius
+      else if click_pos.distanceTo(@credits_pos) < @click_radius
         @gw.credits()
         return
-      else if click_pos.get_distance(@controls_pos) < @click_radius
+      else if click_pos.distanceTo(@controls_pos) < @click_radius
         @gw.controls()
         return


Remove leftover capitanodoc.coffee file (it's now TS)

--- capitanodoc.coffee
+++ capitanodoc.coffee
@@ -1,115 +0,0 @@
-# coffeelint: disable=max_line_length
-
-module.exports =
-	title: 'Resin CLI Documentation'
-	introduction: '''
-		This tool allows you to interact with the resin.io api from the comfort of your command line.
-
-		Please make sure your system meets the requirements as specified in the [README](https://github.com/resin-io/resin-cli).
-
-		To get started download the CLI from npm.
-
-			$ npm install resin-cli -g
-
-		Then authenticate yourself:
-
-			$ resin login
-
-		Now you have access to all the commands referenced below.
-
-		## Proxy support
-
-		The CLI does support HTTP(S) proxies.
-
-		You can configure the proxy using several methods (in order of their precedence):
-
-		* set the `RESINRC_PROXY` environment variable in the URL format (with protocol, host, port, and optionally the basic auth),
-		* use the [resin config file](https://www.npmjs.com/package/resin-settings-client#documentation) (project-specific or user-level)
-		and set the `proxy` setting. This can be:
-			* a string in the URL format,
-			* or an object following [this format](https://www.npmjs.com/package/global-tunnel-ng#options), which allows more control,
-		* or set the conventional `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY`
-		environment variable (in the same standard URL format).
-	'''
-
-	categories: [
-		{
-			title: 'Application'
-			files: [ 'lib/actions/app.coffee' ]
-		},
-		{
-			title: 'Authentication',
-			files: [ 'lib/actions/auth.coffee' ]
-		},
-		{
-			title: 'Device',
-			files: [ 'lib/actions/device.coffee' ]
-		},
-		{
-			title: 'Environment Variables',
-			files: [ 'lib/actions/environment-variables.coffee' ]
-		},
-		{
-			title: 'Help',
-			files: [ 'lib/actions/help.coffee' ]
-		},
-		{
-			title: 'Information',
-			files: [ 'lib/actions/info.coffee' ]
-		},
-		{
-			title: 'Keys',
-			files: [ 'lib/actions/keys.coffee' ]
-		},
-		{
-			title: 'Logs',
-			files: [ 'lib/actions/logs.coffee' ]
-		},
-		{
-			title: 'Sync',
-			files: [ 'lib/actions/sync.coffee' ]
-		},
-		{
-			title: 'SSH',
-			files: [ 'lib/actions/ssh.coffee' ]
-		},
-		{
-			title: 'Notes',
-			files: [ 'lib/actions/notes.coffee' ]
-		},
-		{
-			title: 'OS',
-			files: [ 'lib/actions/os.coffee' ]
-		},
-		{
-			title: 'Config',
-			files: [ 'lib/actions/config.coffee' ]
-		},
-		{
-			title: 'Preload',
-			files: [ 'lib/actions/preload.coffee' ]
-		},
-		{
-			title: 'Settings',
-			files: [ 'lib/actions/settings.coffee' ]
-		},
-		{
-			title: 'Wizard',
-			files: [ 'lib/actions/wizard.coffee' ]
-		},
-		{
-			title: 'Local',
-			files: [ 'lib/actions/local/index.coffee' ]
-		},
-		{
-			title: 'Deploy',
-			files: [
-				'lib/actions/build.coffee'
-				'lib/actions/deploy.coffee'
-			]
-		},
-		{
-			title: 'Utilities',
-			files: [ 'lib/actions/util.coffee' ]
-		},
-	]


Add drop item into dropshiprecords (#12)

--- models/report/drop-ship.coffee
+++ models/report/drop-ship.coffee
@@ -3,6 +3,7 @@

 DropShipRecord = new Schema
   shipId: Number
+  itemId: Number
   mapId: Number
   quest: String
   cellId: Number


Use a collection which we maintain, instead of rebuilding one each time

--- core/app/backbone/modules/memory_profiler.coffee
+++ core/app/backbone/modules/memory_profiler.coffee
@@ -4,6 +4,9 @@
     @on 'start', ->
       MemoryProfiler.view_counts = {}
       MemoryProfiler.views = {}
+
+      MemoryProfiler.view_count_collection = new ViewCountCollection
+
       Backbone.View.prototype.constructor = (options)->
         @cid = _.uniqueId('view')
         openView(this)
@@ -23,27 +26,54 @@
     class MemoriesView extends Marionette.CollectionView
       itemView: MemoryView

+    class ViewCountCollection extends Backbone.Collection
+      increaseCount: (view_name) ->
+        model = @modelByName view_name
+        if model
+          model.set count: model.get('count') + 1
+        else
+          model = new Backbone.Model
+            class_name: view_name
+            count: 1
+          @add model
+
+      decreaseCount: (view_name) ->
+        model = @modelByName view_name
+        model.set count: model.get('count') - 1
+        if model.get('count') == 0
+          @remove model
+
+      modelByName: (name) ->
+        results = @where class_name: name
+        if results.length > 0
+          results[0]
+        else
+          null
+
+      comparator: (model) -> -model.get('count')
+
     openView = (view)->
       console.info('+CID ', view.cid)
       MemoryProfiler.views[view.cid] = view
       view_name = view.__proto__.constructor.name
+
       MemoryProfiler.view_counts[view_name] ||= 0
       MemoryProfiler.view_counts[view_name] = MemoryProfiler.view_counts[view_name] + 1
+
+      MemoryProfiler.view_count_collection.increaseCount view_name

     closeView = (view)->
       console.info('-CID', view.cid)
       delete MemoryProfiler.views[view.cid]
       view_name = view.__proto__.constructor.name
+
       MemoryProfiler.view_counts[view_name] = MemoryProfiler.view_counts[view_name] - 1

+      MemoryProfiler.view_count_collection.decreaseCount view_name
+
+
     MemoryProfiler.showModal = ->
-      view_count_array = []
-
-      for view_name in Object.keys(MemoryProfiler.view_counts)
-        view_count_array.push {class_name: view_name, count: MemoryProfiler.view_counts[view_name]}
-
-      memoryView = new MemoriesView
-        collection: new Backbone.Collection view_count_array
-
+      MemoryProfiler.view_count_collection.sort()
+      memoryView = new MemoriesView collection: MemoryProfiler.view_count_collection
       FactlinkApp.Modal.show 'Views not closed', memoryView


Don't add Topic with duplicate slug_title to SuggestedTopic collection

--- core/app/backbone/views/notification_view.coffee
+++ core/app/backbone/views/notification_view.coffee
@@ -56,7 +56,18 @@
   onRender: ->
     super()

-    suggested_topics = new SuggestedTopics [@channel_as_topic(), @other_channel_as_topic()]
+    # TODO clean this behaviour.
+    # We don't want Topics with duplicate slug in the SuggestedTopics collection
+    topic1 = @channel_as_topic()
+    topic2 = @other_channel_as_topic()
+
+    same_slug_title = topic1.get('slug_title') == topic2.get('slug_title')
+
+    topics = [topic1]
+    topics.push(topic2) unless same_slug_title
+    # end
+
+    suggested_topics = new SuggestedTopics topics
     @addBackRegion.show new AddChannelToChannelsButtonView
                                 model: @other_channel()
                                 suggested_topics: suggested_topics


improved checkbox change: using this instead of @element

--- coffee/client/modules/VMB4RYNO.coffee
+++ coffee/client/modules/VMB4RYNO.coffee
@@ -15,14 +15,14 @@
     do checkbox.bootstrapToggle

     # send message to change relay status
-    checkbox.change =>
-      if checkbox.prop 'checked'
+    checkbox.change ->
+      if $(this).prop 'checked'
         # don't update the element until the action is confirmed
-        @element.find('input').data('bs.toggle').off(true)
+        $(this).data('bs.toggle').off(true)
         message.command = 'switch_relay_on'
       else
         # don't update the element until the action is confirmed
-        @element.find('input').data('bs.toggle').on(true)
+        $(this).data('bs.toggle').on(true)
         message.command = 'switch_relay_off'
       send JSON.stringify message
       return


prevent JS exception when translations not returned as expected from back-end

--- app/assets/javascripts/species/views/document_result_component.js.coffee
+++ app/assets/javascripts/species/views/document_result_component.js.coffee
@@ -6,6 +6,9 @@
   setup: ( ->
     primaryDocumentId = parseInt(@get('doc.primary_document_id'))
     primaryDocumentVersion = @get('doc.document_language_versions').findBy('id', primaryDocumentId)
+    # when things went wrong with translations
+    unless primaryDocumentVersion
+      primaryDocumentVersion = @get('doc.document_language_versions.firstObject')
     primaryLanguage = primaryDocumentVersion['language']
     allLanguages = @get('doc.document_language_versions').mapBy('language').sort()
     @setProperties(


ConfirmEmail: search route and handleQuery is added to Dasboard

--- client/Dashboard/AppController.coffee
+++ client/Dashboard/AppController.coffee
@@ -12,6 +12,7 @@
       "/:name?/Dashboard/:section" : ({params : {section,name}})->
         handler name, (app)-> app.loadSection title : decodeURIComponent section
     hiddenHandle : yes
+    searchRoute : "/Dashboard?q=:text:"

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

@@ -129,3 +130,21 @@

   productViewAdded: (pane, view) ->
     new GroupProductsController { view }
+
+  loadView: (mainView, firstRun = yes, loadFeed = no)->
+    if firstRun
+      @on "searchFilterChanged", (value) =>
+        return if value is @_searchValue
+        @_searchValue = Encoder.XSSEncode value
+        @getOptions().view.search @_searchValue
+        @loadView mainView, no, yes
+
+  handleQuery:(query)->
+    @getOptions().view.ready =>
+      {q} = query
+      if q
+        @emit "searchFilterChanged", q
+      else
+        @emit "searchFilterChanged", ""
+        super query
+


dashboardapp : select the settings when no pane selected.

--- client/Dashboard/AppController.coffee
+++ client/Dashboard/AppController.coffee
@@ -7,7 +7,8 @@
   KD.registerAppClass this,
     name         : "Dashboard"
     routes                         :
-      "/:name?/Dashboard"          : null
+      "/:name?/Dashboard"          : ({params : {section,name}})->
+        handler name, (app)-> app.loadSection title : "Settings"
       "/:name?/Dashboard/:section" : ({params : {section,name}})->
         handler name, (app)-> app.loadSection title : section
     hiddenHandle : yes
@@ -106,7 +107,7 @@

   loadSection: ({title}) ->
     @getView().nav.ready =>
-      @getView().tabs.showPaneByName title or 'Settings'
+      @getView().tabs.showPaneByName title

   policyViewAdded: (pane, view) ->


KodingKontrol:: createKite: after injecting transportOptions, also autoconnect

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -104,22 +104,24 @@
     return kite

-  createKite: (options)->
+  createKite: (options, query)->

     {kite} = options

     # If its trying to create a klient kite instance
     # allow to use websockets by emptying the protocols_whitelist
     if kite.name is 'klient'
-      options.transportOptions =
-        protocols_whitelist : []
+      options.transportOptions = protocols_whitelist: []

-    super options
+    kite = KontrolJS::createKite.call this, options
+
+
+    kite.connect()  if kite.name is 'klient'
+
+    return kite

   getKite: (options = {}) ->
-
-    @reauthenticate()  unless @kite?

     # Get options
     { name, correlationName, region, transportOptions,


If error starts with cannot access, try to parse the lsoutput

--- client/app/MainApp/fs/fsfolder.coffee
+++ client/app/MainApp/fs/fsfolder.coffee
@@ -25,13 +25,12 @@
     , (err, response)=>
       # log "------------------------------------------------------------------"
       # log "l flag response in: #{Date.now()-a} msec."
-      if err
-        warn err
-        @emit "fs.fetchContents.finished", err
-      else
+      if not err or /ls\:\scannot\saccess/.test err
         files = FSHelper.parseLsOutput [@path], response
         @emit "fs.fetchContents.finished", files
         callback? files
+      else
+        @emit "fs.fetchContents.finished", err

   # forkRepoCommandMap = ->


KodingKite: log the reason of close event

--- client/app/lib/kite/kodingkite.coffee
+++ client/app/lib/kite/kodingkite.coffee
@@ -22,7 +22,8 @@
       @_state = CONNECTED
       @emit "connected"

-    @on 'close', =>
+    @on 'close', (reason)=>
+      kd.log "Disconnected with reason:", reason
       @_state = DISCONNECTED

     @waitingCalls = []


debug view collection bad linking of model given as parameter

--- client/app/lib/view_collection.coffee
+++ client/app/lib/view_collection.coffee
@@ -47,7 +47,7 @@
         @

     renderOne: (model) =>
-        view = new @view model
+        view = new @view model: model
         @$el.append view.render().el
         @add view
         @


deurlRoot fixed

deurlRoot fixed

--- client/app/models/result_model.coffee
+++ client/app/models/result_model.coffee
@@ -1,5 +1,5 @@
 module.exports = class ResultModel extends Backbone.Model
-	rootUrl: "search"
+	urlRoot: "search"

 	methodUrl : {
 		'delete': 'delete'


adding template and setting default cancel class to btn-secondary for bootstrap 4

--- vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
+++ vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
@@ -9,34 +9,50 @@
     modal_class: ""

 TwitterBootstrapConfirmBox = (message, element, callback) ->
-  bootstrap_version = if (typeof $().emulateTransitionEnd == 'function') then 3 else 2
+  bootstrap_version = if (typeof $().emulateTransitionEnd == 'function') then parseInt($.fn.tooltip.Constructor.VERSION[0]) else 2

-  if bootstrap_version == 2
-    $dialog = $('
-

-

-          <button type="button" class="close" data-dismiss="modal">×</button>
-
...

+  switch bootstrap_version
+    when 2
+      $dialog = $('
+

+

+            <button type="button" class="close" data-dismiss="modal">×</button>
+
...

+

+

+

-

-

-

-    ')
-  else
-    $dialog = $('
-

-

-

-

-              <button type="button" class="close" data-dismiss="modal">×</button>
-
...

+      ')
+    when 3
+      $dialog = $('
+

+

+

+

+                <button type="button" class="close" data-dismiss="modal">×</button>
+
...

+

+

+

-

-

-

-    ')
+      ')
+    else # 4
+      $dialog = $('
+

+

+

+

+
...

+                <button type="button" class="close" data-dismiss="modal">×</button>
+

+

+

+

+

+

+      ')

   $dialog.addClass(element.data("confirm-modal-class") || $.fn.twitter_bootstrap_confirmbox.defaults.modal_class)

@@ -58,7 +74,7 @@
         $("", {href: "#", "data-dismiss": "modal"})
           .html(element.data("confirm-cancel") || $.fn.twitter_bootstrap_confirmbox.defaults.cancel)
           .addClass($.fn.twitter_bootstrap_confirmbox.defaults.cancel_class)
-          .addClass(element.data("confirm-cancel-class") || "btn-default")
+          .addClass(element.data("confirm-cancel-class") || ("btn-secondary" if bootstrap_version == 4) || "btn-default")
           .click((event) ->
             event.preventDefault()
             $dialog.modal("hide")


How does one write tests for this...?

How does one write tests for this...?

--- spec/html-significant-other-spec.coffee
+++ spec/html-significant-other-spec.coffee
@@ -8,11 +8,15 @@
 describe "your significant other", ->
   describe "on start-up, your significant other", ->

-    it "subscribes to the editor"#, ->
-      # expect(atom.packages.getActivePackage('html-significant-other')).not.toExist()
+    instance = atom.packages.getLoadedPackage('html-significant-other')
+
+    it "subscribes to the editor", ->
+      expect(atom.packages.getActivePackage('html-significant-other')).not.toExist()
       # needs to then spy on the subscription method and activate the atom package
       # expect that we ran the subscription method
       # expect that its subscriptions include a subscription to the text buffer
+
+    atom.packages.activatePackage('html-significant-other')

     it "can tell when you've typed something"
     it "can tell when you've typed a closing angle bracket"


--- spec/notice/hub/hub_handler_spec.coffee
+++ spec/notice/hub/hub_handler_spec.coffee
@@ -131,6 +131,35 @@
                 stateAt: 2
             done()

+    context 'capsule', ->
+
+        before -> @HandlerClass = handler()
+        beforeEach ->
+
+            @instance = @HandlerClass.create(
+
+                hubName      = 'hubname'
+                @hubNotifier = control: ->
+                @hubContext  =
+                    clients: {}
+                    name2id: {}
+                opts = {}
+
+            )
+
+        it.only 'reassembles the inbound payload into a capusle for the hubside middleware traversal', (done) ->
+
+            handle = @instance.capsule
+                id: 'SOCKET_ID'
+
+            handle(
+                header  = {}
+                config  = {}
+                payload = {}
+            )
+
+            done()
+

     context 'handshake', ->


Fix whoami test

Fix whoami test

--- spec/server/functional/auth.spec.coffee
+++ spec/server/functional/auth.spec.coffee
@@ -18,7 +18,7 @@
   it 'returns 200', utils.wrap (done) ->
     yield utils.logout()
     [res, body] = yield request.getAsync(getURL('/auth/whoami'), {json: true})
-    expect(res.body.createdOnHost).toBe(true)
+    expect(res.body.createdOnHost).toBeTruthy()
     expect(res.statusCode).toBe(200)
     done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -20,7 +20,8 @@
   equals 5.clamp(0, 3), 3
   equals 5.clamp(-1, 0), 0
   equals -5.clamp(0, 1), 0
-  equals 1.clamp(0, null)
+  equals 1.clamp(0, null), 1
+  equals

 test "#floor", ->
   equals 4.9.floor(), 4, "(4.9).floor() equals 4"


use meteor.defer

use meteor.defer

--- client/controllers/searchInput.coffee
+++ client/controllers/searchInput.coffee
@@ -46,14 +46,12 @@
         $regex: instance.$(event.target).val()
         $options: 'i'
       if instance.data.tableId
-        Meteor.setTimeout(->
+        Meteor.defer ->
           count = parseInt($("##{instance.data.tableId}").next().find('span.rows-per-page-count').text(), 10)
-          if count == 0
-            # hide loading indicator
+          if count <= 1
             $('.loading').hide()
           else
             $('.loading').show()
-        , 0)

   'click .search-icon.toggleable:not(.cancel)': (event, instance) ->
     searching = instance.searching


ChatView: Show chat pane when session started.

--- client/ide/views/chat/chatview.coffee
+++ client/ide/views/chat/chatview.coffee
@@ -64,6 +64,8 @@
       'ParticipantJoined', 'ParticipantLeft'
     ]

+    @settingsPane.on 'SessionStarted', @bound 'showChatPane'
+
     @emit 'ready'


feat(app generator/router): add ability to adjust any of the current url's parameters without requiring knowledge of the current url itself or its structure

--- generators/app/templates/src/router.coffee
+++ generators/app/templates/src/router.coffee
@@ -56,6 +56,7 @@
             ##  Create function handlers for routes that haven't yet got one.
             ##
             ##  Created handlers will pass on to `@_openPage( ... )`:
+            ##    - Their route's matched url pattern;
             ##    - The targeted view class of their route;
             ##    - The url pattern's named arguments of their route;

@@ -80,9 +81,9 @@
                     ##
                     routes[ urlPattern ] =
                         if paramNames.length
-                            () -> @_openPage( View, _.object( paramNames, arguments ))
+                            () -> @_openPage( urlPattern, View, _.object( paramNames, arguments ))
                         else
-                            () -> @_openPage( View )
+                            () -> @_openPage( urlPattern, View )
                 else
                     throw new Error( "Cannot create handler for '#{handler}'." )

@@ -176,7 +177,6 @@
         #
         #   @method         initialize
         #   @protected
-        #
         ###

         initialize: () ->
@@ -208,6 +208,23 @@

         ###*
+        #   Convenience method to re-render the current page.
+        #   Navigates `@home()` if no current @page exists.
+        #
+        #   @method         refresh
+        ###
+
+        refresh: () ->
+
+            if ( @page )
+                @_openPage( @page... )
+            else
+                @home()
+
+            return
+
+
+        ###*
         #   Convenience method to navigate straight to the app's home state.
         #
         #   @method         home
@@ -246,7 +263,6 @@
         #
         #   @param          {Object}            [options]
         #   @param          {Boolean}           [options.trigger]
-        #
         ###

         navigate: ( fragment, options ) ->
@@ -271,7 +287,6 @@
         #   @method         startApp
         #
         #   @param          {Object}            [options]               Any options you may want to pass on to `Backbone.history.start( options )`.
-        #
         ###

         startApp: ( options ) ->
@@ -286,6 +301,70 @@
             ##  dispatched.
             ##
             Backbone.history.start( options )
+
+            return
+
+
+        ###*
+        #   Set the browsers url equal to the current fragment with the supplied parameters replaced.
+        #
+        #   Works by reconstructing the url from the route path for the supplied parameters combined with the ones
+        #   saved, and then @navigate()ing to it.
+        #
+        #   @method         youAreHere
+        #   @param          {Object}            paramsNew               The parameters you want changed.
+        #   @param          {Object}            [options]               Options passed on to @navigate().
+        ###
+
+        youAreHere: ( paramsNew, options ) ->
+
+            return unless @page?
+
+            [ path, View, params ] = @page
+
+            ##  Update the current fragments parameters,
+            ##  And get a local handle on it.
+            ##
+            _( params ).extend( paramsNew )
+
+            ##  Recreate a fragment url from the current fragment's route path pattern and the parameters
+            ##
+            fragment =
+                path.replace(
+
+                    ///                         #
+                        (?:                     #   Group but don't capture
+                            \(                  #   A mandatory opening bracket, and:
+                            ( [^:\(\)]* )       #   Optional text (zero or more characters that cannot be ':' '(' or ')' ); capture this text as 'before'.
+                        )?                      #   When the parameter is NOT optional, the preceding group won't be there, so make the group optional.
+                                                #
+                        [:*]                    #   A colon (':') or asterisk ('*') indicates a parameter, followed by:
+                        ( \w+ )                 #   Its identifier (one or more alfanumeric characters); capture this identifier as 'key'.
+                                                #
+                        (?:                     #   Group but don't capture
+                                                #   Again, when a parameter is optional, it may be followed by:
+                            ( [^:\(\)]* )       #   Text (zero or more characters that cannot be ':' '(' or ')' ); capture this text as 'after'.
+                            \)                  #   A closing bracket is a must.
+                        )?                      #   Unless the parameter was NOT optional of course, in which case the preceding group won't be there.
+                                                #
+                    ///g                        #   Repeat
+
+                    ( match, before, key, after ) ->
+
+                        ##  Since the 'before' and 'after' are both optional in the regex, it must still be established
+                        ##  that they either BOTH matched or opted-out.
+                        ##  If only one matched, there's likely something wrong with the path pattern, and we will simply
+                        ##  not touch what we've matched.
+
+                        param = encodeURIComponent( params[ key ] )
+
+                        return if before?
+                            if after? then "#{before}#{param}#{after}" else match
+                        else
+                            if after? then match else param
+                )
+
+            @navigate( fragment, options )

             return

@@ -298,11 +377,17 @@
         #   @method         _openPage
         #   @protected
         #
+        #   @param          {String}            urlPattern              The route's matched url pattern.
         #   @param          {Class}             View                    The targeted view class of the matched route.
         #   @param          {Object}            params                  A key-value mapping of the matched route's url pattern's parameters.
-        ###
-
-        _openPage: ( View, params ) ->
+        #   @param          {String}            [fragment]              The current history entry's full url fragment. Used only on retries.
+        ###
+
+        _openPage: ( urlPattern, View, params, fragment ) ->
+
+            ##  Save for possible retries.
+            ##
+            @page   = [ urlPattern, View, params, fragment or Backbone.history.fragment ]

             divert  = undefined

@@ -321,6 +406,10 @@
                 View    = @viewMap[ divert ]
                 params  = undefined

+
+            ##  If `fragment` is set, this was effectively a retry after a diversion, and so the current url may need updating.
+            ##
+            @navigate( fragment, replace: true ) if ( fragment and fragment isnt Backbone.history.fragment )

             if ( @pageView instanceof View )

@@ -357,7 +446,6 @@
             #
             #   @param          {Backbone.View}     view                    The view instance currently loaded.
             #   @param          {Object}            params                  A key-value mapping of the matched route's url pattern's parameters.
-            #
             ###

             @trigger( 'open', @pageView, params )


note next step with TODO comment

note next step with TODO comment

--- jsapp/xlform/src/model.survey.coffee
+++ jsapp/xlform/src/model.survey.coffee
@@ -106,6 +106,9 @@
               (r) => @_ensure_row_list_is_copied(r),
               {includeGroups: true}
             )
+
+          # TODO: adding same block multiple times places it in wrong place
+          # after first time

           @_insertRowInPlace(
             row,


Another fix

Another fix

--- test/tests/LayerAnimationTest.coffee
+++ test/tests/LayerAnimationTest.coffee
@@ -1288,7 +1288,7 @@
 					time: 0.1
 			Utils.delay a.options.time / 2, ->
 				l.x.should.be.within(-5, 40)
-				l.y.should.be.within(50, 90)
+				l.y.should.be.within(40, 90)

 			a.onAnimationEnd ->
 				l.x.should.equal 45


Remove Davy references.

Remove Davy references.

--- app/initialize.coffee
+++ app/initialize.coffee
@@ -4,10 +4,6 @@

   # Add FastClick
   FastClick.attach(document.body)
-
-  # Add Davy promises if available and we are using Exoskeleton
-  if !Backbone.Deferred and window.Davy
-    Backbone.Deferred = -> new Davy

   # Set up Rivets if available
   if window.rivets then rivets.adapters[':'] =


Added additional commands to list log files and their content

--- src/scripts/singularity.coffee
+++ src/scripts/singularity.coffee
@@ -17,6 +17,9 @@
 #   hubot singularity show active tasks for <request> on <alias>
 #   hubot singularity kill task <taksId> on <alias>
 #   hubot singularity show task updates for <taskId> on <alias>
+#   hubot singularity show log files for task <taskId> on <alias>
+#   hubot singularity show log <logName> for task <taskId> on <alias> <length> <offset>
+#   hubot singularity grep log <logName> for task <taskId> on <alias> <length> <offset> <grepString>
 #
 # Author:
 #   Arca Artem - <aartem@opentable.com>
@@ -93,6 +96,9 @@
     msg.send "hubot singularity show active tasks for <request> on <alias>"
     msg.send "hubot singularity kill task <taksId> on <alias>"
     msg.send "hubot singularity show task updates for <taskId> on <alias>"
+    msg.send "hubot singularity show log files for task <taskId> on <alias>"
+    msg.send "hubot singularity show log <logName> for task <taskId> on <alias> <length> <offset>"
+    msg.send "hubot singularity grep log <logName> for task <taskId> on <alias> <length> <offset> <grepString>"

   showActiveTasks = (msg, request, alias) ->
     if not isAliasDefined msg, alias
@@ -197,6 +203,54 @@
       msg.send "Request '#{request}' not found on #{alias}"
     )

+  showTaskLogFiles = (msg, taskId, alias) ->
+    if not isAliasDefined msg, alias
+      return
+
+    url = _singularityAliases[alias].url + '/api/sandbox/' + taskId + '/browse'
+
+    robot.http(url)
+      .header('Accept', 'application/json')
+      .get() (err, res, body) ->
+        if err or res.statusCode == 404
+          msg.send "There was an error while making the request:"
+          msg.send error or body
+          return
+
+        try
+          logFiles = JSON.parse body
+          msg.send "Log files for: '#{taskId}'"
+          for file in logFiles.files
+            msg.send "Name: '#{file.name}', size: #{file.size}[B]"
+        catch error
+          msg.send "Ran into an error parsing JSON :"
+          msg.send error
+
+  showTaskLog = (msg, logName, taskId, alias, length, offset, grep) ->
+    if not isAliasDefined msg, alias
+      return
+
+    grepParam = if grep != undefined then "&grep=#{grep}" else ""
+    url = "#{_singularityAliases[alias].url}/api/sandbox/#{taskId}"+
+          "/read?path=#{logName}&offset=#{offset}&length=#{length}"+
+          "#{grepParam}"
+
+    robot.http(url)
+      .header('Accept', 'application/json')
+      .get() (err, res, body) ->
+        if err or res.statusCode == 404
+          msg.send "There was an error while making the request:"
+          msg.send error or body
+          return
+
+        try
+          log = JSON.parse body
+          msg.send "Log content for: '#{logName}'"
+          msg.send log.data
+        catch error
+          msg.send "Ran into an error parsing JSON :"
+          msg.send error
+
   robot.brain.on 'loaded', ->
     if robot.brain.data.singularity_aliases?
       _singularityAliases = robot.brain.data.singularity_aliases
@@ -225,5 +279,14 @@
   robot.respond /singularity show task updates for (.*) on (.*)/i, (msg) ->
     showTaskUpdates msg, msg.match[1], msg.match[2]

+  robot.respond /singularity show log files for task (.*) on (.*)/i, (msg) ->
+    showTaskLogFiles msg, msg.match[1], msg.match[2]
+
+  robot.respond /singularity show log (.*) for task (.*) on (.*) (.*) (.*)/i, (msg) ->
+    showTaskLog msg, msg.match[1], msg.match[2], msg.match[3], msg.match[4], msg.match[5]
+
+  robot.respond /singularity grep log (.*) for task (.*) on (.*) (.*) (.*) (.*)/i, (msg) ->
+    showTaskLog msg, msg.match[1], msg.match[2], msg.match[3], msg.match[4], msg.match[5], msg.match[6]
+
   robot.respond /singularity\?/i, (msg) ->
     showCommands(msg)


better true testing for async on checkout


Fix SAT to only compare edges facing the other object

--- app/js/physics.coffee
+++ app/js/physics.coffee
@@ -49,12 +49,16 @@
   debug: {}
   resetDebug: -> @debug = {}
   drawDebug: (ctx) ->
+    if edge = @debug.myBest
+      Utils.debugLine ctx, edge.from, edge.to, "#F66"
+    if edge = @debug.theirBest
+      Utils.debugLine ctx, edge.from, edge.to, "#66F"
     if ref = @debug.reference
-      Utils.debugLine ctx, ref.from, ref.to, "#0F0"
+      Utils.debugLine ctx, ref.from, ref.to, "#F66"
     if inc = @debug.incident
-      Utils.debugLine ctx, inc.from, inc.to, "#FF0"
+      Utils.debugLine ctx, inc.from, inc.to, "#66F"
     if clipped = @debug.clipped
-      Utils.debugLine ctx, clipped[0], clipped[1], "#0FF"
+      Utils.debugLine ctx, clipped[0], clipped[1], "#FF0"
     if trimmed = @debug.trimmed
       Utils.debugPoints ctx, "#000", trimmed...

@@ -65,8 +69,8 @@
     console.log "found minimum separation axis of #{minAxis}"
     return [] unless minAxis

-    e1 = @bestEdge minAxis
-    e2 = other.bestEdge Vec.invert minAxis # A->B always
+    @debug.myBest = e1 = @bestEdge minAxis
+    @debug.theirBest = e2 = other.bestEdge Vec.invert minAxis # A->B always

     # Now, clip the edges. Do a series of line/plane clips to get the contact
     # manifold. Identify the reference edge and incident edge. Reference is most
@@ -184,7 +188,7 @@
     minAxis    = null
     minOverlap = Infinity

-    for axis in @perpendicularAxes()
+    for axis in @perpendicularAxesFacing(other)
       us = @projectionInterval axis
       them = other.projectionInterval axis
       overlap = Utils.intervalOverlap us, them
@@ -193,7 +197,7 @@
         minOverlap = overlap
         minAxis = axis

-    for axis in other.perpendicularAxes()
+    for axis in other.perpendicularAxesFacing(this)
       us = @projectionInterval axis
       them = other.projectionInterval axis
       overlap = Utils.intervalOverlap us, them
@@ -210,6 +214,10 @@
   perpendicularAxes: ->
     for pair in Utils.pairs @points()
       Vec.perpendicular Vec.sub pair[1], pair[0]
+
+  perpendicularAxesFacing: (other) ->
+    dir = Vec.sub other.position, @position
+    (axis for axis in @perpendicularAxes() when Vec.dotProduct(axis, dir) > 0)

 class Rectangle extends Polygon
   constructor: (sizeX, sizeY, position, angle, @color) ->


replace development in config

replace development in config

--- app/lib/config.coffee
+++ app/lib/config.coffee
@@ -14,7 +14,7 @@
       workflowId: '502a701e516bcb0001000002'

   development:
-    apiHost: 'https://api.zooniverse.org'
+    apiHost: 'http://localhost:3000'
     surveys:
       candels:
         id: '50251c3b516bcb6ecb000001'


bugfixes for routename and swipe

bugfixes for routename and swipe

--- client/main.coffee
+++ client/main.coffee
@@ -13,18 +13,16 @@
     )
     swipecontrol.on('swipeleft', (evt) ->
         evt.preventDefault()
-        console.log('swipeleft')
+        console.log('swipe right to left')
         routename = Router.current().route.getName()
         if routename is "root"
-            return
+            history.forward()
         else if routename is "ontap"
-            return
-        else
             history.forward()
     )
     swipecontrol.on('swiperight', (evt) ->
         evt.preventDefault()
-        console.log('swiperight')
+        console.log('swipe left to right')
         routename = Router.current().route.getName()
         if routename is "beer"
             history.back()
@@ -88,5 +86,5 @@
             route = Router.current().route.getName()
             if route is 'beer'
                 return 'right-to-left'
-            else if route is '/' or route is 'ontap'
+            else if route is 'root' or route is 'ontap'
                 return 'left-to-right'


trying to merge

trying to merge

--- client/main.coffee
+++ client/main.coffee
@@ -77,7 +77,11 @@
       return [{name: "All Replies", posts: children}]
     else
       return makeGroups(children)
-  identifier: -> @_id
+  identifier: ->
+    id = @_id
+    if @group_name?
+      id += @group_name
+    return id

   ###
   <div class="your-reply" id="reply-cfa3e5db-97c9-4617-884b-d1e7f173a7ea">
@@ -90,7 +94,6 @@
   events: {
     "click button[name='replySubmit']": (event) ->
       event.stopPropagation()
-      console.log("a")
       replyTextBox = document.getElementById("replyText-#{ @_id }")#Bryan thinks there's a way to do this without traversing the DOM.

       replyContent = replyTextBox.value
@@ -100,7 +103,7 @@
         Posts.insert(
           {
             content: replyContent,
-            parent_id: @_id
+            parent_id: @_id,
             instance_id: @instance_id
           }
         )


Update shit-candidates-say.coffee

Update shit-candidates-say.coffee

--- src/shit-candidates-say.coffee
+++ src/shit-candidates-say.coffee
@@ -18,6 +18,7 @@
   [
     "cover letter: Passionate Rubyist and Microsoft certified database developer.",
     "cover letter: I'm a Ruby developer who first started working with Rails in 2007, before it was cool.",
+    "cover letter: I've been building Rails applications for the last 34 years.",
     "candidate: ...So are you guys an incubator or consultancy or what?",
     "candidate: I'm as close to a CTO as you can get when you work remotely.",
     "candidate explained an arrest by stating, 'We stole a pig, but it was a really small pig.'",


Allow dehighlight.

Allow dehighlight.

--- client/publication.coffee
+++ client/publication.coffee
@@ -74,6 +74,7 @@
   closestElementIndex

 setupTextSelection = (publication, page, $textLayer) ->
+  highlightStartPosition = null
   highlightStartIndex = -1

   $textLayer.mousemove (e) ->
@@ -90,14 +91,27 @@

   $textLayer.mousedown (e) ->
     offset = $textLayer.offset()
-    highlightStartIndex = findClosestElement $textLayer,
+    highlightStartPosition =
       left: e.pageX - offset.left
       top: e.pageY - offset.top
+    highlightStartIndex = findClosestElement $textLayer, highlightStartPosition

   $textLayer.mouseup (e) ->
+    return if highlightStartIndex is -1
+
+    offset = $textLayer.offset()
+    if highlightStartPosition.left is e.pageX - offset.left and highlightStartPosition.top is e.pageY - offset.top
+      hideHiglight $textLayer
+
+    highlightStartPosition = null
     highlightStartIndex = -1

   $textLayer.mouseleave (e) ->
+    return if highlightStartIndex is -1
+
+    hideHiglight $textLayer
+
+    highlightStartPosition = null
     highlightStartIndex = -1

 displayPublication = (publication) ->


Storing annotations.

Storing annotations.

--- client/publication.coffee
+++ client/publication.coffee
@@ -204,8 +204,19 @@
     e.preventDefault()
     Meteor.subscribe 'publications-by-id', @_id, ->
       Deps.afterFlush ->
-        $(template.find('.abstract')).slideToggle(200)
+        $(template.find '.abstract').slideToggle(200)

 Template.publicationAnnotations.annotations = ->
   Annotations.find
     publication: Session.get 'currentPublicationId'
+
+updateAnnotation = (id, template) ->
+  Annotations.update id, $set: body: $(template.find '.text').text(), ->
+    Deps.afterFlush ->
+      $(template.find '.text').focus()
+
+updateAnnotation = _.debounce updateAnnotation, 5000
+
+Template.publicationAnnotations.events =
+  'keyup .text': (e, template) ->
+    updateAnnotation @_id, template


Initialize scroller.

Initialize scroller.

--- client/publication.coffee
+++ client/publication.coffee
@@ -319,6 +319,16 @@
       Notify.error "Error rendering page #{ page.pdfPage.pageNumber }", args

   showTEI: =>
+    focusAnnotationId = null
+
+    # To make sure we are starting with empty slate
+    @_$displayWrapper.empty()
+    publicationDOMReady.set false
+    currentViewport.set
+      top: null
+      bottom: null
+    currentHighlights.set {}
+
     # TODO: Handle errors
     $.ajax
       url: @url()
@@ -384,6 +394,8 @@
             @_highlighter.setNumPages 0
             @_highlighter._checkHighlighting()

+            publicationDOMReady.set true
+
   # Fields needed when displaying (rendering) the publication: those which are needed for PDF URL to be available
   @DISPLAY_FIELDS: ->
     fields:


making create and update RFC2616 compliant (https://www.ietf.org/rfc/rfc2616.txt)

--- src/grapp-rest-resource.coffee
+++ src/grapp-rest-resource.coffee
@@ -63,7 +63,7 @@
           callback: (data, response) ->
             json = (if data.trim() != '' then JSON.parse data else {})
             switch response.status
-              when 200 then success? json
+              when 200, 201, 204 then success? json
               when 401 then self.fire 'grapp-authentication-error'
               else
                 error? json
@@ -77,7 +77,7 @@
           callback: (data, response) ->
             json = (if data.trim() != '' then JSON.parse data else {})
             switch response.status
-              when 200 then success? json
+              when 200, 201, 204 then success? json
               when 401 then self.fire 'grapp-authentication-error'
               else
                 error? json


After creating a wish, redirect to the permalink page of the wish

--- client/view/wishes.coffee
+++ client/view/wishes.coffee
@@ -4,7 +4,9 @@
     title = $('#title').val()
     description = $('#description').val()
     if title
-      Wish.makeWish(title: title, description: description)
+      w = Wish.makeWish(title: title, description: description)
+      Router.go('wish', {id: w})
+      alertify.success('Great, now you can promote it on Twitter')

   'click .delete':  ->
     Wishes.remove(@_id)


InstallApp method re-written by using node-modules if possible

--- kites/applications/api.coffee
+++ kites/applications/api.coffee
@@ -65,6 +65,27 @@

 module.exports = new Kite 'applications'

+  installApp: (options, callback)->
+
+    {username, owner, appPath, appName, version} = options
+
+    version   or= 'latest'
+    kpmAppPath  = escapePath "/opt/Apps/#{owner}/#{appName}/#{version}"
+    userAppPath = escapePath appPath
+
+    if kpmAppPath.indexOf("/opt/Apps/") isnt 0 or not safeForUser username, userAppPath
+      callback? new AuthorizationError username
+      return false
+
+    fs.exists kpmAppPath, (exists)->
+      if not exists then callback? new KodingError "App files not found! Install cancelled."
+      else
+        executeCommand {username, command: "mkdir -p #{userAppPath}"}, (err)->
+          if err then new KodingError "Cannot create Application directory", userAppPath
+          else
+            fse.copy "#{kpmAppPath}/index.js", "#{userAppPath}/index.js", (err)->
+              fse.copy "#{kpmAppPath}/.manifest", "#{userAppPath}/.manifest", (err)->
+                chownr {username, path: userAppPath}, callback
   copyAppSkeleton:(options, callback)->
     {username, appPath, type} = options

@@ -161,36 +182,6 @@
           else
             cb null

-  installApp: (options, callback)->
-
-    {username, owner, appPath, appName, version} = options
-    version ?= 'latest'
-
-    cb = (err)->
-      console.error err if err
-      callback? err, null
-
-    kpmAppPath = escapePath "/opt/Apps/#{owner}/#{appName}/#{version}"
-    appPath    = escapePath appPath
-
-    if kpmAppPath.indexOf("/opt/Apps/") isnt 0 or not safeForUser username, appPath
-      callback? new AuthorizationError username
-      return false
-
-    createAppsDir username, (err)->
-      makedirp appPath, username, (err)->
-        if err then cb err
-        else
-          exec "cp #{kpmAppPath}/index.js #{appPath} && cp #{kpmAppPath}/.manifest #{appPath}", (err, stdout, stderr)->
-            if err or stderr.length
-              cb err or "[ERROR] #{stderr}"
-            else
-              exec "chown -R #{username}: #{appPath}", (err, stdout, stderr)->
-                if err or stderr.length
-                  cb err or "[ERROR] #{stderr}"
-                else
-                  cb null
-
   approveApp: (options, callback)->

     {username, authorNick, version, appName} = options
@@ -214,4 +205,3 @@
         exec "rm -f #{latestPath} && ln -s #{versionedPath} #{latestPath}", (err, stdout, stderr)->
           if err or stderr then cb err
           else cb null
-


Update requirements add pistachioCompiler and async

--- kites/applications/api.coffee
+++ kites/applications/api.coffee
@@ -1,27 +1,38 @@
+## Gokmen Goksel ~ Koding Inc. 2012

 # Kite requirements
-Kite         = require 'kite-amqp'
-config       = require './config'
+Kite           = require 'kite-amqp'
+config         = require './config'

 # Logger
-log4js       = require 'log4js'
-log          = log4js.getLogger("[#{config.name}]")
+log4js         = require 'log4js'
+log            = log4js.getLogger("[#{config.name}]")

 # Custom Libraries for this Kite
-{exec}       = require 'child_process'
-fs           = require 'fs'
-fse          = require 'fs.extra'
-mkdirp       = require 'mkdirp'
-createTmpDir = require './createtmpdir'
+{exec}         = require 'child_process'
+fs             = require 'fs'
+fse            = require 'fs.extra'
+mkdirp         = require 'mkdirp'
+nodePath       = require 'path'
+async          = require 'async'
+pistachioc     = require 'pistachio-compiler'
+
+# Coffee-Script
+coffee         = require 'coffee-script'
+
+# Execute Command
+executeCommand = require '../sharedHosting/executecommand'

 # Utilities
 {normalizeUserPath,
+ createAppsDir,
  safeForUser,
  escapePath,
- createAppsDir,
+ makedirp,
+ slugify,
  chownr,
  getIds,
- makedirp,
+ KodingError,
  AuthorizationError} = require './utils.coffee'

 # Dummy-Admins


Fix bip39

Fix bip39

--- app/src/utils/bitcoin/bip39.coffee
+++ app/src/utils/bitcoin/bip39.coffee
@@ -63,10 +63,12 @@

   entropyToMnemonic: (entropy, entropyBitLength = @ENTROPY_BIT_LENGTH) ->
     # convert it to integers array
-    if entropy instanceof Array
+    if typeof entropy == "string"
+      entropyIntegersArray = entropy.match(/\w{8}/g).map (h) -> parseInt(h,16)
+    else if entropy instanceof Uint8Array
+      entropyIntegersArray = @_bytesArrayToIntegersArray(entropy)
+    else
       entropyIntegersArray = entropy
-    else
-      entropyIntegersArray = entropy.match(/\w{8}/g).map (h) -> parseInt(h,16)
     # apply sha256 to hash
     hashedEntropyIntegersArray = sjcl.hash.sha256.hash entropyIntegersArray
     # get first x bits of hash


make functions more readabe

make functions more readabe

--- app/generator.coffee
+++ app/generator.coffee
@@ -15,7 +15,7 @@
 		@pkg = myUtils.loadJSON("../package.json", __dirname)
 		@settings = myUtils.loadJSON("./settings.json", __dirname)

-	askFor: =>
+	askFor: () =>
 		cb = @async()

@@ -39,15 +39,15 @@
 			message: "What is the domain name for the production website (without protocol)?"
 			default: "example.com"

-		@prompt prompts, (props) =>
+		@prompt prompts, (props) () =>
 			@props = props
 			cb()

-	app: =>
+	app: () =>
 		github = (repo) ->
 			"https://github.com/#{repo}.git"

-		dest= (p = '/')=>
+		dest = (p = '/') =>
 			path.join(@destinationRoot(), p)

 		at = (p, cb) ->
@@ -61,7 +61,7 @@
 		#*------------------------------------*\
 		#   $REPOSITORY
 		#*------------------------------------*/
-		setupRepo = =>
+		setupRepo = () =>
 			@mkdir d for d in [
 				'src',
 				'database/dev',
@@ -84,7 +84,7 @@
 		#*------------------------------------*\
 		#   $PROCESSWIRE
 		#*------------------------------------*/
-		setupProcesswire = =>
+		setupProcesswire = () =>
 			@log.info "Installing ProcessWire..."
 			repo_path = GitUtils.cacheRepo github(@settings.github.processwire)
 			@log.info "Copying ProcessWire install..."
@@ -98,7 +98,7 @@
 			@log.info "Installing ProcessWire MVC boilerplate..."
 			repo_path = GitUtils.cacheRepo github(@settings.github.pwBoilerplate)
 			GitUtils.export repo_path, dest('src/site/templates')
-			at dest('src/'), =>
+			at dest('src/'), () =>
 				shell.ls('-A', "site/templates/\!root/*").forEach (file) ->
 					shell.mv '-f', file, '.'
 				shell.rm '-rf', "site/templates/\!root"
@@ -106,7 +106,7 @@
 				shell.rm "robots.txt"

 			# remove default ProcessWire templates
-			at dest('src/site/templates/'), =>
+			at dest('src/site/templates/'), () =>
 				shell.rm '-rf', "scripts"
 				shell.rm '-rf', "styles"
 				shell.rm "README.txt"
@@ -119,7 +119,7 @@
 				shell.rm "_func.php"

 			# remove alternative ProcessWire site profiles
-			at dest('src/'), =>
+			at dest('src/'), () =>
 				shell.rm '-rf', "site-beginner"
 				shell.rm '-rf', "site-blank"
 				shell.rm '-rf', "site-classic"
@@ -132,7 +132,7 @@
 			@copy ".gitkeep", "src/site/assets/logs/.gitkeep"

 			# setup for ProcessWire install
-			at dest('src/site/'), =>
+			at dest('src/site/'), () =>
 				shell.chmod '777', "assets"
 				shell.chmod '777', "assets/*"
 				shell.chmod '777', "modules"
@@ -144,12 +144,12 @@
 		#*------------------------------------*\
 		#   $KSS BOILERPLATE
 		#*------------------------------------*/
-		setupKSS = =>
+		setupKSS = () =>
 			@log.info "Installing KSS Boilerplate"
 			repo_path = GitUtils.cacheRepo github(@settings.github.kssBoilerplate)
 			@mkdir 'styleguide'
 			GitUtils.export repo_path, dest('styleguide/')
-			at dest('styleguide/'), =>
+			at dest('styleguide/'), () =>
 				@log.info 'KSS Living Styleguide - bundle install'
 				GitUtils.exec 'bundle install'
 				shell.mv '-f', './scss', '../src/site/templates/assets/css'
@@ -160,7 +160,7 @@
 		#*------------------------------------*\
 		#   $CSS FRAMEWORK
 		#*------------------------------------*/
-		setupCSSFramework = =>
+		setupCSSFramework = () =>
 			@log.info "Installing CSS framework..."

 			repo_path = GitUtils.cacheRepo github(@settings.github.cssFramework)
@@ -176,7 +176,7 @@
 		#*------------------------------------*\
 		#   $GIT
 		#*------------------------------------*/
-		setupGit= =>
+		setupGit = () =>
 			GitUtils.init(dest())

@@ -189,7 +189,7 @@
 		setupCSSFramework()
 		setupGit()

-	projectfiles: =>
+	projectfiles: () =>
 		@copy "_gulpfile.coffee", "gulpfile.coffee"
 		@copy ".editorconfig", ".editorconfig"


add search contains #492

add search contains #492

--- client/index.coffee
+++ client/index.coffee
@@ -44,6 +44,7 @@
   # Chosen
   $('select').chosen
     no_results_text: '沒有選項符合'
+    search_contains: on

   # Popover
   $('.popover-trigger').webuiPopover()


add parentheses to snippet if no params

add parentheses to snippet if no params

--- lib/atom-ternjs-helper.coffee
+++ lib/atom-ternjs-helper.coffee
@@ -127,8 +127,8 @@

   extractParams: (type, name) ->
     params = type.match(@manager.regExp.params)
+    return "#{name}()" if !params
     suggestionParams = []
-    return unless params
     for param, i in params
       suggestionParams.push "${#{i + 1}:#{param}}"
     "#{name}(#{suggestionParams.join(',')})"


change apply argument

change apply argument

--- lib/control-structures.coffee
+++ lib/control-structures.coffee
@@ -84,14 +84,14 @@
     if isarr
       for val,i in obj
         num++
-        applyfunc.apply null,[val,i,->
-          next.apply null,[num,endfunc].concat([].slice.call(arguments))
+        applyfunc.apply this,[val,i,->
+          next.apply this,[num,endfunc].concat([].slice.call(arguments))
         ].concat(applyargs_array)
     else
       for key,val of obj
         num++
-        applyfunc.apply null,[key,val,->
-          next.apply null,[num,endfunc].concat([].slice.call(arguments))
+        applyfunc.apply this,[key,val,->
+          next.apply this,[num,endfunc].concat([].slice.call(arguments))
         ].concat(applyargs_array)
   )(->
     count = 0
@@ -105,7 +105,7 @@
             result.push([])
           result[i].push(arg)
       if count == num
-        next.apply null,result
+        next.apply this,result
   )

 ###
@@ -141,17 +141,17 @@
   (y (func) ->
     return ->
       if f_judge i
-        loopfunc.apply null,[i,->
+        loopfunc.apply this,[i,->
           #_break
-          endfunc.apply null,[i].concat([].slice.call(arguments))
+          endfunc.apply this,[i].concat([].slice.call(arguments))
         ,->
           #_next
           i = f_iter i
-          func.apply null,arguments
+          func.apply this,arguments
         ].concat([].slice.call(arguments))
       else
-        endfunc.apply null,[i].concat([].slice.call(arguments))
-  ).apply null,firstarg_array
+        endfunc.apply this,[i].concat([].slice.call(arguments))
+  ).apply this,firstarg_array

 ###
    for in Array or Object
@@ -179,21 +179,21 @@
     return ->
       if i < indexlimit
         if isarr
-          loopfunc.apply null,[obj[i],i, ->
-            endfunc.apply null,arguments
+          loopfunc.apply this,[obj[i],i, ->
+            endfunc.apply this,arguments
           ,->
-            func.apply null,arguments
+            func.apply this,arguments
           ].concat([].slice.call(arguments))
         else
           key = Object.keys(obj)[i]
           value = obj[key]
-          loopfunc.apply null,[key, value, ->
-            endfunc.apply null,arguments
+          loopfunc.apply this,[key, value, ->
+            endfunc.apply this,arguments
           ,->
-            func.apply null,arguments
+            func.apply this,arguments
           ].concat([].slice.call(arguments))
       else
-        endfunc.apply null,arguments
+        endfunc.apply this,arguments
   )(firstargs)

 ###
@@ -220,22 +220,22 @@
       endfunc(...)
          次の処理です。上記の通り、引数を受け取ることができます。
 ###
-target['_while'] = _while = (f_judge, firstarg_array, loopfunc, endfunc) ->
+target['_while'] = _w = (f_judge, firstarg_array, loopfunc, endfunc) ->
   if firstarg_array?
     firstarg_array = []
   (y (func) ->
     return ->
-      if f_judge.apply null,arguments
-        loopfunc.apply null,[->
+      if f_judge.apply this,arguments
+        loopfunc.apply this,[->
           #_break
-          endfunc.apply null,[].slice.call(arguments)
+          endfunc.apply this,[].slice.call(arguments)
         ,->
           #_next
-          func.apply null,arguments
+          func.apply this,arguments
         ].concat([].slice.call(arguments))
       else
-        endfunc.apply null,[].slice.call(arguments)
-  ).apply null,firstarg_array
+        endfunc.apply this,[].slice.call(arguments)
+  ).apply this,firstarg_array

 ###
    exception handling
@@ -281,15 +281,15 @@

   fa = ->
     stackpop = _stack.pop()
-    stackpop._finally.apply null,[].slice.call(arguments)
+    stackpop._finally.apply this,[].slice.call(arguments)

   _finally: ->
     stackpop = _stack.pop()
-    stackpop._finally.apply null,[stackpop.next].concat([].slice.call(arguments))
+    stackpop._finally.apply this,[stackpop.next].concat([].slice.call(arguments))

   _throw: (_e) =>
     catchset = {}
-    _while ->(true),
+    _w ->(true),
     [],
     (_break,_next)=>
       catchset = _stack.pop()
@@ -297,12 +297,12 @@
         _stack.push catchset
         result = catchset.e_array.indexOf _e
         if result != -1
-          catchset._catch.apply null,[_e].concat([].slice.call(arguments))
+          catchset._catch.apply this,[_e].concat([].slice.call(arguments))
         else
           if arguments.length <= 2
-            fa.apply null,[_next]
+            fa.apply this,[_next]
           else
-            fa.apply null,[].slice.call(arguments).shift().shift().unshift(_next)
+            fa.apply this,[].slice.call(arguments).shift().shift().unshift(_next)
       else
         _break()
     ,->


Use option

Use option

--- lib/csv-editor-element.coffee
+++ lib/csv-editor-element.coffee
@@ -121,7 +121,7 @@
     delete @formContainer

   updatePreview: (options) ->
-    return if options.remember
+    return if options.remember or atom.config.get('tablr.disablePreview')

     @form.preview.clean()
     @model.previewCSV(options).then (preview) =>