removed white spaces

removed white spaces

--- src/chroma.coffee
+++ src/chroma.coffee
@@ -14,8 +14,8 @@
     may distribute non-source (e.g., minimized or compacted) forms of
     that code without the copy of the GNU GPL normally required by
     section 4, provided you include this license notice and a URL
-    through which recipients can access the Corresponding Source.
-
+    through which recipients can access the Corresponding Source.
+
     @source: https://github.com/gka/chroma.js
 ###

@@ -30,28 +30,28 @@
 class Color
 	###
 	data type for colors
-
+
 	eg.
 	new Color() // white
 	new Color(120,.8,.5) // defaults to hsl color
 	new Color([120,.8,.5]) // this also works
 	new Color(255,100,50,'rgb') //  color using RGB
 	new Color('#ff0000') // or hex value
-
+
 	###
 	constructor: (x,y,z,m) ->
 		me = @
-
+
 		if not x? and not y? and not z? and not m?
 			x = [255,0,255]
-
+
 		if type(x) == "array" and x.length == 3
 			m ?= y
 			[x,y,z] = x
-
+
 		if type(x) == "string"
 			m = 'hex'
-		else
+		else
 			m ?= 'rgb'

 		if m == 'rgb'
@@ -68,29 +68,29 @@
 			me.rgb = Color.hcl2rgb(x,y,z)
 		else if m == 'hsi'
 			me.rgb = Color.hsi2rgb(x,y,z)
-
-
+
+
 	hex: ->
 		Color.rgb2hex(@rgb)
-
+
 	toString: ->
 		@hex()
-
+
 	hsl: ->
 		Color.rgb2hsl(@rgb)
-
+
 	hsv: ->
 		Color.rgb2hsv(@rgb)
-
+
 	lab: ->
 		Color.rgb2lab(@rgb)
-
+
 	hcl: ->
 		Color.rgb2hcl(@rgb)
-
+
 	hsi: ->
 		Color.rgb2hsi(@rgb)
-
+
 	interpolate: (f, col, m) ->
 		###
 		interpolates between colors
@@ -98,7 +98,7 @@
 		me = @
 		m ?= 'rgb'
 		col = new Color(col) if type(col) == "string"
-
+
 		if m == 'hsl' or m == 'hsv' or m == 'hcl' or m == 'hsi'
 			if m == 'hsl'
 				xyz0 = me.hsl()
@@ -112,10 +112,10 @@
 			else if m == 'hsi'
 				xyz0 = me.hsi()
 				xyz1 = col.hsi()
-
+
 			[hue0, sat0, lbv0] = xyz0
 			[hue1, sat1, lbv1] = xyz1
-
+
 			if not isNaN(hue0) and not isNaN(hue1)
 				if hue1 > hue0 and hue1 - hue0 > 180
 					dh = hue1-(hue0+360)
@@ -131,24 +131,24 @@
 				hue = hue1
 				sat = sat1 if lbv0 == 1 or lbv0 == 0
 			else
-				hue = undefined
-
+				hue = undefined
+
 			sat ?= sat0 + f*(sat1 - sat0)

 			lbv = lbv0 + f*(lbv1-lbv0)
-
+
 			new Color(hue, sat, lbv, m)
-
+
 		else if m == 'rgb'
 			xyz0 = me.rgb
 			xyz1 = col.rgb
 			new Color(xyz0[0]+f*(xyz1[0]-xyz0[0]), xyz0[1] + f*(xyz1[1]-xyz0[1]), xyz0[2] + f*(xyz1[2]-xyz0[2]), m)
-
+
 		else if m == 'lab'
 			xyz0 = me.lab()
 			xyz1 = col.lab()
 			new Color(xyz0[0]+f*(xyz1[0]-xyz0[0]), xyz0[1] + f*(xyz1[1]-xyz0[1]), xyz0[2] + f*(xyz1[2]-xyz0[2]), m)
-
+
 		else
 			throw "color mode "+m+" is not supported"

@@ -167,7 +167,7 @@
 	g = u >> 8 & 0xFF
 	b = u & 0xFF
 	[r,g,b]
-
+

 Color.rgb2hex = (r,g,b) ->
 	if r != undefined and r.length == 3
@@ -199,13 +199,13 @@
 			when 2 then [r,g,b] = [p, v, t]
 			when 3 then [r,g,b] = [p, q, v]
 			when 4 then [r,g,b] = [t, p, v]
-			when 5 then [r,g,b] = [v, p, q]
+			when 5 then [r,g,b] = [v, p, q]
 	r = Math.round r
 	g = Math.round g
 	b = Math.round b
 	[r, g, b]

-
+
 Color.rgb2hsv = (r,g,b) ->
 	if r != undefined and r.length == 3
 		[r,g,b] = r
@@ -249,10 +249,10 @@
 				c[i] = t2
 			else if 3 * t3[i] < 2
 				c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6
-			else
+			else
 				c[i] = t1
 		[r,g,b] = [Math.round(c[0]*255),Math.round(c[1]*255),Math.round(c[2]*255)]
-	[r,g,b]
+	[r,g,b]

 Color.rgb2hsl = (r,g,b) ->
@@ -265,7 +265,7 @@
 	max = Math.max(r, g, b)

 	l = (max + min) / 2
-
+
 	if max == min
 		s = 0
 		h = undefined
@@ -275,14 +275,14 @@
 	if r == max then h = (g - b) / (max - min)
 	else if (g == max) then h = 2 + (b - r) / (max - min)
 	else if (b == max) then h = 4 + (r - g) / (max - min)
-
+
 	h *= 60;
 	h += 360 if h < 0
 	[h,s,l]

 #
-# L*a*b* scale by David Dalrymple
-# http://davidad.net/colorviz/
+# L*a*b* scale by David Dalrymple
+# http://davidad.net/colorviz/
 #
 Color.lab2xyz = (l,a,b) ->
 	###
@@ -300,7 +300,7 @@
 	x = ill[0] * finv(sl + (a/5.0))
 	z = ill[2] * finv(sl - (b/2.0))
 	[x,y,z]
-
+
 Color.xyz2rgb = (x,y,z) ->
 	###
 	Convert from XYZ doubles to sRGB bytes
@@ -308,7 +308,7 @@
 	###
 	if type(x) == "array" and x.length == 3
 		[x,y,z] = x
-
+
 	rl =  3.2406*x - 1.5372*y - 0.4986*z
 	gl = -0.9689*x + 1.8758*y + 0.0415*z
 	bl =  0.0557*x - 0.2040*y + 1.0570*z
@@ -317,24 +317,24 @@
 		rl = if rl<0.0 then 0.0 else if rl>1.0 then 1.0 else rl
 		gl = if gl<0.0 then 0.0 else if gl>1.0 then 1.0 else gl
 		bl = if bl<0.0 then 0.0 else if bl>1.0 then 1.0 else bl
-
+
 	# Uncomment the below to detect clipping by making clipped zones red.
-	if clip
+	if clip
 		[rl,gl,bl] = [undefined,undefined,undefined]
-
+
 	correct = (cl) ->
 		a = 0.055
 		if cl<=0.0031308 then 12.92*cl else (1+a)*Math.pow(cl,1/2.4)-a
-
+
 	r = Math.round 255.0*correct(rl)
 	g = Math.round 255.0*correct(gl)
 	b = Math.round 255.0*correct(bl)
-
+
 	[r,g,b]
-
+
 Color.lab2rgb = (l,a,b) ->
 	###
-	Convert from LAB doubles to sRGB bytes
+	Convert from LAB doubles to sRGB bytes
 	(just composing the above transforms)
 	###
 	if l != undefined and l.length == 3
@@ -344,17 +344,17 @@
 		[l,a,b] = l
 	[x,y,z] = Color.lab2xyz(l,a,b)
 	Color.xyz2rgb(x,y,z)
-
-
+
+
 Color.hcl2lab = (c,s,l) ->
 	###
 	Convert from a qualitative parameter c and a quantitative parameter l to a 24-bit pixel. These formulas were invented by David Dalrymple to obtain maximum contrast without going out of gamut if the parameters are in the range 0-1.
-
+
 	A saturation multiplier was added by Gregor Aisch
 	###
 	if type(c) == "array" and c.length == 3
 		[c,s,l] = c
-
+
 	c /= 360.0
 	TAU = 6.283185307179586476925287
 	L = l*0.61+0.09 # L of L*a*b*
@@ -363,44 +363,44 @@
 	a = Math.sin(angle)*r
 	b = Math.cos(angle)*r
 	[L,a,b]
-
+

 Color.hcl2rgb = (c,s,l) ->
 	[L,a,b] = Color.hcl2lab(c,s,l)
 	Color.lab2rgb(L,a,b)
-
-
+
+
 Color.rgb2xyz = (r,g,b) ->
 	if r != undefined and r.length == 3
 		[r,g,b] = r
-
+
 	correct = (c) ->
 		a = 0.055
-		if c <= 0.04045 then c/12.92 else Math.pow((c+a)/(1+a), 2.4)
-
+		if c <= 0.04045 then c/12.92 else Math.pow((c+a)/(1+a), 2.4)
+
 	rl = correct(r/255.0)
 	gl = correct(g/255.0)
 	bl = correct(b/255.0)
-
+
 	x = 0.4124 * rl + 0.3576 * gl + 0.1805 * bl
 	y = 0.2126 * rl + 0.7152 * gl + 0.0722 * bl
 	z = 0.0193 * rl + 0.1192 * gl + 0.9505 * bl
 	[x,y,z]
-
+
 Color.xyz2lab = (x,y,z) ->
 	# 6500K color templerature
 	if x != undefined and x.length == 3
 		[x,y,z] = x
-
-	ill = [0.96421, 1.00000, 0.82519]
+
+	ill = [0.96421, 1.00000, 0.82519]
 	f = (t) ->
 		if t > Math.pow(6.0/29.0,3) then Math.pow(t, 1/3) else (1/3)*(29/6)*(29/6)*t+4.0/29.0
 	l = 1.16 * f(y/ill[1]) - 0.16
 	a = 5 * (f(x/ill[0]) - f(y/ill[1]))
-	b = 2 * (f(y/ill[1]) - f(z/ill[2]))
+	b = 2 * (f(y/ill[1]) - f(z/ill[2]))
 	[l,a,b]
-
-
+
+
 Color.rgb2lab = (r,g,b) ->
 	if r != undefined and r.length == 3
 		[r,g,b] = r
@@ -411,25 +411,25 @@
 Color.lab2hcl = (l,a,b) ->
 	###
 	Convert from a qualitative parameter c and a quantitative parameter l to a 24-bit pixel. These formulas were invented by David Dalrymple to obtain maximum contrast without going out of gamut if the parameters are in the range 0-1.
-
+
 	A saturation multiplier was added by Gregor Aisch
 	###
 	if type(l) == "array" and l.length == 3
 		[l,a,b] = l
 	L = l
 	l = (l-0.09) / 0.61
-
+
 	r = Math.sqrt(a*a + b*b)
 	s = r / (l*0.311+0.125)
-
+
 	TAU = 6.283185307179586476925287
-
+
 	angle = Math.atan2(a,b)
-
+
 	c = (TAU/6 - angle) / TAU
 	c *= 360
 	c += 360 if c < 0
-
+
 	[c,s,l]

@@ -438,7 +438,7 @@
 		[r,g,b] = r
 	[l,a,b] = Color.rgb2lab(r,g,b)
 	Color.lab2hcl(l,a,b)
-
+

 Color.rgb2hsi = (r,g,b) ->
 	###
@@ -463,9 +463,9 @@
 		if b > g
 			h = TWOPI - h
 		h /= TWOPI
-	[h*360,s,i]
-
-
+	[h*360,s,i]
+
+
 Color.hsi2rgb = (h,s,i) ->
 	###
 	borrowed from here:
@@ -474,13 +474,13 @@
 	if type(h) == "array" and h.length == 3
 		[h,s,i] = h
 	TWOPI = Math.PI*2
-	PITHIRD = Math.PI/3
+	PITHIRD = Math.PI/3
 	cos = Math.cos
-
+
 	# normalize hue
 	h += 360 if h < 0
 	h -= 360 if h > 360
-
+
 	h /= 360
 	if h < 1/3
 		b = (1-s)/3
@@ -500,9 +500,9 @@
 	g = i*g*3
 	b = i*b*3
 	[r*255,g*255,b*255]
-
-
-chroma.Color = Color
+
+
+chroma.Color = Color

 #
 # static constructors
@@ -519,21 +519,21 @@

 chroma.hex = (x) ->
 	new Color(x)
-
+
 chroma.lab = (l,a,b) ->
 	new Color(l,a,b,'lab')

 chroma.hcl = (c,s,l) ->
 	new Color(c,s,l,'hcl')
-
+
 chroma.hsi = (h,s,i) ->
 	new Color(h,s,i,'hsi')
-
+
 chroma.interpolate = (a,b,f,m) ->
 	a = new Color(a) if type(a) == 'string'
 	b = new Color(b) if type(b) == 'string'
 	a.interpolate(f,b,m)
-
+

@@ -549,35 +549,35 @@
 		for c in [0..cols.length-1]
 			col = cols[c]
 			cols[c] = new Color(col) if type(col) == "string"
-
+
 		if opts.positions?
 			me.pos = opts.positions
 		else
 			me.pos = []
 			for c in [0..cols.length-1]
 				me.pos.push c/(cols.length-1)
-
+
 		me.mode = opts.mode ? 'hsv'
 		me.nacol = opts.nacol ? '#ccc'
 		me.setClasses opts.limits ? [0,1]
 		me
-
-
+
+
 	getColor: (value) ->
 		me = @
 		if isNaN(value) then return me.nacol
-
+
 		if me.classLimits.length > 2
 			c = me.getClass value
 			f = c/(me.numClasses-1)
-
+
 		else
 			f = f0 = (value - me.min) / (me.max - me.min)
 			f = Math.min(1, Math.max(0, f))
-
+
 		me.fColor f
-
-
+
+
 	fColor: (f) ->
 		me = @
 		cols = me.colors
@@ -585,7 +585,7 @@
 			p = me.pos[i]
 			if f <= p
 				col = cols[i]
-				break
+				break
 			if f >= p and i == me.pos.length-1
 				col = cols[i]
 				break
@@ -594,37 +594,37 @@
 				col = chroma.interpolate cols[i], cols[i+1], f, me.mode
 				break
 		col
-
-
+
+
 	classifyValue: (value) ->
-		self = @
+		self = @
 		limits = self.classLimits
 		if limits.length > 2
 			n = limits.length-1
 			i = self.getClass(value)
-			value = limits[i] + (limits[i+1] - limits[i]) * 0.5
+			value = limits[i] + (limits[i+1] - limits[i]) * 0.5
 			minc = limits[0]# + (limits[1]-limits[0])*0.3
 			maxc = limits[n-1]# + (limits[n]-limits[n-1])*0.7
 			value = self.min + ((value - minc) / (maxc-minc)) * (self.max - self.min)
 		value
-
-
+
+
 	setClasses: (limits = []) ->
 		###
 		# use this if you want to display a limited number of data classes
 		# possible methods are "equalinterval", "quantiles", "custom"
 		###
 		me = @
-		me.classLimits = limits
+		me.classLimits = me.limits = limits
 		me.min = limits[0]
 		me.max = limits[limits.length-1]
 		if limits.length == 2
 			me.numClasses = 0
 		else
 			me.numClasses = limits.length-1
-
+
 	getClass: (value) ->
-		self = @
+		self = @
 		limits = self.classLimits
 		if limits?
 			n = limits.length-1
@@ -633,7 +633,7 @@
 				i++
 			return i-1
 		return undefined
-
+
 	validValue: (value) ->
 		not isNaN(value)

@@ -642,7 +642,7 @@

 class Ramp extends ColorScale
-
+
 	constructor: (col0='#fe0000', col1='#feeeee', mode='hsl') ->
 		super [col0,col1], [0,1], mode

@@ -650,13 +650,13 @@

 class Diverging extends ColorScale
-
+
 	constructor: (col0='#d73027', col1='#ffffbf', col2='#1E6189', center='mean', mode='hsl') ->
 		me=@
 		me.mode = mode
 		me.center = center
 		super [col0,col1,col2], [0,.5,1], mode
-
+
 	parseData: (data, data_col) ->
 		super data, data_col
 		me = @
@@ -664,9 +664,9 @@
 		if c == 'median'
 			c = me.median
 		else if c == 'mean'
-			c = me.mean
+			c = me.mean
 		me.pos[1] = (c-me.min)/(me.max-me.min)
-
+

 chroma.Diverging = Diverging

@@ -677,20 +677,20 @@
 		# colors: dictionary of id: colors
 		me = @
 		me.colors = colors
-
+
 	parseData: (data, data_col) ->
 		# nothing to do here..
-
+
 	getColor: (value) ->
 		me = @
 		if me.colors.hasOwnProperty value
 			return me.colors[value]
 		else
 			return '#cccccc'
-
+
 	validValue: (value) ->
 		@colors.hasOwnProperty value
-
+
 chroma.Categories = Categories

@@ -698,10 +698,10 @@

 	constructor: (name) ->
 		me = @
-		me.name = name
+		me.name = name
 		me.setClasses(7)
 		me
-
+
 	getColor: (value) ->
 		me = @
 		c = me.getClass(value)
@@ -721,7 +721,7 @@
 		colors: ['#000000','#ff0000','#ffff00','#ffffff']
 		positions: [0,.25,.75,1]
 		mode: 'rgb'
-
+
 chroma.scales.BlWhOr = ->
 	new Diverging(chroma.hsl(30,1,.55),'#ffffff', new Color(220,1,.55))

@@ -734,7 +734,7 @@
 	max = Number.MAX_VALUE*-1
 	sum = 0
 	values = []
-
+
 	if type(data) == "array"
 		if type(data[0]) != "object" and type(data[0]) != "array"
 			for val in data
@@ -749,10 +749,10 @@
 			else if type(val) == "array" and type(prop) == "number"
 				values.push Number(val[prop]) if not isNaN val[prop]
 			else if type(val) == "number"
-				values.push Number(val) if not isNaN val
-
+				values.push Number(val) if not isNaN val
+
 	for val in values
-		if not not isNaN val
+		if not not isNaN val
 			continue
 		min = val if val < min
 		max = val if val > max
@@ -760,32 +760,31 @@

 	values = values.sort (a,b)->
 		a-b
-
+
 	limits = []
-
-
+
 	if mode.substr(0,1) == 'c' # continuous
 		limits.push min
 		limits.push max
-
+
 	if mode.substr(0,1) == 'e' # equal interval
 		limits.push min
 		for i in [1..num-1]
-			limits.push min+(i/num)*(max-min)
+			limits.push min+(i/num)*(max-min)
 		limits.push max
-
+
 	else if mode.substr(0,1) == 'q' # quantile scale
 		limits.push min
-		for i in [1..num-1]
+		for i in [1..num-1]
 			p = values.length * i/num
 			pb = Math.floor p
 			if pb == p
-				limits.push values[pb]
-			else # p > pb
+				limits.push values[pb]
+			else # p > pb
 				pr = p - pb
 				limits.push values[pb]*pr + values[pb+1]*(1-pr)
 		limits.push max
-
+
 	else if mode.substr(0,1) == 'k' # k-means clustering
 		###
 		implementation based on
@@ -798,14 +797,14 @@
 		repeat = true
 		nb_iters = 0
 		centroids = null
-
+
 		# get seed values
 		centroids = []
 		centroids.push min
 		for i in [1..num-1]
 			centroids.push min + (i/num) * (max-min)
 		centroids.push max
-
+
 		while repeat
 			# assignment step
 			for j in [0..num-1]
@@ -820,7 +819,7 @@
 						best = j
 				clusterSizes[best]++
 				assignments[i] = best
-
+
 			# update centroids step
 			newCentroids = new Array num
 			for j in [0..num-1]
@@ -833,20 +832,20 @@
 					newCentroids[cluster] += values[i]
 			for j in [0..num-1]
 				newCentroids[j] *= 1/clusterSizes[j]
-
+
 			# check convergence
 			repeat = false
 			for j in [0..num-1]
 				if newCentroids[j] != centroids[i]
 					repeat = true
 					break
-
+
 			centroids = newCentroids
 			nb_iters++
-
+
 			if nb_iters > 200
 				repeat = false
-
+
 		# finished k-means clustering
 		# the next part is borrowed from gabrielflor.it
 		kClusters = {}
@@ -867,6 +866,6 @@
 				limits.push tmpKMeansBreaks[i]

 	limits
-
-
-
+
+
+


fix workflow typo (#3526)

fix workflow typo (#3526)

--- app/pages/lab/workflow-create-form.cjsx
+++ app/pages/lab/workflow-create-form.cjsx
@@ -46,7 +46,7 @@

         New Workflow Title

-        <input className="standard-input full" type="text" ref="newDisplayName" defaultValue="new worfklow title" autoFocus required />
+        <input className="standard-input full" type="text" ref="newDisplayName" defaultValue="new workflow title" autoFocus required />

       {if @state.error?


fix #13: ignore files that are hashed

fix #13: ignore files that are hashed

--- tasks/bust.coffee
+++ tasks/bust.coffee
@@ -13,12 +13,17 @@
 					true
 			.map (filePath) ->
 				cwd = f.orig.cwd
-				contents = grunt.file.read filePath
-				hash = crypto.createHash('sha1').update(contents).digest('hex').substr(0, 10)
+
 				dir = path.dirname filePath
 				ext = path.extname filePath
 				base = path.basename filePath, ext
 				idir = dir.split(cwd)[1]
+
+				contents = grunt.file.read filePath
+				hash = crypto.createHash('sha1').update(contents).digest('hex').substr(0, 10)
+
+				if base.indexOf(hash) > -1
+					return grunt.verbose.ok "#{filePath} already contains a hashed name"

 				newFileName = "/#{idir}/#{base}.#{hash}#{ext}"
 				newFilePath = path.join dir, "#{base}.#{hash}#{ext}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -63,7 +63,7 @@
           parsedColor = (parseFloat(channel) for channel in color)
           parsedColor[3] = alpha
         else
-          parsedColor = parseRGB(color)
+          parsedColor = parseRGB(color) || parseHSL(color)
           #parsedColor = lookup[normalizeKey(color)] || parseHex(color) || parseRGB(color) || parseHSL(color)
           parsedColor[3] = alpha


Report data to backend

Report data to backend

--- index.cjsx
+++ index.cjsx
@@ -4,6 +4,7 @@
 request = Promise.promisifyAll require 'request'
 REPORTER_VERSION = '1.0.0'
 KCWIKI_HOST="dev.kcwiki.moe/kwks"
+TEST_HOST="133.130.100.133:8080/kwks"

 {getTyku, sum, hashCode} = require './common'
 if config.get('plugin.KcwikiReporter.enable', true)
@@ -74,7 +75,14 @@
           slots: body.api_eSlot
           param: body.api_eParam
         console.log JSON.stringify info if process.env.DEBUG?
-        # TODO: post data to backend
+        try
+          yield request.postAsync "http://#{TEST_HOST}/enemy.action",
+            form:
+              data: JSON.stringify info
+            headers:
+              'User-Agent': "Kcwiki Reporter v#{REPORTER_VERSION}"
+        catch err
+          console.log err
       when '/kcsapi/api_req_sortie/battleresult', '/kcsapi/api_req_combined_battle/battleresult'
         decks = []
         decks = (_decks[0].api_ship.concat _decks[1].api_ship)
@@ -135,7 +143,14 @@
             docks: decks
             map: _map
           console.log JSON.stringify info if process.env.DEBUG?
-          # TODO: post data to backend
+          try
+            yield request.postAsync "http://#{TEST_HOST}/path.action",
+              form:
+                data: JSON.stringify info
+              headers:
+                'User-Agent': "Kcwiki Reporter v#{REPORTER_VERSION}"
+          catch err
+            console.log err
       when '/kcsapi/api_req_kousyou/getship'
         ship = _ships[body.api_ship.api_id]
         slots = (_slotitems[slot].api_sortno for slot in ship.api_slot when slot isnt -1)
@@ -145,7 +160,17 @@
         info =
           ships: data
         console.log JSON.stringify info if process.env.DEBUG?
-        # TODO: post data to backend
+        try
+          yield request.postAsync "http://#{TEST_HOST}/initEquip.action",
+            form:
+              data: JSON.stringify info
+            headers:
+              'User-Agent': "Kcwiki Reporter v#{REPORTER_VERSION}"
+          .spread (response, body) ->
+            console.log "updateData.action reply: #{body}" if process.env.DEBUG?
+        catch err
+          console.log err
+
   # Drop ship report
   window.addEventListener 'battle.result', async (e) ->
     {rank, map, mapCell, dropShipId, deckShipId } = e.detail
@@ -160,7 +185,15 @@
       cellId: mapCell
       tyku: tyku
       rank: rank
-    # TODO: post data to backend
+    try
+      yield request.postAsync "http://#{TEST_HOST}/tyku.action",
+        form:
+          data: JSON.stringify info
+        headers:
+          'User-Agent': "Kcwiki Reporter v#{REPORTER_VERSION}"
+    catch err
+      console.log err
+
 module.exports =
   name: 'Kcwiki-Reporter'
   author: [grzhan]


Add zh-TW to i18n

Add zh-TW to i18n

--- index.cjsx
+++ index.cjsx
@@ -9,7 +9,7 @@
 {APPDATA_PATH, SERVER_HOSTNAME} = window

 window.i18n.mapHP = new(require 'i18n-2')
-  locales: ['en-US', 'ja-JP', 'zh-CN']
+  locales: ['en-US', 'ja-JP', 'zh-CN', 'zh-TW']
   defaultLocale: 'zh-CN'
   directory: path.join(__dirname, 'assets', 'i18n')
   updateFiles: false


Bug fix: night combat

Bug fix: night combat

--- index.cjsx
+++ index.cjsx
@@ -401,9 +401,9 @@
   if body.api_hougeki?
      if enemyCombined > 0
       if body.api_active_deck[1] == 1
-        Shelling escortFleet, enemyFleet, null, body.api_hougeki
+        Shelling mainFleet, enemyFleet, null, body.api_hougeki
       else
-        Shelling escortFleet, enemyEscort, null, body.api_hougeki
+        Shelling mainFleet, enemyEscort, null, body.api_hougeki
      else
       if combinedFlag > 0
         Shelling escortFleet, enemyFleet, enemyEscort, body.api_hougeki


Modified Config->addHostname() to save before returning itself.

--- src/config.coffee
+++ src/config.coffee
@@ -37,6 +37,9 @@
       false
     else
       @config[hostname] = []
+
+    @save()
+    @config[hostname]

   getHostname: (hostname) ->
     hostname = helpers.getHostname hostname


Let the debouncer debounce

Let the debouncer debounce

--- src/config.coffee
+++ src/config.coffee
@@ -956,9 +956,7 @@
       @set(key, value, save: false) for key, value of newSettings
       if @pendingOperations.length
         op() for op in @pendingOperations
-        @requestSave()
         @pendingOperations = []
-      return

   getRawValue: (keyPath, options) ->
     unless options?.excludeSources?.indexOf(@getUserConfigPath()) >= 0


Bug fix

Bug fix

--- src/client.coffee
+++ src/client.coffee
@@ -53,9 +53,9 @@
   request.send()

 cacheDiffString = ->
-  (Object.keys cache.cached).join '=1&' + '=1'
+  ((Object.keys cache.cached).join '=1&') + '=1'

 cache =
   modules : {}
   fetched : {}
-  cached  : {}
+  cached  : {}


remove amex specifics and adjust

remove amex specifics and adjust

--- src/coffee/card.coffee
+++ src/coffee/card.coffee
@@ -210,10 +210,9 @@

   maskCardNumber: (val, el, out) =>
     mask = @options.masks.cardNumber
-    cardType = payment.fns.cardType(val)
     numbers = val.split(' ')

-    if (cardType == 'amex' && (numbers.length == 3)) || numbers.length >= 4
+    if numbers.length >= 3
       numbers.forEach (item, idx) ->
         numbers[idx] = numbers[idx].replace(/\d/g, mask) unless idx == numbers.length - 1
       numbers.join(' ')


Fix Typo in Choice EDSL

Fix Typo in Choice EDSL

--- src/coffee/edsl.coffee
+++ src/coffee/edsl.coffee
@@ -21,7 +21,7 @@
         {visibleReqs, activeReqs} = args
         visibleReqs ?= {}
         activeReqs ?= {}
-        choiceFactory id, title, text, visibleReqs, activeReqs, args
+        choiceFactory id, title, text, visibleReqs, activeReqs, next
       front: (choice) ->
         frontalChoices.register choice
         return


Better format for the last CoffeeScript change

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -9,6 +9,7 @@
         $('body').on 'post-load', ->
             # We only apply Prism on the last n posts, to save resource
             $('.post')
-                .slice(0 - window.infiniteScroll.settings.query_args.posts_per_page).find('code[class^=lang]')
+                .slice(0 - window.infiniteScroll.settings.query_args.posts_per_page)
+                .find('code[class^=lang]')
                 .each ->
                     Prism.highlightElement @


ispage sample component

ispage sample component

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -1,4 +1,9 @@
 $ = require 'jquery'
+isPage = require './components/ispage.coffee'
+
 console.log 'ProjectName'
+console.log 'jQuery', $.fn.jquery

 #Things here
+if isPage 'index'
+	console.log 'index page'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -18,8 +18,7 @@
         if hexString.length == 4
           alpha = ((parseInt(hexString.substr(3, 1), 16) * 0x11) / 255.0) || 1.0

-        rgb = for i in [0..2]
-          parseInt(hexString.substr(i, 1), 16) * 0x11
+        rgb = (parseInt(hexString.substr(i, 1), 16) * 0x11 for i in [0..2])

         return rgb.push(alpha)

@@ -27,8 +26,7 @@
         if hexString.length == 8
           alpha = (parseInt(hexString.substr(6, 2), 16) / 255.0) || 1.0

-        rgb = for i in [0..2]
-          parseInt(hexString.substr(2 * i, 2), 16)
+        rgb = (parseInt(hexString.substr(2 * i, 2), 16) for i in [0..2])

         return rgb.push(alpha)


Remove debug code

Remove debug code

--- src/common.coffee
+++ src/common.coffee
@@ -13,7 +13,6 @@

     if @number.length == 12
       checkDigit = @generateCheckDigit(@number)
-      console.log(checkDigit)
       @number+=checkDigit

     # check if length of code matches specification


Added onFIleReaderError callback

Added onFIleReaderError callback

--- src/cropit.coffee
+++ src/cropit.coffee
@@ -90,19 +90,23 @@
   onFileChange: ->
     @options.onFileChange?()

-    oFReader = new FileReader()
+    fileReader = new FileReader()
     file = @$fileInput.get(0).files[0]
     if file?.type.match 'image'
       @setImageLoadingClass()

-      oFReader.readAsDataURL file
-      oFReader.onload = @onFileReaderLoaded.bind @
+      fileReader.readAsDataURL file
+      fileReader.onload = @onFileReaderLoaded.bind @
+      fileReader.onerror = @onFileReaderError.bind @

   onFileReaderLoaded: (e) ->
     @imageSrc = e.target.result
     @zoom = @initialZoom
     @setOffset @initialOffset
     @loadImage()
+
+  onFileReaderError: ->
+    @options.onFileReaderError?()

   loadImage: ->
     @$hiddenImage.attr 'src', @imageSrc


Go back to last room when close admin view

--- packages/rocketchat-ui/lib/sideNav.coffee
+++ packages/rocketchat-ui/lib/sideNav.coffee
@@ -78,6 +78,13 @@
 		focusInput()

 	closeFlex = (callback = null) ->
+		if FlowRouter.current().path.indexOf('/admin/') is 0
+			subscription = RocketChat.models.Subscriptions.findOne({rid: Session.get('openedRoom')})
+			if subscription?
+				RocketChat.roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
+			else
+				FlowRouter.go('home')
+
 		return if animating == true
 		toggleArrow -1
 		toggleFlex -1, callback


Save subscription cache and load

Save subscription cache and load

--- packages/rocketchat-ui/lib/RoomManager.coffee
+++ packages/rocketchat-ui/lib/RoomManager.coffee
@@ -60,18 +60,31 @@
 		if subscriptionReady.get()
 			return

-		Meteor.call 'subscriptions', (error, data) ->
-			subscriptionReady.set true
-			for item in data
-				ChatSubscription.insert(item)
-
-		RocketChat.Notifications.onUser 'subscription-change', (type, record) ->
-			if type is 'removed'
-				ChatSubscription.remove(record._id)
-			else
-				_id = record._id
-				delete record._id
-				ChatSubscription.upsert({_id: _id}, record)
+		localforage.getItem 'subscription', (error, data) ->
+			if data?.records?
+				subscriptionReady.set true
+				for item in data.records
+					ChatSubscription.insert(item)
+
+			Meteor.call 'subscriptions', (error, data) ->
+				subscriptionReady.set true
+				for item in data
+					_id = item._id
+					delete item._id
+					ChatSubscription.upsert({_id: _id}, item)
+
+				localforage.setItem('subscription', {
+					updatedAt: new Date
+					records: ChatSubscription.find().fetch()
+				})
+
+			RocketChat.Notifications.onUser 'subscription-change', (type, record) ->
+				if type is 'removed'
+					ChatSubscription.remove(record._id)
+				else
+					_id = record._id
+					delete record._id
+					ChatSubscription.upsert({_id: _id}, record)

 		return


style

style

--- packages/tower-controller/client/flash.coffee
+++ packages/tower-controller/client/flash.coffee
@@ -1,19 +1,18 @@
 Tower.ControllerFlash =
   InstanceMethods:
     # Both send and recieve flash messages
-
-    flash: (type, msg) ->
-      msgs = $.jStorage.get("flash", {})
-      if type && msg
-        msgs[type] = String msg
-        $.jStorage.set("flash", msgs)
+    flash: (type, message) ->
+      messages = $.jStorage.get('flash', {})
+      if type && message
+        messages[type] = String message
+        $.jStorage.set('flash', messages)
       else if type
-        arr = msgs[type]
-        delete msgs[type]
-        $.jStorage.set("flash", msgs)
-        String arr || ""
+        arr = messages[type]
+        delete messages[type]
+        $.jStorage.set('flash', messages)
+        String arr || ''
       else
-        $.jStorage.set("flash", {})
-        msgs
+        $.jStorage.set('flash', {})
+        messages

 module.exports = Tower.ControllerFlash


invitation

invitation

--- server/app/bongo/models/invitation.coffee
+++ server/app/bongo/models/invitation.coffee
@@ -13,7 +13,7 @@
     indexes         :
       code          : 'unique'
     sharedMethods   :
-      static        : ['create','byCode','sendBetaInviteFromClient'] # ,'__createBetaInvites'
+      static        : ['create','byCode','sendBetaInviteFromClient','grantInvitesFromClient']
     schema          :
       code          : String
       inviteeEmail  : String
@@ -43,65 +43,6 @@
         targetType  : JAccount
         as          : 'redeemer'

-  # @__attemptToFixChrisFuckup =(callback)->
-  #   i = 0
-  #   JAccount.one {'profile.nickname': 'devrim'}, (err, devrim)=>
-  #     if err
-  #       callback err
-  #     else
-  #       @all { 'origin.constructorName': $exists: no }, (err, invites)->
-  #         invites.forEach (invite)->
-  #           if err
-  #             callback err
-  #           else
-  #             invite.update {$set: origin: ObjectRef devrim}, {multi: yes}, callback
-  #             # invite.addInvitedBy devrim, (err)->
-  #             #   console.log err
-  #             #   console.log 'finished', i++
-  #             #   callback 'ok'
-  #
-  # @__sendBetaInvites = bongo.secure do->
-  #   betaTestersEmails = fs.readFileSync 'invitee-emails2.txt', 'utf-8'
-  #   # betaTestersEmails = 'chris123412341234@jraphical.com'
-  #   betaTestersHTML   = fs.readFileSync 'email/beta-testers-invite.txt', 'utf-8'
-  #   protocol = 'https://'
-  #   (client,callback)->
-  #     i = 0
-  #     recipients = []
-  #     {host, port} = server
-  #     account = client.connection.delegate
-  #     unless 'super-admin' in account.globalFlags
-  #       return callback new KodingError "not authorized"
-
-  #     # host = 'localhost:3000'
-  #     # protocol = 'http://'
-  #     uniq(betaTestersEmails.split '\n').slice(2983, 3999).forEach (email)=>
-  #       recipients.push =>
-  #         @one {inviteeEmail: email}, (err, invite)=>
-  #           if err
-  #             console.log err
-  #           else if invite?
-  #             url = "#{protocol}#{host}/invitation/#{invite.code}"
-  #             # bitly.shorten url, (err, response)=>
-  #             #   shortenedUrl = response.data.url
-  #             #   if shortenedUrl?
-  #                 # shortenedUrl = url
-  #             personalizedMail = betaTestersHTML.replace '#{url}', url#shortenedUrl
-
-  #             Emailer.send
-  #               From      : @getInviteEmail()
-  #               To        : email
-  #               Subject   : '[Koding] Here is your beta invite!'
-  #               TextBody  : personalizedMail
-  #             , (err)->
-  #               # console.log 'finished', i++, err
-  #               recipients.next(err)
-  #               # else console.log email
-  #           else
-  #             console.log "no invitation was found for #{email}"
-  #             recipients.next null
-  #     recipients.push callback
-  #     daisy recipients

   createBetaInvite = (options,callback)->
     {inviterUsername,inviteeEmail,inviteType} = options
@@ -137,7 +78,7 @@
       return callback new KodingError "not authorized"
     else
       if options.batch?
-        JInvitationRequest.some {sent:$ne:true}, {limit:options.batch, sort:requestedAt:1},(err,emails)->
+        JInvitationRequest.some {sent:$ne:true}, {limit:options.batch, sort:requestedAt:-1},(err,emails)->
           daisy queue = emails.map (item)->
             ->
               continueLooping = ->
@@ -203,33 +144,23 @@
           else
             log "[JInvitation.sendBetaInvite] something got messed up."

-
-  # @__createBetaInvites =do ->
-  #   #betaTestersEmails = 'chris123412341234@jraphical.com'
-  #   betaTestersEmails = fs.readFileSync('./invitee-emails2.txt', 'utf-8')
-  #   #betaTestersEmails = 'chris123123@jraphical.com'
-  #   (callback)->
-  #     JAccount.one {'profile.nickname': 'devrim'}, (err, devrim)=>
-  #       i = 0
-  #       recipients = []
-  #       uniq(betaTestersEmails.split '\n').forEach (email)->
-  #         code = crypto
-  #           .createHmac('sha1', 'kodingsecret')
-  #           .update(email)
-  #           .digest('hex')
-  #         # console.log email, code
-  #         recipients.push ->
-  #           invite = new JInvitation {
-  #             code
-  #             inviteeEmail  : email
-  #             maxUses       : 1
-  #             origin        : ObjectRef(devrim)
-  #             type          : 'launchrock'
-  #           }
-  #           invite.save (err)->
-  #             recipients.next(err)
-  #       recipients.push -> callback(recipients)
-  #       daisy recipients
+  @grantInvitesFromClient = bongo.secure (client, options,callback)->
+    account = client.connection.delegate
+    # unless 'super-admin' in account.globalFlags
+    unless account?.profile?.nickname is 'devrim'
+      return callback new KodingError "not authorized"
+    else
+      if options.batch?
+        callback "not implemented yet"
+      else
+        {username,quota} = options
+        quota = 3 if quota is ''
+        @grant {'profile.nickname':username},quota,(err)->
+          if err
+            callback err
+          else
+            callback null, "#{quota} invites granted to #{username}"
+

   @grant =(selector, quota, callback)->
     unless quota > 0


adding elapsedTime

adding elapsedTime

--- src/engine.coffee
+++ src/engine.coffee
@@ -136,7 +136,7 @@
         window.requestAnimationFrame(animLoop)

     update = (elapsedTime) ->
-      self.trigger "beforeUpdate"
+      self.trigger "beforeUpdate", ela
       self.trigger "update"
       self.trigger "afterUpdate"


server: landing: remove obsolete tags

server: landing: remove obsolete tags

--- workers/social/lib/social/render/loggedout/kodinghome.coffee
+++ workers/social/lib/social/render/loggedout/kodinghome.coffee
@@ -24,7 +24,6 @@
       <link href="https://plus.google.com/+KodingInc" rel="publisher" />
       <link href='https://chrome.google.com/webstore/detail/koding/fgbjpbdfegnodokpoejnbhnblcojccal' rel='chrome-webstore-item'>

-      <link rel="stylesheet" href="/a/site.#{site}/css/kd.css?#{KONFIG.version}" />
       <link rel="stylesheet" href="/a/site.#{site}/css/main.css?#{KONFIG.version}" />
     </head>
     <body class='home'>
@@ -47,10 +46,6 @@
       #{addSiteScripts site}

       <script src="/a/site.#{site}/js/libs.js?#{KONFIG.version}"></script>
-      <script src="/a/site.#{site}/js/kd.libs.js?#{KONFIG.version}"></script>
-      <script src="/a/site.#{site}/js/kd.js?#{KONFIG.version}"></script>
-      <script>KD.userAccount=#{userAccount}</script>
-      <script>KD.campaignStats=#{campaignStats}</script>
       <script src="/a/site.#{site}/js/main.js?#{KONFIG.version}"></script>
     </body>
     </html>


Server: sending campaign stats and raw user account with payload

--- workers/social/lib/social/render/loggedout/kodinghome.coffee
+++ workers/social/lib/social/render/loggedout/kodinghome.coffee
@@ -2,8 +2,11 @@

   getTitle = require './../title'

-  {campaign} = options
+  {campaign, account, bongoModels} = options
   campaign or= 'landing'
+
+  userAccount   = JSON.stringify account
+  campaignStats = null

   prepareHTML = ->
     """
@@ -29,6 +32,8 @@
       <script src="/a/site.#{campaign}/js/pistachio.js?#{KONFIG.version}"></script>
       <script src="/a/site.#{campaign}/js/kd.libs.js?#{KONFIG.version}"></script>
       <script src="/a/site.#{campaign}/js/kd.js?#{KONFIG.version}"></script>
+      <script>KD.userAccount=#{userAccount}</script>
+      <script>KD.campaignStats=#{campaignStats}</script>
       <script src="/a/site.#{campaign}/js/main.js?#{KONFIG.version}"></script>

       <!-- SEGMENT.IO -->
@@ -49,6 +54,16 @@
     </html>
     """

-  callback null, prepareHTML()
+  switch campaign
+    when 'hackathon'
+      bongoModels.JWFGH.getStats account, (err, stats) ->
+
+        return console.log err  if err
+
+        campaignStats = JSON.stringify stats
+        callback null, prepareHTML()
+    else
+      callback null, prepareHTML()

+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -56,7 +56,7 @@
   enemy.follow(player)

   # => The enemy now has it's velocity attribute set in
-  # the dire
+  # the direction of the

   enemy.I.velocity = enemy.I.direction.scale(4)
   # now the enemy has a velocity, pointing toward player,


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -15,6 +15,7 @@
   y: 50
   width: 10
   height: 10
+  ve

 enemy.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -6,7 +6,8 @@
 each object.

 This method relies on both objects having position methods.
-All GameObjects have this method by default.
+
+include

 player = GameObject


Added missing return statements to the annotation manager module

--- lib/annotation-manager.coffee
+++ lib/annotation-manager.coffee
@@ -33,9 +33,13 @@
       else
         annotationOverlayView.hide()

+    return
+
   removeAnnotationForExecutionIssue: (executionIssue) ->
     executionIssueId = executionIssue.getId()
     @textEditorSubscriptionsByExecutionIssueId[executionIssueId].dispose()
     delete @textEditorSubscriptionsByExecutionIssueId[executionIssueId]
     @markersByExecutionIssueId[executionIssueId].destroy()
-    delete @markersByExecutionIssueId[executionIssueId]+    delete @markersByExecutionIssueId[executionIssueId]
+
+    return


Only reposition hidden input when editor has focus

--- src/editor.coffee
+++ src/editor.coffee
@@ -696,6 +696,7 @@

   handleInputEvents: ->
     @on 'cursor:moved', =>
+      return unless @isFocused
       cursorView = @getCursorView()
       @hiddenInput.offset(cursorView.offset()) if cursorView.is(':visible')


update app.url

update app.url

--- env.coffee
+++ env.coffee
@@ -19,7 +19,7 @@
 		url:		"mongodb://#{proj}rw:pass1234@localhost/#{proj}"

 	app:
-		url:		"http://localhost:3000/#{proj}"
+		url:		app.url
 		path:		"/#{proj}"
 		uploadDir:	"#{__dirname}/uploads"
 		mode:		parseInt('0700', 8)


check if _editor is defined

check if _editor is defined

--- lib/atom-ternjs-client.coffee
+++ lib/atom-ternjs-client.coffee
@@ -37,6 +37,7 @@
   update: (editor) ->
     _editor = @manager.getEditor(editor)
     return Promise.resolve() if _editor?.diffs.length is 0
+    _editor?.diffs = []
     # buffer = editor.getBuffer()
     # if buffer.getMaxCharacterIndex() > 5000
     #   doDiff = true
@@ -44,7 +45,6 @@
     #     start = Math.max(0, diff.oldRange.start.row - 50)
     #     end = Math.min(buffer.getLineCount(), diff.oldRange.end.row + 20)
     #   text = buffer.getTextInRange([[start, 0], [end, buffer.lineLengthForRow(end)]])
-    _editor.diffs = []
     # if (false)
       # @post(JSON.stringify
       #   files: [


Adding element remove comment. :

Adding element remove comment.
:

--- src/engine.coffee
+++ src/engine.coffee
@@ -56,9 +56,13 @@

     # Remove the given elements from the engine.
     remove : (elements...) ->
-        # FIXME: won't scale. O(n)
+        # FIXME: won't scale O(n)
         for element in elements
             index = @elements.indexOf(element)
+            # FIXME: element's are often removed while we're iterating the
+            # engine's elements (e.g. during collision handling) which is
+            # causing problematic behaviour and lots of checks for element is
+            # not null when looping over elements.
             @elements.splice(index, 1) if index >= 0
         return this


some cleanup

some cleanup

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -174,14 +174,15 @@
         "tree-view:#{command}", => @clearFocusedElement()

   # updates styling on the .tree-view-resizer and the panel element
-  update: (pushEditor = getConfig('pushEditor'))->
+  update: (pushEditor = getConfig('pushEditor')) ->
     Promise.resolve().then =>
+      panelView = atom.views.getView @treeView.panel
       if pushEditor
         @treeViewEl.style.position = ''
-        atom.views.getView(@treeView.panel)?.style.width = ''
+        panelView?.style.width = ''
       else
         @treeViewEl.style.position = 'absolute'
-        atom.views.getView(@treeView.panel)?.style.width = "#{getConfig('hiddenWidth')}px"
+        panelView?.style.width = "#{getConfig 'hiddenWidth'}px"
       @resize()
     .catch error

@@ -295,9 +296,6 @@
         , delay
         return

-      # explicitly set the elements initial width
-      @treeViewEl.style.width = "#{initialWidth}px"
-
       # cache the current animationPlayer so we can
       # cancel it as another animation begins
       @currentAnimation = animation = @treeViewEl.animate [


update configuration docs

update configuration docs

--- src/garble.coffee
+++ src/garble.coffee
@@ -5,7 +5,9 @@
 #   None
 #
 # Configuration:
-#   None
+#   YANDEX_TRANSLATE_API_KEY              string; a Yandex API key (required)
+#   HUBOT_GARBLE_LOCAL_LANGUAGE           string; language code for chatroom language (optional; default is 'en')
+#   HUBOT_GARBLE_TRANSLATION_PATH_LOG     boolean; display the translation path that text followed during garbling process (optional; default is true)
 #
 # Commands:
 #   hubot garble <text> - garbles your text
@@ -23,13 +25,13 @@
 LANGUAGES = require('./data/languages.json')
 TRANSLATE_URI = 'https://translate.yandex.net/api/v1.5/tr.json/translate'
 API_KEY = process.env.YANDEX_TRANSLATE_API_KEY
-NATIVE_LANGUAGE = 'en'
+LOCAL_LANGUAGE = process.env.HUBOT_GARBLE_LOCAL_LANGUAGE || 'en'
+SHOW_TRANSLATION_PATH = process.env.HUBOT_GARBLE_TRANSLATION_PATH_LOG || true
 DEFAULT_ITERATIONS = 5
-SUPPRESS_TRANSLATION_PATH_LOG = process.env.HUBOT_GARBLE_TRANSLATION_PATH_LOG || false

 getRandomLanguages = (count) ->
-  # an array of all available language codes except NATIVE_LANGUAGE
-  languageCodes = _.chain(LANGUAGES).pluck('code').pull(NATIVE_LANGUAGE).value()
+  # an array of all available language codes except LOCAL_LANGUAGE
+  languageCodes = _.chain(LANGUAGES).pluck('code').pull(LOCAL_LANGUAGE).value()

   # an array of randomly sorted languages
   randomLanguages = _.times count, () ->
@@ -37,8 +39,8 @@
     _.pull(languageCodes, lang)
     lang

-  # add NATIVE_LANGUAGE as first and last items
-  _.chain(randomLanguages).unshift(NATIVE_LANGUAGE).push(NATIVE_LANGUAGE).value()
+  # add LOCAL_LANGUAGE as first and last items
+  _.chain(randomLanguages).unshift(LOCAL_LANGUAGE).push(LOCAL_LANGUAGE).value()

 getRequestString = (langFrom, langTo, text) ->
   opts =
@@ -73,5 +75,5 @@
             request getRequestString(langs[4], langs[5], parseResponse(body)), (error, response, body) ->
               request getRequestString(langs[5], langs[6], parseResponse(body)), (error, response, body) ->
                 msg.send parseResponse(body)
-                unless SUPPRESS_TRANSLATION_PATH_LOG == 'true'
+                unless SHOW_TRANSLATION_PATH == 'false'
                   msg.send logTranslationPath(langs)


scroll to step when mounted or it changes

--- src/components/exercise/index.cjsx
+++ src/components/exercise/index.cjsx
@@ -7,6 +7,7 @@
 ExerciseGroup = require './group'
 ExerciseBadges = require '../exercise-badges'
 ExerciseIdentifierLink = require '../exercise-identifier-link'
+ScrollTo = require '../scroll-to-mixin'

 ExerciseMixin =
   getLastPartId: ->
@@ -68,6 +69,7 @@
         includeGroup: false
         includeFooter: @shouldControl(part.id)
         keySet: keySet
+        stepPartIndex: index
         key: "exercise-part-#{index}"

       # stim and stem are the same for different steps currently.
@@ -97,7 +99,6 @@
         onContinue: _.partial onNextStep, currentStep: step.stepIndex

     footerProps = _.omit(@props, 'onContinue')
-
     <ExFooter {...canContinueControlProps} {...footerProps} panel='review'/>

   renderIdLink: (related = true) ->
@@ -114,25 +115,31 @@

 ExerciseWithScroll = React.createClass
   displayName: 'ExerciseWithScroll'
-  mixins: [ExerciseMixin]
+  mixins: [ExerciseMixin, ScrollTo]
+
+  componentDidMount: ->
+    {currentStep} = @props
+    if currentStep?
+      @scrollToSelector("[data-step='#{currentStep}']")
+
+  componentWillReceiveProps: (nextProps) ->
+    if nextProps.currentStep isnt @props.currentStep
+      @scrollToSelector("[data-step='#{nextProps.currentStep}']")
+

   render: ->
     {parts, footer, pinned} = @props

     if @isSinglePart()
-      return @renderSinglePart()
-
-    exerciseParts = @renderMultiParts()
-
-    exerciseGroup = @renderGroup()
-    footer ?= @renderFooter() if pinned
-
-    <CardBody footer={footer} pinned={pinned} className='openstax-multipart-exercise-card'>
-      <ExerciseBadges isMultipart={true}/>
-      {exerciseGroup}
-      {exerciseParts}
-      {@renderIdLink(false)}
-    </CardBody>
+      @renderSinglePart()
+    else
+      footer ?= @renderFooter() if pinned
+      <CardBody footer={footer} pinned={pinned} className='openstax-multipart-exercise-card'>
+        <ExerciseBadges isMultipart={true}/>
+        {@renderGroup()}
+        {@renderMultiParts()}
+        {@renderIdLink(false)}
+      </CardBody>

 Exercise = React.createClass
@@ -142,19 +149,18 @@
     {footer, pinned} = @props

     if @isSinglePart()
-      return <CardBody footer={footer} className='openstax-multipart-exercise-card'>
+      <CardBody footer={footer} className='openstax-multipart-exercise-card'>
         { @renderSinglePart() }
       </CardBody>
-
-    exerciseParts = @renderMultiParts()
-    exerciseGroup = @renderGroup()
-    footer ?= @renderFooter()
-
-    <CardBody footer={footer} pinned={pinned} className='openstax-multipart-exercise-card'>
-      <ExerciseBadges isMultipart={true}/>
-      {exerciseGroup}
-      {exerciseParts}
-      {@renderIdLink(false)}
-    </CardBody>
+    else
+      <CardBody pinned={pinned}
+        footer={footer or @renderFooter()}
+        className='openstax-multipart-exercise-card'
+      >
+        <ExerciseBadges isMultipart={true}/>
+        {@renderGroup()}
+        {@renderMultiParts()}
+        {@renderIdLink(false)}
+      </CardBody>

 module.exports = {Exercise, ExerciseWithScroll}


answer correctness logic was giving false positives

--- src/components/question/index.cjsx
+++ src/components/question/index.cjsx
@@ -43,17 +43,20 @@
   getChildContext: ->
     processHtmlAndMath: @props.processHtmlAndMath

+  isArrayMissingProperty: (arrayToCheck, property) ->
+    not _.isEmpty(_.compact(_.pluck(arrayToCheck, property)))
+
   hasAnswerCorrectness: ->
     {correct_answer_id, model} = @props
     {answers} = model

-    correct_answer_id? or not _.isEmpty _.pluck(answers, 'correctness')
+    correct_answer_id or @isArrayMissingProperty(answers, 'correctness')

   hasSolution: ->
     {model, correct_answer_id} = @props
     {collaborator_solutions} = model

-    @hasAnswerCorrectness() and not _.isEmpty(_.pluck(collaborator_solutions, 'content_html'))
+    @hasAnswerCorrectness() and @isArrayMissingProperty(collborator_solutions, 'content_html')

   render: ->
     {model, correct_answer_id, exercise_uid, className} = @props


add task details to task end

add task details to task end

--- src/components/task-step/ends.cjsx
+++ src/components/task-step/ends.cjsx
@@ -159,8 +159,14 @@
     courseId: React.PropTypes.string.isRequired

   render: ->
-    {courseId, backButton} = @props
-    footer = backButton
+    {courseId, taskId, backButton} = @props
+    task = TaskStore.get(taskId)
+
+    footer = <PinnableFooter>
+        {backButton}
+
+
{task.title}

+      </PinnableFooter>

       <CardBody footer={footer} className='-reading-completed'>


Fix: typo

Fix: typo

--- src/core/config.coffee
+++ src/core/config.coffee
@@ -48,7 +48,7 @@
       DEFAULT_DATA.blitzPath = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\World of Tanks Blitz"
     when "m"
       DEFAULT_DATA.blitzPath = "~/Library/Application Support/Steam/SteamApps/common/World of Tanks Blitz/World of Tanks Blitz.app/Contents/Resources/"
-      # DEFAULT_DATA.blitzPath = "Applications/World of Tanks Blitz.app/Contents/Resources/"
+      # DEFAULT_DATA.blitzPath = "~/Applications/World of Tanks Blitz.app/Contents/Resources/"
     else
       DEFAULT_DATA.blitzPath = "World of Tanks Blitz"
   return


better heuristic for guessing change index

We can't just concat the diffs between two deltas since the left delta
diff may omit trailing retains.

--- src/core/editor.coffee
+++ src/core/editor.coffee
@@ -180,13 +180,12 @@
     newIndex = @savedRange?.start
     try
       if oldIndex? and newIndex? and oldIndex <= @delta.length() and newIndex <= newDelta.length()
-        oldLeftDelta = @delta.slice(0, oldIndex)
         oldRightDelta = @delta.slice(oldIndex)
-        newLeftDelta = newDelta.slice(0, newIndex)
         newRightDelta = newDelta.slice(newIndex)
-        diffLeft = oldLeftDelta.diff(newLeftDelta)
-        diffRight = oldRightDelta.diff(newRightDelta)
-        return new Delta(diffLeft.ops.concat(diffRight.ops))
+        if _.isEqual(oldRightDelta.ops, newRightDelta.ops)
+          oldLeftDelta = @delta.slice(0, oldIndex)
+          newLeftDelta = newDelta.slice(0, newIndex)
+          return oldLeftDelta.diff(newLeftDelta)
     catch ignored
     return @delta.diff(newDelta)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -50,7 +50,7 @@
     speed: 1

   ###*
-  Set your direction to face another object.
+  Set your veloc to face another object.

   enemy.follow(player)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -51,11 +51,7 @@
   Set your direction to face another object.

-  # Player's health will be within [0, 100] at the end of every update
-  player.clamp
-    health:
-      min: 0
-      max: 100
+  play

   # Score can only be positive
   player.clamp


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -5,7 +5,7 @@
 The calculated direction is based on the center point of
 each object.

-This method relies on both objects having position` methods.
+This method relies on both objects having position methods.
 All GameObjects have this method by default.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -69,4 +69,4 @@
   @param {GameObject} obj The object you want to follow
   ###
   follow: (obj) ->
-    I.direction = obj.position().subtract(self.position()).norm()
+    I.velocity = obj.position().subtract(self.position()).norm()


Fix test to trap room_users emission

Fix test to trap room_users emission

--- plugins/tenpoints/test/socket.spec.coffee
+++ plugins/tenpoints/test/socket.spec.coffee
@@ -55,9 +55,16 @@
     @client.onceJSON (data) =>
       expect(data).to.eql({route: "join", body: {room: room, first: true}})
       @client2.writeJSON {route: "join", body: {room}}
-      @client2.onceJSON (data) ->
-        expect(data).to.eql({route: "join", body: {room: room, first: true}})
-        done()
+      async.parallel [
+        (done) =>
+          @client.onceJSON (data) =>
+            expect(data.route).to.eql("room_users")
+            done()
+        (done) =>
+          @client2.onceJSON (data) ->
+            expect(data).to.eql({route: "join", body: {room: room, first: true}})
+            done()
+      ], done

   it "updates a tenpoint", (done) ->
     @client.writeJSON {


Add an option to turn off interpolation for LinePath shapes

--- src/core/shapes.coffee
+++ src/core/shapes.coffee
@@ -239,6 +239,7 @@
     points = args.points or []
     @order = args.order or 3
     @tailSize = args.tailSize or 3
+    @interpolate = if 'interpolate' of args then args.interpolate else true

     # The number of smoothed points generated for each point added
     @segmentSize = Math.pow(2, @order)
@@ -286,6 +287,8 @@
   addPoint: (point) ->
     @points.push(point)

+    return @smoothedPoints = @points if !@interpolate
+
     if not @smoothedPoints or @points.length < @sampleSize
       @smoothedPoints = bspline(@points, @order)
     else


properly convert 'for (var i = 0; ...)' into an ast that escodegen can deal with (we were putting an ExpressionStatement into .init, which can only be a VariableDeclaration or an Expression)

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -269,6 +269,15 @@
                 @function_stack.shift()
                 n

+        visitFor: (n) ->
+                @skipExpressionStatement = true
+                init = @visit n.init
+                @skipExpressionStatement = false
+                n.test = @visit n.test
+                n.update = @visit n.update
+                n.body = @visit n.body
+                n
+
         visitForIn: (n) ->
                 if n.left.type is syntax.VariableDeclaration
                         @function_stack[0].vars.push
@@ -302,23 +311,34 @@
                         assignments = []
                         for i in [0...n.declarations.length]
                                 if n.declarations[i].init?
-                                        assignments.push
-                                                type: syntax.ExpressionStatement
-                                                expression:
-                                                        type: syntax.AssignmentExpression
-                                                        left: create_identifier n.declarations[i].id.name
-                                                        right: n.declarations[i].init
-                                                        operator: "="
+                                        assignment =
+                                                type: syntax.AssignmentExpression
+                                                left: create_identifier n.declarations[i].id.name
+                                                right: n.declarations[i].init
+                                                operator: "="
+
+                                        if @skipExpressionStatement
+                                                assignments.push assignment
+                                        else
+                                                assignments.push
+                                                        type: syntax.ExpressionStatement
+                                                        expression: assignment

                         # now return the new assignments, which will replace the original variable
                         # declaration node.
                         if assignments.length == 1
                                 return assignments[0]

-                        return {
-                                type: syntax.BlockStatement
-                                body: assignments
-                        }
+                        if @skipExpressionStatement
+                                return {
+                                        type: syntax.SequenceExpression
+                                        expressions: assignments
+                                }
+                        else
+                                return {
+                                        type: syntax.BlockStatement
+                                        body: assignments
+                                }
                 n

 # this class really doesn't behave like a normal NodeVisitor, as it modifies the tree in-place
@@ -348,7 +368,9 @@
                 # if any of the variables declared in n.init are closed over
                 # we promote all of them outside of n.init.

+                @skipExpressionStatement = true
                 init = @visit n.init
+                @skipExpressionStatement = false
                 n.test = @visit n.test
                 n.update = @visit n.update
                 n.body = @visit n.body
@@ -384,18 +406,22 @@
                 for decl in n.declarations
                         decl.init = @visit decl.init
                         if @currentMapping().hasOwnProperty decl.id.name
-                                rv.push {
-                                        type: syntax.ExpressionStatement
-                                        expression:
-                                                type: syntax.AssignmentExpression,
-                                                operator: "="
-                                                left:
-                                                        type: syntax.CallExpression
-                                                        callee: create_identifier "%slot"
-                                                        arguments: [ @currentMapping()["%env"], (create_number_literal @currentMapping()["%slot_mapping"][decl.id.name]) ]
-                                                right: decl.init || { type: syntax.Identifier, name: "undefined" }
-
-                                }
+                                assignment =
+                                        type: syntax.AssignmentExpression,
+                                        operator: "="
+                                        left:
+                                                type: syntax.CallExpression
+                                                callee: create_identifier "%slot"
+                                                arguments: [ @currentMapping()["%env"], (create_number_literal @currentMapping()["%slot_mapping"][decl.id.name]) ]
+                                        right: decl.init || { type: syntax.Identifier, name: "undefined" }
+
+                                if @skipExpressionStatement
+                                        rv.push assignment
+                                else
+                                        rv.push {
+                                                type: syntax.ExpressionStatement
+                                                expression: assignment
+                                        }
                         else
                                 rv.push {
                                         type: syntax.VariableDeclaration
@@ -729,4 +755,7 @@
         passes.forEach (passType) ->
                 pass = new passType(filename)
                 tree = pass.visit tree
+                #debug.log 1, "after: #{passType.name}"
+                #debug.log 1, -> escodegen.generate tree
+
         tree


Revert to the default lock timeout now we have write barriers

Revert "increase lock timeouts for archiving"

This reverts commit 9eee1b383772adf058130d6e5eab409f57ce03cd.

--- app/coffee/DocArchiveManager.coffee
+++ app/coffee/DocArchiveManager.coffee
@@ -6,11 +6,6 @@
 _ = require "underscore"
 async = require "async"
 settings = require("settings-sharelatex")
-
-# increase lock timeouts because archiving can be slow
-LockManager.LOCK_TEST_INTERVAL = 500 # 500ms between each test of the lock
-LockManager.MAX_LOCK_WAIT_TIME = 30000 # 30s maximum time to spend trying to get the lock
-LockManager.LOCK_TTL = 30 # seconds

 module.exports = DocArchiveManager =


feature(macAutocomplete): Support attribute declaration style

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -48,7 +48,7 @@
   "$document"
   "keys"
   ($animate, $http, $filter, $compile, $timeout, $parse, $rootScope, $document, keys) ->
-    restrict:    "E"
+    restrict:    "EA"
     templateUrl: "template/autocomplete.html"
     replace:     true
     require:     "ngModel"


Refactor: Beautify comments

Refactor: Beautify comments

--- app/components/blockly-basic.coffee
+++ app/components/blockly-basic.coffee
@@ -1,90 +1,98 @@
 `import Ember from 'ember'`

 BlocklyBasicComponent = Ember.Component.extend(
-
+
 	tagName: 'div'

 	classNames: ['ember-blockly']
-
+
 	blocklyEditor: null
-
+
 	allBlocks_VisualType_InWorkspace: null
-
+
 	allBlocks_EmberObjType_FromWorkspace: Ember.A([])

 	selectedBlock_EmberObjType: null
-
+
 	# --------------------------------
 	# --- Component Initialisation ---
 	# --------------------------------
 	didInsertElement: ->

 		self = @
-
-		# -----------------------------------------------------
+
+		# ----------------------------------------------------------
 		# Initialise Blockly-Editor
-		# Note! This editor is composed of: Toolbox + Workspace
-		# -----------------------------------------------------
-		# instantiate a blockly-editor
-		@blocklyEditor = Blockly.inject('blocklyDiv',
+		# Note! A blockly-editor is composed of: Toolbox + Workspace
+		# ----------------------------------------------------------
+		# Instantiate a blockly-editor
+		@blocklyEditor = Blockly.inject('blocklyDiv',
 						{ media: 'media/', toolbox: self.$('.blocky-basic-toolbox').get(0)})
-
-		# Assign ember-context to blockly-editor's
-		# (i.e. a context that wraps/controls the blockly-editor's behaviour)
+
+		# Assign ember-context to blockly-editor
 		@blocklyEditor._emberContext = @
-
-		# -------------------------------------------------
-		# Handle Custom Blocks
-		# -------------------------------------------------
-		# Create & inject custom visual-blocks into toolbox
+
+		# -----------------------------------------------------
+		# Inject Custom Blocks
+		# -----------------------------------------------------
+		# Create & inject custom visual-blocks into the toolbox
 		# Note! Toolbox contains: Categories
-		# -------------------------------------------------
-		# 'Itebooks' visual-block: Create & inject a custom visual-block to be
-		# housed in the toolbox category called 'Datasource'
+		# -----------------------------------------------------
+		# 'Itebooks' visual-block: Create & inject a custom visual-block
+		# to be the toolbox category called 'Datasource'
 		@addCustomVisualBlockItebooksBlock_DatasourceCategory(@)

-		# 'Join' visual-block: Create & inject a custom visual-block to be
-		# housed in the toolbox category called 'Datasource'
+		# 'Join' visual-block: Create & inject a custom visual-block
+		# to be the toolbox category called 'Datasource'
 		@addCustomVisualBlockJoinBlock_DatasourceCategory(@)
-
+
 		# ------------------
-		# Populate workspace
+		# Populate Workspace
 		# ------------------
-		# Approach 1: Populate workspace with predefined visual-block-composition:
-		# Handle predefinition of TEXT type that containing XML
+		# Approach 1: Populate workspace with predefined visual-block
+		# composition: Handle predefinition of TEXT type that containing XML
 		xml_text = @get('blockToLoad')
 		if xml_text
 			xml = Blockly.Xml.textToDom(xml_text)
 			Blockly.Xml.domToWorkspace(@blocklyEditor, xml)
-
+
 		###
-		# Approach 2: Populate workspace with predefined visual-block-composition:
+		# Approach 2: Populate workspace with predefined visual-block composition:
 		# Handle predefinition of DOM element type that containing XML
-		Blockly.Xml.domToWorkspace(@blocklyEditor, self.$('.blocky-basic-initial-blocks1').get(0))
+		Blockly.Xml.domToWorkspace(
+			@blocklyEditor, self.$('.blocky-basic-initial-blocks1').get(0)
+		)
 		###

 		# --------------------
 		# Setup Event-Handlers
 		# --------------------
-		#--------------------------------------------------------------------------
-		# 1. Assign event-handler for reacting to visual-block's mouse-interactions
-		# 2. Build an ember-object-type collection from visual-blocks
-		#--------------------------------------------------------------------------
+		# ----------------------------------------------------------------------
+		# 1. Assign event-handler for mouse-interactions with visual-blocks
+		# 2. Build an ember-object-type collection to house visual-block objects
+		# ----------------------------------------------------------------------
 		@allBlocks_VisualType_InWorkspace = Blockly.mainWorkspace.getAllBlocks()
 		for visualBlock in @allBlocks_VisualType_InWorkspace

-			# Assign mouse-interaction event-handler for each visual-block found in the workspace
-			Blockly.bindEvent_(visualBlock.getSvgRoot(), 'mouseup', visualBlock, @blockOnClick)
-
-			# Build-up an ember-object-type collection from all visual-blocks found in the workspace
-			@allBlocks_EmberObjType_FromWorkspace.pushObject(Ember.Object.create({block: visualBlock}))
-
-      		#--------------------------------------------------------------------------------
-		# Assign event-handler for reacting to add/removal of visual-blocks to workspace
-      		#--------------------------------------------------------------------------------
+			# Assign mouse-interaction event-handler for
+			# each visual-block found in the workspace
+			Blockly.bindEvent_(
+				visualBlock.getSvgRoot(), 'mouseup', visualBlock, @blockOnClick
+			)
+
+			# Build-up an ember-object-type collection from
+			# all visual-blocks found in the workspace
+			@allBlocks_EmberObjType_FromWorkspace.pushObject(
+				Ember.Object.create( {block: visualBlock} )
+			)
+
+    # -----------------------------------------------------
+		# Assign event-handler for reacting to addition/removal
+		# of visual-blocks to/from the workspace
+    # -----------------------------------------------------
 		@blocklyEditor.addChangeListener(@workspaceOnChange)
-
-
+
+
 	# ---------------------
 	# --- Action Events ---
 	# ---------------------
@@ -98,15 +106,15 @@
 			Blockly.JavaScript.INFINITE_LOOP_TRAP = 'if (--window.LoopTrap == 0) throw "Infinite loop.";\n'
 			code = Blockly.JavaScript.workspaceToCode(@blocklyEditor)
 			Blockly.JavaScript.INFINITE_LOOP_TRAP = null
-
-			# -------------------
-			# Run JavaScript code
-			# -------------------
+
+			# -----------------------
+			# Execute JavaScript code
+			# -----------------------
 			try
 				eval(code)
 			catch error
 				alert error
-
+
 		show_JSCode: ->

 			# --------------------------------------------
@@ -115,18 +123,19 @@
 			Blockly.JavaScript.INFINITE_LOOP_TRAP = null
 			code = Blockly.JavaScript.workspaceToCode(@blocklyEditor)
 			alert(code)
-
+
 		show_WorkspaceAsXml: ->

-			# ---------------------------------------------------------------------------------
-			# Generate XML from visual-block-composition present within the workspace & display
-			# ---------------------------------------------------------------------------------
+			# ------------------------------------------
+			# Generate XML from visual-block composition
+			# present within the workspace & display
+			# ------------------------------------------
 			xml = Blockly.Xml.workspaceToDom(@blocklyEditor)
 			xml_text = Blockly.Xml.domToText(xml)
 			console.log xml_text
 			alert(xml_text)
-
-
+
+
 	# -------------------------------
 	# --- Declare Local Functions ---
 	# -------------------------------
@@ -138,27 +147,27 @@
 		# ------------------------------------
 		# Initialise & set selected-block item
 		# ------------------------------------
-		# Act only on the selected-visual-block
+		# Act only on the selected visual-block
 		if Blockly.selected.id == @id

-			# --------------------------------------------
-			# Get selected-block item of ember-object-type
-			# --------------------------------------------
+			# ---------------------------------------------------------
+			# Get selected block item from ember-object-type collection
+			# ---------------------------------------------------------
 			editorEmberContext = Blockly.mainWorkspace._emberContext
 			selectedItem = editorEmberContext.allBlocks_EmberObjType_FromWorkspace.
-						findBy('block.id', Blockly.selected.id)
+											findBy('block.id', Blockly.selected.id)

 			# -------------------------------------------------------
-			# Initialise the selected-block item of ember-object-type
+			# Initialise the selected block item of ember-object-type
 			# -------------------------------------------------------
 			selectedItem.block._outputModel = Ember.A([])
 			selectedItem.block._isChildernRouteLoaded = false

 			# ------------------------------------------------
-			# Set the selected-block item of ember-object-type
+			# Set the selected block item of ember-object-type
 			# ------------------------------------------------
 			editorEmberContext.set('selectedBlock_EmberObjType', selectedItem)
-
+
 	# -----------------------------------------------------------------------
 	# Respond-To-Event: On add/removal of visual-blocks to/from the workspace
 	# -----------------------------------------------------------------------
@@ -166,13 +175,14 @@

 		allVisualBlocks_Latest = Blockly.mainWorkspace.getAllBlocks()
 		editorEmberContext = Blockly.mainWorkspace._emberContext
-
-		# ---------------------------------------------------------------------------
-		# Scenario 1: When number of visual-blocks in the workspace remains as before
-		# ---------------------------------------------------------------------------
+
+		# ----------------------------------------
+		# Scenario 1: When number of visual-blocks
+		# in the workspace remains unchange
+		# ----------------------------------------
 		if allVisualBlocks_Latest.length == editorEmberContext.allBlocks_VisualType_InWorkspace.length
 			# Do nothing
-			return
+			return

 		# ----------------------------------------------------------
 		# Scenario 2: On addition of a visual-block to the workspace
@@ -180,18 +190,19 @@
 		else if allVisualBlocks_Latest.length > editorEmberContext.allBlocks_VisualType_InWorkspace.length

 			addedVisualBlock = Blockly.selected
-
+
 			# Assign mouse-interaction event-handler for the newly added visual-block
-			Blockly.bindEvent_(addedVisualBlock.getSvgRoot(),
-						'mouseup', addedVisualBlock, editorEmberContext.blockOnClick)
+			Blockly.bindEvent_(addedVisualBlock.getSvgRoot(), 'mouseup',
+				addedVisualBlock, editorEmberContext.blockOnClick)

 			# Keep account of newly added visual-block entries into the workspace
 			editorEmberContext.allBlocks_VisualType_InWorkspace.push(addedVisualBlock)
-
-			# Create & add a new blocks entry to the ember-object-type collection
-			editorEmberContext.allBlocks_EmberObjType_FromWorkspace.
-				pushObject(Ember.Object.create({block: addedVisualBlock}))
-
+
+			# Create & add a new block entry to the ember-object-type collection
+			editorEmberContext.allBlocks_EmberObjType_FromWorkspace.pushObject(
+				Ember.Object.create( {block: addedVisualBlock} )
+			)
+
 		# -----------------------------------------------------------
 		# Scenario 3: On-removal of a visual-block from the workspace
 		# -----------------------------------------------------------
@@ -205,40 +216,42 @@
 					# Purge visual-blocks from the ember-object-type collection
 					# ---------------------------------------------------------
 					deletedBlock = editorEmberContext.allBlocks_VisualType_InWorkspace.
-							findBy('id', id)
-					editorEmberContext.allBlocks_EmberObjType_FromWorkspace.
-						removeObject(editorEmberContext.allBlocks_EmberObjType_FromWorkspace.
-								findBy('block.id', deletedBlock.id))
+													findBy('id', id)
+					editorEmberContext.allBlocks_EmberObjType_FromWorkspace.removeObject(
+						editorEmberContext.allBlocks_EmberObjType_FromWorkspace.
+							findBy('block.id', deletedBlock.id)
+					)

 					# -----------------------------------------------------
-					# Re-initialise the selected-block of ember-object-type
+					# Re-initialise the selected block of ember-object-type
 					# -----------------------------------------------------
 					editorEmberContext.set('selectedBlock_EmberObjType', null)

-			# Keep account of all the latest visual-blocks within the workspace
+			# Keep account of all the latest visual-blocks within the workspace
 			editorEmberContext.allBlocks_VisualType_InWorkspace = allVisualBlocks_Latest

-
+
 	# ---------------------------------------------------------
-	# --- Declare Custom Visual-Blocks with code generators ---
+	# --- Declare custom visual-blocks with code generators ---
 	# ---------------------------------------------------------
-	#-----------------------------------------------------------------------------------------------------------
-	# Custom Visual-Block
-	#-----------------------------------------------------------------------------------------------------------
-	# Name: 		It-ebooks
+
+	#-----------------------------------------------------------------------------
+	# Custom Visual-Block: 1
+	#-----------------------------------------------------------------------------
+	# Name: 						It-ebooks
 	# Target-Category: 	Datasource
 	# Block Factory URL:
-	# 			v0.2 >> https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#ky4fee
-	# 			v0.1 >> https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#kkjtrt
-	#-----------------------------------------------------------------------------------------------------------
-	addCustomVisualBlockItebooksBlock_DatasourceCategory: (context)->
+	# 									v0.2 >> https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#ky4fee
+	# 									v0.1 >> https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#kkjtrt
+	#-----------------------------------------------------------------------------
+	addCustomVisualBlockItebooksBlock_DatasourceCategory: (context)->

 		# --------------------------------------------------------------------------
 		# Declare custom visual-block: It-eBooks
 		# --------------------------------------------------------------------------
-		# Block Functionality: To make RESTful search-query request to IT-ebooks API
-		# Input Type: - None -
-		# Output Type: Array
+		# Block Functionality:	To make RESTful search-query request to IT-ebooks API
+		# Input Type: 					- None -
+		# Output Type:					Array
 		# --------------------------------------------------------------------------
 		Blockly.Blocks['it-ebooks_datasource'] = {

@@ -257,7 +270,7 @@
 				@setHelpUrl('')

 		}
-
+
 		# --------------------------
 		# Code-generator: JavaScript
 		# --------------------------
@@ -266,28 +279,29 @@
 			text_search_string = block.getFieldValue('SEARCH_STRING')
 			theUrl = "http://it-ebooks-api.info/v1/search/restful"
 			code = "var xmlHttp = new XMLHttpRequest();" +
-				   "xmlHttp.open( 'GET', 'http://it-ebooks-api.info/v1/search/restful', false );" +
-				   "xmlHttp.send( null );" +
-				   "var response = xmlHttp.responseText;"
+							"xmlHttp.open( 'GET', 'http://it-ebooks-api.info/v1/search/restful', false );" +
+							"xmlHttp.send( null );" +
+							"var response = xmlHttp.responseText;"
 			# TODO: Change ORDER_NONE to the correct strength.
 			return [code, Blockly.JavaScript.ORDER_NONE]
-
-	#-----------------------------------------------------------------------------------------------------------
-	# Custom Visual-Block
-	#-----------------------------------------------------------------------------------------------------------
-	# Name: 		Join
+
+	#-----------------------------------------------------------------------------
+	# Custom Visual-Block: 2
+	#-----------------------------------------------------------------------------
+	# Name: 						Join
 	# Target-Category: 	Datasource
-	# Source: 		https://github.com/google/blockly/blob/e3009310713209e80acc58a49d677d69f80d7b70/blocks/text.js starting from line 69 (i.e. Blockly.Blocks['text_join'])
-	#-----------------------------------------------------------------------------------------------------------
+	# Source: 					https://github.com/google/blockly/blob/e3009310713209e80acc58a49d677d69f80d7b70/blocks/text.js starting from line 69 (i.e. Blockly.Blocks['text_join'])
+	#-----------------------------------------------------------------------------
 	addCustomVisualBlockJoinBlock_DatasourceCategory: (context)->

-		# ----------------------------------------------------------------------------------
+		# -----------------------------------------------------
 		# Declare custom visual-block: Join
-		# ----------------------------------------------------------------------------------
-		# Block Functionality: Aggregate outputs of mulitple datasource-blocks of array type
-		# Input Type: Array
-		# Output Type: Array
-		# ----------------------------------------------------------------------------------
+		# -----------------------------------------------------
+		# Block Functionality:	Aggregate outputs of mulitple
+		#												datasource-blocks of array type
+		# Input Type:						Array
+		# Output Type:					Array
+		# -----------------------------------------------------
 		Blockly.Blocks['join_datasource'] = {

 			init: ->
@@ -299,37 +313,37 @@
 				@setColour(290) # 290 = Purple colour
 				@setTooltip('Aggregate several datasources output together')
 				@setHelpUrl('')
-
-			# -----------------------------------------------
-			# Create XML to represent number of array inputs.
-			# -----------------------------------------------
-			# @return {Element} XML storage elemen
-			# @this Blockly.Block
-			# -----------------------------------------------
+
+			# ----------------------------------------------
+			# Create XML to represent number of array inputs
+			# ----------------------------------------------
+			# @return {Element} XML storage element
+			# @this Blockly.Block
+			# ----------------------------------------------
 			mutationToDom: ->

 				container = document.createElement('mutation')
 				container.setAttribute('items', @itemCount_)
 				return container
-
-			# -------------------------------------------------
-			# Parse XML to restore the array inputs.
-			# -------------------------------------------------
-			# @param {!Element} xmlElement XML storage element.
-			# @this Blockly.Block
-			# -------------------------------------------------
+
+			# ------------------------------------------------
+			# Parse XML to restore the array inputs
+			# ------------------------------------------------
+			# @param {!Element} xmlElement XML storage element
+			# @this Blockly.Block
+			# ------------------------------------------------
 			domToMutation: (xmlElement) ->

 				@itemCount_ = parseInt(xmlElement.getAttribute('items'), 10)
 				@updateShape_()
-
-			# -----------------------------------------------------------
-			# Populate the mutator's dialog with this block's components.
-			# -----------------------------------------------------------
+
+			# ----------------------------------------------------------
+			# Populate the mutator's dialog with this block's components
+			# ----------------------------------------------------------
 			# @param {!Blockly.Workspace} Mutator's workspace.
 			# @return {!Blockly.Block} Root block in mutator.
 			# @this Blockly.Block
-			# -----------------------------------------------------------
+			# ----------------------------------------------------------
 			decompose: (workspace) ->

 				containerBlock = Blockly.Block.obtain(workspace, 'text_create_join_container')
@@ -341,18 +355,18 @@
 					connection.connect(itemBlock.previousConnection)
 					connection = itemBlock.nextConnection
 				return containerBlock
-
-			# ----------------------------------------------------------------
-			# Reconfigure this block based on the mutator dialog's components.
-			# ----------------------------------------------------------------
-			# @param {!Blockly.Block} containerBlock is Root block in mutator.
-			# @this Blockly.Block
-			# ----------------------------------------------------------------
+
+			# ---------------------------------------------------------------
+			# Reconfigure this block based on the mutator dialog's components
+			# ---------------------------------------------------------------
+			# @param {!Blockly.Block} containerBlock is Root block in mutator
+			# @this Blockly.Block
+			# ---------------------------------------------------------------
 			compose: (containerBlock) ->

 				itemBlock = containerBlock.getInputTargetBlock('STACK')

-				# ----------------------
+				# ----------------------
 				# Count number of inputs
 				# ----------------------
 				connections = []
@@ -363,20 +377,20 @@
 					i++
 				@itemCount_ = i
 				@updateShape_()
-
+
 				# --------------------------
 				# Reconnect any child blocks
 				# --------------------------
 				for num in [0..@itemCount_-1]
 					if connections[num]
 						@getInput('ADD' + num).connection.connect(connections[num])
-
-			# ----------------------------------------------------------------
+
+			# ---------------------------------------------------------------
 			# Store pointers to any connected child blocks
-			# ----------------------------------------------------------------
-			# @param {!Blockly.Block} containerBlock is Root block in mutator.
-			# @this Blockly.Block
-			# ----------------------------------------------------------------
+			# ---------------------------------------------------------------
+			# @param {!Blockly.Block} containerBlock is Root block in mutator
+			# @this Blockly.Block
+			# ---------------------------------------------------------------
 			saveConnections: (containerBlock) ->

 				itemBlock = containerBlock.getInputTargetBlock('STACK')
@@ -386,13 +400,13 @@
 				  itemBlock.valueConnection_ = input && input.connection.targetConnection
 				  itemBlock = itemBlock.nextConnection && itemBlock.nextConnection.targetBlock()
 				  i++
-
-			# -------------------------------------------------------
-			# Modify this block to have the correct number of inputs.
-			# -------------------------------------------------------
+
+			# ------------------------------------------------------
+			# Modify this block to have the correct number of inputs
+			# ------------------------------------------------------
 			# @private
 			# @this Blockly.Block
-			# -------------------------------------------------------
+			# ------------------------------------------------------
 			updateShape_: ->

 				# -----------------
@@ -405,7 +419,7 @@
 					while @getInput('ADD' + i)
 						@removeInput('ADD' + i)
 						i++
-
+
 				# -------------
 				# Rebuild block
 				# -------------
@@ -419,22 +433,22 @@
 						if num == 0
 							input.appendField('Join - Datasource')
 		}
-
+

 	# -------------------------
 	# --- Declare Observers ---
 	# -------------------------
 	selectedBlock_EmberObjectType_Changed: ( ->

-		# ---------------------------------------------------------
-		# On selecting a visual-block transtion to respective route
-		# ---------------------------------------------------------
+		# ----------------------------------------------------------
+		# On selecting a visual-block, transtion to respective route
+		# ----------------------------------------------------------
 		if @selectedBlock_EmberObjType
 			blockToSelect = @allBlocks_VisualType_InWorkspace.
-						findBy('id', @selectedBlock_EmberObjType.block.id)
+												findBy('id', @selectedBlock_EmberObjType.block.id)
 			blockToSelect.select()
 			@get('currentController').transitionToRoute(blockToSelect.type,
-									@selectedBlock_EmberObjType.block.id)
+				@selectedBlock_EmberObjType.block.id)
 		# -------------------------------------
 		# By default, transition to index route
 		# -------------------------------------
@@ -450,15 +464,16 @@

 		# Clear ember-object-type collection
 		@allBlocks_EmberObjType_FromWorkspace.clear()
-
-		# Get predefined visual-block-composition, which is to be loaded on to workspace
+
+		# Get predefined visual-block-composition to-be loaded to the workspace
 		xml_text = @get('currentController.blockToLoad')

-		# -----------------------------------------------------------------------------
+		# --------------------------------------------------------------------------
 		# 1. Load to workspace the predefined visual-block-composition
 		# 2. Add event-handlers to visual-blocks found in workspace
-		# 3. Load visual-blocks from workspace as entry to ember-object-type collection
-		# -----------------------------------------------------------------------------
+		# 3. Add visual-blocks from workspace as entry to ember-object-type
+		# 	 collection
+		# --------------------------------------------------------------------------
 		# Load predefined visual-block-composition to the workspace
 		if xml_text
 			xml = Blockly.Xml.textToDom(xml_text)
@@ -467,12 +482,15 @@
 			@allBlocks_VisualType_InWorkspace = Blockly.mainWorkspace.getAllBlocks()
 			for visualBlock in @allBlocks_VisualType_InWorkspace

-				# Assign mouse-interaction event-handler for each visual-block found in the workspace
-				Blockly.bindEvent_(visualBlock.getSvgRoot(), 'mouseup', visualBlock, @blockOnClick)
+				# Assign mouse-interaction event-handler for
+				# each visual-block found in the workspace
+				Blockly.bindEvent_(visualBlock.getSvgRoot(), 'mouseup', visualBlock,
+					@blockOnClick)

 				# Create & add a new blocks entry to the ember-object-type collection
-				@allBlocks_EmberObjType_FromWorkspace.
-					pushObject(Ember.Object.create({block: visualBlock}))
+				@allBlocks_EmberObjType_FromWorkspace.pushObject(
+					Ember.Object.create( {block: visualBlock} )
+				)

 	).observes('currentController.blockToLoad')
 )


Remove looking at local chat storage for public files

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -274,15 +274,6 @@
             chat               : undefined
             chat_width         : undefined

-        # grab chat state from local storage
-        local_storage = require('./editor').local_storage
-        if local_storage?
-            opts.chat       ?= local_storage(@project_id, opts.path, 'is_chat_open')
-            opts.chat_width ?= local_storage(@project_id, opts.path, 'chat_width')
-
-        if misc.filename_extension(opts.path) == 'sage-chat'
-            opts.chat = false
-
         @_ensure_project_is_open (err) =>
             if err
                 @set_activity(id:misc.uuid(), error:"opening file -- #{err}")
@@ -336,11 +327,19 @@
                         if not is_public
                             # the ? is because if the user is anonymous they don't have a file_use Actions (yet)
                             @redux.getActions('file_use')?.mark_file(@project_id, opts.path, 'open')
-
                             @log
                                 event     : 'open'
                                 action    : 'open'
                                 filename  : opts.path
+
+                            # grab chat state from local storage
+                            local_storage = require('./editor').local_storage
+                            if local_storage?
+                                opts.chat       ?= local_storage(@project_id, opts.path, 'is_chat_open')
+                                opts.chat_width ?= local_storage(@project_id, opts.path, 'chat_width')
+
+                            if misc.filename_extension(opts.path) == 'sage-chat'
+                                opts.chat = false

                         store = @get_store()
                         if not store?  # if store not initialized we can't set activity


allow find to run longer

allow find to run longer

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -1612,8 +1612,8 @@
         webapp_client.exec
             project_id      : @project_id
             command         : cmd + " | cut -c 1-256"  # truncate horizontal line length (imagine a binary file that is one very long line)
-            timeout         : 10   # how long grep runs on client
-            network_timeout : 15   # how long network call has until it must return something or get total error.
+            timeout         : 20   # how long grep runs on client
+            network_timeout : 25   # how long network call has until it must return something or get total error.
             max_output      : max_output
             bash            : true
             err_on_exit     : true


Fixed drag state in window controller to make it compatible with new drag method.

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -227,11 +227,13 @@
     view = @dragView

     {pageX, pageY}   = event
-    {startX, startY} = view.dragState
+    {initial}        = view.dragState.position
+    initialX         = initial.x
+    initialY         = initial.y

     delta =
-      x : pageX - startX
-      y : pageY - startY
+      x : pageX - initialX
+      y : pageY - initialY

     view.drag event, delta


[test] [fix] Switched port range to avoid conflict

--- test/front.coffee
+++ test/front.coffee
@@ -14,7 +14,7 @@
   it 'should start, connect to api, and provide a WS server', ->
     api = new Api
     engine = new Engine
-    engine.start {port: 3033, host: "0.0.0.0" }, () =>
-      api.start {port: 3032, host: "0.0.0.0", engineEndpoint: 'ws://0.0.0.0:3033' }, () =>
-        front.start {port: 3034, host: "0.0.0.0", apiEndpoint: 'ws://0.0.0.0:3022' }, () =>
+    engine.start {port: 3043, host: "0.0.0.0" }, () =>
+      api.start {port: 3042, host: "0.0.0.0", engineEndpoint: 'ws://0.0.0.0:3043' }, () =>
+        front.start {port: 3044, host: "0.0.0.0", apiEndpoint: 'ws://0.0.0.0:3042' }, () =>
           done


Conditionally include deprecations in Cursor

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -39,13 +39,13 @@
         textChanged: textChanged
         cursor: this

-      @emit 'moved', movedEvent
+      @emit 'moved', movedEvent if Grim.includeDeprecations
       @emitter.emit 'did-change-position', movedEvent
       @editor.cursorMoved(movedEvent)
     @marker.onDidDestroy =>
       @destroyed = true
       @editor.removeCursor(this)
-      @emit 'destroyed'
+      @emit 'destroyed' if Grim.includeDeprecations
       @emitter.emit 'did-destroy'
       @emitter.dispose()

@@ -89,6 +89,8 @@
     @emitter.on 'did-change-visibility', callback

   on: (eventName) ->
+    return unless Grim.includeDeprecations
+
     switch eventName
       when 'moved'
         Grim.deprecate("Use Cursor::onDidChangePosition instead")
@@ -222,9 +224,6 @@
   # Returns a {ScopeDescriptor}
   getScopeDescriptor: ->
     @editor.scopeDescriptorForBufferPosition(@getBufferPosition())
-  getScopes: ->
-    Grim.deprecate 'Use Cursor::getScopeDescriptor() instead'
-    @getScopeDescriptor().getScopesArray()

   # Public: Returns true if this cursor has no non-whitespace characters before
   # its current position.
@@ -476,10 +475,6 @@

     endOfWordPosition or currentBufferPosition

-  getMoveNextWordBoundaryBufferPosition: (options) ->
-    Grim.deprecate 'Use `::getNextWordBoundaryBufferPosition(options)` instead'
-    @getNextWordBoundaryBufferPosition(options)
-
   # Public: Retrieves the buffer position of where the current word starts.
   #
   # * `options` (optional) An {Object} with the following keys:
@@ -593,7 +588,7 @@
   setVisible: (visible) ->
     if @visible != visible
       @visible = visible
-      @emit 'visibility-changed', @visible
+      @emit 'visibility-changed', @visible if Grim.includeDeprecations
       @emitter.emit 'did-change-visibility', @visible

   # Public: Returns the visibility of the cursor.
@@ -686,3 +681,12 @@
         position = range.start
         stop()
     position
+
+if Grim.includeDeprecations
+  Cursor::getScopes = ->
+    Grim.deprecate 'Use Cursor::getScopeDescriptor() instead'
+    @getScopeDescriptor().getScopesArray()
+
+  Cursor::getMoveNextWordBoundaryBufferPosition = (options) ->
+    Grim.deprecate 'Use `::getNextWordBoundaryBufferPosition(options)` instead'
+    @getNextWordBoundaryBufferPosition(options)


That needed a comment

That needed a comment

--- src/dentin.coffee
+++ src/dentin.coffee
@@ -93,6 +93,8 @@
     kind = @el_kind(node)
     kind.mixed = kind.mixed or parent_kind?.mixed

+    # If we're not outputting a version string, and this is the root, we don't
+    # want a newline.
     if not @noversion or parent_kind?
       out "\n"
     out spaces @indent_spaces*indent


:lipstick:

:lipstick:

--- src/keytar.coffee
+++ src/keytar.coffee
@@ -4,22 +4,26 @@
   getPassword: (service, account) ->
     throw new Error("Service is required.") unless service?.length > 0
     throw new Error("Account is required.") unless account?.length > 0
+
     keytar.getPassword(service, account)

   addPassword: (service, account, password) ->
     throw new Error("Service is required.") unless service?.length > 0
     throw new Error("Account is required.") unless account?.length > 0
     throw new Error("Password is required.") unless password?.length > 0
+
     keytar.addPassword(service, account, password)

   deletePassword: (service, account) ->
     throw new Error("Service is required.") unless service?.length > 0
     throw new Error("Account is required.") unless account?.length > 0
+
     keytar.deletePassword(service, account)

   replacePassword: (service, account, password) ->
     throw new Error("Service is required.") unless service?.length > 0
     throw new Error("Account is required.") unless account?.length > 0
     throw new Error("Password is required.") unless password?.length > 0
+
     keytar.deletePassword(service, account)
     keytar.addPassword(service, account, password)


WIP support up, down, right, left linewise duplicate

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -312,10 +312,16 @@
     lineTexts = @duplicateText(selection)
     console.log inspect(lineTexts)
     switch @direction
-      when 'up'
-        null
-      when 'down'
-        null
+      when 'up', 'down'
+        reversed = selection.isReversed()
+
+        which = if @direction is 'up' then 'start' else 'end'
+        point = selection.getBufferRange()[which]
+
+        text = lineTexts.join("\n") + "\n"
+        range = @editor.setTextInBufferRange([point, point], text)
+        selection.setBufferRange(range, {reversed})
+
       when 'left', 'right'
         reversed = selection.isReversed()
         text = lineTexts.join("\n") + "\n"


Stub forward

Stub forward

--- src/draw_turtle.coffee
+++ src/draw_turtle.coffee
@@ -43,6 +43,8 @@
         draw_legs(scale) + draw_face(scale)# + draw_eyes(scale)
         draw_shell(scale)
       ]
+    forward: (world, args) ->
+      "Do nothing"
     children: {
       ME: {
         i: 2


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/emitterable.coffee
+++ src/emitterable.coffee
@@ -36,7 +36,8 @@
     I.particles.invoke "trigger", "overlay", canvas

   self.bind 'beforeUpdate', (dt) ->
-    I.particles.invoke "trigger", "beforeU"
+    I.particles.invoke "trigger", "beforeUpdate", dt
+

   self.bind 'update', (dt) ->
     I.batchSize.times ->


Use editor.fontFamily config value in preview

Previously it was just using a monospace font family

Closes #132

--- lib/project/match-view.coffee
+++ lib/project/match-view.coffee
@@ -14,7 +14,7 @@

     @li class: 'search-result list-item', =>
       @span range.start.row + 1, class: 'line-number text-subtle'
-      @span class: 'preview', =>
+      @span class: 'preview', outlet: 'preview', =>
         @span prefix
         @span match.matchText, class: 'match highlight-info', outlet: 'matchText'
         @span match.matchText, class: 'replacement highlight-success', outlet: 'replacementText'
@@ -23,6 +23,9 @@
   initialize: (@model, {@filePath, @match}) ->
     @render()
     @subscribe @model, 'replacement-pattern-changed', @render
+
+    if fontFamily = atom.config.get('editor.fontFamily')
+      @preview.css('font-family', fontFamily)

   render: =>
     if @model.replacementPattern and @model.regex and not @model.replacedPathCount?


add to model tests

add to model tests

--- test/model.coffee
+++ test/model.coffee
@@ -68,3 +68,61 @@
     inst.fetch()

     done()
+
+  it 'should have default URL if not provided', (done) ->
+    m = fission.model
+      props:
+        firstName: 'string'
+        lastName: 'string'
+    inst = new m
+      firstName: 'Larry'
+      lastName: 'Page'
+
+    inst.urlRoot.should.equal '/'
+
+    done()
+
+  it 'should map model.url to model.urlRoot', (done) ->
+    m = fission.model
+      props:
+        firstName: 'string'
+        lastName: 'string'
+      url: '/api/v2/users'
+    inst = new m
+      firstName: 'Larry'
+      lastName: 'Page'
+    inst.urlRoot.should.exist
+    inst.urlRoot.should.equal '/api/v2/users'
+
+    done()
+
+  it 'should set model.url to function', (done) ->
+    m = fission.model
+      props:
+        firstName: 'string'
+        lastName: 'string'
+      url: '/api/v2/users'
+    inst = new m
+      firstName: 'Larry'
+      lastName: 'Page'
+    inst.url.should.exist
+    inst.url.should.be.type 'function'
+
+    done()
+
+  it 'should save new data with proxies', (done) ->
+    m = fission.model
+      props:
+        firstName: 'string'
+        lastName: 'string'
+      url: '/api/v2/users'
+    inst = new m
+      firstName: 'Larry'
+      lastName: 'Page'
+    inst.on 'change', (data) ->
+      data.firstName.should.equal 'Ellen'
+      inst.firstName.should.equal 'Ellen'
+      done()
+
+    inst.firstName = 'Ellen'
+    inst.save()


user Promise.join instead of Promise.all

--- src/kite-server/kite-server.coffee
+++ src/kite-server/kite-server.coffee
@@ -86,11 +86,10 @@

   register: ({ kontrolURL: u, host: h, kiteKey: k }) ->
     throw new Error "Already registered!"  if @kontrol?
-    Promise.all([
-      Promise.cast u
-      Promise.cast h
-      @normalizeKiteKey k
-    ]).spread (userKontrolURL, host, key) =>
+    url     = Promise.cast u
+    host    = Promise.cast h
+    kiteKey = @normalizeKiteKey k
+    Promise.join url, host, kiteKey, (userKontrolURL, host, key) =>
       { name, username, environment, version, region, hostname, logLevel, transportClass, secure } = @options

       Server = @getServerClass()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -48,7 +48,7 @@
   @param {Number} [frequency=3] The number of frames in between opacity changes
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###
-  flicker: ({duration, frequency, alpha}={}) ->
+  flicker: ({duration, frequency, alpha}) ->
     Object.reverseMerge I,
       flickerAlpha: 0.5
       flickerDuration: 30


fix(error): Let APIErrors have proper stack traces

--- src/flux/errors.coffee
+++ src/flux/errors.coffee
@@ -12,8 +12,14 @@
     @statusCode ?= @response?.statusCode
     @requestOptions ?= @response?.requestOptions
     @name = "APIError"
+    @stack = (new Error()).stack
     @message = @body?.message ? @body ? @error?.toString?()
     @errorTitle = @body?.error
+
+  fromJSON: (json = {}) ->
+    for key, val of json
+      @[key] = val
+    @

 class TimeoutError extends Error
   constructor: ->