timeInputView: bugfix: edit setMax when time isNaN

this allows setMax to bound previous infinity

--- coffee/src/views/time_input_view.coffee
+++ coffee/src/views/time_input_view.coffee
@@ -67,9 +67,11 @@
   setMax: (max) =>
     return unless _.isNumber(max) and !_.isNaN max

-    # update max and call onInputChanged in case current time is now invalid
     @_max = max
-    @_onInputChanged()
+
+    # if current input is invalid, set to max value, else handle state change
+    invalidInput = _.isNaN parseFloat @input.val()
+    if invalidInput then @_handleInput @_max else @_onInputChanged()

   _bound: (val) =>


Minor typo fixes

Minor typo fixes

--- app/pages/lab/how-to-page.cjsx
+++ app/pages/lab/how-to-page.cjsx
@@ -20,7 +20,7 @@
       then click the "Build a Project" button in the top right. Here you can see all of the projects you own and
       collaborate on. Click on "Create a project" to start building.

-      [![Project Builder screenshot](./assets/how-to-lab/how-to-2.jpg)](./assets/how-to-lab/how-to-3.jpg)
+      [![Project Builder screenshot](./assets/how-to-lab/how-to-2.jpg)](./assets/how-to-lab/how-to-2.jpg)

       **Start building:** Now you're in the Project Builder itself. This is where the magic happens.
       On the left-hand side, you've got your main menus: Project, Workflow, and Subjects. These are
@@ -30,7 +30,7 @@
       of tasks that you ask volunteers to do, and subjects are the things (usually images)
       that volunteers do those tasks on.

-      [![Project details editor screenshot](./assets/how-to-lab/how-to-3.jpg)](./assets/how-to-lab/how-to-2.jpg)
+      [![Project details editor screenshot](./assets/how-to-lab/how-to-3.jpg)](./assets/how-to-lab/how-to-3.jpg)

       ### Define your project.

@@ -46,17 +46,17 @@
       This is where you build the tasks that volunteers actually do. When you first get to this page,
       you'll see there is a sample task (specifically a question) already in place.

-      [![Project workflow editor screenshot](./assets/how-to-lab/how-to-4.jpg)](./assets/how-to-lab/how-to-3.jpg)
+      [![Project workflow editor screenshot](./assets/how-to-lab/how-to-4.jpg)](./assets/how-to-lab/how-to-4.jpg)

       We want to create this:

-      [![Project workflow example](./assets/how-to-lab/how-to-5.jpg)](./assets/how-to-lab/how-to-4.jpg)
+      [![Project workflow example](./assets/how-to-lab/how-to-5.jpg)](./assets/how-to-lab/how-to-5.jpg)

       We'll start by replacing the sample text with our question, which asks people how many cats are in the
       image. We add more answers using the "+" button under the "Yes" answer. Use the screenshot below to fill
       in the workflow details (you may need to zoom in!)

-      [![Project workflow editing screenshot](./assets/how-to-lab/how-to-6.jpg)](./assets/how-to-lab/how-to-5.jpg)
+      [![Project workflow editing screenshot](./assets/how-to-lab/how-to-6.jpg)](./assets/how-to-lab/how-to-6.jpg)

       We added both text and images into the _Help Text_ box using the markdown language ([learn more about markdown](http://markdownlivepreview.com)).

@@ -227,7 +227,7 @@

       Add people to your team and specify what their roles are so that they have the right access to the tools they need (including access to the project before it's public).

-      [![Project collaborator editor screenshot](./assets/how-to-lab/how-to-13.jpg)](./assets/how-to-lab/how-to-13/jpg)
+      [![Project collaborator editor screenshot](./assets/how-to-lab/how-to-13.jpg)](./assets/how-to-lab/how-to-13.jpg)

       _Owner_: The owner is the original project creator. There can be only one.


refactor

refactor

--- app/scripts/robot/radio.coffee
+++ app/scripts/robot/radio.coffee
@@ -7,6 +7,10 @@
   ($interval) ->
     # whether or not we've initialized
     _init = false
+
+    # thing
+    _ndl3Radio = undefined
+
     # an object of arrays, where the keys are
     # the channel of events to repond to
     # and the values are arrays of callbacks
@@ -30,20 +34,17 @@
         value: num
         })

-    init = ->
+    radioInit = (radioAddr = "AAAAAAAAAAAAAAAA", portPath = "/dev/ttyUSB0") ->
       SerialPort = require("serialport").SerialPort
-      radioAddr = "AAAAAAAAAAAAAAAA"
-      serialPort = new SerialPort("/dev/ttyUSB0",
-        baudrate: 57600
-      )
+      serialPort = new SerialPort(portPath, baudrate: 57600)

       radio = requireNode('kyleradio')
-      rad = new radio.Radio()
+      _ndl3Radio = new radio.Radio()

-      rad.connectXBee(radioAddr, serialPort)
+      _ndl3Radio.connectXBee(radioAddr, serialPort)
       # $interval( ->
       #   console.log 'try to send shit'
-      #   rad.send(
+      #   _ndl3Radio.send(
       #     PiELESAnalogValues: [127,127,127,127,127,127,127]
       #     PiELESDigitalValues: [true, true, true, true, true, true, true, true]
       #     )
@@ -51,9 +52,10 @@

     return {
       init: ->
+        unless _init
+          radioInit()
         _init = true
         return true
-      initRadio: init
       enableMock: (millis = 100) ->
         unless mockEnabled
           mockEnabled = true
@@ -75,8 +77,15 @@
         return true
       send: (channels..., object) ->
         return false unless _init # exit if we're not initialized
+        return false unless object?

         for channel in channels
+          object._channel = channel
+
+          _ndl3Radio.send(
+            channel: object
+            )
+
           console.log "radio channel 'chname': fake sent object"
           .replace(/object/, String(object))
           .replace(/chname/, channel)


Tests: Environments: Domain: updated assignDomain test.

--- tests/src/environments/domain.coffee
+++ tests/src/environments/domain.coffee
@@ -54,12 +54,18 @@

   assignDomain: (browser) ->

-     domainName = environmentHelpers.addDomain(browser)
+    domainName = environmentHelpers.addDomain(browser)

-     browser
+    browser
       .waitForElementVisible     domainItem, 20000
       .click                     domainItem + ' .koding-on-off.on'
       .waitForElementVisible     loader, 10000
       .waitForElementNotVisible  loader, 20000
+      .refresh()
+
+    environmentHelpers.openVmSettingsModal(browser)
+    environmentHelpers.clickMoreButtonInVMSettingsModal(browser)
+
+    browser
       .waitForElementVisible     domainItem + ' .koding-on-off.off', 20000
       .end()


turned if-else chain into a switch

turned if-else chain into a switch

--- rock.coffee
+++ rock.coffee
@@ -140,129 +140,131 @@
 		debug "tokens:"
 		debug "  #{t}" for t in tok

-	if fst == 'jump'
-		# unconditional jump (jump label or jump #lineno)
-		fstch = tok[1][0]
-		if fstch == '#'
-			return tonumber tok[1][1..]
-		if fstch == '@'
-			return getvar tok[1][1..]
-		unless _labels[tok[1]]?.lineno?
-			err "Label #{tok[1]} not found!"
-			return
-		debug "Jump to label #{tok[1]} @ line #{_labels[tok[1]].lineno}"
-		return _labels[tok[1]].lineno
-
-	if fst == 'jumpif'
-		# conditional jump (jumpif label expr or jumpif #lineno expr)
-		labelno = 0
-		fstch = tok[1][0]
-		if fstch == '#'
-			labelno = tonumber tok[1][1..]
-		else if fstch == '@'
-			labelno = getvar tok[1][1..]
-		else
+	switch fst
+		when 'jump'
+			# unconditional jump (jump label or jump #lineno)
+			fstch = tok[1][0]
+			if fstch == '#'
+				return tonumber tok[1][1..]
+			if fstch == '@'
+				return getvar tok[1][1..]
 			unless _labels[tok[1]]?.lineno?
 				err "Label #{tok[1]} not found!"
 				return
+			debug "Jump to label #{tok[1]} @ line #{_labels[tok[1]].lineno}"
+			return _labels[tok[1]].lineno
+
+		when 'jumpif'
+			# conditional jump (jumpif label expr or jumpif #lineno expr)
+			labelno = 0
+			fstch = tok[1][0]
+			if fstch == '#'
+				labelno = tonumber tok[1][1..]
+			else if fstch == '@'
+				labelno = getvar tok[1][1..]
 			else
-				labelno = _labels[tok[1]].lineno
-		if evaluate tok[2..]
-			debug "Condition true on jump to label #{tok[1]} @ line #{labelno}"
-			return labelno
-		else
-			debug "Condition false on jump to label #{tok[1]} @ line #{labelno}"
+				unless _labels[tok[1]]?.lineno?
+					err "Label #{tok[1]} not found!"
+					return
+				else
+					labelno = _labels[tok[1]].lineno
+			if evaluate tok[2..]
+				debug "Condition true on jump to label #{tok[1]} @ line #{labelno}"
+				return labelno
+			else
+				debug "Condition false on jump to label #{tok[1]} @ line #{labelno}"
+				return lineno + 1
+
+		when 'call'
+			# like jump, but also sets variable $ra to (lineno + 1) to provide a return point
+			# to the subroutine called (this only accepts a label as argument)
+			unless _labels[tok[1]]?.lineno?
+				err "Label #{tok[1]} not found!"
+				return
+			# save return address in current scope
+			setvar '$ra', lineno + 1, true
+			# create new context
+			_context.push {}
+			# bind call arguments to function's named parameters (if any)
+			if _labels[tok[1]].params?
+				params = {}
+				for i in [0...Math.min _labels[tok[1]].params.length, tok[2..].length]
+					pname = _labels[tok[1]].params[i]
+					pval = get tok[2+i]
+					params[pname] = pval
+					setvar pname, pval, true
+			debug "Function call: #{tok[1]}(" +
+				"#{if params? and Object.keys(params).length > 1 then '\n\t\t' else ''}" +
+				"#{("#{pn}=#{dump pv}: #{type pv}" for pn, pv of params).join '\n\t\t'})"
+			debug "Creating new context. Current context chain length: #{_context.length}", 2
+			return _labels[tok[1]].lineno
+
+		when 'del'
+			# delete variable (del a)
+			setvar tok[1], undefined
+			debug "Deleted variable #{tok[1]} from context"
 			return lineno + 1

-	if fst == 'call'
-		# like jump, but also sets variable $ra to (lineno + 1) to provide a return point
-		# to the subroutine called (this only accepts a label as argument)
-		unless _labels[tok[1]]?.lineno?
-			err "Label #{tok[1]} not found!"
-			return
-		# save return address in current scope
-		setvar '$ra', lineno + 1, true
-		# create new context
-		_context.push {}
-		# bind call arguments to function's named parameters (if any)
-		if _labels[tok[1]].params?
-			params = {}
-			for i in [0...Math.min _labels[tok[1]].params.length, tok[2..].length]
-				pname = _labels[tok[1]].params[i]
-				pval = get tok[2+i]
-				params[pname] = pval
-				setvar pname, pval, true
-		debug "Function call: #{tok[1]}(" +
-			"#{if params? and Object.keys(params).length > 1 then '\n\t\t' else ''}" +
-			"#{("#{pn}=#{dump pv}: #{type pv}" for pn, pv of params).join '\n\t\t'})"
-		debug "Creating new context. Current context chain length: #{_context.length}", 2
-		return _labels[tok[1]].lineno
-
-	if fst == 'del'
-		# delete variable (del a)
-		setvar tok[1], undefined
-		debug "Deleted variable #{tok[1]} from context"
-		return lineno + 1
-
-	if fst == 'return'
-		if _context.length is 1
-			err "Cannot return from top level!"
-			return
-		# if a return value is set, save it from local context
-		res = if tok.length > 1 then evaluate(tok[1..]) else undefined
-		# delete inmost context
-		_context.pop()
-		debug "Destroying local context. Current context chain length: #{_context.length}", 2
-		ra = getvar('$ra')
-		if ra == undefined
-			err "No return address in current context!"
-			return
-		# set $res to the return value (may be undefined)
-		setvar '$res', res, true
-		return ra
-
-	if fst == 'die'
-		return -1
-
-	if fst == 'say' or fst == 'put'
-		fnc = if fst is 'say' then print else printnb
-		expr = evaluate tok[1..]
-		switch typeof expr
-			when 'string', 'number'
-				fnc expr
+		when 'return'
+			if _context.length is 1
+				err "Cannot return from top level!"
+				return
+			# if a return value is set, save it from local context
+			res = if tok.length > 1 then evaluate(tok[1..]) else undefined
+			# delete inmost context
+			_context.pop()
+			debug "Destroying local context. Current context chain length: #{_context.length}", 2
+			ra = getvar('$ra')
+			if ra == undefined
+				err "No return address in current context!"
+				return
+			# set $res to the return value (may be undefined)
+			setvar '$res', res, true
+			return ra
+
+		when 'die'
+			return -1
+
+		when'say', 'put'
+			fnc = if fst is 'say' then print else printnb
+			expr = evaluate tok[1..]
+			switch typeof expr
+				when 'string', 'number'
+					fnc expr
+				else
+					fnc dump expr
+			return lineno + 1
+
+	switch tok[1]
+		when '='
+			# var (re)assign (a = expr)
+			# check if array assignment
+			m = fst.match /([^\[]+)\[(.+)\]/
+			if m
+				v = getvar m[1]
+				if v instanceof Array
+					i = get m[2]
+					val = evaluate tok[2..]
+					v[i] = val
+					debug "Var reassign: #{m[1]}[#{i}] = #{dump val}: #{type val}"
+				else
+					err "Variable #{m[1]} is not an array, but a #{type v}!"
+					return
 			else
-				fnc dump expr
-		return lineno + 1
-
-	if tok[1] == '='
-		# var (re)assign (a = expr)
-		# check if array assignment
-		m = fst.match /([^\[]+)\[(.+)\]/
-		if m
-			v = getvar m[1]
-			if v instanceof Array
-				i = get m[2]
+				if getvar(fst) == undefined
+					err "Variable #{fst} not in context!"
+					return
 				val = evaluate tok[2..]
-				v[i] = val
-				debug "Var reassign: #{m[1]}[#{i}] = #{dump val}: #{type val}"
-			else
-				err "Variable #{m[1]} is not an array, but a #{type v}!"
-				return
-		else
-			if getvar(fst) == undefined
-				err "Variable #{fst} not in context!"
-				return
+				setvar fst, val
+				debug "Var reassign: #{fst} = #{dump val}: #{type val}"
+			return lineno + 1
+
+		when ':='
+			#var first assign (a := expr) - may shadow a pre-existing variable
 			val = evaluate tok[2..]
-			setvar fst, val
-			debug "Var reassign: #{fst} = #{dump val}: #{type val}"
-		return lineno + 1
-
-	if tok[1] == ':='
-		#var first assign (a := expr) - may shadow a pre-existing variable
-		val = evaluate tok[2..]
-		setvar fst, val, true
-		debug "Var define: #{fst} := #{dump val}: #{type val}"
-		return lineno + 1
+			setvar fst, val, true
+			debug "Var define: #{fst} := #{dump val}: #{type val}"
+			return lineno + 1

 	err "[aborted] Invalid line: #{line} @ #{lineno}"

@@ -308,18 +310,20 @@
 # "string with spaces too
 # variable
 evaluate = (toks) ->
-	# string
-	if toks[0][0] == '"'
-		toks[0] = toks[0][1..]
-		return toks.join ' '
-
-	# array
-	if toks[0][0] == '['
-		toks[0] = toks[0][1..]
-		v = []
-		if toks.join('').length > 0
-			v.push(get tok) for tok in toks
-		return v
+
+	switch toks[0][0]
+		when '"'
+			# string
+			toks[0] = toks[0][1..]
+			return toks.join ' '
+
+		when '['
+			# array
+			toks[0] = toks[0][1..]
+			v = []
+			if toks.join('').length > 0
+				v.push(get tok) for tok in toks
+			return v

 	# builtin
 	switch toks[0]


Cleaned up Gruntfile a bit

Cleaned up Gruntfile a bit

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -22,9 +22,11 @@
 				'.jpeg': 'image/jpeg'
 				'.png': 'image/png'
 				'.ico': 'image/x-icon'
+				'.ttf': 'application/octet-stream'

 			sendFile = (reqUrl) ->
 				filePath = path.join options.base, reqUrl
+				console.log "Fetching #{filePath}"

 				res.writeHead 200,
 					'Content-Type': contentTypesMap[extName] || 'text/html'
@@ -116,6 +118,21 @@
 					base: ''
 					middleware: connect_middleware

+		### STATIC FILES ###
+		rsync:
+			options:
+				args: ["--verbose", "-i"]
+				exclude: [".git*"]
+				recursive: true
+			compiled:
+				options:
+					src: "./src/static/"
+					dest: "./compiled/static"
+			dist:
+				options:
+					src: "./src/static/"
+					dest: "./dist/static"
+
 		### HTML ###

 		jade:
@@ -149,7 +166,7 @@
 			compile:
 				options:
 					paths: ['src/stylus/import']
-					import: ['variables', 'functions']
+					import: ['variables', 'functions', 'helpers']
 				files:
 					'compiled/css/project.css': [
 						'src/stylus/**/*.styl'
@@ -239,61 +256,85 @@
 	### TASKS ###
 	#############

-	grunt.loadNpmTasks 'grunt-contrib-coffee'
-	grunt.loadNpmTasks 'grunt-contrib-concat'
-	grunt.loadNpmTasks 'grunt-contrib-connect'
-	grunt.loadNpmTasks 'grunt-contrib-copy'
-	grunt.loadNpmTasks 'grunt-contrib-cssmin'
-	grunt.loadNpmTasks 'grunt-contrib-jade'
-	grunt.loadNpmTasks 'grunt-contrib-requirejs'
-	grunt.loadNpmTasks 'grunt-contrib-stylus'
-	grunt.loadNpmTasks 'grunt-contrib-uglify'
-	grunt.loadNpmTasks 'grunt-contrib-watch'
-	grunt.loadNpmTasks 'grunt-shell'
-	grunt.loadNpmTasks 'grunt-text-replace'
-	grunt.loadNpmTasks 'grunt-groc'
-
-	grunt.registerTask('default', ['sw']);
-
-	# Generate docs
-	grunt.registerTask 'd', ['shell:groc']
-
-	# Server
-	grunt.registerTask 's', ['connect:keepalive']
-
-	# Watch
-	grunt.registerTask 'w', ['watch']
-
-	# Server and watch
-	grunt.registerTask 'sw', [
-		'connect:compiled'
-		'connect:dist'
-		'connect:test'
-		'watch'
+	tasks = [
+		'grunt-contrib-coffee'
+		'grunt-contrib-concat'
+		'grunt-contrib-connect'
+		'grunt-contrib-copy'
+		'grunt-contrib-cssmin'
+		'grunt-contrib-jade'
+		'grunt-contrib-requirejs'
+		'grunt-contrib-stylus'
+		'grunt-contrib-uglify'
+		'grunt-contrib-watch'
+		'grunt-shell'
+		'grunt-text-replace'
+		'grunt-groc'
+		'grunt-rsync'
 	]

-	# Compile
-	grunt.registerTask 'c', [
-		'shell:emptycompiled' # rm -rf compiled/
-		'shell:bowerinstall' # Get dependencies first, cuz css needs to be included (and maybe images?)
-		'createSymlinks:compiled'
-		'coffee:init'
-		'jade:init'
-		'stylus:compile'
-		'concat:css'
+	grunt.loadNpmTasks task for task in tasks
+
+	jobs = [
+			# default
+			aliases: ['default']
+			tasks: ['sw']
+		,
+			# generate docs
+			aliases: ['d']
+			tasks: ['shell:groc']
+		,
+			# server
+			aliases: ['s']
+			tasks:['connect:keepalive']
+		,
+			# watch
+			aliases: ['w']
+			tasks: ['watch']
+		,
+			# server and watch
+			aliases: ['sw']
+			tasks: [
+				'connect:compiled'
+				'connect:dist'
+				'connect:test'
+				'watch'
+			]
+		,
+			# compile
+			aliases: ['c', 'compile']
+			tasks: [
+				'shell:emptycompiled'	# rm -rf compiled/
+				'shell:bowerinstall'	# Get dependencies first, cuz css needs to be included (and maybe images?)
+				'createSymlinks:compiled'
+				'coffee:init'
+				'jade:init'
+				'stylus:compile'
+				'rsync:compiled'
+				'concat:css'
+			]
+		,
+			# build
+			aliases: ['b', 'build']
+			tasks: [
+				'shell:emptydist'
+				'createSymlinks:dist'
+				'replace:html' 				# Copy and replace index.html
+				'cssmin:dist'
+				'rsync:dist'
+				'requirejs:compile' 	# Run r.js
+				# 'shell:rsync'				# Rsync to test server
+			]
+		,q
+			# all
+			aliases: ['all']
+			tasks: ['compile', 'docs', 'build', 'sw']
 	]

-	# Build
-	grunt.registerTask 'b', [
-		'shell:emptydist'
-		'createSymlinks:dist'
-		'replace:html' # Copy and replace index.html
-		'cssmin:dist'
-		'requirejs:compile' # Run r.js
-		# 'shell:rsync' # Rsync to test server
-	]
-
-	grunt.registerTask 'all', ['c', 'd', 'b', 'sw']
+	for job in jobs
+		for alias in job.aliases
+			grunt.registerTask alias, job.tasks
+

 	grunt.registerMultiTask 'createSymlinks', 'Creates a symlink', ->
 		for own index, config of this.data
@@ -311,23 +352,20 @@
 			fs.symlinkSync src, dest

-
-
 	##############
 	### EVENTS ###
 	##############

 	grunt.event.on 'watch', (action, srcPath) ->
-		if srcPath.substr(0, 3) is 'src' # Make sure file comes from src/
-			type = 'coffee' if srcPath.substr(-7) is '.coffee'
-			type = 'jade' if srcPath.substr(-5) is '.jade'
+		if srcPath.substr(0, 3) is 'src' # Make sure file comes from src/
+			type = (srcPath.split('.').splice -1)[0]

 			if type is 'coffee'
 				testDestPath = srcPath.replace 'src/coffee', 'test'
 				destPath = 'compiled'+srcPath.replace(new RegExp(type, 'g'), 'js').substr(3);

 			if type is 'jade'
-				destPath = 'compiled'+srcPath.replace(new RegExp(type, 'g'), 'html').substr(3);
+				destPath = 'compiled' + srcPath.replace(new RegExp(type, 'g'), 'html').substr(3);

 			if type? and action is 'changed' or action is 'added'
 				data = {}
@@ -341,4 +379,4 @@
 				grunt.file.delete testDestPath

 		if srcPath.substr(0, 4) is 'test' and action is 'added'
-			return false+			return false


ace: add "enable emmet" setter :rabbit:

ace: add "enable emmet" setter :rabbit:

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -11,6 +11,7 @@
 trackEvent           = require 'app/util/trackEvent'
 FSHelper             = require 'app/util/fs/fshelper'
 settings             = require './settings'
+EmmetLoader          = require './emmetloader'

 module.exports =

@@ -165,19 +166,20 @@
     @appStorage.fetchStorage (storage) =>

       @setTheme()
-      @setUseSoftTabs         @appStorage.getValue('useSoftTabs')         ? yes    ,no
-      @setShowGutter          @appStorage.getValue('showGutter')          ? yes    ,no
-      @setUseWordWrap         @appStorage.getValue('useWordWrap')         ? no     ,no
-      @setShowPrintMargin     @appStorage.getValue('showPrintMargin')     ? no     ,no
-      @setHighlightActiveLine @appStorage.getValue('highlightActiveLine') ? yes    ,no
-      @setShowInvisibles      @appStorage.getValue('showInvisibles')      ? no     ,no
-      @setFontSize            @appStorage.getValue('fontSize')            ? 12     ,no
-      @setTabSize             @appStorage.getValue('tabSize')             ? 4      ,no
+      @setUseSoftTabs         @appStorage.getValue('useSoftTabs')         ? yes       , no
+      @setShowGutter          @appStorage.getValue('showGutter')          ? yes       , no
+      @setUseWordWrap         @appStorage.getValue('useWordWrap')         ? no        , no
+      @setShowPrintMargin     @appStorage.getValue('showPrintMargin')     ? no        , no
+      @setHighlightActiveLine @appStorage.getValue('highlightActiveLine') ? yes       , no
+      @setShowInvisibles      @appStorage.getValue('showInvisibles')      ? no        , no
+      @setFontSize            @appStorage.getValue('fontSize')            ? 12        , no
+      @setTabSize             @appStorage.getValue('tabSize')             ? 4         , no
       @setKeyboardHandler     @appStorage.getValue('keyboardHandler')     ? 'default'
       @setScrollPastEnd       @appStorage.getValue('scrollPastEnd')       ? yes
       @setOpenRecentFiles     @appStorage.getValue('openRecentFiles')     ? yes
-      @setEnableAutocomplete  @appStorage.getValue('enableAutocomplete')  ? yes    ,no
-      @setEnableSnippets      @appStorage.getValue('enableSnippets')      ? yes    ,no
+      @setEnableAutocomplete  @appStorage.getValue('enableAutocomplete')  ? yes       , no
+      @setEnableSnippets      @appStorage.getValue('enableSnippets')      ? yes       , no
+      @setEnableEmmet         @appStorage.getValue('enableEmmet')         ? no        , no

   saveStarted: ->
@@ -506,6 +508,20 @@

   setOpenRecentFiles: (value, save = yes) ->
     @appStorage.setValue 'openRecentFiles', value
+
+
+  setEnableEmmet: (value, save = yes) ->
+
+    next = =>
+      @editor.setOption 'enableEmmet', value
+      @appStorage.setValue 'enableEmmet', value  if save
+
+    if value is yes
+      EmmetLoader.load (err) =>
+        throw err  if err
+        next()
+    else
+      next()

   setEnableSnippets: (value, save = yes) ->


Fix Sessions route when switching profiles

[Fixes #57868590]

--- client/helpers/nav.coffee
+++ client/helpers/nav.coffee
@@ -10,6 +10,10 @@
   'payments'
   'profiles'
 ]
+
+templateRoutes = {
+  financeSessions: 'sessions'
+}

 spendflowRoutes.forEach (route) ->
   Template.nav["#{route}Active"] = ->
@@ -37,6 +41,9 @@
       Session.set('currentProfile', newProfileId)

       currentPage = Meteor.Router.page()
+      # Is this a page that trolls us? De-trollify the route.
+      if not _.isUndefined templateRoutes[currentPage]
+        currentPage = templateRoutes[currentPage]

       if Meteor.Router["#{currentPage}Url"]
         # Route to same page we're on but with new profile


Sort comments chronologically

Sort comments chronologically

--- client/publication.coffee
+++ client/publication.coffee
@@ -874,6 +874,9 @@
 Template.annotationCommentsList.comments = ->
   Comment.documents.find
     'annotation._id': @_id
+  ,
+    sort:
+      createdAt: 1

 Template.annotationCommentEditor.created = ->
   @_rendered = false


setup defaultValue miss to fix onchange and checked

--- client/src/sidebar.coffee
+++ client/src/sidebar.coffee
@@ -15,13 +15,13 @@
                 homedata: @props.homedata

+
 SearchPlaces = React.createFactory React.createClass

     getInitialState: ->
         return searchdata: @props.searchdata

     render: ->
-
         div id: "sidebar-searchdata", className: 'sidebar-searchdata',

             Searchdata {}, 'text'
@@ -41,7 +41,6 @@
 SidebarHeading = React.createFactory React.createClass

     render: ->
-        console.log 'SidebarHeading'
         console.log @props

         div id: "sidebar-heading", className: 'sidebar-heading',
@@ -53,26 +52,47 @@
 # Le composant qui va définir une ligne de homedata
 Homedata = React.createFactory React.createClass

-    #test
     getInitialState: ->
-        console.log @props.userConfig
         return {
+            # homedata: @props.homedata
+
             helloworld: @props.homedata.helloworld
             username: @props.homedata.username
             title: @props.homedata.title
-            lat: 39.4568257456
-            lng: 0.500042567
-            zoom: 3
+            lat: @props.homedata.lat
+            lng: @props.homedata.lng
+            zoom: @props.homedata.zoom
+            show_pin_point: @props.homedata.show_pin_point
         }

     onOkClicked: ->
+        # homedata: @state.homedata
+
         username = @refs.usernameInput.getDOMNode().value
+        lat = @refs.latInput.getDOMNode().value
+        lng = @refs.lngInput.getDOMNode().value
+        zoom = @refs.zoomInput.getDOMNode().value
+        # show_pin_point = @refs.show_pin_pointInput.getDOMNode().value
+
         homedata =
             id: @props.homedata.id
             username: username
+            lat: lat
+            lng: lng
+            zoom: zoom
+            # show_pin_point: show_pin_point
+
+        # homedata.push homedata
+        # @setState homedata: homedata

         backend.putConfigid homedata, ->
-            alert 'data saved'
+            console.log "data saved"
+
+            # http://facebook.github.io/react/docs/forms.html
+            # change state
+
+
+

     render: ->
@@ -80,16 +100,10 @@
             p {className: 'hello'},
                 "#{@state.helloworld} #{@state.username}"
                 div id: 'sidebar-home-view', className: 'view-title',
-                    p {className: "title"},
-                        a href: "#config", className: "config", id: "cfg"
-                        "Preferences "
-                        br null, null
-                    p {className: 'view-coords'},
-                        "#{@state.title} : "
-                        "Lat: #{@state.lat.toFixed(4)} "
-                        "Lng: #{@state.lng.toFixed(4)} "
-                        "zoom: #{@state.zoom}"
+                    # icon Db
                     p { className: 'dbpanel'},
+                        a href: "#config", className: "userconfig"
+                        " "
                         a href: "#", className: "placesdata"

@@ -101,38 +115,43 @@
                     "change username: "
                     input {
                         ref: "usernameInput"
-                        type: "text"
-                        placeholder: "#{@state.username}"}
-
-
+                        type: "String"
+                        defaultValue: "#{@state.username}"}

                     br null, null
                     br null, null
                     "view finder is:"
-                    br null, null
-                    "Lat, Lng: "
-
-                    input {
-                        id: "vflatlng"
+
+                    br null, null
+                    "Lat: "
+                    input {
+                        ref: "latInput"
                         type: 'text',
-                        value: "#{@state.lat}, #{@state.lng}"}
-
-                    br null, null
-                    "zomm: "
-
-                    input {
-                        id: "vfzoom",
+                        defaultValue: "#{@state.lat}"}
+
+                    br null, null
+                    "Lng: "
+                    input {
+                        ref: "lngInput"
                         type: 'text',
-                        value: "#{@state.zoom}"}
+                        defaultValue: "#{@state.lng}"}
+
+                    br null, null
+                    "zoom: "
+                    input {
+                        ref: "zoomInput",
+                        type: 'number',
+                        defaultValue: "#{@state.zoom}"}

                     br null, null
                     br null, null
                     "Show 'view finder' marker: "

                     input {
-                        id: "showmarker",
+                        id: "show_pin_point",
                         type: 'checkbox',
-                        name: "true", value: "true"}
+                        defaultChecked: "#{@state.show_pin_point}",
+                        initialChecked: "#{@state.show_pin_point}"}

                     br null, null
                     br null, null


Revert parallelizing of resource download (breaks sqlite)

--- app/coffee/ResourceWriter.coffee
+++ app/coffee/ResourceWriter.coffee
@@ -14,7 +14,7 @@
 			jobs = for resource in resources
 				do (resource) =>
 					(callback) => @_writeResourceToDisk(project_id, resource, basePath, callback)
-			async.parallelLimit jobs, 5, callback
+			async.series jobs, callback

 	_removeExtraneousFiles: (resources, basePath, _callback = (error) ->) ->
 		timer = new Metrics.Timer("unlink-output-files")


Fix wrong headers usage on model service.

--- app/coffee/services/model.coffee
+++ app/coffee/services/model.coffee
@@ -15,7 +15,9 @@

 ModelProvider = ($q, $http, $gmUrls, $gmStorage) ->
     headers = ->
-        return {"X-SESSION-TOKEN": $gmStorage.get('token')}
+        return {
+            "Authorization": "Bearer #{$gmStorage.get('token')}"
+        }

     class Model
         constructor: (name, data, dataTypes) ->


Close vnc model in one more place

Close vnc model in one more place

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -33,6 +33,7 @@
     if ENUMS.DYNAMIC_STATUS.READY is @get 'file.dynamicStatus'
       @send "connect"
     else
+      @set "showAsModal", false
       @send "disconnect"
   ).observes 'file.dynamicStatus'


Require first entered character to start match

--- app/javascripts/SearchBox.coffee
+++ app/javascripts/SearchBox.coffee
@@ -114,7 +114,7 @@
     if matchString == ""
       return false

-    expression = ""
+    expression = "^"
     len = matchString.length - 1
     expression += matchString.charAt(i) + "+.?" for i in [0..len]
     re = new RegExp(expression, "i")


docs(comments): fix `inject` description

--- app/scripts/contentscript.coffee
+++ app/scripts/contentscript.coffee
@@ -1,4 +1,4 @@
-# Helper function, takes string and injects it as a script to the page context.
+# Helper function, takes code and injects it to the page context.
 inject = ( content ) ->
 	if typeof content is "function"
 		content = "(" + content + ")()"


Fix race between page url changing and it loading.

The differentiating factor seems to be that the title changes!

--- app/scripts/contentscript.coffee
+++ app/scripts/contentscript.coffee
@@ -1,7 +1,11 @@
 'use strict'

 isPullRequestPage = (url) ->
-  /github\.com\/.*\/.*\/pull\/\d+\/?$/.test(url || document.URL)
+  ///
+    github\.com
+    \/.*\/.*
+    \/pull\/\d+
+  ///.test(url || document.URL)

 sendTask = (summary) ->
   message =
@@ -13,7 +17,7 @@

 addButton = (summary, $parent) ->
   button = $('''
-    <button id="extn-lgtm-merge-btn" class="btn btn-outline extn-lgtm" style="float: left;">
+    <button class="btn btn-outline extn-lgtm" style="float: left;">

       Merge when tests pass
     </button>
@@ -30,24 +34,54 @@
   if (summary.canMerge && summary.status == "pending")
     addButton summary, $('.commit-form-actions')

-# Github doesn't do full reloads on state changes, but rather uses push
+# Github doesn't do full reloads on state changes, but rather uses pushState
+# poll for that
+callAtLatest = (timeout, handler) ->
+  canHappen = true
+  happened = false
+  setTimeout(->
+    canHappen = false
+    if !happened
+      console.log "it didn't happen!"
+      handler()
+  , timeout)
+  (args...) ->
+    if canHappen
+      console.log "called", args, $("title").text()
+      canHappen = false
+      happened = true
+      handler args...
+
 onPageChange = (callInitial, callback) ->
   if !callback?
     callback = callInitial
     callInitial = false
-  oldUrl = if callInitial then undefined else document.URL
+  title = -> $("title").text()
+  resolve = ->
+    oldTitle = title()
+    callback()
+
+  if callInitial
+    callback()
+
+  oldUrl = document.URL
+  oldTitle = title()
   setInterval ->
     if document.URL != oldUrl
+      # Wait until the title changes or up to 5 seconds before continuing
+      # This should fix the race if a page loads slowly
+      if title() == oldTitle
+        $("title").one 'DOMSubtreeModified', callAtLatest(5000, resolve)
+      else
+        resolve()
       oldUrl = document.URL
-      callback()
   , 400

 onPageChange true, ->
   console.debug("page changed", document.URL)
+  $(".extn-lgtm").remove()
   if isPullRequestPage()
     updateSite()
-  else
-    $(".extn-lgtm").remove()

 if location.search == "?mergenow"
   $("form.merge-branch-form").submit()


rtmp: Call logger.error() when ASC is missing

--- rtmp.coffee
+++ rtmp.coffee
@@ -738,7 +738,7 @@
           logger.warn "[rtmp] skipping empty AudioSpecificConfig"
       when flv.AAC_PACKET_TYPE_RAW
         if not stream.audioASCInfo?
-          throw new Error "[rtmp:publish] malformed audio data: AudioSpecificConfig is missing"
+          logger.error "[rtmp:publish] malformed audio data: AudioSpecificConfig is missing"

         # TODO: This must be a little heavy and needs better alternative.
         adtsHeader = new Buffer aac.createADTSHeader stream.audioASCInfo, info.rawDataBlock.length


Remove extra whitespace :p

Remove extra whitespace :p

--- apps/about2/client/view.coffee
+++ apps/about2/client/view.coffee
@@ -139,6 +139,3 @@
       $(this).addClass 'is-active' if direction is 'down'
       $(this).removeClass 'is-active' if direction is 'up'
     , offset: '90%'
-
-
-


chore(web): more stable browserify

chore(web): more stable browserify

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -1,4 +1,5 @@
 argv = require('yargs').argv
+assign = require 'lodash.assign'
 autoprefixer = require 'gulp-autoprefixer'
 browserify = require 'browserify'
 buffer = require 'vinyl-buffer'
@@ -8,7 +9,6 @@
 gulp = require 'gulp'
 gutil = require 'gulp-util'
 gulpif = require 'gulp-if'
-assign = require 'lodash.assign'
 minifycss = require 'gulp-minify-css'
 sass = require 'gulp-sass'
 source = require 'vinyl-source-stream'
@@ -40,14 +40,22 @@

 jsBundleFile = 'app-js.js'

-gulp.task 'js', ->
-  bundler.bundle()
-    .pipe(source(jsBundleFile))
-    .pipe(buffer())
-    .pipe(gulpif(!argv.production, sourcemaps.init({loadMaps: true})))
-      .pipe(gulpif(argv.production, uglify()))
-    .pipe(gulpif(!argv.production, sourcemaps.write('./')))
-    .pipe(gulp.dest('./dist/assets/js'))
+gulp.task 'browserify', ->
+  watcher = watchify(bundler)
+
+  watcher.on 'update', ->
+    updateStart = Date.now()
+    console.log('Browserify started')
+    watcher.bundle()
+      .pipe(source(jsBundleFile))
+        ## uglify
+      .pipe(gulp.dest('./dist/assets/js'))
+      .pipe(connect.reload())
+    console.log('Browserify ended', (Date.now() - updateStart) + 'ms')
+  .bundle()
+  .pipe(source(jsBundleFile))
+  .pipe(gulp.dest('./dist/assets/js'))
+  .pipe(connect.reload())

 gulp.task 'sass', ->
   gulp.src ['./app/**/*.scss']
@@ -68,28 +76,10 @@
     .pipe gulp.dest './dist/'
     .pipe connect.reload()

-gulp.task 'watchify', ->
-  watcher = watchify(bundler)
-  watcher
-    .on 'error', gutil.log.bind(gutil, 'Browserify Error')
-    .on 'update', ->
-      watcher.bundle()
-        .pipe(source(jsBundleFile))
-        .pipe(buffer())
-        .pipe(sourcemaps.init({loadMaps: true}))
-        .pipe(sourcemaps.write('./'))
-        .pipe(gulp.dest('./dist/assets/js'))
-
-      gutil.log("Updated JavaScript sources")
-    .bundle()
-    .pipe(source(jsBundleFile))
-    .pipe(gulp.dest('./dist/assets/js'))
-    .pipe connect.reload()

 gulp.task 'watch', ['server'], ->
   gulp.watch ['./app/**/*.coffee'], ['coffee']
   gulp.watch ['./app/**/*.scss'], ['sass']
-  gulp.watch ['./app/**/*.js', './app/**/*.jsx'], ['watchify']
   gulp.watch ['./index.html', './app/**/*.html'], ['html']

 gulp.task 'assets', ->
@@ -117,9 +107,9 @@
     root: ['./dist/', './bower_components/', './node_modules/']
     livereload: true

-gulp.task 'build', ['assets', 'coffee', 'js', 'sass', 'html', 'usemin']
+gulp.task 'build', ['assets', 'coffee', 'browserify', 'sass', 'html', 'usemin']

-gulp.task 'build:dev', ['assets', 'coffee', 'js', 'sass', 'html']
+gulp.task 'build:dev', ['assets', 'coffee', 'browserify', 'sass', 'html']

 gulp.task 'dev', ['build:dev', 'server', 'watch']


Hide editor unless logged in

Hide editor unless logged in

--- client/publication.coffee
+++ client/publication.coffee
@@ -381,9 +381,13 @@
 Deps.autorun ->
   return unless Session.get 'currentPublicationId'

+  # No editor unless logged in
+  return unless Meteor.person()
+
   localAnnotation = LocalAnnotation.documents.findOne
+    local: true
+    'author._id': Meteor.personId()
     'publication._id': Session.get 'currentPublicationId'
-    local: true

   return if localAnnotation

@@ -571,6 +575,10 @@
       # We display those which have a corresponding highlight visible
       'highlights._id':
         $in: visibleHighlights
+    ,
+      # We display the annotation editor
+      local: true
+      'author._id': Meteor.personId()
     ]
     'publication._id': Session.get 'currentPublicationId'
   ,


Fixed typo

Fixed typo

--- core/app/backbone/views/wheel/persistent_wheel_view.coffee
+++ core/app/backbone/views/wheel/persistent_wheel_view.coffee
@@ -1,5 +1,5 @@
 class window.PersistentWheelView extends BaseFactWheelView
-  clickOpinionType: (optionType, e)->
+  clickOpinionType: (opinionType, e)->
     @trigger 'opinionSet'

     @updateTo '1.0',


Remember view

Remember view

--- static/src/scripts/app/views/teams/ListPageView.coffee
+++ static/src/scripts/app/views/teams/ListPageView.coffee
@@ -24,6 +24,7 @@

       @paginationView = new PaginationView
         collection: options.collection
+      @rememberView @paginationView

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


Avoid scrollToBottom when messages is undefined

--- src/ui/views/input.coffee
+++ src/ui/views/input.coffee
@@ -85,7 +85,7 @@
                     # move. by pinning the div of the outer holding div, we
                     # are not moving the scroller.
                     ta.parentNode.style.height = (ta.offsetHeight + 24) + 'px'
-                    messages.scrollToBottom()
+                    messages.scrollToBottom() if messages?
             , onkeydown: (e) ->
                 if (e.metaKey or e.ctrlKey) and e.keyIdentifier == 'Up' then action 'selectNextConv', -1
                 if (e.metaKey or e.ctrlKey) and e.keyIdentifier == 'Down' then action 'selectNextConv', +1


Update error message

Update error message

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -140,7 +140,7 @@
         installDirectory = CommandInstaller.getInstallDirectory()
         atom.confirm
           message: error.message
-          detailedMessage: "Make sure #{CommandInstaller.getInstallDirectory()} is writable. Run 'sudo chmod o+w #{installDirectory}' to fix this problem."
+          detailedMessage: "Make sure #{CommandInstaller.getInstallDirectory()} exists and is writable. Run 'sudo mkdir -p #{installDirectory} && sudo chown $USER #{installDirectory}' to fix this problem."
       else
         atom.confirm
           message: "Command installed."


raspberrypi.coffee: Change the getting started guide links.

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- raspberrypi.coffee
+++ raspberrypi.coffee
@@ -11,9 +11,9 @@

 	instructions: commonImg.instructions
 	gettingStartedLink:
-		windows: 'http://docs.resin.io/#/pages/installing/gettingStarted.md#windows'
-		osx: 'http://docs.resin.io/#/pages/installing/gettingStarted.md#on-mac-and-linux'
-		linux: 'http://docs.resin.io/#/pages/installing/gettingStarted.md#on-mac-and-linux'
+		windows: 'https://docs.resin.io/raspberrypi/nodejs/getting-started/#adding-your-first-device'
+		osx: 'https://docs.resin.io/raspberrypi/nodejs/getting-started/#adding-your-first-device'
+		linux: 'https://docs.resin.io/raspberrypi/nodejs/getting-started/#adding-your-first-device'
 	supportsBlink: true

 	options: [ networkOptions.group ]


Closes #44 Fixes facility map multiple LGA selections

--- static/coffee/nmis.coffee
+++ static/coffee/nmis.coffee
@@ -1052,6 +1052,7 @@
       else if what is "nav"
         if !nav
           nav = $('.lga-nav').on 'submit', 'form', (evt)->
+            NMIS.clear()
             d = NMIS.findDistrictById nav.find('select').val()
             dashboard.setLocation NMIS.urlFor.extendEnv state: d.group, lga: d
             evt.preventDefault()


Listen on loopback interface

Listen on loopback interface

--- server.coffee
+++ server.coffee
@@ -8,7 +8,7 @@

 if not module.parent
     port = process.env.PORT || 9250
-    host = process.env.HOST || "0.0.0.0"
+    host = process.env.HOST || "127.0.0.1"
     server = app()
     server.listen port, host, ->
         console.log(


Set reactive callbacks based on whether or not cursor is ordered

--- server.coffee
+++ server.coffee
@@ -40,16 +40,26 @@
       handle.stop()
   handle

+callbacksOrdered =
+  addedBefore: true
+  removed: true
+  changed: true
+  movedBefore: true
+
+callbacksUnordered =
+  added: true
+  changed: true
+  removed: true
+
 for method in ['forEach', 'map', 'fetch']
   do (method) ->
     originalMethod = MeteorCursor::[method]
     MeteorCursor::[method] = (args...) ->
       if @_isReactive()
-        @_depend
-          addedBefore: true
-          removed: true
-          changed: true
-          movedBefore: true
+        ordered = @skip || (@limit && !@sort)
+        callbacks = if ordered then callbacksOrdered else callbacksUnordered
+        # callbacks = callbacksUnordered
+        @_depend callbacks

       originalMethod.apply @, args


minors

minors

--- server.coffee
+++ server.coffee
@@ -166,10 +166,9 @@
   app.set "view options", {layout: false}
   app.use express.favicon __dirname + "/public/favicon.ico"
   app.use express.logger()
-  app.use express.compress()
+  # app.use express.compress()
   app.use express.cookieParser()
   app.use express.bodyParser()
-  app.use express.methodOverride()
   app.use express.session { secret: "session-secret-key" }


output jsonld

output jsonld

--- server.coffee
+++ server.coffee
@@ -1,3 +1,4 @@
+# !./node_modules/coffee-script/bin/coffee %

 http = require("http")
 csv = require("csv")
@@ -30,17 +31,44 @@
   links.end()
   return

+_matches_to_objects = (matches) ->
+    ret = []
+    for id in matches
+        ret.push {
+            "@type": "MatchResult",
+            "accuracy": Math.random(),
+            "id": id
+        }
+    ret
+
+
 handle_ids_for_id = (req, res, url_parts) ->
     needle = url_parts.query.id
     console.log needle
-    matches = mapping[needle] or []
-    if matches.length > 0
+    matches_raw = mapping[needle] or []
+    if matches_raw.length > 0
         res.writeHead 200, "Content-Type": "application/json"
     else
         res.writeHead 404, "Content-Type": "application/json"
     ret = {
+        "@context": {
+            "needle": {
+                "@id": "http://onto.dm2e.eu/omnom/parameterValue",
+                "@type": "@id"
+            },
+            "accuracy": {
+                "@id": "http://rs.tdwg.org/dwc/terms/measurementAccuracy",
+                "@type": "http://www.w3.org/2001/XMLSchema#float",
+            },
+            "id": {
+                "@id": "http://foo.bar/id",
+                "@type": "@id"
+            }
+        },
+        "@id": req.url,
+        "@type": "MatchResultList",
         "needle": needle,
-        "matches": matches
+        "skos:broadMatch": _matches_to_objects matches_raw
     }
     res.end JSON.stringify(ret)

@@ -55,9 +83,13 @@
     res.end index

 http.createServer((req, res) ->
-    url_parts = url.parse(req.url, true)
+    res.setHeader "Access-Control-Allow-Origin", "*"
+    res.setHeader "Access-Control-Allow-Headers", "X-Requested-With, exlrequesttype"
+    url_parts = url.parse req.url, true
+    action = url_parts.pathname.split('/').pop()
     console.log url_parts
-    if url_parts.pathname is "/ids-for-id"
+    console.log action
+    if action is "ids-for-id"
         handle_ids_for_id(req, res, url_parts)
     else
         handle_default(req, res, url_parts)


deleted stray log statements

deleted stray log statements

--- src/interface/api.coffee
+++ src/interface/api.coffee
@@ -35,10 +35,8 @@
   allFeatures = _.flatten(roots.map((x) -> x.features))

   tagFilter = (include, exclude) ->
-    console.log("args", arguments)
     include ||= []
     exclude ||= []
-    console.log(include, exclude)
     (s) -> (
       include.length == 0 ||
       _.intersection(s.tags, include).length > 0 ||


OHM-55: converted to coffee

OHM-55: converted to coffee

--- src/koaMiddleware.coffee
+++ src/koaMiddleware.coffee
@@ -26,18 +26,15 @@

 compress = require 'koa-compress'

-rawBodyReader = `function *(next) {
-  var body = yield getRawBody(this.req, {
+rawBodyReader = (next) ->
+  body = yield getRawBody this.req, {
     length: this.length,
     encoding: this.charset
-  });
-
-  if (body) {
-    this.body = body;
   }

-  yield next;
-}`
+  this.body = body if body
+  yield next
+

 # Primary app


Fix modal close button for retina screen in badge

--- src/plugins/badge.coffee
+++ src/plugins/badge.coffee
@@ -376,7 +376,7 @@
       background-image: url('#{asset_url("badge/floating/large/theme/#{configuration.theme}/star_empty@2x.png")}');
     }

-    #sa-badge-floating-plugin #sa-badge-modal #sa-badge-modal-inner #sa-badge-modal-close-button {
+    #sa-badge-modal #sa-badge-modal-inner #sa-badge-modal-close-button {
       background-image: url('#{asset_url("badge/close@2x.png")}');
     }


Fix 'Play' button bug.

Fix 'Play' button bug.

When player press 'Play' button, if `game.js` hasn't loaded yet, the welcome screen will disappeared with the game not started.

--- src/private/index.coffee
+++ src/private/index.coffee
@@ -1,5 +1,6 @@
 $ ()->
-  $('.pause-btn').click ()->
+  $('.pause-btn').click ()->
+    if game?
     if $('.pause-btn').text() is 'Pause'
       game.pause()
       $('.pause-btn').text('Resume')


use main element instead of div with id 'main'

--- js/app.coffee
+++ js/app.coffee
@@ -9,8 +9,7 @@
       .append(HAML.header(args))

   content =
-    $("

")
-      .attr('id', 'content')
+    $("")
       .removeClass('outer')
       .removeClass('inner')
       .addClass(klass)


Fix code execution issue in email command.

--- src/scripts/email.coffee
+++ src/scripts/email.coffee
@@ -18,16 +18,18 @@

 util = require 'util'
 child_process = require 'child_process'
-exec = child_process.exec

 module.exports = (robot) ->
   emailTime = null
   sendEmail = (recipients, subject, msg, from) ->
-    mailCommand = """echo '#{msg}' | mail -s '#{subject}' -r '#{from}' '#{recipients}'"""
-    exec mailCommand, (error, stdout, stderr) ->
+    mailArgs = ['-s', subject, '-a', "From: #{from}", '--']
+    mailArgs = mailArgs.concat recipients
+    p = child_process.execFile 'mail', mailArgs, {}, (error, stdout, stderr) ->
       util.print 'stdout: ' + stdout
       util.print 'stderr: ' + stderr
+    p.stdin.write "#{msg}\n"
+    p.stdin.end()

   robot.respond /email (.*) -s (.*) -m (.*)/i, (msg) ->
-    sendEmail msg.match[1], msg.match[2], msg.match[3], msg.message.user.id
+    sendEmail msg.match[1].split(" "), msg.match[2], msg.match[3], msg.message.user.id
     msg.send "email sent"


Cursors in creature picker

Cursors in creature picker

--- src/scripts/style.coffee
+++ src/scripts/style.coffee
@@ -7,6 +7,7 @@
 				'text-anchor': 'start'

 			deleteButton:
+				cursor: 'pointer'
 				stroke: 'none'
 				'stroke-width': 0

@@ -20,6 +21,7 @@
 				'stroke-width': 0

 		circle:
+			cursor: 'move'
 			fill: '#fff'
 			r: 5
 			stroke: 'none'
@@ -29,6 +31,7 @@
 				r: 7

 		crossCircle:
+			cursor: 'move'
 			fill: '#fff'
 			r: 7
 			stroke: '#fff'


IE requires a null filter arg

IE requires a null filter arg

--- src/trix/core/dom.coffee
+++ src/trix/core/dom.coffee
@@ -72,4 +72,4 @@
       when "text" then NodeFilter.SHOW_TEXT
       else NodeFilter.SHOW_ALL

-    document.createTreeWalker(tree, whatToShow, usingFilter, expandEntityReferences is true)
+    document.createTreeWalker(tree, whatToShow, usingFilter ? null, expandEntityReferences is true)


null check for embed link

null check for embed link

--- vue/src/shared/helpers/embed_link.coffee
+++ vue/src/shared/helpers/embed_link.coffee
@@ -1,4 +1,5 @@
 export getEmbedLink = (link) ->
+  link = link || ""
   if link.includes("youtube.com") || link.includes("youtu.be")
     getYoutubeEmbedLink(link)
   else if link.includes("vimeo.com/")


handle relative config path. fixes #13

handle relative config path. fixes #13

--- anon.coffee
+++ anon.coffee
@@ -36,8 +36,13 @@
       return true
   return false

+getConfig = (path) ->
+  if path[0] != '/' and path[0..1] != './'
+    path = './' + path
+  return require(path)
+
 main = ->
-  config = require(argv.config)
+  config = getConfig(argv.config)
   twitter = new Twit config unless argv['noop']
   wikipedia = new WikiChanges(ircNickname: config.nick)
   wikipedia.listen (edit) ->


Tests: to verify upstream fix for bug.

Tests: to verify upstream fix for bug.

This commit is for two tests that verify that the combineMethod of nodes is being stored correctly.

Story:
"Setting for combining variables is not being saved or restored properly."

[#154706564]
https://www.pivotaltracker.com/story/show/154706564

--- test/loading-serialization-test.coffee
+++ test/loading-serialization-test.coffee
@@ -205,6 +205,7 @@
       @graphStore.nodeKeys['a'].image.should.equal "img/nodes/chicken.png"
       @graphStore.nodeKeys['b'].image.should.equal "img/nodes/egg.png"

+
     it "should load the nodes previous frames", ->
       data = JSON.parse(@serializedForm)
       data.nodes[1].frames = [50]
@@ -212,3 +213,24 @@
       @graphStore.nodeKeys['a'].frames.length.should.equal 0
       @graphStore.nodeKeys['b'].frames.length.should.equal 1
       @graphStore.nodeKeys['b'].frames[0].should.equal 50
+
+    it "Should load saved `combineMethod` for nodes if present", ->
+      sampleNodes = '{
+        "filename": "sample model",
+        "nodes": [
+          {
+            "key": "a",
+            "combineMethod": "average"
+          },{
+            "key": "b",
+            "combineMethod": "product"
+          },{
+            "key": "c"
+          }
+        ]
+      }'
+      data = JSON.parse(sampleNodes)
+      @graphStore.loadData(data)
+      @graphStore.nodeKeys['a'].combineMethod.should.equal "average"
+      @graphStore.nodeKeys['b'].combineMethod.should.equal "product"
+      should.not.exist(@graphStore.nodeKeys['c'].combineMethod)


rtmp: Strip query string from publishingName

--- rtmp.coffee
+++ rtmp.coffee
@@ -4,6 +4,7 @@
 # http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf

 net = require 'net'
+url = require 'url'
 crypto = require 'crypto'
 Sequent = require 'sequent'

@@ -1317,6 +1318,21 @@
   respondPublish: (requestCommand, callback) ->
     @receiveTimestamp = null
     publishingName = requestCommand.objects[1]?.value
+
+    # Strip the query string from
+    # "livestream?videoKeyframeFrequency=5&totalDatarate=248"
+    urlInfo = url.parse publishingName
+    if urlInfo.query?
+      pairs = urlInfo.query.split '&'
+      params = {}
+      for pair in pairs
+        kv = pair.split '='
+        params[ kv[0] ] = kv[1]
+      # TODO: Use this information for something
+      # totalDatarate: Total kbps for video + audio
+      logger.info JSON.stringify params
+
+    publishingName = urlInfo.pathname
     @streamId = publishingName
     stream = avstreams.get @streamId
     if stream?


Fix spelling.

Fix spelling.

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -94,7 +94,7 @@

       @itemView = options.itemView if options.itemView?

-      # Initialize lists for views and visible items
+      # Initialize list for visible items
       @visibleItems = []

       # Debugging
@@ -299,7 +299,7 @@
       # Instantiate a new view by calling getView if necessary
       unless view
         view = @getView(item)
-        # Save the view in the viewsByCid hash
+        # Save the view in the subviews
         @subview "itemView:#{item.cid}", view

       # Render in any case


fix overwriting buffer while Socket.send() in progress

--- rtsp.coffee
+++ rtsp.coffee
@@ -327,11 +327,10 @@

       rtpData = rtpData.concat audioHeader

-      # Append the access unit (rawDataBlock)
-      rtpBuffer = Buffer.concat [new Buffer(rtpData), concatRawDataBlock],
-        rtp.RTP_HEADER_LEN + audioHeader.length + accessUnitLength
-
       for clientID, client of stream.rtspClients
+        # Append the access unit (rawDataBlock)
+        rtpBuffer = Buffer.concat [new Buffer(rtpData), concatRawDataBlock],
+          rtp.RTP_HEADER_LEN + audioHeader.length + accessUnitLength
         if client.isPlaying
           rtp.replaceSSRCInRTP rtpBuffer, client.audioSSRC

@@ -990,8 +989,6 @@

       # Append NAL unit
       thisNalUnitLen = thisNalUnit.length
-      rtpBuffer = Buffer.concat [new Buffer(rtpData), thisNalUnit],
-        rtp.RTP_HEADER_LEN + 2 + thisNalUnitLen

       for clientID, client of stream.rtspClients
         if client.isWaitingForKeyFrame and isKeyFrame
@@ -999,6 +996,8 @@
           client.isWaitingForKeyFrame = false

         if client.isPlaying
+          rtpBuffer = Buffer.concat [new Buffer(rtpData), thisNalUnit],
+            rtp.RTP_HEADER_LEN + 2 + thisNalUnitLen
           rtp.replaceSSRCInRTP rtpBuffer, client.videoSSRC

           logger.tag 'rtsp:out', "[rtsp:stream:#{stream.id}] send video to #{client.id}: fragment n=#{fragmentNumber} timestamp=#{ts} bytes=#{rtpBuffer.length} marker=false keyframe=#{isKeyFrame}"
@@ -1036,14 +1035,15 @@
       isEnd: true

     nalUnitLen = nalUnit.length
-    rtpBuffer = Buffer.concat [new Buffer(rtpData), nalUnit],
-      rtp.RTP_HEADER_LEN + 2 + nalUnitLen
+
     for clientID, client of stream.rtspClients
       if client.isWaitingForKeyFrame and isKeyFrame
         client.isPlaying = true
         client.isWaitingForKeyFrame = false

       if client.isPlaying
+        rtpBuffer = Buffer.concat [new Buffer(rtpData), nalUnit],
+          rtp.RTP_HEADER_LEN + 2 + nalUnitLen
         rtp.replaceSSRCInRTP rtpBuffer, client.videoSSRC

         client.videoPacketCount++
@@ -1089,8 +1089,6 @@
       ssrc: null

     nalUnitLen = nalUnit.length
-    rtpBuffer = Buffer.concat [new Buffer(rtpHeader), nalUnit],
-      rtp.RTP_HEADER_LEN + nalUnitLen

     for clientID, client of stream.rtspClients
       if client.isWaitingForKeyFrame and isKeyFrame
@@ -1098,6 +1096,8 @@
         client.isWaitingForKeyFrame = false

       if client.isPlaying
+        rtpBuffer = Buffer.concat [new Buffer(rtpHeader), nalUnit],
+          rtp.RTP_HEADER_LEN + nalUnitLen
         rtp.replaceSSRCInRTP rtpBuffer, client.videoSSRC

         client.videoPacketCount++


Whoops, accidentally removed "siteUrl"

Whoops, accidentally removed "siteUrl"

--- js-library/app/js/util/highlight.coffee
+++ js-library/app/js/util/highlight.coffee
@@ -34,7 +34,7 @@
 # special hacky-patchy method for in the blog
 Factlink.highlightAdditionalFactlinks = (siteUrl) ->
   console.info "Factlink:", "highlightAdditionalFactlinks"
-  fetchFacts()
+  fetchFacts(siteUrl)

 Factlink.stopHighlighting = ->
   return unless highlighting


auto expands questions with lists on creation

--- jsapp/xlform_model_view/view.row.coffee
+++ jsapp/xlform_model_view/view.row.coffee
@@ -66,7 +66,7 @@
       @$label = @$('.card__header-title')
       @$card = @$el.find('.card')
       if 'getList' of @model and (cl = @model.getList())
-        @$card.addClass('card--selectquestion')
+        @$card.addClass('card--selectquestion card--expandedchoices')
         @listView = new $viewChoices.ListView(model: cl, rowView: @).render()

       # @rowExtras = @$(".row-extras")


JButtonMenu: add scrollTop to context menu position calculation

--- src/components/buttons/buttonmenu.coffee
+++ src/components/buttons/buttonmenu.coffee
@@ -43,7 +43,7 @@
     @$(".chevron-ghost-wrapper").css ghostCss

     @$().css
-      top       : top
+      top       : top + mainScroll
       left      : button.getX() + buttonWidth - menuWidth

     # left here for reference to be able to put menu left or right for button menus


update gulp template.

update gulp template.

Signed-off-by: Bo-Yi Wu <f80eec1bf98b3feebacc247f35f2b506218004cb@gmail.com>

--- templates/gulpfile.coffee
+++ templates/gulpfile.coffee
@@ -1,7 +1,7 @@
 'use strict'

-gulp = require 'gulp'<% if (includeRequireJS) { %>
-rjs = require 'requirejs'<% } %>
+gulp = require 'gulp'
+rjs = require 'requirejs'
 runs = require 'run-sequence'
 $ = require('gulp-load-plugins')()
 minifyCSS = require 'gulp-minify-css'
@@ -21,6 +21,7 @@
   test: 'test'
   dist: 'dist'
   vendor: 'app/assets/vendor'
+  hbs: 'app/assets/templates'

 coffeelintTasks = lazypipe()
   .pipe $.coffeelint
@@ -43,20 +44,12 @@

 gulp.task 'html', ->
   gulp.src paths.src + '/*.html'
-    .pipe $.changed paths.dist<% if (!includeRequireJS) { %>
-    .pipe $.replace 'main-built', filename
-    .pipe $.useref.assets()
-    .pipe $.if '*.js', $.uglify()
-    .pipe $.useref.restore()
-    .pipe $.useref()
-    .pipe $.if '*.html', $.htmlmin
-      removeComments: true
-      collapseWhitespace: true<% } else { %>
+    .pipe $.changed paths.dist
     .pipe $.replace 'js/main', 'js/' + filename
     .pipe $.replace 'vendor/requirejs/require.js', 'js/require.js'
     .pipe $.htmlmin
       removeComments: true
-      collapseWhitespace: true<% } %>
+      collapseWhitespace: true
     .pipe gulp.dest paths.dist

 gulp.task 'styles', ->
@@ -76,10 +69,12 @@
     .pipe gulp.dest paths.css<% } %>
     .pipe $.if production, gulp.dest paths.dist + '/assets/css/'

-gulp.task 'lint', ->
-  gulp.src 'gulpfile.js'
-    .pipe $.jshint()
-    .pipe $.jshint.reporter 'default'
+gulp.task 'copy', ->
+  gulp.src [
+    paths.src + '/.htaccess'
+    paths.src + '/favicon.ico'
+    paths.src + '/robots.txt']
+    .pipe gulp.dest paths.dist

 # Clean
 gulp.task 'clean', require('del').bind null, [
@@ -92,17 +87,11 @@
 # Images
 gulp.task 'images', ->
   gulp.src paths.images + '/**/*.{jpg,jpeg,png,gif}'
-    .pipe $.if production, $.changed paths.dist + '/assets/images'
-    .pipe $.if production, $.cache $.imagemin
+    .pipe $.changed paths.dist + '/assets/images'
+    .pipe $.cache $.imagemin
       progressive: true
       interlaced: true
-    .pipe $.if production, gulp.dest paths.dist + '/assets/images'
-
-# testing via mocha tool
-gulp.task 'test', ->
-  gulp.src paths.test + '/test.js'
-    .pipe $.mocha
-      reporter: 'spec'
+    .pipe gulp.dest paths.dist + '/assets/images'

 # Connect
 gulp.task 'connect:app', ->
@@ -117,26 +106,10 @@
   gulp.watch paths.src + '/*.html', reload<% if (!includeCss) { %>
   gulp.watch paths.sass + '/**/*.scss', ['styles']<% } else { %>
   gulp.watch paths.css + '/**/*.css', ['styles']<% } %>
-  gulp.watch paths.images + '/**/*.{jpg,jpeg,png,gif}', ['images', reload]
   gulp.watch paths.script + '/**/*.js', reload
-  gulp.watch paths.css + '/**/*.css', reload
+  gulp.watch paths.hbs + '/**/*.hbs', reload
+  gulp.watch paths.images + '/**/*.{jpg,jpeg,png,gif}', reload

-# Connect
-gulp.task 'connect:dist', ->
-  browserSync
-    notify: false
-    server:
-      baseDir: [paths.dist]
-
-  gulp.watch paths.dist + '/**/*', reload
-
-gulp.task 'copy', ->
-  gulp.src [
-    paths.src + '/.htaccess'
-    paths.src + '/favicon.ico'
-    paths.src + '/robots.txt']
-    .pipe gulp.dest paths.dist
-<% if (includeRequireJS) { %>
 gulp.task 'rjs', ['build'], (cb) ->
   rjs.optimize
     baseUrl: paths.script
@@ -153,21 +126,26 @@
     .pipe gulp.dest 'dist'
   gulp.src paths.vendor + '/requirejs/require.js'
     .pipe $.uglify()
-    .pipe gulp.dest paths.dist + '/assets/js/'<% } %>
+    .pipe gulp.dest paths.dist + '/assets/js/'
+
+# testing via $.mocha tool
+gulp.task 'test', ->
+  gulp.src paths.test + '/**/*.js'
+    .pipe $.mocha
+      reporter: 'spec'

 # The default task (called when you run `gulp`)
 gulp.task 'default', (cb) ->
   runs(
-    ['coffee', 'styles']
+    ['coffee', 'compass']
     'connect:app'
     cb)

 # Build
-<% if (includeRequireJS) { %>
 gulp.task 'build', [
   'coffee'
   'images'
-  'styles'
+  'compass'
   'html'
   'copy'
 ], ->
@@ -175,20 +153,10 @@
     .pipe $.size
       showFiles: true,
       gzip: true
-<% } else { %>
-gulp.task 'build', (cb) ->
-  runs([
-    'coffee'
-    'images'
-    'styles'
-    'copy']
-    'html'
-    cb)
-<% } %>

 gulp.task 'release', (cb) ->
   runs(
-    ['build'<% if (includeRequireJS) { %>, 'rjs', 'rename'<% } %>]
+    ['build', 'rjs', 'rename']
     cb)

 module.exports = gulp


Use model property instead of content on controllers

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -18,7 +18,7 @@
       @_super.apply(this, arguments)

   signedIn: ->
-    @controllerFor('currentUser').get('content')
+    @controllerFor('currentUser').get('model')

   needsAuth: (->
     # on pro, we need to auth on every route
@@ -237,7 +237,7 @@

   contentDidChange: ->
     path = @get('path')
-    @controllerFor('builds').set('content', @controllerFor('repo').get(path))
+    @controllerFor('builds').set('model', @controllerFor('repo').get(path))

   path: (->
     type = @get('contentType')


Correct slack team name.

Correct slack team name.

--- angular/core/models/group_identity_model.coffee
+++ angular/core/models/group_identity_model.coffee
@@ -16,7 +16,7 @@
       _.first @recordStore.identities.find(id: @identityId)

     slackTeamName: ->
-      @userIdentity().slackTeamName
+      @userIdentity().customFields.slack_team_name

     slackChannelName: ->
       @customFields.slack_channel_name


:space_invader: updated initial zoom in

:space_invader: updated initial zoom in

--- app/assets/javascripts/inspector_toponym.coffee
+++ app/assets/javascripts/inspector_toponym.coffee
@@ -48,7 +48,7 @@
     # constrain the map to the current sheet
     @bounds = Utils.bboxToBounds(bbox)
     @map.setMaxBounds(@bounds)
-    @map.setView([random_lat, random_lon], 16)
+    @map.setView([random_lat, random_lon], 19)
     @fogOfWar(bbox)
     @showCurrentToponyms()

@@ -339,7 +339,6 @@
     @hideFlagSpinner(item)
     close = obj.elem.find(".num-close")
     close.addClass("saving")
-    close.text("y")
     close.prepend(Utils.spinner(spinnerOpts).el)

   hideFlagSpinner: (item, saved = true) ->
@@ -350,7 +349,6 @@
       close.addClass("saved")
     else
       close.removeClass("saved")
-      close.text("x")
     close.find(".spinner-mini").remove()

   cleanEmptyFlags: () =>


OX-266 implemented fullscreen toggling with q

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -29,6 +29,7 @@
   constructor : ->

     _.extend(@, new EventMixin())
+    @fullScreen = false

     @model = new Model()

@@ -146,14 +147,6 @@

     new Input.Keyboard(

-      #Fullscreen Mode
-      "q" : =>
-        body = $("body")[0]
-        requestFullscreen = body.webkitRequestFullScreen or body.mozRequestFullScreen or body.requestFullScreen
-        if requestFullscreen
-          requestFullscreen.call(body, body.ALLOW_KEYBOARD_INPUT)
-
-
       #ScaleTrianglesPlane
       "l" : => @view.scaleTrianglesPlane(-@model.user.scaleValue)
       "k" : => @view.scaleTrianglesPlane( @model.user.scaleValue)
@@ -171,6 +164,10 @@
     )

     new Input.KeyboardNoLoop(
+
+      #Fullscreen Mode
+      "q" : => @toggleFullScreen()
+
       #Branches
       "b" : => @pushBranch()
       "j" : => @popBranch()
@@ -253,6 +250,19 @@
           @zoomIn()
         else
           @zoomOut()
+
+  toggleFullScreen : =>
+    if @fullScreen
+      cancelFullscreen = document.webkitCancelFullScreen or document.mozCancelFullScreen or document.cancelFullScreen
+      @fullScreen = false
+      if cancelFullscreen
+        cancelFullscreen.call(document)
+    else
+      body = $("body")[0]
+      requestFullscreen = body.webkitRequestFullScreen or body.mozRequestFullScreen or body.requestFullScreen
+      @fullScreen = true
+      if requestFullscreen
+        requestFullscreen.call(body, body.ALLOW_KEYBOARD_INPUT)

   ########### Click callbacks


#1114 In case of failure, wait 2s before redirecting to dashboard

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -323,7 +323,9 @@
                 differentTaskType = annotation.task.type.id != @model.task?.type.id
                 differentTaskTypeParam = if differentTaskType then "?differentTaskType" else ""
                 window.location.href = "/annotations/#{annotation.typ}/#{annotation.id}#{differentTaskTypeParam}"
-              -> window.location.href = "/dashboard"
+              ->
+                # Wait a while so users have a chance to read the error message
+                setTimeout((-> window.location.href = "/dashboard"), 2000)
             )
           )


1 cent price difference bug (floating point error)

--- app/assets/javascripts/realtime_price.js.coffee
+++ app/assets/javascripts/realtime_price.js.coffee
@@ -50,7 +50,8 @@
             #Update the saleprice
             if optemo_module.layout == "fs"
               c.find('.saleprice').find('span.price_dollars').html(parseInt(this.salePrice))
-              c.find('.saleprice').find('span.price_cents').html(parseInt(100 * (this.salePrice - parseInt(this.salePrice))))
+              # This contrived next line eliminates floating point bugs
+              c.find('.saleprice').find('span.price_cents').html(Math.round(0.01 * parseInt(10000 * (this.salePrice - parseInt(this.salePrice)))))
             else # Do the regular (Best Buy) layout
               if optemo_french?
                 c.find('.saleprice > span').html(french_price_format(this.salePrice) + " $")
@@ -60,7 +61,7 @@
             #Update the regular price
             if optemo_module.layout == "fs"
               c.find('.price').find('span.price_dollars').html(parseInt(this.regularPrice))
-              c.find('.price').find('span.price_cents').html(parseInt(100 * (this.regularPrice - parseInt(this.regularPrice))))
+              c.find('.price').find('span.price_cents').html(Math.round(0.01 * parseInt(10000 * (this.regularPrice - parseInt(this.regularPrice)))))
             else
               if optemo_french?
                 c.find('.price > span').html(french_price_format(this.regularPrice) + " $")


Coffee Script refactoring

Coffee Script refactoring

--- app/assets/javascripts/typist-teacher.js.coffee
+++ app/assets/javascripts/typist-teacher.js.coffee
@@ -7,9 +7,13 @@
   keycount = 0
   wordcount = 0

-  updateSpeed: ->
-    cpm = 0
-    wpm = 0
+  printSpeed: ->
+    data = calculateSpeed()
+    $("#CPM").html(data.cpm)
+    $("#WPM").html(data.wpm)
+    data
+
+  calculateSpeed = ->
     timeCurrent = new Date().getTime()
     unless iLastTime is 0
       keycount++
@@ -21,56 +25,56 @@
     return { "wpm": wpm, "cpm": cpm }

-class Typist
-  data = 0
+class Practice
+  arr = []

-  this.startPractice = (tracker, lesson_id = 1) ->
-    trainer = $("#trainer")
-    typist = $("#typist")
-    data.user_id = window.userid
-    data.lesson_id = lesson_id
-
-    $.get "/lessons/#{lesson_id}.json", (data) ->
-      arr = data.text.split(" ")
-      trainer.text data.text
-
-      typist.keydown (e) ->
-        currentKey = String.fromCharCode(e.which).toLowerCase()
-        inputText = typist.val() + currentKey
-
-        if inputText is arr[0] + " "
-          arr.shift()
-          text = arr.join(" ")
-          trainer.text text
-          typist.val ""
-          return false
-
-      typist.keyup ->
-        data = tracker.updateSpeed(tracker)
-        $("#CPM").html data.cpm
-        $("#WPM").html data.wpm
-
-        if arr.length is 0
-          Typist.submitScore(data)
-          Typist.startPractice(new SpeedTracker, lesson_id)
-
-
-  this.submitScore = (data) ->
+  submitPracticeScore = (practice_data) ->
     $.ajax "/practices",
       type: 'POST'
       dataType: 'json'
       contentType: "application/json"
-      data: JSON .stringify({practice:data})
+      data: JSON .stringify({practice:practice_data})
       error: (jqXHR, textStatus, errorThrown) ->
         $('body').append "AJAX Error: #{textStatus}"
-      success: (data, textStatus, jqXHR) ->
-        $('body').append "Successful AJAX call: #{data}"
+      success: (practice_data, textStatus, jqXHR) ->
+        $('body').append "Successful AJAX call: #{practice_data}"
+
+  updatePracticeText = (text) ->
+    if text is arr[0] + " "
+      arr.shift()
+      text = arr.join(" ")
+      $("#trainer").text text
+      $("#typist").val ""
+      return false
+
+  startPractice: (tracker, lesson_id = 1) ->
+    trainer = $("#trainer")
+    typist = $("#typist")
+
+    $.get "/lessons/#{lesson_id}.json", (lesson_data) ->
+      arr = lesson_data.text.split(" ")
+      trainer.text lesson_data.text
+
+    typist.keydown (e) ->
+      currentKey = String.fromCharCode(e.which).toLowerCase()
+      inputText = typist.val() + currentKey
+      updatePracticeText(inputText)
+
+    typist.keyup ->
+      data = tracker.printSpeed()
+
+      if arr.length is 0
+        data.user_id = window.userid
+        data.lesson_id = lesson_id
+        submitPracticeScore(data)
+        (new Practice).startPractice(new SpeedTracker, lesson_id + 1) # bad?

 # body...
-@tracker = new SpeedTracker
+@tracker = new SpeedTracker()
+@practice = new Practice()

 $(window).bind "page:change", ->
-  Typist.startPractice(tracker)
+  practice.startPractice(tracker)

 $ ->
-  Typist.startPractice(tracker)+  practice.startPractice(tracker)


Fix bug in DB fetching of root timestamp

--- lib/db.coffee
+++ lib/db.coffee
@@ -394,7 +394,7 @@
           if kk == '_SerialNumber'
             res.push([['DeviceID', 'SerialNumber'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
       else if common.typeOf(v) is common.OBJECT_TYPE
-        storeParams(v, [k], 0, projection == null)
+        storeParams(v, [k], device['_timestamp'] ? 0, projection == null)

     return callback(null, res)
   )


DocumentBrowser

DocumentBrowser

- Work around problems when server delivered content is HTML instead of Text.

--- src/elements/DocumentBrowser/Node.coffee
+++ src/elements/DocumentBrowser/Node.coffee
@@ -41,11 +41,17 @@
 		dive = true

 		dfr = new CUI.Deferred()
-		new CUI.XHR
+		xhr = new CUI.XHR
 			url: @__url + @getNodePath("menu.cms")
 			responseType: "text"
-		.start()
-		.done (data, xhr) =>
+
+		xhr.start()
+		.done (data) =>
+			if data.startsWith("<") # assume HTML, something isnt right
+				@opts.leaf = true
+				dfr.resolve([])
+				return
+
 			# console.debug @getNodePath()
 			children = []
 			items = []
@@ -111,6 +117,10 @@
 			responseType: "text"
 		.start()
 		.done (@__content) =>
+			if @__content.startsWith("<")
+				dfr.reject()
+				return
+
 			@__htmlNodes = CUI.DOM.htmlToNodes(@_browser.marked(@, @__content))
 			@__texts = CUI.DOM.findTextInNodes(@__htmlNodes)
 			@_browser.addWords(@__texts)


Less confusing debug log

Less confusing debug log

--- app/javascript/src/classes/notes_manager.coffee
+++ app/javascript/src/classes/notes_manager.coffee
@@ -11,21 +11,21 @@

   show: =>
     if @debug
-      console.debug '@show'
+      console.debug 'notesManager.show'
     $('note-container').show()
     return

   hide: =>
     if @debug
-      console.debug '@hide'
+      console.debug 'notesManager.hide'
     $('note-container').hide()
     return

   find: (id) =>
     if @debug
-      console.debug '@find'
+      console.debug 'notesManager.find'

     for item in @all
       return item if item.id == id
@@ -35,7 +35,7 @@

   toggle: (e) =>
     if @debug
-      console.debug '@toggle'
+      console.debug 'notesManager.toggle'

     return if e.currentTarget.getAttribute('data-drag-element') == '1'

@@ -51,7 +51,7 @@

   updateNoteCount: =>
     if @debug
-      console.debug '@updateNoteCount'
+      console.debug 'notesManager.updateNoteCount'
     if @all.length > 0
       label = ''
       if @all.length == 1
@@ -66,7 +66,7 @@

   create: =>
     if @debug
-      console.debug '@create'
+      console.debug 'notesManager.create'
     @show()
     insertion_position = @getInsertionPosition()
     top = insertion_position[0]
@@ -88,7 +88,7 @@

   getInsertionPosition: =>
     if @debug
-      console.debug '@getInsertionPosition'
+      console.debug 'notesManager.getInsertionPosition'
     # We want to show the edit box somewhere on the screen, but not outside the image.
     scroll_x = $('image').cumulativeScrollOffset()[0]
     scroll_y = $('image').cumulativeScrollOffset()[1]


Add a mixing to offer the same API for decorations as editors

--- lib/mixins/decoration-management.coffee
+++ lib/mixins/decoration-management.coffee
@@ -1,6 +1,70 @@
 Mixin = require 'mixto'
+Decoration = require atom.config.resourcePath + '/src/decoration'

 module.exports =
 class DecorationManagement extends Mixin
+  initializeDecorations: ->
+    @decorationsById = {}
+    @decorationsByMarkerId = {}
+    @decorationMarkerChangedSubscriptions = {}
+    @decorationMarkerDestroyedSubscriptions = {}

-  decorateMarker: (marker, options={}) ->
+  decorationForId: (id) ->
+    @decorationsById[id]
+
+  decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
+    decorationsByMarkerId = {}
+    for marker in @findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
+      if decorations = @decorationsByMarkerId[marker.id]
+        decorationsByMarkerId[marker.id] = decorations
+    decorationsByMarkerId
+
+  decorateMarker: (marker, decorationParams) ->
+    marker = @getMarker(marker.id)
+
+    @decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', =>
+      @removeAllDecorationsForMarker(marker)
+
+    @decorationMarkerChangedSubscriptions[marker.id] ?= @subscribe marker, 'changed', (event) =>
+      decorations = @decorationsByMarkerId[marker.id]
+
+      # Why check existence? Markers may get destroyed or decorations removed
+      # in the change handler. Bookmarks does this.
+      if decorations?
+        for decoration in decorations
+          @emit 'decoration-changed', marker, decoration, event
+
+    decoration = new Decoration(marker, this, decorationParams)
+    @decorationsByMarkerId[marker.id] ?= []
+    @decorationsByMarkerId[marker.id].push(decoration)
+    @decorationsById[decoration.id] = decoration
+    @emit 'decoration-added', marker, decoration
+    decoration
+
+  removeDecoration: (decoration) ->
+    {marker} = decoration
+    return unless decorations = @decorationsByMarkerId[marker.id]
+    index = decorations.indexOf(decoration)
+
+    if index > -1
+      decorations.splice(index, 1)
+      delete @decorationsById[decoration.id]
+      @emit 'decoration-removed', marker, decoration
+      @removedAllMarkerDecorations(marker) if decorations.length is 0
+
+  removeAllDecorationsForMarker: (marker) ->
+    decorations = @decorationsByMarkerId[marker.id].slice()
+    for decoration in decorations
+      @emit 'decoration-removed', marker, decoration
+    @removedAllMarkerDecorations(marker)
+
+  removedAllMarkerDecorations: (marker) ->
+    @decorationMarkerChangedSubscriptions[marker.id].off()
+    @decorationMarkerDestroyedSubscriptions[marker.id].off()
+
+    delete @decorationsByMarkerId[marker.id]
+    delete @decorationMarkerChangedSubscriptions[marker.id]
+    delete @decorationMarkerDestroyedSubscriptions[marker.id]
+
+  decorationUpdated: (decoration) ->
+    @emit 'decoration-updated', decoration


visually, the delete option button works, but it is not reflected in the model

--- jsapp/xlform_model_view/view.choices.coffee
+++ jsapp/xlform_model_view/view.choices.coffee
@@ -76,9 +76,10 @@
     className: "xlf-option-view"
     events:
       "keyup input": "keyupinput"
+      "click .js-remove-option": "remove"
     initialize: (@options)->
     render: ->
-      @t = $("")
+      @t = $("")
       @pw = $("
")
       @p = $("")
       @c = $("Value: Automatic")
@@ -133,10 +134,12 @@
         ifield.addClass("empty")
       else
         ifield.removeClass("empty")
+    remove: ()->
+      @$el.remove()
+      @model.destroy()
     saveValue: (ick, nval, oval, ctxt)->
       if nval is ""
-        @$el.remove()
-        @model.destroy()
+        @remove()
       else
         @model.set("label", nval, silent: true)
         other_names = @options.cl.getNames()


allow overriding the xhr to use for reflectRedirectedUrl. Turboforms needs this

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -94,8 +94,13 @@
   if url isnt referer
     window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', url

-reflectRedirectedUrl = ->
-  if location = xhr.getResponseHeader 'X-XHR-Redirected-To'
+reflectRedirectedUrl = (overrideXhr) ->
+  if overrideXhr?
+    xhrToUse = overrideXhr
+  else
+    xhrToUse = xhr
+
+  if location = xhrToUse.getResponseHeader 'X-XHR-Redirected-To'
     preservedHash = if removeHash(location) is location then document.location.hash else ''
     window.history.replaceState currentState, '', location + preservedHash


Remove redundant existential operator expressions

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1549,8 +1549,8 @@
     { nickname : oldUsername }  = account.profile

     # if firstname is not received use username as firstname
-    userFormData.firstName = username  if not firstName or firstName is ''
-    userFormData.lastName  = ''        if not lastName
+    userFormData.firstName = username  unless firstName
+    userFormData.lastName  = ''        unless lastName

     email = userFormData.email = emailsanitize email

@@ -1572,14 +1572,16 @@

       =>
         @extractOauthFromSession client.sessionToken, (err, foreignAuthInfo) ->
-          console.log 'Error while getting oauth data from session', err  if err
-
-          foreignAuthType = foreignAuthInfo?.foreignAuthType
+          if err
+            console.log 'Error while getting oauth data from session', err
+            return queue.next()
+
+          return queue.next()  unless foreignAuthInfo

           # Password is not required for GitHub users since they are authorized via GitHub.
           # To prevent having the same password for all GitHub users since it may be
           # a security hole, let's auto generate it if it's not provided in request
-          if foreignAuthInfo?.foreignAuthType and not password?
+          unless password
             password        = userFormData.password        = createId()
             passwordConfirm = userFormData.passwordConfirm = password


social: throw error if no user found when deleting

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -175,6 +175,8 @@
       email = "#{username}@koding.com"
       @fetchUser client, (err, user) =>
         return callback err  if err?
+        return callback createKodingError "User not found #{username}"  unless user
+
         userValues = {
           username
           email


Social: check if user is a member of a group, if not check if user's email is in allowed domains

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -531,15 +531,22 @@

     , =>
       # check for membership
-      JGroup.one { slug: groupName }, (err, group)->
+      JGroup.one { slug: groupName }, (err, group)=>
         if not group or err
           return callback { message: "group doesnt exist"}

-        group.isMember account, (err , isMember)->
+        group.isMember account, (err , isMember)=>
           return callback err  if err
-          return callback { message: "not a member - cannot login"}  unless isMember
-          queue.next()
-
+          return queue.next()  if isMember # if user is already member, we can continue
+
+          # check if user's email domain is in allowed domains
+          isAllowed = group.isInAllowedDomain user.email
+          return callback { message: "Your email domain is not in allowed \
+            domains for this group and you are not a member" }  unless isAllowed
+
+          # if member is not in group, but his/her email domain is in allowed
+          # domains, add them to team
+          return @addToGroup account, groupName, null, null, queue.next
     , ->
       # we are sure that user can access to the group, set group name into
       # cookie while logging in


Introduce `sanitizedEmail` field to `JUser` schema

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -64,6 +64,7 @@
     indexes         :
       username      : 'unique'
       email         : 'unique'
+      sanitizedEmail: ['unique', 'sparse']
       'foreignAuth.github.foreignId'   : 'ascending'
       'foreignAuth.odesk.foreignId'    : 'ascending'
       'foreignAuth.facebook.foreignId' : 'ascending'
@@ -109,6 +110,10 @@
         type        : String
         validate    : JName.validateEmail
         set         : emailsanitize
+      sanitizedEmail:
+        type        : String
+        validate    : JName.validateEmail
+        set         : (value) -> emailsanitize value, excludeDots: yes, excludePlus: yes
       password      : String
       salt          : String
       twofactorkey  : String
@@ -922,6 +927,8 @@
     { username, email, password, passwordStatus,
       firstName, lastName, foreignAuth, silence, emailFrequency } = userInfo

+    sanitizedEmail = emailsanitize email, excludeDots: yes, excludePlus: yes
+
     emailFrequencyDefaults = {
       global         : on
       daily          : on
@@ -955,6 +962,7 @@
       user = new JUser {
         username
         email
+        sanitizedEmail
         salt
         password         : hashPassword password, salt
         passwordStatus   : passwordStatus or 'valid'


email: remove console.log

email: remove console.log

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -271,8 +271,6 @@
                   Payment.deleteAccount deletedClient, (err)=>
                     @logout deletedClient, (err) =>
                       callback err
-
-                      console.log user.email
                       Sendgrid.deleteUser oldEmail, ->

   @isRegistrationEnabled = (callback)->


Let's try this one more time…

Let's try this one more time…

(remind me _not_ to debug this stuff late at night) ;_;

--- config/application.coffee
+++ config/application.coffee
@@ -6,7 +6,7 @@
 module.exports =
   pkg: grunt.file.readJSON("package.json")
   meta:
-    banner: "/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today(\"yyyy-mm-dd\") %>\\n */"
+    banner: "/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today(\"yyyy-mm-dd\") %> */\n"

   appTasks:
     common: ["coffee", "less", "sass", "jshint", "handlebars", "jst", "concat:js", "concat:spec", "concat:css", "images:dev", "webfonts:dev", "homepage:dev"]


Cast ObjectID to string before using

Cast ObjectID to string before using

--- test/document-spec.coffee
+++ test/document-spec.coffee
@@ -27,7 +27,7 @@
     req.get(url('abc123')).expect(400, done)

   it 'should return 404 for not existing document', (done) ->
-    req.get(url(new ObjectID())).expect(404).end (err, res) ->
+    req.get(url(new ObjectID().toString())).expect(404).end (err, res) ->
       assert.ifError(err)
       assert.equal res.body.error, 'Document Not Found'
       done()


more pleasing ordering

more pleasing ordering

--- server/stgr.coffee
+++ server/stgr.coffee
@@ -28,8 +28,8 @@

 queryIntervalInMinutes = 2
 queryInterval = 1000 * 60 * queryIntervalInMinutes
+lastChange    = 0
 serverResults = {}
-lastChange    = 0
 serverData    = {}
 serverList    = []
 propertyList  = [


about cson

about cson

--- sample-config.cson
+++ sample-config.cson
@@ -1,5 +1,6 @@
 # 이 파일은 샘플입니다.
 # 이 파일을 복사하여 같은 경로에 config.cson 파일을 만들고 원하는 정보로 수정하시면 됩니다.
+# 이 설정파일의 포맷에 대해서는 http://noe.mearie.org/cson/ 이 문서를 참고해주시기 바랍니다.

 user-agent = |Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
 phantom = {


Don't use bootstrap's buttons-radio, do it manually

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -351,12 +351,11 @@

 class ButtonOptionFilter extends Filter
     template: _.template """
-        <div class="btn-group <%= filterClass %>" data-toggle="buttons-radio">
-            <% _.each(options, function (el, i) { %>
-                <button class="btn <%= init == el.value ? "active" : "" %>" value="<%= el.value %>"><%= el.name %></button>
-            <% }) %>
-        </div>
-    """
+        <% _.each(options, function (el, i) { %>
+            <button class="btn <%= init == el.value ? "active" : "" %>" value="<%= el.value %>"><%= el.name %></button>
+        <% }) %>
+    """
+    className: "btn-group inline"
     events:
         "click .btn": "update"

@@ -376,4 +375,6 @@
                 {name: name, value: value}

     update: (e) =>
+        $(".btn", @$el).removeClass "active"
+        $(e.currentTarget, @$el).addClass "active"
         @setData @id, e.currentTarget.value


treat attributes passed at object creation as root properties

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -115,9 +115,11 @@
     # Binds the collection update event for rendering
     initialize: ->
         @collection.on "reset", @renderData
-        @data = @options.initialData or @initialData or {}
-        @data.page = @options.page or @page or 1
-        @data.size = @options.size or @size or 10
+        for key, val of @options
+            this[key] = val or this[key]
+        @data = @initialData or {}
+        @data.page = @page or 1
+        @data.size = @size or 10
         return @

     # Set data and update collection


Update api.js.coffee

Update api.js.coffee

--- backend/doc/api.js.coffee
+++ backend/doc/api.js.coffee
@@ -261,22 +261,3 @@
 @apiUse error
 @apiVersion 0.0.1
 ###
-
-###
-@apiName Logout
-@api {delete} /logout Remove cookies to logout
-@apiGroup user
-
-@apiUse json
-@apiUse userRes
-
-@apiSuccess {json} 204
-@apiSuccessExample {json} No Content:
-HTTP/1.1 204 No Content
-null
-
-@apiError {json} 400 Bad Request: There's no cookie to remove
-@apiError {json} 401 Unauthorized: a wrong token is given
-@apiUse error
-@apiVersion 0.0.2
-###


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -33,6 +33,6 @@
 test "should move player back and forth when keys are changed", ->
   ;

-test "should move player faste"
+test "should move player "

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -24,4 +24,7 @@
 test "should move player right while key is pressed", ->

+
+
+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -51,7 +51,7 @@

   player.update()

-  equals player.I.y, 18
+  equals player.I.velocity.y, 3

 test "should move player down while key is pressed", ->
   window.keydown.down = true


add tests for element with format option

--- test/element-test.coffee
+++ test/element-test.coffee
@@ -2,54 +2,109 @@
 bHtml = require './'

 describe 'Element (<)', ->
-  it 'works', ->
-    source = '''
-      <p
-    '''
-    assert bHtml(source) is '

'
-    assert bHtml(source, format: 'demo') is '''
-

-

-    '''
-
-    source = '''
-      <p
-
+      source = '''

+        assert nodes.length is 1
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 0
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '

'
+      assert bHtml(source, format: 'demo') is '''

-

-    '''
+      '''

-    source = '''
-

+    it 'works', ->
+      source = '''
         <p

+        assert nodes.length is 2
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 0
+        assert nodes[1].type is 'element'
+        assert nodes[1].name is 'p'
+        assert nodes[1].attributes.length is 0
+        assert nodes[1].children.length is 0
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '

'
+      assert bHtml(source, format: 'demo') is '''

-

-    '''
+      '''
+
+  context '3', ->
+    it 'works', ->
+      source = '''
+        <p
+

+        assert nodes.length is 1
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 1
+        assert nodes[0].children[0].type is 'element'
+        assert nodes[0].children[0].name is 'p'
+        assert nodes[0].children[0].attributes.length is 0
+        assert nodes[0].children[0].children.length is 0
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '

'
+      assert bHtml(source, format: 'demo') is '''
+

+

+

+

+      '''
+
+  context '4', ->
+    it 'works', ->
+      source = '''
+        <p
+          <p
+

+        assert nodes.length is 1
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 2
+        assert nodes[0].children[0].type is 'element'
+        assert nodes[0].children[0].name is 'p'
+        assert nodes[0].children[0].attributes.length is 0
+        assert nodes[0].children[0].children.length is 0
+        assert nodes[0].children[1].type is 'element'
+        assert nodes[0].children[1].name is 'p'
+        assert nodes[0].children[1].attributes.length is 0
+        assert nodes[0].children[1].children.length is 0
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '

'
+      assert bHtml(source, format: 'demo') is '''
+

+

+

+

+

+

+      '''

   context '(errors)', ->
     it 'works', ->


Add more logging since we don't seem to be getting issue events

--- scripts/crashlytics-notifications.coffee
+++ scripts/crashlytics-notifications.coffee
@@ -30,6 +30,8 @@
     try
       data = req.body

+      console.log 'Entering Crashlytics crash hook with event: ' + data.event
+
       if data.event == 'verification'
         console.log "verified with Crashlytics"


Minor: remove unnecessary CoffeeScript register.

--- app._coffee
+++ app._coffee
@@ -1,5 +1,3 @@
-require 'coffee-script/register'
-
 config = require './config'
 Content = require './lib/content'
 Embed = require './lib/embed'


Removed duplicate information

Removed duplicate information

--- tests/doctypelist.coffee
+++ tests/doctypelist.coffee
@@ -7,7 +7,7 @@
 helpers.options =
     serverHost: 'localhost'
     serverPort: '8888'
-client = new Client "http://localhost:8888/"
+client = new Client "http://#{helpers.options.serverHost}:#{helpers.options.serverPort}/"

 fixtures.setDefaultValues
     dirPath: path.resolve __dirname, './fixtures/'


Fix Issue#255 Problem with sortable directive

--- modules/directives/sortable/sortable.coffee
+++ modules/directives/sortable/sortable.coffee
@@ -10,7 +10,7 @@

   require: '?ngModel'
   link: (scope, element, attrs, ngModel) ->
-    opts = angular.extend({}, options, scope.$eval(attrs.uiOptions))
+    opts = angular.extend({}, options, scope.$eval(attrs.uiSortable))

     if ngModel?
       onStart = (e, ui) ->


Again, improved the algorithm by simplifying it. Now calls makeTransitionsConsistent once, instead of multiple times for each call to selectTransitions.

--- src/main/coffeescript/scxml/SCXML.coffee
+++ src/main/coffeescript/scxml/SCXML.coffee
@@ -320,10 +320,13 @@
 			eventNames = (event.name for event in eventSet.iter())

 			#debugger
-			allTransitionsForEachState = (new @opts.TransitionSet @opts.transitionSelector state,eventNames,e for state in states)
+			allTransitions = new @opts.TransitionSet
+			for state in states
+				for t in @opts.transitionSelector state,eventNames,e
+					allTransitions.add t

 			if @opts.printTrace then console.debug("allTransitionsForEachState",allTransitionsForEachState)
-			consistentTransitions = @_makeTransitionsConsistent reduce((@_makeTransitionsConsistent transitionSet for transitionSet in allTransitionsForEachState),((a,b) -> a.union b), new @opts.TransitionSet)
+			consistentTransitions = @_makeTransitionsConsistent allTransitions
 			if @opts.printTrace then console.debug("consistentTransitions",consistentTransitions)
 			return consistentTransitions


[enh] Align levels vertically when formatting lines

--- main.coffee
+++ main.coffee
@@ -29,10 +29,17 @@
     format: (level, text) ->
         text = JSON.stringify text if text instanceof Object
         text = "#{@options.prefix} | #{text}" if @options.prefix?
+
+        if level is 'info' or level is 'warn'
+             padding = ' '
+        else padding = ''
+
         if process.env.NODE_ENV isnt 'production'
             level = @colorify level, levelColors[level]

-        text = "#{level} - #{text}" if level
+        if level
+            if level
+        text = "#{level}#{padding} - #{text}" if level
         if @options.date
             date = new Date().format @options.dateFormat
             text = "[#{date}] #{text}"


Fix 404 for monsters.

Fix 404 for monsters.

--- main.coffee
+++ main.coffee
@@ -232,10 +232,13 @@
 	options = request.uonline.basicOpts
 	options.instance = 'monster'
 	chars = lib.game.getMonsterPrototypeCharacters.sync null, dbConnection, request.param('id')
-
+
+	if not chars?
+		throw new Error '404'
+
 	for i of chars
 		options[i] = chars[i]
-
+
 	response.render 'monster', options


Constrain babel to browsers that support ES6 classes

--- main.coffee
+++ main.coffee
@@ -169,10 +169,16 @@
             .pipe cached('scripts')
             # babel build
             .pipe(catch_errors(gif("*.js", babel({
-                presets: ['@babel/preset-env'],
-                plugins: [
-                    '@babel/transform-runtime',
-                    'angularjs-annotate'
+                presets: [
+                    [   '@babel/preset-env',
+                        "targets": {
+                           "chrome": "56",
+                           "firefox": "52",
+                           "edge": "13",
+                           "safari": "10"
+                        },
+                        "modules": false
+                    ]
                 ]
             }))))
             # coffee build
@@ -230,8 +236,17 @@
             .pipe gif(dev, sourcemaps.init())
             # babel build
             .pipe(catch_errors(gif("*.js", babel({
-                presets: ['@babel/preset-env'],
-                plugins: ['@babel/transform-runtime']
+                presets: [
+                    [   '@babel/preset-env',
+                        "targets": {
+                           "chrome": "56",
+                           "firefox": "52",
+                           "edge": "13",
+                           "safari": "10"
+                        },
+                        "modules": false
+                    ]
+                ]
             }))))
             # coffee build
             .pipe(catch_errors(gif("*.coffee", ngClassify(config.ngclassify))))