added debug logging

added debug logging

--- src/service.coffee
+++ src/service.coffee
@@ -64,15 +64,20 @@
       logger.error 'findOneWorklogToProgress: ', {err: err} if err?
       return callback() if err? or not worklog?

+      logger.debug "found worklog to progress : #{worklog.file}"
+
       persistence.progressWorklog worklog._id, (err) ->
         if err?
           logger.error 'findOneWorklogs: ', {err: err}
           return callback err

+        logger.debug "start progressing : #{worklog.file}"
         persistence.findTimeline worklog.file, (err, cursor) ->
           if err?
             logger.error 'findOneWorklogs: ', {err: err}
             return callback err
+
+          logger.debug "found timeline : #{worklog.file}"

           isNotLimited = true
           progressCount = 0
@@ -84,12 +89,13 @@
                 if isNotLimited
                   timeline.getCommitInfo url, (err, commit) ->
                     if err?
-                      logger.error 'getCommitInfo: ', {err: err}
+                      logger.error 'getCommitInfo: ', {err: err} if isNotLimited
                       isNotLimited = false if /^API Rate Limit Exceeded/.test err.message
                       if not isNotLimited and progressCount > 3000
                         persistence.completeWorklog worklog._id, (err) ->
                           if err?
                             logger.error 'completeWorklog: ', {err: err}
+                          logger.debug 'completed worklog case 1', {file: worklog.file}
                     else
                       conventions = parser.parse commit
                       conventions.forEach (conv) ->
@@ -101,14 +107,14 @@
                           commiturl: commit.html_url
                         persistence.insertConvention data, (err) ->
                           logger.error 'insertConvention', {err: err} if err?
-                          logger.info 'insered convention'
+                          logger.info "insered convention - #{progressCount}"
                           progressCount += 1
                           if not isNotLimited and progressCount > 3000
                             persistence.completeWorklog worklog._id, (err) ->
                               if err?
                                 logger.error 'completeWorklog: ', {err: err}
                                 return
-                              logger.info 'completed worklog', {file: worklog.file}
+                              logger.debug 'completed worklog case 2', {file: worklog.file}
           callback()

   summarizeScore: (callback) ->


Adds search history to commit status page

--- app/views/commits/show.js.coffee
+++ app/views/commits/show.js.coffee
@@ -13,6 +13,9 @@
 #    limitations under the License.

 $(window).ready ->
+  table = $('#translations')
+  searchForm = $('#filter-form')
+
   $("input[name='commit[exported]']").change ->
     checkbox = $(this)
     form = checkbox.closest('form')
@@ -30,8 +33,12 @@
       error: ->
         new Flash('alert').text("Couldn't update commit export status")

-  table = $('#translations')
-  searchForm = $('#filter-form')
+  prefillForm = () ->
+    for own param, val of $.url().param()
+      searchForm.find("[name=#{param}]").val(val.trim())
+
+  prefillForm()
+
   makeURL = -> "#{table.data('url')}?#{searchForm.serialize()}"
   localeOrder = table.data('locales').split(',')

@@ -77,5 +84,14 @@
   searchForm.submit ->
     table.find('tbody').empty()
     scroll.reset()
+    # ONLY HTML5
+    window.history.pushState("params", "", "?#{searchForm.serialize()}")
     scroll.loadNextPage()
     false
+
+  window.onpopstate =  (e) ->
+    prefillForm()
+    table.find('tbody').empty()
+    scroll.reset()
+    scroll.loadNextPage()
+    false


add note

add note

--- src/teamforge.iced
+++ src/teamforge.iced
@@ -41,6 +41,9 @@
       key_pubkeyv2nacls: {} # map from keyid to keybase1.PublicKeyV2NaCl's
       # map from team id to {seqno, linkid}
       # also map from "<teamid>-seqno:<seqno>"
+      # This is part of the fake merkle tree that is represented in the test outputs.
+      # It is consumed by the Go test driver mock in the client-side tests
+      # and used to return team merkle leafs to the client code being tested.
       team_merkle: {}
       merkle_triples: {} # map from keys with a dash "LeafID-HashMeta" to MerkleTriple's
       sessions: @chain.sessions


Properly use apply scope, and only update bounds if necessary

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -40,14 +40,34 @@
         lng: Number.MAX_VALUE

   # expand bounding box to include pt
+  # returns true if we changed the bounding box
   expand: (lat, lon) ->
     # Updating bounds are _very_ expensive in leaflet, so we try to 'bucket' our bounds in .1 deg increments
     roundUp = (x) -> Math.ceil(x * 10) / 10.0
     roundDown = (x) -> Math.floor(x * 10) / 10.0
-    @bounds.southWest.lat = Math.min(roundDown(lat), @bounds.southWest.lat)
-    @bounds.southWest.lng = Math.min(roundDown(lon), @bounds.southWest.lng)
-    @bounds.northEast.lat = Math.max(roundUp(lat), @bounds.northEast.lat)
-    @bounds.northEast.lng = Math.max(roundUp(lon), @bounds.northEast.lng)
+
+    dirty = false
+
+    # We use the dirty flag to avoid changing scope object if nothing changed
+    min = (newval, old) ->
+      if newval < old
+        dirty = true
+        newval
+      else
+        old
+
+    max = (newval, old) ->
+      if newval > old
+        dirty = true
+        newval
+      else
+        old
+
+    @bounds.southWest.lat = min(roundDown(lat),@bounds.southWest.lat)
+    @bounds.southWest.lng = min(roundDown(lon), @bounds.southWest.lng)
+    @bounds.northEast.lat = max(roundUp(lat), @bounds.northEast.lat)
+    @bounds.northEast.lng = max(roundUp(lon), @bounds.northEast.lng)
+    dirty

 class LiveMapController extends MapController
   @$inject: ['$scope', '$http', 'missionService']
@@ -77,15 +97,16 @@
     @missionService.atmosphere.on("text", @updateVehicleMessage)

   onLive: (data) =>
-    key = @vehicleKey(data.missionId)
-    @updateVehicle(key, data)
+    # FIXME - not sure if I need apply... (or how to optimize it)
+    # Apply should be called around the function to be guarded... -kevinh
+    @scope.$apply(() =>
+      key = @vehicleKey(data.missionId)
+      @updateVehicle(key, data)

-    # Grow (or create) our bounds
-    @boundsFactory.expand(data.payload.lat, data.payload.lon)
-    @scope.bounds = @boundsFactory.bounds
-
-    # FIXME - not sure if I need apply... (or how to optimize it)
-    @scope.$apply()
+      # Grow (or create) our bounds
+      if @boundsFactory.expand(data.payload.lat, data.payload.lon)
+        @scope.bounds = @boundsFactory.bounds
+    )

   onMissionStart: (data) =>
     # creating empty markers is bad - causes null ref in leaflet


Hide error if indexStatus was successful

--- admin/static/coffee/namespaces/namespace.coffee
+++ admin/static/coffee/namespaces/namespace.coffee
@@ -289,6 +289,8 @@
                     index_list_fail: true
                 @get_indexes_with_delay @error_interval
             else
+                @$('.main_alert_error').slideUp 'fast'
+
                 index_hash = {}
                 indexes_not_ready = 0
                 for index, i in result


Check also for superclass of Travis.AuthRoute

Ember.js creates subclasses of given class in container, so we need to
check also superclass

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -46,7 +46,7 @@
       Travis.auth.set('afterSignInTransition', null)
       transition.retry()
     else
-      @transitionTo('index.current') if @constructor == Travis.AuthRoute
+      @transitionTo('index.current') if @constructor == Travis.AuthRoute || @constructor.superclass == Travis.AuthRoute

   afterSignOut: ->
     @transitionTo('index.current')


Set the header search size/limit to 7

Set the header search size/limit to 7

--- components/main_layout/header/view.coffee
+++ components/main_layout/header/view.coffee
@@ -26,6 +26,7 @@
       displayEmptyItem: true
       autoselect: true
       mode: 'suggest'
+      limit: 7

     @searchBarView.on 'search:entered', (term) -> window.location = "/search?q=#{term}"
     @searchBarView.on 'search:selected', @searchBarView.selectResult


Show package modal for installed

Show package modal for installed

--- conda_ui/static/conda_ui/installed.coffee
+++ conda_ui/static/conda_ui/installed.coffee
@@ -107,6 +107,12 @@
             @$el.prepend(alert)
             alert.hide().slideDown(500)

+        on_name_click: (event) =>
+            name = $(event.node).data("package-name")
+            pkg = @pkgs.get_by_name(name)
+            # TODO handle pkgs not in index - add unknown to index?
+            new PackageModal.View({pkg: pkg, envs: @envs, pkgs: @pkgs}).show()
+
         on_check: (event) =>
             pkg = $(event.node).parent().next().data('package-name')
             checked = $(event.node).prop('checked')


Improved deprecation warning

Improved deprecation warning

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -908,7 +908,7 @@

 		#Support the old properties syntax
 		if properties.properties?
-			# console.warn "Using Layer.animate with 'properties' key is deprecated: please use 'options' key to provide animation options instead"
+			# console.warn "Using Layer.animate with 'properties' key is deprecated: please provide properties directly and use the 'options' key to provide animation options instead"
 			options = properties
 			properties = options.properties
 			delete options.properties


Fix de la banniere

Fix de la banniere

--- grunt/coffee.coffee
+++ grunt/coffee.coffee
@@ -3,9 +3,9 @@
 		join: true
 		sourceMap: true
 		banner: "/*! <%= package.title || package.name %> - v<%= package.version %> -
-			<%= grunt.template.today("dd/mm/yyyy") %> -
+			<%= grunt.template.today(\"dd/mm/yyyy\") %> -
 			<%= package.homepage %>\n
-			* Copyright (c) <%= package.author %> <%= grunt.template.today("yyyy") %>;
+			* Copyright (c) <%= package.author %> <%= grunt.template.today(\"yyyy\") %>;
 			Licensed <%= package.license %> */\n"
 	dist:
 		src: [


getFormData an additional method to fetch all form data, we will replace it with getData later.

--- client/Framework/components/forms/KDFormView.coffee
+++ client/Framework/components/forms/KDFormView.coffee
@@ -69,9 +69,14 @@

   getData: ->
     formData = $.extend {},@getCustomData()
+    @serializeFormData formData
+    formData

-    @serializeFormData formData
-
+  getFormData: ->
+    inputs   = KDFormView.findChildInputs @
+    formData = @getCustomData() or {}
+    inputs.forEach (input)->
+      formData[input.getName()] = input.getValue() if input.getName()
     formData

   focusFirstElement:-> KDFormView.findChildInputs(@)[0].$().trigger "focus"


activity: no topics hint updated

activity: no topics hint updated

--- client/activity/lib/sidebar/topicsearchmodal.coffee
+++ client/activity/lib/sidebar/topicsearchmodal.coffee
@@ -10,11 +10,11 @@
     options.cssClass          = kd.utils.curry 'topic-search sidebar-white-modal', options.cssClass
     options.title           or= 'Browse Topics on Koding'
     options.placeholder     or= 'Search all topics...'
-    options.noItemText      or= 'You don\'t follow any topics yet. You can search for some topics above e.g HTML, CSS, golang.'
+    options.noItemText      or= 'There are no topics here. You can create a new topic by making it a part of a new post. eg: I love #programming'
     options.emptySearchText or= 'Sorry, your search did not have any results'
     options.endpoints        ?=
       fetch                   : kd.singletons.socialapi.channel.list
-      search                  : kd.singletons.socialapi.channel.searchTopics
+      search                  : kd.singletons.socialapi.channel.searchTopicyous

     super options, data

@@ -30,13 +30,6 @@
       kd.singletons.router.handleRoute item.getOption 'route'
       @destroy()

-    @addSubView new KDCustomHTMLView
-      cssClass   : 'tag-description'
-      partial    : "
-        You can also create a new topic by making it a part of a new post.

-        eg: I love #programming
-      "

-


Use ENV['PORT'] Heroku provides us instead of trying to bind to 4000


[fix] Suppress xml2js dependency.

[fix] Suppress xml2js dependency.


Fix a typo

Fix a typo

--- src/timeago.coffee
+++ src/timeago.coffee
@@ -76,7 +76,7 @@
     (Math.abs(timeDistance) / 1000) / 60

   distanceOfTimeInWords: (dim) ->
-    # Wrods need to be the same with Rails
+    # Words need to be the same with Rails
     # 0 <-> 29 secs                                                             # => less than a minute
     # 30 secs <-> 1 min, 29 secs                                                # => 1 minute
     # 1 min, 30 secs <-> 44 mins, 29 secs                                       # => [2..44] minutes


Open contact popup on click

Open contact popup on click

--- client/app/components/participant.coffee
+++ client/app/components/participant.coffee
@@ -96,9 +96,12 @@
             node.addEventListener 'mouseover', ->
                 delay = setTimeout ->
                     addTooltip()
-                , 1000
+                , 5000
             node.addEventListener 'mouseout', ->
                 clearTimeout delay
+            node.addEventListener 'click', (event) ->
+                event.stopPropagation()
+                addTooltip()

     componentDidMount: ->
         @tooltip()


RealtimeController: disable on default environment

--- client/app/lib/realtimecontroller.coffee
+++ client/app/lib/realtimecontroller.coffee
@@ -27,11 +27,10 @@

     super options, data

-    @initPubNub()
-
-    @syncTime()
-
-    @initAuthentication()
+    unless globals.config.environment is 'default'
+      @initPubNub()
+      @syncTime()
+      @initAuthentication()

   fetchServerTime: (callback = noop) ->


fix(locales): fixed string parsing

fix(locales): fixed string parsing

--- vue/src/shared/services/session.coffee
+++ vue/src/shared/services/session.coffee
@@ -19,9 +19,9 @@
     if loadedLocales.includes(locale)
       setI18nLanguage(locale)
     else
-      import("date-fns/locale/#{locale.replace('_','-')}/index.js").then (dateLocale) ->
+      import("date-fns/locale/#{locale.toLowerCase().replace('_','-')}/index.js").then (dateLocale) ->
         i18n.dateLocale = dateLocale
-      import("@/../../config/locales/client.#{locale}.yml").then (data) ->
+      import("@/../../config/locales/client.#{locale.replace('-','_')}.yml").then (data) ->
         data = data[locale]
         loadedLocales.push(locale)
         i18n.setLocaleMessage(locale, data)


noticed bug in spell checking

noticed bug in spell checking

--- src/smc-webapp/editor-html-md/editor-html-md.coffee
+++ src/smc-webapp/editor-html-md/editor-html-md.coffee
@@ -335,6 +335,8 @@
                     return
                 else
                     for cm in @source_editor.codemirrors()
+                        if not cm
+                            return
                         cm.spellcheck_highlight(words)

     has_unsaved_changes: () =>


- [grunt] scsslint mapping for jit-grunt

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -7,6 +7,7 @@
   # Load grunt tasks automatically
   require('jit-grunt') grunt,
     nunjucks: 'grunt-nunjucks-2-html'
+    scsslint: 'grunt-scss-lint'
     sprite: 'grunt-spritesmith'

   # Define the configuration for all the tasks


Not make docs in this repo

Not make docs in this repo

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -5,7 +5,6 @@
 	grunt.loadNpmTasks 'grunt-contrib-concat'
 	grunt.loadNpmTasks 'grunt-contrib-uglify'
 	grunt.loadNpmTasks 'grunt-contrib-clean'
-	grunt.loadNpmTasks 'grunt-typedoc'
 	grunt.loadNpmTasks 'grunt-dtsm'
 	grunt.loadNpmTasks 'grunt-tslint'

@@ -59,21 +58,6 @@
 				files:
 					'lib/jaco.min.js': ['lib/jaco.js']

-		clean:
-			options:
-				force: on
-			docs:
-				src: [
-					'docs/'
-				]
-
-		typedoc:
-			app:
-				src: 'src/index.ts'
-				options:
-					name: '<%= pkg.name %>'
-					out: 'docs/'
-
 		dtsm:
 			main:
 				options:
@@ -108,6 +92,4 @@
 		'typescript'
 		'concat'
 		'uglify'
-		'clean:docs'
-		'typedoc'
 	]


videoLinkShell: add loops property manager

--- coffee/src/shells/videolink.shell.coffee
+++ coffee/src/shells/videolink.shell.coffee
@@ -25,6 +25,7 @@

   timeStart: @property 'timeStart'
   timeEnd: @property 'timeEnd'
+  loops: @property 'loops'

   description: =>
@@ -112,7 +113,7 @@
       # avoid decrementing the loop count multiple times before restart finishes
       return if @restarting

-      loops = @model.get 'loops'
+      loops = @model.loops()

       # if loops is a number, count video loops
       if parseInt(loops) >= 0
@@ -199,12 +200,12 @@

   _initializeLoopsButton: =>
-    loops = @model.get 'loops'
+    loops = @model.loops()

     # ensure loops property is set
     unless loops?
       loops = 'one'
-      @model.set 'loops', loops
+      @model.loops(loops)

     # determine initial view index for cycle button
     initialView = switch loops
@@ -270,7 +271,7 @@

   _onChangeLoops: (changed) =>
     loops = if changed.name == 'n' then changed.value else changed.name
-    @model.set 'loops', loops
+    @model.loops(loops)


Read project runtime type correctly

Read project runtime type correctly

--- components/PopulateProjectRuntime.coffee
+++ components/PopulateProjectRuntime.coffee
@@ -1,26 +1,30 @@
 noflo = require 'noflo'
 { getGraphType, getComponentType } = require '../src/runtime'

-getType = (project) ->
-  if project.graphs.length
-    graphType = getGraphType project.graphs[0]
+getType = (context) ->
+  if context.graphs.length
+    # Current main graph in view
+    graphType = getGraphType context.graphs[0]
     return graphType if graphType
-  if project.component
-    componentType = getComponentType project.component
+  if context.component
+    # Current component in editor
+    componentType = getComponentType context.component
     return componentType if componentType
-  return project.type
+  return null unless context.project
+  return context.project.type

-findCompatibleRuntimes = (project, runtimes) ->
-  projectType = getType project
+findCompatibleRuntimes = (context, runtimes) ->
+  projectType = getType context
   return runtimes.filter (rt) ->
     return true if projectType is 'all'
     return rt.type is projectType

-findCurrentRuntime = (project, runtimes) ->
-  return project.runtime if project.runtime
+findCurrentRuntime = (context, runtimes) ->
+  # TODO: Switch runtime if no longer in list of compatible
+  return context.runtime if context.runtime
   return null unless runtimes.length
   [matched] = runtimes.filter (rt) ->
-    return true if rt.project is project.id
+    return true if context.project and rt.project is context.project.id
     return true if rt.protocol is 'iframe'
   return matched or null

@@ -37,17 +41,17 @@

   c.process (input, output) ->
     return unless input.hasData 'in', 'runtimes'
-    [project, runtimes] = input.getData 'in', 'runtimes'
+    [context, runtimes] = input.getData 'in', 'runtimes'

-    project.compatible = findCompatibleRuntimes project, runtimes
-    project.runtime = findCurrentRuntime project, project.compatible
+    context.compatible = findCompatibleRuntimes context, runtimes
+    context.runtime = findCurrentRuntime context, context.compatible

-    unless project.runtime
+    unless context.runtime
       # No runtime matched, send as-is
       output.sendDone
-        skipped: project
+        skipped: context
       return

     output.sendDone
-      out: project
+      out: context
     return


Port to Process API

Port to Process API

--- components/UpdateComponentLibrary.coffee
+++ components/UpdateComponentLibrary.coffee
@@ -1,68 +1,104 @@
 noflo = require 'noflo'
 _ = require 'underscore'
+
+# Debounce runtime saving so we don't do it more often than needed
+updateRuntime = _.debounce (runtime, output) ->
+  # Send updated runtime definition to storage
+  output.send
+    runtime:
+      action: 'storage:save:runtime'
+      payload: runtime.runtime
+  # Deactivate all running contexts
+  while runtime.contexts.length
+    ctx = runtime.contexts.shift()
+    ctx.deactivate()
+, 300

 exports.getComponent = ->
   c = new noflo.Component
   c.components = {}
   c.runtime = null
   c.runtimeUpdated = {}
-
-  updateRuntime = _.debounce ->
-    return unless c.runtime
-    return unless c.components[c.runtime.id]
-    c.runtime.components = c.components[c.runtime.id]
-    c.outPorts.runtime.beginGroup 'storage'
-    c.outPorts.runtime.beginGroup 'save'
-    c.outPorts.runtime.beginGroup 'runtime'
-    c.outPorts.runtime.send c.runtime
-    c.outPorts.runtime.endGroup()
-    c.outPorts.runtime.endGroup()
-    c.outPorts.runtime.endGroup()
-    c.outPorts.runtime.disconnect()
-  , 300
-
   c.inPorts.add 'in',
     datatype: 'object'
-    process: (event, payload) ->
-      return unless event is 'data'
-      return unless payload.componentDefinition
-      def = payload.componentDefinition
-      c.outPorts.out.send
-        componentDefinition: def
-      return unless def.runtime
-      c.components[def.runtime] = {} unless c.components[def.runtime]
-
-      # Clear component listing from runtime when receiving first
-      c.components[def.runtime] = {} unless c.runtimeUpdated[def.runtime]
-      c.runtimeUpdated[def.runtime] = true
-
-      c.components[def.runtime][def.name] = def
-      do updateRuntime
-
   c.inPorts.add 'runtime',
     datatype: 'object'
-    process: (event, payload) ->
-      return unless event is 'data'
-      c.outPorts.out.send
-        clearLibrary: true
-      return unless payload.definition
-      return unless payload.definition.id
-      c.runtime = payload.definition
-      c.runtimeUpdated[payload.definition.id] = false
-      return unless c.runtime.components
-      return unless typeof c.runtime.components is 'object'
-      for name, def of c.runtime.components
-        c.outPorts.out.send
-          componentDefinition: def
-
   c.outPorts.add 'out',
     datatype: 'object'
   c.outPorts.add 'runtime',
     datatype: 'object'
+  c.runtimes = {}
+  c.tearDown = (callback) ->
+    c.runtimes = {}
+    do callback
+  c.process (input, output, context) ->
+    if input.hasData 'in'
+      # New or updated component definition from runtime
+      payload = input.getData 'in'
+      unless payload.componentDefinition
+        output.done()
+        return

-  c.shutdown = ->
-    c.components = {}
-    c.runtime = null
-    c.runtimeUpdated = {}
+      def = payload.componentDefinition

-  c
+      if def.runtime and c.runtimes[def.runtime] and not c.runtimes[def.runtime].updated
+        # Clear component listing from runtime when receiving first
+        output.send
+          out:
+            clearLibrary: true
+        c.runtimes[def.runtime].runtime.components = {}
+        c.runtimes[def.runtime].updated = true
+
+      # Send updated component definition out
+      output.send
+        out:
+          componentDefinition: def
+
+      unless def.runtime
+        output.done()
+        return
+      unless c.runtimes[def.runtime]
+        output.done()
+        return
+
+      # Add this component to the listing
+      c.runtimes[def.runtime].runtime.components[def.name] = def
+
+      # Add this activation context so it can be deactivated
+      c.runtimes[def.runtime].contexts.push context
+
+      updateRuntime c.runtimes[def.runtime], output
+      return
+
+    if input.hasData 'runtime'
+      payload = input.getData 'runtime'
+
+      # New runtime connection, start with fresh library
+      output.send
+        out:
+          clearLibrary: true
+
+      unless payload.definition?.id
+        # Non-persistent runtime, we have no further information for it
+        output.done()
+        return
+
+      # Keep runtime definition so it can be updated when we receive components
+      c.runtimes[payload.definition.id] =
+        runtime: payload
+        updated: false
+        contexts: []
+
+      unless typeof payload.components is 'object'
+        # No cached component information available
+        output.done()
+        return
+
+      # Send previously cached components to have a starting point
+      for name, def of payload.components
+        output.send
+          out:
+            componentDefinition: def
+
+      output.done()
+      return


Remove unused leaflet map search control.

--- src/widgets.coffee
+++ src/widgets.coffee
@@ -1,5 +1,4 @@
 define "app/widgets", ['app/draw', 'leaflet', 'servicetree', 'underscore', 'jquery', 'backbone'], (draw, leaflet, service_tree, _, $, Backbone) ->
-

     TitleControl: L.Control.extend
         options:
@@ -23,17 +22,6 @@
             ')
             $(container).append $logo
             return container
-
-    SearchControl: L.Control.extend
-        options:
-            position: 'topright'
-        onAdd: (map) ->
-            $container = $("<div id='search' />")
-            $el = $("<input type='text' name='query' class='form-control'>")
-            $el.css
-                width: '250px'
-            $container.append $el
-            return $container.get(0)

     ServiceSidebarControl: L.Control.extend
         initialize: (@element, options) ->


Removed suppress_double_scrollbar feature toggle

--- js-library/app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
+++ js-library/app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
@@ -1,4 +1,3 @@
 FactlinkJailRoot.on 'modalOpened', ->
-  if FactlinkJailRoot.can_haz.suppress_double_scrollbar
-    document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
+  document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
 FactlinkJailRoot.on 'modalClosed', -> document.documentElement.removeAttribute('data-factlink-suppress-scrolling')


Subsequent error #99

Subsequent error #99

--- _src/test/mocha_test.coffee
+++ _src/test/mocha_test.coffee
@@ -1192,7 +1192,7 @@
 					done()
 					return
 				return
-			, 200)
+			, 500)
 			return

 		it "set another key with ttl", () ->


Checkboxes looking nice now

Checkboxes looking nice now

--- views/coffeescript/filters.coffee
+++ views/coffeescript/filters.coffee
@@ -33,12 +33,25 @@
   for relType in allRelTypes
     hasIncoming = (rel for rel in incoming when relType == rel).length > 0
     hasOutgoing = (rel for rel in outgoing when relType == rel).length > 0
-    $('#relationsFilterTable').append("
#{relType} <input type='checkbox' value='in'/>in	<input type='checkbox' value='out'/>out
")
+    inCheckboxHtml = buildCheckboxHtml "in", hasIncoming
+    outCheckboxHtml = buildCheckboxHtml "out", hasOutgoing
+
+    $('#relationsFilterTable').append("
#{relType} #{inCheckboxHtml}	#{outCheckboxHtml}
")

   console.log "Incoming:"
   console.dir incoming
   console.log "Outgoing:"
   console.dir outgoing
+
+buildCheckboxHtml = (value, enabled) ->
+  html = "<input type='checkbox' value='#{value}'"
+  html += " checked='true'" if enabled
+  html += " disabled='disabled'" if !enabled
+  html += " />"
+  html += "" if !enabled
+  html += value
+  html += "" if !enabled
+  html

 getIncomingRelTypes = (nodeId, rels) ->
   result = (rel.data.rel_type for rel in rels when rel.end_node == nodeId)


UI`s almost done

UI`s almost done

--- lib/bebopt.coffee
+++ lib/bebopt.coffee
@@ -95,11 +95,11 @@
     @_beatError('long', name)

     if fn is undefined
-      @_short[name] = @_parent
+      @_half[name] = @_parent
     else
-      @_short[name] =
+      @_half[name] =
         cb: fn
-    @_parent = @_short[name]
+    @_parent = @_half[name]
     return @

   _decodeRef: (ref) ->
@@ -133,6 +133,10 @@

   help: (text) =>
     @_parentError()
+    @_parent.usage = text
+    @_parent = null
+    console.log(@)
+    return @

   op: (code, help) =>
     if @_parent is null


Bug fix: couldn't set heights if there was no .actions div.

--- app/assets/javascripts/machine.js.coffee
+++ app/assets/javascripts/machine.js.coffee
@@ -14,8 +14,12 @@

 resizeDivs = (w, h) ->
   assignment_y2 = $one('div.assignment-above').getBoundingClientRect().bottom
-  actions_h = $one('.actions').getBoundingClientRect().bottom -
-              $one('.actions').getBoundingClientRect().top
+  actions_div = $one('.actions')
+  if actions_div
+    actions_h = actions_div.getBoundingClientRect().bottom -
+                actions_div.getBoundingClientRect().top
+  else
+    actions_h = 0
   console_y = $one('div.machine .console').getBoundingClientRect().top

   height_total = Math.floor(h - assignment_y2 - actions_h)


Remove unused JS workaround

Remove unused JS workaround

--- app/assets/javascripts/navigation.coffee
+++ app/assets/javascripts/navigation.coffee
@@ -17,9 +17,3 @@
   $('li.contact').on 'click mouseleave', ->
     $(this).css 'background': 'white'
     $(this).children('.tools').hide()
-
-# Safari bug
-# http://stackoverflow.com/questions/5297122/preventing-cache-on-back-button-in-safari-5
-#$(window).bind 'pageshow', (event) ->
-#  if event.originalEvent.persisted
-#    window.location.reload()


improve file exception

improve file exception

--- lib/browse.coffee
+++ lib/browse.coffee
@@ -41,9 +41,9 @@
   revealFile: ->
     # Get parent folder of active file
     editor = atom.workspace.getActivePaneItem()
-    file = editor?.buffer.file
+    file = editor?.buffer?.file

-    if file isnt null
+    if file?
       @selectFile(file.path)
       return


adding support for command type syncing

adding support for command type syncing

--- app/assets/javascripts/workspaces.coffee
+++ app/assets/javascripts/workspaces.coffee
@@ -3,25 +3,40 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 class RemoteWorkspace
+  syncState: {
+    movement: true,
+    layers: false
+  }
+
+  commandTypes: {
+    layers: ['hideLayer', 'showLayer', 'setStyle'],
+    movement: ['move']
+  }
+
   constructor: (@channel_key) ->
-    @enable()
-
-  hideLayer: (ws, data) ->
-    return if @myMessage(data)
-    ws.hideLayer(data.name, true)
-
-  showLayer: (ws, data) ->
-    return if @myMessage(data)
-    ws.showLayer(data.name, true)
-
-  move: (ws, data) ->
-    return if @myMessage(data)
-    # ws.moveTo(data, true)
-    location.hash = data.hash
-
-  setStyle: (ws, data) ->
-    return if @myMessage(data)
-    ws.setStyle(data.name, true)
+    # @enable()
+
+  commands: {
+    hideLayer: (ws, data) ->
+      ws.hideLayer(data.name, true)
+
+    showLayer: (ws, data) ->
+      ws.showLayer(data.name, true)
+
+    move: (ws, data) ->
+      location.hash = data.hash
+
+    setStyle: (ws, data) ->
+      ws.setStyle(data.name, true)
+  }
+
+  runCommand: (ws, data) =>
+    return if @myMessage(data) || !@commandEnabled(data.command)
+    if @commands[data.command]?
+      @commands[data.command](ws, data)
+    else
+      console.log "Unknown command: #{data.command}"
+

   myMessage: (data) =>
     return data.sentBy == @channel_key
@@ -31,16 +46,27 @@
     data.sentBy ||= @channel_key

     # only broadcast message if we generated the event
-    App.workspaces.send(data) if @is_enabled() && @myMessage(data)
-
-  is_enabled: () =>
-    @sync
-
-  enable: () =>
-    @sync = true
-
-  disable: () =>
-    @sync = false
+    App.workspaces.send(data) if @commandEnabled(data.command) && @myMessage(data)
+
+  commandEnabled: (command) =>
+    cmdType = (type for type, cmdlist of @commandTypes when command in cmdlist)
+    @is_enabled(cmdType)
+
+  is_enabled: (type) =>
+    @syncState[type]
+
+  setSyncState: (type, state) =>
+    if type == 'all'
+      for name,value in @syncState
+        @syncState[name] = state
+    else
+      @syncState[type] = state
+
+  enable: (type = 'all') =>
+    @setSyncState(type, true)
+
+  disable: (type = 'all') =>
+    @setSyncState(type, false)

 class Workspace
   constructor: (el, channel_key) ->
@@ -73,12 +99,7 @@
     @map.addControl(nav)

   runRemoteCommand: (data) =>
-    return if !@remote.is_enabled() || @remote.myMessage(data)
-
-    if @remote[data.command]?
-      @remote[data.command](@, data, true)
-    else
-      console.log "Unknown command: #{data.command}"
+    @remote.runCommand(@, data)

   featurePopup: (e) =>
     features = @map.queryRenderedFeatures(e.point, { layers: @clickable_layers });


make sure there are enough data-breakpoint values for all the styles we inserted

--- app/classifier/tasks/survey/chooser.cjsx
+++ app/classifier/tasks/survey/chooser.cjsx
@@ -1,7 +1,7 @@
 React = require 'react'
 TriggeredModalForm = require 'modal-form/triggered'

-THUMBNAIL_BREAKPOINTS = [Infinity, 40, 20, 10, 5, 0]
+THUMBNAIL_BREAKPOINTS = [Infinity, 160, 140, 120, 100, 80, 60, 40, 20, 10, 5, 0]

 module.exports = React.createClass
   displayName: 'Chooser'


No songs.

No songs.

--- app/client/js/react/pages/mysongs.coffee
+++ app/client/js/react/pages/mysongs.coffee
@@ -9,7 +9,7 @@
     @constructor
   ###
   constructor: (routes, userStore, touch) ->
-    {div,h1,ul,li,nav} = React.DOM
+    {div,h1,ul,li,p,nav} = React.DOM
     {a} = touch.scroll 'a'

     @component = React.createClass
@@ -20,10 +20,14 @@
         deletedSongs = allSongs.filter (song) -> song.inTrash

         div className: 'page',
-          ul className: 'songs', songs.map (song) ->
-            li key: song.id,
-              a href: routes.mySong.url(song),
-                "#{song.getDisplayName()} [#{song.getDisplayArtist()}]"
+          if songs.length
+            ul className: 'songs',
+              songs.map (song) ->
+                li key: song.id,
+                  a href: routes.mySong.url(song),
+                    "#{song.getDisplayName()} [#{song.getDisplayArtist()}]"
+          else
+            p {}, MySongs.MSG_NO_SONGS
           nav {},
             a
               className: 'btn btn-link'
@@ -36,3 +40,4 @@
               , "Trash (#{deletedSongs.length})"

   @MSG_ADD_NEW_SONG: goog.getMsg 'New Song'
+  @MSG_NO_SONGS: goog.getMsg 'No songs.'


fix #1777 - restore taskboard column colors

--- app/coffee/modules/taskboard/main.coffee
+++ app/coffee/modules/taskboard/main.coffee
@@ -372,7 +372,7 @@
             if width
                 column.css('max-width', width)
             else
-                column.removeAttr("style")
+                column.css("max-width", 'none')

         refreshTaskboardTableWidth = () =>
             columnWidths = []


timeline: ordering

timeline: ordering

--- js/collections/locations.coffee
+++ js/collections/locations.coffee
@@ -39,12 +39,12 @@

     sortByDistance: ->
       @sortBy (model) ->
-        model.get "distance"
+        -model.get "distance"

     sortByTime: ->
       @sortBy (model) ->
-        model.get "time"
+        -model.get "time"

     getById: (id) ->


check memory to keep it low

check memory to keep it low

--- app.coffee
+++ app.coffee
@@ -4,6 +4,7 @@
 metrics = require "metrics-sharelatex"
 metrics.initialize "github-latex-ci"
 metrics.memory.monitor(logger)
+metrics.memory.Check(logger)

 settings = require "settings-sharelatex"
 {mountPoint, publicUrl} = settings.internal.github_latex_ci


update show coffee with new route url

update show coffee with new route url

--- app/coffee/showentry.coffee
+++ app/coffee/showentry.coffee
@@ -22,7 +22,7 @@

 	  $.ajax {
       type: 'PUT'
-      url: '/tags/sort_custom_tags'
+      url: '/tags/sort_custom'
       data:
         entry_id: entry
         position_list: order


Possibly fix #98

Possibly fix #98

--- app/controllers/sync.coffee
+++ app/controllers/sync.coffee
@@ -468,60 +468,62 @@
     # Add Changes from client
     for type of queue
       for key, value of queue[type]
-        switch value[0]
-          when "create"
-            # we copy the change into the resultant
-            oldId = key
-            newId = "c-" + (resultantindex[type].max += 1)
-            client[type][clientindex[type][key]].id = newId
-            resultant[type].push(client[type][clientindex[type][key]])
-
-            # update the index
-            resultantindex[type][newId] = resultant[type].length
-
-            # add to fschanges
-            fschanges[type].push(["upload", newId])
-
-            # add to name changes
-            namechanges[type].push([oldId, newId]) if oldId isnt newId
-
-          when "update"
-            # If the item update was before the update on the server, create a conflicted copy
-            if value[1] < resultant[type][resultantindex[type][key]].date
-              # bad case of DRY here
-
+        # Ensures that the key exists
+        if clientindex[type][key]
+          switch value[0]
+            when "create"
               # we copy the change into the resultant
               oldId = key
               newId = "c-" + (resultantindex[type].max += 1)
               client[type][clientindex[type][key]].id = newId
-              client[type][clientindex[type][key]].name += " (Conflicted Copy)"
               resultant[type].push(client[type][clientindex[type][key]])

               # update the index
               resultantindex[type][newId] = resultant[type].length

               # add to fschanges
-              fschanges[type].push(["download", resultant[type][resultantindex[type][key]].id])
               fschanges[type].push(["upload", newId])

               # add to name changes
               namechanges[type].push([oldId, newId]) if oldId isnt newId
-            else
-              # copies the new change in
-              resultant[type][resultantindex[type][key]] = client[type][clientindex[type][key]]
-
-              fschanges[type].push(["upload", resultant[type][resultantindex[type][key]].id])
-          when "destroy"
-            # If the item was after before the delete event, don't do anything
-            if value[1] > resultant[type][resultantindex[type][key]].date
-              # tell the server to delete it
-              fschanges[type].push(["destroy", resultant[type][resultantindex[type][key]].id])
-
-              # destroy the change from the resultant
-              resultant[type].splice(resultantindex[type][key], 1)
-
-              # reindex
-              resultantindex = indexer(resultant)
+
+            when "update"
+              # If the item update was before the update on the server, create a conflicted copy
+              if value[1] < resultant[type][resultantindex[type][key]].date
+                # bad case of DRY here
+
+                # we copy the change into the resultant
+                oldId = key
+                newId = "c-" + (resultantindex[type].max += 1)
+                client[type][clientindex[type][key]].id = newId
+                client[type][clientindex[type][key]].name += " (Conflicted Copy)"
+                resultant[type].push(client[type][clientindex[type][key]])
+
+                # update the index
+                resultantindex[type][newId] = resultant[type].length
+
+                # add to fschanges
+                fschanges[type].push(["download", resultant[type][resultantindex[type][key]].id])
+                fschanges[type].push(["upload", newId])
+
+                # add to name changes
+                namechanges[type].push([oldId, newId]) if oldId isnt newId
+              else
+                # copies the new change in
+                resultant[type][resultantindex[type][key]] = client[type][clientindex[type][key]]
+
+                fschanges[type].push(["upload", resultant[type][resultantindex[type][key]].id])
+            when "destroy"
+              # If the item was after before the delete event, don't do anything
+              if value[1] > resultant[type][resultantindex[type][key]].date
+                # tell the server to delete it
+                fschanges[type].push(["destroy", resultant[type][resultantindex[type][key]].id])
+
+                # destroy the change from the resultant
+                resultant[type].splice(resultantindex[type][key], 1)
+
+                # reindex
+                resultantindex = indexer(resultant)

     # Er, well if notebook name id changes we have to change note ids
     # @consindo is an idiot. Please send him angry messages.


Add fixme

Add fixme

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -193,6 +193,7 @@
         'http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
         attribution: '(c) Esri')

+      # FIXME: hardcoded categories
       categoryMap =
         "functional" : 0
         "not functional" : 1


Fixed duplicate growl message. Added date to growl message

--- lib/docpad.coffee
+++ lib/docpad.coffee
@@ -15,13 +15,7 @@
 watchTree = false
 queryEngine = false
 growl = false
-
-# Logger
-logger = new caterpillar.Logger
-	transports:
-		level: 7
-		formatter:
-			module: module
+logger = false

 # -------------------------------------
 # Prototypes
@@ -270,6 +264,7 @@
 	skeletonsPath: "#{__dirname}/../skeletons"
 	skeletonPath: 'bootstrap'
 	maxAge: false
+	logLevel: 6

 	# Docpad
 	generating: false
@@ -379,7 +374,7 @@
 	# Main

 	# Init
-	constructor: ({rootPath,outPath,srcPath,skeletonsPath,skeletonPath,maxAge,port,server}={}) ->
+	constructor: ({rootPath,outPath,srcPath,skeletonsPath,skeletonPath,maxAge,port,server,logLevel}={}) ->
 		# Options
 		@PluginsArray = []
 		@PluginsObject = {}
@@ -391,6 +386,11 @@
 		@port = port  if port
 		@maxAge = maxAge  if maxAge
 		@server = server  if server
+		logger = new caterpillar.Logger
+			transports:
+				level: logLevel or @logLevel
+				formatter:
+					module: module
 		@loadPlugins null, =>
 			logger.log 'info', 'Finished loading'
 			@loading = false
@@ -587,7 +587,7 @@

 	# Write files
 	generateWriteFiles: (next) ->
-		logger.log 'debug', 'Copying files'
+		logger.log 'debug', 'Writing files'
 		util.cpdir(
 			# Src Path
 			@srcPath+'/files',
@@ -595,8 +595,7 @@
 			@outPath
 			# Next
 			(err) ->
-				unless err
-					logger.log 'debug', 'Copied files'
+				logger.log 'debug', 'Wrote files'  unless err
 				next err
 		)

@@ -608,6 +607,7 @@

 		# Async
 		tasks = new util.Group (err) ->
+			logger.log 'debug', 'Wrote documents'  unless err
 			next err

 		# Find documents
@@ -642,14 +642,10 @@
 		tasks.total = 2

 		# Files
-		@generateWriteFiles (err) ->
-			logger.log 'debug', 'Wrote files'  unless err
-			next err
+		@generateWriteFiles tasks.completer()

 		# Documents
-		@generateWriteDocuments (err) ->
-			logger.log 'debug', 'Wrote documents'  unless err
-			next err
+		@generateWriteDocuments tasks.completer()

 	# Generate
@@ -723,7 +719,7 @@
 											# Write Completed
 											docpad.triggerEvent 'writeFinished', {}, (err) ->
 												unless err
-													growl.notify 'Website Generated'
+													growl.notify (new Date()).toLocaleTimeString(), title: 'Website Generated'
 													logger.log 'info', 'Generated'
 													docpad.cleanModels()
 													docpad.generating = false


Add missed `required` attr in #170

Add missed `required` attr in #170

Would apply better UX to Join Team w/ empty password attempt

--- picoCTF-web/web/coffee/account.coffee
+++ picoCTF-web/web/coffee/account.coffee
@@ -126,7 +126,7 @@
         <Panel header="Team Management">
           <form onSubmit={@onTeamJoin}>
             <Input type="text" valueLink={@linkState "team_name"} addonBefore={towerGlyph} label="Team Name" required/>
-            <Input type="password" valueLink={@linkState "team_password"} addonBefore={lockGlyph} label="Team Password"/>
+            <Input type="password" valueLink={@linkState "team_password"} addonBefore={lockGlyph} label="Team Password" required/>
             <Col md={6}>
               <span>
                 <Button type="submit">Join Team</Button>


Add unsupported event handling

Add unsupported event handling

--- app/assets/javascripts/record_collection/multi_select.js.coffee
+++ app/assets/javascripts/record_collection/multi_select.js.coffee
@@ -1,8 +1,19 @@
 class MultiSelect
-  setup: (table) ->
+  callbacks: {}
+  on: (event, callback) ->
+    @callbacks[event] ||= []
+    @callbacks[event].push callback
+  trigger: (event)->
+    return unless @callbacks[event]
+    for callback in @callbacks[event]
+      callback.call(@)
+  setup: (table, options) ->
     return unless table and table.length
+    selector = @
     table.data 'multi_select', @
     @set 'table', table
+    @options = options
+    @on 'changed', options.changed if options.changed

     @set 'resource', table.data('resource')
     # Add an extra th to all header rows
@@ -20,6 +31,7 @@
       else
         table.find('td.selection .checker').removeClass('unchecked').addClass('checked')
         $(this).removeClass('unchecked').addClass('checked')
+      selector.trigger 'changed'
     table.find('thead tr:last th:first').append toggle_all

     # Create a toggle/checkbox for all records and add it to the row
@@ -29,6 +41,7 @@
         $(this).removeClass('checked').addClass('unchecked')
       else
         $(this).removeClass('unchecked').addClass('checked')
+      selector.trigger 'changed'
     record_td = $('	').addClass('selection').append(record_toggle)
     table.find('tbody tr').prepend record_td

@@ -41,19 +54,22 @@
   set: (path, value) -> @[path] = value
   get: (path) -> @[path]

+  selected_count: -> @table.find('td.selection .checked').length
+  total_count: -> @table.find('td.selection .checker').length
+
   #selected_records: -> @table.find("td.selection input:checked").map( -> $(this).parents('tr').data('record') )
   selected_records: -> @table.find("td.selection .checked").map( -> $(this).parents('tr').data('record') )
-  selected_ids: -> @selected_records().map( -> this.id ).toArray()
+  selected_ids: -> @selected_records().map( -> @id ).toArray()
 root = @
 root.MultiSelect = new MultiSelect()
-$.fn.multi_select = ->
+$.fn.multi_select = (options = {})->
   if @.hasClass('with-selection') or @.prop('tagName') is 'TABLE'
     select = new MultiSelect()
     root.MultiSelect = select
-    select.setup @
+    select.setup @, options
   else
     @.find('table.with-selection').each (i, el)->
       select = new MultiSelect()
       root.MultiSelect = select
-      select.setup $(el)
+      select.setup $(el), options
   select


some docs on crop widget

some docs on crop widget

--- app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
+++ app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
@@ -12,12 +12,33 @@
     class ns.CropWidget

       defaultOptions =
-        container: null # required
-        url: null # required
+
+        # DOM element or selector or jQuery object to which widget
+        # will be appended (required)
+        container: null
+
+        # URL of image to process (required)
+        url: null
+
+        # If set to `true` the resize method will be appended to result URL
+        # like "-/resize/%preferedSize%/". (optional)
         scale: true
+
+        # If set to `true` image in widget will be upscaled
+        # to widget size if necessary. (optional)
         upscale: false
-        widgetSize: null # if `null` widget size will be equal the `container` size
-        preferedSize: null # `null` means any aspect ratio acceptable
+
+        # Defines widget size. if set to `null` widget size will be equal
+        # the `container` size. Syntax: '123x123'. (optional)
+        widgetSize: null
+
+        # Defines image size you want to get at the end.
+        # If `scale` option set to `false` it defines only prefered aspect ratio.
+        # if set to `null` any aspect ratio will be acceptable.
+        # Syntax: '123x123'. (optional)
+        preferedSize: null
+
+        # Specifies whether to show done button in widget or not. (optional)
         controls: true

       LOADING_ERROR = "loadingerror"
@@ -72,15 +93,18 @@
       croppedImageUrl: ->
         @__deferred.promise()

+      # This method could be usefull if you want to make your own done button.
       forceDone: ->
         if @__currentCoords
           @__deferred.resolve @__buildUrl(@__currentCoords)
         else
           throw "not ready"

+      # Returns last selected area coords
       getCurrentCoords: ->
         @__currentCoords

+      # Destroys widget completly
       destroy: ->
         @__jCropApi.destroy()
         @__widgetElement.remove()


#370: dump cache activity history in debug

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -216,7 +216,18 @@
     @debug = @debug_on
     @log = @log_on
     @flash('Debug mode active', 'Debug mode is active. This will affect performance.') unless silent
+
+    clearInterval(Zotero.BetterBibTeX.debugInterval) if Zotero.BetterBibTeX.debugInterval
+    Zotero.BetterBibTeX.debugInterval = setInterval(->
+      Zotero.BetterBibTeX.cacheHistory ||= []
+      Zotero.BetterBibTeX.cacheHistory.push({
+        serialized: Zotero.BetterBibTeX.serialized.stats
+        cache: Zotero.BetterBibTeX.cache.stats
+      })
+    , 1000)
   else
+    clearInterval(Zotero.BetterBibTeX.debugInterval) if Zotero.BetterBibTeX.debugInterval
+    delete Zotero.BetterBibTeX.cacheHistory
     @debug = @debug_off
     @log = @log_off

@@ -745,7 +756,7 @@
       fetch:  (sandbox, params...) => @cache.fetch.apply(@cache, params)
       store:  (sandbox, params...) => @cache.store.apply(@cache, params)
       dump:   (sandbox, params...) => @cache.dump.apply(@cache, params)
-      stats:  (sandbox)            -> {input: Zotero.BetterBibTeX.serialized.stats, output: Zotero.BetterBibTeX.cache.stats }
+      stats:  (sandbox)            -> {history: Zotero.BetterBibTeX.cacheHistory, serialized: Zotero.BetterBibTeX.serialized.stats, cache: Zotero.BetterBibTeX.cache.stats }
     }
     CSL: {
       parseParticles: (sandbox, name) ->


Don't always show console, but bind a key for it and show when profiling

--- benchmark/benchmark-helper.coffee
+++ benchmark/benchmark-helper.coffee
@@ -5,15 +5,18 @@
 Point = require 'point'

 require 'window'
-window.showConsole()

 keymap = new Keymap
 keymap.bindDefaultKeys()
 $(window).on 'keydown', (e) -> keymap.handleKeyEvent(e)
-keymap.bindKeys '*', 'meta-w': 'close'
+keymap.bindKeys '*',
+  'meta-w': 'close'
+  'alt-meta-i': 'show-console'
 $(document).on 'close', -> window.close()
+$(document).on 'show-console', -> window.showConsole()

 window.profile = (description, fn) ->
+  window.showConsole()
   window.benchmark(description, fn, true)

 window.pbenchmark = window.profile


removed wololo color effect, avatar intro and project loading via js

--- js/grenin.coffee
+++ js/grenin.coffee
@@ -1,31 +1,4 @@
-
-fs = require 'fs'
-window.jss = jss = require 'jss-browserify'
-Mustache = require 'mustache'
-
-
-
-# lists so that I can order the projects easily:
-
-projectList = [
-    "animus",
-    "Kipos",
-    "KairosHacks",
-    "LudumPad",
-    "ld48-29",
-    "L8RYO",
-    "rule#34",
-    "pzero",
-    "enitor",
-    "cellvorsum"
-]
-
-stuffList = [
-    "mocha",
-    "mustache.js",
-    "subclass-error",
-    "eximo.js"
-]
+jss = require 'jss-browserify'

@@ -37,60 +10,20 @@

-# generate html for projects
-
-projectsTemplate = fs.readFileSync 'mu/projects.mu', 'utf-8'
-
-projectContainer = document.getElementById 'projects'
-stuffContainer = document.getElementById 'stuff'
-
-reqListener = ->
-    database = JSON.parse @responseText
-
-    projectList = (database[projectName] for projectName in projectList)
-    stuffList = (database[projectName] for projectName in stuffList)
-
-    projectContainer.innerHTML = Mustache.render projectsTemplate, projectList
-    stuffContainer.innerHTML = Mustache.render projectsTemplate, stuffList
-
-dbReq = new XMLHttpRequest()
-dbReq.onload = reqListener
-dbReq.open 'get', '/js/projects.json', true
-dbReq.send()
-
-
-
-# w0l0l0 button color change / rickroll / etc
-
-getRandomColor = () ->
-    letters = '0123456789ABCDEF'.split ''
-    color = '#'
-    for i in [0..5]
-        color += letters[Math.floor(Math.random() * 16)]
-    color
-
-# http://www.sitepoint.com/javascript-generate-lighter-darker-color/
-ColorLuminance = (hex, lum = 0) ->
-    hex = String(hex).replace /[^0-9a-f]/gi, ''
-    hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2] if hex.length < 6
-    rgb = '#'
-    for i in [0..2]
-        c = parseInt (hex.substr i*2, 2), 16
-        c = Math.round(Math.min(Math.max(0, c + c*lum), 255)).toString(16)
-        rgb += ('00' + c).substr c.length
-    rgb
+# w0l0l0 button / rickroll / etc

 preloadedSound = (src) ->
     audio = new Audio src
     audio.preload = true
     audio

-wololo = preloadedSound 'snd/wololo.mp3'
+wololoSrc = '/snd/wololo.mp3'
+wololo = preloadedSound wololoSrc
 signature = document.getElementById 'signature'
 signature.onclick = () ->

     wololo.play()
-    wololo = preloadedSound 'snd/wololo.mp3'
+    wololo = preloadedSound wololoSrc

     #random chance of rick roll ftw
     if Math.random() < 0.05
@@ -98,86 +31,6 @@
         reqyo 'RICKROLLD'
         window.open 'https://www.youtube.com/watch?v=BROWqjuTM0g', '_blank'
         return
-
-    color = ColorLuminance getRandomColor(), 0
-    document.body.style.background = color
-    jss.set 'h1, h2, h3, p, li',
-        color: 'white'
-    jss.set 'a, .links a',
-        color: ColorLuminance color, 0.5
-        'text-decoration': 'none'
-    jss.set 'a:hover, .links a:hover',
-        color: ColorLuminance color, 0.7
-        'text-decoration': 'none'
-
-
-
-# avatar intro animation
-
-doUp = ->
-    jss.set '.dc-fadeIn',
-        transition: 'opacity 1s'
-        '-webkit-transition': 'opacity 1s'
-        opacity: '1'
-
-    jss.set '.moveUp',
-        transition: 'transform 3s'
-        '-webkit-transition': '-webkit-transform 3s'
-        transform: 'translateY(0px)'
-        '-webkit-transform': 'translateY(0px)'
-
-    jss.set '#avatar',
-        opacity: '0'
-
-showLogo = ->
-    jss.set '#avatar',
-        transition: 'opacity 1s'
-        '-webkit-transition': 'opacity 1s'
-        opacity: '1'
-    setTimeout ->
-        jss.set '.loader',
-            transition: 'opacity 0.5s'
-            '-webkit-transition': 'opacity 0.5s'
-            opacity: '0'
-    , 500
-
-immediate = (cb) -> setTimeout cb, 1
-(->
-    elapsed = 0
-    logo_wait = 750
-    up_wait = 1100
-    loaded = false
-
-    window.onload = ->
-
-        return if loaded
-        loaded = true
-
-        doUp()
-
-        elapsed = +new Date - elapsed
-
-        if elapsed < logo_wait
-            setTimeout showLogo, logo_wait - elapsed
-        else
-            showLogo()
-
-    # in case your browser is a turtle
-    setTimeout window.onload, 2000
-
-    immediate ->
-        elapsed = +new Date
-        jss.set '.loader',
-            transition: 'transform 1s ease-out, background-color 1s'
-            '-webkit-transition': '-webkit-transform 1s ease-out, background-color 1s'
-            transform: 'scale(1)'
-            '-webkit-transform': 'scale(1)'
-            'background-color': 'black'
-)()
-
-replaceHTML = (id, lambda) ->
-    document.getElementById id
-    .innerHTML = lambda()

@@ -192,6 +45,10 @@
 getRandomLabel = ->
     labels[Math.floor(Math.random() * labels.length)]

+replaceHTML = (id, lambda) ->
+    document.getElementById id
+    .innerHTML = lambda()
+
 replaceHTML 'say-my-name', getRandomName


Move background argument closer to input arguments

That makes more sense to me.

--- lib/imager.coffee
+++ lib/imager.coffee
@@ -17,12 +17,12 @@
       .in(face.eyes)
       .in(face.nose)
       .in(face.mouth)
+      .background(face.color)
       .mosaic()
       .resize(size.width, size.height)
       .trim()
       .gravity('Center')
       .extent(size.width, size.height)
-      .background(face.color)
       .stream('png', callback)

   resize: (imagePath, size, callback) ->


Score list is now stateful

Score list is now stateful

--- resources/assets/coffee/react/beatmapset-page/scoreboard.coffee
+++ resources/assets/coffee/react/beatmapset-page/scoreboard.coffee
@@ -24,12 +24,16 @@

     @state =
       currentScoreboard: 'global'
+      scores: @props.scores

   componentDidMount: ->
     $.subscribe 'beatmapset:scoreboard:set.beatmapSetPageScoreboard', @_scoreboardSwitch

   componentWillUnmount: ->
     $.unsubscribe '.beatmapSetPageScoreboard'
+
+  componentWillReceiveProps: (props) ->
+    @setState scores: props.scores

   _scoreboardSwitch: (_e, scoreboard) =>
     @setState currentScoreboard: scoreboard
@@ -52,7 +56,7 @@
       div className: 'beatmapset-scoreboard__line'

       el BeatmapSetPage.ScoreboardFirst,
-        score: @props.scores[0]
+        score: @state.scores[0]

       div className: 'beatmapset-scoreboard__row',
         header.map (m) =>
@@ -62,5 +66,5 @@
           span className: className, key: m, Lang.get "beatmaps.beatmapset.show.extra.scoreboard.list.#{m}"

-      @props.scores[1..].map (s, i) =>
+      @state.scores[1..].map (s, i) =>
         el BeatmapSetPage.ScoreboardItem, score: s, position: i + 2, key: i


Kernel: Revert `jupyter/console-text` hack

* This hack isn't necessary after adding `text/plain` to
  `consoleTextTransform`.

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -230,8 +230,7 @@

             else if msg_type is 'inspect_reply'
                 callback
-                    data:
-                        'jupyter/console-text': message.content.data['text/plain']
+                    data: message.content.data
                     found: message.content.found

             else


#24 was just typo!

#24 was just typo!

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -74,7 +74,7 @@
           unless (c.isAtEndOfLine() and not c.isAtBeginningOfLine())
             c.moveRight()
       newRange = selection.getBufferRange().union(tailRange)
-      options = {autoscroll: false, preserveFold: true}
+      options = {autoscroll: false, preserveFolds: true}
       selection.setBufferRange(newRange, options)

   # Utils


remove unnecessary payments script

remove unnecessary payments script

fixes #6353
fixes #6322

--- backend/app/assets/javascripts/spree/backend/select_payments.js.coffee
+++ backend/app/assets/javascripts/spree/backend/select_payments.js.coffee
@@ -1,7 +0,0 @@
-$ ->
-  if $('.new_payment').is('*')
-    $('.payment-method-settings fieldset').addClass('hidden').first().removeClass('hidden')
-    $('input[name="payment[payment_method_id]"]').click ()->
-      $('.payment-method-settings fieldset').addClass('hidden')
-      id = $(this).parents('li').data('id')
-      $("fieldset[data-id='#{id}']").removeClass('hidden')


params: add prepare

params: add prepare

--- lib/params.coffee
+++ lib/params.coffee
@@ -38,6 +38,19 @@
       name: 'host', shortcut: 'h'
       description: 'Server hostname associated with the plan'
       required: true
+    ,
+      name: 'modules', shortcut: 'm', type: 'array'
+      description: 'Limit to a list of modules'
+    ,
+      name: 'fast', shortcut: 'f', type: 'boolean'
+      description: 'Fast mode without dependency resolution'
+    ]
+  ,
+    name: 'prepare'
+    description: 'Prepare components before deployment'
+    options: [
+      name: 'hosts', shortcut: 'h', type: 'array'
+      description: 'Limit to a list of server hostnames'
     ,
       name: 'modules', shortcut: 'm', type: 'array'
       description: 'Limit to a list of modules'


Made parser work recursively, fetch all edge cases

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -1,17 +1,3 @@
-# to do: erkennt erste anonyme func noch nicht
-# var server = http.createServer( function( req, res ) {
-#  parseMarkdown( function( err, data ) {
-#    compileHtml( data, function( err, data ) {
-#      res.end( data );
-#    });
-#  } );
-# });
-#
-# Auch das nicht:
-# ret[l] = _.sortBy(ret[l], function (el) {
-#  return el.character;
-#});
-
 esprima = require 'esprima'
 _ = require 'lodash'

@@ -59,62 +45,65 @@
     @functions = []

     # Going through all the statements
-    hoisting = false
+    @hoisting = false
     @hoistingPosition = null

     for statement, index in body
-
       # Set hoistingPosition for first statement
-      if 0 == index
-        @hoistingPosition = statement.loc
-
-      # All the variables
-
-      if statement.type == 'VariableDeclaration'
-        for declarator in statement.declarations
-          variable = new Variable(declarator, @)
-          variable.hoisted = hoisting
-          #console.log "#{variable.name} will be hoisted :)" if hoisting
-          if variable.node.init?.type == "FunctionExpression"
-            @functions.push new Funktion(variable.node.init, @, variable)
-          else if variable.node.init?.type == "CallExpression"
-            call = variable.node.init
-            for argument in call.arguments when argument.type == 'FunctionExpression'
-              @functions.push new Funktion(argument, @)
-          else
-            @variables.push variable
-      else
-        hoisting = true
-
-      # All the function declarations
-
-      if statement.type == 'FunctionDeclaration'
-        @functions.push new Funktion(statement, @)
-
-      # All the function expressions
-
-      else if statement.type == 'ExpressionStatement'
-        #console.log statement
-        if statement.expression.type == 'CallExpression'
-          call = statement.expression
-          for argument in call.arguments when argument.type == 'FunctionExpression'
-            @functions.push new Funktion(argument, @)
-
-    #varDeclarations = (value for value in body when value.type == 'VariableDeclaration')
-
-    # Getting all functions
-    #@functions.push(new Funktion(value, @)) for value in body when value.type == 'FunctionDeclaration'
-
-    # Getting all function expressions
-    #expressionStatements = (statement for statement in body when statement.type == 'ExpressionStatement')
-    #for statement in expressionStatements
-
+      @hoistingPosition = statement.loc if 0 == index
+      @parseNode statement

     @name = "GLOBAL" if not @parentScope?

     # Registering children
     @children.push @variables
     @children.push @functions
+
+  parseNode: (node, identifier) ->
+
+    if node.type isnt "VariableDeclaration"
+      @hoisting = true
+
+    switch node.type
+
+      when "ExpressionStatement"
+        @parseNode(node.expression)
+
+      # *Expressions
+      when "FunctionExpression"
+        @functions.push new Funktion(node, @, identifier)
+
+      when "CallExpression"
+        @parseNode node.callee
+        @parseNode(argument) for argument in node.arguments
+
+      when "MemberExpression"
+        @parseNode node.object
+        @parseNode node.property
+
+      when "AssignmentExpression"
+        @parseNode node.left
+        @parseNode node.right
+
+      # *Declarations
+      when "VariableDeclaration"
+        for declarator in node.declarations
+          variable = new Variable(declarator, @)
+          variable.hoisted = @hoisting
+          if variable.node.init
+            @parseNode(variable.node.init, identifier)
+          @variables.push variable
+
+      when "FunctionDeclaration"
+        @functions.push new Funktion(node, @)
+
+      when "Identifier"
+        # Do nothing
+        return
+
+      when "EmptyStatement"
+        # Do nothing
+        return

   # Returns a child identifier from this scope
   getIdentifier: (name) ->


test pack for getDocUpdates with version

--- test/unit/coffee/MongoManager/MongoManagerTests.coffee
+++ test/unit/coffee/MongoManager/MongoManagerTests.coffee
@@ -164,19 +164,29 @@
 	describe "getDocUpdates", ->
 		beforeEach ->
 			@results = [
-				{foo: "mock-update", v: 56},
-				{foo: "mock-update", v: 55},
-				{foo: "mock-update", v: 42},
-				{foo: "mock-update", v: 41}
+				{foo: "mock-update", v: 56, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
+				{pack: [ {foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
+					{foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
+					{foo: "mock-update", v: 52, doc_id: 100, project_id: 1} ]
+					, v: 52, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 42, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 41, doc_id: 100, project_id: 1}
 			]
 			@updates_between = [
-				{foo: "mock-update", v: 55},
-				{foo: "mock-update", v: 42}
+				{foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 52, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 42, doc_id: 100, project_id: 1}
 			]
 			@updates_after = [
-				{foo: "mock-update", v: 56},
-				{foo: "mock-update", v: 55},
-				{foo: "mock-update", v: 42}
+				{foo: "mock-update", v: 56, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 52, doc_id: 100, project_id: 1},
+				{foo: "mock-update", v: 42, doc_id: 100, project_id: 1}
 			]
 			@db.docHistory = {}
 			@db.docHistory.find = sinon.stub().returns @db.docHistory


Deal with http requests using phantomjs

Deal with http requests using phantomjs

--- lib/reader.coffee
+++ lib/reader.coffee
@@ -1,7 +1,7 @@
-fs    = require('fs')
-url   = require('url')
-http  = require('http')
-https = require('https')
+fs      = require('fs')
+url     = require('url')
+through = require('through')
+phantom = require('phantom')

 module.exports = class Reader
   messages =
@@ -31,23 +31,24 @@
         callback new Error(messages.noSourceError)
     return

-  getFileStream: (path, callback = ->) ->
-    callback null, fs.createReadStream(path)
+  getFileStream: (source, callback = ->) ->
+    callback null, fs.createReadStream(source)
     return

-  getUrlStream: (options, callback = ->) ->
-    options = @_httpOptions options
-    client  = if options.protocol is 'https:' then https else http
+  getUrlStream: (source, callback = ->) ->
+    tr = through (data) ->
+      @emit 'data', data

-    client.get options, (res) ->
-      unless res.statusCode is 200
-        err = new Error "#{messages.httpStatusError} #{res.statusCode}:
-          #{http.STATUS_CODES[res.statusCode]}"
-        callback err
-        return
-      callback null, res
-    .on 'error', callback
-    return
+
+    phantom.create (ph) ->
+      ph.createPage (page) ->
+        page.open source, (status) ->
+          return callback new Error(status) if status isnt 'success'
+          callback null, tr
+
+          page.evaluate (-> document.body.innerHTML), (result) ->
+            tr.end result
+            ph.exit()

   _httpOptions: (options) ->
     options = url.parse options if typeof options is 'string'


Fix for page height JS error that doesn't prohibit 'any size' screenshot requests

--- lib/render.coffee
+++ lib/render.coffee
@@ -3,9 +3,9 @@

 userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17"
 url = system.args[1]
-format = system.args[2] or "PNG"
+format = (system.args[2] or "PNG").toUpperCase()
 width = parseInt(system.args[3]) or 1280
-height = parseInt(system.args[4]) # default: fit content
+height = parseInt(system.args[4]) or null # default: fit content
 crop = true

 setTimeout ->
@@ -15,11 +15,21 @@

 render = ->
   result = page.evaluate ->
-    title: document.title
-    width: document.body?.clientWidth or width
-    height: document.body?.clientHeight or height
+    result = {}

-  if crop and height and result.height > height
+    result.title          = document.title if document?.title?
+
+    if false # debug
+      result.documentWidth  = document.width if document?.width?
+      result.documentHeight = document.height if document?.height?
+      result.innerWidth     = window.innerWidth if window?.innerWidth?
+      result.innerHeight    = window.innerHeight if window?.innerHeight?
+      result.clientWidth    = document.body.clientWidth if document?.body?.clientWidth?
+      result.clientHeight   = document.body.clientHeight if document?.body?.clientHeight?
+
+    result
+
+  if crop and width > 0 and height > 0
     # Cropping isn't exactly what we want, but PhantomJS does
     # not yet have a "window size" concept (See NOTE above).
     page.clipRect =
@@ -29,10 +39,14 @@
       height: height

     result.height = height
-    result.width = width
+    result.width  = width
+
+  delete result.height unless result.height
+  delete result.width  unless result.width

   result.imageData = page.renderBase64(format)
   result.format = format
+
   console.log JSON.stringify(result)
   phantom.exit()

@@ -70,9 +84,7 @@
   phantom.exit 1

 page.onLoadFinished = (status) ->
-  loaded = page.evaluate(->
-    !!document.body
-  )
+  loaded = page.evaluate -> !!document.body
   setTimeout render, 300  if status is "success" or loaded is true

 page.open url


fixed issue with querystrings not routing correctly

--- lib/router.coffee
+++ lib/router.coffee
@@ -33,7 +33,7 @@
     res.send(opts.content)

   respond: (req, res, next) ->
-    filename = helpers.find_file(@renderer.opts.root, req.url)
+    filename = helpers.find_file(@renderer.opts.root, req.url.split(/[#?]/)[0])
     return next() unless filename?
     return next() unless @tree.is_visible(filename) and @tree.is_file(filename)


:art:

:art:

--- lib/scroll.coffee
+++ lib/scroll.coffee
@@ -51,58 +51,61 @@
     firstScreenRow = @rows.first + @scrolloff
     @editor.scrollToScreenPosition([firstScreenRow - @getCount(1), 0])

+# Scroll without Cursor Position change.
+# -------------------------
 class ScrollCursor extends Scroll
   @extend()
   constructor: ->
     super
-    cursor = @editor.getCursorScreenPosition()
-    @pixel = @editorElement.pixelPositionForScreenPosition(cursor).top
+    point = @editor.getCursorScreenPosition()
+    @pixelCursorTop = @editorElement.pixelPositionForScreenPosition(point).top

   execute: ->
     @moveToFirstCharacterOfLine() unless @options.leaveCursor
+    @editor.setScrollTop @getScrollTop() if @isScrollable()

   moveToFirstCharacterOfLine: ->
     @editor.moveToFirstCharacterOfLine()

+  getOffSetPixelHeight: (where) ->
+    lineCount =
+      switch where
+        when 'top' then @scrolloff
+        when 'bottom' then @scrolloff + 1
+    @editor.getLineHeightInPixels() * lineCount
+
 class ScrollCursorToTop extends ScrollCursor
   @extend()
-  execute: ->
-    super
-    @scrollUp()
+  isScrollable: ->
+    not (@rows.last is @rows.final)

-  scrollUp: ->
-    return if @rows.last is @rows.final
-    @pixel -= (@editor.getLineHeightInPixels() * @scrolloff)
-    @editor.setScrollTop(@pixel)
+  getScrollTop: ->
+    @pixelCursorTop - @getOffSetPixelHeight('top')
+
+class ScrollCursorToBottom extends ScrollCursor
+  @extend()
+  isScrollable: ->
+    not (@rows.first is 0)
+
+  getScrollTop: ->
+    @pixelCursorTop - (@editor.getHeight() - @getOffSetPixelHeight('bottom'))

 class ScrollCursorToMiddle extends ScrollCursor
   @extend()
-  execute: ->
-    super
-    @scrollMiddle()
+  isScrollable: ->
+    true

-  scrollMiddle: ->
-    @pixel -= (@editor.getHeight() / 2)
-    @editor.setScrollTop(@pixel)
+  getScrollTop: ->
+    @pixelCursorTop - (@editor.getHeight() / 2)

-class ScrollCursorToBottom extends ScrollCursor
-  @extend()
-  execute: ->
-    super
-    @scrollDown()
-
-  scrollDown: ->
-    return if @rows.first is 0
-    offset = (@editor.getLineHeightInPixels() * (@scrolloff + 1))
-    @pixel -= (@editor.getHeight() - offset)
-    @editor.setScrollTop(@pixel)
-
+# Horizontal Scroll
+# -------------------------
 class ScrollHorizontal extends Scroll
   @extend()
   constructor: ->
     super
     cursorPos = @editor.getCursorScreenPosition()
-    @pixel = @editorElement.pixelPositionForScreenPosition(cursorPos).left
+    @pixelCursorTop = @editorElement.pixelPositionForScreenPosition(cursorPos).left

   putCursorOnScreen: ->
     @editor.scrollToCursorPosition({center: false})
@@ -110,13 +113,13 @@
 class ScrollCursorToLeft extends ScrollHorizontal
   @extend()
   execute: ->
-    @editor.setScrollLeft(@pixel)
+    @editor.setScrollLeft(@pixelCursorTop)
     @putCursorOnScreen()

 class ScrollCursorToRight extends ScrollHorizontal
   @extend()
   execute: ->
-    @editor.setScrollRight(@pixel)
+    @editor.setScrollRight(@pixelCursorTop)
     @putCursorOnScreen()

 module.exports = {ScrollDown, ScrollUp, ScrollCursorToTop, ScrollCursorToMiddle,


Fixed issue with undefined slack invite panel

--- lib/modules/slack_invite.coffee
+++ lib/modules/slack_invite.coffee
@@ -18,7 +18,7 @@
       inviteView.on 'core:confirm', =>
         messageRcpt = inviteView.miniEditor.getText()
         @sendSlackMessageTo(messageRcpt)
-        invitePanel.panel.hide()
+        inviteView.panel.hide()

   sendSlackMessageTo: (messageRcpt) ->
     #prepare the slack stuff


Added lispy stuff

Added lispy stuff

--- lib/stdlib.coffee
+++ lib/stdlib.coffee
@@ -28,5 +28,23 @@
       false
   not: (x) ->
     not x[0]
+  # Lispy stuff
+  '+': (x) ->
+    r = 0
+    r += y for y in x
+    r
+  '-': (x) ->
+    r = x[0]
+    r -= y for y in x[1..]
+    r
+  '*': (x) ->
+    r = x[0]
+    r *= y for y in x[1..]
+    r
+  '/': (x) ->
+    r = x[0]
+    r /= y for y in x[1..]
+    r
+  '=': equal

 module.exports = library


Stream::select(): added guards against @rest() returning null.

--- lib/stream.coffee
+++ lib/stream.coffee
@@ -29,6 +29,8 @@

 class Stream
+  #TODO @rest() returning null is no fun
+
   constructor: (@first, rest) ->
     @rest = if rest?
       => val = rest(); (@rest = -> val)()
@@ -50,9 +52,9 @@

   select: (pred) ->
     if pred(@first)
-      new Stream(@first, => if @rest() then @rest().select(pred))
+      new Stream(@first, => if @rest() then @rest()?.select(pred))
     else
-      if @rest then @rest().select(pred)
+      if @rest then @rest()?.select(pred)

   combine: (other, op) ->
     new Stream(op(this.first, other.first), =>


Small changes to Stream

Small changes to Stream

--- lib/stream.coffee
+++ lib/stream.coffee
@@ -81,6 +81,7 @@
     while stream?
       func(stream.first)
       stream = stream.rest()
+    null

   size: ->
     count = 0
@@ -100,10 +101,10 @@

   toString: -> @toArray().join(', ')

-Stream.range = (start, step) ->
-  new Stream(start, -> Stream.range(step(start), step))
+Stream.iterate = (start, step) ->
+  new Stream(start, -> Stream.iterate(step(start), step))

-Stream.from = (start) -> Stream.range(start, (n) -> n + 1)
+Stream.from = (start) -> new Stream(start, -> Stream.from(start + 1))

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


#942 - Question labels don't revert from input mode

--- jsapp/xlform_model_view/view.utils.coffee
+++ jsapp/xlform_model_view/view.utils.coffee
@@ -31,6 +31,7 @@
       current_value = selector.text()
       edit_box = $('<input type="text" value="' + current_value + '" class="js-cancel-sort"/>')
       selector.replaceWith edit_box
+      edit_box.focus()

       commit_edit = () ->
         parent_element.find('.error-message').remove()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -159,7 +159,7 @@
   color1 = Color2(255, 255, 255, 1)
   color2 = Color2([255, 255, 255])

-  log Point()
+  log color1

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -182,9 +182,11 @@

 test "Integration", ->
   engine = Engine()
-
+
   object = engine.add
     class: "GameObject"
+
+  engine.

 test "#setState", 3, ->
   engine = Engine()


Add also light&dark variants

Add also light&dark variants

--- examples/helloworld/sections/post.coffee
+++ examples/helloworld/sections/post.coffee
@@ -36,6 +36,11 @@
     c.branch 'right', (b) ->
       b.set 'variant', 'right'
       item
+  .then (c, d) ->
+    c.branch 'light', (b) ->
+      d
+    c.branch 'dark', (b) ->
+      d
   .else (d, e) ->
     d.get 'item'
   .some [


better tests

better tests

--- test/hinoki.coffee
+++ test/hinoki.coffee
@@ -171,7 +171,7 @@

             test.done()

-    'inject':
+    'inject with one container':

         'missing factory': (test) ->
             container = {}
@@ -231,7 +231,7 @@
                     c: 4
                 test.done()

-        'one factory and two seeds': (test) ->
+        '1 factory and 2 seeds': (test) ->
             container =
                 scope:
                     a: 1
@@ -349,7 +349,9 @@
                     c: 4
                 test.done()

-        'two containers': (test) ->
+    'inject with 3 containers':
+
+        '3 async dependencies': (test) ->
             container1 =
                 factories:
                     c: (a, b) ->
@@ -359,22 +361,26 @@

             container2 =
                 factories:
+                    b: (a) ->
+                        deferred = q.defer()
+                        q.nextTick -> deferred.resolve a + 1
+                        return deferred.promise
+
+            container3 =
+                factories:
                     a: ->
                         deferred = q.defer()
                         q.nextTick -> deferred.resolve 1
                         return deferred.promise
-                    b: (a) ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve a + 1
-                        return deferred.promise
-
-            hinoki.inject [container1, container2], (a, b, c) ->
+
+            hinoki.inject [container1, container2, container3], (a, b, c) ->
                 test.equals a, 1
                 test.equals b, 2
                 test.equals c, 4
                 test.deepEqual container1.scope,
                     c: 4
                 test.deepEqual container2.scope,
-                    a: 1
-                    b: 2
-                test.done()
+                    b: 2
+                test.deepEqual container3.scope,
+                    a: 1
+                test.done()


case insensitive hackery

case insensitive hackery

--- client/code/view/view/tile.coffee
+++ client/code/view/view/tile.coffee
@@ -11,10 +11,10 @@
     @$el.html JST['view-tile'] @model.toJSON()

     # :TODO: Make this suck less
-    if /spreadsheet/.test @model.get('name')
+    if /spreadsheet/i.test @model.get('name')
       @$el.addClass 'spreadsheet'
-    if /download/.test @model.get('name')
+    if /download/i.test @model.get('name')
       @$el.addClass 'download'
-    if /newdataset/.test @model.get('name')
+    if /newdataset/i.test @model.get('name')
       @$el.addClass 'source'
     @


enough for tonight, we have to think over this prompting system

--- test/lib/user.iced
+++ test/lib/user.iced
@@ -111,7 +111,18 @@

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

-  signup : () ->
+  signup : (cb) ->
+    script = [
+      @username
+      @password
+      @password
+      @email
+      ""
+    ]
+    stdin = script.join("\n")
+    await @keybase { args : [ "signup" ], stdin }, defer err, out
+    console.log out.toString('utf8')
+    cb err

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

@@ -129,6 +140,7 @@
   await init { }, esc defer()
   user = User.generate()
   await user.init esc defer()
+  await user.signup esc defer()
   cb null

 await test defer err


activityticker: removed the filter for ticker

--- workers/social/lib/social/models/activityticker.coffee
+++ workers/social/lib/social/models/activityticker.coffee
@@ -15,35 +15,9 @@
           (signature Object, Function)
         ]

-  relationshipNames = ["follower", "like", "member", "author"]
-  constructorNames  = ["JAccount", "JGroup", "JTag", "JNewStatusUpdate"]

   JAccount = require './account'

-  mapSourceNames = (filters)=>
-    relationshipMap =
-      "follower" : ["JAccount", "JTag"]
-      "like"     : ["JAccount", "JNewStatusUpdate"]
-      "member"   : ["JGroup"]
-
-    sources = []
-    for filter in filters
-      sources.concat relationshipMap[filter]
-
-    if sources.length is 0
-      return constructorNames
-    return sources
-
-  mapTargetNames = (filters) ->
-    validFilters = ["follower", "like", "member"]
-    targets = []
-    # The only possible options are either returning only "JAccount" or
-    # returning whole constructorNames. I left this function for future-cases.
-    for filter in filters
-      if filter in validFilters
-        targets.push "JAccount"
-        return targets
-    return constructorNames

   decorateEvents = (relationship, callback) ->
     {source, target, as, timestamp} = relationship


use CoffeeScript features where appropriate

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee
@@ -39,7 +39,7 @@

         @valueToOrderIndex = {}

-        _.each @available.element.options, (option, i) =>
+        for option,i in @available.element.options
           @valueToOrderIndex[option.value] = i

         # This occurs even when the palette is disabled, to present the
@@ -55,7 +55,8 @@
         values = JSON.parse @hidden.value()
         valueToPosition = {}

-        _.each values, (v, i) -> valueToPosition[v] = i
+        for v, i in values
+          valueToPosition[v] = i

         e = @available.element

@@ -79,7 +80,7 @@
         @updateButtons()

       updateHidden: ->
-        values = _.pluck(@selected.element.options, "value")
+        values = (option.value for option in @selected.element.options)
         @hidden.value JSON.stringify values

       bindEvents: ->
@@ -132,14 +133,16 @@
       doMoveUp: ->
         options = _.toArray @selected.element.options

-        movers = _.filter options, isSelected
+        groups = _.partition options, isSelected
+
+        movers = groups[0]

         # The element before the first selected element is the pivot; all the selected elements will
         # move before the pivot. If there is no pivot, the elements are shifted to the front of the list.
         firstMoverIndex = _.first(movers).index
         pivot = options[firstMoverIndex - 1]

-        options = _.reject options, isSelected
+        options = groups[1]

         splicePos = if pivot then _.indexOf options, pivot else 0

@@ -154,14 +157,16 @@
       doMoveDown: ->
         options = _.toArray @selected.element.options

-        movers = _.filter options, isSelected
+        groups = _.partition options, isSelected
+
+        movers = groups[0]

         # The element after the last selected element is the pivot; all the selected elements will
         # move after the pivot. If there is no pivot, the elements are shifted to the end of the list.
-        lastMoverIndex = _.last(movers).index
+        lastMoverIndex = movers[-1..-1][0].index
         pivot = options[lastMoverIndex + 1]

-        options = _.reject options, isSelected
+        options = groups[1]

         splicePos = if pivot then _.indexOf(options, pivot) + 1 else options.length


Added team as a property to goals in the level schema.

--- server/levels/level_schema.coffee
+++ server/levels/level_schema.coffee
@@ -30,6 +30,7 @@
   worldEndsAfter: {title: 'World Ends After', description: "When included, ends the world this many seconds after this goal succeeds or fails.", type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 300, default: 3}
   howMany: {title: "How Many", description: "When included, require only this many of the listed goal targets instead of all of them.", type: 'integer', minimum: 1}
   hiddenGoal: {title: "Hidden", description: "Hidden goals don't show up in the goals area for the player until they're failed. (Usually they're obvious, like 'don't die'.)", 'type': 'boolean', default: false}
+  team: c.shortString(title: 'Team', description: 'Name of the team this goal is for, if it is not for all of the playable teams.')
   killThangs: c.array {title: "Kill Thangs", description: "A list of Thang IDs the player should kill, or team names.", uniqueItems: true, minItems: 1, "default": ["ogres"]}, thang
   saveThangs: c.array {title: "Save Thangs", description: "A list of Thang IDs the player should save, or team names", uniqueItems: true, minItems: 1, "default": ["humans"]}, thang
   getToLocations: c.object {title: "Get To Locations", description: "TODO: explain", required: ["who", "targets"]},


Update news.coffee

Update news.coffee

--- server/materials/data/news.coffee
+++ server/materials/data/news.coffee
@@ -3,8 +3,8 @@

 export default allNews = () ->
     return news([
-        newsItem("Занятий 13 марта во ВШЭ и 16 марта в лицее 40 не будет", String.raw"""
-            Занятий 13 марта во ВШЭ и 16 марта в лицее 40 не будет. Занимайтесь из дома. Если хотите, я готов организовать консультации по zoom, пишите.
+        newsItem("Занятий 20 марта во ВШЭ и 23 марта в лицее 40 не будет", String.raw"""
+            Занятий 20 марта во ВШЭ и 23 марта в лицее 40 не будет. Занимайтесь из дома. Если хотите, я готов организовать консультации по zoom, пишите.
             Далее следите за объявлениями на сайте.
         """),
         newsItem("Опрос про алгопрог", String.raw"""


network activity spinner is shown by parent app

--- exercises/src/components/vocabulary.cjsx
+++ exercises/src/components/vocabulary.cjsx
@@ -46,9 +46,6 @@
     vocabId = @getVocabId()
     return null unless vocabId

-    if vocabId and VocabularyStore.isLoading(vocabId)
-      return <NetworkActivity />
-
     vocabTerm = VocabularyStore.getFromExerciseId(@props.id)
     unless vocabTerm
       return <RecordNotFound recordType="Vocabulary Term" id={vocabId} />


add logging upon session destruction

add logging upon session destruction

--- library/exposure/session.coffee
+++ library/exposure/session.coffee
@@ -67,6 +67,8 @@
         assert _.isString qualified = @namespaced sid
         message = "Redis session engine error at destroy"
         assert _.isObject(@redis), "no Redis client yet"
+        message = "Destroying a Redis stored session %"
+        logger.debug message.gray, "#{sid}".underline
         @redis.del qualified, (error, trailings...) ->
             logger.error message.red, error if error
             return callback.call this, error if error


fix a misnomer with a dir name

fix a misnomer with a dir name

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -40,7 +40,7 @@
 # the project. Use one of these instead of hardcoding the defaults!
 # See the task definitions below for information on defaults usage.
 DEFAULT_LIBRARY = "library"
-DEFAULT_ARTIFACT = "artifact"
+DEFAULT_ARTIFACTS = "artifacts"
 DEFAULT_DOCUMENTS = "documents"
 DEFAULT_SCOPING = "development"
 DEFAULT_LOGGING = "info"
@@ -58,7 +58,7 @@
     # options are shared among all of the tasks and the entire file!
     # Please refer to `Cakefile` and `CoffeeScript` for information.
     option "-l", "--library [PATH]", "Path to the library sources"
-    option "-a", "--artifact [PATH]", "Path to the artifact directory"
+    option "-a", "--artifacts [PATH]", "Path to the artifacts directory"
     option "-d", "--documents [PATH]", "Path to the documents directory"
     option "-s", "--scoping [SCOPE]", "The name of the scope to boot kernel"
     option "-i", "--logging [LEVEL]", "The level to use for the logging output"
@@ -93,8 +93,8 @@
     # Please see the implementation for some of the important details
     task "compile", "compile CoffeeScript into JavaScript", (options) ->
         assert library = options.library or DEFAULT_LIBRARY
-        assert artifact = options.artifact or DEFAULT_ARTIFACT
-        assert parameters = ["-c", "-o", artifact, library]
+        assert artifacts = options.artifacts or DEFAULT_ARTIFACTS
+        assert parameters = ["-c", "-o", artifacts, library]
         parameters.unshift "-w" if options.watch or false
         assert watching = "Watching the %s directory".blue
         logger.info watching, library.bold if options.watch


Add check for empty cwd

Add check for empty cwd

--- lib/controllers/process-controller.coffee
+++ lib/controllers/process-controller.coffee
@@ -276,7 +276,10 @@
         @envBackup[key] = shell.env[key];
         shell.env[key] = @insertFields(val);

-    shell.cd(@options.cwd);
+    if @options.cwd
+      @options.cwd = @options.cwd.trim();
+      if @options.cwd.length > 0
+        shell.cd(@options.cwd);

     pathToShell = atom.config.get("process-palette.shell");

@@ -288,9 +291,12 @@
       if pathToShell.length > 0
         execOptions["shell"] = pathToShell;

-    @process = shell.exec @fields.fullCommand, execOptions, (code) =>
-      @fields.exitStatus = code;
-      @processStopped(!code?);
+    try
+      @process = shell.exec @fields.fullCommand, execOptions, (code) =>
+        @fields.exitStatus = code;
+        @processStopped(!code?);
+    catch e
+      console.log(e);

     if !@process?
       notifOptions = {};


TOUCH IT #5

TOUCH IT #5

--- lib/app-list.coffee
+++ lib/app-list.coffee
@@ -45,7 +45,7 @@

   touch: (e) =>
     console.log "TOUCH"
-    console.log e
+    console.log e.diff
     @dom.css 'right',"#{event.diff.right}px"


Remove migration of old config setting

Remove migration of old config setting

--- lib/autosave.coffee
+++ lib/autosave.coffee
@@ -4,9 +4,7 @@
   configDefaults:
     enabled: false

-  activate: (state) ->
-    @migrateOldAutosaveConfig()
-
+  activate: ->
     atom.workspaceView.on 'focusout', ".editor:not(.mini)", (event) =>
       editor = event.targetView()?.getModel()
       @autosave(editor)
@@ -21,10 +19,3 @@
   autosave: (paneItem) ->
     if atom.config.get('autosave.enabled') and paneItem?.getUri?()?
       paneItem?.save?()
-
-  migrateOldAutosaveConfig: ->
-    enabled = atom.config.get('core.autosave')
-    return unless enabled?
-
-    atom.config.set('autosave.enabled', enabled)
-    atom.config.set('core.autosave', null)


Take prefix exception from Can I Use

Take prefix exception from Can I Use

--- lib/browsers.coffee
+++ lib/browsers.coffee
@@ -37,13 +37,10 @@
   # Return prefix for selected browser
   prefix: (browser) ->
     [name, version] = browser.split(' ')
-    version = parseFloat(version)
-    if name == 'opera' and version < 15
-      '-o-'
-    else if name == 'op_mob' and version >= 15
-      '-webkit-'
-    else
-      '-' + @data[name].prefix + '-'
+    data     = @data[name]
+    prefix   = data.prefix_exceptions[version] if data.prefix_exceptions
+    prefix ||= data.prefix
+    '-' + prefix + '-'

   # Is browser is selected by requirements
   isSelected: (browser) ->


Use fat arrow instead of .bind(@)

Use fat arrow instead of .bind(@)

--- lib/commands.coffee
+++ lib/commands.coffee
@@ -6,7 +6,7 @@
     @_subscriptions.add atom.commands.add 'atom-workspace',
       'linter:next-error': @nextError.bind(@)
       'linter:toggle': @toggleLinter.bind(@)
-      'linter:set-bubble-transparent': @setBubleTransparent.bind(@)
+      'linter:set-bubble-transparent': @setBubleTransparent

     # Default values
     @_messages = null
@@ -16,7 +16,7 @@
     return unless activeEditorLinter
     activeEditorLinter.toggleStatus()

-  setBubleTransparent: ->
+  setBubleTransparent: =>
     @linter.views.setBubbleTransparent()

   nextError: ->


the version of escodegen we're using doesn't support ES6 classes yet, so we need to disable the pre-class-desugaring output

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -1817,8 +1817,8 @@

         toplevel_name = tree.body[0].id.name

-        debug.log 1, "before closure conversion"
-        debug.log 1, -> escodegen.generate tree
+        #debug.log 1, "before closure conversion"
+        #debug.log 1, -> escodegen.generate tree

         tree = closure_conversion.convert tree, path.basename source_filename


remove a bunch of old commented code, and factor out the push/visit/pop scope stuff.

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -69,6 +69,11 @@
         popScope: ->
                 @current_scope = @current_scope[PARENT_SCOPE_KEY]

+        visitWithScope: (scope, children) ->
+                @pushScope scope
+                @visit child for child in children
+                @popScope()
+
         createAlloca: (func, type, name) ->
                 saved_insert_point = llvm.IRBuilder.getInsertBlock()
                 llvm.IRBuilder.setInsertPoint func.entry_bb
@@ -97,54 +102,14 @@
             allocas

         visitProgram: (n) ->
-#                 funcs = []
-#                 nonfunc = []
-
-#                 for child in n.body
-#                         if child.type is syntax.FunctionDeclaration
-#                                 funcs.push child
-#                         else
-#                                nonfunc.push child
-
-                # generate the IR for all the functions
+                # by the time we make it here the program has been
+                # transformed so that there is nothing at the toplevel
+                # but function declarations.
                 @visit func for func in n.body
-
-#                 ir_func = @currentFunction
-#                 ir_args = ir_func.args
-#                 if top?
-#                         # XXX this block needs reworking to mirror what happens in visitFunction (particularly the distinction between entry/body_bb)
-#                         @currentFunction = ir_func
-#                         # Create a new basic block to start insertion into.
-#                         entry_bb = new llvm.BasicBlock "entry", ir_func
-#                         llvm.IRBuilder.setInsertPoint entry_bb
-#                         ir_func.entry_bb = entry_bb
-#                         new_scope = {}
-#                         ir_func.topScope = new_scope
-#                         @current_scope = new_scope
-
-#                 allocas = []
-#                 for i in [0..BUILTIN_ARGS.length-1]
-#                         ir_args[i].setName BUILTIN_ARGS[i].name
-#                         allocas[i] = llvm.IRBuilder.createAlloca EjsValueType, "local_#{BUILTIN_ARGS[i].name}"
-#                         new_scope[BUILTIN_ARGS[i].name] = allocas[i]
-#                         llvm.IRBuilder.createStore ir_args[i], allocas[i]
-
-#                 for i in [0..nonfunc.length-1]
-#                         ir = @visit nonfunc[i]
-
-#                 ir_func

         visitBlock: (n) ->
                 new_scope = {}
-
-                @pushScope new_scope
-
-                debug.log "hi22"
-                @visit statement for statement in n.body
-                debug.log "hi23"
-
-                @popScope()
-
+                @visitWithScope new_scope, n.body
                 n

         visitIf: (n) ->
@@ -301,11 +266,7 @@
                 body_bb = new llvm.BasicBlock "body", ir_func
                 llvm.IRBuilder.setInsertPoint body_bb

-                @pushScope new_scope
-
-                @visit n.body
-
-                @popScope()
+                @visitWithScope new_scope, [n.body]

                 # XXX more needed here - this lacks all sorts of control flow stuff.
                 # Finish off the function.


Changed the path for requests

Changed the path for requests

--- app/hello.coffee
+++ app/hello.coffee
@@ -17,8 +17,9 @@
 app.get( "/", ( req, res ) ->
 	res.render( 'home', {title: "TESTAN"} ) )

-app.post( "/recommend", ( req, res ) ->
-	)
+app.get( "/url/", ( req, res ) ->
+	 )
+

 server = app.listen( 9001, ->
 	host = server.address().address


Assign pt-BR or zh-HANS for those domains

--- server/routes/auth.coffee
+++ server/routes/auth.coffee
@@ -202,6 +202,8 @@
   user.set 'testGroupNumber', Math.floor(Math.random() * 256)  # also in app/core/auth
   lang = languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
   user.set 'preferredLanguage', lang if lang[...2] isnt 'en'
+  user.set 'preferredLanguage', 'pt-BR' if not user.get('preferredLanguage') and /br\.codecombat\.com/.test(req.get('host'))
+  user.set 'preferredLanguage', 'zh-HANS' if not user.get('preferredLanguage') and /cn\.codecombat\.com/.test(req.get('host'))
   user.set 'lastIP', (req.headers['x-forwarded-for'] or req.connection.remoteAddress)?.split(/,? /)[0]
   user.set 'chinaVersion', true if req.chinaVersion
   #log.info "making new user #{user.get('_id')} with language #{user.get('preferredLanguage')} of #{req.acceptedLanguages} and chinaVersion #{req.chinaVersion} on #{if config.tokyo then 'Tokyo' else 'US'} server and lastIP #{req.connection.remoteAddress}."


remove syncrandom

remove syncrandom

--- server/routes/user.coffee
+++ server/routes/user.coffee
@@ -16,7 +16,6 @@
 Array::getRandomElement = -> this[Math.floor(Math.random() * this.length)]

 router.get '/', (req, res, next) ->
-  Day.syncRandom -> null
   bootstrapData (err, years, shows, songs) ->

     unless years && shows && songs


rewrote view_engine better

rewrote view_engine better

--- server/view_engine.coffee
+++ server/view_engine.coffee
@@ -2,49 +2,42 @@
 fs = require('fs')
 BaseView = require('../shared/base/view')

-layout_path = "#{rendr.entryPath}/templates/layout.hbs"
-cache = {}
+layoutPath = "#{rendr.entryPath}/templates/layout.hbs"

 fetcher = require('../shared/fetcher')

-module.exports = (view_path, data, callback) ->
+module.exports = (viewPath, data, callback) ->
   data.locals ||= {}

-  viewLocals = _.clone data.locals
+  layoutData =
+    body: getViewHtml(viewPath, data.locals, data.app)
+    bootstrappedData: getBootstrappedData(data.locals)
+    _app: data.app

+  renderWithLayout(layoutData, callback)
+
+# render with a layout
+renderWithLayout = (locals, cb) ->
+  fs.readFile layoutPath, 'utf8', (err, str) ->
+    return cb(err) if (err)
+
+    templateFn = Handlebars.compile(str)
+    html = templateFn(locals)
+    cb(null, html)
+
+getViewHtml = (viewPath, locals, app) ->
+  locals = _.clone locals
   # Pass in the app.
-  viewLocals.app = data.app
+  locals.app = app

-  bootstrappedLocals = _.clone data.locals
+  name = path.basename(viewPath).replace('.coffee', '')
+  View = BaseView.getView(name)
+  view = new View(locals)
+  view.getHtml()

-  view_key = path.basename(view_path).replace('.coffee', '')
-  View = BaseView.getView(view_key)
-  view = new View(viewLocals)
-  view_html = view.getHtml()
-
+getBootstrappedData = (locals) ->
   bootstrappedData = {}
-  for own name, modelOrCollection of bootstrappedLocals
+  for own name, modelOrCollection of locals
     bootstrappedData[name] =
       summary: fetcher.summarize(modelOrCollection)
       data: modelOrCollection.toJSON()
-
-  layoutData =
-    body: view_html
-    bootstrappedData: JSON.stringify(bootstrappedData)
-    globalConfig: JSON.stringify(data.req.globalConfig)
-    currentUser: JSON.stringify(data.req.currentUser)
-    _app: data.app
-
-  render_with_layout(layoutData, callback)
-
-  # render with a layout
-render_with_layout = (locals, cb) ->
-  fs.readFile layout_path, 'utf8', (err, str) ->
-    return cb(err) if (err)
-
-    layout_template = Handlebars.compile(str)
-    cache[layout_path] = layout_template
-
-    html = layout_template(locals)
-    cb(null, html)
-