archiving feature

archiving feature

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -1,14 +1,60 @@
 (($, window) ->

 	log = (args...) ->
-    	console.log.apply console, args if console?.log and mb?.debug is true
-    	return
-
-    #__msg = (args...) ->
-    #	ret = chrome.i18n.getMessage.apply chrome.i18n, args
-    #	log args, ret
-    #	ret
-    __msg = chrome.i18n.getMessage
+		console.log.apply console, args if console?.log and mb?.debug is true
+		return
+
+	#__msg = (args...) ->
+	#	ret = chrome.i18n.getMessage.apply chrome.i18n, args
+	#	log args, ret
+	#	ret
+	__msg = chrome.i18n.getMessage
+
+	# Adapted from http://stackoverflow.com/questions/6157929
+	class Eventr
+		@eventMatchers:
+			HTMLEvents:		/^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/i
+			MouseEvents:	/^(?:click|dblclick|mouse(?:down|up|over|move|out))$/i
+
+		@defaults:
+			pointerX: 	0
+			pointerY: 	0
+			button: 	0
+			ctrlKey: 	false
+			altKey: 	false
+			shiftKey: 	false
+			metaKey: 	false
+			bubbles: 	true
+			cancelable: true
+
+		@simulate: (target, eventName, options = {}) ->
+			for name,matcher of @eventMatchers
+				if matcher.test eventName
+					eventType = name
+					break
+
+			throw new SyntaxError 'Only HTMLEvents and MouseEvents interfaces are supported' unless eventType
+			eventName = eventName.toLowerCase()
+			options = _.defaults @defaults, options
+
+			if document.createEvent
+				evt = document.createEvent eventType
+				switch eventType
+					when 'HTMLEvents'
+						evt.initEvent eventName, options.bubbles, options.cancelable
+					else
+						evt.initMouseEvent eventName, options.bubbles, options.cancelable, document.defaultView, options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, target
+				target.dispatchEvent evt
+			else
+				options.clientX = options.pointerX
+				options.clientY = options.pointerY
+				delete options.pointerX
+				delete options.pointerY
+				evt = document.createEventObject()
+				oEvent = _.extend evt, options
+				target.fireEvent "on#{eventName}", oEvent
+
+			target

 	class M
 		debug: 	false
@@ -35,6 +81,8 @@
 		# Current GMail address of the logged in user
 		currentGmail: null

+		currentView: null
+
 		constructor: ->
 			window.addEventListener "message", @gmailrListener, false

@@ -44,6 +92,19 @@
 				return

+		inConversation: ->
+			@currentView is 'conversation'
+
+		getArchiveButton: ->
+			$ '.'+"T-I J-J5-Ji lR T-I-ax7 T-I-Js-IF ar7".split(' ').join '.'
+
+		activateArchiveButton: ->
+			return unless @inConversation()
+			button = @getArchiveButton().get 0
+			if button
+				Eventr.simulate button, 'mousedown'
+				Eventr.simulate button, 'mouseup'
+			return

 		getSettingEmail: (resp) ->
 			chrome.extension.sendMessage {action: "setting", key: 'email'}, resp
@@ -61,7 +122,8 @@
 						when 'init'
 							@currentGmail = evt.email
 						when 'viewChanged'
-							if evt.args[0] is "conversation"
+							@currentView = evt.args[0]
+							if @inConversation()
 								log 'User switched to conversation view'
 								@getSettingEmail (settingEmail) =>
 									log 'Email address in settings', settingEmail
@@ -603,14 +665,11 @@
 			div

 		getMessageId: ->
-			address = window.location.href
-
-			catPos = address.lastIndexOf '#'
-			slashPos = address.lastIndexOf '/'
-			if catPos > slashPos
+			id = /[0-9a-f]{16}/.exec window.location.hash
+			if id is null
 				throw __msg 'errorNotWithinAConversation'
 			else
-				address.substr (1 + slashPos)
+				id[0]

 		onSchedule: (props, loadingIcon, resetIcon) =>
 			try
@@ -620,8 +679,6 @@
 					error: e.toString()
 				return

-			loadingIcon?()
-
 			data =
 				action:		'schedule'
 				messageId:	messageId
@@ -635,6 +692,8 @@

 			log 'scheduling mail...', data

+			loadingIcon?() unless data.archive
+
 			# remove false values to transmit less data over the wire
 			_.each data, (val, key) ->
 				delete data[key] if val is false
@@ -647,14 +706,22 @@
 				data:			data
 				success:		(data, textStatus, jqXHR) =>
 									@onScheduleSuccess data
+									return
 				error:			(jqXHR, textStatus, errorThrown) =>
 									log arguments
 									@onScheduleError data, textStatus, errorThrown
-				#complete:		(jqXHR, textStatus) ->
-				#					resetIcon?()
-
-			_.delay (-> resetIcon?()), 400
-
+									return
+				complete:		(jqXHR, textStatus) ->
+									resetIcon?() unless data.archive
+									return
+
+			if data.archive
+				@activateArchiveButton()
+			else
+				_.delay (->
+						resetIcon?()
+						return
+						), 400
 			return


Refreshing attachments reorder in activity

--- app/coffee/modules/common/attachments.coffee
+++ app/coffee/modules/common/attachments.coffee
@@ -182,7 +182,8 @@
             newIndex = ui.item.index()

             $ctrl.reorderAttachment(attachment, newIndex)
-            $ctrl.saveAttachments()
+            $ctrl.saveAttachments().then ->
+                $scope.$emit("attachment:edit")

         showSizeInfo = ->
             $el.find(".size-info").removeClass("hidden")


fixing infinite loop error while intercepting start

--- modules/gui/lib/ViewContext.coffee
+++ modules/gui/lib/ViewContext.coffee
@@ -8,7 +8,10 @@
         ##
         ##
         configure: (config)->
+            @_hiddenKeys.push 'wire', 'wiring'
+
             @_template ?= config.template
+

             floyd.tools.objects.intercept @, 'boot', (done, boot)=>

@@ -18,29 +21,6 @@
                     boot done

-            config = super new floyd.Config
-
-                data:
-                    selector: undefined
-
-                    elements:
-                        root: '
'
-
-            , config
-
-            if config.widget
-                @_widget = floyd.tools.gui.ck config.widget
-
-            if !@_content && typeof (@_content = config.data.content || config.content) is 'function' && !config.data.raw
-                @_content = floyd.tools.gui.ck @_content
-
-            return config
-
-        ##
-        ##
-        ##
-        boot: (done)->
-            @_hiddenKeys.push 'wire', 'wiring'

             floyd.tools.objects.intercept @, 'start', (done, start)=>
                 start (err)=>
@@ -50,6 +30,28 @@

                     else done()

+            config = super new floyd.Config
+
+                data:
+                    selector: undefined
+
+                    elements:
+                        root: '
'
+
+            , config
+
+            if config.widget
+                @_widget = floyd.tools.gui.ck config.widget
+
+            if !@_content && typeof (@_content = config.data.content || config.content) is 'function' && !config.data.raw
+                @_content = floyd.tools.gui.ck @_content
+
+            return config
+
+        ##
+        ##
+        ##
+        boot: (done)->

             super done


KDFramework: KDListViewController lazyloader code cleaning

--- components/list/KDListViewController.coffee
+++ components/list/KDListViewController.coffee
@@ -365,7 +365,7 @@

     unless @lazyLoader
       @scrollView.addSubView @lazyLoader = new KDCustomHTMLView cssClass : "lazy-loader", partial : "Loading..."
-      @lazyLoader.addSubView @lazyLoader.canvas = new KDLoaderView
+      @lazyLoader.addSubView @lazyLoader.spinner = new KDLoaderView
         size          :
           width       : 16
         loaderOptions :
@@ -376,11 +376,11 @@
           speed       : 3
           FPS         : 24

-      @lazyLoader.canvas.show()
+      @lazyLoader.spinner.show()
       @emit 'LazyLoadThresholdReached'  if emitWhenReached

   hideLazyLoader:->
     if @lazyLoader
-      @lazyLoader.canvas.hide()
+      @lazyLoader.spinner.hide()
       @lazyLoader.destroy()
       @lazyLoader = null


CHG: fix bug of remote table panel

CHG: fix bug of remote table panel

--- lib/assets/javascripts/tao/ui/table/expandable.coffee
+++ lib/assets/javascripts/tao/ui/table/expandable.coffee
@@ -30,7 +30,7 @@
     $row.addClass 'expanded expanding'
     $panel.addClass 'expanded'
     $panelWrapper.css 'height', $panelWrapper[0].scrollHeight
-    @_loadRemotePanel $panel.find('> [data-url]')
+    @_loadRemotePanel $panelWrapper
     @

   collapseRow: (row) ->


ComputeUtils: if a credential already revived do not try to revive it

--- workers/social/lib/social/models/computeproviders/computeutils.coffee
+++ workers/social/lib/social/models/computeproviders/computeutils.coffee
@@ -9,15 +9,18 @@
   engineyard   : require './engineyard'

-reviveCredential = (client, pubKey, callback)->
+reviveCredential = (client, credential, callback)->

-  [pubKey, callback] = [callback, pubKey]  unless callback?
+  [credential, callback] = [callback, credential]  unless callback?

-  if not pubKey?
+  if not credential?
     return callback null

-  JCredential = require './credential'
-  JCredential.fetchByPublicKey client, pubKey, callback
+  if credential.bongo_?.constructorName is 'JCredential'
+    callback null, credential
+  else
+    JCredential = require './credential'
+    JCredential.fetchByPublicKey client, credential, callback

 reviveClient = (client, callback, revive = yes)->
@@ -45,7 +48,9 @@

 revive = do -> ({
-    shouldReviveClient, shouldPassCredential, shouldReviveProvider
+    shouldReviveClient
+    shouldPassCredential
+    shouldReviveProvider
   }, fn) ->

   (client, options, callback) ->


Improved error handling. Need to set “pointer-events: none” on .main for firefox (focus/select issue with user/groups and popovers in .main).

--- app/assets/javascripts/app/controllers/ticket_overview.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.coffee
@@ -18,6 +18,7 @@
     '.js-batch-assign-group-name':  'batchAssignGroupName'
     '.js-batch-assign-group-inner': 'batchAssignGroupInner'
     '.js-batch-macro':              'batchMacro'
+    '.main':                        'mainContent'

   events:
     'mousedown .item': 'startDragItem'
@@ -48,6 +49,8 @@

     $(document).on 'mousemove.item', @dragItem
     $(document).one 'mouseup.item', @endDragItem
+
+    @mainContent.addClass('u-unclickable')
     # TODO: fire @cancelDrag on ESC

   dragItem: (event) =>
@@ -104,6 +107,7 @@

   endDragItem: (event) =>
     return if !@batchSupport
+    @mainContent.removeClass('u-unclickable')
     $(document).off 'mousemove.item'
     $(document).off 'mouseup.item'
     pos = @batchDragger.data()
@@ -403,7 +407,9 @@
     group = App.Group.find(groupId)
     users = []

-    users.push App.User.find(i) for i in group.user_ids
+    for user_id in group.user_ids
+      if App.User.exists(user_id)
+        users.push App.User.find(user_id)

     @batchAssignGroupName.text group.displayName()
     @batchAssignGroupInner.html $(App.view('ticket_overview/batch_overlay_user_group')(


fix some bugs with lower casing script; make debug output better.

--- salvus/cassandra.coffee
+++ salvus/cassandra.coffee
@@ -1133,19 +1133,23 @@
                     t[k].push(r)
                 dbg("There are #{misc.len(t)} distinct lower case email addresses.")
                 f = (k, cb) =>
+                    dbg("Canonicalizing #{k}")
                     v = t[k]
                     account_id = undefined
                     async.series([
                         (c) =>
                             if v.length == 1
+                                dbg("#{k}: easy case of only one account")
                                 account_id = v[0][1]
                                 c(true)
-                                return
+                            else
+                                dbg("#{k}: have to deal with #{v.length} accounts")
+                                c()
                         (c) =>
                             @select
                                 table    : 'successful_sign_ins'
                                 columns  : ['time','account_id']
-                                where    : {'email_address':{'in':(x[0] for x in v)}}
+                                where    : {'account_id':{'in':(x[1] for x in v)}}
                                 order_by : 'time'
                                 cb       : (e, results) =>
                                     if e


Making templates available to paymentMethods module

--- app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
+++ app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
@@ -1 +1 @@
-angular.module("admin.paymentMethods", ['ngTagsInput', 'admin.utils'])
+angular.module("admin.paymentMethods", ['ngTagsInput', 'admin.utils', 'templates'])


remove pocket angular service

remove pocket angular service

--- public/javascripts/angular/angularapp.coffee
+++ public/javascripts/angular/angularapp.coffee
@@ -12,8 +12,4 @@
     $locationProvider.html5Mode true
     return

-app.service "PocketService", ($resource) ->
-
-    return
-
 app.controller "AppCtrl", ($scope) ->


add refactor notes

add refactor notes

--- app/assets/javascripts/hanuman/controllers/question.js.coffee
+++ app/assets/javascripts/hanuman/controllers/question.js.coffee
@@ -61,9 +61,14 @@

     deleteQuestion: ->
       question = @get('model')
+
+      ##### adolfo need to refactor to share this method with surveyStep controller #####
+      ##### call updateSortOrder(indexes)
+
       question.get('surveyStep').get('questions').removeObject(question)
       question.deleteRecord()
       question.save()
+

     newAnswerChoice: ->
       @set "isNewAnswerChoice", true


edit error view not exist

edit error view not exist

--- lib/client/javascripts/nali/model.js.coffee
+++ lib/client/javascripts/nali/model.js.coffee
@@ -336,7 +336,7 @@
     unless ( view = @views[ name ] )?
       if ( view = @::views[ name ] )?
         view = @views[ name ] = view.clone model: @
-      else console.error "View %s of model %O does not exist", name, @
+      else console.error 'View "%s" of model "%s" does not exist', name, @_name
     view

   show: ( name, insertTo ) ->


hotfix

hotfix

--- lib/init.coffee
+++ lib/init.coffee
@@ -34,7 +34,7 @@
     files = plugin.lupa.getState().files
     found = files.filter(
         (f) -> f.metadata.filter(
-            (entry) -> entry.name == 'module' && entry.data.indexOf(name) != -1
+            (entry) -> entry.name == 'modules' && entry.data.indexOf(name) != -1
         ).length
     )
     console.log('getFileFor ' + name, files)


Fix $PROJECT_NAME to be base instead of name from path.parse.

--- lib/init.coffee
+++ lib/init.coffee
@@ -655,7 +655,7 @@
     [projectPath, ...] = atom.project.relativizePath(filepath)
     if not projectPath
       return resolvedPath
-    projectName = path.parse(projectPath).name
+    projectName = path.parse(projectPath).base
     resolvedPath = resolvedPath.replace(/\$PROJECT_NAME/g, projectName)
     resolvedPath = resolvedPath.replace(/\$PROJECT_PATH/g, projectPath)
     return resolvedPath


change /projects/:projectId routes on create-project-page

--- app/components/create-project-page/create-project-page.coffee
+++ app/components/create-project-page/create-project-page.coffee
@@ -1,16 +1,19 @@
 module.exports =
-  url: '/groups/:groupId/projects/new'
+  url: '/projects/new'
   template: require('./create-project-page.html')
-  controller: ($scope, Records, $stateParams, $location, Toast) ->
+  controller: ($scope, Records, $location, Toast) ->
+
+    groupId = global.cobudgetApp.currentGroupId
+
     $scope.bucket = Records.buckets.build()
-    $scope.bucket.groupId = parseInt($stateParams.groupId)
+    $scope.bucket.groupId = groupId

     $scope.cancel = () ->
-      $location.path("/groups/#{$stateParams.groupId}")
+      $location.path("/groups/#{groupId}")

     $scope.done = () ->
       if $scope.bucketForm.$valid
         $scope.bucket.save().then (data) ->
-          bucketId = data.buckets[0].id
-          $location.path("/groups/#{$scope.bucket.groupId}/projects/#{bucketId}")
+          projectId = data.buckets[0].id
+          $location.path("/projects/#{projectId}")
           Toast.show('You launched a project for funding')


move comment for user is false next to if statment

--- services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
+++ services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
@@ -38,10 +38,9 @@
 		# so we can send back our custom `{message: {text: "", type: ""}}` responses on failure,
 		# and send a `{redir: ""}` response on success
 		passport.authenticate('local', (err, user, info) ->
-			# `user` is either a user object or false
 			if err?
 				return next(err)
-			if user
+			if user # `user` is either a user object or false
 				req.login user, (err) ->
 					res.json {redir: req._redir}
 			else


Delay destroying persisted react components

Turbolinks added delay to saving snapshot which caused it to save page after persisted components are unmounted.

--- resources/assets/lib/react-turbolinks.coffee
+++ resources/assets/lib/react-turbolinks.coffee
@@ -24,8 +24,12 @@
     @documentReady = false
     @targets = []
     $(document).on 'turbolinks:load', =>
+      # Delayed to wait until cacheSnapshot finishes.
+      @persistedTargets = @targets
+      @targets = []
+      Timeout.set 1, @destroyPersisted
+
       @documentReady = true
-      @destroyPersisted()
       @boot()
     $(document).on 'turbolinks:before-cache', =>
       @documentReady = false
@@ -52,7 +56,7 @@

   destroyPersisted: =>
-    ReactDOM.unmountComponentAtNode(target) while target = @targets.pop()
+    ReactDOM.unmountComponentAtNode(target) while target = @persistedTargets.pop()

   register: (name, element, propsFunction = ->) =>


Improve node name assignment & avoid error when click on canvas without selected node

--- app/frontend/javascripts/views/visualization-canvas.js.coffee
+++ app/frontend/javascripts/views/visualization-canvas.js.coffee
@@ -311,7 +311,7 @@
       .attr 'id',    (d,i) -> return 'node-label-'+d.id
       .attr 'class', 'node-label'
       .attr 'dy',    @getNodeLabelYPos
-      .text (d) -> return if d.name then d.name.trim() else ''
+      .text (d) -> return d.name
       .call @formatNodesLabels

     # ENTER new elements present in new data
@@ -320,7 +320,7 @@
       .attr 'class', 'node-label'
       .attr 'dx',    0
       .attr 'dy',    @getNodeLabelYPos
-      .text (d) -> return if d.name then d.name.trim() else ''
+      .text (d) -> return d.name
       .call @formatNodesLabels

     @nodes_labels = @nodes_labels_cont.selectAll('.node-label')
@@ -548,14 +548,15 @@
     @updateRelationsLabelsData()

   unfocusNode: ->
-    console.log 'unfocus node', @node_active
-    @node_active.active = false
-    @nodes_cont.selectAll('#node-'+@node_active.id)
-      .style 'stroke',  @getNodeStroke
-    @nodes_cont.selectAll('.node.active')
-      .classed 'active', false
-    @node_active = null
-    @onNodeOut()
+    if @node_active
+      console.log 'unfocus node', @node_active
+      @node_active.active = false
+      @nodes_cont.selectAll('#node-'+@node_active.id)
+        .style 'stroke',  @getNodeStroke
+      @nodes_cont.selectAll('.node.active')
+        .classed 'active', false
+      @node_active = null
+      @onNodeOut()

   sortNodes: (a, b) ->
     if a.size > b.size
@@ -940,36 +941,38 @@
   formatNodesLabels: (nodes) ->
     nodes.each () ->
       node = d3.select(this)
-      words = node.text().split(/\s+/).reverse()
-      line = []
-      i = 0
-      dy = parseFloat node.attr('dy')
-      tspan = node.text(null).append('tspan')
-        .attr('class', 'first-line')
-        .attr('x', 0)
-        .attr('dx', 5)
-        .attr('dy', dy)
-      while word = words.pop()
-        line.push word
-        tspan.text line.join(' ')
-        if tspan.node().getComputedTextLength() > 130
-          line.pop()
+      name = node.text()
+      if name != null and name != ''
+        words = name.trim().split(/\s+/).reverse()
+        line = []
+        i = 0
+        dy = parseFloat node.attr('dy')
+        tspan = node.text(null).append('tspan')
+          .attr('class', 'first-line')
+          .attr('x', 0)
+          .attr('dx', 5)
+          .attr('dy', dy)
+        while word = words.pop()
+          line.push word
           tspan.text line.join(' ')
-          line = [word]
-          # if firs tspan, we add ellipsis
-          if i == 0
-            node.append('tspan')
-              .attr('class', 'ellipsis')
-              .attr('dx', 2)
-              .text('...')
-          tspan = node.append('tspan')
-            .attr('x', 0)
-            .attr('dy', 13)
-            .text(word)
-          i++
-      # reset dx if label is not multiline
-      if i == 0
-        tspan.attr('dx', 0)
+          if tspan.node().getComputedTextLength() > 130
+            line.pop()
+            tspan.text line.join(' ')
+            line = [word]
+            # if firs tspan, we add ellipsis
+            if i == 0
+              node.append('tspan')
+                .attr('class', 'ellipsis')
+                .attr('dx', 2)
+                .text('...')
+            tspan = node.append('tspan')
+              .attr('x', 0)
+              .attr('dy', 13)
+              .text(word)
+            i++
+        # reset dx if label is not multiline
+        if i == 0
+          tspan.attr('dx', 0)

 module.exports = VisualizationCanvas


Fix qualitative color scale

Fix qualitative color scale

--- app/frontend/javascripts/views/visualization-canvas.js.coffee
+++ app/frontend/javascripts/views/visualization-canvas.js.coffee
@@ -17,7 +17,7 @@
     'solid-10': '#f1b6ae'
     'solid-11': '#a8a6a0'
     'solid-12': '#e0deda'
-  COLOR_QUALITATIVE:  ['#f0b6ad', '#d9d06f', '#ee9286', '#a8a69f', '#dfdeda', '#c5a0bb', '#ffebad', '#82a288', '#fbcf80', '#88b5df']
+  COLOR_QUALITATIVE:  ['#88b5df', '#fbcf80', '#82a288','#ffebad', '#c5a0bb', '#dfdeda', '#a8a69f', '#ee9286', '#d9d06f', '#f0b6ad']
   COLOR_QUANTITATIVE: ['#fff0c2', '#ffe795', '#fedf69', '#fed63c', '#b5ba7c', '#6d9ebb', '#2482fb', '#2b64c5', '#31458f', '#382759']

   svg:                    null
@@ -618,9 +618,15 @@

   setColorScale: ->
     if @parameters.nodesColor == 'qualitative' or @parameters.nodesColor == 'quantitative'
-      @color_scale = d3.scaleQuantize().range if @parameters.nodesColor == 'qualitative' then @COLOR_QUALITATIVE else @COLOR_QUANTITATIVE
       color_scale_domain = @data_nodes.map (d) => return d[@parameters.nodesColorColumn]
-      @color_scale.domain [0, d3.max(color_scale_domain)]
+      if @parameters.nodesColor == 'qualitative'
+        @color_scale = d3.scaleOrdinal().range @COLOR_QUALITATIVE
+        @color_scale.domain _.uniq(color_scale_domain)
+        #console.log 'color_scale_domain', _.uniq(color_scale_domain)
+      else
+        @color_scale = d3.scaleQuantize().range @COLOR_QUANTITATIVE
+        @color_scale.domain [0, d3.max(color_scale_domain)]
+        #console.log 'color_scale_domain', d3.max(color_scale_domain)
       # @color_scale = d3.scaleViridis()
       #   .domain([d3.max(color_scale_domain), 0])


Adjusted computation of current selection to favor arrangedContent

--- packages/emberella/lib/mixins/selectable_mixin.coffee
+++ packages/emberella/lib/mixins/selectable_mixin.coffee
@@ -75,7 +75,7 @@
     content = @getActiveContent()
     selection = get(@, '_selection')
     selection.filter((item) -> content.contains(item))
-  .property('_selection.[]', 'content.[]', 'arrangedContent.[]')
+  .property('_selection.[]', 'content', 'arrangedContent.[]')

   ###
     The first member of the content array that would be a valid selection. The
@@ -104,15 +104,15 @@
   ###
     Retrieve an array of items that could appear in the active selection.

-    The default behavior is simply to return the `content` array. Override this
-    method to intorduce custom retrieval or assembly of the array of
-    potentially selectable items.
+    The default behavior is simply to return the `arrangedContent` array.
+    Override this method to intorduce custom retrieval or assembly of the
+    array of potentially selectable items.

     @method getActiveContent
     @return Array
   ###
   getActiveContent: ->
-    get(@, 'content')
+    get(@, 'arrangedContent')

   ###
     Manipulate the selection set.


guest: light refactoring

guest: light refactoring

--- workers/social/lib/social/models/guest.coffee
+++ workers/social/lib/social/models/guest.coffee
@@ -4,10 +4,8 @@

   error =(err)->
     message:
-      if 'string' is typeof err
-        err
-      else
-        err.message
+      if 'string' is typeof err then err
+      else err.message

   {secure, dash} = require 'bongo'

@@ -61,19 +59,21 @@
         avatar      : String
         status      : String

-  @_resetAllGuests =(count=1e4)->
+  @resetAllGuests =(count=1e4)->
     @drop ->
       queue = [0...count].map (guestId)->->
         guest = new JGuest {guestId}
         guest.save (err)->
+          console.trace()
           console.log 'saved a guest!'
           queue.fin err
       dash queue, ->
         console.log 'done restting guests!'

-  @resetAllGuests =(client, callback)->
+  @resetAllGuests$ =(client)->
+    console.trace()
     {delegate} = client.connection
-    @_resetAllGuests() if delegate.can('reset guests')
+    @resetAllGuests() if delegate.can('reset guests')

   @recycle =(guest, callback=->) ->
     guestId = if guest instanceof @ then guest.getId() else guest
@@ -90,7 +90,7 @@
       createId = require 'hat'
       if clientId?
         if obtaining[clientId]
-          @once "ready.#{clientId}", (guest)-> console.log 'finally got', clientId; callback null, guest
+          @once "ready.#{clientId}", (guest)-> callback null, guest
           return
         obtaining[clientId] = yes
       {delegate} = client?.connection


Blockchain.info API response was for addresses that weren't found was changed

--- script/timestamp.coffee
+++ script/timestamp.coffee
@@ -35,7 +35,7 @@

 first_seen = (address, cb) ->
   ($.get "https://blockchain.info/q/addressfirstseen/#{address}").always (resp, status) ->
-    cb status is 'success' and new Date resp*1000
+    cb (new Date resp*1000 if status is 'success' and resp>0)

 display_error = (message) ->
   if message then $('.timestamp .error').text(message).show()


add smoke test env var

add smoke test env var

--- services/clsi/config/settings.defaults.coffee
+++ services/clsi/config/settings.defaults.coffee
@@ -25,7 +25,7 @@
 		clsi:
 			url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013"

-	smokeTest: false
+	smokeTest: process.env["SMOKE_TEST"] or false
 	project_cache_length_ms: 1000 * 60 * 60 * 24
 	parallelFileDownloads:1


Client: AccountSessionListItem: place tooltip on left

--- client/account/lib/accountsessionlistitem.coffee
+++ client/account/lib/accountsessionlistitem.coffee
@@ -20,7 +20,7 @@
       callback : listView.lazyBound 'deleteItem', this

     if kookies.get('clientId') is data.clientId
-      deleteButtonOptions.tooltip = { title : 'This will log you out!' }
+      deleteButtonOptions.tooltip = { title : 'This will log you out!', placement: 'left' }

     @deleteButton = new kd.ButtonView deleteButtonOptions


client: home: minor fixed account url

client: home: minor fixed account url

--- client/home/lib/commons/homeappavatarview.coffee
+++ client/home/lib/commons/homeappavatarview.coffee
@@ -33,7 +33,7 @@
     @profileName = new JCustomHTMLView
       tagName    : 'a'
       cssClass   : 'HomeAppView-Nav--fullname'
-      attributes : { href: '/Home/Account' }
+      attributes : { href: '/Home/My-Account' }
       pistachio  : '{{ #(profile.firstName)}} {{ #(profile.lastName)}}'
     , account


Use .then instead of deprecated .done on promise

--- lib/main.coffee
+++ lib/main.coffee
@@ -112,7 +112,7 @@
       searchAllPanes: true
     if atom.config.get('markdown-preview.openPreviewInSplitPane')
       options.split = 'right'
-    atom.workspace.open(uri, options).done (markdownPreviewView) ->
+    atom.workspace.open(uri, options).then (markdownPreviewView) ->
       if isMarkdownPreviewView(markdownPreviewView)
         previousActivePane.activate()


Cleaned up the main module

Cleaned up the main module

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,120 +1,99 @@
-{CompositeDisposable,Disposable} = require('atom')
-path                             = require('path')
-CSON                             = require('season')
-
-# ------------------------------------------------------------------------------
+path = require 'path'
+CSON = require 'season'

 module.exports =
+  activate: ->
+    @pkgName = 'levels-language-ruby'
+    @pkgDirPath = atom.packages.resolvePackagePath @pkgName

-  ## Language package activation and deactivation ------------------------------
-
-  activate: ->
-    @pkgDirPath = path.dirname(__dirname)
-    pkgMetadataFilePath = CSON.resolve(path.join(@pkgDirPath,'package'))
-    @pkgMetadata = CSON.readFileSync(pkgMetadataFilePath)
-
-    @configFilePath = @getConfigFilePath()
+    @configFilePath = CSON.resolve path.join @pkgDirPath, 'language', 'config'
     @executablePath = @getExecutablePath()

-    pkgSubscr = atom.packages.onDidActivatePackage (pkg) =>
-      if pkg.name is @pkgMetadata.name
+    pkgSubscription = atom.packages.onDidActivatePackage (pkg) =>
+      if pkg.name == @pkgName
         @dummyGrammar = pkg.grammars[0]
-        atom.grammars.removeGrammar(@dummyGrammar)
-        @startUsingLevels() if @consumedLevels
-        pkgSubscr.dispose()
+        atom.grammars.removeGrammar @dummyGrammar
+
+        if @consumedLevels
+          @startUsingLevels()
+
+        pkgSubscription.dispose()
         @activated = true

   deactivate: ->
-    @levelsBinding?.dispose()
+    @stopUsingLevels()
+    @consumedLevels = false
     @activated = false

-  ## Activation helpers --------------------------------------------------------
+  getExecutablePath: ->
+    executableDirPath = path.join @pkgDirPath, 'language', 'executables'
+    switch process.platform
+      when 'darwin' then path.join executableDirPath, 'darwin', 'run'
+      when 'linux'  then path.join executableDirPath, 'linux', 'run'
+      when 'win32'  then path.join executableDirPath, 'win32', 'run.exe'

-  getConfigFilePath: ->
-    CSON.resolve(path.join(@pkgDirPath,'language','config'))
-
-  getExecutablePath: ->
-    executableDirPath = path.join(@pkgDirPath,'language','executables')
-    switch process.platform
-      when 'darwin' then path.join(executableDirPath,'darwin','run')
-      when 'linux'  then path.join(executableDirPath,'linux','run')
-      when 'win32'  then path.join(executableDirPath,'win32','run.exe')
-
-  ## Interacting with the Levels package ---------------------------------------
-
-  consumeLevels: ({@languageRegistry,@notificationUtils}) ->
+  consumeLevels: ({@languageRegistry}) ->
     if @activated
       @startUsingLevels()
     @consumedLevels = true
-    @levelsBinding = new Disposable =>
-      @stopUsingLevels()
-      @consumedLevels = false

   startUsingLevels: ->
-    unless @usingLevels
+    if !@usingLevels
       try
-        unless @language?
-          @language = @languageRegistry.readLanguageSync\
-            (@configFilePath,@executablePath)
+        if !@language
+          @language = @languageRegistry.readLanguageSync @configFilePath, @executablePath
           @dummyGrammar.name = @language.getGrammarName()
           @dummyGrammar.scopeName = @language.getScopeName()
           @dummyGrammar.fileTypes = @language.getLevelCodeFileTypes()
-          @language.setDummyGrammar(@dummyGrammar)
-        atom.grammars.addGrammar(@dummyGrammar)
-        @languageRegistry.addLanguage(@language)
+          @language.setDummyGrammar @dummyGrammar
+
+        atom.grammars.addGrammar @dummyGrammar
+        @languageRegistry.addLanguage @language
+
         @usingLevels = true
         @onDidStartUsingLevels()
       catch error
-        atom.notifications.addError \
-          "Failed to load the language from the #{@pkgMetadata.name} package",
-          detail: error.toString(),
+        atom.notifications.addError "Failed to load the language from the #{@pkgName} package",
+          detail: "#{error}"
           dismissable: true

   stopUsingLevels: ->
     if @usingLevels
-      atom.grammars.removeGrammar(@dummyGrammar)
-      @languageRegistry.removeLanguage(@language)
+      atom.grammars.removeGrammar @dummyGrammar
+      @languageRegistry.removeLanguage @language
       @usingLevels = false
       @onDidStopUsingLevels()

   onDidStartUsingLevels: ->
     @setUpConfigManagement()
-    whitelistPath = path.join(@pkgDirPath,'language','whitelist')
+    whitelistPath = path.join @pkgDirPath, 'language', 'whitelist'
     process.env['LRB_WHITELIST_PATH'] = whitelistPath

   onDidStopUsingLevels: ->
-    @configSubscrs.dispose()
+    @configSubscription.dispose()
     delete process.env['LRB_WHITELIST_PATH']
-
-  ## Language configuration management -----------------------------------------

   config:
     rubyInterpreterDirectoryPath:
       title: 'Ruby Interpreter Directory Path'
-      description:
-        'The path to the directory that contains the Ruby interpreter (a file
-        named `ruby` on Mac OS X/Linux and `ruby.exe` on Windows, respectively).
-        If no path is given, the `PATH` environment variable will be checked
-        automatically.'
+      description: 'The path to the directory that contains the Ruby interpreter
+        (a file named `ruby` on macOS or Linux and `ruby.exe` on Windows, respectively).
+        If no path is given, the `PATH` environment variable will be checked automatically.'
       type: 'string'
       default: ''

   setUpConfigManagement: ->
-    @configSubscrs = new CompositeDisposable
+    configKeyPath = "#{@pkgName}.rubyInterpreterDirectoryPath"

-    configKeyPath = "#{@pkgMetadata.name}.rubyInterpreterDirectoryPath"
-    if (rubyInterpreterDirectoryPath = atom.config.get(configKeyPath).trim())
-      rubyInterpreterPath = path.join(rubyInterpreterDirectoryPath,'ruby')
-      executionCommandPatterns = ["#{rubyInterpreterPath} <filePath>"]
-      @language.setExecutionCommandPatterns(executionCommandPatterns)
+    if rubyInterpreterDirectoryPath = atom.config.get(configKeyPath).trim()
+      rubyInterpreterPath = path.join rubyInterpreterDirectoryPath, 'ruby'
+      @language.setExecutionCommandPatterns ["#{rubyInterpreterPath} <filePath>"]
     else
-      @language.setExecutionCommandPatterns(["ruby <filePath>"])
-    @configSubscrs = atom.config.onDidChange configKeyPath, ({newValue}) =>
-      if (rubyInterpreterDirectoryPath = newValue.trim())
-        rubyInterpreterPath = path.join(rubyInterpreterDirectoryPath,'ruby')
-        executionCommandPatterns = ["#{rubyInterpreterPath} <filePath>"]
-        @language.setExecutionCommandPatterns(executionCommandPatterns)
+      @language.setExecutionCommandPatterns ['ruby <filePath>']
+
+    @configSubscription = atom.config.onDidChange configKeyPath, ({newValue}) =>
+      if rubyInterpreterDirectoryPath = newValue.trim()
+        rubyInterpreterPath = path.join rubyInterpreterDirectoryPath, 'ruby'
+        @language.setExecutionCommandPatterns ["#{rubyInterpreterPath} <filePath>"]
       else
-        @language.setExecutionCommandPatterns(["ruby <filePath>"])
-
-# ------------------------------------------------------------------------------
+        @language.setExecutionCommandPatterns ['ruby <filePath>']


:lipstick: Sort grammars

:lipstick: Sort grammars

--- lib/main.coffee
+++ lib/main.coffee
@@ -3,9 +3,9 @@
 module.exports =
   configDefaults:
     grammars: [
-      'text.plain'
       'source.gfm'
       'text.git-commit'
+      'text.plain'
     ]

   activate: ->


fix nl for dateFns

fix nl for dateFns

--- vue/src/shared/services/session.coffee
+++ vue/src/shared/services/session.coffee
@@ -22,7 +22,7 @@
   locale.replace('-', '_')

 dateFnsLocale = (locale) ->
-  return 'nl' if locale == "nl-NL"
+  return 'nl' if locale.startsWith('nl')
   locale.replace('_','-')

 loadLocale = (locale) ->


Remove line arrows and make it thicker.

Remove line arrows and make it thicker.

--- web/js/views/map_route_leg_view.coffee
+++ web/js/views/map_route_leg_view.coffee
@@ -14,13 +14,11 @@

     render: () ->
       path = (new google.maps.LatLng point.y, point.x for point in @leg.get('shape'))
-      @line = new google.maps.Polyline _.extend({
+      @line = new google.maps.Polyline
           map: @map
           path: path
-          strokeWeight: 3
+          strokeWeight: 5
           strokeColor: Utils.transportColors[@leg.get('type')]
-          strokeOpacity: 1.0
-        }, @_legStyle())
       @marker = new MapLegMarker(@map, path[Math.floor(path.length / 2)], @leg.get('type'))

       google.maps.event.addListener @line, 'click', @onClicked
@@ -66,29 +64,4 @@
       bounds = new google.maps.LatLngBounds()
       if @line? and @line.getPath()?
         bounds.extend(latLng) for latLng in @line.getPath().getArray()
-      bounds
-
-    _legStyle: () ->
-      switch @leg.get('type')
-        when 'walk'
-          strokeOpacity: 0
-          icons: [{
-            icon:
-              path: 'M 0,-0.2 0,0.2'
-              strokeOpacity: 1
-            repeat: '7px'
-          }, {
-            icon:
-              path: google.maps.SymbolPath.FORWARD_OPEN_ARROW
-              scale: 2.5
-              strokeOpacity: 1
-            repeat: '150px'
-          }]
-        else
-          icons: [{
-            icon:
-              path: google.maps.SymbolPath.FORWARD_OPEN_ARROW
-              scale: 2.5
-              strokeOpacity: 1
-            repeat: '150px'
-          }]
+      bounds


Initial commit

Initial commit

--- src/AngularJqueryQueryBuilderDirective.coffee
+++ src/AngularJqueryQueryBuilderDirective.coffee
@@ -1,4 +1,4 @@
-angular.module 'angular-jquery-querybuilder'
+angular.module 'angular-jquery-querybuilder', []
 .directive 'queryBuilderr', [ () ->
   restrict: 'AE'
   scope:
@@ -19,7 +19,7 @@
         $scope.builder.api.getRules = -> $element[0].queryBuilder.getRules()
         $scope.builder.api.updateRules = updateRules
         $scope.builder.api.init = launchBuilder
-        $scope.builder.builder = launchBuilder
+        $scope.builder.builder = $element[0].queryBuilder

     viewToModelNotificator = ->
       $element.on 'afterUpdateRuleValue.queryBuilder    afterUpdateRuleFilter.queryBuilder


Add new env var for which rooms to announce to

--- scripts/forum-poll.coffee
+++ scripts/forum-poll.coffee
@@ -8,7 +8,11 @@
 PROD_MODE        = process.env.NODE_ENV is 'production'
 POLL_INTERVAL    = 1000 * 60 * 10 # 10 minute interval to start with
 POLL_INTERVAL_ID = null
-ANNOUNCE_ROOMS   = process.env.HUBOT_IRC_ROOMS.split ','
+ANNOUNCE_ROOMS   = []
+
+do =>
+  rooms = process.env.FORUM_POLL_ANNOUNCE_ROOMS || process.env.HUBOT_IRC_ROOMS
+  ANNOUNCE_ROOMS = rooms.split ','

 class ThreadPoster
   constructor: (@robot) ->


added id to @clients properties

added id to @clients properties

--- lib/room.coffee
+++ lib/room.coffee
@@ -12,18 +12,18 @@

     @channel.on 'connection', (socket) =>
       socket.on "nickname.set", (name) =>
-        @clients[getID(socket)] = nickname: name
+        @clients[getID(socket)] = id: socket.id, nickname: name
         @channel.emit 'room.list', (client.nickname for id, client of @clients)
         socket.broadcast.emit 'announcement', "#{name} connected"
-        socket.emit 'sit', 'white', @clients[@white].nickname if @white?
-        socket.emit 'sit', 'black', @clients[@black].nickname if @black?
+        socket.emit 'sit', 'white', @clients[@white] if @white?
+        socket.emit 'sit', 'black', @clients[@black] if @black?

       socket.on 'sit', (color) =>
         canSit = switch color
           when 'white' then @sitAsWhite socket
           when 'black' then @sitAsBlack socket
           else false
-        @channel.emit 'sit', color, {id: socket.id, nickname: @clients[getID(socket)].nickname} if canSit
+        @channel.emit 'sit', color, @clients[getID(socket)] if canSit

       socket.on 'stand', (color) =>
         canStand = switch color


Lowercase the right thing

Lowercase the right thing

--- scripts/hot-topics.coffee
+++ scripts/hot-topics.coffee
@@ -27,6 +27,6 @@
   robot.hear /(.+)/i, (msg) ->
     text = msg.message.text
     unless text.match 'hot topic'
-      memories = for phrase, memory of robot.brain.data.hot_topics when text.match(phrase.toLowerCase())
+      memories = for phrase, memory of robot.brain.data.hot_topics when text.toLowerCase().match(phrase)
         memory
       msg.send memories.join("\n")


curl timeout

curl timeout

--- scripts/pipeline.coffee
+++ scripts/pipeline.coffee
@@ -77,11 +77,11 @@
               else if split[0] == webuiPid
                 webuiCpu = +split[1] / nproc
                 webuiMem = +split[2]
-            child_process.exec 'curl -s -o /dev/null -w "%{http_code};%{time_total}\n" localhost:8181/ws/jobs', (error, stdout, stderr) ->
+            child_process.exec 'curl -s -o /dev/null --max-time 30 -w "%{http_code};%{time_total}\n" localhost:8181/ws/jobs | grep ";"', (error, stdout, stderr) ->
               engineLatency = "unreachable"
               if stdout.split('\n')[0].split(";")[0] == "200"
                 engineLatency = +(stdout.split('\n')[0].split(";")[1].replace(/,/,"."))
-              child_process.exec 'curl -s -o /dev/null -w "%{http_code};%{time_total}\n" localhost:9000/alive', (error, stdout, stderr) ->
+              child_process.exec 'curl -s -o /dev/null --max-time 30 -w "%{http_code};%{time_total}\n" localhost:9000/alive | grep ";"', (error, stdout, stderr) ->
                 webuiLatency = "unreachable"
                 if stdout.split('\n')[0].split(";")[0] == "200"
                   webuiLatency = +(stdout.split('\n')[0].split(";")[1].replace(/,/,"."))


Move convert to top

Move convert to top

--- services/web/public/coffee/main/plans.coffee
+++ services/web/public/coffee/main/plans.coffee
@@ -333,13 +333,11 @@
 			$scope.currencyCode = newCurrency

 		$scope.signUpNowClicked = (plan, annual)->
+			if $scope.shouldABTestPlans and plan in ['student', 'collaborator']
+				sixpack.convert 'plans-1610', () ->
 			if $scope.ui.view == "annual"
 				plan = "#{plan}_annual"
-
 			event_tracking.send   'subscription-funnel', 'sign_up_now_button', plan
-			# TODO: double check this is correct
-			if $scope.shouldABTestPlans and plan in ['student', 'collaborator']
-				sixpack.convert 'plans-1610', () ->

 		$scope.switchToMonthly = ->
 			$scope.ui.view = "monthly"


Delete overlay node from hash before removing

--- src/overlay-manager.coffee
+++ src/overlay-manager.coffee
@@ -9,8 +9,8 @@

     for id, overlayNode of @overlayNodesById
       unless presenter.state.content.overlays.hasOwnProperty(id)
+        delete @overlayNodesById[id]
         overlayNode.remove()
-        delete @overlayNodesById[id]

     return


Conditionally include deprecations in PackageManager

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -1,7 +1,6 @@
 path = require 'path'

 _ = require 'underscore-plus'
-EmitterMixin = require('emissary').Emitter
 {Emitter} = require 'event-kit'
 fs = require 'fs-plus'
 Q = require 'q'
@@ -28,8 +27,6 @@
 # settings and also by calling `enablePackage()/disablePackage()`.
 module.exports =
 class PackageManager
-  EmitterMixin.includeInto(this)
-
   constructor: ({configDirPath, @devMode, safeMode, @resourcePath}) ->
     @emitter = new Emitter
     @packageDirPaths = []
@@ -57,11 +54,6 @@
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidLoadInitialPackages: (callback) ->
     @emitter.on 'did-load-initial-packages', callback
-    @emitter.on 'did-load-all', callback # TODO: Remove once deprecated pre-1.0 APIs are gone
-
-  onDidLoadAll: (callback) ->
-    Grim.deprecate("Use `::onDidLoadInitialPackages` instead.")
-    @onDidLoadInitialPackages(callback)

   # Public: Invoke the given callback when all packages have been activated.
   #
@@ -70,11 +62,6 @@
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidActivateInitialPackages: (callback) ->
     @emitter.on 'did-activate-initial-packages', callback
-    @emitter.on 'did-activate-all', callback # TODO: Remove once deprecated pre-1.0 APIs are gone
-
-  onDidActivateAll: (callback) ->
-    Grim.deprecate("Use `::onDidActivateInitialPackages` instead.")
-    @onDidActivateInitialPackages(callback)

   # Public: Invoke the given callback when a package is activated.
   #
@@ -111,16 +98,6 @@
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidUnloadPackage: (callback) ->
     @emitter.on 'did-unload-package', callback
-
-  on: (eventName) ->
-    switch eventName
-      when 'loaded'
-        Grim.deprecate 'Use PackageManager::onDidLoadInitialPackages instead'
-      when 'activated'
-        Grim.deprecate 'Use PackageManager::onDidActivateInitialPackages instead'
-      else
-        Grim.deprecate 'PackageManager::on is deprecated. Use event subscription methods instead.'
-    EmitterMixin::on.apply(this, arguments)

   ###
   Section: Package system data
@@ -331,7 +308,7 @@
     packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath))
     packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath)
     @loadPackage(packagePath) for packagePath in packagePaths
-    @emit 'loaded'
+    @emit 'loaded' if Grim.includeDeprecations
     @emitter.emit 'did-load-initial-packages'

   loadPackage: (nameOrPath) ->
@@ -380,7 +357,7 @@
       packages = @getLoadedPackagesForTypes(types)
       promises = promises.concat(activator.activatePackages(packages))
     Q.all(promises).then =>
-      @emit 'activated'
+      @emit 'activated' if Grim.includeDeprecations
       @emitter.emit 'did-activate-initial-packages'

   # another type of package manager can handle other package types.
@@ -432,3 +409,24 @@
     stack = "#{error.stack}\n  at #{metadataPath}:1:1"
     message = "Failed to load the #{path.basename(packagePath)} package"
     atom.notifications.addError(message, {stack, detail, dismissable: true})
+
+if Grim.includeDeprecations
+  PackageManager::onDidLoadAll = (callback) ->
+    Grim.deprecate("Use `::onDidLoadInitialPackages` instead.")
+    @onDidLoadInitialPackages(callback)
+
+  PackageManager::onDidActivateAll = (callback) ->
+    Grim.deprecate("Use `::onDidActivateInitialPackages` instead.")
+    @onDidActivateInitialPackages(callback)
+
+  EmitterMixin = require('emissary').Emitter
+  EmitterMixin.includeInto(PackageManager)
+  PackageManager::on = (eventName) ->
+    switch eventName
+      when 'loaded'
+        Grim.deprecate 'Use PackageManager::onDidLoadInitialPackages instead'
+      when 'activated'
+        Grim.deprecate 'Use PackageManager::onDidActivateInitialPackages instead'
+      else
+        Grim.deprecate 'PackageManager::on is deprecated. Use event subscription methods instead.'
+    EmitterMixin::on.apply(this, arguments)


Let admin start a new round from list

Let admin start a new round from list

--- client.coffee
+++ client.coffee
@@ -90,21 +90,36 @@
                 else
                     # this round is current and has been ranked, or needs to be ranked
                     Dom.div !->
-                        Dom.style width: '40px', height: '40px', marginRight: '10px', Box: 'center middle'
+                        Dom.style Flex: 1, padding: '8px', margin: '-8px'
+                        Dom.addClass 'b_'
+                        Dom.addClass 'b_center'
+                        Dom.addClass 'b_middle'
+                        Dom.div !->
+                            Dom.style width: '40px', height: '40px', marginRight: '10px', Box: 'center middle'
+                            Icon.render
+                                data: (if hasRanked then 'clock2' else 'new')
+                                color: (if hasRanked then '#aaa' else null)
+                                style: { display: 'block' }
+                                size: 34
+
+                        Dom.div !->
+                            Dom.style Flex: 1
+                            Dom.b !->
+                                Dom.style color: (if !hasRanked then Colors.highlight else 'inherit')
+                                Dom.text Util.qToQuestion(q)
+                            Dom.div !->
+                                Dom.text (if hasRanked then tr("Results & new round") else tr("Voting closes & new round")) + ' '
+                                Time.deltaText(Db.shared.get('next'))
+                        Dom.onTap !->
+                            Page.nav [round.key()]
+                    if Plugin.userIsAdmin() || Plugin.ownerId()==Plugin.userId()
                         Icon.render
-                            data: (if hasRanked then 'clock2' else 'new')
-                            color: (if hasRanked then '#aaa' else null)
-                            style: { display: 'block' }
-                            size: 34
-
-                    Dom.div !->
-                        Dom.style Flex: 1
-                        Dom.b !->
-                            Dom.style color: (if !hasRanked then Colors.highlight else 'inherit')
-                            Dom.text Util.qToQuestion(q)
-                        Dom.div !->
-                            Dom.text (if hasRanked then tr("Results & new round") else tr("Voting closes & new round")) + ' '
-                            Time.deltaText(Db.shared.get('next'))
+                            data: 'good2'
+                            size: 24
+                            color: '#ba1a6e'
+                            onTap: !-> Server.call 'newRound'
+                            style: {borderLeftWidth: '1px', borderLeftStyle: 'solid', borderLeftColor: '#ccc', padding: '10px 14px', marginLeft: '8px'}
+

                 Event.renderBubble [round.key()], style: marginLeft: '4px'
                 ###
@@ -382,12 +397,12 @@

                 myRanking = Db.personal.get 'rankings', round.key()
                 if myRanking and myRanking[1]
-                    Dom.div !->
+                    Ui.item !->
                         Dom.div !->
                             Dom.style color: '#ba1a6e', textTransform: 'uppercase', fontWeight: 'bold'
                             Dom.text tr("Know Thyself Competition")
-                        Dom.style textAlign: 'center', color: '#aaa', padding: '12px 8px', fontSize: '85%'
-                        Dom.css '.item .b_middle .b_vertical'
+                        Dom.style textAlign: 'center', color: '#aaa', padding: '12px 8px', fontSize: '85%', borderBottomStyle: 'none'
+                        Dom.addClass 'b_vertical'
                         scoring = Util.scoring()
                         score = scoring[Math.abs(myRank-myRanking['self'])]
                         Dom.userText tr("You won %1 point|s by predicting to be ranked '%2'", score, Util.selfRankToText(myRanking['self']))


Style.

Style.

--- client.coffee
+++ client.coffee
@@ -27,7 +27,8 @@
   # This class should never really be created.
   assert false

-# TODO: Find a way to pass arguments to the component without having to introduce one intermediary data context into the data context hierarchy (in fact two data contexts, because we add one more when restoring the original one).
+# TODO: Find a way to pass arguments to the component without having to introduce one intermediary data context into the data context hierarchy.
+#       (In fact two data contexts, because we add one more when restoring the original one.)
 Template.registerHelper 'args', ->
   obj = {}
   # We use custom constructor to know that it is not a real data context.
@@ -37,7 +38,10 @@

 # We make Template.dynamic resolve to the component if component name is specified as a template name, and not
 # to the non-component template which is probably used only for the content. We simply reuse Blaze._getTemplate.
-# TODO: How to pass args? Maybe simply by using Spacebars nested expressions (https://github.com/meteor/meteor/pull/4101)? Template.dynamic template="..." data=(args ...)? But this exposes the fact that args are passed as data context. Maybe we should simply override Template.dynamic and add "args" argument?
+# TODO: How to pass args?
+#       Maybe simply by using Spacebars nested expressions (https://github.com/meteor/meteor/pull/4101)?
+#       Template.dynamic template="..." data=(args ...)? But this exposes the fact that args are passed as data context.
+#       Maybe we should simply override Template.dynamic and add "args" argument?
 # TODO: This can be removed once https://github.com/meteor/meteor/pull/4036 is merged in.
 Template.__dynamicWithDataContext.__helpers.set 'chooseTemplate', (name) ->
   Blaze._getTemplate name, =>


TreeView: change deprecated method w/ the new one

--- src/components/tree/treeviewcontroller.coffee
+++ src/components/tree/treeviewcontroller.coffee
@@ -247,7 +247,7 @@
   removeAllNodes:->

     for own id, listController of @listControllers
-      listController.itemsOrdered.forEach @bound 'removeNodeView'
+      listController.getListItems().forEach @bound 'removeNodeView'
       listController?.getView().destroy()
       delete @listControllers[id]
       delete @listData[id]


groups: activity will now stop being fixed to the top of the screen when showing the read me

--- client/app/Applications/Activity.kdapplication/AppView.coffee
+++ client/app/Applications/Activity.kdapplication/AppView.coffee
@@ -43,7 +43,8 @@
     @_windowDidResize()

   setFixed:->
-    pre = if @getScrollTop() > headerHeight then "set" else "unset"
+    {groupEntryPoint:entryPoint} = KD.config
+    pre = if @getScrollTop() > headerHeight and not entryPoint then "set" else "unset"
     @["#{pre}Class"] "fixed"

   navigateHome:(itemData)->


remove console

remove console

--- client/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -296,7 +296,6 @@
     guestCreate      = new Date KD.whoami().meta.createdAt
     guestCreateTime  = guestCreate.getTime()

-    console.log guestTimeout
     endTime       = new Date(guestCreateTime + guestTimeout*60*1000)
     log endTime
     notification  = new GlobalNotification


command conflicts

command conflicts

--- src/rubygems-search.coffee
+++ src/rubygems-search.coffee
@@ -2,14 +2,14 @@
 #   Use hubot to search rubygems.org for a specific gem.
 #
 # Commands:
-#   hubot gems me <query> - Search rubygems.org for a gem
+#   hubot gem me <query> - Search rubygems.org for a gem
 #
 # Author:
 #   Jon Rohan <jon@jonrohan.me>

 module.exports = (robot) ->
-  robot.respond /gems( me)? (.*)/i, (msg) ->
+  robot.respond /gem( me)? (.*)/i, (msg) ->
     query = msg.match[2]
     robot.http("https://rubygems.org/api/v1/search.json")
       .query({


add session secret as env varx

add session secret as env varx

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -104,7 +104,7 @@
 	# If provided, a sessionSecret is used to sign cookies so that they cannot be
 	# spoofed. This is recommended.
 	security:
-		sessionSecret: "CRYPTO_RANDOM" # This was randomly generated for you
+		sessionSecret: process.env["SHARELATEX_SESSION_SECRET"] or "CRYPTO_RANDOM" # This was randomly generated for you

 	# These credentials are used for authenticating api requests
 	# between services that may need to go over public channels


Object dumper: Now it is possible to use it without header

--- src/elements/ObjectDumper/ObjectDumper.coffee
+++ src/elements/ObjectDumper/ObjectDumper.coffee
@@ -37,7 +37,7 @@
 					label: text: "value"
 				]
 				check: (v) ->
-					v.length == 2
+					v.length == 2 or CUI.util.isFalse(v)
 			do_open:
 				mandatory: true
 				default: false


first step of making switching within conversations faster

--- core/app/backbone/controllers/conversations_controller.coffee
+++ core/app/backbone/controllers/conversations_controller.coffee
@@ -1,26 +1,28 @@
 app = FactlinkApp

 class window.ConversationsController
-  showConversations: ->
+  startAction: ->
     app.closeAllContentRegions()
-
     @main = new TabbedMainRegionLayout();
     app.mainRegion.show(@main)

+    return if @alreadyLoaded
+    @conversations = new Conversations()
+    @alreadyLoaded = true
+
+  showConversations: (with_startup=true)->
+    @startAction() if with_startup
+
     @main.showTitle "Conversations"
-
-    @conversation = new Conversations()
-    @conversation.fetch
-      success: (collection, response) => @renderConversations(collection)
-
-  renderConversations: (conversations) ->
     @main.contentRegion.show(
       new ConversationListView
-        collection: conversations
+        collection: @conversations
     )

-  showMessages: (conversation_id)->
-    app.closeAllContentRegions()
+    @conversations.fetch()
+
+  showMessages: (conversation_id, with_startup=true)->
+    @startAction() if with_startup

     @main = new TabbedMainRegionLayout();
     app.mainRegion.show(@main)
@@ -35,8 +37,8 @@

   renderMessages: (conversation) ->
     messages = new Messages(conversation.get('messages'))
-    @main.contentRegion.show(
-      new MessagesView
+    conversationView =  new MessagesView
         model: conversation
         collection: messages
-    )
+
+    @main.contentRegion.show conversationView


Set Push.enabled and set push sendInterval to 1000ms and sendBatchSize to 10

--- server/lib/cordova.coffee
+++ server/lib/cordova.coffee
@@ -7,6 +7,7 @@
 	Push.debug = RocketChat.settings.get 'Push_debug'

 	if RocketChat.settings.get('Push_enable') is true
+		Push.enabled = true
 		Push.allow
 			send: (userId, notification) ->
 				return Meteor.users.findOne({_id: userId})?.admin is true
@@ -26,3 +27,5 @@
 			sound: true
 			alert: true
 			vibrate: true
+			sendInterval: 1000
+			sendBatchSize: 10


Update harvest.coffee

Update harvest.coffee

fix hubot start harvest mismatching

--- src/scripts/harvest.coffee
+++ src/scripts/harvest.coffee
@@ -244,7 +244,7 @@
     catch error
       msg.reply "Failed to start timer: fatal error: #{error}"

-  robot.respond /start harvest/i, (msg) ->
+  robot.respond /start harvest$/i, (msg) ->
     unless user = check_user(robot, msg)
       return


remove logging

remove logging

--- src/smc-util/client.coffee
+++ src/smc-util/client.coffee
@@ -1983,7 +1983,7 @@
             standby : false        # if true and use HTTP post, then will use standby server (so must be read only)
             timeout : 30
             cb      : undefined
-        console.log("QUERY ", JSON.stringify(opts.query))
+        # console.log("QUERY ", JSON.stringify(opts.query))
         if opts.options? and not misc.is_array(opts.options)
             throw Error("options must be an array")


Better link desc.

Better link desc.

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -194,7 +194,7 @@
                 {@render_recent_usage_stats()}
                 <Icon name='line-chart' fixedWidth />{' '}

-                More data...
+                Historical CoCalc Usage Statistics...

                 {@render_historical_metrics()}


implement clicking on items in View

implement clicking on items in View

--- lib/view.coffee
+++ lib/view.coffee
@@ -1,3 +1,6 @@
+curry = (fn, args...) ->
+  -> fn.apply(this, args.concat(Array.slice(arguments)))
+
 # Manages the frame content of the toolbar
 class window.View
   # Current offset of moving ticker items
@@ -8,7 +11,7 @@
   # @param [Element] container The container element where feed items are placed
   constructor : (@container) ->
     window.addEventListener('message', @onMessageReceived, false)
-    window.parent.postMessage({ command: 'READY' }, '*');
+    window.parent.postMessage({ command: 'READY' }, '*')
     @moveTimer = setInterval(@move, 1000);

   # Callback for communication with the @see ViewManager
@@ -34,12 +37,17 @@
     for item in items
       index = old_nodes.findIndex((node) => node.getAttribute('data-ticker-id') == item.id)
       if index == -1
-        @container.appendChild(@createEntry(item)) # TODO: order
+        entry = @createEntry(item)
+        entry.addEventListener("click", curry(@onItemClicked, item))
+        @container.appendChild(entry) # TODO: order
       else
         @updateNode(old_nodes[index], item)
         old_nodes[index..index] = [] # remove from old nodes

     node.remove() for node in old_nodes
+
+  onItemClicked : (item) =>
+    window.parent.postMessage({ command: 'NOTIFY_CLICK', data: item }, '*')

   # Helper method to update a feed entry with new information
   # @private


allow to dispatch to target after capture listeners

--- source/lib/evented.coffee
+++ source/lib/evented.coffee
@@ -47,6 +47,8 @@
               child.broadcastEvent event
             else break

+        if event.target is this then event.phase = 2
+
         if event.phase is 2 then @dispatchEvent event

         if event.source is this


Show proper error message when saving priority fails

--- app/assets/javascripts/LabelManager.js.coffee
+++ app/assets/javascripts/LabelManager.js.coffee
@@ -71,11 +71,8 @@
             data:
               label_ids: @getSortedLabelsIds()

-    xhr.done ->
-      console.log 'done'
-
     xhr.fail ->
-      console.log 'fail'
+      new Flash('Unable to update label prioritization at this time' , 'alert')

   getSortedLabelsIds: ->
     sortedIds = []


Added background loading flag to Faye update.

--- app/assets/javascripts/faye_manager.js.coffee
+++ app/assets/javascripts/faye_manager.js.coffee
@@ -8,5 +8,6 @@
   subscribe_to_date: (date) ->
     @current_subscription?.cancel()
     @current_subscription = @client.subscribe("/messages/date/#{date}", (data) ->
+      window.CalendarManager.background_loading = true
       window.CalendarManager.populate_calendar(data)
     )


fixed download button in tracing UI; fixes #395

--- app/assets/javascripts/oxalis/view/gui.coffee
+++ app/assets/javascripts/oxalis/view/gui.coffee
@@ -182,11 +182,12 @@
         if confirm("Are you sure you want to permanently finish this tracing?")
           window.location.href = event.target.href

+
     $("#trace-download-button").click (event) =>

       event.preventDefault()
       @saveNow().done =>
-          window.location.href = event.target.href
+          window.location.href = event.currentTarget.href

     $("#trace-save-button").click (event) =>


part 2

part 2

--- app/assets/javascripts/oxalis/view/gui.coffee
+++ app/assets/javascripts/oxalis/view/gui.coffee
@@ -66,12 +66,6 @@
     @scaleController = @addSlider(@fViewportcontrols, @user.getSettings(), "scale", constants.MIN_SCALE,
       constants.MAX_SCALE, 0.1, "Viewport Scale")
     @addCheckbox(@fViewportcontrols, @user.getSettings(), "dynamicSpaceDirection", "d/f-Switching")
-
-    @folders.push( fControls = @gui.addFolder("Controls") )
-    @addCheckbox(fControls, @user.getSettings(), "inverseX", "Inverse X")
-    @addCheckbox(fControls, @user.getSettings(), "inverseY", "Inverse Y")
-    @addSlider(fControls, @user.getSettings(), "keyboardDelay",
-      0, 500, 10, "Keyboard delay (ms)" )

     @folders.push( @fFlightcontrols = @gui.addFolder("Flightoptions") )
     @addSlider(@fFlightcontrols, @user.getSettings(), "mouseRotateValue",
@@ -109,6 +103,12 @@
     @addFunction(@fColors, @settingsGeneral, "resetColorSettings",
       "Reset")

+    @folders.push( fControls = @gui.addFolder("Controls") )
+    @addCheckbox(fControls, @user.getSettings(), "inverseX", "Inverse X")
+    @addCheckbox(fControls, @user.getSettings(), "inverseY", "Inverse Y")
+    @addSlider(fControls, @user.getSettings(), "keyboardDelay",
+      0, 500, 10, "Keyboard delay (ms)" )
+
     @folders.push( @fView = @gui.addFolder("View") )
     bbController = @fView.add(@settingsGeneral, "boundingBox").name("Bounding Box").onChange(@setBoundingBox)
     @addTooltip(bbController, "Format: minX, minY, minZ, maxX, maxY, maxZ")


Properly handle deleted pages when loading reviews

--- app/assets/javascripts/reviewEditor.js.coffee
+++ app/assets/javascripts/reviewEditor.js.coffee
@@ -367,8 +367,10 @@
     if data
       for page_data in data['pages']
         page = @pagesById[page_data['id']]
-        page.load_review(page_data) if page
-
+        continue unless page
+
+        page.load_review(page_data)
+
         # Subscribe to grade changes
         page.grade.subscribe(=> @saved = false)


commencement des deadlocks en Z

commencement des deadlocks en Z

--- app/assets/jax/models/deadlock_core.js.coffee
+++ app/assets/jax/models/deadlock_core.js.coffee
@@ -161,6 +161,7 @@
       down_right: { m:box_position.m+1, n:box_position.n+1 }

     cell =
+      box:   level.read_pos(pos.box.m,   pos.box.n)
       left:  level.read_pos(pos.left.m,  pos.left.n)
       right: level.read_pos(pos.right.m, pos.right.n)
       down:  level.read_pos(pos.down.m,  pos.down.n)
@@ -172,24 +173,24 @@
       down_right: level.read_pos(pos.down_right.m, pos.down_right.n)

     # up-right square
-    deadlocked_positions = @deadlocked_square(cell.up, cell.up_right, cell.right,
-                                              pos.up,  pos.up_right,  pos.right,
-                                              pos.box, deadlocked_positions)
+    deadlocked_positions = @deadlocked_square(cell.box, cell.up, cell.up_right, cell.right,
+                                              pos.box,  pos.up,  pos.up_right,  pos.right,
+                                              deadlocked_positions)

     # up-left square
-    deadlocked_positions = @deadlocked_square(cell.up, cell.up_left, cell.left,
-                                              pos.up,  pos.up_left,  pos.left,
-                                              pos.box, deadlocked_positions)
+    deadlocked_positions = @deadlocked_square(cell.box, cell.up, cell.up_left, cell.left,
+                                              pos.box,  pos.up,  pos.up_left,  pos.left,
+                                              deadlocked_positions)

     # down-right square
-    deadlocked_positions = @deadlocked_square(cell.down, cell.down_right, cell.right,
-                                              pos.down,  pos.down_right,  pos.right,
-                                              pos.box,   deadlocked_positions)
+    deadlocked_positions = @deadlocked_square(cell.box, cell.down, cell.down_right, cell.right,
+                                              pos.box,  pos.down,  pos.down_right,  pos.right,
+                                              deadlocked_positions)

     # down-left square
-    deadlocked_positions = @deadlocked_square(cell.down, cell.down_left, cell.left,
-                                              pos.down,  pos.down_left,  pos.left,
-                                              pos.box,   deadlocked_positions)
+    deadlocked_positions = @deadlocked_square(cell.box, cell.down, cell.down_left, cell.left,
+                                              pos.box,  pos.down,  pos.down_left,  pos.left,
+                                              deadlocked_positions)

     return deadlocked_positions

@@ -203,12 +204,15 @@
     #  #   #   #   #   ######   ######
     #  #####   #####

-#    // fig 1, upper box
-#    if(posLeft == -1 && posDownRight == -1
-#        && boxesZone->readPos(posDown) == 1)
-#      if(goalBox == 0 || goalDown == 0)
-#        return true;
-#
+    # fig 1, upper box
+    if cell.left == '#' and cell.down_right == '#'
+      if (cell.box == '$' || cell.box == '*') and (cell.down == '$' || cell.down == '*')
+        if not (cell.box == '*' and cell.down == '*')
+          if not @position_in_array(cell.box, deadlocked_positions)
+            deadlocked_positions.push({ m:cell.box.m, n:cell.box.n })
+          if not @position_in_array()
+            deadlocked_positions.push({ m:cell.down.m, n:cell.down.n })
+
 #    // fig 1, lower box
 #    if(posRight == -1 && posUpLeft == -1
 #        && boxesZone->readPos(posUp) == 1)
@@ -256,24 +260,20 @@

   ###
     Test a specific deadlocked square and return the deadlocked positions
-    @param box_pos position of the box we want to test
+    @param cell_box letter of the box we want to test ('$' or '*')
+    @param pos_box position of the box we want to test
     @param cell1, cell2, cell3 values of cells that made a square with the current box
     @param pos1, pos2, pos3 {m,n} positions of the corresponding cells
     @param deadlocked_positions array of deadlocked boxes positions
     @return completed array of deadlocked_positions (no duplicates positions)
   ###
-  deadlocked_square: (cell1, cell2, cell3, pos1, pos2, pos3, box_pos, deadlocked_positions) ->
-    console.log("#{cell1} - #{cell2} - #{cell3}")
-    console.log(pos1)
-    console.log(pos2)
-    console.log(pos3)
-    console.log(box_pos)
-
-    if (cell1 == '#' or cell1 == '$') and
+  deadlocked_square: (cell_box, cell1, cell2, cell3, pos_box, pos1, pos2, pos3, deadlocked_positions) ->
+    if cell_box == '$' and
+       (cell1 == '#' or cell1 == '$') and
        (cell2 == '#' or cell2 == '$') and
        (cell3 == '#' or cell3 == '$')
-      if not @position_in_array(box_pos, deadlocked_positions)
-        deadlocked_positions.push({ m:box_pos.m, n:box_pos.n })
+      if not @position_in_array(pos_box, deadlocked_positions)
+        deadlocked_positions.push({ m:pos_box.m, n:pos_box.n })
       if not @position_in_array(pos1, deadlocked_positions) and cell1 == '$'
         deadlocked_positions.push({ m:pos1.m, n:pos1.n })
       if not @position_in_array(pos2, deadlocked_positions) and cell2 == '$'


Update conversation.coffee

Update conversation.coffee

--- scripts/conversation.coffee
+++ scripts/conversation.coffee
@@ -25,8 +25,8 @@
   "まだ仕事ありますか？"
   "いつでも仕事こなしますよ"
   "お仕事ですか？"
-  "What do you need?"
-  "Say the word"
+  "何が必要ですか？"
+  "命令をください"
   "Aye, my lord"
   "Locked and loaded"
   "Aye, sir?"


Added 大好き :sushi:

Added 大好き :sushi:

--- scripts/idobata-talk.coffee
+++ scripts/idobata-talk.coffee
@@ -9,7 +9,9 @@
       "そんなこと言わないでよ……心配…するじゃない…"
     ]
     msg.send msg.random words
-
+
+
+
   robot.hear /帰りたい/i, (msg) ->
     words = [
       "ほら、あと3分待ってあげるからとっとと支度しなさい！"
@@ -97,6 +99,14 @@
     ]
     msg.send msg.random words

+  robot.repond /大好き$/i, (msg) ->
+    words = [
+      "アホなの？"
+      "なんなの？バカなの？死ぬの？"
+      "私もよ…///"
+    ]
+    msg.send msg.random words
+
   robot.respond /かわいい|カワイイ|可愛い|きれい|キレイ|綺麗|すてき|ステキ|素敵/i, (msg) ->
     msg.send msg.random [
       "てっ、照れてないもんっ。"


Stepped okSubmits date

Stepped okSubmits date

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -56,7 +56,7 @@
 export default dashboard = () ->
     queries =
         # remember that months start from 0
-        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 8, 17)}},
+        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 8, 24)}},
         wa: {solved: 0, ok: 0, ignored: 0, attempts: {$gt: 0}},
         ig: {ignored: 1},
         ac: {solved: 1}


Fix leave room

Fix leave room

--- client/views/app/sideNav/chatRoomItem.coffee
+++ client/views/app/sideNav/chatRoomItem.coffee
@@ -52,7 +52,7 @@
 		e.preventDefault()

 		if (FlowRouter.getRouteName() is 'room' and FlowRouter.getParam('_id') is this.rid)
-			FlowRouter.go 'index'
+			FlowRouter.go 'home'

 		Meteor.call 'hideRoom', this.rid

@@ -61,7 +61,7 @@
 		e.preventDefault()

 		if (FlowRouter.getRouteName() is 'room' and FlowRouter.getParam('_id') is this.rid)
-			FlowRouter.go 'index'
+			FlowRouter.go 'home'

 		RoomManager.close this.rid


Fix rendering of SFUCopyrightComplianceNotice in new course form

--- gems/plugins/sfu_course_form/app/coffeescripts/bundles/course_request_form.coffee
+++ gems/plugins/sfu_course_form/app/coffeescripts/bundles/course_request_form.coffee
@@ -261,7 +261,7 @@

     ReactDOM.render(
       React.createElement(
-        SFUCopyrightComplianceNotice,
+        SFUCopyrightComplianceNotice.default,
         { className: 'sfu-ic-wizard-box__message-text__copyright_compliance' }
       ),
       document.getElementsByClassName('copyright')[0]


Ordered List component ignore title

Ordered List component ignore title

--- src/js/components/OrderedListComponent.coffee
+++ src/js/components/OrderedListComponent.coffee
@@ -8,7 +8,6 @@
   # Check README to find out more about how to make components
   template: '

-
{{ b.title }}

       <faber-render data-faber-render-block="b"/>

'


add default value for empty params

add default value for empty params

--- coffeescript/002-using-modules/csmain.coffee
+++ coffeescript/002-using-modules/csmain.coffee
@@ -6,7 +6,7 @@
   domReady( ->
       ps = url.params()
       ctx =
-          keys: _.keys(ps),
+          keys: _.keys(ps) ? []
           vals: ps
       list = _.template(
         "
<% _.each(keys, function(key) { %>
<%= vals[key] %>
 <% }); %>
",


Add unit tests for AsciiDoc-style headings.

--- spec/asciidoc-spec.coffee
+++ spec/asciidoc-spec.coffee
@@ -23,3 +23,28 @@
     expect(tokens[0]).toEqual value: "this is ", scopes: ["source.asciidoc"]
     expect(tokens[1]).toEqual value: "_italic_", scopes: ["source.asciidoc", "markup.italic.asciidoc"]
     expect(tokens[2]).toEqual value: " text", scopes: ["source.asciidoc"]
+
+  it "tokenizes AsciiDoc-style headings", ->
+    {tokens} = grammar.tokenizeLine("= Heading 0")
+    expect(tokens[0]).toEqual value: "= ", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+    expect(tokens[1]).toEqual value: "Heading 0", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+
+    {tokens} = grammar.tokenizeLine("== Heading 1")
+    expect(tokens[0]).toEqual value: "== ", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+    expect(tokens[1]).toEqual value: "Heading 1", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+
+    {tokens} = grammar.tokenizeLine("=== Heading 2")
+    expect(tokens[0]).toEqual value: "=== ", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+    expect(tokens[1]).toEqual value: "Heading 2", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+
+    {tokens} = grammar.tokenizeLine("==== Heading 3")
+    expect(tokens[0]).toEqual value: "==== ", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+    expect(tokens[1]).toEqual value: "Heading 3", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+
+    {tokens} = grammar.tokenizeLine("===== Heading 4")
+    expect(tokens[0]).toEqual value: "===== ", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+    expect(tokens[1]).toEqual value: "Heading 4", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+
+    {tokens} = grammar.tokenizeLine("====== Heading 5")
+    expect(tokens[0]).toEqual value: "====== ", scopes: ["source.asciidoc", "markup.heading.asciidoc"]
+    expect(tokens[1]).toEqual value: "Heading 5", scopes: ["source.asciidoc", "markup.heading.asciidoc"]


Use promises with activatePackage

Use promises with activatePackage

--- spec/autosave-spec.coffee
+++ spec/autosave-spec.coffee
@@ -6,16 +6,18 @@

   beforeEach ->
     atom.workspaceView = new WorkspaceView()
-    pack = atom.packages.activatePackage("autosave", immediate: true)
+    waitsForPromise ->
+      atom.packages.activatePackage("autosave")

-    atom.workspaceView.attachToDom()
-    initialActiveItem = atom.workspaceView.openSync('sample.js')
-    otherItem1 = atom.project.openSync('sample.coffee')
-    otherItem2 = otherItem1.copy()
+    runs ->
+      atom.workspaceView.attachToDom()
+      initialActiveItem = atom.workspaceView.openSync('sample.js')
+      otherItem1 = atom.project.openSync('sample.coffee')
+      otherItem2 = otherItem1.copy()

-    spyOn(initialActiveItem, 'save').andCallThrough()
-    spyOn(otherItem1, 'save').andCallThrough()
-    spyOn(otherItem2, 'save').andCallThrough()
+      spyOn(initialActiveItem, 'save').andCallThrough()
+      spyOn(otherItem1, 'save').andCallThrough()
+      spyOn(otherItem2, 'save').andCallThrough()

   describe "when a pane loses focus", ->
     it "saves the item if autosave is enabled and the item has a uri", ->


Added prepareForSnapshot to device

Added prepareForSnapshot to device

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -711,6 +711,13 @@

 	toInspect: ->
 		return "<Device '#{@deviceType}' #{@screenSize.width}x#{@screenSize.height}>"
+
+	prepareForSnapshot: ->
+		@hideBezel = true
+		@setDeviceScale(-1, true)
+		@screenMask.visible = false
+		@screenBackground.visible = false
+		@background.backgroundColor = null

 ###########################################################################


improve code organization

improve code organization

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -58,9 +58,6 @@
 		@deviceType = defaults.deviceType

 		_.extend(@, _.defaults(options, defaults))
-
-		if Utils.isMobile()
-			window.addEventListener("resize", @_orientationChange, true)

 	_setup: ->

@@ -101,6 +98,7 @@
 		@content.originY = 0

 		Framer.CurrentContext.domEventManager.wrap(window).addEventListener("resize", @_update) unless Utils.isMobile()
+		Framer.CurrentContext.domEventManager.wrap(window).addEventListener("resize", @_orientationChange) if Utils.isMobile()

 		# This avoids rubber banding on mobile
 		for layer in [@background, @phone, @viewport, @content, @screen]


Fix device settings

Fix device settings

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -52,10 +52,13 @@

 	constructor: (options={}) ->

-		# We still listen to DeviceView defaults here for compat
-		defaults = Defaults.getDefaults("DeviceView", options)
-		defaults = _.extend(defaults, Defaults.getDefaults("DeviceComponent", options))
-
+		defaults = Defaults.getDefaults("DeviceComponent", options)
+
+		# If we have defaults for DeviceView, we are likely using an older version of
+		# Framer Studio. It's best to default to those then for now.
+		if Framer.Defaults.hasOwnProperty("DeviceView")
+			defaults = _.extend(defaults, Framer.Defaults.DeviceView)
+
 		@_setup()

 		@animationOptions = defaults.animationOptions
@@ -64,7 +67,7 @@
 		_.extend(@, _.defaults(options, defaults))

 	_setup: ->
-
+
 		if @_setupDone
 			return

@@ -109,7 +112,7 @@

 	_update: =>

-		# # Todo: pixel align at zoom level 1, 0.5
+		# Todo: pixel align at zoom level 1, 0.5

 		contentScaleFactor = @contentScale
 		contentScaleFactor = 1 if contentScaleFactor > 1


project updated

project updated

--- source/module/seeker.coffee
+++ source/module/seeker.coffee
@@ -163,7 +163,7 @@
       'AlloyTeam'
       'AppInn'
       'iPlaySoft'
-      'Ryf'
+      #'Ryf'
       'waitSun'
       'williamLong'
       'Zxx'
@@ -238,8 +238,12 @@
       else throw new Error "invalid task name <#{name}>"

     $.info.pause 'seeker.task'
-    list = yield @getList option
+    list = try yield @getList option catch err then err
     $.info.resume 'seeker.task'
+
+    if _.isError list
+      $.info $.info 'error', "#{option.title}: #{list}"
+      list = []

     # return
     title = option.title


adds missing domain event for failed logins

--- source/server/events.coffee
+++ source/server/events.coffee
@@ -13,3 +13,10 @@
     timestamp: Date
     via: String
   }
+
+  UserLoginFailed: {
+    sourceId: Guid
+    timestamp: Date
+    via: String
+    error: String
+  }


SocialWorker: Tests: extend groupData from opts.groupData

--- workers/social/testhelper/index.coffee
+++ workers/social/testhelper/index.coffee
@@ -215,11 +215,12 @@
     return callback group  if group or not opts.createGroup

     # no group found, let's create one
-    groupData         =
+    groupData = _.extend
       slug           : slug
       title          : slug
       visibility     : 'visible'
       allowedDomains : [ 'koding.com' ]
+    , opts.groupData

     account = client?.connection?.delegate
     JGroup.create client, groupData, account, (err, group) ->


ListViewColumn

ListViewColumn

- Allow String for "element", turns out some users need that.

--- src/elements/ListView/ListViewColumn.coffee
+++ src/elements/ListView/ListViewColumn.coffee
@@ -20,7 +20,7 @@
 				check: "Integer"
 			element:
 				check: (v) ->
-					isContent(v)
+					isContent(v) or isString(v)
 			onSetElement:
 				check: Function


ECS convenience PreparedSearcher.singleEntity

--- src/javascript/lib/ecs/entity_search.coffee
+++ src/javascript/lib/ecs/entity_search.coffee
@@ -1,6 +1,7 @@
 C = require '../../modules/block_maze/components' # FIXME !!! doh.  Shouldn't have implemented this class based on assumed access to the type domain.
 # (^^^ is really only used by expandFilter which couldb be parameterized by the Domain instance,
 #  and EntitySearchFilter.toString() is only for debug nice nice.)
+CompSet = require './comp_set'

 class EntitySearchFilter
   constructor: (@compType,@matchers=null) ->
@@ -64,7 +65,15 @@

   run: (estore,fn) ->
     doSearch estore,@query,fn
+
   #TODO runParam
+
+  singleEntity: (estore) ->
+    ent = null
+    @run estore, (r) ->
+      ent = r.entity
+    ent
+

 class PreparedCompoundSearcher
   constructor: (@compoundQuery) ->


:new: Add some validator specs about class attribute

--- spec/validate-spec.coffee
+++ spec/validate-spec.coffee
@@ -24,37 +24,45 @@
       .toThrow("Expected messages to be array, provided: boolean")
     it 'throws if type field is not present', ->
       expect ->
-        validate.messages([{}])
+        validate.messages([{}], {name: ''})
       .toThrow()
     it "throws if type field is invalid", ->
       expect ->
-        validate.messages([{type: 1}])
+        validate.messages([{type: 1}], {name: ''})
       .toThrow()
     it "throws if there's no html/text field on message", ->
       expect ->
-        validate.messages([{type: 'Error'}])
+        validate.messages([{type: 'Error'}], {name: ''})
       .toThrow()
     it "throws if html/text is invalid", ->
       expect ->
-        validate.messages([{type: 'Error', html: 1}])
+        validate.messages([{type: 'Error', html: 1}], {name: ''})
       .toThrow()
       expect ->
-        validate.messages([{type: 'Error', text: 1}])
+        validate.messages([{type: 'Error', text: 1}], {name: ''})
       .toThrow()
       expect ->
-        validate.messages([{type: 'Error', html: false}])
+        validate.messages([{type: 'Error', html: false}], {name: ''})
       .toThrow()
       expect ->
-        validate.messages([{type: 'Error', text: false}])
+        validate.messages([{type: 'Error', text: false}], {name: ''})
       .toThrow()
       expect ->
-        validate.messages([{type: 'Error', html: []}])
+        validate.messages([{type: 'Error', html: []}], {name: ''})
       .toThrow()
       expect ->
-        validate.messages([{type: 'Error', text: []}])
+        validate.messages([{type: 'Error', text: []}], {name: ''})
       .toThrow()
     it 'throws if trace is invalid', ->
       expect ->
         validate.messages([{type: 'Error', html: 'a', trace: 1}], {name: ''})
       .toThrow()
       validate.messages([{type: 'Error', html: 'a', trace: false}], {name: ''})
+    it 'throws if class is invalid', ->
+      expect ->
+        validate.messages([{type: 'Error', text: 'Well', class: 1}], {name: ''})
+      .toThrow()
+      expect ->
+        validate.messages([{type: 'Error', text: 'Well', class: []}], {name: ''})
+      .toThrow()
+      validate.messages([{type: 'Error', text: 'Well', class: 'error'}], {name: ''})


:new: Add specs for change

:new: Add specs for change

--- spec/validate-spec.coffee
+++ spec/validate-spec.coffee
@@ -110,3 +110,4 @@
       .toThrow()
       validate.messages([{type: 'Error', text: 'Well', class: 'error', filePath: '/'}], {name: ''})
       validate.messages([{type: 'Error', html: 'Well', class: 'error', filePath: '/'}], {name: ''})
+      validate.messages([{type: 'Error', html: document.createElement('div'), class: 'error', filePath: '/'}], {name: ''})


Fixed argument processing in run_specs.coffee

--- run_specs.coffee
+++ run_specs.coffee
@@ -10,7 +10,7 @@
 showColors = true
 pattern = "_spec.coffee$"

-process.argv.forEach (arg) ->
+process.argv[2..].forEach (arg) ->
   switch arg
     when '--color'   then showColors = true
     when '--noColor' then showColors = false


tests: Remove some unecessary includes

tests: Remove some unecessary includes

--- spec/GetFeaturesYAPE.coffee
+++ spec/GetFeaturesYAPE.coffee
@@ -1,8 +1,6 @@
 noflo = require 'noflo'
 unless noflo.isBrowser()
   chai = require 'chai' unless chai
-  fs = require 'fs'
-  Canvas = require 'canvas'
   GetFeaturesYAPE = require '../components/GetFeaturesYAPE.coffee'
 else
   GetFeaturesYAPE = require 'noflo-canvas/components/GetFeaturesYAPE.js'


Remove some logging

Remove some logging

--- src/ubret/base_tool.coffee
+++ src/ubret/base_tool.coffee
@@ -51,7 +51,6 @@
     # @start() if @initialized # temp

   receiveSetting: (key, value) =>
-    console.log key, value
     @[key] = value
     @start()


Add test

Add test

--- spec/whitespace-spec.coffee
+++ spec/whitespace-spec.coffee
@@ -35,7 +35,7 @@

     it "strips trailing whitespace before an editor saves a buffer", ->
       # works for buffers that are already open when package is initialized
-      editor.insertText("foo   \nbar\t   \n\nbaz\n")
+      editor.insertText("foo   \nbar\t   \n\t \nbaz\n")
       editor.save()
       expect(editor.getText()).toBe "foo\nbar\n\nbaz\n"

@@ -81,6 +81,30 @@
       editor.save()
       expect(editor.getText()).toBe "1\n2\n3\n"

+  describe "when 'whitespace.ignoreWhitespaceOnlyLines' is false", ->
+    beforeEach ->
+      atom.config.set("whitespace.ignoreWhitespaceOnlyLines", false)
+
+    it "removes the wthiespace from all lines, including the whitespace-only lines", ->
+      editor.insertText "1  \n2\t  \n\t \n3\n"
+
+      #move cursor to bottom for preventing effect of whitespace.ignoreWhitespaceOnCurrentLine
+      editor.moveCursorToBottom()
+      editor.save()
+      expect(editor.getText()).toBe "1\n2\n\n3\n"
+
+  describe "when 'whitespace.ignoreWhitespaceOnlyLines' is true", ->
+    beforeEach ->
+      atom.config.set("whitespace.ignoreWhitespaceOnlyLines", true)
+
+    it "removes the wthiespace from all lines, excluding the whitespace-only lines", ->
+      editor.insertText "1  \n2\t  \n\t \n3\n"
+
+      #move cursor to bottom for preventing effect of whitespace.ignoreWhitespaceOnCurrentLine
+      editor.moveCursorToBottom()
+      editor.save()
+      expect(editor.getText()).toBe "1\n2\n\t \n3\n"
+
   describe "when 'whitespace.ensureSingleTrailingNewline' is true", ->
     beforeEach ->
       atom.config.set("whitespace.ensureSingleTrailingNewline", true)
@@ -167,3 +191,10 @@
       editor.setCursorBufferPosition([0,4])
       editor.save()
       expect(editor.getText()).toBe "foo \nline break!\n"
+
+    it "respects 'whitespace.ignoreWhitespaceOnlyLines' setting", ->
+      atom.config.set("whitespace.ignoreWhitespaceOnlyLines", true)
+
+      editor.insertText "\t \nline break!"
+      editor.save()
+      expect(editor.getText()).toBe "\t \nline break!\n"


Use wrap-guide config prefix in specs

Use wrap-guide config prefix in specs

--- spec/wrap-guide-spec.coffee
+++ spec/wrap-guide-spec.coffee
@@ -41,21 +41,21 @@

   describe "using a custom config column", ->
     it "places the wrap guide at the custom column", ->
-      atom.config.set('wrapGuide.columns', [{pattern: '\.js$', column: 20}])
+      atom.config.set('wrap-guide.columns', [{pattern: '\.js$', column: 20}])
       wrapGuide.updateGuide()
       width = editorView.charWidth * 20
       expect(width).toBeGreaterThan(0)
       expect(wrapGuide.position().left).toBe(width)

     it "uses the default column when no custom column matches the path", ->
-      atom.config.set('wrapGuide.columns', [{pattern: '\.jsp$', column: '100'}])
+      atom.config.set('wrap-guide.columns', [{pattern: '\.jsp$', column: '100'}])
       wrapGuide.updateGuide()
       width = editorView.charWidth * wrapGuide.getDefaultColumn()
       expect(width).toBeGreaterThan(0)
       expect(wrapGuide.position().left).toBe(width)

     it "hides the guide when the config column is less than 1", ->
-      atom.config.set('wrapGuide.columns', [{pattern: 'sample\.js$', column: -1}])
+      atom.config.set('wrap-guide.columns', [{pattern: 'sample\.js$', column: -1}])
       wrapGuide.updateGuide()
       expect(wrapGuide).toBeHidden()


Revert "Don't use session to store pageLimit, breaks IR 0.7.1. Don't need it anyway."

This reverts commit c224d48ffba5e07c5811efc98fb8865f3773f5bb.

--- router.coffee
+++ router.coffee
@@ -31,8 +31,11 @@
       if Blog.settings.blogIndexTemplate
         @template = Blog.settings.blogIndexTemplate

+      if not Session.get('postLimit') and Blog.settings.pageSize
+        Session.set 'postLimit', Blog.settings.pageSize
+
     waitOn: -> [
-      Meteor.subscribe 'posts', Blog.settings.pageSize
+      Meteor.subscribe 'posts', Session.get('postLimit')
       Meteor.subscribe 'authors'
     ]

@@ -56,6 +59,10 @@
     onBeforeAction: ->
       if Blog.settings.blogIndexTemplate
         @template = Blog.settings.blogIndexTemplate
+
+      # Set up our own 'waitOn' here since IR does not atually wait on 'waitOn'
+      # (see https://github.com/EventedMind/iron-router/issues/265).
+      @subscribe('taggedPosts', @params.tag).wait()

     waitOn: -> [
       Meteor.subscribe 'taggedPosts', @params.tag


Assign unique key to each stacked bar target

Ensures that positions are correctly assigned when the same query is
used to position two target lines (one for the present, one for the
future).

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -153,7 +153,9 @@

       # move the target lines
       @svg.selectAll('rect.target_line')
-        .data(@model.target_series(), (d) -> d.get 'gquery_key')
+        .data(@model.target_series(), (d) ->
+          "#{ d.get('gquery_key') }_#{ d.get('target_line_position') }"
+        )
         .transition()
         .attr 'y', (d) =>
           value = if d.get('target_line_position') is '1'


bugfixes in sendUnderstaffed (see #113)

bugfixes in sendUnderstaffed (see #113)

--- imports/api/mailer/mails/sendUnderstaffed.coffee
+++ imports/api/mailer/mails/sendUnderstaffed.coffee
@@ -13,7 +13,8 @@
 	for team in shift.teams when team._id = teamId
 		shiftData.teams[0] = team

-		tlNeeded = false if team.participants.filter((p) -> p.teamleader).length > 1
+		tlNeeded = false if team.participants.filter((p) -> p.teamleader).length >= 1
+		tlNeeded = false if team.pending.filter((p) -> p.teamleader).length >= 1

 	if tlNeeded
 		users = Roles.getUsersInRole Permissions.teamleader, shift.tagId, fields: profile: 1


Notify when using an unsupported browser

--- app/assets/javascripts/modules/banner.coffee
+++ app/assets/javascripts/modules/banner.coffee
@@ -51,6 +51,19 @@
     preferences.dismissedEvents(pruned)
     null

+  # DELETE BEFORE 1.0 RELEASE
+  $(document).ready ->
+    isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
+    isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
+
+    if !isChrome && !isSafari
+      showBanner(
+        'unsupportedbrowser',
+        'Your browser is not yet supported',
+        'The Earthdata Search Alpha Preview is designed to work best in Chrome and Safari browsers.')
+  # END DELETE
+
+
   $(document).on 'click', '.banner-show-events', (e) ->
     e.preventDefault()
     banners = []


Added newline

Added newline

--- core/app/backbone/models/conversation.coffee
+++ core/app/backbone/models/conversation.coffee
@@ -1,2 +1,2 @@
 class window.Conversation extends Backbone.Model
-  urlRoot: '/m'+  urlRoot: '/m'


also toggle menu on page click when menu is open

--- public/coffeescript/base/directives/MenuToggle.coffee
+++ public/coffeescript/base/directives/MenuToggle.coffee
@@ -11,10 +11,16 @@
       link: (scope, el, attrs) ->

         $body = angular.element(document).find('body')
+        $page = angular.element(document).find('.page')

         el.bind 'click', ->
           $body.toggleClass('open-menu')

+        $page.bind 'click', ->
+          if $body.hasClass 'open-menu'
+            $body.toggleClass('open-menu')
+
+
     return directive

 module.exports = menuToggle


Allow our alert id to be in milliseconds as it lowers the chance of duplicates

--- src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.flash.coffee
+++ src/SumoCoders/FrameworkCoreBundle/Resources/assets/coffee/framework.flash.coffee
@@ -1,7 +1,7 @@
 class Flash

   add: (message, type) ->
-    alertId = Math.floor(Date.now() / 1000);
+    alertId = Date.now();

     $('body').prepend(
       '<div class="alert alert-' + type + ' alert-dismissible notification" role="alert" data-alert-id="' + alertId + '">' +


Fix #1039: Show us.is_closed instead us.status.is_closed

--- app/coffee/modules/userstories/detail.coffee
+++ app/coffee/modules/userstories/detail.coffee
@@ -199,7 +199,7 @@
     template = _.template("""

-            <% if (status.is_closed) { %>
+            <% if (is_closed) { %>
             Closed
             <% } else { %>
             Open
@@ -304,6 +304,7 @@
                 owner: owner
                 date: date
                 editable: editable
+                is_closed: us.is_closed
                 status: status
                 totalPoints: us.total_points
                 rolePoints: rolePoints


reverting tp #1628 to prevent critical tp #1655

--- jsapp/xlform_model_view/model.survey.coffee
+++ jsapp/xlform_model_view/model.survey.coffee
@@ -225,13 +225,13 @@

   # Settings (assigned to each $survey.Survey instance)
   class Settings extends $base.BaseModel
-    validation:
-      form_title:
-        required: true
-        invalidChars: '`'
-      form_id:
-        required: true
-        invalidChars: '`'
+    # validation:
+    #   form_title:
+    #     required: true
+    #     invalidChars: '`'
+    #   form_id:
+    #     required: true
+    #     invalidChars: '`'
     defaults:
       form_title: "New form"
       form_id: "new_form"


JNewStatusUpdate: add feedType if it is set

--- workers/social/lib/social/models/messages/newstatusupdate/index.coffee
+++ workers/social/lib/social/models/messages/newstatusupdate/index.coffee
@@ -230,7 +230,7 @@
     showExemptComments = @checkForTrollMode delegate, options
     @getCurrentGroup client, (err, group)=>
       if err then return callback err
-      {to, searchText} = options
+      {to, searchText, feedType} = options
       to = if to then new Date(to)  else new Date()
       selector =
         'meta.createdAt' :
@@ -250,7 +250,7 @@

         options.sort = 'meta.createdAt' : -1
         options.limit ?= 20
-        selector.feedType = options.feedType
+        selector.feedType = feedType if feedType

         @some selector, options, (err, data)=>
           return callback err if err


OX-355 added [data-ajax] for forms

OX-355 added [data-ajax] for forms

--- app/assets/javascripts/main/enhancements.coffee
+++ app/assets/javascripts/main/enhancements.coffee
@@ -14,11 +14,11 @@
     e.preventDefault()

-  $(document).on "click", "a[data-ajax]", (event) ->
+  dataAjaxHandler = (event) ->

     event.preventDefault()
     $this = $(this)
-    $form = $this.parents("form").first()
+    $form = if $this.is("form") then $this else $this.parents("form").first()

     options = {}
     for action in $this.data("ajax").split(",")
@@ -26,7 +26,7 @@
       options[key] = value ? true

     ajaxOptions =
-      url : this.href
+      url : if $this.is("form") then this.action else this.href
       dataType : "json"

     if options["confirm"]
@@ -36,7 +36,7 @@
       ajaxOptions["type"] = options.method

     if options["submit"]
-      $validationGroup = $this.parents("form, [data-validation-group]").first()
+      $validationGroup = if $this.is("form") then $this else $this.parents("form, [data-validation-group]").first()
       isValid = true
       $validationGroup.find(":input")
         .each( ->
@@ -110,6 +110,9 @@
       ->
         $this.removeClass("busy")
     )
+
+  $(document).on "click", "a[data-ajax]", dataAjaxHandler
+  $(document).on "submit", "form[data-ajax]", dataAjaxHandler

   $(document).on "change", "table input.select-all-rows", ->


Fix nvd3 gold/xp graph axis labels (time)

--- app/assets/javascripts/matches/goldxp.js.coffee
+++ app/assets/javascripts/matches/goldxp.js.coffee
@@ -10,12 +10,17 @@
       id = '#chart'
       chart = nv.models.lineChart()

+      # http://stackoverflow.com/a/11287084/751774
+      formatTime = d3.time.format "%H:%M"
+      formatMinutes = (d) -> formatTime(new Date(2012, 0, 1, 0, d))
+
       chart.xAxis
         .axisLabel('Time')
-        .tickFormat(d3.format(',r'))
+        #.tickFormat(d3.format(',r'))
+        .tickFormat((d) -> "#{parseInt(d/60)}:#{if parseInt(d%60) is 0 then '00' else parseInt(d%60)}")

       chart.yAxis
-        .tickFormat(d3.format('.02f'))
+        .tickFormat(d3.format(',d'))

       d3.select("#{id} svg")
         .datum(gold_xp_data(id))


wiring up mobile header menu hide/show

wiring up mobile header menu hide/show

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -19,6 +19,9 @@
             return if ($ @).data('js-handle') is 'true'
             location.href = ($ @).attr('href')
             false
+
+$ ->
+    ($ '#menu_button').click -> ($ '#menu').toggle()

 window.pluralize_like_count = (like_count) ->
     if like_count is 1 then 'like' else 'likes'


Now render a session dialog for users that are not logged in when they try to visit clouds.

--- app/assets/javascripts/routers/clouds.js.coffee
+++ app/assets/javascripts/routers/clouds.js.coffee
@@ -8,9 +8,21 @@
     args = {}
     args.id = id

+    if session.isLoggedIn()
+      @renderCloudPage(args)
+    else
+      Backbone.history.navigate("/",true)
+
+      view = new Cloudsdale.Views.SessionsDialog(state: 'login', callback: @renderCloudPage, callbackArgs: args).el
+      if $('.modal-container').length > 0 then $('.modal-container').replaceWith(view) else $('body').append(view)
+
+      false
+
+
+  renderCloudPage: (args) ->
     cloud = session.get('clouds').findOrInitialize(args)
-
-    if $(".view-container[data-page-id=#{id}]").size() == 0
+
+    if $(".view-container[data-page-id=#{args.id}]").size() == 0
       $('.main-container').append new Cloudsdale.Views.CloudsShow(model: cloud).el

-    $.event.trigger 'page:show', id+    $.event.trigger 'page:show', args.id


Don't double-scale the delete button stroke

--- app/classifier/drawing-tools/delete-button.cjsx
+++ app/classifier/drawing-tools/delete-button.cjsx
@@ -30,9 +30,7 @@
       rotate(#{@props.rotate})
     "

-    averageScale = (@props.scale.horizontal + @props.scale.vertical) / 2
-
-    <g className="clickable drawing-tool-delete-button" transform={transform} stroke={STROKE_COLOR} strokeWidth={STROKE_WIDTH * averageScale} onClick={@props.onClick}>
+    <g className="clickable drawing-tool-delete-button" transform={transform} stroke={STROKE_COLOR} strokeWidth={STROKE_WIDTH} onClick={@props.onClick}>
       <circle r={RADIUS} fill={FILL_COLOR} />
       <path d={CROSS_PATH} transform="rotate(45)" />
     </g>