Removed jQuery dependence (breaks LRSListView for now). LRSView.el is therefore no longer a jQuery object.

--- src/view.coffee
+++ src/view.coffee
@@ -11,25 +11,25 @@
 					outlet.set(view.el, value)
 			input:
 				get: (outlet, view) ->
-					if outlet.el.attr('type') is 'checkbox'
-						outlet.el.prop('checked')
+					if outlet.el.type is 'checkbox'
+						outlet.el.checked
 					else
-						outlet.el.val()
+						outlet.el.value
 				set: (outlet, view, value) ->
-					if outlet.el.attr('type') is 'checkbox'
-						outlet.el.prop('checked', value)
+					if outlet.el.type is 'checkbox'
+						outlet.el.checked = value
 					else
-						outlet.el.val(value)
+						outlet.el.value = value
 			img:
 				get: (outlet, view) ->
-					outlet.el.attr('src')
+					outlet.el.src
 				set: (outlet, view, value) ->
-					outlet.el.attr('src', value)
+					outlet.el.src = value
 			html:
 				get: (outlet, view) ->
-					outlet.el.html()
+					outlet.el.innerHTML
 				set: (outlet, view, value) ->
-					outlet.el.html(value)
+					outlet.el.innerHTML = value

 		@outletTypes.default = @outletTypes.html
 		@outletTypes.textarea = @outletTypes.input
@@ -44,8 +44,11 @@
 	@views = {}

 	@actionPattern = ///^(.*?):(.*?)\((.*?)\)///
-
+
+	# ### `class` parseTemplates
+	# Collect all templates from the document. Must be called once, before view initialisation.
 	@parseTemplates: ->
+		return if @templates

 		# Define templates
 		@templates = {}
@@ -62,22 +65,22 @@

 			# Iterate all the children (templates)
 			for template in templateContainerHTML.children
+				# If no data-template attribute exists skip this element.
+				continue unless template.hasAttribute('data-template')
 				# Get the name.
 				name = template.getAttribute('data-template')
-				# If it wasn't provided it's not a template; skip.
-				continue unless name
 				# Remove the data-template attribute.
 				template.removeAttribute('data-template')
 				# Save the HTML in @templates.
 				@templates[name] = template.outerHTML

 	constructor: (el = null, @options = null, @owner) ->
-		if @template and (not el or el.children().length is 0)
+		if @template and (not el or el.children.length is 0)
 			@_loadTemplate()
 			if (el)
 				classes = el.attr('class')
-				if classes then @el.addClass(classes)
-				el.replaceWith(@el)
+				if classes then @el.classList.add(classes.split(' '))
+				el.parentNode.replaceChild(@el, el)
 		else
 			@el = el

@@ -90,8 +93,8 @@
 		super(@options)

 	initialize: () ->
-		@hidden = @el.hasClass('hidden')
-		@enabled = !@el.hasClass('disabled')
+		@hidden = @el.classList.contains('hidden')
+		@enabled = !@el.classList.contains('disabled')

 		@initializeViews()

@@ -103,93 +106,99 @@
 		)

 	_loadTemplate: ->
-		@el = $(LRSView.templates[@template])
+		_el = document.createElement('div')
+		_el.innerHTML = LRSView.templates[@template]
+		@el = _el.firstChild

 	_createViews: ->
-		viewEls = @el.find('[data-view] ')
+		viewEls = @el.querySelectorAll('[data-view]')
 		@views = {}
-		#console.log @, viewEls
-		for view in viewEls
-			$view = $(view)
-			continue unless $view.attr('data-view')
-			info = $view.attr('data-view').split(':')
-			$view.removeAttr('data-view')
-			#console.log info[0] || info[1], viewEls, '1'
-
-			if (info.length == 1)
+		for viewEl in viewEls
+			continue unless viewEl.hasAttribute('data-view')
+			info = viewEl.getAttribute('data-view').split(':')
+			viewEl.removeAttribute('data-view')
+
+			if info.length is 1
 				name = info[0]
-				view = new lrs.LRSView($view, null,  @)
+				view = new lrs.LRSView(viewEl, null,  @)
 			else
 				name = info[1]
-				view = new lrs.LRSView.views[info[0]+'View']($view, {subTemplate: info[2]}, @)
+				view = new lrs.LRSView.views[info[0]+'View'](viewEl, {subTemplate: info[2]}, @)

 			if name is 'view'
 				@view = view
-			else
-				if @views[name]
-					unless Array.isArray(@views[name])
-						@views[name] = [@views[name]]
-					@views[name].push(view)
-				else
-					@views[name] = view
+
+			if @views[name]
+				unless Array.isArray(@views[name])
+					@views[name] = [@views[name]]
+				@views[name].push(view)
+			else
+				@views[name] = view

 	_createOutlets: ->
-		outletEls = @el.find('[data-outlet]')
-		if (@el.attr('data-outlet')) then outletEls.push(@el)
-
 		@outlets = {}
-
-		for outlet in outletEls
-			do (outlet) =>
-				$outlet = $(outlet)
-				#info = $outlet.attr('data-view').split(':')
-				#if (info.length == 1)
-				#	info.unshift('default')
-				#@outlets[info[1]] = {type: info[0], el: $outlet}
-				name = $outlet.attr('data-outlet')
-				$outlet.removeAttr('data-outlet')
-				type = (outlet.tagName || '').toLowerCase()
-				if type is 'input' or type is 'textarea'
-					$outlet.on 'change', () => @updateOutletFromDom(name)
-				@outlets[name] = {type: type, el: $outlet}
-
-				@updateOutletFromDom(name)
+
+		outletEls = @el.querySelectorAll('[data-outlet]')
+		@_createOutlet(@el) if @el.hasAttribute('data-outlet')
+
+		for outletEl in outletEls
+			@_createOutlet(outletEl)

 		if (@customOutlets)
 			outlet.type = 'custom' for name, outlet of @customOutlets
-			_.extend(@outlets, @customOutlets)
+			_.extend(@outlets, @customOutlets) # TODO: Update

 		@outlets
+
+	_createOutlet: (outletEl) ->
+		self = @
+
+		name = outletEl.getAttribute('data-outlet')
+		outletEl.removeAttribute('data-outlet')
+		type = (outletEl.tagName || '').toLowerCase()
+		if type is 'input' or type is 'textarea'
+			outletEl.addEventListener('change', => self.updateOutletFromDom(name))
+		@outlets[name] =
+			type: type
+			el: outletEl
+
+		@updateOutletFromDom(name)

 	_createActions: ->
-		els = @el.find('[data-action]')
-		if (@el.attr('data-action')) then els.push(@el)
-
+		@actions = {}
+
+		actionEls = @el.querySelectorAll('[data-action]')
+		@_createAction(@el) if @el.hasAttribute('data-action')
+
+		for actionEl in actionEls
+			@_createAction(actionEl)
+
+		@actions
+
+	_createAction: (actionEl) ->
 		self = @
-
-		@actions = {}
-
-		for el in els
-			$el = $(el)
-			actionStrings = $el.attr('data-action').split(';')
-			$el.removeAttr('data-action')
-			for string in actionStrings
-				action = string.match(LRSView.actionPattern)
-				action.shift()
-				action[2] = action[2].split(',')
-				action[2] = [] if action[2][0] == ''
-
-				if !lrs.LRSView.isTouch && (action[0] == 'tap' or action[0] == 'singleTap')
-					action[0] = 'click'
-
-				if (!@actions[action[0]])
-					@actions[action[0]] = []
-
-				$el.on(action[0], (e) -> self.delegateAction(e, @))
-
-				@actions[action[0]].push({el: $el, function: action[1], parameters: action[2]})
-
-		@actions
+
+		actionStrings = actionEl.getAttribute('data-action').split(';')
+		actionEl.removeAttribute('data-action')
+		for string in actionStrings
+			action = string.match(LRSView.actionPattern) # TODO: Improve reliability; probably use regex.
+			action.shift()
+			action[2] = action[2].split(',')
+			action[2] = [] if action[2][0] == ''
+
+			if !lrs.LRSView.isTouch && (action[0] == 'tap' or action[0] == 'singleTap')
+				action[0] = 'click'
+
+			if (!@actions[action[0]])
+				@actions[action[0]] = []
+
+			actionEl.addEventListener(action[0], (e) -> self.delegateAction(e, @))
+
+			@actions[action[0]].push(
+				el: actionEl
+				function: action[1]
+				parameters: action[2]
+			)

 	delegateAction: (e, el = false) =>
 		return unless @enabled
@@ -199,7 +208,7 @@
 		return unless actions

 		for action in actions
-			continue if el != false && action.el[0] != el
+			continue if el != false && action.el != el
 			parameters = []

 			for parameter in action.parameters
@@ -254,29 +263,47 @@
 		@updateDomFromOutlet(name)
 		@

-	appendTo: (el) ->
-		@el.appendTo(el.el or el) # Okay?
-		@
-
-	insertBefore: (el) ->
-		@el.insertBefore(el.el or el) # Okay?
-		@
-
-	insertAfter: (el) ->
-		@el.insertAfter(el.el or el) # Okay?
-		@
+	appendTo: (view) ->
+		(view.el or view).appendChild(@el)
+		@
+
+	insertBefore: (view) ->
+		el = view.el or view
+		el.parentNode.insertBefore(@el, el)
+		@
+
+	insertAfter: (view) ->
+		@insertBefore((view.el or view).nextSibling)
+		@
+
+	retract: ->
+		parentNode = @el.parentNode
+		nextSibling = @el.nextSibling
+		@el.parentNode.removeChild(@el)
+
+		return ->
+			if nextSibling
+				parentNode.insertBefore(@el, nextSibling)
+			else
+				parentNode.appendChild(@el)

 	remove: ->
-		@el.remove()
-		@
-
-	addClass: (classes) ->
-		@el.addClass(classes)
-		@
-
-	removeClass: (classes) ->
-		@el.removeClass(classes)
-		@
+		@el.parentNode.removeChild(@el)
+		@
+
+	addClass: (names...) ->
+		@el.classList.add.apply(@el.classList, names)
+		@
+
+	removeClass: (names...) ->
+		@el.classList.remove.apply(@el.classList, names)
+		@
+
+	toggleClass: (name, addOrRemove) ->
+		@el.classList.toggle(name, addOrRemove)
+
+	hasClass: (name) ->
+		@el.classList.contains(name)

 	enable: (recursive = true, updateClass = true)->
 		@enabled = true


dont throw error when we cant find a module

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -220,7 +220,7 @@
             not resolve.isCore(mfile)      and
             not (opts.target? and @has(opts.target))
         moduleError = (msg) ->
-            new Error "#{msg}: #{JSON.stringify mfile} " +
+            "#{msg}: #{JSON.stringify mfile} " +
                 "from directory #{JSON.stringify opts.dirname}" +
                 (opts.fromFile? and " while processing file #{opts.fromFile}" or "")
         opts.file = mfile if opts.body
@@ -232,7 +232,8 @@
                     normPath = mfile
                 opts.file = @resolver(normPath, opts.dirname)
             catch err
-                throw moduleError "Cannot find module. #{err?.message}"
+                msg = moduleError("Cannot find module. #{err?.message}")
+                @emit('error', msg)
         return this if @has(opts.file)
         dirname = path.dirname(opts.file)
         pkgfile = path.join(dirname, 'package.json')


main.prod.coffee keeps getting corrupted.

--- config/main.prod.coffee
+++ config/main.prod.coffee
@@ -170,31 +170,6 @@

 # THESE COMMANDS WILL EXECUTE SEQUENTIALLY.
-KONFIG.workers =
-  rerouting           : command : "#{GOBIN}/rerouting          -c #{configName}"
-  cron                : command : "#{GOBIN}/cron               -c #{configName}"
-  broker              : command : "#{GOBIN}/broker             -c #{configName}"
-  socialapi           : command : "#{GOBIN}/api                -c #{socialapi.configFilePath} -port #{socialapi.port}"
-  dailyemailnotifier  : command : "#{GOBIN}/dailyemailnotifier -c #{socialapi.configFilePath}"
-  notification        : command : "#{GOBIN}/notification       -c #{socialapi.configFilePath}"
-  popularpost         : command : "#{GOBIN}/popularpost        -c #{socialapi.configFilePath}"
-  populartopic        : command : "#{GOBIN}/populartopic       -c #{socialapi.configFilePath}"
-  realtime            : command : "#{GOBIN}/realtime           -c #{socialapi.configFilePath}"
-  sitemapfeeder       : command : "#{GOBIN}/sitemapfeeder      -c #{socialapi.configFilePath}"
-  topicfeed           : command : "#{GOBIN}/topicfeed          -c #{socialapi.configFilePath}"
-  trollmode           : command : "#{GOBIN}/trollmode          -c #{socialapi.configFilePath}"
-  webserver           : command : "node   #{projectRoot}/server/index.js                   -c #{configName} -p #{KONFIG.webserver.port}   --disable-newrelic"
-  socialworker        : command : "node   #{projectRoot}/workers/social/index.js           -c #{configName} -p #{KONFIG.social.port}      -r #{region} --disable-newrelic --kite-port=13020"
-  sourcemaps          : command : "node   #{projectRoot}/server/lib/source-server/index.js -c #{configName} -p #{KONFIG.sourcemaps.port}"
-  authworker          : command : "node   #{projectRoot}/workers/auth/index.js             -c #{configName}"
-  emailsender         : command : "node   #{projectRoot}/workers/emailsender/index.js      -c #{configName}"
-  boxproxy            : command : "node #{projectRoot}/server/boxproxy.js            -c #{configName}"
-  clientWatcher       : command : "coffee #{projectRoot}/build-client.coffee               --watch --sourceMapsUri #{hostname}"
-  kontrol             : command : "#{GOBIN}/kontrol -c #{configName} -r #{region}"
-  kloud               : command : "#{GOBIN}/kloud -c #{configName} -r #{region} -port #{KONFIG.kloud.port} -public-key #{KONFIG.kloud.publicKeyFile} -private-key #{KONFIG.kloud.privateKeyFile} -kontrol-url \"#{KONFIG.kloud.kontrolUrl}\" -debug"
-
-  # guestcleaner        : command : "node #{projectRoot}/workers/guestcleaner/index.js     -c #{configName}"
-

 KONFIG.workers =
   # dailyemailnotifier  : command : "#{GOBIN}/dailyemailnotifier -c #{socialapi.configFilePath}"
@@ -214,12 +189,12 @@

   socialapi           : command : "cd go/src/socialapi && make develop -j config=#{socialapi.configFilePath}"

-  authworker          : command : "cd #{projectRoot}/workers/auth/lib/auth/     && nodemon main.coffee   -c #{configName}"
-  socialworker        : command : "cd #{projectRoot}/workers/social/lib/social/ && nodemon main.coffee   -c #{configName} -p #{KONFIG.social.port}      -r #{region} --disable-newrelic --kite-port=13020"
-  sourcemaps          : command : "cd #{projectRoot}/servers/lib/source-server/ && nodemon main.coffee   -c #{configName} -p #{KONFIG.sourcemaps.port}"
-  emailsender         : command : "cd #{projectRoot}/workers/emailsender/       && nodemon main.coffee   -c #{configName}"
-  boxproxy            : command : "cd #{projectRoot}/servers/boxproxy/          && nodemon boxproxy.js   -c #{configName}"
-  webserver           : command : "cd #{projectRoot}/servers/lib/server/        && nodemon index.coffee  -c #{configName} -p #{KONFIG.webserver.port}   --disable-newrelic"
+  authworker          : command : "cd #{projectRoot}/workers/auth/lib/auth/     && nodemon main.coffee  -c #{configName}"
+  socialworker        : command : "cd #{projectRoot}/workers/social/lib/social/ && nodemon main.coffee  -c #{configName} -p #{KONFIG.social.port}      -r #{region} --disable-newrelic --kite-port=13020"
+  sourcemaps          : command : "cd #{projectRoot}/servers/lib/source-server/ && nodemon main.coffee  -c #{configName} -p #{KONFIG.sourcemaps.port}"
+  emailsender         : command : "cd #{projectRoot}/workers/emailsender/       && nodemon main.coffee  -c #{configName}"
+  boxproxy            : command : "cd #{projectRoot}/servers/boxproxy/          && nodemon boxproxy.js  -c #{configName}"
+  webserver           : command : "cd #{projectRoot}/servers/lib/server/        && nodemon index.coffee -c #{configName} -p #{KONFIG.webserver.port}   --disable-newrelic"

   clientWatcher       : command : "coffee #{projectRoot}/build-client.coffee    --watch --sourceMapsUri #{hostname}"


Add 1m marks

Add 1m marks

--- assets/javascripts/_game.coffee
+++ assets/javascripts/_game.coffee
@@ -44,8 +44,24 @@

     @svg.append("svg:text")
       .attr("x", 60)
+      .attr("y", 103)
+      .text("1m")
+      .attr("font-family", "sans-serif")
+      .attr("font-size", "10px")
+      .attr("fill", "gray")
+
+    @svg.append("svg:text")
+      .attr("x", 60)
       .attr("y", 203)
-      .text("0 m")
+      .text("0m")
+      .attr("font-family", "sans-serif")
+      .attr("font-size", "10px")
+      .attr("fill", "gray")
+
+    @svg.append("svg:text")
+      .attr("x", 56)
+      .attr("y", 303)
+      .text("-1m")
       .attr("font-family", "sans-serif")
       .attr("font-size", "10px")
       .attr("fill", "gray")


disable tabbing to spy selector

disable tabbing to spy selector

--- shared/src/components/spy-mode.cjsx
+++ shared/src/components/spy-mode.cjsx
@@ -16,7 +16,10 @@
   render: ->

       {@props.children}
-      π
+      π

 SpyModeContent = React.createClass


[fix] Logic error

[fix] Logic error

--- app/assets/javascripts/angular/controllers/walking_guide_ctrl.coffee
+++ app/assets/javascripts/angular/controllers/walking_guide_ctrl.coffee
@@ -196,10 +196,10 @@

     getDelay = ->
       diff = Date.now() - lastQueryTimestamp
-      delay = if diff >= 1000
+      if diff >= 1000
         50
       else
-        Math.min(50, 1000 - diff)
+        Math.max(50, 1000 - diff)

     $scope.drawRoute = ->
       # Split relics in to chunks to prevent MAX_WAYPOINTS_EXCEEDED error


add formal parameters to pistachios

add formal parameters to pistachios

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -123,8 +123,9 @@
             child.emit 'viewAppended', child

-  setTemplate:(tmpl)->
-    @template = new Pistachio(@, tmpl)
+  setTemplate:(tmpl, params=@pistachioParams)->
+    options = if params? then {params}
+    @template = new Pistachio @, tmpl, options
     @updatePartial @template.html
     @template.embedSubViews()

@@ -177,7 +178,7 @@
     @addEventHandlers options

     if options.pistachio
-      @setTemplate options.pistachio
+      @setTemplate options.pistachio, options.pistachioParams
       @template.update()

     @setLazyLoader options.lazyLoadThreshold      if options.lazyLoadThreshold


Fix https://github.com/poooi/poi/issues/469

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -4,7 +4,7 @@
 {$, $$, _, React, ReactBootstrap, ROOT} = window
 {Grid, Col, Input, Alert} = ReactBootstrap
 {config} = window
-{openExternal} = require 'shell'
+shell = require 'shell'
 Divider = require './divider'
 plugins = glob.sync(path.join(ROOT, 'plugins', '*'))
 plugins = plugins.map (filePath) ->
@@ -16,7 +16,10 @@
   config.get "plugin.#{plugin.name}.enable", true

 getAuthorLink = (author, link) ->
-  {author}
+  handleClickAuthorLink = (e) ->
+    shell.openExternal e.target.dataset.link
+    e.preventDefault()
+  {author}

 PluginConfig = React.createClass
   getInitialState: ->


Use default proxy setting

Use default proxy setting

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -106,7 +106,7 @@
     if !@props.disabled
       updating = @state.updating
       updating[index] = true
-      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
+      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server, http_proxy: 'http://127.0.0.1:12450'}, (err) ->
         npm.commands.update [plugins[index].packageName], (er, data) ->
           callback(index)
       @setState {updating}
@@ -123,7 +123,7 @@
     for installTarget of installTargets
       installAllStatus.push 1
       toInstall.push installTarget
-    npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
+    npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server, http_proxy: 'http://127.0.0.1:12450'}, (err) ->
       npm.commands.install toInstall, (er, data) ->
         callback()
     @setState
@@ -146,7 +146,7 @@
         if semver.lt(plugin.version, @state.latest[plugin.packageName]) && @state.removeStatus[index] == 0
           updating[index] = true
           toUpdate.push plugin.packageName
-      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
+      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server, http_proxy: 'http://127.0.0.1:12450'}, (err) ->
         npm.commands.update toUpdate, (er, data) ->
           callback()
       @setState
@@ -160,7 +160,7 @@
     if !@props.disabled
       removeStatus = @state.removeStatus
       removeStatus[index] = 1
-      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
+      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server, http_proxy: 'http://127.0.0.1:12450'}, (err) ->
         npm.commands.uninstall [plugins[index].packageName], (er, data) ->
           callback(index)
       @setState {removeStatus}
@@ -172,7 +172,7 @@
     if !@props.disabled
       installStatus = @state.installStatus
       installStatus[index] = 1
-      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
+      npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server, http_proxy: 'http://127.0.0.1:12450'}, (err) ->
         npm.commands.install [name], (er, data) ->
           callback(index)
       @setState {installStatus}
@@ -199,7 +199,7 @@
       checking: false
       isUpdateAvailable: isUpdateAvailable
   checkUpdate: (callback, isfirst) ->
-    npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
+    npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server, http_proxy: 'http://127.0.0.1:12450'}, (err) ->
       npm.config.set 'depth', 1
       npm.commands.outdated [], (er, data) ->
         callback(data, isfirst)


Correct minor error in album loading

Correct minor error in album loading

--- scripts/albums.coffee
+++ scripts/albums.coffee
@@ -47,7 +47,7 @@
     isLoading = false

     checkLoad = () ->
-        return if itemsLoaded == window.files.length + 1
+        return if itemsLoaded == window.files.length
         return if isLoading
         if lastItemY - getScroll() < viewPortHeight()
             isLoading = true


up.fragment.all: Tests for layer matching

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -6,27 +6,28 @@

   describe 'JavaScript functions', ->

-    describe 'up.fragment.first', ->
-
-      it 'returns the first element with the given selector', ->
+    describe 'up.fragment.all', ->
+
+      it 'returns elements matching the given selector', ->
         match = fixture('.match')
+        otherMatch = fixture('.match.other')
         noMatch = fixture('.no-match')
-        result = up.fragment.first('.match')
-        expect(result).toBe(match)
-
-      it 'returns the first argument if the first argument is already an element', ->
+        results = up.fragment.all('.match')
+        expect(results).toEqual [match, otherMatch]
+
+      it 'returns an array of the first argument if the first argument is already an element', ->
         match = fixture('.match')
-        result = up.fragment.first(match)
-        expect(result).toBe(match)
-
-      it 'returns undefined if there are no matches', ->
-        result = up.fragment.first('.match')
-        expect(result).toBeUndefined()
+        results = up.fragment.all(match)
+        expect(results).toEqual [match]
+
+      it 'returns an empty array if there are no matches', ->
+        results = up.fragment.all('.match')
+        expect(results).toEqual []

       it 'does not return an element that is currently destroying', ->
         match = fixture('.match.up-destroying')
-        result = up.fragment.first('.match')
-        expect(result).toBeUndefined()
+        results = up.fragment.all('.match')
+        expect(results).toEqual []

       describe 'when given a root element for the search', ->

@@ -37,28 +38,44 @@
           parent2 = fixture('.parent1')
           parent2Match = e.affix(parent2, '.match')

-          expect(up.fragment.first(parent1, '.match')).toBe(parent1Match)
-          expect(up.fragment.first(parent2, '.match')).toBe(parent2Match)
-
-        it 'returns the second argument if the second argument is already an element', ->
+          expect(up.fragment.all(parent1, '.match')).toEqual [parent1Match]
+          expect(up.fragment.all(parent2, '.match')).toEqual [parent2Match]
+
+        it 'returns an array of the second argument if the second argument is already an element', ->
           root = fixture('.root')
           match = fixture('.match')
-          result = up.fragment.first(root, match)
-          expect(result).toBe(match)
-
-      describe 'with { origin } option', ->
-
-        it 'resolves an & in the selector string with an selector for the origin'
-
-        it 'prefers to find an element in the same layer as the origin'
-
-        it "returns the element in the top-most layer if there are no matches in the origin's layer"
-
-      describe 'with { layer } option', ->
-
-        it 'only matches elements in that layer'
-
-        it 'returns the first argument if the first argument is already an element', ->
+          result = up.fragment.all(root, match)
+          expect(result).toEqual [match]
+
+      it 'resolves an & in the selector string with an selector for the { origin }'
+
+      describe 'layer matching', ->
+
+        beforeEach (done) ->
+          @rootElement = fixture('.element.in-root')
+          up.layer.open(target: '.element', document: '

').then =>
+            @overlayElement = document.querySelector('.up-overlay .element')
+            @overlayElement or throw "Could not find element in overlay"
+            done()
+
+        it 'only matches elements in the current layer', ->
+          results = up.fragment.all('.element')
+          expect(results).toEqual [@overlayElement]
+
+        it 'returns undefined if the only match is in a non-current layer', ->
+          results = up.fragment.all('.element.in-root')
+          expect(results).toEqual []
+
+        it "only matches elements in the layer of the given { origin }", ->
+          otherRootElement = fixture('.element.other.in-root')
+          results = up.fragment.all('.element', origin: otherRootElement)
+          expect(results).toEqual [@rootElement, otherRootElement]
+
+        it 'only matches elements in the given { layer }', ->
+          results = up.fragment.all('.element', layer: 'root')
+          expect(results).toEqual [@rootElement]
+
+        it 'returns the first argument if the first argument is already an element, even if { layer } is given', ->
           match = fixture('.match')
           result = up.fragment.first(match, layer: 'root')
           expect(result).toBe(match)


Tests: Added new helper.

Tests: Added new helper.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -321,6 +321,12 @@
           assert.equal result.value.length, 1

+  assertMainHeader: (browser) ->
+
+    browser
+      .waitForElementVisible   '[testpath=main-header] a#koding-logo', 25000
+      .waitForElementVisible   '[testpath=main-header] [testpath=login-link]', 25000
+

   getUrl: ->
     return 'http://lvh.me:8090'


Tests: Added assertNotLoggedIn helper.

Tests: Added assertNotLoggedIn helper.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -22,6 +22,18 @@

     return user

+
+  assertNotLoggedIn: (browser, user) ->
+    url = @getUrl()
+    browser.url(url)
+    browser.maximizeWindow()
+
+    browser.execute 'KD.isTesting = true;'
+
+    @attemptLogin(browser, user)
+
+    browser
+      .waitForElementVisible   '.flex-wrapper', 20000 # Assertion

   attemptLogin: (browser, user) ->


Run autoprefixer regardless

Run autoprefixer regardless

--- cli/Gruntfile.coffee
+++ cli/Gruntfile.coffee
@@ -93,6 +93,9 @@
     'concat:js'
     'concat:css'
     'concat:html'
+
+    'autoprefixer'
+
     'inject:mode'
     'copy:index'
     'inject:dev'
@@ -434,7 +437,6 @@
   grunt.registerTask 'optimizeStyle', ->
     if grunt.file.exists('public/style.css')
       grunt.task.run [
-        'autoprefixer'
         'cssshrink'
       ]


Update last login date in tracker identification

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -857,12 +857,15 @@
           $set            :
             username      : username
             clientId      : replacementToken
-            lastLoginDate : new Date
+            lastLoginDate : lastLoginDate = new Date
           $unset          :
             guestId       : 1

         session.update sessionUpdateOptions, (err) ->
           return callback err  if err
+
+          Tracker.identify username, { lastLoginDate }
+
           queue.next()

       ->


change to deploy2

change to deploy2

--- scripts/deploy.coffee
+++ scripts/deploy.coffee
@@ -6,7 +6,7 @@

 module.exports = (robot) ->
 	robot.on 'github_deployment_event', ->
-	robot.respond /deploy/i, (msg) ->
+	robot.respond /deploy2/i, (msg) ->

 		unless robot.auth.hasRole(msg.envelope.user, 'deploy')
 			msg.send "#{msg.envelope.user} You don't have Deploy permissions"


Fixed issue #13

Fixed issue #13

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -32,6 +32,10 @@
       classname: "HelloWorld",
       arguments: $('#arguments').val()
   ,(data) ->
+    if data.error
+      $('#compile-error').text(data.error.join(''))
+      return
+
     if window.location.pathname is '/'
       window.location.href = window.location.href + "#{data.uuid}"
     else
@@ -60,7 +64,7 @@
       if after then after()
     else
       error = data.error.join('')
-      $('#terminal').html('<pre>'+error+'</pre>')
+      $('#compile-error').text(error)
       $('iframe').hide()
       $('#build-run-btn').removeClass('disabled')
       $('#build-btn').removeClass('disabled')


exclude aborts from disconnection detection, as they can happen during validation.

--- app/assets/javascripts/init.coffee
+++ app/assets/javascripts/init.coffee
@@ -96,7 +96,7 @@

   # TODO: tidy this up, make a single view class of some sort
   $(document).ajaxError (e, xhr, req) ->
-    if MCM.currentUser == undefined or req.url == "/users/sign_in.json"
+    if MCM.currentUser == undefined or req.url == "/users/sign_in.json" or xhr.statusText == "abort"
       return

     loginDialogs = $(".logout-notification").length


use public interface of View

use public interface of View

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -90,7 +90,7 @@
 				g.setVertices (View.createArrayBufferObject g.normalVertices), g.normalVertices.length

 				#sends current position to Model for preloading data
-				Model.Binary.ping(transMatrix)?.done(renderFunction)
+				Model.Binary.ping(transMatrix)?.done(View.draw)

 				#sends current position to Model for caching route
 				Model.Route.put cam.getPos(), null


For username instead of people.

For username instead of people.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -131,9 +131,16 @@

             # Returns active users only with their points
             # Getting list from 'people' works but not robot.brain.data
-            score = ["#{username} has #{points[username]}" for people in peoplelist]
+            score = ["#{username} has #{points[username]}" for username in peoplelist]

             msg.send "Scores: #{score}"
+
+    # robot.respond /getset$/i, (msg) ->
+    #     if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+    #
+    #         robot.brain.set 'coins', 10
+    #         coincount = robot.brain.get 'coins'
+    #         msg.send "Coints: #{coincount}"

     robot.respond /getset$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])


Fixed weird @.get calling

Fixed weird @.get calling

--- app/backbone/models/comment.coffee
+++ app/backbone/models/comment.coffee
@@ -41,4 +41,4 @@
       @saveWithState(attributes, options)

   textContent: ->
-    stripHtml(@.get('formatted_content')) || @.get('content')
+    stripHtml(@get('formatted_content')) || @get('content')


Fix bootstrap filestyle

Fix bootstrap filestyle

--- scripts/themis.coffee
+++ scripts/themis.coffee
@@ -97,7 +97,7 @@
     View

-define 'signupView', ['jquery', 'view', 'renderTemplate', 'dataStore', 'navigationBar', 'jquery.form'], ($, View, renderTemplate, dataStore, navigationBar) ->
+define 'signupView', ['jquery', 'view', 'renderTemplate', 'dataStore', 'navigationBar', 'jquery.form', 'bootstrap-filestyle'], ($, View, renderTemplate, dataStore, navigationBar) ->
     class SignupView extends View
         constructor: ->
             @urlRegex = /^\/signup$/
@@ -115,6 +115,7 @@
                     navigationBar.present()

                     $form = $main.find 'form.themis-form-signup'
+                    $form.find('input:file').filestyle()
                     $form.on 'submit', (e) ->
                         e.preventDefault()
                         $form.ajaxSubmit
@@ -130,6 +131,7 @@
             $main = $ '#main'
             $form = $main.find 'form.themis-form-signup'
             if $form.length > 0
+                $form.find('input:file').filestyle 'destroy'
                 $form.off 'submit'
             $main.html ''
             navigationBar.dismiss()
@@ -339,6 +341,6 @@
         $navbar.html ''

-define 'themis', ['jquery', 'stateController', 'bootstrap', 'bootstrap-filestyle'], ($, stateController) ->
+define 'themis', ['jquery', 'stateController', 'bootstrap'], ($, stateController) ->
     $(document).ready ->
         stateController.init()


added logging, better interval checker

added logging, better interval checker

--- scripts/trello.coffee
+++ scripts/trello.coffee
@@ -27,7 +27,7 @@
 Trello = require 'node-trello'

 check_interval_ms = process.env.HUBOT_TRELLO_INTERVAL ? 1000 * 60 * 5
-
+checker = undefined
 trello_key = process.env.HUBOT_TRELLO_KEY
 trello_token = process.env.HUBOT_TRELLO_TOKEN
 trello_board_id = process.env.HUBOT_TRELLO_BOARD_ID
@@ -36,15 +36,16 @@

 filter_actions =
   changeCard: (notif) ->
-    if 'listAfter' of notif.data
-      "#{notif.memberCreator.username} moved card `#{notif.data.card.name}` from `#{notif.data.listBefore.name}` to `#{notif.data.listAfter.name}`" + cardUrl(notif.data.card)
+    if 'listAfter' not of notif.data
+      return
+    "#{notif.memberCreator.username} moved card `#{notif.data.card.name}` from `#{notif.data.listBefore.name}` to `#{notif.data.listAfter.name}` - #{cardUrl(notif.data.card)}"
   commentCard: (notif) ->
-    "#{notif.memberCreator.username} commented on card `#{notif.data.card.name}`: #{notif.data.text}" + cardUrl(notif.data.card)
+    "#{notif.memberCreator.username} commented on card `#{notif.data.card.name}`: #{notif.data.text} - #{cardUrl(notif.data.card)}"
   createdCard: (notif) ->
-    "#{notif.memberCreator.username} created card `#{notif.data.card.name}`" + cardUrl(notif.data.card)
+    "#{notif.memberCreator.username} created card `#{notif.data.card.name}` - #{cardUrl(notif.data.card)}"

 cardUrl = (card) ->
-  " - https://trello.com/card/#{trello_board_id}/#{card.idShort}"
+  "https://trello.com/card/#{trello_board_id}/#{card.idShort}"

 connect = (onConnected, onError) ->
   console.error "missing HUBOT_TRELLO_KEY" if not trello_key
@@ -62,12 +63,31 @@

 get_notifs = (options, onComplete, onError) ->
   connect (t) ->
+    console.info('retrieving notifications...')
     t.get "/1/members/me/notifications", options, (err, data) ->
       if data.length
+        console.info('set last_notif_id=' + data[0].id)
         last_notif_id = data[0].id
-      notifs = (filter_actions[notif.type](notif) for notif in data)
-      onComplete(notifs)
+      raw_notifs = (filter_actions[notif.type](notif) for notif in data)
+      onComplete(notif for notif in raw_notifs when notif isnt undefined)
   , onError
+
+init_checker = (robot) ->
+  console.info "set interval check to #{check_interval_ms}"
+  checker = setInterval ->
+    console.info "checking for new notifications..."
+    options =
+      filter: Object.keys(filter_actions)
+      read_filter: 'unread'
+      limit: 10
+      since: last_notif_id
+    get_notifs options, (notifs) ->
+      console.info "#{notifs.length} new notifications received"
+      for notif in notifs
+        robot.messageRoom("", notif)
+    , (err) ->
+      msg.send "Error: #{err}"
+  , check_interval_ms

 module.exports = (robot) ->
   robot.respond /trello ping/i, (msg) ->
@@ -80,23 +100,9 @@
       limit: 10
       since: last_notif_id
     get_notifs options, (notifs) ->
-      filtered = []
       for notif in notifs
-        if notif
-          msg.send notif
+        msg.send(notif)
     , (err) ->
       msg.send "Error: #{err}"

-  setInterval ->
-    options =
-      filter: Object.keys(filter_actions)
-      read_filter: 'unread'
-      limit: 10
-      since: last_notif_id
-    get_notifs options, (notifs) ->
-      for notif in notifs
-        if notif
-          robot.messageRoom "", notif
-    , (err) ->
-      msg.send "Error: #{err}"
-  , check_interval_ms
+  init_checker(robot)


First playable version

First playable version

--- app/javascripts/application.coffee
+++ app/javascripts/application.coffee
@@ -13,7 +13,9 @@
     options =
       hideTabs: true
       onFirstInteractive: ->
+        getSheet().applyFilterAsync "Field Name + Piece", "", tableau.FilterUpdateType.ALL
         viz.addEventListener(tableau.TableauEventName.MARKS_SELECTION, onMarksSelection)
+        DrawPieces()
         console.log "onFirstInteractive done, we have our board"
     window.viz = new tableau.Viz(containerDiv, vizURL, options)

@@ -46,16 +48,22 @@
   aiCallback = (player, from, to) ->
     console.log player, from, to

+  pieces = "\xa0\u265f\u265a\u265e\u265d\u265c\u265b  \u2659\u2654\u2658\u2657\u2656\u2655"
+
   DrawPieces = ->
     console.log 'DrawPieces'
-    pieces = "\xa0\u265f\u265a\u265e\u265d\u265c\u265b  \u2659\u2654\u2658\u2657\u2656\u2655"
-    q = undefined
-    p = 21
-    while p < 99
-      if q = document.getElementById('o' + p)
-        q.innerHTML = pieces.charAt(ai.board[p] & 15)
-        q.style.borderColor = if p == ai.getMoveFrom() then 'red' else '#dde'
-      ++p
+
+    board = _.map(
+      _.range 21, 99
+      (p) -> "#{p}#{pieces.charAt(ai.board[p] & 15)}"
+    )
+    console.log "board: #{board}"
+    getSheet().applyFilterAsync(
+        "Toledo ID + Piece",
+        board,
+        tableau.FilterUpdateType.REPLACE
+    )
+

   window.initViz = initViz
   ai = ToledoChess
@@ -65,4 +73,3 @@
   window.getSheet = getSheet
   window.ai = ai

-


[♻] Cleaned up.

[♻] Cleaned up.

--- public/javascripts/bundle.js.coffee
+++ public/javascripts/bundle.js.coffee
@@ -56,7 +56,7 @@
         contact = backstage.find '> .contact'

         if do contact.present
-          request = contact.find 'form.request'
+          request = contact.find '.request'

           do request.formify if do request.present


Reuse same express handler code for app and embedding.

--- server-src/dev.coffee
+++ server-src/dev.coffee
@@ -46,54 +46,32 @@
     else
         'fi'

-requestHandler = (req, res, next) ->
-    unless req.path? and req.host?
-        next()
-        return
-    match = false
-    for pattern in ALLOWED_URLS
-        if req.path.match pattern
-            match = true
-            break
-    if not match
-        next()
-        return
-    host = req.get('host')
-    config.default_language = get_language host
-    vars =
-        configJson: JSON.stringify config
-        config: config
-        staticFile: staticFileHelper
-        pageMeta: req._context or {}
-        siteName:
-            fi: 'Pääkaupunkiseudun palvelukartta'
-            sv: 'Servicekarta'
-            en: 'Service Map'
+makeHandler = (template) ->
+    requestHandler = (req, res, next) ->
+        unless req.path? and req.host?
+            next()
+            return
+        match = false
+        for pattern in ALLOWED_URLS
+            if req.path.match pattern
+                match = true
+                break
+        if not match
+            next()
+            return
+        host = req.get('host')
+        config.default_language = get_language host
+        vars =
+            configJson: JSON.stringify config
+            config: config
+            staticFile: staticFileHelper
+            pageMeta: req._context or {}
+            siteName:
+                fi: 'Pääkaupunkiseudun palvelukartta'
+                sv: 'Servicekarta'
+                en: 'Service Map'

-    res.render 'home.jade', vars
-
-embeddedHandler = (req, res, next) ->
-    # TODO: enable
-    # match = false
-    # for pattern in ALLOWED_URLS
-    #     if req.path.match pattern
-    #         match = true
-    #         break
-    # if not match
-    #     next()
-    #     return
-    config.default_language = get_language req.get('host')
-    vars =
-        configJson: JSON.stringify config
-        config: config
-        staticFile: staticFileHelper
-        pageMeta: req._context or {}
-        siteName:
-            fi: 'Pääkaupunkiseudun palvelukartta'
-            sv: 'Servicekarta'
-            en: 'Service Map'
-
-    res.render 'embed.jade', vars
+        res.render template, vars

 handleUnit = (req, res, next) ->
     if req.query.service? or req.query.division?
@@ -154,15 +132,15 @@

     # Static files handler
     @use STATIC_URL, express.static staticDir
+    # Redirect all trailing slash urls to slashless urls
     @use slashes(false)
     # Expose the original sources for better debugging
     @use config.url_prefix + 'src', express.static(__dirname + '/../src')
-
+    # Emit unit data server side for robots
     @use config.url_prefix + 'unit', handleUnit
-
-    @use config.url_prefix + 'embed', embeddedHandler
-
+    # Handler for embed urls
+    @use config.url_prefix + 'embed', makeHandler('embed.jade')
     # Handler for everything else
-    @use config.url_prefix, requestHandler
+    @use config.url_prefix, makeHandler('home.jade')

 server.listen serverPort


Changement ordre logs

Changement ordre logs

--- server/api/log.coffee
+++ server/api/log.coffee
@@ -12,6 +12,7 @@
       SELECT date, description, nom, prenom
       FROM log
       INNER JOIN utilisateur on utilisateur_id = utilisateur.id
+      ORDER BY date DESC
       LIMIT $1::int
       OFFSET $2::int
     """, [(req.query.limit or 50),(req.query.skip or 0)]


don't really need this any more

don't really need this any more

--- keymaps/julia-client.cson
+++ keymaps/julia-client.cson
@@ -1,13 +1,3 @@
-# Keybindings require three things to be fully defined: A selector that is
-# matched against the focused element, the keystroke and the command to
-# execute.
-#
-# Below is a basic keybinding which registers on all platforms by applying to
-# the root workspace element.
-
-# For more detailed documentation see
-# https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-
 '.platform-darwin atom-text-editor[data-grammar="source julia"]:not([mini])':
   'cmd-enter': 'julia-client:evaluate'
   'cmd-shift-enter': 'julia-client:evaluate-all'


Ratings: Use redis multi instead of async parallel.

--- server/ratings.coffee
+++ server/ratings.coffee
@@ -22,15 +22,17 @@

 @getPlayer = (id, next) ->
   id = id.toLowerCase()
-  hash = {}
+  multi = db.multi()
   for attribute in RATINGS_ATTRIBUTES
-    do (attribute) ->
-      hash[attribute] = (callback) ->
-        db.zscore RATINGS_SUBKEYS[attribute], id, (err, score) ->
-          return callback(err)  if err
-          score ||= exports.algorithm.createPlayer()[attribute]
-          return callback(null, score)
-  async.parallel(hash, next)
+    multi = multi.zscore(RATINGS_SUBKEYS[attribute], id)
+  multi.exec (err, results) ->
+    return next(err)  if err
+    object = {}
+    for value, i in results
+      attribute = RATINGS_ATTRIBUTES[i]
+      value ||= exports.algorithm.createPlayer()[attribute]
+      object[attribute] = value
+    return next(null, object)

 @getRating = (id, next) ->
   id = id.toLowerCase()
@@ -40,9 +42,7 @@

 @setRating = (id, newRating, next) ->
   id = id.toLowerCase()
-  @getPlayer id, (err, player) ->
-    if err then return next(err)
-    db.zadd(RATINGS_SUBKEYS['rating'], newRating, id, next)
+  db.zadd(RATINGS_SUBKEYS['rating'], newRating, id, next)

 @getPlayers = (idArray, next) ->
   idArray = idArray.map((id) -> id.toLowerCase())
@@ -57,14 +57,13 @@
     return next(null, players.map((p) -> p.rating))

 @updatePlayer = (id, object, next) ->
-  hash = {}
+  multi = db.multi()
   for attribute in RATINGS_ATTRIBUTES
-    do (attribute) ->
-      hash[attribute] = (callback) ->
-        db.zadd RATINGS_SUBKEYS[attribute], object[attribute], id, (err, ok) ->
-          return callback(err)  if err
-          return callback(null, object[attribute])
-  async.parallel(hash, next)
+    value = object[attribute]
+    multi = multi.zadd(RATINGS_SUBKEYS[attribute], value, id)
+  multi.exec (err, results) ->
+    return next(err)  if err
+    return next(null, object)

 @updatePlayers = (id, opponentId, score, next) ->
   if score not of INVERSE_RESULTS


V1 link editor (key navigation in popover)

--- app/views/CNeditor/CNeditor.coffee
+++ app/views/CNeditor/CNeditor.coffee
@@ -258,6 +258,8 @@
         @urlPopover.urlInput.value = segment.href
         @urlPopover.textInput.value = segment.textContent
         @urlPopover.style.display = 'block'
+        @urlPopover.urlInput.focus()
+        @urlPopover.urlInput.select()
         return true

     _hideUrlPopover : (segment) =>
@@ -270,10 +272,10 @@

     _initUrlPopover : () ->
         frag = document.createDocumentFragment()
-        divElm = document.createElement('div')
-        divElm.className = 'CNE_urlpop'
-        frag.appendChild(divElm)
-        divElm.innerHTML =
+        pop = document.createElement('div')
+        pop.className = 'CNE_urlpop'
+        frag.appendChild(pop)
+        pop.innerHTML =
             """
             Link
             (Ctrl+K)
@@ -288,15 +290,20 @@
         b = document.querySelector('body')
         # b.appendChild(frag)
         b.insertBefore(frag,b.firstChild)
-        pop = b.firstChild
-        @urlPopover = pop
         [btnOK,btnCancel] = pop.querySelectorAll('button')
         btnOK.addEventListener('click',@_validateUrlPopover)
         # btnOK = pop.querySelector('button')
         btnCancel.addEventListener('click',@_hideUrlPopover)
         [urlInput,textInput] = pop.querySelectorAll('input')
-        @urlPopover.urlInput = urlInput
-        @urlPopover.textInput = textInput
+        pop.urlInput = urlInput
+        pop.textInput = textInput
+        pop.addEventListener 'keypress', (e) =>
+            if e.keyCode == 13
+                @_validateUrlPopover()
+            else if e.keyCode == 27
+                @_hideUrlPopover()
+        pop.addEventListener('focusout',@_hideUrlPopover) #don't work ?
+        @urlPopover = pop

         return true


Only fetch 6 rails

Only fetch 6 rails

--- apps/home_2/queries/initial.coffee
+++ apps/home_2/queries/initial.coffee
@@ -1,6 +1,6 @@
 module.exports = """
   query {
-    home_page_modules {
+    home_page_modules(max_rails: 6){
       key
       params {
         id


add new variable @fail and set it to 0

add new variable @fail and set it to 0

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -1,6 +1,7 @@
 $ ->

   @current = ""
+  @fail = 0

   $("#question, #answer").hide()
   words = $.getJSON("spellings.json")


FIX: Fix broken filter for non-aggregate tutorial filters

--- src/app/tasks/task-inbox-list/task-inbox-list.coffee
+++ src/app/tasks/task-inbox-list/task-inbox-list.coffee
@@ -29,7 +29,6 @@
     )
     $scope.tutorialIdChanged = ->
       tutorialId = $scope.filters.tutorialIdSelected
-      return unless _.isString(tutorialId)
       if tutorialId == 'mine'
         $scope.filters.tutorials = $scope.unit.tutorialsForUserId(currentUser.id)
       else if tutorialId == 'all'


Fix an issue causing error when trying to broadcast non-saved file

--- lib/broadcast-target.coffee
+++ lib/broadcast-target.coffee
@@ -62,8 +62,11 @@
       content = content.replace /[\w-\.\/]+node_modules\/roaster\/node_modules/g, ''

   getHighlightedCodeContent: ->
-    @highlighter.highlightSync
-      filePath: @editor.getPath()
+    if @editor.getPath()?
+      @highlighter.highlightSync
+        filePath: @editor.getPath()
+    else
+      @getPlainTextContent()

   getPlainTextContent: ->
     content = @editor.getText?()


Fixed minor issue in clang-format

Fixed minor issue in clang-format

--- src/beautifiers/clang-format.coffee
+++ src/beautifiers/clang-format.coffee
@@ -21,7 +21,7 @@
     Dump contents to a given file
   ###
   dumpToFile: (name = "atom-beautify-dump", contents = "") ->
-    return new Promise((resolve, reject) =>
+    return new @Promise((resolve, reject) =>
       fs.open(name, "w", (err, fd) =>
         @debug('dumpToFile', name, err, fd)
         return reject(err) if err


prompt space

prompt space

--- lib/Repl/ReplClass.coffee
+++ lib/Repl/ReplClass.coffee
@@ -6,6 +6,7 @@

     processCmd:()->
       #console.log('2')
+      process.stdout.write(@prompt)
       if(@cmdQueue.length > 0)
         cmd = @cmdQueue.shift()
         @print += @prompt+' '+cmd
@@ -14,7 +15,7 @@
     processOutputData:(data) ->
       #console.log(@prompt)
       @print += ""+data
-      console.log(@print)
+      process.stdout.write(@print)
       @print = ""
       @processCmd()
       #@prompt = true
@@ -37,7 +38,11 @@
       @replProcess.stdout.on('data', (data)->self.processOutputData(data))
       @replProcess.stderr.on('data', (data)->self.processErrorData(data))
       @replProcess.on('close', ()->self.closeRepl())
+<<<<<<< HEAD
       #console.log(@print)
+=======
+      process.stdout.write(@print)
+>>>>>>> 81108ea3eb1e6ad154cee1be46bfe5550522007d

 myrepl = new Repl('ocaml', ['-noprompt'], "#")
 myrepl.writeInRepl("let _ = 2*2;;\n")


Update sdk references in device.coffee

Update sdk references in device.coffee

Change-type:patch

--- lib/actions/device.coffee
+++ lib/actions/device.coffee
@@ -411,7 +411,7 @@
 		tmpNameAsync = Promise.promisify(tmp.tmpName)
 		tmp.setGracefulCleanup()

-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		patterns = require('../utils/patterns')
 		{ runCommand } = require('../utils/helpers')


Re-highlight on encoding change

Re-highlight on encoding change

--- client/components/Submit.coffee
+++ client/components/Submit.coffee
@@ -60,7 +60,7 @@
         @doHighlight()

     componentDidUpdate:  (prevProps, prevState) ->
-        if @props.submit._id != prevProps.submit._id
+        if @props.submit._id != prevProps.submit._id or prevState.encoding != @state.encoding
             @doHighlight()

     doHighlight: ->


修改登陆描述

修改登陆描述

--- lib/login/login-view.coffee
+++ lib/login/login-view.coffee
@@ -21,7 +21,7 @@
             @div class: 'input-container', id: 'psw', =>
               @subview 'loginPassword', new TextEditorView(mini: true, placeholderText: 'Password...')
           @div class: 'login-row', =>
-            @a class: 'forgetpsw', href: config.forgetpwdrUrl, "#{forgetPwd}?"
+            @a class: 'forgetpsw', href: config.forgetpwdrUrl, "#{desc.forgetPwd}?"
           @div class: 'login-row', =>
             @button id: 'login', class: 'btn login-btn', desc.login
             @a '注册', class: 'btn login-btn', href: config.registerUrl


Added more functionality

Added more functionality

--- lib/class-complete.coffee
+++ lib/class-complete.coffee
@@ -4,10 +4,10 @@

     complete: ->
         editor = atom.workspace.activePaneItem
-        editor.insertText("
-            Class = (function() {\n
-                \tfunction Class() {\n
+        className = editor.getWordUnderCursor()
+        editor.insertText(" = (function() {\n
+                \tfunction " + className + "() {\n
                 \t\n
                 \t}\n
                 \n
-                \treturn Class;\n}());")
+                \treturn " + className + ";\n}());")


Remove useless code

Remove useless code

--- lib/command-runner.coffee
+++ lib/command-runner.coffee
@@ -77,7 +77,6 @@

   run: (callback) ->
     CommandRunner.getEnv (env) =>
-      env ?= process.env
       @runWithEnv(env, callback)

   runWithEnv: (env, callback) ->


since its verbose

since its verbose

--- lib/register-manager.coffee
+++ lib/register-manager.coffee
@@ -9,18 +9,18 @@
 # name - The name of the register to fetch.
 #
 # Returns the value of the given register or undefined
-validRegisterNames = /[a-zA-Z*+%_"]/
+validNames = /[a-zA-Z*+%_"]/

 module.exports =
 class RegisterManager
   constructor: (@vimState) ->
     {@editor, @globalVimState} = @vimState

-  isValidRegisterName: (name) ->
-    validRegisterNames.test(name)
+  isValidName: (name) ->
+    validNames.test(name)

   get: (name) ->
-    return unless @isValidRegisterName(name)
+    return unless @isValidName(name)
     if name is '"'
       name = settings.get('defaultRegister')

@@ -47,7 +47,7 @@
   #
   # Returns nothing.
   set: (name, {text, type}={}) ->
-    return unless @isValidRegisterName(name)
+    return unless @isValidName(name)
     type = @getCopyType(text) unless type

     if name is '"'


Toggle showing_all_issues in the sidebar. Fix #1375

--- admin/static/coffee/sidebar.coffee
+++ admin/static/coffee/sidebar.coffee
@@ -61,9 +61,10 @@

         # As issues get resolved, we need to make sure that we're showing the right elements
         issues_being_resolved: =>
-            if issues.length is 0
+            if issues.length is 0 and @.$('#issue-alerts').children().length is 0
                 @.$('.all-issues').hide()
                 @showing_all_issues = false
+                @issues_banner.set_showing_issues @showing_all_issues

         destroy: =>
             issues.off 'remove', @issues_being_resolved


Use Disposables instead of Subscriber mixin

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -9,34 +9,36 @@
     @div class: 'spell-check'

   initialize: (@editor) ->
+    @disposables = new CompositeDisposable
     @views = []
     @task = new SpellCheckTask()

-    @subscribe @editor.onDidChangePath =>
+    @disposables.add @editor.onDidChangePath =>
       @subscribeToBuffer()

-    @subscribe @editor.onDidChangeGrammar =>
+    @disposables.add @editor.onDidChangeGrammar =>
       @subscribeToBuffer()

-    @subscribe atom.config.onDidChange 'editor.fontSize', =>
+    @disposables.add atom.config.onDidChange 'editor.fontSize', =>
       @subscribeToBuffer()

-    @subscribe atom.config.onDidChange 'spell-check.grammars', =>
+    @disposables.add atom.config.onDidChange 'spell-check.grammars', =>
       @subscribeToBuffer()

     @subscribeToBuffer()

-    @subscribe @editor.onDidDestroy(@destroy.bind(this))
+    @disposables.add @editor.onDidDestroy(@destroy.bind(this))

   destroy: ->
     @unsubscribeFromBuffer()
+    @disposables.dispose()
     @task.terminate()

   unsubscribeFromBuffer: ->
     @destroyViews()

     if @buffer?
-      @unsubscribe(@buffer)
+      @bufferDisposable.dispose()
       @buffer = null

   subscribeToBuffer: ->
@@ -44,7 +46,7 @@

     if @spellCheckCurrentGrammar()
       @buffer = @editor.getBuffer()
-      @subscribe @buffer.onDidStopChanging => @updateMisspellings()
+      @bufferDisposable = @buffer.onDidStopChanging => @updateMisspellings()
       @updateMisspellings()

   spellCheckCurrentGrammar: ->


Revert console.log statement

Revert console.log statement

--- src/beautifiers/executable.coffee
+++ src/beautifiers/executable.coffee
@@ -130,7 +130,7 @@
   Run command-line interface command
   ###
   run: (args, options = {}) ->
-    console.log("Run: ", @cmd, args, options)
+    @debug("Run: ", @cmd, args, options)
     { cmd, cwd, ignoreReturnCode, help, onStdin, returnStderr, returnStdoutOrStderr } = options
     exeName = cmd or @cmd
     cwd ?= os.tmpdir()


reinitialize fileupload after validation failed

--- app/assets/javascripts/admin/form_answers.js.coffee
+++ app/assets/javascripts/admin/form_answers.js.coffee
@@ -74,33 +74,35 @@
       "form_answer_attachment[title]": $("#form_answer_attachment_title").val()
       "form_answer_attachment[restricted_to_admin]": $("#form_answer_attachment_restricted_to_admin").prop("checked")

-  $("#new_form_answer_attachment").fileupload
-    autoUpload: false
-    dataType: "html"
-    forceIframeTransport: true
-    add: (e, data) ->
-      $(".attachment-title").val(data.files[0].name)
-      $("#new_form_answer_attachment").closest(".sidebar-section").addClass("show-attachment-form")
-      $("#new_form_answer_attachment .btn-submit").focus().blur()
-      $("#new_form_answer_attachment .btn-submit").unbind("click").on "click", (e) ->
-        e.preventDefault()
-        data.submit()
-    success: (result, textStatus, jqXHR) ->
-      result = $($.parseHTML(result))
-      $("#attachment-buffer").append(result.text())
-
-      if $("#form-answer-attachment-valid", $("#attachment-buffer")).length
-        $("#application-attachment-form").html(result.text())
-        moveAttachDocumentButton()
-      else
-        $(".document-list .p-empty").addClass("visuallyhidden")
-        $(".document-list ul").append(result.text())
-        form = $("#new_form_answer_attachment")
-        form.closest(".sidebar-section").removeClass("show-attachment-form")
-        $("#form_answer_attachment_title").val(null)
-        $("#form_answer_attachment_restricted_to_admin").prop("checked", false)
-
-      $("#attachment-buffer").empty()
+  do initializeFileUpload = ->
+    $("#new_form_answer_attachment").fileupload
+      autoUpload: false
+      dataType: "html"
+      forceIframeTransport: true
+      add: (e, data) ->
+        $(".attachment-title").val(data.files[0].name)
+        $("#new_form_answer_attachment").closest(".sidebar-section").addClass("show-attachment-form")
+        $("#new_form_answer_attachment .btn-submit").focus().blur()
+        $("#new_form_answer_attachment .btn-submit").unbind("click").on "click", (e) ->
+          e.preventDefault()
+          data.submit()
+      success: (result, textStatus, jqXHR) ->
+        result = $($.parseHTML(result))
+        $("#attachment-buffer").append(result.text())
+
+        if $("#form-answer-attachment-valid", $("#attachment-buffer")).length
+          $("#application-attachment-form").html(result.text())
+          moveAttachDocumentButton()
+          initializeFileUpload()
+        else
+          $(".document-list .p-empty").addClass("visuallyhidden")
+          $(".document-list ul").append(result.text())
+          form = $("#new_form_answer_attachment")
+          form.closest(".sidebar-section").removeClass("show-attachment-form")
+          $("#form_answer_attachment_title").val(null)
+          $("#form_answer_attachment_restricted_to_admin").prop("checked", false)
+
+        $("#attachment-buffer").empty()

   moveAttachDocumentButton()


Remove logging on init

Remove logging on init

--- app/assets/javascripts/alongslide/alongslide.coffee
+++ app/assets/javascripts/alongslide/alongslide.coffee
@@ -67,8 +67,6 @@
       # Emit notification that layout is complete.
       $(document).triggerHandler 'alongslide.ready', @frames

-      console.log 'Alongslide ready!'
-
       @hashToPosition()
       FixedAspect.prototype.fitPanels(frameAspect)
       postRenderCallback()


Change the flash message verbiage for quiz migration

Fixes: QUIZ-2202

Test-Plan:
  - Start a quiz migration job
  - Make sure the flash message text says "Migration in progress"

Change-Id: Ia935639e5f8b00df3e58637407d6dddefc733c2b
Reviewed-on: https://gerrit.instructure.com/118001
Tested-by: Jenkins
Reviewed-by: John Corrigan <3d73713d0cc718326e4fd7c06c64e067ad6e2270@instructure.com>
QA-Review: Michael Hargiss <59e3fde81b8d9a4f5e0cc016a3c2695591912604@instructure.com>
Product-Review: Jayce Higgins <8f0644d105785a923e938c7b131e3ab930cb6139@instructure.com>

--- app/coffeescripts/views/quizzes/QuizItemView.coffee
+++ app/coffeescripts/views/quizzes/QuizItemView.coffee
@@ -111,9 +111,9 @@
       @$el.disableWhileLoading dfd
       $.when(dfd)
         .done (response, status, deferred) =>
-          $.flashMessage I18n.t('Migration successful')
+          $.flashMessage I18n.t('Migration in progress')
         .fail =>
-          $.flashError I18n.t("An error occurred while migrating.")
+          $.flashError I18n.t('An error occurred while migrating.')

     canDelete: ->
       @model.get('permissions').delete


Update config.coffee

Update config.coffee

--- lib/package/config.coffee
+++ lib/package/config.coffee
@@ -16,7 +16,7 @@
         title: 'Boot Mode'
         type: 'string'
         description: '`Basic` spins up a local Julia process on demand and is the most
-                      robust option. The `Cycler` will keep three local Julia processes
+                      robust option. The `Cycler` will keep a few local Julia processes
                       around at all times to reduce downtime when a process exits.
                       `External Terminal` opens an external terminal and connects it to Juno,
                       much like the `Julia Client: Connect Terminal` command.


Disabled check for now

Disabled check for now

--- lib/relations/many.coffee
+++ lib/relations/many.coffee
@@ -153,7 +153,9 @@
     collection = @_ensureCollection(model)
     current_related_model = collection.get(related_model.id)
     return if current_related_model is related_model
-    throw new Error "\nModel added twice: #{util.inspect(current_related_model)}\nand\n#{util.inspect(related_model)}" if current_related_model
+    if current_related_model
+      collection.remove(current_related_model)
+      # throw new Error "\nModel added twice: #{util.inspect(current_related_model)}\nand\n#{util.inspect(related_model)}"
     collection.add(related_model)

   # TODO: update isLoaded when adding / removing


changed isActive to use reactiveCurrent()

--- client/helpers.coffee
+++ client/helpers.coffee
@@ -16,7 +16,7 @@

     check view.hash, pattern

-    controller = FlowRouter.current()
+    controller = FlowRouter.reactiveCurrent()

     return false unless controller


Add trailing slashes to paths

Add trailing slashes to paths

--- lib/utils/glob-pages.coffee
+++ lib/utils/glob-pages.coffee
@@ -59,14 +59,13 @@
               pageData.path = "/"
             else
               pageData.path = "/" + parsed.dirname + "/"
-          # Else if not an index, create a path like /foo/bar
+          # Else if not an index, create a path like /foo/bar/
           # and rely upon static-site-generator-webpack-plugin to add index.html
           else
-
             if parsed.dirname is "."
-              pageData.path = "/" + parsed.name
+              pageData.path = "/" + parsed.name + "/"
             else
-              pageData.path = "/" + parsed.dirname + "/" + parsed.name
+              pageData.path = "/" + parsed.dirname + "/" + parsed.name + "/"

       # Set the "template path"
       else if parsed.name is "_template"


Handle links to internal files. Fix #220

--- client/message.coffee
+++ client/message.coffee
@@ -220,6 +220,7 @@

 #Session.setDefault 'images', {}
 images = {}
+imagesInternal = {}
 id2template = {}
 scrollToLater = null
 fileQuery = null
@@ -233,10 +234,13 @@
   .observeChanges
     added: (id, fields) ->
       images[id].file = fields.file
+      initImageInternal images[id].file, id if images[id].file?
     changed: (id, fields) ->
       if fields.file? and images[id].file != fields.file
         forceImgReload urlToFile id
+        imagesInternal[images[id].file].image = null if images[id].file?
         images[id].file = fields.file
+        initImageInternal images[id].file, id if images[id].file?

 initImage = (id) ->
   if id not of images
@@ -245,15 +249,30 @@
       file: null
     updateFileQuery()

+initImageInternal = (id, image = null) ->
+  if id not of imagesInternal
+    imagesInternal[id] =
+      count: 0
+      image: image
+  else if image?
+    imagesInternal[id].image = image
+
 checkImage = (id) ->
   return unless id of images
   ## Image gets unnaturally folded if it's referenced at least once.
-  messageFolded.set id, (images[id].count > 0) or images[id].naturallyFolded
+  messageFolded.set id, images[id].naturallyFolded or
+    images[id].count > 0 or
+    (imagesInternal[images[id].file]? and
+     imagesInternal[images[id].file].count > 0)
   ## No longer care about this image if it's not referenced and doesn't have
   ## a rendered template.
   if images[id].count == 0 and id not of id2template
     delete images[id]
     updateFileQuery()
+
+checkImageInternal = (id) ->
+  image = imagesInternal[id].image
+  checkImage image if image?

 messageDrag = (target, bodyToo = true) ->
   return unless target
@@ -305,8 +324,11 @@
   scrollToMessage @data._id if scrollToLater == @data._id
   #images = Session.get 'images'
   @images = {}
+  @imagesInternal = {}
   initImage @data._id
-  images[@data._id].file = @data.file
+  ## initImage calls updateFileQuery which will do this:
+  #images[@data._id].file = @data.file
+  #initImageInternal @data.file if @data.file?
   @autorun =>
     data = Template.currentData()
     ## If message is naturally folded, don't count images it references.
@@ -316,12 +338,20 @@
         images[id].count -= 1
         checkImage id
       @images = {}
+      for id of @imagesInternal
+        imagesInternal[id].count -= 1
+        checkImageInternal id
+      @imagesInternal = {}
     else
       newImages = {}
-      $($.parseHTML("
#{formatBody data.format, data.body}
"))
-      .find 'img[src^="/file/"]'
+      body = $($.parseHTML("
#{formatBody data.format, data.body}
"))
+      body.find 'img[src^="/file/"]'
       .each ->
         newImages[url2file @getAttribute('src')] = true
+      newImagesInternal = {}
+      body.find 'img[src^="/gridfs/"]'
+      .each ->
+        newImagesInternal[url2internalFile @getAttribute('src')] = true
       for id of @images
         unless id of newImages
           images[id].count -= 1
@@ -333,6 +363,17 @@
           images[id].count += 1
           checkImage id
       @images = newImages
+      for id of @imagesInternal
+        unless id of newImagesInternal
+          imagesInternal[id].count -= 1
+          checkImageInternal id
+      for id of newImagesInternal
+        unless id of @imagesInternal
+          #console.log 'source', id
+          initImageInternal id
+          imagesInternal[id].count += 1
+          checkImageInternal id
+      @imagesInternal = newImagesInternal

 scrollDelay = 750


Update Forgot password URL

Update Forgot password URL

--- lib/views/login-view.coffee
+++ lib/views/login-view.coffee
@@ -25,7 +25,7 @@
         @button click: 'login', id: 'loginButton', class: 'btn btn-primary', outlet: 'loginButton', 'Log in'
         @button click: 'cancel', id: 'cancelButton', class: 'btn', outlet: 'cancelButton', 'Cancel'
         @span class: 'three-quarters inline-block hidden', outlet: 'spinner'
-        @a href: 'https://build.spark.io/forgot-password', class: 'pull-right', 'Forgot password?'
+        @a href: 'https://build.particle.io/forgot-password', class: 'pull-right', 'Forgot password?'

   initialize: (serializeState) ->
     {CompositeDisposable} = require 'atom'


Set cursor to first position in spec

Set cursor to first position in spec

--- spec/link-spec.coffee
+++ spec/link-spec.coffee
@@ -45,7 +45,9 @@

           [here]: http://github.com
         """
+
         spyOn(shell, 'openExternal')
+        editor.setCursorBufferPosition([0,0])
         editorView.trigger('link:open')
         expect(shell.openExternal).not.toHaveBeenCalled()


decrease test duration

decrease test duration

--- spec/sloc.spec.coffee
+++ spec/sloc.spec.coffee
@@ -2,8 +2,8 @@
 should    = chai.should()
 langs     = require "./languages"
 sloc      = require "../src/sloc"
-manyLines = ('# \n'    for i in [0...100000]).join('')
-longLine  = ('### ###' for i in [0...100000]).join('')
+manyLines = ('# \n'    for i in [0...10000]).join('')
+longLine  = ('### ###' for i in [0...10000]).join('')

 describe "The sloc module", ->

@@ -46,8 +46,8 @@
     for k in sloc.keys
       (k in keys).should.be.true

-  it "can handle at least 100.000 lines", ->
+  it "can handle at least 10.000 lines", ->
     (-> sloc manyLines, "coffee") .should.not.throw()

-  it "can handle lines with at least 100.000 characters", ->
+  it "can handle lines with at least 10.000 characters", ->
     (-> sloc longLine, "coffee") .should.not.throw()


Fix tests

Fix tests

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -892,18 +892,18 @@
           expect(tabBar.tabForItem(editor1)).not.toExist()
           expect($(tabBar.tabForItem(editor2)).find('.title')).toHaveClass 'temp'

-      it 'makes the tab permanent when double clicking the tab', ->
+      it 'makes the tab permanent when clicking the tab', ->
         editor2 = null

         waitsForPromise ->
-          atom.workspace.open('sample.txt').then (o) -> editor2 = o
+          atom.workspace.open('sample.txt', pending: true).then (o) -> editor2 = o

         runs ->
           pane.activateItem(editor2)
-          dbclickEvt = document.createEvent 'MouseEvents'
-          dbclickEvt.initEvent 'dblclick'
-          tabBar.tabForItem(editor2).dispatchEvent dbclickEvt
+          expect($(tabBar.tabForItem(editor2)).find('.title')).toHaveClass 'temp'
+          triggerMouseEvent('mousedown', tabBar.tabForItem(editor2), which: 1)
           expect($(tabBar.tabForItem(editor2)).find('.title')).not.toHaveClass 'temp'
+          waits 0 # To prevent uncaught error "Pane has been destroyed"

     describe 'when opening views that do not have file paths', ->
       editor2 = null
@@ -911,21 +911,20 @@

       beforeEach ->
         waitsForPromise ->
+          atom.workspace.open('sample.txt', pending: true).then (o) ->
+            editor2 = o
+
+        waitsForPromise ->
           atom.packages.activatePackage('settings-view').then ->
             atom.workspace.open('atom://config').then (o) ->
               settingsView = o

-        waitsForPromise ->
-          atom.workspace.open('sample.txt', pending: true).then (o) ->
-            editor2 = o
-
       it 'creates a permanent tab', ->
         expect(tabBar.tabForItem(settingsView)).toExist()
         expect($(tabBar.tabForItem(settingsView)).find('.title')).not.toHaveClass 'temp'

-      it 'keeps an existing temp tab', ->
-        expect(tabBar.tabForItem(editor2)).toExist()
-        expect($(tabBar.tabForItem(editor2)).find('.title')).toHaveClass 'temp'
+      it 'destroys an existing temp tab', ->
+        expect(tabBar.tabForItem(editor2)).not.toExist()

     describe 'when editing a file', ->
       it 'makes the tab permanent', ->


Add spec for opening untitled editor on double click

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -351,3 +351,14 @@

         expect(dragStartEvent.originalEvent.dataTransfer.getData("text/plain")).toEqual editor1.getPath()
         expect(dragStartEvent.originalEvent.dataTransfer.getData("text/uri-list")).toEqual 'file://' + editor1.getPath()
+
+    describe "when the tab bar is double clicked", ->
+      it "opens a new empty editor", ->
+        newFileHandler = jasmine.createSpy('newFileHandler')
+        atom.workspaceView.on('application:new-file', newFileHandler)
+
+        tabBar.getTabs()[0].dblclick()
+        expect(newFileHandler.callCount).toBe 0
+
+        tabBar.dblclick()
+        expect(newFileHandler.callCount).toBe 1


version events -> master

version events -> master

--- client/version.coffee
+++ client/version.coffee
@@ -1 +1 @@
-@version = '[events]'
+@version = '[master]'


Always unsubscribe when changing buffers

Previously subscribeToBuffer() returned early
in certain cases without first unsubscribing to
the current buffer.

--- src/packages/spell-check/lib/spell-check-view.coffee
+++ src/packages/spell-check/lib/spell-check-view.coffee
@@ -21,12 +21,15 @@
   subscribeToBuffer: ->
     @destroyViews()
     @task?.abort()
-    return unless @spellCheckCurrentGrammar()

-    @buffer?.off '.spell-check'
-    @buffer = @editor.getBuffer()
-    @buffer.on 'contents-modified.spell-check', => @updateMisspellings()
-    @updateMisspellings()
+    if @buffer?
+      @buffer.off '.spell-check'
+      @buffer = null
+
+    if @spellCheckCurrentGrammar()
+      @buffer = @editor.getBuffer()
+      @buffer.on 'contents-modified.spell-check', => @updateMisspellings()
+      @updateMisspellings()

   spellCheckCurrentGrammar: ->
     grammar = @editor.getGrammar().scopeName


Don not transfer categories between projects.

--- src/spec/integration/integration-product.spec.coffee
+++ src/spec/integration/integration-product.spec.coffee
@@ -177,6 +177,7 @@
       deferred.promise

     syncProducts = (sync, new_product, old_product) ->
+      new_product.categories = [] # categories can not be transformed by Id to another project.
       deferred = Q.defer()
       try
         sync.buildActions(new_product, old_product).update (e, r, b) ->


Disable failing spec (should be fixed in the backend first)

--- src/spec/integration/integration-product.spec.coffee
+++ src/spec/integration/integration-product.spec.coffee
@@ -185,7 +185,8 @@
       done()
     .fail (error) -> done(_.prettify(error))

-  it 'should update variants (with SKUs) by removing -> adding them', (done) ->
+  # TODO: should be fixed in the backend
+  xit 'should update variants (with SKUs) by removing -> adding them', (done) ->
     # set some variants + SKUs first
     @newProduct.masterVariant.sku = 'v1'
     @newProduct.variants = [


synctable: another subtle issue when connection closed but there might still be another message before feed canceled... ?

--- salvus/smc-util/synctable.coffee
+++ salvus/smc-util/synctable.coffee
@@ -309,6 +309,8 @@
     # Handle an update of all records from the database.  This happens on
     # initialization, and also if we disconnect and reconnect.
     _update_all: (v) =>
+        if @_closed
+            return
         #console.log("_update_all(#{@_table})", v)
         if not v?
             console.warn("_update_all(#{@_table}) called with v=undefined")
@@ -378,6 +380,8 @@
             @save()

     _update_change: (change) =>
+        if @_closed
+            return
         #console.log("_update_change", change)
         if not @_value_local?
             console.warn("BUG: tried to call _update_change even though local not yet defined")


took out the cam2d stuff

took out the cam2d stuff

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -6,7 +6,7 @@

 # global View variables
 cam = null
-cam2d = null
+#cam2d = null

 #constants
 CAM_DISTANCE = 140
@@ -64,7 +64,7 @@
     # calculate which pixel are visible on the trianglesplane
     # after moving around.
     cam = new Flycam CAM_DISTANCE
-    cam2d = new Flycam2d CAM_DISTANCE
+    #cam2d = new Flycam2d CAM_DISTANCE

     # FPS stats
     stats = new Stats()
@@ -83,7 +83,7 @@

     # refresh the scene once a bucket is loaded
     # FIXME: probably not the most elgant thing to do
-    $(window).on("bucketloaded", => cam.hasChanged = true; cam2d.hasChanged = true)
+    $(window).on("bucketloaded", => cam.hasChanged = true)

   animate : ->
     @renderFunction()
@@ -100,8 +100,8 @@
     # ATTENTION: this limits the FPS to 30 FPS (depending on the keypress update frequence)
     if cam.hasChanged is false
       return
-    if cam2d.hasChanged is false
-      return
+    #if cam2d.hasChanged is false
+    #  return

     @updateTrianglesplane()

@@ -112,7 +112,7 @@
     @stats.update()

     cam.hasChanged = false
-    cam2d.hasChanged = false
+    #cam2d.hasChanged = false
     @rendererxy.render @scenexy, @cameraxy
     @rendereryz.render @sceneyz, @camerayz
     @rendererxz.render @scenexz, @cameraxz
@@ -174,7 +174,7 @@
   draw : ->
     #FIXME: this is dirty
     cam.hasChanged = true
-    cam2d.hasChanged = true
+    #cam2d.hasChanged = true

   setMatrix : (matrix) ->
     cam.setMatrix(matrix)
@@ -232,7 +232,7 @@
         # why z? keep in mind the plane is rotated 90°
         g.scale.x = g.scale.z = x
         cam.hasChanged = true
-        cam2d.hasChanged = true
+        #cam2d.hasChanged = true

   zoomIn : ->
     if cam.getZoomStep() > 0


fix project results order, sort by time

fix project results order, sort by time

--- app/coffee/MongoPackManager.coffee
+++ app/coffee/MongoPackManager.coffee
@@ -154,6 +154,9 @@
 				return false if cutoff? && ts < cutoff
 				return true

+			timeOrder = (a, b) ->
+				b.meta.end_ts - a.meta.end_ts
+
 			updates = MongoPackManager._filterAndLimit(updates, unpackedSet, filterFn, limit)
 			#console.log 'initial updates are', updates

@@ -210,15 +213,15 @@

 			tail.toArray (err, result2) ->
 				if err?
-					return callback err, updates
+					return callback err, updates.sort timeOrder
 				else
 					applyAndUpdate result2
 					overlap.toArray (err, result3) ->
 						if err?
-							return callback err, updates
+							return callback err, updates.sort timeOrder
 						else
 							applyAndUpdate result3
-							callback err, updates
+							callback err, updates.sort timeOrder

 	_unpackResults: (updates) ->
 		#	iterate over the updates, if there's a pack, expand it into ops and


Minor improvements on wiki controller.

Minor improvements on wiki controller.

--- app/coffee/controllers/wiki.coffee
+++ app/coffee/controllers/wiki.coffee
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.

-WikiController = ($scope, $rootScope, $location, $routeParams, $data, rs, $confirm) ->
+WikiController = ($scope, $rootScope, $location, $routeParams, $data, rs, $confirm, $q) ->
     $rootScope.pageSection = 'wiki'
     $rootScope.projectId = parseInt($routeParams.pid, 10)
     $rootScope.pageBreadcrumb = [
@@ -31,6 +31,24 @@

     $data.loadProject($scope)

+    loadAttachments = (page) ->
+        rs.getWikiPageAttachments(projectId, page.id).then (attachments) ->
+            $scope.attachments = attachments
+
+    saveNewAttachments = ->
+        if $scope.newAttachments.length == 0
+            return
+
+        promises = []
+        for attrachment in $scope.newAttachments
+            promise = rs.uploadWikiPageAttachment(projectId, $scope.page.id, attrachment)
+            promises.push(promise)
+
+        promise = Q.all(promises)
+        promise.then ->
+            $scope.newAttachments = []
+            loadAttachments($scope.page)
+
     promise = rs.getWikiPage(projectId, slug)
     promise.then (page) ->
         $scope.page = page
@@ -39,11 +57,6 @@

     promise.then null, (data) ->
         $scope.formOpened = true
-
-    loadAttachments = (page) ->
-        rs.getWikiPageAttachments(projectId, page.id).then (attachments) ->
-            $scope.attachments = attachments
-        $scope.newAttachments = []

     $scope.openEditForm = ->
         $scope.formOpened = true
@@ -68,15 +81,9 @@
             $scope.page.content = $scope.content
             $scope.page.save().then (page) ->
                 $scope.page = page
+                $scope.formOpened = false
+                $scope.content = $scope.page.content
                 saveNewAttachments()
-                $scope.formOpened = false
-
-    saveNewAttachments = ->
-        _.forEach $scope.newAttachments, (newAttach) ->
-            rs.uploadWikiPageAttachment(projectId, $scope.page.id, newAttach).then (attach) ->
-                $scope.deleteNewAttachment(newAttach)
-                $scope.attachments.push(attach)
-                $scope.$apply()

     $scope.deletePage = ->
         $scope.page.remove().then ->
@@ -95,4 +102,5 @@

 module = angular.module("greenmine.controllers.wiki", [])
-module.controller("WikiController", ['$scope', '$rootScope', '$location', '$routeParams', '$data', 'resource', "$confirm", WikiController])
+module.controller("WikiController", ['$scope', '$rootScope', '$location', '$routeParams',
+                                     '$data', 'resource', "$confirm", "$q", WikiController])


Adds alerts fadeOut on close click

Adds alerts fadeOut on close click

--- coffee/furatto.coffee
+++ coffee/furatto.coffee
@@ -1,3 +1,8 @@
 window.Furatto =
   name: 'Furatto'
   version: '1.0.0'
+
+$('.alert .close').each ->
+  $(@).click (e) ->
+    e.preventDefault()
+    $(@).parent().fadeOut()


App: render view on showPage

App: render view on showPage

--- coffee/src/app.coffee
+++ coffee/src/app.coffee
@@ -27,6 +27,8 @@

   showPage: (page) =>
     @view.content page
+    unless @view.rendering
+      @view.render()

   start: =>


Add elm linter

Add elm linter

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -34,4 +34,8 @@
   "tree-view":
     hideVcsIgnoredFiles: true
   "ide-haskell": {}
-  "language-elm": {}
+  "language-elm":
+    elmOraclePath: '''
+      ~/node/bin/elm-oracle
+
+    '''


Button: When text is empty, output an empty <span> instead of nothing.

This fixed unrecognized "max-chars" in Select with empty Option.

--- src/elements/Button/Button.coffee
+++ src/elements/Button/Button.coffee
@@ -856,13 +856,13 @@

 	setText: (@__txt) ->
 		if isEmpty(@__txt)
-			@empty("center")
-		else
-			span = $text(@__txt)
-			if not @__hasAriaLabel
-				span.id = "button-text-"+@getUniqueId()
-				@setAria("labelledby", span.id)
-			@replace(span, "center")
+			@__txt = ''
+
+		span = $text(@__txt)
+		if not @__hasAriaLabel
+			span.id = "button-text-"+@getUniqueId()
+			@setAria("labelledby", span.id)
+		@replace(span, "center")

 	setTextMaxChars: (max_chars) ->
 		CUI.DOM.setAttribute(@getCenter().firstChild, "data-max-chars", max_chars)


Changed to old Port

Changed to old Port

--- gulp/development/webserver.coffee
+++ gulp/development/webserver.coffee
@@ -15,7 +15,7 @@

     app.use( express.static(__projectdir) )

-    http.listen 71080, ->
+    http.listen 8001, ->
       console.log "yea listengin!"

     userAmount = 0


remove default number of rows shown to fix pagination

--- app/assets/javascripts/category_page.coffee
+++ app/assets/javascripts/category_page.coffee
@@ -1,7 +1,6 @@
 $ ->
   $(".table").dataTable
     sPaginationType: "bootstrap"
-    iDisplayLength: "50"
     sDom: "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>"
     sWrapper: "dataTables_wrapper form-inline"
     oLanguage:


fix keg_common $form

fix keg_common $form

--- app/assets/javascripts/keg_common.js.coffee
+++ app/assets/javascripts/keg_common.js.coffee
@@ -112,14 +112,14 @@
 bind_clearable_fields = ->
   $("[data-clearable-field]").each ->
     $input = $(@)
-    # $form = (if $input.length > 0 then $($input[0].form) else $())
-    $form = $input.parent("form")
+    $form = (if $input.length > 0 then $($input[0].form) else $())
+    # $form = $input.parent("form")
     $link = $("")
     $(@).after $link

     $input.on
       change: ->
-        console.log "change", $input
+        # console.log "change", $input
         $form.submit()
       # changeDate: ->
       #   console.log "changeDate", $input


fix mini js bug

fix mini js bug

--- app/assets/javascripts/modules/gmaps.coffee
+++ app/assets/javascripts/modules/gmaps.coffee
@@ -124,7 +124,7 @@
         # If category links exist: event listener on input change to update them
         if $('.nav-sections__list').length
           $('#search_form_search_location').on(
-            'input', Clarat.GMaps.PlacesAutocomplete.updateCategoryLink)
+            'input', Clarat.GMaps.PlacesAutocomplete.updateCategoryLinks)
           $('#search_form_generated_geolocation').on(
             'input', Clarat.GMaps.PlacesAutocomplete.updateCategoryLinks)
           google.maps.event.addListener(


Workspace: Removed splitView resizing after mainView transitionend.

--- client/app/CommonViews/workspace/panel/panel.coffee
+++ client/app/CommonViews/workspace/panel/panel.coffee
@@ -19,10 +19,6 @@
     @createHeaderHint()     if options.hint

     @createLayout()
-
-    if @splitView
-      @getSingleton("mainView").once "transitionend", =>
-        @splitView._windowDidResize()

   createHeader: (title = "") ->
     @header     = new KDView


1tik asagi alindi :P

1tik asagi alindi :P

--- client/app/MainApp/book/embedded/socialshare.coffee
+++ client/app/MainApp/book/embedded/socialshare.coffee
@@ -6,7 +6,7 @@
       tagName       : "iframe"
       attributes    :
         width       : 90
-        height      : 21
+        height      : 20
         frameborder : 0
         scrolling   : "no"
         allowtransparency: "true"


Add turbolinks 5 event name

Add turbolinks 5 event name

--- app/assets/javascripts/select2-rails.coffee
+++ app/assets/javascripts/select2-rails.coffee
@@ -3,4 +3,4 @@
     selector = if typeof selector == 'string' then selector else '.select2-rails'
     $(selector).select2()

-$(document).on 'ready page:load', window.Binco.Select2.load
+$(document).on 'ready turbolinks:load', window.Binco.Select2.load


fixing collection refresh

fixing collection refresh

--- server/api/lib/collection.coffee
+++ server/api/lib/collection.coffee
@@ -30,7 +30,7 @@
   return API.es.map this._index, this._type, mapping, true # would overwrite any existing mapping
 API.collection.prototype.mapping = (original) -> return if this._mapping and original then this._mapping else API.es.mapping this._index, this._type

-API.collection.prototype.refresh = () -> API.es.refresh this._route
+API.collection.prototype.refresh = () -> API.es.refresh this._index

 API.collection.prototype.delete = (confirm, history) ->
   # TODO who should be allowed to do this, and how should it be recorded in history, if history is not itself removed?


Retrieve SLQ data

Retrieve SLQ data

--- public/javascripts/game.photography.coffee
+++ public/javascripts/game.photography.coffee
@@ -47,6 +47,38 @@
       console.log("current position", this.position, "new position", location.position, "distance travelled", distanceTravelled(this.position, location.position) + 'km')
       @position = location.position
       @playerMarker.setPosition(new google.maps.LatLng(location.position.lat, location.position.lng))
+
+  retrieveResources = (amount) ->
+    reqParam = {
+      resource_id: '9913b881-d76d-43f5-acd6-3541a130853d',
+      limit: amount
+    }
+    $.ajax {
+      url: 'http://data.gov.au/api/action/datastore_search',
+      data: reqParam,
+      dataType: 'jsonp',
+      cache: true
+  }
+
+  processData = (data) ->
+    processedData = []
+    for item in data.result.records
+      if item['dcterms:spatial']
+        if item['dcterms:spatial'].split(';')[1]
+          processedData.push(item['dcterms:spatial'].split(';'))
+    return processedData
+
+  generateMarkers = (set) ->
+    marker = []
+    i = 0
+    for place in set
+      lat = parseFloat(place[1].split(',')[0])
+      lng = parseFloat(place[1].split(',')[1])
+      marker[i] = new location {lat, lng}, place[0]
+      marker[i].addTo(googleMap)
+      i++
+    return
+
   googleMap = new google.maps.Map($('#map')[0], {
     zoom: 6,
     center: {lat:-27.4698, lng: 153.0251}
@@ -56,3 +88,7 @@
   mark.initTo(googleMap)
   #brisbane = new location {lat:-27.4698, lng: 153.0251}, 'brisbane'
   #brisbane.addTo(@map)
+
+  retrieveResources(100).then (res) ->
+    generateMarkers(processData(res))
+    return


Updating update on date selection.

Updating update on date selection.

--- app/assets/javascripts/views/profile.coffee
+++ app/assets/javascripts/views/profile.coffee
@@ -7,7 +7,7 @@
   setupMainChart: ->
     @setMainChartCanvasSize()
     ctx = $("#standard-report")[0].getContext '2d'
-    data = @controller.get('controllers.logs').onsiteVsOffsiteReport()
+    data = @get('controller.controllers.logs').onsiteVsOffsiteReport()
     @set 'main_chart', new Chart(ctx).Pie data,
       segmentShowStroke: false
       tooltipTemplate: '<%= value %>%'
@@ -66,16 +66,17 @@
     moment(controller.get('start_date')).isBefore moment(controller.get('end_date'))

   updateLogs: ->
-    controller = @get('controller')
-    logs_controller = controller.get('controllers.logs')
+    logs_controller = @get('controller.controllers.logs')

     logs_controller.getLogsFor
-      profile_url: controller.get('model.profile_url')
-      start_date: controller.get('start_date')
-      end_date: controller.get('end_date')
+      profile_url: @get('controller.model.profile_url')
+      start_date: @get('controller.start_date')
+      end_date: @get('controller.end_date')
     .then =>
       chart = @get('main_chart')
       data = logs_controller.onsiteVsOffsiteReport()

-      chart.data = data
+      chart.segments[0].value = data[0].value
+      chart.segments[1].value = data[1].value
+
       chart.update()


Correction erreur fichier mail_handler.coffee

--- server/mails/mail_handler.coffee
+++ server/mails/mail_handler.coffee
@@ -120,7 +120,8 @@
             subjectKey = 'email delete title'
             mailOptions =
                 to: guest.email
-                subject: localization.t subjectKey, description: event.description
+                subject: localization.t subjectKey
+                description: event.description
                 content: localization.t 'email delete content', templateOptions
                 html: htmlTemplate templateOptions
             cozydb.api.sendMailFromUser mailOptions, (err) ->


chat: fix chatpane type to privatemessage

--- client/ide/lib/views/chat/idechatmessagepane.coffee
+++ client/ide/lib/views/chat/idechatmessagepane.coffee
@@ -14,6 +14,9 @@
   constructor: (options = {}, data)->

     options.cssClass = 'privatemessage'
+
+    # this is backwards compatibility related. ~Umut
+    options.type = 'privatemessage'

     super options, data


video: allow only super-admins to start video

--- client/ide/lib/views/chat/idechatmessagepane.coffee
+++ client/ide/lib/views/chat/idechatmessagepane.coffee
@@ -7,6 +7,7 @@
 ReplyInputWidget     = require 'activity/views/privatemessage/replyinputwidget'
 PrivateMessagePane   = require 'activity/views/privatemessage/privatemessagepane'
 isMyChannel          = require 'app/util/isMyChannel'
+isVideoFeatureEnabled = require 'app/util/isVideoFeatureEnabled'

 IDEChatMessageParticipantAvatar = require './idechatmessageparticipantavatar'

@@ -163,16 +164,17 @@
       'Learn More' : { separator: yes, callback : -> kd.utils.createExternalLink 'http://learn.koding.com/collaboration' }
       # 'Settings' : { callback : @getDelegate().bound 'showSettingsPane' }

-    # wtf? this somehow means that we are host. ~Umut
-    unless @isInSession
+    isHost = not @isInSession
+
+    if isVideoFeatureEnabled() and isHost
       seperator = yes
       if @videoActive
       then menu['End Video Chat'] = { seperator, callback: @bound 'requestEndVideo' }
       else menu['Start Video Chat']  = { seperator, callback: @bound 'requestStartVideo' }

-    if @isInSession
-    then menu['Leave Session'] = { callback : => @parent.settingsPane.leaveSession() }
-    else menu['End Session']   = { callback : => @parent.settingsPane.stopSession() }
+    if isHost
+    then menu['End Session']   = { callback : => @parent.settingsPane.stopSession() }
+    else menu['Leave Session'] = { callback : => @parent.settingsPane.leaveSession() }

     return menu


Define unset real time bindings method

Define unset real time bindings method

--- client/ide/lib/workspace/panes/ideeditorpane.coffee
+++ client/ide/lib/workspace/panes/ideeditorpane.coffee
@@ -323,9 +323,7 @@
     @rtm
       .on 'TextInsertedIntoString', @bound 'handleCollaborativeStringEvent'
       .on 'TextDeletedFromString',  @bound 'handleCollaborativeStringEvent'
-      .on 'RealtimeManagerWillDispose', =>
-        @rtm.unbindRealtimeListeners string, 'string'
-        @removeAllCursorWidgets()
+      .on 'RealtimeManagerWillDispose', @bound 'unsetRealtimeBindings'

   handleCollaborativeStringEvent: (changedString, change) ->
@@ -395,6 +393,14 @@
     @dontEmitChangeEvent = no

+  unsetRealtimeBindings: ->
+
+    return  unless string = @rtm.getFromModel @getFile().path
+
+    @rtm.unbindRealtimeListeners string, 'string'
+    @removeAllCursorWidgets()
+
+
   makeReadOnly: ->

     @getEditor()?.setReadOnly yes


FinderPane: Emit RootFolderChanged event and handle it.

--- client/ide/lib/workspace/panes/idefinderpane.coffee
+++ client/ide/lib/workspace/panes/idefinderpane.coffee
@@ -67,6 +67,7 @@
       @finderController.treeController.deleteWorkspaceRootFolder machineUId, rootPath

+    fc.on 'RootFolderChanged', (path) => @emitChangeHappened 'RootFolderChanged', path
     tc.on 'FolderCollapsed',   (path) => @emitChangeHappened 'Collapsed', path
     tc.on 'FolderExpanded',    (path) => @emitChangeHappened 'Expanded', path

@@ -90,6 +91,7 @@
       context  :
         action : action
         path   : path
+        uid    : @mountedMachine.uid

     return change

@@ -98,16 +100,22 @@

     return  unless change.type is 'FileTreeInteraction'

-    {context} = change
+    { context } = change
     return  unless context

-    {action} = context
+    { action, path, uid } = context
+
     fc = @finderController
     tc = fc.treeController

     tc.dontEmitChangeEvent = yes
+    fc.dontEmitChangeEvent = yes

-    if      action is 'Expanded'  then fc.expandFolders context.path
-    else if action is 'Collapsed' then tc.collapseFolder tc.nodes[context.path]
+    switch action
+      when 'Expanded'           then fc.expandFolders  path
+      when 'Collapsed'          then tc.collapseFolder tc.nodes[path]
+      when 'RootFolderChanged'  then fc.updateMachineRoot uid, path
+

     tc.dontEmitChangeEvent = no
+    fc.dontEmitChangeEvent = no


Fix error in the pagination of memberships

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -39,7 +39,7 @@
         "$tgResources",
         "$routeParams",
         "$q",
-        "$location"
+        "$tgLocation"
     ]

     constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) ->


fix membership pagination

fix membership pagination

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -82,6 +82,7 @@
         return @rs.memberships.list(@scope.projectId, httpFilters).then (data) =>
             @scope.memberships = _.filter(data.models, (membership) ->
                                     membership.user == null or membership.is_user_active)
+
             @scope.page = data.current
             @scope.count = data.count
             @scope.paginatedBy = data.paginatedBy
@@ -110,7 +111,7 @@
 ## Member Avatar Directive
 #############################################################################

-MembershipsDirective = ($template) ->
+MembershipsDirective = ($template, $compile) ->
     template = $template.get("admin/admin-membership-paginator.html", true)

     linkPagination = ($scope, $el, $attrs, $ctrl) ->
@@ -158,7 +159,11 @@
                 else
                     pages.push({classes: "page", num: i, type: "page"})

-            $pagEl.html(template(options))
+            html = template(options)
+            html = $compile(html)($scope)
+
+            $pagEl.html(html)
+            $pagEl.show()

         $scope.$watch "memberships", (value) ->
             # Do nothing if value is not logical true
@@ -198,7 +203,7 @@

     return {link:link}

-module.directive("tgMemberships", ["$tgTemplate", MembershipsDirective])
+module.directive("tgMemberships", ["$tgTemplate", "$compile", MembershipsDirective])

 #############################################################################
@@ -400,6 +405,10 @@
             $confirm.askOnDelete(title, message).then (finish) ->
                 onSuccess = ->
                     finish()
+
+                    if $scope.page > 1 && ($scope.count - 1) <= $scope.paginatedBy
+                        $ctrl.selectFilter("page", $scope.page - 1)
+
                     $ctrl.loadMembers()

                     text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_DELETE")


Override console.(log|error) and process.std(out|err) when headless

--- src/initialize-test-window.coffee
+++ src/initialize-test-window.coffee
@@ -32,7 +32,17 @@

     {testRunnerPath, legacyTestRunnerPath, headless, logFile, testPaths, env} = getWindowLoadSettings()

-    unless headless
+    if headless
+      # Install console functions that output to stdout and stderr.
+      util = require 'util'
+
+      Object.defineProperties process,
+        stdout: {value: remote.process.stdout}
+        stderr: {value: remote.process.stderr}
+
+      console.log = (args...) -> process.stdout.write "#{util.format(args...)}\n"
+      console.error = (args...) -> process.stderr.write "#{util.format(args...)}\n"
+    else
       # Show window synchronously so a focusout doesn't fire on input elements
       # that are focused in the very first spec run.
       remote.getCurrentWindow().show()


server: disable team payment data check on team creation tests

--- servers/lib/server/handlers/createteam.test.coffee
+++ servers/lib/server/handlers/createteam.test.coffee
@@ -86,9 +86,9 @@
           expect(err).to.not.exist
           expect(group.title).to.be.equal companyName

-          expect(group.payment).to.exist
-          expect(group.payment.subscription).to.exist
-          expect(group.payment.subscription.status).to.be.equal Status.TRIALING
+          # expect(group.payment).to.exist
+          # expect(group.payment.subscription).to.exist
+          # expect(group.payment.subscription.status).to.be.equal Status.TRIALING

           allowedDomains.forEach (domain) ->
             expect(group.allowedDomains).to.include domain


also remove initalRequestPath cookie when signing out

--- app/components/group-page/group-page.coffee
+++ app/components/group-page/group-page.coffee
@@ -44,6 +44,7 @@
           Toast.show("You've been signed out")
           ipCookie.remove('currentGroupId')
           ipCookie.remove('currentUserId')
+          ipCookie.remove('initialRequestPath')
           $location.path('/')

     return


Working +history command for "add"s in the history

--- bot.iced
+++ bot.iced
@@ -174,6 +174,21 @@

 			balance = user.funds
 			bot.sendMessage chatterID, "You currently have #{balance} DOGE to tip with"
+		when "+history"
+			await checkIfRegistered chatterID, defer(registered, user)
+			if registered is undefined
+				return bot.sendMessage chatterID, "The database ran into an error"
+			unless registered
+				return bot.sendMessage chatterID, "You must register before you can add funds. Do this by sending '+register'."
+			history = user.history
+			history = history.splice history.length - 10, history.length # Last 10 transactions in the history
+			message = "#{user.name}, here are your last 10 transactions:\n"
+			message += "Current balance: #{user.funds}\n"
+			for item in history by -1 # Go backwards
+				switch item.type
+					when "add"
+						message += "\n\tType: add, Amount: #{item.amount}, Status: #{item.status}"
+			bot.sendMessage chatterID, message

 bot.on "friend", (steamID, Relationship) ->
 	if pendingInvites.indexOf(steamID) isnt -1