Fixes to codecat lib to pass tests

Fixes to codecat lib to pass tests

--- src/codecat.coffee
+++ src/codecat.coffee
@@ -39,38 +39,48 @@
 		lineReader.on 'close', ->
 			callback?(prepend: prepends, append: appends)

-	concat: (options, callback) ->
+	concat: (options = {}, callback) ->
+		[options, callback] = [{}, options] if options instanceof Function
 		{newline = EOL} = options
-		@findConcats (concats) ->
+		@findConcats (concats) =>
 			concatted = ''
-			concats?.prepends?.forEach (prepend) ->
-				concatted += readFileSync(prepend, encoding: @encoding)
-				concatted += newline
+			concats?.prepend?.map (prepend) => @getRelativePath(prepend)
+				.forEach (prependPath) ->
+					concatted += readFileSync(prependPath, encoding: @encoding)
+					concatted += newline
 			concatted += readFileSync(@source, encoding: @encoding)
-			concats?.appends?.forEach (append) ->
-				concatted += newline
-				concatted += readFileSync(append, encoding: @encoding)
+			concats?.append?.map (append) => @getRelativePath(append)
+				.forEach (appendPath) ->
+					concatted += newline
+					concatted += readFileSync(appendPath, encoding: @encoding)
 			callback?(concatted)

 	concatTo: (file, options, callback) ->
+		[options, callback] = [{}, options] if options instanceof Function
 		{newline = EOL} = options
-		@findConcats (concats) ->
+		@findConcats (concats) =>
 			error = null
 			stream = createWriteStream(file, defaultEncoding: @encoding)
-			stream.once 'open', ->
-				concats?.prepends?.forEach (prepend) ->
-					write = readFileSync(prepend, encoding: @encoding)
-					write += newline
-					stream.write(write)
+			stream.once 'open', =>
+				concats?.prepend?.map (prepend) => @getRelativePath(prepend)
+					.forEach (prependPath) ->
+						write = readFileSync(prependPath, encoding: @encoding)
+						write += newline
+						stream.write(write)
 				write = readFileSync(@source, encoding: @encoding)
 				stream.write(write)
-				concats?.appends?.forEach (append) ->
-					write = newline
-					write += readFileSync(append, encoding: @encoding)
-					stream.write(write)
-				stream.end()
-				callback?(error)
+				concats?.append?.map (append) => @getRelativePath(append)
+					.forEach (appendPath) ->
+						write = newline
+						write += readFileSync(appendPath, encoding: @encoding)
+						stream.write(write)
+				stream.end ->
+					callback?(error)

+	getRelativePath: (file) ->
+		sourceDir = Path.dirname(@source)
+		Path.join(sourceDir, file)
+
 	@Commenters =
 		'': '//'
 		js: '//'


Silly error names

Silly error names

--- app/lib/world/world.coffee
+++ app/lib/world/world.coffee
@@ -55,7 +55,7 @@
     @thangMap[thang.id] = thang

   thangDialogueSounds: ->
-    if @frames.length < @totalFrames then worldShouldBeOverBeforeGrabbingDialogue
+    if @frames.length < @totalFrames then throw new Error("World should be over before grabbing dialogue")
     [sounds, seen] = [[], {}]
     for frame in @frames
       for thangID, state of frame.thangStateMap
@@ -245,7 +245,7 @@

   serialize: ->
     # Code hotspot; optimize it
-    if @frames.length < @totalFrames then worldShouldBeOverBeforeSerialization
+    if @frames.length < @totalFrames then throw new Error("World Should Be Over Before Serialization")
     [transferableObjects, nontransferableObjects] = [0, 0]
     o = {name: @name, totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}}
     o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []


Move mediator assignment to top of initialize

--- app/models/TILEDMap.coffee
+++ app/models/TILEDMap.coffee
@@ -27,13 +27,15 @@
   ###
   initialize: ->
     super
+    mediator.map = @
+
     @imgLoadCount = 0
     canvas = document.createElement 'canvas'
     ctx = canvas.getContext '2d'
     @set 'canvas':canvas
     @set 'ctx':ctx
+
     @listenTo @, 'change:fullyLoaded', @render
-    mediator.map = @

   ###
   Starts a XMLHttpRequest and calls the given callback when finished loading.


refactor: Improving readability

refactor: Improving readability

--- src/compile.coffee
+++ src/compile.coffee
@@ -219,10 +219,10 @@

 compileOriginExampleName = (mediaType, parseResult, httpTransaction) ->
-  exampleName = ''
-
   transition = parent(httpTransaction, parseResult, {element: 'transition'})
   httpResponse = child(httpTransaction, {element: 'httpResponse'})
+
+  exampleName = ''

   if mediaType is 'text/vnd.apiblueprint'
     if content(transition.attributes.examples) > 1


Transforms the minimap package into an instance of a Minimap class

--- lib/minimap.coffee
+++ lib/minimap.coffee
@@ -1,6 +1,7 @@
 MinimapView = require './minimap-view'

-module.exports =
+class Minimap
+
   # We'll be storing each MinimapView using the id of their PaneView
   minimapViews: {}

@@ -44,3 +45,5 @@
         delete @minimapViews[paneView.model.id]

         @updateAllViews()
+
+module.exports = new Minimap()


remove deprecated tabs script support on homepage

--- app/assets/javascripts/application/home.coffee
+++ app/assets/javascripts/application/home.coffee
@@ -1,41 +1,4 @@
 jQuery.initializer 'body.show.resource_home', ->
-  show_tab = (panel) ->
-    unless $(panel).find('iframe').length
-      spinner = new Spinner(
-        lines: 13
-        length: 7
-        width: 4
-        radius: 10
-        rotate: 0
-        color: '#000'
-        speed: 1
-        trail: 60
-        shadow: false
-        hwaccel: false
-        className: 'spinner'
-        zIndex: 2e9
-        top: 88
-        left: 180
-      ).spin(panel)
-      $(panel).append($(panel).find('script').html())
-      $(panel).find('iframe').load ->
-        $(this).css(opacity: 1)
-        spinner.stop()
-
-  stop_videos = ->
-    $('#tabs iframe').each ->
-      $f(this).api('pause')
-
-  $("#tabs").tabs
-    create: (enevt, ui) ->
-      $('#tabs img').css('display', 'block')
-    select: (event, ui) ->
-      do stop_videos
-      show_tab(ui.panel)
-
-  $(window).load ->
-    show_tab($('#tabs-1')[0])
-
   # jquery footer cycle
   jQuery(".partner-slider-1, .partner-slider-2, .partner-slider-3").cycle
     fx: 'fade',


name the link match

name the link match

--- src/packages/gfm.tmbundle/Syntaxes/gfm.cson
+++ src/packages/gfm.tmbundle/Syntaxes/gfm.cson
@@ -62,6 +62,7 @@
   }
   {
     'match': '\\!?\\[([^\\]]*)\\]\\(([^\\)]+)\\)'
+    'name': 'link'
     'captures':
       '1': 'name': 'entity.gfm'
       '2': 'name': 'markup.underline.gfm'


fix(onboarding): Remove dead login specs

--- internal_packages/onboarding/spec/login-page-spec.cjsx
+++ internal_packages/onboarding/spec/login-page-spec.cjsx
@@ -1,45 +0,0 @@
-_ = require "underscore"
-React = require "react/addons"
-ReactTestUtils = React.addons.TestUtils
-
-LoginPage = require '../lib/login-page'
-OnboardingActions = require '../lib/onboarding-actions'
-
-describe "LoginPage", ->
-
-  it "shows env picker in Dev Mode", ->
-    spyOn(atom, "inDevMode").andReturn true
-    @loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
-    picker = ReactTestUtils.findRenderedDOMComponentWithClass(@loginPage, "environment-selector")
-    expect(picker).toBeDefined()
-
-  it "hides env picker in other modes", ->
-    spyOn(atom, "inDevMode").andReturn false
-    @loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
-    expect(-> ReactTestUtils.findRenderedDOMComponentWithClass(@loginPage, "environment-selector")).toThrow()
-
-  it 'can change the environment', ->
-    spyOn(atom, "inDevMode").andReturn true
-    spyOn(OnboardingActions, "changeAPIEnvironment")
-    @loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
-    sel = ReactTestUtils.findRenderedDOMComponentWithTag(@loginPage, "select")
-    ReactTestUtils.Simulate.change(sel, {target: {value: 'staging'}})
-    expect(OnboardingActions.changeAPIEnvironment).toHaveBeenCalledWith("staging")
-
-  describe "logging in", ->
-    beforeEach ->
-      @connectURL = "foo"
-      spyOn(OnboardingActions, "moveToPage")
-      @loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
-
-    hasEmail = (email) ->
-      page = OnboardingActions.moveToPage.calls[0].args[0]
-      data = OnboardingActions.moveToPage.calls[0].args[1]
-      expect(page).toBe "add-account-auth"
-      expect(data.url.length).toBeGreaterThan 0
-
-    it "submits information when the form submits", ->
-      @loginPage.setState email: "test@nylas.com"
-      form = ReactTestUtils.findRenderedDOMComponentWithClass(@loginPage, 'email-form')
-      ReactTestUtils.Simulate.submit(form)
-      hasEmail("test@nylas.com")


Fix bug in content_item.js

Fix bug in content_item.js

--- app/assets/javascripts/content_items.js.coffee
+++ app/assets/javascripts/content_items.js.coffee
@@ -11,7 +11,7 @@
   createSortables: ->
     $('.content-items').sortable
       axis: 'y'
-      handle: '.handle'
+      handle: '.move-handle'
       cursor: 'crosshair'
       items: '> .content-item'
       scroll: true


Restore broken code

Restore broken code

--- app/assets/javascripts/labels_select.js.coffee
+++ app/assets/javascripts/labels_select.js.coffee
@@ -20,15 +20,6 @@
       $value = $block.find('.value')
       $loading = $block.find('.block-loading').fadeOut()

-      if newLabelField.length
-        $newLabelCreateButton = $('.js-new-label-btn')
-        $colorPreview = $('.js-dropdown-label-color-preview')
-        $newLabelError = $dropdown.parent().find('.js-label-error')
-        $newLabelError.hide()
-
-        # Suggested colors in the dropdown to chose from pre-chosen colors
-        $('.suggest-colors-dropdown a').on 'click', (e) ->
-
       issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
       if issueUpdateURL
         labelHTMLTemplate = _.template(
@@ -43,6 +34,12 @@
         labelNoneHTMLTemplate = _.template('
None
')

       if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+        $newLabelCreateButton = $('.js-new-label-btn')
+        $colorPreview = $('.js-dropdown-label-color-preview')
+        $newLabelError = $dropdown.parent().find('.js-label-error')
+        $newLabelError.hide()
+
+        # Suggested colors in the dropdown to chose from pre-chosen colors
         $('.suggest-colors-dropdown a').on "click", (e) ->
           e.preventDefault()
           e.stopPropagation()


Add `settings` arg for jsonp util and `type` is required now

--- app/assets/javascripts/uploadcare/utils.coffee
+++ app/assets/javascripts/uploadcare/utils.coffee
@@ -266,11 +266,8 @@
     crossDomain: true
     cache: false

-  ns.jsonp = (url, type, data) ->
-    if $.isPlainObject(type)
-      data = type
-      type = 'GET'
-    $.ajax($.extend({url, type, data}, ns.ajaxDefaults)).then (data) ->
+  ns.jsonp = (url, type, data, settings = {}) ->
+    $.ajax($.extend({url, type, data}, settings, ns.ajaxDefaults)).then (data) ->
       if data.error
         text = data.error.content or data.error
         $.Deferred().reject(text)


Fix typo in send preparing dialog view controller

--- app/src/controllers/wallet/send/wallet_send_preparing_dialog_view_controller.coffee
+++ app/src/controllers/wallet/send/wallet_send_preparing_dialog_view_controller.coffee
@@ -16,7 +16,7 @@
           when ledger.errors.DustTransaction then 'dust_transaction'
         @dismiss =>
           errorMessage = switch reason
-            when 'dust_transaction' then _.str.sprintf(t("common.errors." + reason), ledger.formatters.formatValue(ledger.wallet.transaction.MINIMUM_OUTPUT_VALUE))
+            when 'dust_transaction' then _.str.sprintf(t("common.errors." + reason), ledger.formatters.formatValue(ledger.wallet.Transaction.MINIMUM_OUTPUT_VALUE))
             else t("common.errors." + reason)
           Api.callback_cancel 'send_payment', errorMessage
           dialog = new CommonDialogsMessageDialogViewController(kind: "error", title: t("wallet.send.errors.sending_failed"), subtitle: errorMessage)


Pulls and fixes gravatar url.

Pulls and fixes gravatar url.

--- app/assets/javascripts/visualization.js.coffee
+++ app/assets/javascripts/visualization.js.coffee
@@ -6,147 +6,146 @@

 # This function extracts the colors and frequencies from our input data
 window.setup_visualize = ->
-	window.all_colors = []
-	window.all_facet_nums = []
-	window.all_facet_names = []
-	window.colormap = {}
-	window.frequencies = {}
-	facets = $("li.vis_facet")
-
-	for facet in facets
-		color = $("input.color", facet).val()
-		facet_num = $("input.order", facet).val()
-		facet_name = facet.textContent
-		window.all_colors.push(color)
-		window.all_facet_nums.push(facet_num)
-		window.all_facet_names.push(facet_name)
-		window.frequencies[facet_num] = JSON.parse($("input.freq", facet).val())
+  window.all_colors = []
+  window.all_facet_nums = []
+  window.all_facet_names = []
+  window.colormap = {}
+  window.frequencies = {}
+  facets = $("li.vis_facet")

-
-	# Clicking on a facet sets the color
-	$("li.vis_button").click ->
-		window.select_facet($(this))
-
-	$("span.vis_selection").click ->
-		window.select_vis($(this))
+  for facet in facets
+    color = $("input.color", facet).val()
+    facet_num = $("input.order", facet).val()
+    facet_name = facet.textContent
+    window.all_colors.push(color)
+    window.all_facet_nums.push(facet_num)
+    window.all_facet_names.push(facet_name)
+    window.frequencies[facet_num] = JSON.parse($("input.freq", facet).val())

-	window.chart = new google.visualization.PieChart(document.getElementById('chart_div'));

-	#default empty chart for page load
-	dataArray = [['Facet', 'Highlights'],['No Word Selected',1]]
-	chartColors = ["lightgrey"]
-	window.drawChart(dataArray,chartColors)
+  # Clicking on a facet sets the color
+  $("li.vis_button").click ->
+    window.select_facet($(this))
+
+  $("span.vis_selection").click ->
+    window.select_vis($(this))
+
+  window.chart = new google.visualization.PieChart(document.getElementById('chart_div'));
+
+  #default empty chart for page load
+  dataArray = [['Facet', 'Highlights'],['No Word Selected',1]]
+  chartColors = ["lightgrey"]
+  window.drawChart(dataArray,chartColors)

 window.select_word = (i,word) ->
-	current_word = word.textContent
-	current_word = current_word.replace(/[^a-zA-Z0-9]+/g,"")
-	chart_title = 'Highlights for "' + current_word + '":'
-	$("p.chart_title").text(chart_title)
+  current_word = word.textContent
+  current_word = current_word.replace(/[^a-zA-Z0-9]+/g,"")
+  chart_title = 'Highlights for "' + current_word + '":'
+  $("p.chart_title").text(chart_title)

-	$("span.word").removeClass("vis-selected-word")
-	$(word).addClass("vis-selected-word")
+  $("span.word").removeClass("vis-selected-word")
+  $(word).addClass("vis-selected-word")

-	dataArray = [['Facet', 'Highlights']]
-	totalHighlights = 0
-	for facet_num in all_facet_nums
-		dataArray.push([window.all_facet_names[facet_num], frequencies[facet_num][i]])
-		totalHighlights += frequencies[facet_num][i]
+  dataArray = [['Facet', 'Highlights']]
+  totalHighlights = 0
+  for facet_num in all_facet_nums
+    dataArray.push([window.all_facet_names[facet_num], frequencies[facet_num][i]])
+    totalHighlights += frequencies[facet_num][i]

-	chartColors = [];
-	for facetColor in window.all_colors
-		chartColors.push($('.'+facetColor+'-vis').css('color'))
-
-	if totalHighlights==0
-		dataArray = [['Facet', 'Highlights'],['No Highlights',1]]
-		chartColors = ["lightgrey"]
+  chartColors = [];
+  for facetColor in window.all_colors
+    chartColors.push($('.'+facetColor+'-vis').css('color'))

-	window.drawChart(dataArray,chartColors)
+  if totalHighlights==0
+    dataArray = [['Facet', 'Highlights'],['No Highlights',1]]
+    chartColors = ["lightgrey"]
+
+  window.drawChart(dataArray,chartColors)

 # This function selects a facet, gives the box a border, and highlights text
 window.select_facet = (facet) ->
-	current_color = $("input.color", facet).val()
-	console.log(current_color)
-	current_num = $("input.order", facet).val()
-	$("span.facet.border").removeClass("border")
-	$(facet).find("span.facet").addClass("border")
-	$("body").attr('class', current_color)
+  current_color = $("input.color", facet).val()
+  console.log(current_color)
+  current_num = $("input.order", facet).val()
+  $("span.facet.border").removeClass("border")
+  $(facet).find("span.facet").addClass("border")
+  $("body").attr('class', current_color)

-	words = d3.selectAll("span.word")
-	for color in window.all_colors
-		words.classed(color+"-vis", false)
+  words = d3.selectAll("span.word")
+  for color in window.all_colors
+    words.classed(color+"-vis", false)

-	if current_color=='winning'
-		words.data(window.frequencies[0])
-			.transition()
-			.style("font-size", (d) -> min_size + "px")
+  if current_color=='winning'
+    words.data(window.frequencies[0])
+      .transition()
+      .style("font-size", (d) -> min_size + "px")

-		words = $("span.word")
-		words.each( (i,word) ->
+    words = $("span.word")
+    words.each( (i,word) ->

-			word_freqs = []
-			for facet_num in all_facet_nums
-				word_freqs.push([window.all_colors[facet_num],frequencies[facet_num][i]])
+      word_freqs = for facet_num in all_facet_nums
+        [frequencies[facet_num][i], window.all_colors[facet_num]]

-			word_freqs.sort (a,b) ->
-				return ((a[1] < b[1]) ? -1 : ((a[1] > b[1]) ? 1 : 0));
+      word_freqs.sort()
+      word_freqs.reverse()

-			#no highlighting gets grey, ties get black
+      #no highlighting gets grey, ties get black

-			if word_freqs[0][1] > word_freqs[1][1]
-				winning_color = word_freqs[0][0]
-				$(word).toggleClass(winning_color + "-vis", true)
-			else if word_freqs[0][1] != 0
-				$(word).css('color', 'black')
-			else
-				$(word).css('color', 'lightgrey')
+      if word_freqs[0][0] > word_freqs[1][0]
+        winning_color = word_freqs[0][1]
+        $(word).toggleClass(winning_color + "-vis", true)
+      else if word_freqs[0][0] != 0
+        $(word).css('color', 'black')
+      else
+        $(word).css('color', 'lightgrey')

-			#$(word).css('color', 'rgb('+Math.round(f*frequencies['red'][i])+','+Math.round(f*frequencies['green'][i])+','+Math.round(f*frequencies['blue'][i])+')')
-		)
+      #$(word).css('color', 'rgb('+Math.round(f*frequencies['red'][i])+','+Math.round(f*frequencies['green'][i])+','+Math.round(f*frequencies['blue'][i])+')')
+    )

-	else
-		min = Math.min.apply @, window.frequencies[current_num]
-		max = Math.max.apply @, window.frequencies[current_num]
-		multiplier = if max == 0 then 0 else (max_size-min_size)/(max-min)
-		# sets color and font-size by linear interpolation
-		words.data(window.frequencies[current_num])
-			.style("color", "") #clear color from 'winning' viz
-			.classed(current_color+"-vis", (d) -> (d > 0))
-			.transition()
-			.style("font-size", (d) -> (min_size+(d-min)*multiplier) + "px" )
+  else
+    min = Math.min.apply @, window.frequencies[current_num]
+    max = Math.max.apply @, window.frequencies[current_num]
+    multiplier = if max == 0 then 0 else (max_size-min_size)/(max-min)
+    # sets color and font-size by linear interpolation
+    words.data(window.frequencies[current_num])
+      .style("color", "") #clear color from 'winning' viz
+      .classed(current_color+"-vis", (d) -> (d > 0))
+      .transition()
+      .style("font-size", (d) -> (min_size+(d-min)*multiplier) + "px" )

 window.select_vis = (vis) ->
-	vis_type = $("input.vis_type", vis).val()
-	if vis_type == "font_size"
-		window.select_facet($("li.vis_button")[1])
+  vis_type = $("input.vis_type", vis).val()
+  if vis_type == "font_size"
+    window.select_facet($("li.vis_button")[1])

-		#remove highlighting for winning facet vis
-		$("span.word").each( (i,word) ->
-			$(word).unbind('click')
-			if $(word).hasClass("vis-selected-word")
-				$(word).removeClass("vis-selected-word")
-				$(word).addClass("vis-selected-word-hidden")
-		)
-	else if vis_type == "winning_facet"
-		window.select_facet($("li.vis_button")[0])
+    #remove highlighting for winning facet vis
+    $("span.word").each( (i,word) ->
+      $(word).unbind('click')
+      if $(word).hasClass("vis-selected-word")
+        $(word).removeClass("vis-selected-word")
+        $(word).addClass("vis-selected-word-hidden")
+    )
+  else if vis_type == "winning_facet"
+    window.select_facet($("li.vis_button")[0])

-		#clicking word updates pie chart
-		$("span.word").each( (i,word) ->
-			$(word).click ->
-				window.select_word(i,word)
-			if $(word).hasClass("vis-selected-word-hidden")
-				$(word).removeClass("vis-selected-word-hidden")
-				$(word).addClass("vis-selected-word")
-		)
+    #clicking word updates pie chart
+    $("span.word").each( (i,word) ->
+      $(word).click ->
+        window.select_word(i,word)
+      if $(word).hasClass("vis-selected-word-hidden")
+        $(word).removeClass("vis-selected-word-hidden")
+        $(word).addClass("vis-selected-word")
+    )

 window.drawChart = (dataArray,chartColors) ->
-	data = google.visualization.arrayToDataTable(dataArray);
-	options = {
-		legend: {'position':'bottom'}
-		chartArea:{left:0,top:0,width:"99%",height:"50%"}
-		colors: chartColors
-	};
-	window.chart.draw(data, options);
+  data = google.visualization.arrayToDataTable(dataArray);
+  options = {
+    legend: {'position':'bottom'}
+    chartArea:{left:0,top:0,width:"99%",height:"50%"}
+    colors: chartColors
+  };
+  window.chart.draw(data, options);


Declare correct octal

Declare correct octal

--- src/creator.coffee
+++ src/creator.coffee
@@ -17,7 +17,7 @@
   mkdirDashP: (path) ->
     Path.exists path, (exists) ->
       unless exists
-        Fs.mkdir path, '0755', (err) ->
+        Fs.mkdir path, 0o0755, (err) ->
           throw err if err

   # Copy the contents of a file from one place to another


Bugfix

Bugfix

--- app/controllers/TranscriptionController.coffee
+++ app/controllers/TranscriptionController.coffee
@@ -34,7 +34,8 @@
       Spine.Route.navigate '/'
       return

-    if @archive.progress() is 100 then Spine.Route.navigate '/'
+    if @archive.progress() is 100 then return Spine.Route.navigate '/'
+

     # Adjust page attributes for archive.
     $('body').addClass("transcribingScreen #{ @archive.slug() }")


Remove unused template

Remove unused template

--- go/templates/app/blank/index.coffee
+++ go/templates/app/blank/index.coffee
@@ -1,37 +0,0 @@
-class %%APPNAME%%MainView extends KDView
-
-  constructor:(options = {}, data)->
-    options.cssClass = '%%APPNAME%% main-view'
-    super options, data
-
-  viewAppended:->
-    @addSubView new KDView
-      partial : "Welcome to %%APPNAME%% app!"
-
-class %%APPNAME%%Controller extends AppController
-
-  constructor:(options = {}, data)->
-    options.view    = new %%APPNAME%%MainView
-    options.appInfo =
-      name     : "%%APPNAME%%"
-      type     : "application"
-
-    super options, data
-
-
-do ->
-
-  # In live mode you can add your App view to window's appView
-  if appView?
-
-    view = new %%APPNAME%%MainView
-    appView.addSubView view
-
-  else
-
-    KD.registerAppClass %%APPNAME%%Controller,
-      name     : "%%APPNAME%%"
-      routes   :
-        "/%%APPNAME%%" : null
-        "/Apps/%%AUTHOR%%/%%APPNAME%%/run" : null
-      behavior : "application"


Fix specs

Fix specs

--- app/utils/test-auth.coffee
+++ app/utils/test-auth.coffee
@@ -48,4 +48,6 @@
     if @get('state') == 'signed-in'
       'a-token'

+  refreshUserData: (->)
+
 `export default Auth`


tests for doc not found

tests for doc not found

--- test/encrypt.coffee
+++ test/encrypt.coffee
@@ -184,25 +184,38 @@
 			assert.equal err, null
 			done()

-	it 'should return an unencryped version', (done) ->
-			BasicEncryptedModel.findById simpleTestDoc3._id, (err, doc) ->
-				assert.equal err, null
-				assert.propertyVal doc, 'text', 'Unencrypted text'
-				assert.propertyVal doc, 'bool', true
-				assert.propertyVal doc, 'num', 42
-				assert.property doc, 'date'
-				assert.equal doc.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
-				assert.propertyVal doc, 'id2', mongoose.Schema.Types.ObjectId '5303e65d34e1e80d7a7ce212'
-				assert.lengthOf doc.arr, 2
-				assert.equal doc.arr[0], 'alpha'
-				assert.equal doc.arr[1], 'bravo'
-				assert.property doc, 'mix'
-				assert.deepEqual doc.mix, { str: 'A string', bool: false }
-				assert.property doc, 'buf'
-				assert.equal doc.buf.toString(), 'abcdefg'
-				assert.property doc, '_id'
-				assert.notProperty doc, '_ct'
-				done()
+	it 'when doc found, should pass an unencrypted version to the callback', (done) ->
+		BasicEncryptedModel.findById simpleTestDoc3._id, (err, doc) ->
+			assert.equal err, null
+			assert.propertyVal doc, 'text', 'Unencrypted text'
+			assert.propertyVal doc, 'bool', true
+			assert.propertyVal doc, 'num', 42
+			assert.property doc, 'date'
+			assert.equal doc.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
+			assert.propertyVal doc, 'id2', mongoose.Schema.Types.ObjectId '5303e65d34e1e80d7a7ce212'
+			assert.lengthOf doc.arr, 2
+			assert.equal doc.arr[0], 'alpha'
+			assert.equal doc.arr[1], 'bravo'
+			assert.property doc, 'mix'
+			assert.deepEqual doc.mix, { str: 'A string', bool: false }
+			assert.property doc, 'buf'
+			assert.equal doc.buf.toString(), 'abcdefg'
+			assert.property doc, '_id'
+			assert.notProperty doc, '_ct'
+			done()
+
+	it 'when doc not found by id, should pass null to the callback', (done) ->
+		BasicEncryptedModel.findById '534ec48d60069bc13338b354', (err, doc) ->
+			assert.equal err, null
+			assert.equal doc, null
+			done()
+
+	it 'when doc not found by query, should pass [] to the callback', (done) ->
+		BasicEncryptedModel.find text: 'banana', (err, doc) ->
+			assert.equal err, null
+			assert.isArray doc
+			assert.lengthOf doc, 0
+			done()

 describe 'EncryptedModel.find lean option', ->


contextFactory refactoring

contextFactory refactoring

--- src/contextFactory.coffee
+++ src/contextFactory.coffee
@@ -1,16 +1,16 @@
-define 'contextFactory', ['store', 'jasmine'], (DefaultStore, DefaultJasmine)->
-  (prop, Store = DefaultStore, Jasmine = DefaultJasmine)->
+define 'contextFactory', ['store', 'jasmine', 'privateStore'], (_Store_, _Jasmine_, _Context_)->
+  (prop, Store = _Store_, Jasmine = _Jasmine_, Context = _Context_)->
     return {} unless Jasmine.defined()

     @is = (value)->
       Store[prop] = value

-      do (Store = Store)->
-        Jasmine.instance.beforeEach.call this, ->
-          eval.call this, "var #{prop} = '#{Store[prop]}';"
+      Jasmine.instance.beforeEach.call Context.get(), ->
+        this[prop] = eval("'#{Store[prop]}';")
+        eval("#{prop} = this.#{prop};")

-        Jasmine.instance.afterEach.call this, ->
-          delete Store[prop]
-          eval.call this, "delete #{prop};"
+      Jasmine.instance.afterEach.call Context.get(), ->
+        delete Store[prop]
+        eval "delete #{prop};"

     this


Fix playfield initialization

Fix playfield initialization

--- src/core/Playfield.coffee
+++ src/core/Playfield.coffee
@@ -12,7 +12,7 @@
 	return

-Playfield::_initPathPlane = (width = DEFAULT.WIDTH, height = DEFAULT.HEIGHT) ->
+Playfield::_initPathPlane = (width, height) ->
 	@pathPlane = []

 	for i in [1..height]
@@ -54,7 +54,7 @@

 		@field.push line

-	@_initPathPlane width, height
+	@_initPathPlane @width, @height

 	@


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -18,6 +18,6 @@

   equals obj.I.maxHealth, 150

-test "should set both <attribute> and max<", ->
+test "should set both <attribute> and max<att>", ->

 module()


Rearrange cs2/gd1/wd1 in outcomes report if progress seems out of order

--- app/views/admin/OutcomeReportResultView.coffee
+++ app/views/admin/OutcomeReportResultView.coffee
@@ -17,6 +17,16 @@

     @courses = @options.courses.map (course) =>
       _.merge course, {completion: @options.courseCompletion[course._id].completion}
+
+    # Reorder CS2 in front of WD1/GD1 if it's more completed, to account for us changing the order around.
+    cs1 = _.find @courses, {slug: 'introduction-to-computer-science'}
+    cs2 = _.find @courses, {slug: 'computer-science-2'}
+    gd1 = _.find @courses, {slug: 'game-development-1'}
+    wd1 = _.find @courses, {slug: 'web-development-1'}
+
+    if cs2?.completion > _.max([gd1?.completion, wd1?.completion])
+      @courses.splice(@courses.indexOf(cs2), 1)
+      @courses.splice(_.max([@courses.indexOf(cs1), 0]) + 1, 0, cs2)
     super()

   onClickBackButton: ->


added camera transform to debug draw

added camera transform to debug draw

--- src/engine_physics.coffee
+++ src/engine_physics.coffee
@@ -67,7 +67,8 @@
           world.SetDebugDraw(debugDraw)

         world.DrawDebugData()
-        canvas.drawImage(debugElement, 0, 0, debugElement.width, debugElement.height, 0, 0, debugElement.width, debugElement.height)
+        canvas.withTransform I.cameraTransform, (canvas) ->
+          canvas.drawImage(debugElement, 0, 0, debugElement.width, debugElement.height, 0, 0, debugElement.width, debugElement.height)

     self.bind "beforeAdd", (entityData) ->
       entityData.world = world


Cleanup: minor

Cleanup: minor

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -76,9 +76,6 @@

     mainView.addSubView navView = @navController.getView()

-    # navView.on 'ItemWasAdded', @bound 'bindItemClickHandler'
-
-
     mainView.addSubView @tabView = new KDTabView
       tabHandleContainer : new KDCustomHTMLView


Passing specs

Passing specs

--- apps/artwork/test/client/layered-search.coffee
+++ apps/artwork/test/client/layered-search.coffee
@@ -13,20 +13,20 @@
 describe 'Layers, Layer', ->
   beforeEach ->
     sinon.stub Backbone, 'sync'
-    @artworkId  = 'foobar'
-    @layers     = new Layers artworkId: @artworkId
+    @artwork  = new Artwork fabricate 'artwork'
+    @layers   = new Layers artwork: @artwork

   afterEach ->
     Backbone.sync.restore()

   describe 'Layers', ->
-    it 'should have an artworkId', ->
-      @layers.artworkId.should.equal @artworkId
+    it 'should have an artwork', ->
+      @layers.artwork.should.equal @artwork

     it 'should fetch the correct URL', ->
       @layers.fetch()
       @layers.url.should.include 'api/v1/related/layers'
-      Backbone.sync.args[0][1].artworkId.should.equal @artworkId
+      Backbone.sync.args[0][1].artwork.id.should.equal @artwork.id

   describe 'Layer', ->
     beforeEach ->
@@ -36,14 +36,14 @@
         type: 'main'
       @layers.reset layers

-    it 'should have a collection and subsequently an artworkId', ->
+    it 'should have a collection and subsequently an artwork_id', ->
       @layers.first().collection.length.should.be.ok
-      @layers.first().artworkId.should.equal @artworkId
+      @layers.first().get('artwork_id').should.equal @artwork.id

     it 'should have an artworks collection with the appropriate URL', ->
       layer = @layers.first()
       layer.artworks.url.
-        should.include "/api/v1/related/layer/#{layer.get('type')}/#{layer.id}/artworks?artwork[]=#{layer.artworkId}"
+        should.include "/api/v1/related/layer/#{layer.get('type')}/#{layer.id}/artworks?artwork[]=#{layer.get('artwork_id')}"

 describe 'LayeredSearchView', ->
   before (done) ->
@@ -57,8 +57,8 @@

   beforeEach (done) ->
     sinon.stub Backbone, 'sync'
-    @artwork = new Artwork fabricate 'artwork'
-    @view = new LayeredSearchView $el: $('

'), artwork: @artwork
+    @artwork  = new Artwork fabricate 'artwork'
+    @view     = new LayeredSearchView $el: $('

'), artwork: @artwork
     done()

   afterEach ->
@@ -66,9 +66,9 @@

   describe '#setupLayers', ->
     it 'has the artwork ID', ->
-      @view.layers.artworkId.should.equal @artwork.id
+      @view.layers.artwork.id.should.equal @artwork.id
     it 'fetches the layer based on the artwork ID', ->
-      Backbone.sync.args[0][1].artworkId.should.equal @artwork.id
+      Backbone.sync.args[0][1].artwork.id.should.equal @artwork.id

   describe 'rendered view', ->
     beforeEach ->


stacks/stacktemplateitem: check for stack existency

--- client/home/lib/stacks/components/stacktemplateitem/index.coffee
+++ client/home/lib/stacks/components/stacktemplateitem/index.coffee
@@ -111,7 +111,7 @@

     editorUrl = "/Stack-Editor/#{template.get '_id'}"
     listItemClassName = 'HomeAppViewListItem-label'
-    unless isAdmin() or stack.get('accessLevel') is 'private'
+    unless isAdmin() or stack?.get('accessLevel') is 'private'
       listItemClassName = 'HomeAppViewListItem-label member'
     <div className='HomeAppViewListItem StackTemplateItem'>
       <a


Collaboration: Participants: Don't show context menu when any avatar view of participant is clicked

--- client/ide/lib/views/chat/idechatmessageparticipantavatar.coffee
+++ client/ide/lib/views/chat/idechatmessageparticipantavatar.coffee
@@ -30,7 +30,14 @@
     @setClass 'is-hostParticipant'  if @nickname is getNick()

+  killIntentTimer: ->
+
+    kd.utils.killWait @intentTimer  if @intentTimer
+
+
   click: (event) ->
+
+    @killIntentTimer()

     return  unless participant = @getData()

@@ -40,14 +47,17 @@

   mouseEnter: ->
-    kd.utils.killWait @intentTimer  if @intentTimer
+
+    @killIntentTimer()
+
     @intentTimer = kd.utils.wait INTENT_DELAY, @bound 'showMenu'

   mouseLeave: ->
-    kd.utils.killWait @intentTimer  if @intentTimer
-    if MENU
-      @intentTimer = kd.utils.wait INTENT_DELAY, MENU.bound 'destroy'
+
+    @killIntentTimer()
+
+    @intentTimer = kd.utils.wait INTENT_DELAY, MENU.bound 'destroy'  if MENU

   showMenu: ->
@@ -99,9 +109,9 @@
           offset   : { top: 35, left: -78 }
           arrow    : { placement: 'top', margin: menuWidth / 2 }
           mouseenter : =>
-            kd.utils.killWait @intentTimer  if @intentTimer
+            @killIntentTimer()
           mouseleave : =>
-            kd.utils.killWait @intentTimer  if @intentTimer
+            @killIntentTimer()
             kd.utils.wait INTENT_DELAY, MENU.bound 'destroy'
         , items


GlobalAppMenu: it is implemented back

GlobalAppMenu: it is implemented back

--- client/Main/maincontroller/mainviews/mainview.coffee
+++ client/Main/maincontroller/mainviews/mainview.coffee
@@ -191,7 +191,7 @@
       KD.getSingleton('router').handleRoute "/Activity"

     @panelWrapper.addSubView @mainTabView
-    # @contentPanel.addSubView @appSettingsMenuButton
+    @panelWrapper.addSubView @appSettingsMenuButton

   createSideBar:->


fix inheritance of TopicsListItemView

fix inheritance of TopicsListItemView

--- client/Social/Topics/Views/TopicsListItemView.coffee
+++ client/Social/Topics/Views/TopicsListItemView.coffee
@@ -1,4 +1,6 @@
-class TopicsListItemView extends JCustomHTMLView
+class TopicsListItemView extends KDListItemView
+
+  JView.mixin @prototype

   constructor:(options = {}, data)->
     options.type = "topics"


app: replace require with kd-react

app: replace require with kd-react

--- client/app/lib/components/profile/profiletext.coffee
+++ client/app/lib/components/profile/profiletext.coffee
@@ -1,4 +1,4 @@
-React = require 'app/react'
+React = require 'kd-react'

 module.exports = class ProfileText extends React.Component


myteam: add load disabled members action

--- client/home/lib/myteam/index.coffee
+++ client/home/lib/myteam/index.coffee
@@ -46,6 +46,7 @@

     TeamFlux.actions.loadTeam()
     TeamFlux.actions.loadPendingInvitations()
+    TeamFlux.actions.loadDisabledUsers()
     AppFlux.actions.user.loadLoggedInUserEmail()

     options =


admin form validator now accepts more than 3 digits before decimal

--- app/assets/javascripts/admin/gramgoods.js.coffee
+++ app/assets/javascripts/admin/gramgoods.js.coffee
@@ -33,6 +33,6 @@
     , "Please specify a valid postal code")

     $.validator.addMethod("decimalTwo", (value, element) ->
-        this.optional(element) or /^(\d{1,3})(\.\d{1,2})?$/.test(value) && value > 0
+        this.optional(element) or /^(\d+)(\.\d{1,2})?$/.test(value) && value > 0
     , "Must be in US currency format 0.99")


pretty default avatars

pretty default avatars

--- app/assets/javascripts/app/models/user.js.coffee
+++ app/assets/javascripts/app/models/user.js.coffee
@@ -3,4 +3,4 @@

   load: ->
     super
-    @avatar_url or= "http://robohash.org/#{@email}.png?size=48x48"+    @avatar_url or= "http://robohash.org/#{@email}.png?size=48x48&bgset=1"


login: decorate GitHub button title respectively

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -121,7 +121,6 @@
         registerHandler()

     @github       = new KDButtonView
-      title       : "Sign up with GitHub"
       style       : 'solid github'
       icon        : yes
       callback    : ->
@@ -529,6 +528,7 @@

       switch name
         when "register"
+          @github.setTitle "Sign up with GitHub"
           @registerForm.email.input.setFocus()
         when "finishRegistration"
           @finishRegistrationForm.username.input.setFocus()
@@ -536,6 +536,7 @@
           @$('.flex-wrapper').addClass 'one'
           @redeemForm.inviteCode.input.setFocus()
         when "login"
+          @github.setTitle "Sign in with GitHub"
           @loginForm.username.input.setFocus()
         when "recover"
           @$('.flex-wrapper').addClass 'one'


fixed #135 minor typo in comments

fixed #135 minor typo in comments

--- src/default.coffee
+++ src/default.coffee
@@ -67,7 +67,7 @@
   #   $.ajax url,
   #     data: params
   #     success: (data) ->
-  #       render_view(data)
+  #       callback(data)

   # Sorter data of course.
   #


Should look for bid in query rather than param

--- apps/auction/routes.coffee
+++ apps/auction/routes.coffee
@@ -87,6 +87,6 @@
                   artwork: saleArtwork.artwork()
                   saleArtwork: saleArtwork
                   isRegistered: isRegistered
-                  maxBid: req.params.bid || ""
+                  maxBid: req.query.bid or ''
                   monthRange: order.getMonthRange()
                   yearRange: order.getYearRange()


More coffeescript'ish list comprehension for finding unique ids.

--- app/assets/javascripts/mercury/snippet.js.coffee
+++ app/assets/javascripts/mercury/snippet.js.coffee
@@ -34,10 +34,8 @@
     return instance

   @uniqueId: ->
-    i = 0
-    identities = []
-    identity = "snippet_#{i}"
-    identities.push snippet.identity for snippet in @all
+    [i, identity] = [0, "snippet_0"]
+    identities = (snippet.identity for snippet in @all)

     while identities.indexOf(identity) isnt -1
       i += 1


minor removing dead tests and final summary of tests run

--- tests.coffee
+++ tests.coffee
@@ -787,21 +787,17 @@
   """
   "1 catch all branch. the end."

-  #@ a ← 5 someUndefinedMessage'
-  #'7"
-
 ]

 ###
 tests = [
-  #@ a ← 5 someUndefinedMessage'
-  #'7"
 ]
 ###

 flContexts = []
 rWorkspace = null
-
+OKs = 0
+FAILs = 0

 for i in [0...tests.length] by 2
     [testBody, testResult] = tests[i .. i + 1]
@@ -889,7 +885,10 @@

     console.log "final return: " + returnedContext.returned?.value
     if environmentPrintout + environmentErrors == testResult
+      OKs++
       console.log "...test " + (i/2+1) + " OK, obtained: " + environmentPrintout + environmentErrors
     else
+      FAILs++
       console.log "...test " + (i/2+1) + " FAIL, test: " + testBodyMultiline + " obtained: " + environmentPrintout + environmentErrors + " expected: " + testResult

+console.log "all tests done. obtained " + OKs + " OKs and " + FAILs + " FAILs"


Fixed tests.

Fixed tests.

--- tests.coffee
+++ tests.coffee
@@ -48,10 +48,10 @@
       ).observeChanges
         added: (id) =>
           count++
-          @changed countId, count: count unless initializing
+          @changed 'Counts', countId, count: count unless initializing
         removed: (id) =>
           count--
-          @changed countId, count: count unless initializing
+          @changed 'Counts', countId, count: count unless initializing

       initializing = false

@@ -135,6 +135,14 @@
       testSetEqual test, _.pluck(Posts.find().fetch(), '_id'), @posts
       test.equal Counts.findOne()?.count, @posts.length

+      Posts.remove @posts[0], expect (error, count) =>
+          test.isFalse error, error?.toString?() or error
+          test.equal count, 1
+  ,
+    (test, expect) ->
+      testSetEqual test, _.pluck(Posts.find().fetch(), '_id'), @posts[1..]
+      test.equal Counts.findOne()?.count, @posts.length - 1
+
       Users.remove @userId,
         expect (error) =>
           test.isFalse error, error?.toString?() or error


avoid emails to be printed out on it build logs

--- it/pingpong.coffee
+++ it/pingpong.coffee
@@ -65,12 +65,14 @@
       .then (response) ->
         userConnection.getUser({userId: response.userId})
       .then (response) ->
-        logger.info "User name is #{response.displayName} [#{response.emailAddress}]"
+        # Avoid emails to be printed out on build logs
+        # logger.info "User name is #{response.displayName} [#{response.emailAddress}]"
         botConnection.whoAmI()
       .then (response) ->
         botConnection.getUser({userId: response.userId})
       .then (response) ->
-        logger.info "Bot name is #{response.displayName} [#{response.emailAddress}]"
+        # Avoid emails to be printed out on build logs
+        # logger.info "Bot name is #{response.displayName} [#{response.emailAddress}]"
         botUserId = response.id
         botConnection.createDatafeed()
         .then (response) ->
@@ -86,4 +88,4 @@
           .then (response) ->
             botConnection.readDatafeed(datafeedId)
       .then (response) ->
-        assert.equal('<messageML>ping</messageML>', msg.message) for msg in response when msg.v2messageType = 'V2Message'+        assert.equal('<messageML>ping</messageML>', msg.message) for msg in response when msg.v2messageType = 'V2Message'


Use tinyliquid helper for outputting custom tags

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -478,21 +478,8 @@
         # Call the plugin function using a much simpler API
         result = fn words, site

-        # Tinyliquid custom tags are a little complex and not very well
-        # documented. The output of this function is ultimately evaluated as a
-        # script. For now, restrict the custom tag model here to be someting
-        # very simple -- outputting a string only. This means we don't support
-        # start & end tags for now.
-
-        # Escape the content first
-        result = result.replace '\\', '\\\\'
-        result = result.replace /"/img, '\\"'
-        # Join lines appropriately
-        sanitized = []
-        for line in result.split '\n'
-          sanitized.push "\"#{line}\""
-
-        return """$_buf += #{sanitized.join '+ "\n" + '} """
+        # Use tinyliquid helper method to output HTML
+        return methods.printString result

       return


Fixed module routing

Fixed module routing

--- src/javascript/app.coffee
+++ src/javascript/app.coffee
@@ -14,8 +14,8 @@
   ($routeProvider) ->
     $routeProvider
       .when '/module/:moduleName',
-        templateUrl: ($route) ->
-          'modules/' + $route.current.params.moduleName + '/' + $route.current.params.moduleName + '.html'
+        templateUrl: (routeObj) ->
+          'modules/' + routeObj.moduleName + '/' + routeObj.moduleName + '.html'
         controller: 'ModulesController'
         resolve:
           moduleName: ($route) ->


improve code layout a bit

improve code layout a bit

--- src/factory.coffee
+++ src/factory.coffee
@@ -191,21 +191,21 @@
                     return deps.exceptionRejection container, id, err

                 unless isThenable instanceOrPromise
-                    # not a promise!
-                    instance = instanceOrPromise
-                    deps.emitInstanceCreated container, id, instance
-                    return Promise.resolve instance
+                    # instanceOrPromise is not a promise but an instance
+                    deps.emitInstanceCreated container, id, instanceOrPromise
+                    return Promise.resolve instanceOrPromise
+
+                # instanceOrPromise is a promise

                 deps.emitPromiseCreated container, id, instanceOrPromise

-                onResolve = (value) ->
-                    deps.emitPromiseResolved container, id, value
-                    return value
-
-                onReject = (rejection) ->
-                    deps.rejectionRejection container, id, rejection
-
-                return instanceOrPromise.then onResolve, onReject
+                return instanceOrPromise.then(
+                    (value) ->
+                        deps.emitPromiseResolved container, id, value
+                        return value
+                    (rejection) ->
+                        deps.rejectionRejection container, id, rejection
+                )

 ###################################################################################
 # path


Enforce that paths are non-empty strings

Previously only the length was checked which would allow more than
just strings to be checked.

--- src/fs-plus.coffee
+++ src/fs-plus.coffee
@@ -74,11 +74,11 @@

   # Public: Returns true if a file or folder at the specified path exists.
   existsSync: (pathToCheck) ->
-    pathToCheck?.length > 0 and statSyncNoException(pathToCheck) isnt false
+    isPathValid(pathToCheck) and (statSyncNoException(pathToCheck) isnt false)

   # Public: Returns true if the given path exists and is a directory.
   isDirectorySync: (directoryPath) ->
-    return false unless directoryPath?.length > 0
+    return false unless isPathValid(directoryPath)
     if stat = statSyncNoException(directoryPath)
       stat.isDirectory()
     else
@@ -86,7 +86,7 @@

   # Public: Asynchronously checks that the given path exists and is a directory.
   isDirectory: (directoryPath, done) ->
-    return done(false) unless directoryPath?.length > 0
+    return done(false) unless isPathValid(directoryPath)
     fs.exists directoryPath, (exists) ->
       if exists
         fs.stat directoryPath, (error, stat) ->
@@ -99,7 +99,7 @@

   # Public: Returns true if the specified path exists and is a file.
   isFileSync: (filePath) ->
-    return false unless filePath?.length > 0
+    return false unless isPathValid(filePath)
     if stat = statSyncNoException(filePath)
       stat.isFile()
     else
@@ -107,7 +107,7 @@

   # Public: Returns true if the specified path is a symbolic link.
   isSymbolicLinkSync: (symlinkPath) ->
-    return false unless symlinkPath?.length > 0
+    return false unless isPathValid(symlinkPath)
     if stat = lstatSyncNoException(symlinkPath)
       stat.isSymbolicLink()
     else
@@ -115,7 +115,7 @@

   # Public: Calls back with true if the specified path is a symbolic link.
   isSymbolicLink: (symlinkPath, callback) ->
-    if symlinkPath?.length > 0
+    if isPathValid(symlinkPath)
       fs.lstat symlinkPath, (error, stat) ->
         callback?(stat? and stat.isSymbolicLink())
     else
@@ -123,7 +123,7 @@

   # Public: Returns true if the specified path is executable.
   isExecutableSync: (pathToCheck) ->
-    return false unless pathToCheck?.length > 0
+    return false unless isPathValid(pathToCheck)
     if stat = statSyncNoException(pathToCheck)
       (stat.mode & 0o777 & 1) isnt 0
     else
@@ -131,7 +131,7 @@

   # Public: Returns the size of the specified path.
   getSizeSync: (pathToCheck) ->
-    if pathToCheck?.length > 0
+    if isPathValid(pathToCheck)
       statSyncNoException(pathToCheck).size ? -1
     else
       -1
@@ -489,4 +489,7 @@
   catch error
     false

+isPathValid = (pathToCheck) ->
+  pathToCheck? and typeof pathToCheck is 'string' and pathToCheck.length > 0
+
 module.exports = _.extend({}, fs, fsPlus)


Convert extensions to lowercase before examining file-type

Some files are saved to disk with uppercased extensions, such as ".PDF".
These won't return true for helper methods like isPdfExtension().

--- src/fs-plus.coffee
+++ src/fs-plus.coffee
@@ -457,19 +457,22 @@

   # Public: Returns true for extensions associated with compressed files.
   isCompressedExtension: (ext) ->
-    COMPRESSED_EXTENSIONS.hasOwnProperty(ext)
+    return false unless ext?
+    COMPRESSED_EXTENSIONS.hasOwnProperty(ext.toLowerCase())

   # Public: Returns true for extensions associated with image files.
   isImageExtension: (ext) ->
-    IMAGE_EXTENSIONS.hasOwnProperty(ext)
+    return false unless ext?
+    IMAGE_EXTENSIONS.hasOwnProperty(ext.toLowerCase())

   # Public: Returns true for extensions associated with pdf files.
   isPdfExtension: (ext) ->
-    ext is '.pdf'
+    ext?.toLowerCase() is '.pdf'

   # Public: Returns true for extensions associated with binary files.
   isBinaryExtension: (ext) ->
-    BINARY_EXTENSIONS.hasOwnProperty(ext)
+    return false unless ext?
+    BINARY_EXTENSIONS.hasOwnProperty(ext.toLowerCase())

   # Public: Returns true for files named similarily to 'README'
   isReadmePath: (readmePath) ->
@@ -479,7 +482,8 @@

   # Public: Returns true for extensions associated with Markdown files.
   isMarkdownExtension: (ext) ->
-    MARKDOWN_EXTENSIONS.hasOwnProperty(ext)
+    return false unless ext?
+    MARKDOWN_EXTENSIONS.hasOwnProperty(ext.toLowerCase())

   # Public: Is the filesystem case insensitive?
   #


timeout -> fromTimeout for clarity

timeout -> fromTimeout for clarity

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -626,7 +626,7 @@
   # It disables the longest of the pending partially matching bindings, then
   # replays the queued keyboard events to allow any bindings with shorter
   # keystroke sequences to be matched unambiguously.
-  terminatePendingState: (timeout) ->
+  terminatePendingState: (fromTimeout) ->
     bindingsToDisable = @pendingPartialMatches
     eventsToReplay = @queuedKeyboardEvents

@@ -650,7 +650,7 @@

     atom?.assert(not bindingsToDisable?, "Invalid keymap state")

-    if timeout and @pendingPartialMatches?
+    if fromTimeout and @pendingPartialMatches?
       @terminatePendingState(true)

     return


Centralize middleware test helpers

Centralize middleware test helpers

--- spec/UrlMiddleware.coffee
+++ spec/UrlMiddleware.coffee
@@ -75,5 +75,5 @@
           component: null
           nodes: []
           remote: []
-      mw.receiveAction newAction, 'github:open', checkOpen, done
+      mw.receiveAction 'github:open', checkOpen, done
       window.location.hash = '#example/abc123'


Remove parens from require

Remove parens from require

--- spec/app/atom-spec.coffee
+++ spec/app/atom-spec.coffee
@@ -3,7 +3,7 @@
 {$$} = require 'space-pen'
 fsUtils = require 'fs-utils'
 Exec = require('child_process').exec
-path = require('path')
+path = require 'path'

 describe "the `atom` global", ->
   beforeEach ->


move create project spinner to left for consistency with rest of cocalc.

--- src/smc-webapp/projects/create-project.cjsx
+++ src/smc-webapp/projects/create-project.cjsx
@@ -71,7 +71,10 @@
     render_info_alert: ->
         if @state.state == 'saving'
             <div style={marginTop:'30px'}>
-                <Alert bsStyle='info'>Creating project... <Icon name='cc-icon-cocalc-ring' spin /></Alert>
+                <Alert bsStyle='info'>
+                <Icon name='cc-icon-cocalc-ring' spin />
+                <Space/> Creating project...
+                </Alert>
             </div>

     render_error: ->


change color for heat

change color for heat

--- atmospheric/002-accumulated-heat/viewer.coffee
+++ atmospheric/002-accumulated-heat/viewer.coffee
@@ -34,7 +34,7 @@
         "#" + hexR + hexG + hexB

     colorHeat = (idx) ->
-        r = 128 / 128 * idx
+        r = 128 + idx
         b = 0
         g = 0

@@ -54,7 +54,7 @@
                 lng = 2 * Math.PI * col / num
                 lat = Math.PI * (0.5 - row / num)
                 [x, y] = positioning(lng, lat)
-                if x != -1 && y != -1
+                if x != -1 or y != -1
                     pos = row * num + row + col
                     height = heights[pos] / 64

@@ -86,9 +86,9 @@
                 points = find(heatdata, cur, cur + num - 1, value)
                 for col in points
                     [x, y] = positioning(lng(col), lat(row))
-                    if x != -1 && y != -1
+                    if x != -1 or y != -1
                         context.fillStyle = colorHeat(idx)
-                        context.fillRect(Math.floor(256 + 250 * x), Math.floor(256 + 250 * y), 4, 4)
+                        context.fillRect(Math.floor(256 + 250 * x), Math.floor(256 + 250 * y), 1, 1)

     viewer.paint = (positioning, geodata, heatdata) ->
         context.clearRect(0, 0, 512, 512)


Fix for existing MRU display test

Fix for existing MRU display test

--- spec/mru-list-spec.coffee
+++ spec/mru-list-spec.coffee
@@ -53,11 +53,19 @@

   describe "contents", ->
     pane = null
+    realSetTimeout = window.setTimeout

     beforeEach ->
+      # The MRU tab list is deliberately delayed before display.
+      # Here we mock window.setTimeout rather than introducing a corresponding delay in tests
+      # because faster tests are better.
+      jasmine.getGlobal().setTimeout = (callback, wait) => callback()
       waitsForPromise ->
         atom.workspace.open("sample.png")
       pane = atom.workspace.getActivePane()
+
+    afterEach ->
+      jasmine.getGlobal().setTimeout = realSetTimeout

     it "has one item per tab", ->
       if pane.onChooseNextMRUItem?


changes added to match the new atom api and path fixed for macosx

--- spec/provider-spec.coffee
+++ spec/provider-spec.coffee
@@ -57,7 +57,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample.php',initialLine: 13).then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample.php',initialLine: 13).then (o) -> editor = o

         runs ->
             expected = [{
@@ -114,7 +114,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample.php',initialLine: 18).then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample.php',initialLine: 18).then (o) -> editor = o

         runs ->

@@ -146,7 +146,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-multiple.php',initialLine: 25).then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-multiple.php',initialLine: 25).then (o) -> editor = o

         runs ->

@@ -174,7 +174,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-multiple.php',initialLine: 25).then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-multiple.php',initialLine: 25).then (o) -> editor = o

         runs ->

@@ -191,7 +191,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-multiple.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-multiple.php').then (o) -> editor = o

         runs ->
             expect(provider.getLocalAvailableCompletions(editor)).toEqual(expectedCompletions)
@@ -209,7 +209,7 @@
         local = []

         waitsForPromise ->
-            atom.project.open('sample/sample-full.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-full.php').then (o) -> editor = o

         runs ->
             local = []
@@ -221,7 +221,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-var.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-var.php').then (o) -> editor = o

         runs ->
             expect(provider.getParentClassName(editor)).toEqual('\\SomeParent')
@@ -297,7 +297,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample.php').then (o) -> editor = o

         runs ->
             prefix = ''
@@ -313,7 +313,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-use.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-use.php').then (o) -> editor = o

         runs ->

@@ -337,7 +337,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-use.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-use.php').then (o) -> editor = o

         runs ->

@@ -361,7 +361,7 @@
         editor = null

         waitsForPromise ->
-            atom.project.open('sample/sample-use.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-use.php').then (o) -> editor = o

         runs ->

@@ -405,7 +405,7 @@
         promise = null

         waitsForPromise ->
-            atom.project.open('sample/sample.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample.php').then (o) -> editor = o

         waitsForPromise ->

@@ -428,7 +428,7 @@
         promise = null

         waitsForPromise ->
-            atom.project.open('sample/sample-multiple.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-multiple.php').then (o) -> editor = o

         waitsForPromise ->

@@ -452,7 +452,7 @@
         promise = null

         waitsForPromise ->
-            atom.project.open('sample/sample-full-static.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-full-static.php').then (o) -> editor = o

         waitsForPromise ->

@@ -484,7 +484,7 @@
         provider = require providerPath

         waitsForPromise ->
-            atom.project.open('sample/sample-full.php').then (o) -> editor = o
+            atom.workspace.open(__dirname + '/sample/sample-full.php').then (o) -> editor = o

         waitsForPromise ->


Add test for C keybinding

Add test for C keybinding

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -344,6 +344,18 @@
           expect(editor.getCursorScreenPosition()).toEqual([0,0])
           expect(editor).not.toHaveClass 'command-mode'
           expect(editor).toHaveClass 'insert-mode'
+
+    describe "the C keybinding", ->
+      beforeEach ->
+        editor.getBuffer().setText "012\n"
+        editor.setCursorScreenPosition [0, 1]
+
+      it "deletes the contents until the end of the line and enters insert mode", ->
+        keydown('C', shift: true, element: editor[0])
+
+        expect(editor.getText()).toBe "0\n"
+        expect(editor).not.toHaveClass 'command-mode'
+        expect(editor).toHaveClass 'insert-mode'

     describe "the y keybinding", ->
       beforeEach ->


htmldoc: cleanup iteration of rules code

--- src/htmldoc.coffee
+++ src/htmldoc.coffee
@@ -1,9 +1,10 @@
 _ = require 'underscore'
-{ RULES: rules } = (require './coffeelint')
+{ RULES: rules } = require './coffeelint'

 render = () ->
     rulesHTML = ''
-    for ruleName in Object.keys(rules).sort()
+    ruleNames = Object.keys(rules).sort()
+    for ruleName in ruleNames
         rule = rules[ruleName]
         rule.name = ruleName
         rule.description = '[no description provided]' unless rule.description


add a new unit test that doesn't provide full_url (as my test jenkins doesn't)

--- src/test/hubot-jenkins-notifier_test.coffee
+++ src/test/hubot-jenkins-notifier_test.coffee
@@ -78,6 +78,21 @@
     }
   },
   {
+    "name": "finished-failed-nofull_url",
+    "expected_out": "JobName build #1 started failing (job/project/5)",
+    "body": {
+      "name":"JobName",
+      "url":"JobUrl",
+      "build":{
+        "number":1,
+        "phase":"FINISHED",
+        "status":"FAILURE",
+        "url":"job/project/5",
+        "parameters":{"branch":"master"}
+      }
+    }
+  },
+  {
     "name": "started-failed",
     "expected_out": false,
     "body": {


Rotation and scale fix

Rotation and scale fix

--- framer/LayerPinchable.coffee
+++ framer/LayerPinchable.coffee
@@ -28,8 +28,8 @@

 	@define "rotate", @simpleProperty("rotate", true)
 	@define "rotateIncrements", @simpleProperty("rotateIncrements", 0)
-	@define "rotateMin", @simpleProperty("rotateMin", Number.MIN_VALUE)
-	@define "rotateMax", @simpleProperty("rotateMax", Number.MAX_VALUE)
+	@define "rotateMin", @simpleProperty("rotateMin", 0)
+	@define "rotateMax", @simpleProperty("rotateMax", 0)
 	@define "rotateFactor", @simpleProperty("rotateFactor", 1)

 	constructor: (@layer) ->
@@ -71,8 +71,8 @@
 			@_scaleStart ?= @layer.scale
 			scale = event.scale * @_scaleStart
 			scale = scale * @scaleFactor
-			scale = Utils.clamp(scale, @scaleMin, @scaleMax)
-			scale = Utils.nearestIncrement(scale, @scaleIncrements)
+			scale = Utils.clamp(scale, @scaleMin, @scaleMax) if (@scaleMin and @scaleMax)
+			scale = Utils.nearestIncrement(scale, @scaleIncrements) if @scaleIncrements
 			@layer.scale = scale
 			@emit(Events.Scale, event)

@@ -81,8 +81,8 @@
 			@_rotationOffset ?= event.rotation
 			rotation = event.rotation - @_rotationOffset + @_rotationStart
 			rotation = rotation * @rotateFactor
-			rotation = Utils.clamp(rotation, @rotateMin, @rotateMax)
-			rotation = Utils.nearestIncrement(rotation, @rotateIncrements)
+			rotation = Utils.clamp(rotation, @rotateMin, @rotateMax) if (@rotateMin and @rotateMax)
+			rotation = Utils.nearestIncrement(rotation, @rotateIncrements) if @rotateIncrements
 			@layer.rotation = rotation
 			@emit(Events.Rotate, event)


Bump version number

Bump version number

--- holla_back.coffee
+++ holla_back.coffee
@@ -1,5 +1,7 @@
 # HollaBack - Any object that extends HollaBack can serve as an event emitter
-# Felix Flores 2011 v0.1.0
+# Felix Flores 2011 v0.1.3
+
+# TODO Need to refactor unbind and handlerInNamespace function

 class HollaBack
   constructor: () ->


Remove tunnelproxymanager command line options

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/workers.coffee
+++ config/workers.coffee
@@ -471,7 +471,7 @@
     tunnelproxymanager  :
       group             : "proxy"
       supervisord       :
-        command         : "#{GOBIN}/tunnelproxymanager -ebenvname #{options.ebEnvName} -accesskeyid #{credentials.awsKeys.worker_tunnelproxymanager.accessKeyId} -secretaccesskey #{credentials.awsKeys.worker_tunnelproxymanager.secretAccessKey} -route53accesskeyid #{credentials.awsKeys.worker_tunnelproxymanager_route53.accessKeyId} -route53secretaccesskey #{credentials.awsKeys.worker_tunnelproxymanager_route53.secretAccessKey} -hostedzone-name #{options.tunnelHostedZoneName} -hostedzone-callerreference #{options.tunnelHostedZoneCallerRef}"
+        command         : "#{GOBIN}/tunnelproxymanager"

     tunnelserver        :
       group             : "proxy"


Extend collection

Extend collection

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -9,16 +9,7 @@
     'application/vnd.org.cnx.collection' : 'book'
     'application/vnd.org.cnx.module': 'page'

-  CONTENT_URI = "#{location.protocol}//#{settings.cnxarchive.host}:#{settings.cnxarchive.port}/contents"
-
-  return class Content extends Backbone.AssociatedModel
-    url: () -> "#{CONTENT_URI}/#{@id}"
-
-    defaults:
-      title: 'Untitled Book'
-      pages: 1
-      authors: []
-
+  return class Content extends Collection
     relations: [{
       type: Backbone.Many
       key: 'contents'
@@ -41,6 +32,12 @@
       key: 'toc'
       collectionType: Backbone.Collection
     }]
+
+    initialize: (options = {}) ->
+      @set('toc', [])
+
+      @fetch
+        success: () => @load(options.page)

     parse: (response) ->
       type = response.type = MEDIA_TYPES[response.mediaType]
@@ -86,11 +83,6 @@
       response.pages = page - 1

       return response
-
-    initialize: (options = {}) ->
-      @set('toc', [])
-      @fetch
-        success: () => @load(options.page)

     load: (page) ->
       if @get('type') is 'book'


Trigger 'changePage' event whenever the current page changes

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -93,7 +93,9 @@
     fetchPage: () ->
       page = @get('currentPage')
       page.fetch
-        success: () -> page.loaded = true
+        success: () =>
+          page.loaded = true
+          @trigger('changePage')

     setPage: (num) ->
       if num < 1 then num = 1
@@ -105,6 +107,7 @@
       @get('currentPage')?.set('active', false)
       @set('currentPage', page)
       page.set('active', true)
+      @trigger('changePage')

       if not page.loaded
         @fetchPage()


インスタンス幅、左右のマージンの調整を追加

インスタンス幅、左右のマージンの調整を追加

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -1,11 +1,12 @@
 MESSSAGE_OFFSET = 40
 MESSSAGE_BODY_OFFSET = -18
-MESSSAGE_SELF_WIDTH = 27.5
+MESSSAGE_SELF_WIDTH = 28
 MESSSAGE_SELF_HEIGHT = 17
 MESSSAGE_ARROW_WIDTH = 5
 MESSSAGE_ARROW_HEIGHT = -8

 INSTANCE_OFFSET = 40
+INSTANCE_PADDING = 10
 INSTANCE_WIDTH = 70
 INSTANCE_HEIGHT = 30

@@ -23,43 +24,40 @@
   else
     drawNormalMessageLine(ctx, obj)

+drawNote = (ctx, obj) ->
+  pt = obj.note.bodyPoint
+  drawNoteText(ctx, pt.x, pt.y, obj.body)
+
+drawNoteText = (ctx, x, y, body) ->
+  ctx.fillStyle = "rgb(230, 230, 240)"
+  ctx.fillRect(x, y + 3, ctx.measureText(body).width, 10)
+  ctx.fillStyle = "black"
+
+  ctx.font = "12px Sans-Serif"
+  ctx.textAlign = "left"
+  ctx.textBaseline = "top"
+  ctx.fillText(body, x, y)
+
 checkPresenceOfToFromObjects = (obj) ->
   return false unless obj.from?
   return false unless obj.to?
   return true

+isValidMessage = (message) ->
+  return false unless message?
+  return false unless checkPresenceOfToFromObjects(message)
+  return true
+
 isSelfMessage = (obj) -> return obj.to is obj.from

 isNote = (obj) -> return obj.is_note

 drawSelfMessageLine = (ctx, obj) ->
-  rect = getSelfMessageRect(ctx, obj)
-  headPt = getSelfMessageEndPoint(rect)
-  bodyPt = getSelfMessageBodyPoint(rect)
-  drawSelfMessagePath(ctx, rect)
+  headPt = obj.selfMessage.headPoint
+  bodyPt = obj.selfMessage.bodyPoint
+  drawSelfMessagePath(ctx, obj.selfMessage.rect)
   drawMessageArrowhead(ctx, headPt, true)
   drawMessageText(ctx, bodyPt.x, bodyPt.y, obj.body)
-
-getSelfMessageEndPoint = (rect) ->
-  pt = new Object()
-  pt.x = rect.x1
-  pt.y = rect.y2
-  return pt
-
-getSelfMessageBodyPoint = (rect) ->
-  pt = new Object()
-  pt.x = rect.x1
-  pt.y = rect.y1 + MESSSAGE_BODY_OFFSET
-  return pt
-
-getSelfMessageRect = (ctx, obj) ->
-  result = new Object()
-  pt = getIntersectionPoint(obj.from.order, obj.order)
-  result.x1 = pt.x
-  result.y1 = pt.y
-  result.x2 = pt.x + MESSSAGE_SELF_WIDTH
-  result.y2 = pt.y + MESSSAGE_SELF_HEIGHT
-  return result

 drawSelfMessagePath = (ctx, rect) ->
   ctx.beginPath()
@@ -70,40 +68,12 @@
   ctx.closePath()
   ctx.stroke()

-drawNote = (ctx, obj) ->
-  rect = getMessageRect(ctx, obj)
-  pt = getNoteBodyPoint(rect)
-  drawNoteText(ctx, pt.x, pt.y, obj.body)
-
-getNoteBodyPoint = (rect) ->
-  pt = new Object()
-  pt.x = rect.centerX - INSTANCE_WIDTH / 2
-  pt.y = rect.centerY
-  return pt
-
-drawNoteText = (ctx, x, y, body) ->
-  ctx.fillStyle = "rgb(230, 230, 240)"
-  ctx.fillRect(x, y + 3, ctx.measureText(body).width, 10)
-  ctx.fillStyle = "black"
-
-  ctx.font = "12px Sans-Serif"
-  ctx.textAlign = "left"
-  ctx.textBaseline = "top"
-  ctx.fillText(body, x, y)
-
 drawNormalMessageLine = (ctx, obj) ->
-  rect = getMessageRect(ctx, obj)
-  headPt = getIntersectionPoint(obj.to.order, obj.order)
-  bodyPt = getMessageBodyPoint(rect)
-  drawMessageLine(ctx, rect, obj.is_return)
+  headPt = obj.normalMessage.headPoint
+  bodyPt = obj.normalMessage.bodyPoint
+  drawMessageLine(ctx, obj.rect, obj.is_return)
   drawMessageArrowhead(ctx, headPt, isHeadLeft(obj))
   drawMessageText(ctx, bodyPt.x, bodyPt.y, obj.body)
-
-getMessageBodyPoint = (rect) ->
-  pt = new Object()
-  pt.x = rect.centerX
-  pt.y = rect.centerY + MESSSAGE_BODY_OFFSET
-  return pt

 isHeadLeft = (obj) ->
   obj.to.order <= obj.from.order
@@ -150,55 +120,21 @@
 drawMessageText = (ctx, x, y, body) ->
   width = ctx.measureText(body).width
   ctx.fillStyle = "white"
-  ctx.fillRect(x - width / 2, y + 3, width, 10)
+  ctx.fillRect(x - width / 2, y , width, 12)
   ctx.fillStyle = "black"
   ctx.font = "12px Sans-Serif"
   ctx.textAlign = "center"
   ctx.textBaseline = "top"
   ctx.fillText(body, x, y)

-getMessageRect = (ctx, obj) ->
-  result = new Object()
-  pt = getIntersectionPoint(obj.from.order, obj.order)
-  result.x1 = pt.x
-  result.y1 = pt.y
-  pt = getIntersectionPoint(obj.to.order, obj.order)
-  result.x2 = pt.x
-  result.y2 = pt.y
-  result.centerX = getMiddlePos(result.x1, result.x2)
-  result.centerY = getMiddlePos(result.y1, result.y2)
-  return result
-
-getMiddlePos = (a, b) ->
-  return a + (b - a) / 2
-
-getIntersectionPoint = (instanceOrder, messageOrder) ->
-  result = new Object()
-  rect = getInstanceRect(instanceOrder)
-  result.x = rect.centerX
-  result.y = rect.y + rect.height + (messageOrder + 1) * MESSSAGE_OFFSET
-  return result
-
 # ------------------------------

 drawInstances = (ctx, instances, bottom) ->
-  drawInstance(ctx, obj, bottom) for obj in instances
-
-drawInstance = (ctx, obj, bottom) ->
-  rect = getInstanceRect(obj.order)
-  drawInstanceRect(ctx, rect)
-  drawInstanceText(ctx, rect, obj.name)
-  drawInstanceLine(ctx, rect, bottom)
-
-getInstanceRect = (order) ->
-  result = new Object()
-  result.x = order * (INSTANCE_WIDTH + INSTANCE_OFFSET) + CANVAS_PADDING
-  result.y = 0 + CANVAS_PADDING
-  result.width = INSTANCE_WIDTH
-  result.height = INSTANCE_HEIGHT
-  result.centerX = result.x + result.width / 2
-  result.centerY = result.y + result.height / 2
-  return result
+  for obj in instances
+    drawInstanceRect(ctx, obj.rect)
+    drawInstanceText(ctx, obj.rect, obj.name)
+    drawInstanceLine(ctx, obj.rect, bottom)
+  return

 drawInstanceRect = (ctx, rect) ->
   ctx.strokeRect(rect.x, rect.y, rect.width, rect.height)
@@ -215,6 +151,10 @@
   ctx.lineTo(rect.centerX, rect.centerY + bottom)
   ctx.closePath()
   ctx.stroke()
+
+getInstanceRectByOrder = (instances, instanceOrder) ->
+  obj = _.find(instances, (obj) -> obj.order is instanceOrder)
+  return obj.rect

 # ------------------------------

@@ -249,24 +189,195 @@
 drawObjects = (instances, messages) ->
   canvas = document.getElementById("diagram")
   return unless canvas
+  ctx = canvas.getContext("2d")
+  ctx.font = "12px Sans-Serif"
+  addRectToInstances(ctx, instances, messages)
+  addRectToMessages(ctx, instances, messages)
   canvas.height = calcCanvasHeight(messages)
-  canvas.width = calcCanvasWidth(instances)
-  ctx = canvas.getContext("2d")
+  ctx.font = "12px Sans-Serif"
+  canvas.width = calcCanvasWidth(ctx, instances)
   fillBackground(ctx, canvas.width, canvas.height)
   drawInstances(ctx, instances, canvas.height - MESSSAGE_OFFSET - CANVAS_PADDING)
   drawMessages(ctx, messages)

+addRectToInstances = (ctx, instances, messages) ->
+  first = _.min(instances, (obj) -> obj.order)
+  x = CANVAS_PADDING + getInstanceMargin(ctx, instances, messages, first)
+  for obj in instances
+    obj.rect = createInstanceRect(ctx, x, obj)
+    x += Math.round(obj.rect.width + INSTANCE_OFFSET)
+
+getInstanceMargin = (ctx, instances, messages, instance) ->
+  return 0 unless instance?
+
+  # TODO noteBodyMargin
+  normalBodyMargin = getMaxNormalBodyMargin(ctx, instance)
+  selfBodyMargin = getMaxSelfBodyMargin(ctx, instance)
+  bodyMargin = Math.max(normalBodyMargin, selfBodyMargin)
+
+  nameWidth1 = roundInstanceWidth(ctx, instance.name)
+  nameMargin = nameWidth1 / 2
+  gap = Math.round(bodyMargin - nameMargin)
+  return gap if gap > 0
+
+  return 0
+
+getMaxSelfBodyMargin = (ctx, instance) ->
+  message = _.max(messages, (obj) -> getRelatedSelfBodyWidth(ctx, instance, obj))
+  return 0 unless isValidMessage(message)
+  return 0 unless isSelfMessage(message)
+  return 0 if isNote(message)
+  return 0 unless message.from is instance
+
+  bodyWidth = ctx.measureText(message.body).width
+  return bodyWidth / 2
+
+getRelatedSelfBodyWidth = (ctx, instance, message) ->
+  return 0 unless message.from is instance
+  return 0 unless isSelfMessage(message)
+
+  bodyWidth = ctx.measureText(message.body).width
+  return bodyWidth / 2
+
+getMaxNormalBodyMargin = (ctx, instance1) ->
+  message = _.max(messages, (obj) -> getRelatedBodyWidth(ctx, instance1, obj))
+  return 0 unless isValidMessage(message)
+
+  instance2 = message.to
+  return 0 unless instance2?
+
+  nameWidth1 = roundInstanceWidth(ctx, instance1.name)
+  nameWidth2 = roundInstanceWidth(ctx, instance2.name)
+
+  distance = nameWidth1 / 2 + nameWidth2 / 2 + INSTANCE_OFFSET
+  bodyWidth = ctx.measureText(message.body).width
+  return bodyWidth / 2 - distance / 2
+
+getRelatedBodyWidth = (ctx, instance1, message) ->
+  return 0 if isSelfMessage(message)
+
+  if message.from is instance1
+    instance2 = message.to
+  else if message.to is instance1
+    instance2 = message.from
+  else
+    return 0
+  return 0 unless instance2?
+
+  nameWidth1 = roundInstanceWidth(ctx, instance1.name)
+  nameWidth2 = roundInstanceWidth(ctx, instance2.name)
+
+  distance = nameWidth1 / 2 + nameWidth2 / 2 + INSTANCE_OFFSET
+  bodyWidth = ctx.measureText(message.body).width
+  return bodyWidth
+
+createInstanceRect = (ctx, x, obj) ->
+  rect = new Object()
+  rect.x = x
+  rect.y = CANVAS_PADDING
+  rect.width = roundInstanceWidth(ctx, obj.name)
+  rect.height = INSTANCE_HEIGHT
+  rect.centerX = Math.round(rect.x + rect.width / 2)
+  rect.centerY = Math.round(rect.y + rect.height / 2)
+  return rect
+
+roundInstanceWidth = (ctx, name) ->
+  nameWidth = ctx.measureText(name).width
+  return Math.max(INSTANCE_WIDTH, nameWidth + INSTANCE_PADDING)
+
+addRectToMessages = (ctx, instances, messages) ->
+  for obj in messages
+    if checkPresenceOfToFromObjects(obj)
+      obj.rect = createMessageRect(ctx, instances, obj)
+      if isNote(obj)
+        obj.note = createNote(obj.rect)
+      else if isSelfMessage(obj)
+        obj.selfMessage = createSelfMessage(ctx, instances, obj)
+      else
+        obj.normalMessage = createNormalMessage(instances, obj)
+
+createMessageRect = (ctx, instances, obj) ->
+  rect = new Object()
+  pt = createIntersectionPoint(instances, obj.from.order, obj.order)
+  rect.x1 = pt.x
+  rect.y1 = pt.y
+  pt = createIntersectionPoint(instances, obj.to.order, obj.order)
+  rect.x2 = pt.x
+  rect.y2 = pt.y
+  rect.centerX = getMiddlePos(rect.x1, rect.x2)
+  rect.centerY = getMiddlePos(rect.y1, rect.y2)
+  obj.rect = rect
+
+createIntersectionPoint = (instances, instanceOrder, messageOrder) ->
+  result = new Object()
+  rect = getInstanceRectByOrder(instances, instanceOrder)
+  result.x = rect.centerX
+  result.y = rect.y + rect.height + (messageOrder + 1) * MESSSAGE_OFFSET
+  return result
+
+getMiddlePos = (a, b) ->
+  return a + (b - a) / 2
+
+createNote = (rect) ->
+  pt = new Object()
+  pt.x = rect.centerX - INSTANCE_WIDTH / 2
+  pt.y = rect.centerY
+  note = new Object()
+  note.bodyPoint = pt
+  return note
+
+createSelfMessage = (ctx, instances, obj) ->
+  result = new Object()
+  result.rect = createSelfMessageRect(ctx, instances, obj)
+  result.headPoint = createSelfMessageHeadPoint(result.rect)
+  result.bodyPoint = createSelfMessageBodyPoint(result.rect)
+  return result
+
+createSelfMessageRect = (ctx, instances, obj) ->
+  result = new Object()
+  pt = createIntersectionPoint(instances, obj.from.order, obj.order)
+  result.x1 = pt.x
+  result.y1 = pt.y
+  result.x2 = pt.x + MESSSAGE_SELF_WIDTH
+  result.y2 = pt.y + MESSSAGE_SELF_HEIGHT
+  return result
+
+createSelfMessageHeadPoint = (rect) ->
+  pt = new Object()
+  pt.x = rect.x1
+  pt.y = rect.y2
+  return pt
+
+createSelfMessageBodyPoint = (rect) ->
+  pt = new Object()
+  pt.x = rect.x1
+  pt.y = rect.y1 + MESSSAGE_BODY_OFFSET
+  return pt
+
+createMessageBodyPoint = (rect) ->
+  pt = new Object()
+  pt.x = rect.centerX
+  pt.y = rect.centerY + MESSSAGE_BODY_OFFSET
+  return pt
+
+createNormalMessage = (instances, obj) ->
+  result = new Object()
+  result.headPoint = createIntersectionPoint(instances, obj.to.order, obj.order)
+  result.bodyPoint = createMessageBodyPoint(obj.rect)
+  return result
+
 calcCanvasHeight = (messages) ->
   last = _.max(messages, (obj) -> obj.order)
+  return unless last?
   height = INSTANCE_HEIGHT + MESSSAGE_OFFSET * (last.order + 2)
   height += CANVAS_PADDING * 2
   return height

-calcCanvasWidth = (instances) ->
+calcCanvasWidth = (ctx, instances) ->
   last = _.max(instances, (obj) -> obj.order)
-  width = (last.order + 1) * (INSTANCE_WIDTH + INSTANCE_OFFSET)
-  width -= INSTANCE_OFFSET
-  width += CANVAS_PADDING * 2
+  return unless last?
+  x = getInstanceMargin(ctx, instances, messages, last)
+  width = CANVAS_PADDING * 2 + last.rect.x + last.rect.width + x
   return width

 fillBackground = (ctx, width, height) ->


Bug#146: Now show the port only if is needed

--- app/coffee/controllers/project.coffee
+++ app/coffee/controllers/project.coffee
@@ -44,7 +44,9 @@
         $scope.activeTab = type

     $scope.getAbsoluteUrl = (url) ->
-        if $location.port()
+        if $location.protocol() == "http" and $location.port() != 80
+            return "#{$location.protocol()}://#{$location.host()}:#{$location.port()}#{url}"
+        else if $location.protocol() == "https" and $location.port() != 443
             return "#{$location.protocol()}://#{$location.host()}:#{$location.port()}#{url}"
         else
             return "#{$location.protocol()}://#{$location.host()}#{url}"


wipe dev sqlite file when starting cocalc dev server

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -1199,6 +1199,13 @@
     # change global CONF path for local dev purposes
     DEV = true
     SQLITE_FILE = require('path').join(process.env.SALVUS_ROOT, 'data', 'compute.sqlite3')
+    # For the fake dev server, we always reset the database on startup, since
+    # we always kill all projects on startup.
+    try
+        fs.unlinkSync(SQLITE_FILE)
+    catch err
+        winston.debug("unlink sqlite file ", err)
+        # no-op
     async.series [init_sqlite_db, init_mintime], (err) ->
         if err
             winston.debug("Error starting server -- #{err}")


webapp/files listing: split flex divs to avoid #3904

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -59,6 +59,7 @@
     color      : COLORS.GRAY
     height     : '22px'
     margin     : '5px 3px'
+

 {FileListing, TERM_MODE_CHAR} = require("./project/file-listing")

@@ -1819,46 +1820,58 @@
             {start_index, end_index} = pager_range(file_listing_page_size, @props.page_number)
             visible_listing = listing[start_index...end_index]

-        flex_row_style =
+        FLEX_ROW_STYLE =
             display: 'flex'
             flexFlow: 'row wrap'
             justifyContent: 'space-between'
             alignItems: 'stretch'

-

-            {if pay? then @render_course_payment_warning(pay)}
-            {@render_error()}
-            {@render_activity()}
-            {@render_control_row(public_view, visible_listing)}
-            {<AskNewFilename
-                actions            = {@props.actions}
-                current_path       = {@props.current_path}
-                ext_selection      = {@props.ext_selection}
-                new_filename       = {@props.new_filename}
-                other_settings     = {@props.other_settings}
-            /> if @props.ext_selection}
-            {@render_new()}
-
-

-

-                    {@render_files_actions(listing, public_view, project_is_running) if listing?}
+
+        # be careful with adding height:'100%'. it could cause flex to miscalc. see #3904
+

+

+                {if pay? then @render_course_payment_warning(pay)}
+                {@render_error()}
+                {@render_activity()}
+                {@render_control_row(public_view, visible_listing)}
+                {<AskNewFilename
+                    actions            = {@props.actions}
+                    current_path       = {@props.current_path}
+                    ext_selection      = {@props.ext_selection}
+                    new_filename       = {@props.new_filename}
+                    other_settings     = {@props.other_settings}
+                /> if @props.ext_selection}
+                {@render_new()}
+
+

+

+                        {@render_files_actions(listing, public_view, project_is_running) if listing?}
+

+                    {@render_project_files_buttons(public_view)}

-                {@render_project_files_buttons(public_view)}
+
+                {@render_custom_software_reset() if project_is_running}
+
+                {@render_library() if @props.show_library}
+
+                {if @props.checked_files.size > 0 and @props.file_action?
+                    <Row>
+                        {@render_files_action_box(file_map, public_view)}
+                    </Row>
+                }

-
-            {@render_custom_software_reset() if project_is_running}
-
-            {@render_library() if @props.show_library}
-
-            {if @props.checked_files.size > 0 and @props.file_action?
-                <Row>
-                    {@render_files_action_box(file_map, public_view)}
-                </Row>
-            }
-
-            {### Only show the access error if there is not another error. ###}
-            {@render_access_error() if public_view and not error}
-            {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
-            {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
+

+
+                {### Only show the access error if there is not another error. ###}
+                {@render_access_error() if public_view and not error}
+                {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
+                {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
+


tooltips on stencil-big

tooltips on stencil-big

--- www/src/views/stencil/big.coffee
+++ www/src/views/stencil/big.coffee
@@ -7,7 +7,6 @@
 ], (_, Common, sharedModels, Textarea, html) ->
     class extends Common
         template: _.template html
-        features: ["timeago"]

         events:
             "click .edit": "startEdit"
@@ -97,3 +96,5 @@
         closeEdit: =>
             @$("._edit").hide()
             @$("._editable").show()
+
+        features: ['timeago', 'tooltip']


collection handle dropdown menu

collection handle dropdown menu

--- tree2.coffee
+++ tree2.coffee
@@ -38,6 +38,9 @@
 link_style = stroke: 'white', width: 1.5
 light_link_style = stroke: 'white', width: .5
 node_text_style = fill: 'white', font: '16px monospace'
+menu_text_style = fill: 'white', font: '16px sans'
+menu_text_invert_style = fill: 'black', font: '16px sans'
+menu_text_height = 18

 setStyle = (ctx, s) ->
   ctx.fillStyle   = s.fill   if s.fill
@@ -153,6 +156,29 @@

     fn(this)

+collection_dropdown_menu = [
+  {
+    name: 'sort by weight',
+    action: (c) ->
+      console.log('sort by weight')
+  },
+  {
+    name: 'sort by alpha',
+    action: (c) ->
+      console.log('sort by alpha')
+  },
+  {
+    name: 'Shannon-Fano',
+    action: (c) ->
+      console.log('Shannon-Fano')
+  },
+  {
+    name: 'Huffman',
+    action: (c) ->
+      console.log('Huffman')
+  }
+]
+
 class NodeCollection
   constructor: (@shape) ->
     @nodes = []
@@ -191,10 +217,39 @@
     setStyle(ctx, node_style)
     ctx.beginPath()
     renderShape(ctx, @shape, default_node_radius)
-    ctx.stroke()
-    ctx.restore()
-
-  mousedown: (pos) ->
+
+    if @dropdown_menu?
+      ctx.fillStyle = ctx.strokeStyle
+      ctx.fill()
+      ctx.restore()
+
+      ctx.textAlign = 'start'
+      ctx.textBaseline = 'top'
+
+      for option, i in @dropdown_menu.options
+        setStyle(ctx, menu_text_style)
+        measure = ctx.measureText(option.name)
+        ctx.fillStyle= if @dropdown_menu.selected == i then 'white' else 'black'
+        ctx.fillRect(
+          @dropdown_menu.pos.x,
+          @dropdown_menu.pos.y + menu_text_height * (i+1),
+          measure.width,
+          menu_text_height)
+
+        setStyle(ctx, if @dropdown_menu.selected == i
+            menu_text_invert_style
+          else
+            menu_text_style)
+
+        ctx.fillText(option.name,
+          @dropdown_menu.pos.x,
+          @dropdown_menu.pos.y + menu_text_height * (i+1))
+    else
+      ctx.stroke()
+      ctx.restore()
+
+
+  mousedown: (pos, idx) ->
     for n in @nodes
       if n.isHit(pos)
         if @merging?
@@ -226,9 +281,30 @@
           t: Date.now()
         return true

+    if @isHandleHit(pos, idx)
+      @dropdown_menu = {
+        pos: pos
+        options: collection_dropdown_menu
+        selected: -1
+      }
     return false

+  isHandleHit: (pos, idx) ->
+    dx = pos.x - default_node_radius*2
+    dy = pos.y - default_node_radius*(2 + 3 * idx)
+
+    return dx*dx + dy*dy < default_node_radius*default_node_radius
+
   mousemove: (pos) ->
+    if @dropdown_menu?
+      @dropdown_menu.selected = -1
+      for option, i in @dropdown_menu.options
+        if pos.x > @dropdown_menu.pos.x and
+           pos.y >= @dropdown_menu.pos.y + menu_text_height * (i+1) and
+           pos.y <  @dropdown_menu.pos.y + menu_text_height * (i+2)
+          @dropdown_menu.selected = i
+      return
+
     if @selected?
       s = @selected
       dx = (pos.x - s.mx + s.ox) - s.node.x
@@ -246,7 +322,15 @@
       @merging.x = pos.x
       @merging.y = pos.y

+    return
+
   mouseup: (pos) ->
+    if @dropdown_menu?
+      if @dropdown_menu.selected >= 0
+        @dropdown_menu.options[@dropdown_menu.selected].action(this)
+      @dropdown_menu = null
+      return
+
     if @selected?
       if Date.now() - @selected.t < 200
         mdx = pos.x - @selected.mx
@@ -261,6 +345,8 @@
               y: pos.y

     @selected = null
+
+    return

   mergeNodes: (node0, node1) ->
     # filter both out of the list
@@ -313,8 +399,8 @@

 canvas.addEventListener 'mousedown', (e) ->
   pos = getCursorPosition(canvas, e)
-  for i in [collections.length - 1..0] by -1
-    if collections[i].mousedown(pos)
+  for idx in [collections.length - 1..0] by -1
+    if collections[idx].mousedown(pos, idx)
       break

 canvas.addEventListener 'mousemove', (e) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -1,3 +1,37 @@
+###*
+The Bounded module is used to provide basic data about the
+location and dimensions of the including object. This module is included
+by default in GameObject.
+
+

+player = Core
+  x: 10
+  y: 50
+  width: 20
+  height: 20
+  other: "stuff"
+  more: "properties"
+
+player.position()
+# => Uncaught TypeError: Object has no method 'position'
+
+player.include(Bounded)
+
+# now player has all the methods provided by this module
+player.position()
+# => {x: 10, y: 50}
+

+
+@see GameObject
+
+Bounded module
+@name Bounded
+@module
+@constructor
+@param {Object} I Instance variables
+@param {Core} self Reference to including object
+###
+
 Metered = (I={}, self) ->
   Object.reverseMerge I,
     meters: []


progress on issue #132

progress on issue #132

FileInputStream now stores fd information correctly, and the mmap'd
read now seems to work. Still some natives left to implement.

--- src/natives.coffee
+++ src/natives.coffee
@@ -709,6 +709,9 @@
             filepath = filename.jvm2js_str()
             try  # TODO: actually look at the mode
               _this.$file = fs.openSync filepath, 'r'
+              # also store the file handle in the file descriptor object
+              fd = _this.get_field rs, 'java/io/FileInputStream/fd'
+              fd.set_field rs, 'java/io/FileDescriptor/fd', _this.$file
               _this.$pos = 0
             catch e
               if e.code == 'ENOENT'
@@ -1055,6 +1058,28 @@
         o 'defineClass(Ljava/lang/String;[BIILjava/lang/ClassLoader;Ljava/security/ProtectionDomain;)Ljava/lang/Class;', (rs, _this, name, bytes, offset, len, loader, pd) ->
             native_define_class rs, name, bytes, offset, len, loader
       ]
+    nio:
+      ch:
+        FileChannelImpl: [
+          # this poorly-named method actually specifies the page size for mmap
+          o 'initIDs()J', (rs) -> gLong.fromNumber(8192)  # arbitrary
+        ]
+        FileDispatcher: [
+          o 'init()V', (rs) -> # NOP
+          o 'read0(Ljava/io/FileDescriptor;JI)I', (rs, fd_obj, address, len) ->
+            # this is the same as the .$file attribute on FileInputStream
+            fd = fd_obj.get_field rs, 'java/io/FileDescriptor/fd'
+            # read upto len bytes and store into mmap'd buffer at address
+            block_addr = rs.block_addr(address)
+            buf = new Buffer len
+            bytes_read = fs.readSync(fd, buf, 0, len)
+            if DataView?
+              rs.mem_blocks[block_addr] = buf  # TODO: check that this works
+            else
+              for i in [0...bytes_read] by 1
+                rs.mem_blocks[block_addr+i] = buf.readInt8(i)
+            return bytes_read
+        ]
     reflect:
       ConstantPool: [
         o 'getLongAt0(Ljava/lang/Object;I)J', (rs, _this, cp, idx) ->


Fix require()'s deficiencies and special-case common/base

--- bokehjs/src/coffee/util/object_explorer.coffee
+++ bokehjs/src/coffee/util/object_explorer.coffee
@@ -2,10 +2,9 @@
   "underscore"
   "jquery"
   "jstree"
-  "common/base"
   "common/continuum_view"
   "common/has_properties"
-], (_, $, $1, Base, ContinuumView, HasProperties) ->
+], (_, $, $1, ContinuumView, HasProperties) ->

   class ObjectExplorerView extends ContinuumView.View
     initialize: (options) ->
@@ -14,18 +13,24 @@
       @showToolbar = options.showToolbar or false
       @render()

+    base: () ->
+      if not @_base?
+        @_base = require("common/base")
+
+      @_base
+
     delegateEvents: (events) ->
       super(events)

-      for type in _.keys(Base.locations)
-        Base.Collections(type).on("all", @onEvent)
+      for type in _.keys(@base().locations)
+        @base().Collections(type).on("all", @onEvent)

     onEvent: (event) =>
       @reRender()

     createTree: (nonempty=true) ->
-      nodes = for type in _.keys(Base.locations)
-        children = Base.Collections(type).map (obj, index) =>
+      nodes = for type in _.keys(@base().locations)
+        children = @base().Collections(type).map (obj, index) =>
           visited = {}
           visited[obj.id] = 1
           @descend(index, obj, visited)
@@ -41,7 +46,7 @@
         ref = true

         if not visited[obj.id]?
-          obj = Base.Collections(obj.type).get(obj.id)
+          obj = @base().Collections(obj.type).get(obj.id)
         else
           console.log("Cyclic reference to #{obj.type}:#{obj.id}")


Add itemView instead of getView in CollectionView.

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -44,6 +44,10 @@
     # If empty, all children of $list are considered
     itemSelector: null

+    # A class of item in collection.
+    # This property has to be overridden by a derived class.
+    itemView: null
+
     # Filtering
     # ---------

@@ -60,14 +64,14 @@
     visibleItems: null

     # Returns an instance of the view class
-    # This method has to be overridden by a derived class.
     # This is not simply a property with a constructor so that
     # several item view constructors are possible depending
     # on the item model type.
     getView: (model) ->
-      throw new Error 'CollectionView#getView must be overridden'
-      # Example:
-      # new SomeItemView model: model
+      if @itemView?
+        new @itemView({model})
+      else
+        throw new Error 'CollectionView#itemView must be overridden'

     # In contrast to normal views, a template is not mandatory
     # for CollectionViews. Provide an empty `getTemplateFunction`
@@ -83,6 +87,8 @@
         render: true      # Render the view immediately per default
         renderItems: true # Render all items immediately per default
         filterer: null    # No filter function
+
+      @itemView = options.itemView if options.itemView?

       # Initialize lists for views and visible items
       @viewsByCid = {}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -20,7 +20,7 @@
   equals player.I.alpha, 0.9

   player.flicker
-    duration: 17
+    duration: 12
     frequency: 5
     alpha: 0.2


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -1,3 +1,3 @@
-test "GameObject", ->
-  ok(GameObject)
+test "GameObject()", ->
+  gameObject = GameObject()


Don't do different defaults

Don't do different defaults

--- src/app.coffee
+++ src/app.coffee
@@ -97,8 +97,8 @@
 app.post "/hub", routes.hub

 # DB
-driver = config.driver or "disk"
-params = config.params or {}
+driver = config.driver
+params = config.params
 params.schema = Hub.schema
 db = Databank.get(driver, params)
 db.connect {}, (err) ->


Add raw query param to symfiles

Add raw query param to symfiles

--- src/app.coffee
+++ src/app.coffee
@@ -229,10 +229,15 @@
       if not symfile?
         resturn res.send 404, 'Symfile not found'

-      res.render 'symfile-view', {
-        title: 'Symfile'
-        symfile: symfileToViewJson(symfile)
-      }
+      if 'raw' of req.query
+        res.set 'content-type', 'text/plain'
+        res.send(symfile.contents.toString())
+        res.end()
+      else
+        res.render 'symfile-view', {
+          title: 'Symfile'
+          symfile: symfileToViewJson(symfile)
+        }

   breakpad.get '/crashreports/:id', (req, res, next) ->
     Crashreport.findById(req.params.id).then (report) ->


Ensure closeHotspot is closed at start up

--- src/app.coffee
+++ src/app.coffee
@@ -133,8 +133,12 @@
 		app.use (req, res) ->
 			res.redirect('/')

-		# Create TETHER iptables chain (will silently fail if it already exists)
-		iptables.createChainAsync('nat', 'TETHER')
+		# Ensure tethering is disabled before starting
+		wifi.closeHotspotAsync()
+		.catch(ignore)
+		.then ->
+			# Create TETHER iptables chain (will silently fail if it already exists)
+			iptables.createChainAsync('nat', 'TETHER')
 		.catch(ignore)
 		.then ->
 			iptables.deleteAsync({ table: 'nat', rule: 'PREROUTING -i tether -j TETHER'})


fix path to suika.json

fix path to suika.json

--- src/app.coffee
+++ src/app.coffee
@@ -117,7 +117,7 @@
 		graphics.moveTo(0, -10000)
 		graphics.lineTo(0, 10000)

-		loader = new PIXI.AssetLoader(['/suika.json'])
+		loader = new PIXI.AssetLoader(['suika.json'])
 		loader.onComplete = =>
 			actor = new Actor(@$data.animations)
 			actor.scale.x = actor.scale.y = 0.5


Thought I had caught all these

Thought I had caught all these

--- resources/tools/show-list/heading-controller.coffee
+++ resources/tools/show-list/heading-controller.coffee
@@ -5,7 +5,7 @@
   scope.activate = ->
     step =
       title: "Created list " + scope.list.name
-      tool: "/tools/show-list"
+      tool: "show-list"
       data:
         service:
           root: scope.list.root