Clean up some template markup based on jQM beta update.

--- views/default.coffee
+++ views/default.coffee
@@ -5,28 +5,22 @@
             h1 'traphicam'
         div 'data-role': 'content', ->
             ul 'data-role': 'listview', ->
-                li class: 'ui-li-has-icon ui-btn ui-btn-icon-right ui-li ui-btn-down-c ui-btn-up-c', ->
-                    div class: 'ui-btn-inner ui-li', ->
-                        div class: 'ui-btn-text', ->
-                            a class: 'ui-link-inherit', href: 'nearby', ->
-                                img class: 'ui-li-icon ui-li-thumb',
-                                    src: 'img/74-location.png'
-                                    alt: 'Location'
-                                h3 class: 'ui-li-heading', 'Cameras nearby'
-                                p class: 'ui-li-desc', 'Using your current position'
-                        span class: 'ui-icon ui-icon-arrow-r'
-
-                li class: 'ui-li-has-icon ui-btn ui-btn-icon-right ui-li ui-btn-down-c ui-btn-up-c', ->
-                    div class: 'ui-btn-inner ui-li', ->
-                        div class: 'ui-btn-text', ->
-                            a class: 'ui-link-inherit', href: 'roads', ->
-                                img class: 'ui-li-icon',
-                                    src: 'img/roads.png'
-                                    alt: 'Roads'
-                                h3 class: 'ui-li-heading', 'Cameras by road'
-                                p class: 'ui-li-desc', 'Find cameras by the roads they are on'
-                        span class: 'ui-icon ui-icon-arrow-r'
-
+                li ->
+                    a href: 'nearby', ->
+                        img class: 'ui-li-icon',
+                            src: 'img/74-location.png',
+                            alt: 'Location'
+                        h1 'Cameras nearby'
+                        p 'Using your current position'
+                li ->
+                    a href: 'roads', ->
+                        img class: 'ui-li-icon',
+                            src: 'img/roads.png',
+                            alt: 'Roads'
+                        h1 'Cameras by road'
+                        p 'Find cameras by the roads they are on'
             br ->
-            p "Welcome! traphicam.com will display the nearest Philadelphia regional traffic cameras to your current location, if you choose to share it. At the moment, it's probably most useful when accessed with your smart phone."
-            p "Hopefully you will find it helpful during your commute or local Philly trip planning.  Many more features and improvments are coming soon including auto-refreshing and opt-in location watching. In the meantime, please excuse the rough edges of this βeta software!"
+            p ->
+                "Welcome! traphicam.com will display the nearest Philadelphia regional traffic cameras to your current location, if you choose to share it. At the moment, it's probably most useful when accessed with your smart phone."
+            p ->
+                "Hopefully you will find it helpful during your commute or local Philly trip planning.  Many more features and improvments are coming soon including auto-refreshing and opt-in location watching. In the meantime, please excuse the rough edges of this βeta software!"


Fix grammarMap check

Fix grammarMap check

--- lib/script.coffee
+++ lib/script.coffee
@@ -15,11 +15,10 @@
   activate: ->
     atom.project.registerOpener (uri) =>

-      if @lang in grammarMap
-        interpreter = grammarMap[@lang]["interpreter"]
-        makeargs = grammarMap[@lang]["makeargs"]
+      interpreter = grammarMap[@lang]["interpreter"]
+      makeargs = grammarMap[@lang]["makeargs"]

-        @scriptView = new ScriptView(interpreter, makeargs) if uri is configUri
+      @scriptView = new ScriptView(interpreter, makeargs) if uri is configUri

     atom.workspaceView.command "script:run-selection", =>
       editor = atom.workspace.getActiveEditor()
@@ -41,7 +40,7 @@
         console.log("save your file with an appropriate extension.")
         return

-      if grammar.name not in grammarMap
+      if ! grammar.name in grammarMap
         console.log("Interpreter not configured for " + @lang)
         console.log("Send a pull request to add support!")
         return


path instead of file

path instead of file

--- lib/status.coffee
+++ lib/status.coffee
@@ -18,10 +18,10 @@
 	lines = _.str.lines out
 	lines.map (l) ->
 		status = l.substr(0, 2)
-		file = l.substr(2).trim()
+		path = l.substr(2).trim()
 		return {
-			status: fullStatus(status)
-			file: file
+		path: path
+		status: fullStatus(status)
 		}

 fullStatus = (s) ->
@@ -33,14 +33,16 @@
 		when 'R' then 'renamed'
 		when 'C' then 'copied'
 		when 'U' then 'updated'
-		else return ''
+		else
+			return ''

 # normal output parser
 parseNormal = (out) ->
 	return [] if not out

 	lines = _.str.lines out
-	lines = lines.map (l) -> _.str.ltrim(l, '#').trim()
+	lines = lines.map (l) ->
+		_.str.ltrim(l, '#').trim()

 	statuses = [
 		['new file:', 'new'],
@@ -52,10 +54,11 @@
 	# todo support untracked files

 	files = lines.map (l) ->
-		st = _.find statuses, (s) -> _.str(l).startsWith(s[0])
+		st = _.find statuses, (s) ->
+			_.str(l).startsWith(s[0])
 		if st
-			file = l.substr(st[0].length).trim()
-			return {file: file, status: st[1]}
+			path = l.substr(st[0].length).trim()
+			return {path: path, status: st[1]}
 		return null

 	return files.filter (f) -> f?


Use onDidActivateAll

Use onDidActivateAll

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -61,7 +61,7 @@
     if atom.workspace? and atom.workspaceView?
       resolve()
     else
-      @subscribe atom.packages.once('activated', resolve)
+      @subscribe atom.packages.onDidActivateAll(resolve)

   editorForId: (editorId) ->
     for editor in atom.workspace.getEditors()


Fix the interaction between batch actions

--- app/assets/javascripts/active_admin/lib/batch_actions.js.coffee
+++ app/assets/javascripts/active_admin/lib/batch_actions.js.coffee
@@ -14,7 +14,7 @@

   $('.batch_actions_selector li a').on 'confirm:complete', (e, inputs)->
     if val = JSON.stringify inputs
-      $('#batch_action_inputs').val val
+      $('#batch_action_inputs').removeAttr('disabled').val val
     else
       $('#batch_action_inputs').attr 'disabled', 'disabled'


Fixed multi form values.

Fixed multi form values.

--- app/assets/javascripts/app/controllers/_settings/area.js.coffee
+++ app/assets/javascripts/app/controllers/_settings/area.js.coffee
@@ -28,13 +28,16 @@
     @render()

   render: =>
+
     # defaults
     directValue = 0
+    directData  = undefined
     for item in @setting.options['form']
       directValue = +1
+      directData  = @setting.state.value[item.name]

     if directValue > 1
-      item['default'] = @setting.state.value[item.name]
+      item['default'] = directData
     else
       item['default'] = @setting.state.value

@@ -55,10 +58,12 @@
   update: (e) =>
     e.preventDefault()
     params = @formParam(e.target)
-    @log 'submit', @setting, params, e.target, typeof @setting.state.value
+
     directValue = 0
+    directData  = undefined
     for item in @setting.options['form']
       directValue = +1
+      directData  = params[item.name]

     if directValue > 1
       state = {
@@ -66,7 +71,7 @@
       }
     else
       state = {
-        value: params[@setting.name]
+        value: directData
       }

     @setting['state'] = state


fix in crop widget

fix in crop widget

--- app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
+++ app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
@@ -62,13 +62,12 @@
       # Example:
       #   new CropWidget
       #     container: '.crop-widget-home'
-      #     url: 'http://ucarecdn.com/%something%/'
       #     upscale: true
       #     widgetSize: '500x300'
       #     preferedSize: '100x100'
       constructor: (options) ->
         @__options = $.extend {}, defaultOptions, options
-        option.scale = false unless options.preferedSize
+        @__options.scale = false unless @__options.preferedSize
         checkOptions @__options
         @onStateChange = $.Callbacks()
         @__buildWidget()


Fix fact filters

Fix fact filters

--- app/assets/javascripts/views/actions/request/filter/item.coffee
+++ app/assets/javascripts/views/actions/request/filter/item.coffee
@@ -20,11 +20,7 @@
   getRequestFilter: ->
     filterType = @model.attributes.filtertype
     if filterType == "fact"
-      r = { operator : $(@$el).find(".filter-operator").val(), value : @model.attributes.term, fact : @model.attributes.term_key }
-      if r.value == undefined or r.term_key == undefined or r.operator == undefined
-        return undefined
-      else
-        return r
+      return { operator : $(@$el).find(".filter-operator").val(), value : @model.attributes.term, fact : @model.attributes.term_key }
     else
       return $(@$el).find(".filter-string").val()


Moved dom_events as they are not used in CreateButton

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -1,12 +1,9 @@
 class Button
-  constructor: (dom_events={}) ->
+  constructor: ->
     @frame = new FactlinkJailRoot.ControlIframe()
     @frame.setContent($.parseHTML(@content.trim())[0])
     @$el = $(@frame.frameBody.firstChild)
     @$el.on 'mouseenter', => @_addClass 'fl-button-state-hovered'
-
-    for event, callback of dom_events
-      @$el.on event, callback

   startLoading: => @_addClass 'fl-button-state-loading'
   stopLoading: => @_removeClass 'fl-button-state-loading fl-button-state-hovered'
@@ -54,10 +51,13 @@
     </div>
   """

-  constructor: ->
+  constructor: (dom_events={}) ->
     super
     @$el.on 'mouseleave', => @_removeClass 'fl-button-state-hovered'
     $(document).on 'mousemove', => @_removeClass 'fl-button-state-hovered'
+
+    for event, callback of dom_events
+      @$el.on event, callback

   _textContainer: ($el) ->
     for el in $el.parents()


Fix Back Button when photograph is displayed

--- app/assets/javascripts/browser.coffee
+++ app/assets/javascripts/browser.coffee
@@ -13,17 +13,20 @@
 		# Setup HTML5 Browser Buttons for Single Page
 		window.addEventListener("popstate", (event) =>
 			console.log(event)
-			$.fancybox.close()
-			if event.state?
-				if event.state.album?
-					if event.state.story?
-						@displayPhotographs(event.state.album, event.state.story)
-					else
-						@displayStories(event.state.album)
+			if $.fancybox.isOpen
+				history.forward()
+				$.fancybox.close()
+			else
+				if event.state?
+					if event.state.album?
+						if event.state.story?
+							@displayPhotographs(event.state.album, event.state.story)
+						else
+							@displayStories(event.state.album)
+					else
+						@displayAlbums()
 				else
 					@displayAlbums()
-			else
-				@displayAlbums()
 			return
 		)

@@ -198,6 +201,9 @@
 					type: 'image',
 					afterLoad: ->
 						originalImageLink = @.href.split("/resize")
+						#photographUrlSplit = originalImageLink[0].split("/image")[0].split("/")
+						#photograph = photographUrlSplit[photographUrlSplit.length-1]
+						#{}history.pushState({photograph: photograph}, null, '#photographId='+photograph)
 						@.title = 'Download '+@.title
 				})
 				return
@@ -205,5 +211,3 @@
 				return
 		})
 		return
-
-


Bootstrap PodsController js class.

Bootstrap PodsController js class.

--- app/assets/javascripts/pods.js.coffee
+++ app/assets/javascripts/pods.js.coffee
@@ -2,8 +2,19 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-ready = ->
-  render_response = (response, chunk_id) ->
+class @PodsController
+  loadPods: ->
+    for chunk in gon.pods_index
+      @.loadPodsChunk(chunk[0])
+  loadPodsChunk: (chunk_id) ->
+    controller = @
+    xhr = new XMLHttpRequest()
+    xhr.open('GET', '/pods/'+chunk_id+'.mpac', true)
+    xhr.responseType = 'arraybuffer'
+    xhr.onload = (e) ->
+      controller.renderPodsChunk(@.response, chunk_id)
+    xhr.send()
+  renderPodsChunk: (response, chunk_id) ->
     pods = msgpack.decode(response)
     html = JST['pods_tpl']
       pods: pods
@@ -14,15 +25,8 @@
       $('.progress').slideUp(1000)
       window.load_categories()
       $(".timeago").timeago()
-  request_pods = (chunk_id) ->
-    xhr = new XMLHttpRequest()
-    xhr.open('GET', '/pods/'+chunk_id+'.mpac', true)
-    xhr.responseType = 'arraybuffer'
-    xhr.onload = (e) ->
-      render_response(@.response, chunk_id)
-    xhr.send()
-  for chunk in gon.pods_index
-    request_pods(chunk[0])

+ready = ->
+  (new PodsController).loadPods()
 $(document).ready(ready)
 $(document).on('page:load', ready)


Improve loading functionality on Preview

--- app/assets/javascripts/tips.js.coffee
+++ app/assets/javascripts/tips.js.coffee
@@ -35,18 +35,20 @@
     event.preventDefault()

     unless $("#code_preview_tab").parent().hasClass("active")
-
+      $("#code_preview_tab").addClass("load").html("loading...")
       url = "/tips/preview"
       body = { body: $("#tip_body").val() }
-      $.post url, body, success: ->
-      $("#code_preview_tab").addClass("load").html("loading...")
-      console.log("success")
+      $.post(url, body).then ->
+        $("#code_preview_tab").addClass("preview").html("Preview")
+        console.log("success")
+      .fail ->
+        console.log "Oh noes"

       $("#write_input").hide()
       $("#code_preview").show()
       $("#code_preview_tab").parent().addClass("active")
       $("#write_tab").parent().removeClass("active")
-      # $("#code_preview_tab").addClass("preview").html("Preview")
+

   # Switch back to tip body input
   $('#write_tab').click (event) ->


Use song id as key.

Use song id as key.

--- app/client/js/react/pages/home.coffee
+++ app/client/js/react/pages/home.coffee
@@ -10,15 +10,15 @@
   ###
   constructor: (routes, songsStore, touch) ->
     {div,h1,ul,li,br} = React.DOM
-    {a} = touch.scroll 'a', 'button'
+    {a} = touch.scroll 'a'

     @create = React.createClass

       render: ->
         div className: 'home',
           h1 null, Home.MSG_MY_SONGS,
-          ul null, songsStore.all().map (song, i) ->
-            li key: i,
+          ul null, songsStore.all().map (song) ->
+            li key: song.id,
               a href: routes.mySong.createUrl(song),
                 "#{song.name} [#{song.artist}]"
           a


Fix X3d controller name

Fix X3d controller name

--- app/controllers/x3d-controller.coffee
+++ app/controllers/x3d-controller.coffee
@@ -4,7 +4,7 @@
 Sidebar = require 'models/sidebar'
 x3dom = require 'x3dom'

-module.exports = class X3dsController extends Controller
+module.exports = class X3dController extends Controller
   historyURL: 'x3d'

   index: ->


Revert "keep the twitter image square                     [finishes #16138693]"

This reverts commit 9abcc3c26cb57e6f2dc20d19d3116eacd8a1f1df.

--- public/javascripts/people.coffee
+++ public/javascripts/people.coffee
@@ -28,7 +28,7 @@
       $form.find('.bio textarea').text (i, t) -> t or data.description

       unless $form.find('.image_url input').val()
-        image_url = data.profile_image_url.replace '_normal.', '_bigger.'
+        image_url = data.profile_image_url.replace '_normal.', '.'
         $form.find('.image_url')
           .find('img.avatar').attr('src', image_url).end()
           .find('input').val image_url


Fixed a typo in the achievements:new event schema.

--- app/schemas/subscriptions/misc.coffee
+++ app/schemas/subscriptions/misc.coffee
@@ -25,7 +25,7 @@
     viewClass: {type: 'function'}
     viewArgs: {type: 'array'}

-  'achievements:new': c.object {required: 'earnedAchievements'},
+  'achievements:new': c.object {required: ['earnedAchievements']},
     earnedAchievements: {type: 'object'}

   'ladder:game-submitted': c.object {required: ['session', 'level']},


Stop doing the selected thing and marker change.

--- app/scripts/controllers/guests.coffee
+++ app/scripts/controllers/guests.coffee
@@ -46,11 +46,6 @@
               'icon': item.properties['marker-symbol']
               'color': item.properties['marker-color']

-          # Bind to clicks
-          marker.on 'click', ->
-            $scope.selected = @
-          , item # To be executed with the context of item
-
           pu = '<div map-item-detail/>'
           marker.bindPopup pu,
             item: item
@@ -68,7 +63,7 @@
       item = leafletEvent.leafletEvent.popup.options.item

       newScope = $scope.$new()
-      newScope.item = item
+      newScope.selected = item

       $compile(leafletEvent.leafletEvent.popup._contentNode)(newScope)

@@ -82,27 +77,3 @@
         group = L.featureGroup items.map((i) -> i.marker)
         group.addTo map
         map.fitBounds group.getBounds()
-
-        # Pre-select the venue
-        unless $scope.selected
-          $scope.selected = items.filter((i) -> i.properties.type is 'venue')[0]
-
-    # Watch for selected changes
-    $scope.$watch 'selected', (newSelection, oldSelection) ->
-      # Reset currently selected marker
-      if oldSelection
-        oldSelection.marker.setIcon $window.L.MakiMarkers.icon
-            'size': 'm'
-            'icon': oldSelection.properties['marker-symbol']
-            'color': oldSelection.properties['marker-color']
-        # Reset zIndex
-        oldSelection.marker.setZIndexOffset oldSelection.zIndex
-
-      return unless newSelection
-      # Marker size large
-      newSelection.marker.setIcon $window.L.MakiMarkers.icon
-        'size': 'l'
-        'icon': newSelection.properties['marker-symbol']
-        'color': newSelection.properties['marker-color']
-      # On top
-      newSelection.marker.setZIndexOffset 1000


Fix collapse arrow on iPad

Fix collapse arrow on iPad

--- app/assets/javascripts/martian_components/components/collapse.coffee
+++ app/assets/javascripts/martian_components/components/collapse.coffee
@@ -84,7 +84,7 @@

   closeCollapse: ->
     return unless @text.open
-    @collapseToggle.velocity rotateX: '0deg'
+    @collapseToggle.velocity rotateX: '0deg', translateZ: '0px'
     @text.css 'overflow', 'hidden'

     @text.velocity


Support shell user name and id from the env

Also changes the type of the user.id property, this should be an integer to match campfire.

--- src/adapters/shell.coffee
+++ src/adapters/shell.coffee
@@ -50,7 +50,9 @@
             when "history"
               stdout.write "#{line}\n" for line in @repl.history
             else
-              user = @robot.brain.userForId '1', name: 'Shell', room: 'Shell'
+              user_id = parseInt(process.ENV.HUBOT_SHELL_USER_ID or '1')
+              user_name = process.ENV.HUBOT_SHELL_USER_NAME or 'Shell'
+              user = @robot.brain.userForId user_id, name: user_name, room: 'Shell'
               @receive new TextMessage user, buffer, 'messageId'
           @repl.prompt(true)

@@ -59,5 +61,6 @@
         @repl.setPrompt "#{@robot.name}> "
         @repl.prompt(true)

+
 exports.use = (robot) ->
   new Shell robot


currency filter added for total_amount

currency filter added for total_amount

--- app/scripts/directives/d3Donut.coffee
+++ app/scripts/directives/d3Donut.coffee
@@ -3,7 +3,7 @@
 # contributor is an object with the following properties: total_amount, name

 angular.module('appApp.directives')
-  .directive 'd3Donut', ['ApiGet', (ApiGet)->
+  .directive 'd3Donut', ['ApiGet', '$filter', (ApiGet, $filter)->
     restrict: 'E'
     scope:
       scale: '='
@@ -57,7 +57,7 @@
           .classed("d3Donut-company", true)
           .attr("width", scope.radius)
           .attr("transform", "translate(#{scope.radius},#{scope.radius * 2 + 25})")
-          .text(scope.contributor.total_amount)
+          .text($filter('currency')(scope.contributor.total_amount))
         # create the paths for each donute section
         arc = d3.svg.arc()
           .innerRadius(scope.radius - 5)


payment: Dashboard app controller: DRY

payment: Dashboard app controller: DRY

We're doing the same high-level stuff whether we're dealing in products or plans

--- client/app/Applications/Dashboard.kdapplication/AppController.coffee
+++ client/app/Applications/Dashboard.kdapplication/AppController.coffee
@@ -134,45 +134,35 @@
               modal.destroy()
               @refreshPaymentView()

-  productViewAdded: (pane, view) ->
-
-    { JPaymentProduct, JPaymentPlan } = KD.remote.api
-
-    groupsController = KD.getSingleton 'groupsController'
-
-    group = groupsController.getCurrentGroup()
-
-    reloadProducts = ->
-      group.fetchProducts (err, products) -> view.setProducts products
-
-    reloadPlans = -> # noop for now
-
-    view.on 'ProductCreateRequested', (productData) ->
-      JPaymentProduct.create productData, (err) ->
-        return if KD.showError err
-
-        reloadProducts()
-
-    view.on 'ProductDeleteRequested', (code) ->
-      JPaymentProduct.removeByCode code, (err) ->
-        return if KD.showError err
-
-        reloadProducts()
-
-    view.on 'PlanCreateRequested', (planData) ->
-      JPaymentPlan.create planData, (err) ->
-        return if KD.showError err
-
-        reloadPlans()
-
-    view.on 'PlanDeleteRequested', (code) ->
-      JPaymentPlan.removeByCode code, (err) ->
-        return if KD.showError err
-
-        reloadPlans()
-
-    reloadProducts()
-    reloadPlans()
+  productViewAdded: do ->
+
+    prepareProductView = (view, category) ->
+
+      group = KD.getGroup()
+
+      konstructor = KD.remote.api["JPayment#{category}"]
+
+      reload = ->
+        group["fetch#{category}s"] (err, results) ->
+          view["set#{category}s"] results
+
+      view.on "#{category}CreateRequested", (productData) ->
+        konstructor.create productData, (err) ->
+          return if KD.showError err
+
+          reload()
+
+      view.on "#{category}DeleteRequested", (code) ->
+        konstructor.removeByCode code, (err) ->
+          return if KD.showError err
+
+          reload()
+
+      reload()
+
+    productViewAdded = (pane, view) ->
+      prepareProductView view, 'Product'
+      prepareProductView view, 'Plan'

   showPaymentInfoModal: ->
     modal = @createPaymentInfoModal()


typo in homeloginbar

typo in homeloginbar

--- client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
+++ client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
@@ -77,7 +77,7 @@
       click       : (event)=>
         @utils.stopDOMEvent event
         requiresLogin =>
-          @getSingleton('mainController').emit "groupAccessRequested", @group, @policy, (err)=>
+          @appManager.tell 'Groups', "showRequestAccessModal", @group, @policy, (err)=>
             unless err
               @access.hide()
               @requested.show()


Classroom: Implemented fetching related classes.

--- client/app/Applications/Classroom.kdapplication/AppView.coffee
+++ client/app/Applications/Classroom.kdapplication/AppView.coffee
@@ -6,18 +6,31 @@

     super options, data

-    @appStorage = new AppStorage "Classroom", "1.0"
+    @cdnRoot     = "http://fatihacet.kd.io/cdn/classes"
+    @appStorage  = new AppStorage "Classroom", "1.0"

     @createHeader()
-    @fetchUserClasses()
+    @fetchClasses()

-  fetchUserClasses: ->
+  fetchClasses: ->
+    predefinedClasses = [ "CoffeeScript", "JavaScript", "PHP" ]
+
     @appStorage.fetchStorage (storage) =>
+      @enrolledClasses = @appStorage.getValue "UserClasses"
+      @relatedClasses  = []
+
+      for className in predefinedClasses
+        if @enrolledClasses
+          if @enrolledClasses.indexOf(className) is -1
+            @relatedClasses.push className
+        else
+          @relatedClasses.push className

       @addSubView @classesView = new ClassroomClassesView
         delegate : this
       ,
-        enrolled : @appStorage.getValue "UserClasses"
+        enrolled : @enrolledClasses
+        related  : @relatedClasses

   createHeader: ->
     @addSubView @header = new KDView


Workspace: Created a history reference on firebase instance to set history logs.

--- client/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -14,6 +14,7 @@
     @firepadRef     = new Firebase "https://#{currentInstance}.firebaseIO.com/"
     @sessionKey     = options.sessionKey or @createSessionKey()
     @workspaceRef   = @firepadRef.child @sessionKey
+    @historyRef     = @workspaceRef.child "history"
     @users          = {}

     @createUserListContainer()
@@ -234,4 +235,13 @@
       @sessionKey
       container : @userListContainer
       delegate  : @
+    }
+
+  setHistory: (message = "") ->
+    user    = KD.nick()
+    message = message.replace "$0", user
+
+    @historyRef.child(Date.now()).set {
+      message
+      user
     }


Remove track-changes-discount experiment

--- services/web/public/coffee/main/account-upgrade.coffee
+++ services/web/public/coffee/main/account-upgrade.coffee
@@ -9,10 +9,9 @@
 			event_tracking.sendMB "subscription-start-trial", { source, plan: $scope.startTrialPlanCode }

 			w = window.open()
-			sixpack.convert "track-changes-discount", ->
-				ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
-				url = "/user/subscription/new?planCode=#{$scope.startTrialPlanCode}&ssp=true"
-				if couponCode?
-					url = "#{url}&cc=#{couponCode}"
-				$scope.startedFreeTrial = true
-				w.location = url
+			ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
+			url = "/user/subscription/new?planCode=#{$scope.startTrialPlanCode}&ssp=true"
+			if couponCode?
+				url = "#{url}&cc=#{couponCode}"
+			$scope.startedFreeTrial = true
+			w.location = url


Updating pip resource snippet

Updating pip resource snippet

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


Don't start getting work before jobs are started.

Otherwise observer runs, tries to get all the work, but work is not
available because jobs are not yet started, and then those jobs are not
tried to get again until a new job is added to the queue (observe has
to trigger again).

--- server/lib/worker.coffee
+++ server/lib/worker.coffee
@@ -151,27 +151,10 @@
     finally
       jobQueueRunning = false

-Meteor.startup ->
-  workerInstances = parseInt(process.env.WORKER_INSTANCES || '1')
-  workerInstances = 1 unless _.isFinite workerInstances
-
-  # Worker is disabled
-  return Log.info "Worker disabled" unless workerInstances
+startJobs = ->
+  JobQueue.Meta.collection.startJobs()

   Log.info "Worker enabled"
-
-  # Check for promoted jobs at this interval. Jobs scheduled in the
-  # future has to be made ready at regular intervals because time-based
-  # queries are not reactive. time < NOW, NOW does not change as times go
-  # on, once you make a query. More instances we have, less frequently
-  # each particular instance should check.
-  JobQueue.Meta.collection.promote workerInstances * PROMOTE_INTERVAL
-
-  # We randomly delay start so that not all instances are promoting
-  # at the same time, but dispersed over the whole interval.
-  Meteor.setTimeout ->
-    JobQueue.Meta.collection.startJobs()
-  , Random.fraction() * workerInstances * PROMOTE_INTERVAL

   # The query and sort here is based on the query in jobCollection's
   # getWork query. We want to have a query which is the same, just
@@ -194,6 +177,24 @@
     changed: (newDocument, oldDocument) ->
       runJobQueue()

+Meteor.startup ->
+  workerInstances = parseInt(process.env.WORKER_INSTANCES || '1')
+  workerInstances = 1 unless _.isFinite workerInstances
+
+  # Worker is disabled
+  return Log.info "Worker disabled" unless workerInstances
+
+  # Check for promoted jobs at this interval. Jobs scheduled in the
+  # future has to be made ready at regular intervals because time-based
+  # queries are not reactive. time < NOW, NOW does not change as times go
+  # on, once you make a query. More instances we have, less frequently
+  # each particular instance should check.
+  JobQueue.Meta.collection.promote workerInstances * PROMOTE_INTERVAL
+
+  # We randomly delay start so that not all instances are promoting
+  # at the same time, but dispersed over the whole interval.
+  Meteor.setTimeout startJobs, Random.fraction() * workerInstances * PROMOTE_INTERVAL
+
   # Same deal with delaying and spreading the interval based on
   # the number of worker instances that we have for job promotion.
   Meteor.setTimeout ->


updated label for permanent shared vm

updated label for permanent shared vm

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -57,7 +57,7 @@

   createElements: ->

-    text       = 'wants to share this VM with you'
+    text       = 'wants to share their VM with you.'
     titleFirst = no

     if @isApproved


no newline on no_such_task for Cake

no newline on no_such_task for Cake

--- cake.coffee
+++ cake.coffee
@@ -66,5 +66,5 @@

 # Print an error and exit when attempting to all an undefined task.
 no_such_task: (task) ->
-  puts "No such task: \"$task\"\n"
+  puts "No such task: \"$task\""
   process.exit 1


Adds fix to section query

Adds fix to section query

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -137,7 +137,7 @@
     # Separate "find" query from sort/offest/limit
     { limit, offset, sort } = input
     query = _.omit input, 'limit', 'offset', 'sort', 'artist_id', 'artwork_id',
-      'fair_ids', 'partner_id', 'auction_id', 'show_id', 'q', 'all_by_author'
+      'fair_ids', 'partner_id', 'auction_id', 'show_id', 'q', 'all_by_author', 'section_id'
     # Type cast IDs
     # TODO: https://github.com/pebble/joi-objectid/issues/2#issuecomment-75189638
     query.author_id = ObjectId input.author_id if input.author_id
@@ -147,6 +147,7 @@
     query.auction_id = ObjectId input.auction_id if input.auction_id
     query.vertical_id = ObjectId input.vertical_id if input.vertical_id
     query.section_ids = ObjectId input.section_id if input.section_id
+    console.log query
     query.biography_for_artist_id = ObjectId input.biography_for_artist_id if input.biography_for_artist_id

     query.$or = [


fixed add to selector on facet remove

fixed add to selector on facet remove

--- _src/js/views/selector.coffee
+++ _src/js/views/selector.coffee
@@ -29,7 +29,7 @@
 		@searchcoll = @collection.sub( ->true )
 		@result = new @collection.constructor()

-		#@listenTo( @searchcoll, "add", @renderRes )
+		@listenTo( @searchcoll, "add", @renderRes )
 		@listenTo( @searchcoll, "remove", @renderRes )
 		@listenTo( @searchcoll, "remove", @checkOptionsEmpty )


Remove debugging

Remove debugging

--- lib/worker.coffee
+++ lib/worker.coffee
@@ -347,32 +347,22 @@
     if (cached = @cache[key]) != undefined
       cached
     else if (value = @manager.check_helpers(key)) != undefined
-      wat = "#{@key} <- #{key}"
-      console.log "worker: helper (#{wat})"
       if typeof(value) == 'function'
-        console.log "worker: helper function (#{wat})"
         yielded = false
         the_result = undefined
         value (result) =>
-          console.log "worker: helper callback (#{wat})"
           if yielded
-            console.log "worker: running fiber (#{wat})"
             @fiber.run result
           else
-            console.log "worker: no need to run fiber (#{wat})"
             the_result = result
         if the_result != undefined
           Worker.current = this
-          console.log "worker: no need to yield (#{wat})"
           return the_result
         yielded = true
-        console.log "worker: helper yielding (#{wat})"
         v = Fiber.yield()
         Worker.current = this
-        console.log "worker: helper resumed (#{wat})"
         v
       else
-        console.log "worker: helper value (#{wat})"
         value
     else if (cached = @manager.check_cache(key)) != undefined
       msg = source: key, target: @key


Add input restriction so only one keyDown event is processed

--- app/models/InputManager.coffee
+++ app/models/InputManager.coffee
@@ -36,6 +36,8 @@
     window.addEventListener 'keydown', @onKeyDownEvent
     window.addEventListener 'keyup', @onKeyUpEvent

+    @otherKeyPressed = false
+
   ###
   Set the corresponding action in the actions array to true

@@ -44,9 +46,13 @@
   ###
   onKeyDownEvent: (event) =>
     # get attributes
+    code     = event['keyCode']
+
+    if @otherKeyPressed
+      @onKeyUpEvent(@otherKeyPressed)
+
     bindings = @get 'bindings'
     actions  = @get 'actions'
-    code     = event['keyCode']

     action = bindings[code]

@@ -55,6 +61,9 @@

     # set attributes
     @set 'actions':actions
+
+    @otherKeyPressed = event
+

   ###
   Set the corresponding action in the actions array to false


Edits to Laura's biography (#3350)

Edits to Laura's biography (#3350)

--- app/pages/about/team-page.cjsx
+++ app/pages/about/team-page.cjsx
@@ -155,9 +155,9 @@
         been getting more into data science). He worked on both Galaxy Zoo and Radio Galaxy Zoo,
         and developed advanced tools for the volunteers.'''
       lauraTrouille:
-        title: 'Adler Planetarium Zooniverse Director'
+        title: 'Zooniverse co-I'
         bio: '''In July 2015, Laura became Director of Citizen Science at the Adler Planetarium and
-        co-lead of the Zooniverse. While earning her Ph.D. in 2010 studying galaxy evolution,
+        co-I of the Zooniverse. While earning her Ph.D. in 2010 studying galaxy evolution,
         she embodied cosmic collisions as a roller derby queen aptly named ‘The Big Bang’.'''
       lauraWhyte:
         title: 'Adler Planetarium Zooniverse Director'


removed log

removed log

--- app/views/data_settings.coffee
+++ app/views/data_settings.coffee
@@ -64,7 +64,6 @@
     @render()

   checkToolSource: (source_tool, tool, checkedTools) =>
-    console.log 'checking tool', tool
     if _.isEqual source_tool, tool
       return false


scroll more natively

scroll more natively

--- apps/block/client/index.coffee
+++ apps/block/client/index.coffee
@@ -122,7 +122,9 @@
         wait: true

   scrollToTabs: ->
-    $("#block-tabs").get(0).scrollIntoView()
+    $modal = $('.js-modalize-dialog')
+    $tabs = $('#block-tabs')
+    $modal.scrollTop($modal.scrollTop() - $modal.offset().top + $tabs.offset().top)

   remove: ->
     mediator.stopListening "lightbox:slide:next"


Add error alerts

Add error alerts

--- assets/javascripts/main.coffee
+++ assets/javascripts/main.coffee
@@ -49,6 +49,8 @@
         controller = self.get("controllers.game")
         controller.set("response", data)
         self.transitionToRoute("game")
+      ).fail((xhr, status, error) ->
+        alert("Error #{xhr.status}: #{xhr.statusText}. Try refreshing a page")
       )

 , Config))
@@ -94,6 +96,8 @@
         self.transitionToRoute("index")
       else
         self.set("response", data)
+    ).fail((xhr, status, error) ->
+      alert("Error #{xhr.status}: #{xhr.statusText}. Try refreshing a page")
     )

   result: (data) ->
@@ -119,6 +123,8 @@
           alert(data.error)
         else
           self.result(data)
+      ).fail((xhr, status, error) ->
+        alert("Error #{xhr.status}: #{xhr.statusText}. Try refreshing a page")
       )

     next: ->


fix opening of remote revision urls

fix opening of remote revision urls

--- lib/views/blame-line-view.coffee
+++ lib/views/blame-line-view.coffee
@@ -45,8 +45,7 @@
           a onClick: @didClickHashWithoutUrl, className: 'hash', @props.hash.substring(0, HASH_LENGTH)
         else
           url = @props.remoteRevision.url @props.hash
-          a className: 'hash', href: url,
-            @props.hash.substring(0, HASH_LENGTH)
+          a href: url, target: '_blank', className: 'hash', @props.hash.substring(0, HASH_LENGTH)
         span className: 'date', @props.date
         span className: 'committer text-highlight',
           @props.author.split(' ').slice(-1)[0]


Rename addNewOrderRow and modify functionalty to accomidate new view, add setOrderRow

--- app/assets/javascripts/orders.coffee
+++ app/assets/javascripts/orders.coffee
@@ -16,41 +16,56 @@
     element.append """<option value="#{id}" data-current-quantity="#{current_quantity}" data-requested-quantity="#{requested_quantity}">#{description}</option>"""

 populateQuantity = (selected, element) ->
-  if selected.val() == ""
-    element.attr("placeholder", "Select an Item...")
-    element.removeAttr("data-guard data-guard-int-min data-guard-int-max")
-    element.attr("data-guard", "required")
-  else
-    available_quantity = selected.data("current-quantity") - selected.data("requested-quantity")
-    element.attr("placeholder", "#{available_quantity} available")
-    element.attr("data-guard", "required int")
-    element.attr("data-guard-int-min", "1").data("guard-int-min", 1)
-    element.attr("data-guard-int-max", available_quantity).data("guard-int-max", available_quantity)
+  available_quantity = selected.data("current-quantity") - selected.data("requested-quantity")
+  element.attr("data-guard", "required int")
+  element.attr("data-guard-int-min", "1").data("guard-int-min", 1)
+  element.attr("data-guard-int-max", available_quantity).data("guard-int-max", available_quantity)

   element.val("").clearErrors()

-addNewOrderRow = ->
-  currentNumRows = $("#new-order-table tbody").find("tr").length
-  newRow = $("""
-

+populateQuantityAvailable = (selected, element) ->
+  available_quantity = selected.data("current-quantity") - selected.data("requested-quantity")
+  element.text(available_quantity)
+
+window.setOrderRow = (order_details) ->
+  row = $("#order-table tbody tr:last")
+  category = row.find(".category")
+  item = row.find(".item")
+  quantity = row.find(".quantity")
+
+  category.val order_details.category_id
+  category.trigger "change"
+  item.val order_details.item_id
+  item.trigger "change"
+  quantity.val order_details.quantity
+
+callback = -> something param
+
+window.addOrderRow = ->
+  newRow = $ """
+

-          <select class="category form-control row-#{currentNumRows}" data-guard="required">
+          <select class="category form-control" data-guard="required">
             <option value="">Select a category...</option>
           </select>

-          <select name="order_detail[#{currentNumRows}][item_id]" class="item form-control row-#{currentNumRows}" data-guard="different required">
+          <select name="order_detail[#{currentNumRows}][item_id]" class="item form-control" data-guard="different required">
             <option value="">Select an item...</option>
           </select>

-          <input name="order_detail[#{currentNumRows}][quantity]" class="quantity form-control row-#{currentNumRows}" placeholder="Select an Item..." data-guard="required" />
+          <input name="order_detail[#{currentNumRows}][quantity]" class="quantity form-control" placeholder="Select an Item..." data-guard="required" />

+
+
+

+

         <button class="pull-right btn btn-danger btn-xs delete-row">
@@ -58,33 +73,13 @@
         </button>

-  """)
+  """
+
   category = newRow.find ".category"
   populateCategories category
-  $("#new-order-table tbody").append newRow
+  $("#order-table tbody").append newRow

-printOrder = ->
-  window.print()
-
-$(document).on "click", ".add-item", (event) ->
-  event.preventDefault()
-  event.stopPropagation()
-  $("#add_inventory_modal").modal("show")
-
-$(document).on "click", "#add-item-row", (event) ->
-  event.preventDefault()
-  addNewOrderRow()
-
-$(document).on "click", ".delete-row", (event) ->
-  event.preventDefault()
-  $(@).parents("tr:first").remove()
-  addNewOrderRow() if $("#new-order-table tbody tr").length == 0
-
-$(document).on "click", "#print-order", (event) ->
-  printOrder()
-
-$(document).on "click", "#add-tracking-number", (event) ->
-  event.preventDefault()
+addTrackingRow = ->
   newRow = $ """

@@ -122,14 +117,40 @@
   $("#shipments-table tbody").append newRow
   $("#shipments-table").show()

-$(document).on "change", ".new-order-row .category", ->
-  item_element = $(@).parents(".new-order-row").find ".item"
+printOrder = ->
+  window.print()
+
+$(document).on "click", ".add-item", (event) ->
+  event.preventDefault()
+  event.stopPropagation()
+  $("#add_inventory_modal").modal("show")
+
+$(document).on "click", "#add-item-row", (event) ->
+  event.preventDefault()
+  addOrderRow()
+
+$(document).on "click", ".delete-row", (event) ->
+  event.preventDefault()
+  $(@).parents("tr:first").remove()
+  addOrderRow() if $("#order-table tbody tr").length == 0
+
+$(document).on "click", "#print-order", (event) ->
+  printOrder()
+
+$(document).on "click", "#add-tracking-number", (event) ->
+  event.preventDefault()
+  addTrackingRow()
+
+$(document).on "change", ".order-row .category", ->
+  item_element = $(@).parents(".order-row").find ".item"
   populateItems $(@).val(), item_element

-$(document).on "change", ".new-order-row .item", ->
-  quantity_element = $(@).parents(".new-order-row").find ".quantity"
+$(document).on "change", ".order-row .item", ->
+  quantity_element = $(@).parents(".order-row").find ".quantity"
+  quantity_available_element = $(@).parents(".order-row").find ".quantity-available"
   selected = $(@).find('option:selected')
   populateQuantity selected, quantity_element
+  populateQuantityAvailable selected, quantity_available_element

-$(document).on "page:change", ->
-  addNewOrderRow() if $("#new-order-table").length > 0
+$(document).on "turbolinks:ready", ->
+  addOrderRow() if $("#order-table").length > 0


Don't crash ss on missing ID values

Don't crash ss on missing ID values

Fixes #3391

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -769,7 +769,7 @@
                     if (r.category || 0) == info.category.id && !(ri of depends && info.i of depends[ri])
                       rs.push
                         r:r
-                        v:r.measures[info.metric.id].toLowerCase()
+                        v:(r.measures[info.metric.id] ? '').toLowerCase()
                         d:Object.keys(r.measures).sort(byNumber).map(mf(r)).join(', ')
                   editScope.records = rs.sort((a, b) -> byMagic(a.v, b.v))
                 else if info.metric.options


Refactor generic directive code structure.

--- app/coffee/directives/generic.coffee
+++ app/coffee/directives/generic.coffee
@@ -1,13 +1,10 @@
-genericModule = angular.module('greenmine.directives.generic', [])

-versionConstructor = (version) -> (scope, elm, attrs) ->
+AppVersionDirective = (version) -> (scope, elm, attrs) ->
     elm.text(version)

-genericModule.directive('appVersion', ['version', versionConstructor])

-# Select2 Directive
-select2Constructor = ->
+UiSelect2Directive = ->
     require: "?ngModel"
     restrict: "A"
     link: (scope, elm, attrs, ngModel) ->
@@ -23,10 +20,8 @@
             ngModel.$setViewValue(arguments[0].val)
             scope.$digest()

-genericModule.directive('uiSelect2', select2Constructor)

-
-uiEventConstructor = ($parse) -> (scope, elm, attrs) ->
+UiEventDirective = ($parse) -> (scope, elm, attrs) ->
     events = scope.$eval(attrs.uiEvent)
     angular.forEach events,  (uiEvent, eventName) ->
         fn = $parse(uiEvent)
@@ -37,10 +32,8 @@
             scope.$apply ->
                 fn(scope, {$event: evt, $params: params})

-genericModule.directive('uiEvent', ['$parse', uiEventConstructor])

-
-uiParsleyConstructor = ($parse, $http, url) -> (scope, elm, attrs) ->
+UiParsleyDirective = ($parse, $http, url) -> (scope, elm, attrs) ->
     fn = $parse(attrs.uiParsley)

     onFormSubmit = (valid, event, form) ->
@@ -50,7 +43,7 @@
             fn(scope, {$event:event})

     element = $(elm)
-    element.parsley(
+    element.parsley
         listeners: {onFormSubmit: onFormSubmit}
         validators:
             remoteuserverify: (val, opt, self) ->
@@ -71,13 +64,9 @@

         messages:
             remoteuserverify: "Username taken"
-    )

-genericModule.directive('uiParsley', ['$parse', '$http', 'url', uiParsleyConstructor])
-
-
-gmFileConstructor = ($parse) ->
+GmFileDirective = ($parse) ->
     require: "?ngModel",
     restrict: "A",
     link: (scope, elm, attrs, ngModel) ->
@@ -88,4 +77,10 @@
                 scope.$apply ->
                     ngModel.$setViewValue(files[0])

-genericModule.directive('gmFile', ["$parse", gmFileConstructor]);
+
+module = angular.module('greenmine.directives.generic', [])
+module.directive('appVersion', ['version', AppVersionDirective])
+module.directive('uiSelect2', UiSelect2Directive)
+module.directive('uiEvent', ['$parse', UiEventDirective])
+module.directive('uiParsley', ['$parse', '$http', 'url', UiParsleyDirective])
+module.directive('gmFile', ["$parse", GmFileDirective])


also create a network container for 'oneoff' containers

--- src/coffee/compose.coffee
+++ src/coffee/compose.coffee
@@ -9,7 +9,7 @@

   augmentCompose: (instance, options, doc) ->
     addNetworkContainer = (serviceName, service) ->
-      if service.labels['bigboat.service.type'] is 'service'
+      if service.labels['bigboat.service.type'] in ['service', 'oneoff']
         labels = _.extend {}, service.labels,
           'bigboat.service.type': 'net'
         subDomain = "#{instance}.#{config.domain}.#{config.tld}"


newkoding: initialize submodules when cloning code repository

--- config/main.sandbox.coffee
+++ config/main.sandbox.coffee
@@ -371,7 +371,7 @@
       function install() {
         touch /root/run.install.start
         cd /opt
-        git clone --branch '#{tag}' --depth 1 git@github.com:koding/koding.git koding
+        git clone --recursive --branch '#{tag}' --depth 1 git@github.com:koding/koding.git koding

         cd /opt/koding

@@ -381,8 +381,6 @@
         echo $ms | base64 --decode | gunzip > #{projectRoot}/machineSettings
         bash #{projectRoot}/machineSettings

-        git submodule init
-        git submodule update --recursive
         npm i gulp stylus coffee-script -g
         npm i --unsafe-perm
         /usr/local/bin/coffee /opt/koding/build-client.coffee --watch false


Troubleshoot: fixes vms status stucks at pending bug

--- client/Main/troubleshoot/vmchecker.coffee
+++ client/Main/troubleshoot/vmchecker.coffee
@@ -11,8 +11,29 @@

     {vmController} = KD.singletons
     vmController.on 'vm.state.info', ({alias, state})  =>
-      @resetTimeout()
-      @vmList[alias] = state.state
+      switch state.state
+        when "RUNNING"
+          @vmList[alias] = "on"
+        when "STOPPED"
+          @vmList[alias] = "off"
+      @updateStatus()
+
+    vmController.on 'vm.progress.start', ({alias, update}) =>
+      {message} = update
+      switch message
+        when "FINISHED"
+          @vmList[alias] = "on"
+        when "STARTED"
+          @vmList[alias] = "starting"
+      @updateStatus()
+
+    vmController.on 'vm.progress.stop', ({alias, update}) =>
+      {message} = update
+      switch message
+        when "FINISHED"
+          @vmList[alias] = "off"
+        when "STARTED"
+          @vmList[alias] = "stopping"
       @updateStatus()

@@ -26,7 +47,7 @@
     @status = RUNNING
     for own name, status of @vmList
       switch status
-        when "RUNNING"
+        when "on"
           continue
         else
           @status = READY


titlecontroller: minor

titlecontroller: minor

--- client/app/lib/pagetitlecontroller.coffee
+++ client/app/lib/pagetitlecontroller.coffee
@@ -24,10 +24,11 @@

     return  if @focused

-    { socialapi }        = kd.singletons
     { id, typeConstant } = notification.channelMessage

     return  if typeConstant isnt 'privatemessage'
+
+    { socialapi } = kd.singletons

     socialapi.message.byId {id}, (err, message) =>
       id              = message.account._id
@@ -65,4 +66,4 @@

   getRaw: -> return @get().replace /\([0-9]+\)\s(.*)/, '$1'

-  getAppTitle: -> return kd.singletons.appManager.getFrontApp()?.options.name or 'Koding'+  getAppTitle: -> return kd.singletons.appManager.getFrontApp()?.options.name or 'Koding'


Remove unused syncing logic to conform to new sync architecture

--- app/assets/javascripts/farmbot_app/directives/syncbutton.js.coffee
+++ app/assets/javascripts/farmbot_app/directives/syncbutton.js.coffee
@@ -8,24 +8,9 @@
     $scope.text = ->
       switch Devices.current.busy
         when 0 then 'Sync'
-        when 1 then 'Sync (busy)'
+        when 1 then 'Working'
         else 'Waiting'
-    $scope.sync = ->
-      nope = (data) -> alert 'SYNC FAILURE'; console.log data
-      yep  = (data) ->
-        # This method contains a lot of data massaging that shouldn't happen.
-        # I tried to adjust the relationship settings so that the relationship
-        # is auto-stringified (send "Sched => Seq => Steps" automatically.)
-        # JS-Data should be handling this stuff for us, but I don't have time
-        # to learn the conventions atm. Pull requests welcome. Look in
-        # data.js.coffee
-        payload = Data.utils.removeCircular(data.schedules)
-        for schedule in payload
-          seq = _(data.sequences).findWhere _id: schedule.sequence_id
-          schedule.sequence = Data.utils.removeCircular(seq)
-        Devices.send "sync_sequence", payload
-
-      Calendar.loadData().then(yep, nope)
+    $scope.sync = -> Devices.send "sync_sequence"
 ]
 directive =
   restrict: 'AEC'


Hubot should also love

Hubot should also love

--- scripts/kill.coffee
+++ scripts/kill.coffee
@@ -20,3 +20,7 @@
 module.exports = (robot) ->
   robot.respond /kill/i, (msg) ->
     msg.send msg.random(phrases)
+
+  robot.respond /love/i, (msg) ->
+    msg.send 'I was only programmed to KILL.'
+


add neta

add neta

--- scripts/neta.coffee
+++ scripts/neta.coffee
@@ -19,6 +19,8 @@
 #   hubot ダメっぽい - get neta
 #   hubot 本題 - get neta
 #   hubot たやすい - get neta
+#   hubot 呼んだ - get neta
+#   hubot 次は - get neta

 module.exports = (robot) ->
   robot.respond /あきらめたら$/, (msg) ->
@@ -54,3 +56,9 @@
   robot.respond /たやすい$/, (msg) ->
     msg.send "http://blog-imgs-49.fc2.com/a/p/g/apg/201201171.jpg"

+  robot.respond /呼んだ$/, (msg) ->
+    msg.send "http://imgcc.naver.jp/kaze/mission/USER/20120729/15/1022545/34/630x363x336b5a8970376ec2c6767cba.jpg"
+
+  robot.respond /次は$/, (msg) ->
+    msg.send "http://imgcc.naver.jp/kaze/mission/USER/20120729/15/1022545/59/640x583xe9e13016850307124416e999.jpg"
+


got theme + bedsheet's saving again using new methods

--- app/coffee/metamenu.coffee
+++ app/coffee/metamenu.coffee
@@ -145,14 +145,14 @@
           bedsheetUrl = 'url("/images/uploads/' + $(event.currentTarget).data('id') + '-bedsheet.jpg")'
           $('#body').css('background-image', bedsheetUrl)

-         if $('#entry_view_preference_attributes_image_id').attr('name')?
+          if $('#entry_view_preference_attributes_image_id').attr('name')?
             $('#entry_view_preference_attributes_image_id').val($(event.currentTarget).data('id'))
-        else if $('#show_entry_mode').attr('name')?
-          # @updateEntryBedsheet($('#showEntry').data('id'),$(event.currentTarget).data('id'))
-          # @updateEntryViewPreferences($('#showEntry').data('id'),$(event.currentTarget).data('id'),null,null)
-        else
-          # @updateUserBedsheet($(event.currentTarget).data('id'))
-          # @updateUserViewPreferences($(event.currentTarget).data('id'),null,null)
+          else if $('#show_entry_mode').attr('name')?
+            # @updateEntryBedsheet($('#showEntry').data('id'),$(event.currentTarget).data('id'))
+            @updateEntryViewPreferences($('#showEntry').data('id'),$(event.currentTarget).data('id'),null,null)
+          else
+            # @updateUserBedsheet($(event.currentTarget).data('id'))
+            # @updateUserViewPreferences($(event.currentTarget).data('id'),null,null)
         )

@@ -167,7 +167,7 @@

     # trigger theme changes
-    $('#light, #dark').click (event) ->
+    $('#light, #dark').click (event) =>
       entry_id = $('#showEntry').data('id')
       theme = $(event.currentTarget).attr('id')
       if $('#show_entry_mode').attr('name')?


clear docset when Backspace on empty

clear docset when Backspace on empty

--- browser/src/coffee/search.coffee
+++ browser/src/coffee/search.coffee
@@ -112,6 +112,11 @@
         else if e.which is 38
             # up
             next = $('ul#doc-list li.active').prev()
+        else if e.which is 8
+            # backspace
+            if $('#search').val() is ''
+                S.vm.$data.docset = ''
+                $('#docset').val('')
         if next.length
             next.click()
             st = next.parent().parent().scrollTop()


Use SSL setting based on url protocol

Use SSL setting based on url protocol

--- app/coffee/S3PersistorManager.coffee
+++ app/coffee/S3PersistorManager.coffee
@@ -43,8 +43,9 @@
 			secretAccessKey: credentials.auth_secret

 	if settings.filestore.s3.endpoint
+		endpoint = URL.parse(settings.filestore.s3.endpoint)
 		options.endpoint = settings.filestore.s3.endpoint
-		options.sslEnabled = false
+		options.sslEnabled = endpoint.protocol == 'https'

 	return options


Disabling saving of lastIP user property for now.

--- server_setup.coffee
+++ server_setup.coffee
@@ -128,8 +128,9 @@
   app.all '*', (req, res) ->
     if req.user
       sendMain(req, res)
-      req.user.set('lastIP', req.connection.remoteAddress)
-      req.user.save()
+      # Disabling for HoC
+#      req.user.set('lastIP', req.connection.remoteAddress)
+#      req.user.save()
     else
       user = auth.makeNewUser(req)
       makeNext = (req, res) -> -> sendMain(req, res)


Update to new version of experiment, and only get cohort when needed.

--- app/lib/experiments.coffee
+++ app/lib/experiments.coffee
@@ -9,7 +9,7 @@
   Define the active experiment here by using a string which exists in http://experiments.zooniverse.org/active_experiments
   If no experiments should be running right now, set this to null, false or ""
   ###
-  ACTIVE_EXPERIMENT: "SerengetiAdvisoryBoard2015Experiment"
+  ACTIVE_EXPERIMENT: "SerengetiMessagingExperiment2"
   #ACTIVE_EXPERIMENT: "SerengetiBlanksExperiment1"
   ###
   The URL of the experiment server to use
@@ -73,81 +73,7 @@
     @currentCohort = null
     @currentParticipant = null
     @Geordi.UserStringGetter.currentUserID = null
-  ###
-  when we first get participant, and the user has not started experiment in a previous sessions, we'll need to log it to Geordi
-  ###
-  #checkForExperimentStartAndLogIt: (participant) ->
-  #  if @ACTIVE_EXPERIMENT == "SerengetiBlanksExperiment1"
-  #    if participant.blank_subjects_seen.length==0 && participant.non_blank_subjects_seen.length==0
-  #      @Geordi.logEvent 'experimentStart'
-  ###
-  when we first discover that a user has ended the experiment, we log it to Geordi
-  ###
-  #checkForExperimentEndAndLogIt: (participant) ->
-  #  if @ACTIVE_EXPERIMENT == "SerengetiBlanksExperiment1"
-  #    if !experimentCompleted && participant? && !participant.active && participant.blank_subjects_available && participant.blank_subjects_available.length==0 && participant.non_blank_subjects_available && participant.non_blank_subjects_available.length==0
-  #      @experimentCompleted = true
-  #      @Geordi.logEvent 'experimentEnd'
-  ###
-  when we get participant, we need to log to Geordi if the user's was excluded
-  ###
-  #checkForExcludedAndLogIt: (participant) ->
-  #  if @ACTIVE_EXPERIMENT == "SerengetiBlanksExperiment1"
-  #    if !@excludedReason? && participant.excluded
-  #      # if not previously logged, log it.
-  #      @excludedReason = participant.excluded_reason
-  #      @Geordi.logEvent {
-  #        type: 'experimentExcluded'
-  #        relatedID: participant.excluded_reason
-  #        data: {
-  #          excludedReason: participant.excluded_reason
-  #        }
-  #        subjectID: @classification.subject.zooniverseId
-  #      }
-  ###
-  This method will contact the experiment server to find the participant(experimental data) for this user in the specified experiment
-  ###
-  #getParticipant: () ->
-  #  @Geordi.UserStringGetter.currentUserID = "(unknown)"
-  #  @Geordi.UserStringGetter.getUserIDorIPAddress()
-  #  .then (data) =>
-  #    if data?
-  #      @Geordi.UserStringGetter.currentUserID = data.toString()
-  #  .fail =>
-  #    @Geordi.UserStringGetter.currentUserID = "(anonymous)"
-  #  .always =>
-  #    eventualParticipant = new $.Deferred
-  #    if @ACTIVE_EXPERIMENT?
-  #      now = new Date().getTime()
-  #      if @lastFailedAt?
-  #        timeSinceLastFail = now - @lastFailedAt.getTime()
-  #      if @lastFailedAt == null || timeSinceLastFail > @RETRY_INTERVAL
-  #        try
-  #          $.get(@EXPERIMENT_SERVER_URL+ 'experiment/' + @ACTIVE_EXPERIMENT + '?user_id=' + @Geordi.UserStringGetter.currentUserID)
-  #          .then (participant) =>
-  #            @checkForExcludedAndLogIt participant
-  #            @checkForExperimentEndAndLogIt participant
-  #            @currentCohort = participant.cohort
-  #            if !@currentParticipant?
-  #              @Geordi.logEvent 'experimentResume', null, Subject.current?.zooniverseId
-  #              @checkForExperimentStartAndLogIt participant
-  #            @currentParticipant = participant
-  #            eventualParticipant.resolve participant
-  #          .fail =>
-  #            @lastFailedAt = new Date()
-  #            @Geordi.logEvent {
-  #              type: "error"
-  #              errorCode: "500"
-  #              errorDescription: "Couldn't retrieve experimental participant data"
-  #            }
-  #            eventualParticipant.resolve null
-  #        catch error
-  #          eventualParticipant.resolve null
-  #      else
-  #        eventualParticipant.resolve null
-  #    else
-  #      eventualParticipant.resolve null
-  #    eventualParticipant.promise()
+
   ###
   This method will contact the experiment server to find the cohort for this user in the specified experiment
   ###
@@ -155,27 +81,30 @@
     eventualCohort = new $.Deferred
     if @ACTIVE_EXPERIMENT?
       now = new Date().getTime()
-      if @lastFailedAt?
-        timeSinceLastFail = now - @lastFailedAt.getTime()
-      if @lastFailedAt == null || timeSinceLastFail > @RETRY_INTERVAL
-        try
-          $.get(@EXPERIMENT_SERVER_URL+'experiment/' + @ACTIVE_EXPERIMENT + '?userid=' + @Geordi.UserStringGetter.currentUserID)
-          .then (participant) =>
-            @currentCohort = participant.cohort
-            @currentParticipant = participant
-            eventualCohort.resolve participant.cohort
-          .fail =>
-            @lastFailedAt = new Date()
-            @Geordi.logEvent {
-              type: "error"
-              errorCode: "500"
-              errorDescription: "Couldn't retrieve experimental cohort data"
-            }
+      if @currentCohort?
+        eventualCohort.resolve @currentCohort
+      else
+        if @lastFailedAt?
+          timeSinceLastFail = now - @lastFailedAt.getTime()
+        if @lastFailedAt == null || timeSinceLastFail > @RETRY_INTERVAL
+          try
+            $.get(@EXPERIMENT_SERVER_URL+'experiment/' + @ACTIVE_EXPERIMENT + '?userid=' + @Geordi.UserStringGetter.currentUserID)
+            .then (participant) =>
+              @currentCohort = participant.cohort
+              @currentParticipant = participant
+              eventualCohort.resolve participant.cohort
+            .fail =>
+              @lastFailedAt = new Date()
+              @Geordi.logEvent {
+                type: "error"
+                errorCode: "500"
+                errorDescription: "Couldn't retrieve experimental cohort data"
+              }
+              eventualCohort.resolve null
+          catch error
             eventualCohort.resolve null
-        catch error
+        else
           eventualCohort.resolve null
-      else
-        eventualCohort.resolve null
     else
       eventualCohort.resolve null
     eventualCohort.promise()


Expose framepacker on plugin scope

Expose framepacker on plugin scope


Pass through error from deleting asset

Pass through error from deleting asset

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -98,8 +98,8 @@
       return

     if release?
-      deleteExistingAsset release, ->
-        callback(release)
+      deleteExistingAsset release, (error) ->
+        callback(error, release)
       return

     options =


fixed table paging

fixed table paging

--- app/views/table_page_settings.coffee
+++ app/views/table_page_settings.coffee
@@ -9,7 +9,7 @@
     'click button.prev' : 'prevPage'

   initialize: ->
-    @page = @model.settings.get('currentPage')
+    @page = @model.settings.get('currentPage') or 0

   render: =>
     @$el.html @template()


fix(vm.createInterface): syntax fix

fix(vm.createInterface): syntax fix

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -1070,7 +1070,7 @@
   vm: { type: 'string' }
   network: { type: 'string' }
   position: { type: ['integer', 'string'], optional: true }
-  mtu: { type: ['integer', 'string', optional: true }
+  mtu: { type: ['integer', 'string'], optional: true }
   mac: { type: 'string', optional: true }
 }


Listen for correct path loader progress event

--- src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee
+++ src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee
@@ -221,8 +221,11 @@
         @projectPaths = paths
         @reloadProjectPaths = false
         @populateProjectPaths(options)
-      @loadPathsTask.on 'load-paths:paths-loaded', (paths) =>
-        @loadingBadge.text(humanize.intcomma(paths.length))
+
+      pathsFound = 0
+      @loadPathsTask.on 'load-paths:paths-found', (paths) =>
+        pathsFound += paths.length
+        @loadingBadge.text(humanize.intcomma(pathsFound))

   populateOpenBufferPaths: ->
     editSessions = project.getEditSessions().filter (editSession) ->


Remove console.log

Remove console.log

--- client/lanes/workspace/Tabs.cjsx
+++ client/lanes/workspace/Tabs.cjsx
@@ -1,7 +1,6 @@
 class TabView extends Lanes.React.Component

     setDataState: (nextState) ->
-        console.log nextState
         if @model.active
             @props.history.replace(@model.historyUrl())
         if Lanes.Screens.Definitions.displaying.length is 0


Support for showing a marker layer (not enabled)

--- app/scripts/mapctrl.coffee
+++ app/scripts/mapctrl.coffee
@@ -1,6 +1,6 @@
 angular.module('taarifaWaterpointsApp')

-  .controller 'DashMapCtrl', ($scope, $http, leafletData, $timeout, waterpointStats) ->
+  .controller 'DashMapCtrl', ($scope, $http, leafletData, $timeout, modalSpinner, waterpointStats, Waterpoint) ->

     $scope.hoverText = ""
     $scope.choroChoice = "percFun"
@@ -88,10 +88,24 @@
         onEachFeature: onEachFeature
       )

+      categoryMap =
+        "functional" : 0
+        "not functional" : 1
+        "needs repair" : 2
+
+      clusterLayer = new PruneClusterForLeaflet()
+      clusterLayer.Cluster.Size = 100
+
+      waterpoints.forEach((x) ->
+        coords = x.location.coordinates
+        m = new PruneCluster.Marker(coords[1], coords[0])
+        m.category = categoryMap[x.status_group]
+        clusterLayer.RegisterMarker(m))
+
       map = L.map('nationalDashMap',
         center: new L.LatLng(-6.3153, 35.15625),
         zoom: 5,
-        layers: [osmLayer, satLayer, regLayer]
+        layers: [osmLayer, regLayer, clusterLayer]
       )

       baseMaps =
@@ -100,6 +114,7 @@

       overlayMaps =
         "Regions": regLayer
+        #"Waterpoints": clusterLayer

       # add a layer selector
       layerSelector = L.control.layers(baseMaps, overlayMaps).addTo(map)
@@ -147,17 +162,21 @@
       )

+    modalSpinner.open()
+
     # get the region boundaries
     $http.get("data/tz_regions.geojson", cache: true)
       .success((regions, status) ->
         # get the wateropint data per region
-        waterpointStats.getStats(null, null, null, "region", true, (waterpoints) ->
+        waterpointStats.getStats(null, null, null, "region", true, (regionStats) ->

           # create an associative map by region name
-          regs = _.pluck(waterpoints, "region").map((x) -> x.toLowerCase())
-          regionMap = _.object(regs, waterpoints)
+          regs = _.pluck(regionStats, "region").map((x) -> x.toLowerCase())
+          regionMap = _.object(regs, regionStats)

           $scope.regionMap = regionMap

-          initMap(regions, waterpoints)
+          initMap(regions, [])
+
+          modalSpinner.close()
         ))


stub out router to match new implementation

--- tutor/test/components/helpers/component-testing.coffee
+++ tutor/test/components/helpers/component-testing.coffee
@@ -6,9 +6,14 @@
 # No longer exists, needs further investigation if we're using it
 # ReactContext   = require('react/lib/ReactContext')

+{LocationBroadcast} = require 'react-router/locationBroadcast'
+{createRouterLocation} = require 'react-router/LocationUtils'
 {Promise}      = require 'es6-promise'
 {commonActions} = require './utilities'
 sandbox = null
+Sinon = {}
+
+
 ROUTER = null
 CURRENT_ROUTER_PARAMS = null
 CURRENT_ROUTER_PATH   = null
@@ -16,34 +21,27 @@
 # Mock a router for the context
 beforeEach ->
   sandbox = sinon.sandbox.create()
-  ROUTER  = sandbox.spy()
-  ROUTER.makeHref = sandbox.spy()
-  ROUTER.isActive = sandbox.spy()
-  ROUTER.transitionTo = sandbox.spy()
-  ROUTER.getCurrentPath = sandbox.spy( -> CURRENT_ROUTER_PATH )
-  ROUTER.getCurrentQuery = sandbox.spy( -> CURRENT_ROUTER_QUERY )
-  ROUTER.getCurrentPathname = sandbox.spy( -> '/testing' )
-  ROUTER.getLocation = sandbox.spy( ->
-    addChangeListener: sandbox.spy()
-    removeChangeListener: sandbox.spy()
-  )
-  ROUTER.match = sandbox.spy()
-  ROUTER.getCurrentParams = sandbox.spy( -> CURRENT_ROUTER_PARAMS )
-  ROUTER.transitionTo = sandbox.spy (path, params) ->
-    CURRENT_ROUTER_PARAMS = params
-    CURRENT_ROUTER_PATH = path
+  ROUTER  = {
+    transitionTo:     sandbox.spy()
+    replaceWith:      sandbox.spy()
+    blockTransitions: sandbox.spy()
+    createHref: sandbox.spy( -> '/' )
+  }
 afterEach ->
   sandbox.restore()

 # A wrapper component to setup the router context
 Wrapper = React.createClass
   childContextTypes:
-    router: React.PropTypes.func
+    router: React.PropTypes.object
   getChildContext: ->
     router: ROUTER
   render: ->
-    React.createElement(@props._wrapped_component,
-      _.extend(_.omit(@props, '_wrapped_component'), ref: 'element')
+    location = createRouterLocation('/')
+    React.createElement(LocationBroadcast, value: location,
+      React.createElement(@props._wrapped_component,
+        _.extend(_.omit(@props, '_wrapped_component'), ref: 'element')
+      )
     )

 Testing = {
@@ -69,7 +67,7 @@
     # defer adding the then callback so it'll be called after whatever is attached after the return
     _.defer -> promise.then ->
       _.delay( ->
-        React.unmountComponentAtNode(root)
+        ReactDOM.unmountComponentAtNode(root)
         CURRENT_ROUTER_PATH   = '/'
         CURRENT_ROUTER_PARAMS = {}
       , unmountAfter )
@@ -94,4 +92,9 @@
   get: -> ROUTER
 })

+# Object.defineProperties(Sinon, {
+#   spy:
+#     get
+# })
+
 module.exports = {Testing, expect, sinon, React, _, ReactTestUtils}


Ignore all  queries

Ignore all  queries

--- libraries/metrics/mongodb.coffee
+++ libraries/metrics/mongodb.coffee
@@ -15,8 +15,9 @@
 					options = {}
 					callback = () ->

-				if db_command.query? and (db_command.query["ping"] or db_command.query["ismaster"])
-					# Ignore noisy methods
+				collection = db_command.collectionName
+				if collection.match(/\$cmd$/)
+					# Ignore noisy command methods like authenticating, ismaster and ping
 					return _method.call this, db_command, options, callback

 				key = "mongo-requests.#{type}"
@@ -32,7 +33,7 @@
 					logger.log
 						query: db_command.query
 						type: type
-						collection: db_command.collectionName
+						collection: collection
 						"response-time": new Date() - start
 						"mongo request"
 					callback.apply this, arguments


Fix a couple typos

Fix a couple typos

--- src/batman.coffee
+++ src/batman.coffee
@@ -93,13 +93,12 @@
 Batman._block = $block = (fn) ->
   callbackEater = (args...) ->
     ctx = @
-    f = (innerCallback) ->
-      args.push innerCallback
+    f = (callback) ->
+      args.push callback
       fn.apply(ctx, args)

-    if typeof (outerCallback = args[args.length-1]) is 'function'
-      args.pop()
-      f(outerCallback)
+    if typeof args[args.length-1] is 'function'
+      f(args.pop())
     else
       f

@@ -466,8 +465,7 @@
 # Batman.Event
 # Another generic mixin that simply allows an object to emit events. All events
 # require an object that is observable. If you don't want to use an emitter,
-# you can use the $event f
-if outypeofunctions to create ephemeral objects internally.
+# you can use the $event functions to create ephemeral objects internally.
 ###

 Batman.EventEmitter = {


Update documentation

Update documentation

--- src/bijous.coffee
+++ src/bijous.coffee
@@ -39,12 +39,62 @@
 # Returns: `undefined`
 loadModule = (def, results, done) ->
   def.module.call null, @, results, (error, result) =>
-    if result
-      if def.bundle == @defaultBundleName then results[def.name] = result
-      else _.merge results[def.bundle] ?= {}, _.object([def.name], [result])
+    setResult.call(this, def, results, result) if result

     @emit 'loaded', def.name, def.bundle, results unless error
     done error
+
+  return
+
+# Public: Sets a module's result, the result will be namespaced if the bundle
+# name is not the {Bijous.defaultBundleName}.
+#
+# def     - The module's definition as an {Object} with the following keys:
+#           :name - The module's name
+#           :bundle - The bundle's name
+#           :module - The module's load function
+# results - The results of all previously loaded modules as an {Object}
+# result  - The result of the currently loaded module as an {Object}.
+#
+# ## Default Bundles Have No Namespace
+#
+# ```coffee
+# Bijous = require 'bijous'
+#
+# # We define a new {Bijous} instance, using the default bundle configuration.
+# # This causes klect to use the {Bijous.defaultBundleName}, and bijous to
+# # store results without a namespace
+# bijous = new Bijous
+#
+# # We assume there is a module called *module1*
+# bijous.load (err, modules) ->
+#   console.log modules.module1
+#
+# # We define a new {Bijous} instance, specifying a bundle configuration with no
+# # name. This causes klect to use the {Bijous.defaultBundleName}, and bijous
+# # to store results without a namespace
+# bijous = new Bijous
+#   bundles: 'modules/!(routes)'
+#
+# # We assume there is a module called *module1*
+# bijous.load (err, modules) ->
+#   console.log modules.module1
+#
+# # We define a new {Bijous} instance, specifying a bundle configuration with a
+# # name. This causes klect to use a bundle name, and bijous to namespace the
+# # results based on the bundle name
+# bijous = new Bijous
+#   bundles: { private: 'modules/!(routes)' }
+#
+# # We assume there is a module called *module1*
+# bijous.load (err, modules) ->
+#   console.log modules.private.module1
+# ```
+#
+# Returns: `undefined`
+setResult = (def, results, result) ->
+  if def.bundle == @defaultBundleName then results[def.name] = result
+  else _.merge results[def.bundle] ?= {}, _.object([def.name], [result])

   return

@@ -65,7 +115,7 @@
 # bijous instance when the module has completed loading. The callback function
 # has two arguments, the first argument will be an {Error} if one has occurred,
 # the second argument will be an optional {Object} containing pertinent results
-# from loading the module.
+# from loading the module, see {setResult}.
 #
 # ```coffee
 # # We can assume that the following four lines describes the module being
@@ -120,43 +170,31 @@
 #   console.log modules.web.module2
 # ```
 class Bijous extends EventEmitter
-  # Public: The default bundle configuration for klect. This configuration
-  # describes how all modules are to be found. (default: `modules/*`). For more
-  # information see [klect](https://github.com/awnist/klect).
+  # Public: The default bundle configuration for klect as a {String}. This
+  # configuration describes how all modules are to be found.
+  # (default: `modules/*`). For more information see klect.
   @defaultBundles: 'modules/*'

-  # Public: The default bundle name to pass to klect. When a bundle descriptor
-  # is passed from bijous to klect that is not an object (e.g. a string or
-  # an array) this is the name used for the bundle. Bundle results that
-  # bear it's name are not namespaced when received from the `load` callback.
-  # (default: `_`)
-  #
-  # ```coffee
-  # Bijous = require 'bijous'
-  #
-  # bijous = new Bijous
-  #   bundles: 'modules/!(router)'
-  #
-  # bijous.load (err, modules) ->
-  #   # Since our bundle does not have a name the default bundle name is used
-  #   # and it's results are not namespaced
-  #   console.log modules.module1
-  # ```
+  # Public: The default bundle name to pass to klect as a {String}. When a
+  # bundle descriptor is passed from bijous to klect that is not an object
+  # (e.g. a string or an array) this is the name used for the bundle. Bundle
+  # results that bear it's name are not namespaced when received from the `load`
+  # callback, see {setResult}. (default: `_`)
   @defaultBundleName: '_'

   # Public: Instantiates a new bijous loader.
   #
-  # options - The hash options used to configure bijous. (default: {})
-  #           :cwd - The directory where modules can be found. Defaults to the
-  #                  directory the module's parent resides in.
+  # options - The hash {Object} used to configure bijous. (default: {})
+  #           :cwd - The directory where modules can be found as a {String}.
+  #                  Defaults to the directory the module's parent resides in.
   #                  (default: `path.dirname(module.parent.filename)`)
-  #           :bundles - The klect bundles descriptor, used to find modules.
-  #                      (default: `Bijous.defaultBundles`)
-  #           :defaultBundleName - The name to use as the default bundle for
-  #                                *klect*. When passing in a string or an array
-  #                                for `bundles` this is the name used as the
-  #                                bundle name. (default:
-  #                                `Bijous.defaultBundleName`)
+  #           :bundles - The klect bundles descriptor as an {Object}, used to
+  #                      find modules. (default: {Bijous.defaultBundles})
+  #           :defaultBundleName - The {String} name to use as the default
+  #                                bundle for *klect*. When passing in a string
+  #                                or an array for `bundles` this is the name
+  #                                used as the bundle name. (default:
+  #                                {Bijous.defaultBundleName})
   constructor: ({@cwd, @bundles, @defaultBundleName} = {}) ->
     @cwd ?= path.dirname module.parent.filename
     @bundles ?= Bijous.defaultBundles
@@ -165,7 +203,8 @@
   # Public: Retrieves all modules found for it's bundles. When a bundle name is
   # supplied it retrieves files only for the specified bundle.
   #
-  # bundle - The bundle name to use when retrieving modules. (optional)
+  # bundle - The bundle name as a {String} to use when retrieving modules.
+  #          (optional)
   #
   # ```coffee
   # Bijous = require 'bijous'
@@ -180,7 +219,7 @@
   # onlyPublicBundles = bijous.list 'public'
   # ```
   #
-  # Returns an array of klect bundles
+  # Returns an {Array} of klect bundles
   list: (bundle) ->
     klect = new Klect
       cwd: @cwd
@@ -189,10 +228,11 @@
     klect.gather(@bundles).bundles bundle

   # Public: Calls node's `require` function for all module files found for it's
-  # bundles. When a bundle name is supplied it calls `require` with files only
-  # for the specified bundle.
-  #
-  # bundle - The bundle name to use when retrieving modules. (optional)
+  # bundles. When a bundle name is supplied it calls `require` with files
+  # associated only with the specified bundle.
+  #
+  # bundle - The bundle name as a {String} to use when retrieving modules.
+  # (optional)
   #
   # ```coffee
   # Bijous = require 'bijous'
@@ -222,11 +262,11 @@
   # bundles, and executes the returned module function. When a bundle name is
   # supplied it loads the files only for the specified bundle.
   #
-  # bundle - The bundle name to use when retrieving modules. (optional)
-  # callback - The callback function to use when all modules are loaded. The
-  #            `callback` receives two parameters, the first corresponds to any
-  #            errors that have occurred, the second contains the results of all
-  #            of the loaded modules.
+  # bundle - The bundle name as a {String} to use when retrieving modules.
+  #          (optional)
+  # callback - The callback {Function} to use when all modules are loaded. The
+  #            first argument will be an {Error} if one has occurred, the second
+  #            will be the results of all loaded modules see {setResult}.
   #
   # ```coffee
   # Bijous = require 'bijous'
@@ -265,10 +305,10 @@
   #
   # Emits done when loading of modules has completed and no error has occurred.
   #   The first argument will be an {Object} containing the results of all
-  #   loaded modules. The `done` event could be subscribed to by the loaded
-  #   module in order to execute a task once all modules are loaded. An example
-  #   would be if a *server* module wanted to listen for connections once all
-  #   modules were loaded.
+  #   loaded modules, see {setResult}. The `done` event could be subscribed to
+  #   by the loaded module in order to execute a task once all modules are
+  #   loaded. An example would be if a *server* module wanted to listen for
+  #   connections once all modules were loaded.
   #
   # Returns `undefined`
   load: (bundle, callback) ->


[api] [fix] Wrong name in logging messages

--- lib/api.coffee
+++ lib/api.coffee
@@ -111,8 +111,8 @@
     api.engineClient = new WebSocketClient(options.engineEndpoint)

     api.engineClient.on 'error', (err) ->
-      logger.error 'unable to connect to api server ' + options.engineEndpoint
-      logger.help 'did you try starting the api server?'
+      logger.error 'unable to connect to engine server ' + options.engineEndpoint
+      logger.help 'did you try starting the engine server?'
       logger.warn 'throwing connection error!'
       throw err;

@@ -138,4 +138,4 @@
           # Send the message
           # engineIOServer.clients[message.sid].send(JSON.stringify(message, true))

-      callback null, wss
+      callback null, wss


Clean DeployMonitor._resetTimeoutMonitor.

--- src/deploy-monitor.coffee
+++ src/deploy-monitor.coffee
@@ -14,6 +14,7 @@
     @_chat = chat
     @_deploy.on('change', ()=>
       @_resetTimeoutMonitor()
+      @_timeoutMonitor()
     )
     @_deploy.on('close', ()=>
       @.removeDeploy()
@@ -43,7 +44,6 @@
     if @_currentTimeout > 0
       @_chat.send "Continuing..."
     @_currentTimeout = 0
-    @_timeoutMonitor()

   getLastText = (text)->
     textLines = text.split(/\r?\n/)


Move the limit option into view

Move the limit option into view

--- lib/assets/javascripts/outpost/aggregator/aggregator.js.coffee
+++ lib/assets/javascripts/outpost/aggregator/aggregator.js.coffee
@@ -143,7 +143,7 @@

                 # Is there a limit? Add a notification to the top of the
                 # drop zone to let them know.
-                @limit = @options.base.options.viewOptions.dropLimit
+                @limit = @options.limit

                 if @limit
                     @limitNotification =


KDObject::forwardEvent naming fix

KDObject::forwardEvent naming fix

--- client/Framework/core/KDObject.coffee
+++ client/Framework/core/KDObject.coffee
@@ -20,8 +20,8 @@

   lazyBound: (method, rest...)-> @[method].bind this, rest...

-  forwardEvent:(target, event, prefix="")->
-    target.on event, @lazyBound 'emit', prefix + event
+  forwardEvent:(target, eventName, prefix="")->
+    target.on eventName, @lazyBound 'emit', prefix + eventName

   ready:(listener)->
     if @readyState is READY then listener()


KodingKite: code cleanup

KodingKite: code cleanup

--- client/app/lib/kite/kodingkite.coffee
+++ client/app/lib/kite/kodingkite.coffee
@@ -35,16 +35,8 @@
                  or there is a problem with connection."

-  extractInfoFromWsEvent = (event)->
-    {reason, code, wasClean, timestamp, type} = event
+  setTransport: (@transport) ->

-    return {reason, code, wasClean, timestamp, type}
-
-
-  getTransport: -> @transport
-
-
-  setTransport: (@transport) ->
     if @transport?.ws?
       @transport.disconnect()


shortcuts: add proxy methods

shortcuts: add proxy methods

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -115,6 +115,20 @@
     new ShortcutsModal {}, @shortcuts.config

+  get: ->
+    # proxy to shortcuts#get
+    # see: http://github.com/koding/shortcuts
+
+    @shortcuts.get.apply @shortcuts, Array::slice.call arguments
+
+
+  update: ->
+    # proxy to shortcuts#update
+    # see: http://github.com/koding/shortcuts
+
+    @shortcuts.update @shortcuts, Array::slice.call arguments
+
+
   getJSON: (name, filterFn) ->
     # convenience method that returns a collection's json repr.
     # this method omits all binding entries that is not compatible


shortcuts: pass options as null to modal, it is extended anyway

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -205,7 +205,7 @@
   #
   showModal: ->

-    modal = new ShortcutsModal {}, @toCollection()
+    modal = new ShortcutsModal null, @toCollection()

   # Proxy to _shortcuts#get_.


clean coffee syntax + reorder package by type

--- client/app/stores/layout_store.coffee
+++ client/app/stores/layout_store.coffee
@@ -1,8 +1,9 @@
+AppDispatcher = require '../app_dispatcher'
+
 Store = require '../libs/flux/store/store'
+AccountStore = require './account_store'

 {ActionTypes, Dispositions} = require '../constants/app_constants'
-AppDispatcher = require '../app_dispatcher'
-AccountStore = require './account_store'

 MessageActionCreator = null
 getMessageActionCreator = ->
@@ -201,7 +202,7 @@
                 key = "account creation ok configuration needed"

             @_showNotification
-                message: t(key)
+                message: t key
                 autoclose: true

         handle ActionTypes.EDIT_ACCOUNT_FAILURE, ({error}) ->
@@ -213,7 +214,7 @@

         handle ActionTypes.EDIT_ACCOUNT_SUCCESS, ->
             @_showNotification
-                message: t('account updated')
+                message: t 'account updated'
                 autoclose: true

         handle ActionTypes.CHECK_ACCOUNT_FAILURE, ({error}) ->
@@ -225,7 +226,7 @@

         handle ActionTypes.CHECK_ACCOUNT_SUCCESS, ->
             @_showNotification
-                message: t('account checked')
+                message: t 'account checked'
                 autoclose: true

         handle ActionTypes.REFRESH_FAILURE, ({error}) ->


fix download broken

fix download broken

--- server/controllers/routes.coffee
+++ server/controllers/routes.coffee
@@ -18,13 +18,14 @@
         get: album.list
         post: album.create

+    'albums/:albumid.zip':
+        get: album.zip
+
     'albums/:albumid/?':
         get: album.read
         put: album.update
         delete: album.delete

-    'albums/:albumid.zip':
-        get: album.zip

     'photos/?':
         post: photo.create


Improved humanTime helper method.

Improved humanTime helper method.

--- app/assets/javascripts/app/index.js.coffee
+++ app/assets/javascripts/app/index.js.coffee
@@ -109,10 +109,11 @@
       isHtmlEscape = true
       timestamp = App.i18n.translateTimestamp(result)
       escalation = false
-      if attribute_config.class && attribute_config.class.match 'escalation'
+      cssClass = attribute_config.class || ''
+      if cssClass.match 'escalation'
         escalation = true
       humanTime = App.PrettyDate.humanTime(result, escalation)
-      result    = "<time class=\"humanTimeFromNow #{attribute_config.class}\" data-time=\"#{result}\" data-tooltip=\"#{timestamp}\">#{humanTime}</time>"
+      result    = "<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{result}\" data-tooltip=\"#{timestamp}\">#{humanTime}</time>"

     if !isHtmlEscape && typeof result is 'string'
       result = App.Utils.htmlEscape(result)


OX-120 debug

OX-120 debug

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -205,7 +205,7 @@
         loadDirection[1] /= directionMax;
         loadDirection[2] /= directionMax;

-      console.log loadDirection
+      console.log direction, loadDirection

       delta_x = delta_y = delta_z = 0
       direction_x = direction_y = direction_z = 0


OX-120 debug

OX-120 debug

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -152,7 +152,7 @@

   pingImpl : (position, zoomSteps, direction) ->

-    unless position == @lastPosition and direction == @lastDirection and _.isEqual(zoomSteps, @lastZoomSteps)
+    unless _.isEqual(position, @lastPosition) and _.isEqual(direction, @lastDirection) and _.isEqual(zoomSteps, @lastZoomSteps)

       lastPosition = @lastPosition

@@ -197,6 +197,8 @@
         direction[2] * 0.8 + newDirection[2] * 0.2
       ]

+      console.log direction, newDirection, loadDirection
+
       directionMax = Math.abs(Math.max(direction[0], direction[1], direction[2]))

       loadDirection = direction
@@ -205,7 +207,6 @@
         loadDirection[1] /= directionMax;
         loadDirection[2] /= directionMax;

-      console.log direction, loadDirection

       delta_x = delta_y = delta_z = 0
       direction_x = direction_y = direction_z = 0


for #287 - console log ajax error message

--- app/assets/javascripts/modules/ajax.coffee
+++ app/assets/javascripts/modules/ajax.coffee
@@ -16,20 +16,26 @@
     Clarat.Ajax.stack[container] = [] unless Clarat.Ajax.stack[container]
     Clarat.Ajax.stack[container].push targetURL

-    $.get targetURL, (data) ->
-      # update container
-      container.html data
+    console.log targetURL
+    $.ajax
+      url: targetURL
+      success: (data) ->
+        # update container
+        container.html data

-      # update URL
-      if options.historyPush
-        history.pushState { turbolinks: true, url: targetURL }, '', targetURL
+        # update URL
+        if options.historyPush
+          history.pushState { turbolinks: true, url: targetURL }, '', targetURL

-      if Clarat.Ajax.stack[container]
-        # remove appropriate element from stack
-        processedIndex = Clarat.Ajax.stack[container].lastIndexOf targetURL
-        Clarat.Ajax.stack[container].splice processedIndex, 1
+        if Clarat.Ajax.stack[container]
+          # remove appropriate element from stack
+          processedIndex = Clarat.Ajax.stack[container].lastIndexOf targetURL
+          Clarat.Ajax.stack[container].splice processedIndex, 1

-      # remove waiting-for-ajax display styling when stack empty
-      container.removeClass 'Ajax' unless Clarat.Ajax.stack[container]?.length
+        # remove waiting-for-ajax display styling when stack empty
+        container.removeClass 'Ajax' unless Clarat.Ajax.stack[container]?.length

-      $(document).trigger 'ajax_replaced'
+        $(document).trigger 'ajax_replaced'
+      error: (error) ->
+        console.log error.statusText
+        console.log error.responseText.substr(0, 200)


add a timeout.

add a timeout.

--- app/assets/javascripts/test_mode.js.coffee
+++ app/assets/javascripts/test_mode.js.coffee
@@ -1,8 +1,10 @@
 $(document).on('ready page:load', ->
   elem = $("#organization_test_mode")
   elem.bootstrapSwitch()
+  reload = ->
+    location.reload
   switchSuccess = ->
-    location.reload()
+    setTimeout(reload, 1000)
   elem.on('switchChange.bootstrapSwitch', ((event, state) ->
     $.ajax(url: elem.data('update-path'), complete: switchSuccess, type: 'POST', data: {"organization[testmode]": state, _method: 'PATCH' } )
   ));


Clicking ought to select a tag.

Clicking ought to select a tag.

--- app/assets/javascripts/views/tagger.coffee
+++ app/assets/javascripts/views/tagger.coffee
@@ -31,7 +31,6 @@

     switch e.keyCode
       when $.ui.keyCode.TAB, $.ui.keyCode.ENTER
-        @set 'wantsCandidates', false
         pv.willAddSelection()

         if @get 'hasText'
@@ -46,7 +45,6 @@
       when $.ui.keyCode.BACKSPACE
         if !@get 'hasText'
           pv.willRemoveLastSelection()
-          @set 'wantsCandidates', false
           e.preventDefault()
       else
         @set 'wantsCandidates', true
@@ -74,6 +72,12 @@

     mouseEnter: ->
       @set 'parentView.currentIndex', @get('contentIndex')
+
+    click: ->
+      @get('parentView').willAddSelection()
+
+  willAddSelection: ->
+    @get('parentView').willAddSelection()

   cycleIndex: (->
     ci = @get 'currentIndex'
@@ -145,10 +149,12 @@

     if @get('controller').canAddSelection @get('selections.content'), item
       @get('controller').addSelection @get('selections.content'), item
+      @set 'entry.wantsCandidates', false

   willRemoveSelection: (item) ->
     if @get('controller').canRemoveSelection @get('selections.content'), item
       @get('controller').removeSelection @get('selections.content'), item
+      @set 'entry.wantsCandidates', false

   willRemoveLastSelection: ->
     @willRemoveSelection @get('selections.content.lastObject')


keep prev and next buttons

keep prev and next buttons

--- app/assets/javascripts/zvg/verbatim.coffee
+++ app/assets/javascripts/zvg/verbatim.coffee
@@ -116,13 +116,13 @@
     includeEndEllipsis = pEnd < @numberOfPages

     pagination = []
-    pagination.push({ text: 'prev', page: @_page - 1 }) if @_page > 1
+    pagination.push({ text: 'prev', page: if @_page > 1 then @_page - 1 else @_page })
     pagination.push({ text: 1, page: 1 }) if includeStartEllipsis
     pagination.push({ text: '...', page: null }) if includeStartEllipsis
     pagination.push({ text: x, page: x }) for x in [pStart..pEnd]
     pagination.push({ text: '...', page: null }) if includeEndEllipsis
     pagination.push({ text: @numberOfPages, page: @numberOfPages }) if includeEndEllipsis
-    pagination.push({ text: 'next', page: @_page + 1 }) if @_page < @numberOfPages
+    pagination.push({ text: 'next', page: if @_page < @numberOfPages then @_page + 1 else @page })

     console.log(pagination)
     links = @pagination.selectAll('a.zvg_page').data(pagination)


Enable silent/patch releases.

Enable silent/patch releases.

Currently, all new releases trigger a notification.

This changes that behaviour such that if the previous and current
releases have the same major and minor release numbers, then no
notification is shown.

This allows us to push bug-fix and minor releases without bugging the
user.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -18,7 +18,6 @@
         for file in files
           func tab.id, { file: file, allFrames: contentScripts.all_frames }, checkLastRuntimeError

-currentVersion = Utils.getCurrentVersion()
 frameIdsForTab = {}
 portsForTab = {}
 root.urlForTab = {}
@@ -89,7 +88,7 @@
     commandsToKey[command] = (commandsToKey[command] || []).concat(key)

   replacementStrings =
-    version: currentVersion
+    version: Utils.getCurrentVersion()
     title: customTitle || "Help"
     tip: if showCommandNames then "Tip: click command names to yank them to the clipboard." else " "

@@ -473,28 +472,37 @@

 # Show notification on upgrade.
 do showUpgradeMessage = ->
+  currentVersion = Utils.getCurrentVersion()
   # Avoid showing the upgrade notification when previousVersion is undefined, which is the case for new
   # installs.
-  Settings.set "previousVersion", currentVersion  unless Settings.get "previousVersion"
-  if Utils.compareVersions(currentVersion, Settings.get "previousVersion" ) == 1
-    notificationId = "VimiumUpgradeNotification"
-    notification =
-      type: "basic"
-      iconUrl: chrome.runtime.getURL "icons/vimium.png"
-      title: "Vimium Upgrade"
-      message: "Vimium has been upgraded to version #{currentVersion}. Click here for more information."
-      isClickable: true
-    if chrome.notifications?.create?
-      chrome.notifications.create notificationId, notification, ->
-        unless chrome.runtime.lastError
-          Settings.set "previousVersion", currentVersion
-          chrome.notifications.onClicked.addListener (id) ->
-            if id == notificationId
-              chrome.tabs.getSelected null, (tab) ->
-                TabOperations.openUrlInNewTab {tab, tabId: tab.id, url: "https://github.com/philc/vimium#release-notes"}
+  Settings.set "previousVersion", currentVersion  unless Settings.has "previousVersion"
+  previousVersion = Settings.get "previousVersion"
+  if Utils.compareVersions(currentVersion, previousVersion ) == 1
+    currentVersionNumbers = currentVersion.split "."
+    previousVersionNumbers = previousVersion.split "."
+    if currentVersionNumbers[...2].join(".") == previousVersionNumbers[...2].join(".")
+      # We do not show an upgrade message for patch/silent releases.  Such releases have the same major and
+      # minor version numbers.  We do, however, update the recorded previous version.
+      Settings.set "previousVersion", currentVersion
     else
-      # We need to wait for the user to accept the "notifications" permission.
-      chrome.permissions.onAdded.addListener showUpgradeMessage
+      notificationId = "VimiumUpgradeNotification"
+      notification =
+        type: "basic"
+        iconUrl: chrome.runtime.getURL "icons/vimium.png"
+        title: "Vimium Upgrade"
+        message: "Vimium has been upgraded to version #{currentVersion}. Click here for more information."
+        isClickable: true
+      if chrome.notifications?.create?
+        chrome.notifications.create notificationId, notification, ->
+          unless chrome.runtime.lastError
+            Settings.set "previousVersion", currentVersion
+            chrome.notifications.onClicked.addListener (id) ->
+              if id == notificationId
+                chrome.tabs.getSelected null, (tab) ->
+                  TabOperations.openUrlInNewTab {tab, tabId: tab.id, url: "https://github.com/philc/vimium#release-notes"}
+      else
+        # We need to wait for the user to accept the "notifications" permission.
+        chrome.permissions.onAdded.addListener showUpgradeMessage

 # The install date is shown on the logging page.
 chrome.runtime.onInstalled.addListener ({reason}) ->


(fix) Make student exercise list work.

(fix) Make student exercise list work.

--- app/scripts/pages/students/students.coffee
+++ app/scripts/pages/students/students.coffee
@@ -12,7 +12,7 @@
     @solutions = ko.observableArray()

   load: ->
-    api.solutions.getOfStudent(@id).then (solutions) ->
+    api.solutions.getOfStudent(@id).then (solutions) =>
       @solutions solutions

 class ViewModel


cache kodekeeper results for same source (#6493)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/views/play/level/tome/SpellView.coffee
+++ app/views/play/level/tome/SpellView.coffee
@@ -76,6 +76,7 @@
     @highlightCurrentLine = _.throttle @highlightCurrentLine, 100
     $(window).on 'resize', @onWindowResize
     @observing = @session.get('creator') isnt me.id
+    @loadedToken = {}

   afterRender: ->
     super()
@@ -810,13 +811,18 @@
   fetchToken: (source, language) =>
     if language not in ['java', 'cpp']
       return Promise.resolve(source)
+    else if source of @loadedToken
+      return Promise.resolve(@loadedToken[source])

     headers =  { 'Accept': 'application/json', 'Content-Type': 'application/json' }
     m = document.cookie.match(/JWT=([a-zA-Z0-9.]+)/)
     service = window?.localStorage?.kodeKeeperService or "https://asm14w94nk.execute-api.us-east-1.amazonaws.com/service/parse-code-kodekeeper"
     fetch service, {method: 'POST', mode:'cors', headers:headers, body:JSON.stringify({code: source, language: language})}
     .then (x) => x.json()
-    .then (x) => x.token
+    .then (x) =>
+      @loadedToken = {} # only cache 1 source
+      @loadedToken[source] = x.token;
+      return x.token

   fetchTokenForSource: () =>
     source = @ace.getValue()


Better explanation when runaway interpreter is stopped.

--- coffee/ExerciseController.coffee
+++ coffee/ExerciseController.coffee
@@ -109,8 +109,10 @@
         @interpreter = new BytecodeInterpreter()
         @spool.queueRunUntil 'DONE'
         i = 0
-        until @spool.isDone() || i > 10000
+        until @spool.isDone()
           i += 1
+          if i > 10000
+            throw "Interpreter seems to be stuck in a loop"
           bytecode = @spool.getNextBytecode @interpreter.isResultTruthy(),
             @interpreter.gosubbingLabel(), @interpreter.gotoingLabel(),
             @interpreter.stackSize()


check order of messages on applied-ops channel

--- services/real-time/app/coffee/DocumentUpdaterController.coffee
+++ services/real-time/app/coffee/DocumentUpdaterController.coffee
@@ -3,6 +3,7 @@
 redis = require("redis-sharelatex")
 rclient = redis.createClient(settings.redis.documentupdater)
 SafeJsonParse = require "./SafeJsonParse"
+EventLogger = require "./EventLogger"

 MESSAGE_SIZE_LOG_LIMIT = 1024 * 1024 # 1Mb

@@ -21,6 +22,8 @@
 				logger.error {err: error, channel}, "error parsing JSON"
 				return
 			if message.op?
+				if message._id?
+					EventLogger.checkEventOrder(message._id, message)
 				DocumentUpdaterController._applyUpdateFromDocumentUpdater(io, message.doc_id, message.op)
 			else if message.error?
 				DocumentUpdaterController._processErrorFromDocumentUpdater(io, message.doc_id, message.error, message)