Import declaration

Import declaration

Signed-off-by: ysgard <125fa00567d70184e4d0dc6a16b66d7bf20d7d44@gmail.com>

--- grammars/d.cson
+++ grammars/d.cson
@@ -578,20 +578,6 @@
       ]
     },
     #
-    # Mixins
-    #
-    'mixin-declaration': {
-      'patterns': [
-        'begin': 'mixin\\s*\\(',
-        'end': '\\)',
-        'beginCaptures': { '0': { 'name': 'keyword.mixin.begin.d' } },
-        'endCaptures': { '0': { 'name': 'keyword.mixin.end.d' } },
-        'patterns': [
-          { 'include': '#expression' }
-        ]
-      ]
-    },
-    #
     # Static assert
     #
     'static-assert': {
@@ -665,14 +651,7 @@
         ]
       ]
     },
-    'import-declaration': {
-      'patterns': [
-        {
-          'match': '(static\\s+)?import\\s+',
-          'name': 'keyword.package.import.d'
-        }
-      ]
-    },
+
     #
     # Preprocessor and other directives
     #
@@ -1296,10 +1275,95 @@
       ]
     },
     #
-    # Miscellaneous
-    #
-
-
+    # Import Declarations
+    #
+    'import-declaration': {
+      'patterns': [
+        {
+          'match': '(static\\s+)?import\\s+',
+          'name': 'keyword.package.import.d'
+        },
+        {
+          '#include': '#import-list'
+        }
+      ]
+    },
+    'import-list': {
+      'patterns': [
+        { 'include': '#import' },
+        { 'include': '#import-bindings' },
+        { 'include': '#comma' },
+      ]
+    },
+    'import': {
+      'patterns': [
+        { 'include': '#module-fully-qualified-name' },
+        { 'include': '#module-alias-identifier' },
+        { 'include': '#equal' }
+      ]
+    },
+    'import-bindings': {
+      'patterns': [
+        { 'include': '#import' },
+        { 'include': '#colon' },
+        { 'include': '#import-bind-list' }
+      ]
+    },
+    'import-bind-list': {
+      'patterns': [
+        { 'include': '#comma' },
+        { 'include': '#import' }
+      ]
+    },
+    'import-bind': {
+      'patterns': [
+        { 'include': '#identifier' },
+        {
+          'match': '=',
+          'name': 'keyword.operator.equal.d'
+        }
+      ]
+    },
+    'module-alias-identifier': {
+      'patterns': [
+        { 'include': '#identifier' }
+      ]
+    }
+    #
+    # Mixin Decl
+    #
+    'mixin-declaration': {
+      'patterns': [
+        'begin': 'mixin\\s*\\(',
+        'end': '\\)',
+        'beginCaptures': { '0': { 'name': 'keyword.mixin.begin.d' } },
+        'endCaptures': { '0': { 'name': 'keyword.mixin.end.d' } },
+        'patterns': [
+          { 'include': '#expression' }
+        ]
+      ]
+    },
+    #
+    # Misc
+    #
+    'comma': {
+      'patterns': [
+        'match': ',',
+        'name': 'keyword.operator.comma.d'
+      ]
+    },
+    'colon': {
+      'patterns': [
+        'match': ':',
+        'name': 'support.type.colon.d'
+      ]
+    },
+    'equal': {
+      'patterns': [
+        'match': '=',
+        'name': 'keyword.operator.equal.d'
+      ]
+    },

     ###########################################################################
     #


add timeout wrapper for the worker function

--- src/index.coffee
+++ src/index.coffee
@@ -6,7 +6,7 @@
 class Connector extends EventEmitter
   constructor: ({@Lync}) ->
     @Lync ?= require './lync-manager'
-    @worker = async.queue @_handleDesiredState, 1
+    @worker = async.queue async.timeout(@_handleDesiredState, 5000), 1

   start: (device, callback) =>
     @onConfig device, (error) =>


fix redirect

fix redirect

--- src/index.coffee
+++ src/index.coffee
@@ -67,7 +67,7 @@
     req = request opts, (res) ->
         statusCode = res.statusCode

-        if (statusCode in [302, 301]) and res.headers.location and (opts.method is 'GET')
+        if res.headers.location and (opts.method is 'GET')
             delete opts.host
             delete opts.hostname
             delete opts.port


* [bug] smart to judge the aOptions whether is a string or a task name

--- src/index.coffee
+++ src/index.coffee
@@ -61,9 +61,12 @@
       done err, result
   executeSync: (aOptions, aName)->
     if arguments.length is 1 and isString aOptions
-      aName = aOptions
-      aOptions = null
-    vTask = if aName then @get(aName) else @
+      vTask = @get aOptions
+      if vTask
+        aName = aOptions
+        aOptions = null
+    unless vTask
+      vTask = if aName then @get(aName) else @
     aOptions = vTask.mergeTo(aOptions) if !aOptions? or typeof aOptions == 'object'
     vTask._executeSync(aOptions)
   execute: (aOptions, aName, done)->
@@ -73,12 +76,15 @@
     else if arguments.length is 2
       done = aName
       if isString aOptions
-        aName = aOptions
-        aOptions = null
+        vTask = @get aOptions
+        if vTask
+          aName = aOptions
+          aOptions = null
       else
         aName = null

-    vTask = if aName then @get(aName) else @
+    unless vTask
+      vTask = if aName then @get(aName) else @
     aOptions = vTask.mergeTo(aOptions) if !aOptions? or typeof aOptions == 'object'
     vTask._execute(aOptions, done)
   _inspect: (debug, aOptions)->


Support dependencies.

Support dependencies.

--- src/index.coffee
+++ src/index.coffee
@@ -17,9 +17,13 @@
 tasks = {}

 # our Task takes an optional callback to signal when a task is completed
-global.task = (name, description, action) ->
+global.task = (name, description, deps, action) ->
+  # Alternately support deps as an extra argument
+  unless typeof action is 'function'
+    [action, deps] = [deps, []]
+
   # store reference for ourselves
-  tasks[name] = {action, description, name}
+  tasks[name] = {name, description, deps, action}

   # make sure original plumbing still works, inject our shim task
   cakeTask name, description, (options) ->
@@ -31,20 +35,21 @@
   # Call original invoke to set options for our task.
   cakeInvoke name

-  {action, options} = tasks[name]
+  {action, deps, options} = tasks[name]

-  # If task's action expects two arguments order is (options, callback).
-  if action.length == 2
-    action options, cb
+  invokeSerial deps, ->
+    # If task's action expects two arguments order is (options, callback).
+    if action.length == 2
+      action options, cb

-  # If task's action expects a single argument named callback, cb, or done, or
-  # next it expects (callback) and no options object.
-  else if /^function \((callback|cb|done|next)\)/.test action.toString()
-    action cb
+    # If task's action expects a single argument named callback, cb, or done, or
+    # next it expects (callback) and no options object.
+    else if /^function \((callback|cb|done|next)\)/.test action.toString()
+      action cb

-  # Unspecified, or expects (options).
-  else
-    cb action options
+    # Unspecified, or expects (options).
+    else
+      cb action options

 # Invoke tasks in serial
 invokeSerial = (tasks, cb) ->


change parsed format

change parsed format

--- src/index.coffee
+++ src/index.coffee
@@ -1,29 +1,38 @@
 parseEmptyElement = (s) ->
   if s.match(/^<\/\S+$/)
-    name = s.substring(2)
-    [name, 'empty element', null, ->
+    name: s.substring 2
+    type: 'empty element'
+    value: null
+    write: ->
       attributes = (" #{k}=\"#{v}\"" for k, v of @attributes).join ''
-      '<' + name + attributes + ' />'
-    ]
+      '<' + @name + attributes + ' />'

 parseElement = (s) ->
   if s.match(/^<\S+$/)
-    name = s.substring(1)
-    [name, 'element', null, ->
+    name: s.substring 1
+    type: 'element'
+    value: null
+    write: ->
       attributes = (" #{k}=\"#{v}\"" for k, v of @attributes).join ''
       children = @children.map((i) -> i.write()).join('')
-      '<' + name + attributes + '>' + children + '</' + name + '>'
-    ]
+      '<' + @name + attributes + '>' + children + '</' + @name + '>'

 parseAttribute = (s) ->
   m = s.match(/^@(\S+)\s+(.+)$/)
   if m
-    [m[1], 'attribute', m[2], null]
+    name: m[1]
+    type: 'attribute'
+    value: m[2]
+    write: ->
+      throw new Error()

 parseText = (s) ->
   if s.match(/^>.+$/)
-    name = s.substring(1)
-    [name, 'text', null, -> name]
+    name: s.substring(1)
+    type: 'text'
+    value: null
+    write: ->
+      @name

 class Node
   constructor: ({ @level, @parsed, @name, @type, @value }) ->
@@ -43,12 +52,15 @@
       parseAttribute
       parseText
       (s) ->
-        [s, 'text', null, -> s]
+        name: s
+        type: 'text'
+        value: null
+        write: -> s
     ].some (f) ->
       parsed = f node
     new Node {
       level, parsed,
-      name: parsed[0], type: parsed[1], value: parsed[2]
+      name: parsed.name, type: parsed.type, value: parsed.value
     }

   setAttribute: (name, value) ->
@@ -59,7 +71,7 @@
     n.parent = @

   write: ->
-    @parsed[3].apply @, []
+    @parsed.write.apply @, []

 parse = (s) ->
   root = Node.parse '<root'


HelpPage fix, now with client.coffee

HelpPage fix, now with client.coffee

--- client.coffee
+++ client.coffee
@@ -81,7 +81,7 @@
 			if page == 'main'
 				mainContent()
 			else if page == 'help'
-				helpContent(false)
+				helpContent()
 			else if page == 'scores'
 				scoresContent()
 			else if page == 'log'
@@ -98,7 +98,7 @@
 			if page == 'main'
 				endGameContent()
 			else if page == 'help'
-				helpContent(false)
+				helpContent()
 			else if page == 'scores'
 				scoresContent()
 			else if page == 'log'
@@ -119,14 +119,18 @@
 				else
 					log 'Did not get deviceId from server'

+
+exports.renderInfo = !->
+	helpContent()
+
 # Method that is called when admin changes settings (only restart game for now)
 exports.renderSettings = !->
 	if Db.shared
 		Form.check
 			name: 'restart'
 			text: tr 'Restart'
-			sub: tr 'Check this to destroy the current game and start a new one.'
-
+			sub: tr 'Check this to destroy the current game and start a new one.'
+
 # ========== Content fuctions ==========
 addBar = ->
 	Dom.div !->
@@ -137,7 +141,8 @@
 			boxShadow: "0 3px 10px 0 rgba(0, 0, 0, 1)"
 			backgroundColor: hexToRGBA(Db.shared.peek('colors', getTeamOfUser(Plugin.userId()), 'hex'), 0.9)
 			_textShadow: '0 0 5px #000000, 0 0 5px #000000'
-        # Button to help page
+        # Removed help button moved to top of page, should be rewritten to history tab
+		###
 		Dom.div !->
 			Icon.render data: 'info', color: '#fff', size: 30, style: {verticalAlign: 'middle'}
 			Dom.div !->
@@ -146,6 +151,7 @@
 			Dom.cls 'bar-button'
 			Dom.onTap !->
 				Page.nav 'help'
+		###
         # Button to event log
 		Dom.div !->
 			Icon.render data: 'clipboard', color: '#fff', size: 30, style: {verticalAlign: 'middle'}
@@ -444,13 +450,9 @@
 						renderTimeButton 'Days'
 						renderTimeButton 'Months'
 				# Gameinfo setup page:
-				expanded = Obs.create(false)
-				Dom.div !->
-					Dom.style margin: '-8px'
-					Ui.button tr('Game information'), !->
-						expanded.set(!expanded.get())
-					if expanded.get()
-						helpContent(true)
+				Dom.div !->
+					Dom.h2 "Game information"
+					Dom.text "For game information, click the gear or information icon on the top of the page"
 		else if currentPage is 'setup1' # Setup map boundaries
 			# Bar to indicate the setup progress
 			Dom.div !->
@@ -605,39 +607,31 @@
 	renderMap()
 	renderBeacons()
 # Help page
-helpContent = (setup)->
-	Dom.div !->
-		Dom.cls 'container'
-		if(!setup)
-			Dom.h1 "Game information"
-			Dom.text "On this page you find all the information about the game! "
-			Dom.br()
-			Dom.br()
-		Dom.h2 "General info"
-		if(!setup)
-			Dom.text "You are playing this game with " + Plugin.users.count().get() + " users that are randomly divided over " + Db.shared.peek('game','numberOfTeams') + " teams"
-			Dom.br()
-			Dom.br()
-		Dom.text "On the main map there are several beacons. You need to venture to the real location of a beacon to conquer it. "
-		Dom.text "When you get in range of the beacon, you'll automatically start to conquer it. "
-		Dom.text "When the bar at the top of your screen has been filled with your team color, you've conquered the beacon. "
-		Dom.text "A neutral beacon will take 30 seconds to conquer, but an occupied beacon will take one minute. You first need to drain the opponents' color, before you can fill it with yours! "
-		Dom.br()
-		Dom.br()
-		Dom.h2 "Rules"
-		Dom.text "You gain 10 points for being the first team to conquer a certain beacon. "
-		Dom.text "Beacons that are in possession of your team, will gain a circle around it in your team color! "
-		Dom.text "Every hour the beacon is in your posession, it will generate a number of points. "
-		Dom.text "Unfortunately for you, your beacons can be conquered by other teams. "
-		Dom.text "Every time a beacon is conquered the value of the beacon will drop. Scores for conquering a beacon will drop from 10 to 9, 8 all the way to 1. "
-		Dom.text "The team with the highest score at the end of the game wins. "
-		Dom.br()
-		Dom.br()
-		Dom.h2 "Use of Map & Tabs"
-		Dom.text "To find locations of beacons you can navigate over the map by swiping. To obtain a more precise location you can zoom in and out by pinching. "
-		Dom.br()
-		Dom.text "The score tab (that you can reach from the main screen) shows all individual and team scores. The Event Log tab shows all actions that have happened during the game (E.G. conquering a beacon). "
-		Dom.text "This way you can keep track of what is going on in the game and how certain teams or individuals are performing. "
+helpContent = ()->
+	Dom.text "On this page you find all the information about the game! "
+	Dom.br()
+	Dom.br()
+	Dom.h2 "General info"
+	Dom.text "On the main map there are several beacons. You need to venture to the real location of a beacon to conquer it. "
+	Dom.text "When you get in range of the beacon, you'll automatically start to conquer it. "
+	Dom.text "When the bar at the top of your screen has been filled with your team color, you've conquered the beacon. "
+	Dom.text "A neutral beacon will take 30 seconds to conquer, but an occupied beacon will take one minute. You first need to drain the opponents' color, before you can fill it with yours! "
+	Dom.br()
+	Dom.br()
+	Dom.h2 "Rules"
+	Dom.text "You gain 10 points for being the first team to conquer a certain beacon. "
+	Dom.text "Beacons that are in possession of your team, will gain a circle around it in your team color! "
+	Dom.text "Every hour the beacon is in your posession, it will generate a number of points. "
+	Dom.text "Unfortunately for you, your beacons can be conquered by other teams. "
+	Dom.text "Every time a beacon is conquered the value of the beacon will drop. Scores for conquering a beacon will drop from 10 to 9, 8 all the way to 1. "
+	Dom.text "The team with the highest score at the end of the game wins. "
+	Dom.br()
+	Dom.br()
+	Dom.h2 "Use of Map & Tabs"
+	Dom.text "To find locations of beacons you can navigate over the map by swiping. To obtain a more precise location you can zoom in and out by pinching. "
+	Dom.br()
+	Dom.text "The score tab (that you can reach from the main screen) shows all individual and team scores. The Event Log tab shows all actions that have happened during the game (E.G. conquering a beacon). "
+	Dom.text "This way you can keep track of what is going on in the game and how certain teams or individuals are performing. "
 # Scores page
 scoresContent = ->
 	#position = 0


Adds a default value to a module function

--- src/client/component/datasource.coffee
+++ src/client/component/datasource.coffee
@@ -11,7 +11,7 @@
       console.warn "An error occurred with datasource #{datasourceName}", err.status, err.statusText

     # Adds datasources to the instance of the component
-    addDatasources: (datasources)->
+    addDatasources: (datasources={})->
       _builtDs = _.map datasources, (ds, i)=>
         ds = _.bind ds, @ if _.isFunction ds
         ds = new module.datasourceModel(ds, @api) unless ds instanceof module.datasourceModel
@@ -41,7 +41,7 @@

       app.components.before 'initialize', (options)->
         datasources = _.extend {}, default_datasources, @datasources, options.datasources
-        module.addDatasources.call(@, datasources)
+        module.addDatasources.call @, datasources

       app.components.after 'buildContext', module.fetchDatasources


FSItem: fix promise call in `exists` method

--- client/Finder/fs/fsitem.coffee
+++ client/Finder/fs/fsitem.coffee
@@ -231,9 +231,11 @@
   isHidden:-> FSItem.isHidden @name

   exists:(callback=noop)->
-    @getKite().vmOn()
-
-    .fsExists(path: @getPath())
+    kite = @getKite()
+
+    kite.vmOn().then =>
+
+      kite.fsExists(path: @getPath())

     .nodeify(callback)


Terminal: AppView: remove vmController dependencies cont.

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -42,8 +42,6 @@
     @on 'TerminalStarted', ->
       KD.mixpanel "Open new Webterm, success"

-    # {vmController} = KD.singletons
-    # vmController.on 'vm.progress.error', => @notify cssClass : 'error'

     # @on "SessionSelected", ({vm, session}) => @createNewTab {vm, session, mode: 'resume'}
   viewAppended: ->
@@ -64,8 +62,6 @@
         location.replace path

-    # {vmController} = KD.singletons
-    # {terminalKites} = vmController

   initPane: (pane) ->


restore the (accidentally) deleted client/arbitrate/manage, and make it work with new bitcoinjs-lib

--- client/arbitrate/manage.coffee
+++ client/arbitrate/manage.coffee
@@ -0,0 +1,34 @@
+{ render, iferr, error_displayer, parse_query, format_url } = require '../lib/util.coffee'
+{ parse_key_bytes, get_address, ADDR_PRIV } = require '../../lib/bitcoin/index.coffee'
+{ load_user } = require '../lib/user.coffee'
+{ Crypto: { util: { randomBytes } } } = require 'bitcoinjs-lib'
+view = require './views/manage.jade'
+headsup_view = require './views/dialogs/heads-up.jade'
+
+display_error = error_displayer $ '.content'
+
+try
+  { key, _is_new } = parse_query()
+  { pub, priv } = parse_key_bytes key
+
+  # When loaded for the first time, display the headsup message
+  # and remove the _is_new flag from the URL
+  if _is_new then do ->
+    document.location.hash = format_url null, { _: (randomBytes 160), key }
+
+    dialog = $ headsup_view {
+      url: location.href
+      has_priv: priv?
+    }
+    dialog.on 'hidden', -> dialog.remove()
+    dialog.modal()
+
+
+  load_user pub, iferr display_error, (user) ->
+    render $ view {
+      user
+      priv: (priv? and get_address priv, ADDR_PRIV)
+      current_url: document.location.href
+    }
+
+catch e then display_error e


Disable medium-editor-insert-plugin embeds

--- client/views/admin/edit.coffee
+++ client/views/admin/edit.coffee
@@ -60,7 +60,7 @@
             processData: false
             data: that.options.formatData(file)

-      embeds: {}
+      #embeds: {}

   editor


Better way to do 30297eb

Better way to do 30297eb

--- client/views/admin/edit.coffee
+++ client/views/admin/edit.coffee
@@ -149,19 +149,22 @@

 Template.blogAdminEdit.rendered = ->

-  # Can't use reactive template vars for contenteditable :-(
-  # https://github.com/meteor/meteor/issues/1964
-
-  @autorun ->
-    Meteor.subscribe 'singlePostById', Session.get('postId')
-
-  # Load initial post body, if any
-  Meteor.setTimeout =>
-    post = getPost()
-    if post?.body
-      @$('.editable').html post.body
-      @$('.html-editor').html post.body
-  , 0
+  # We can't use reactive template vars for contenteditable :-(
+  # (https://github.com/meteor/meteor/issues/1964). So we put the single-post
+  # subscription in an autorun. If we're loading an existing post, once its
+  # ready, we populate the contents via jQquery. The catch is, we only want to
+  # run it once because when we set the contents, we lose our cursor position
+  # (re: autosave).
+  runOnce = false
+  @autorun =>
+    sub = Meteor.subscribe 'singlePostById', Session.get('postId')
+    # Load post body initially, if any
+    if sub.ready() and not runOnce
+      runOnce = true
+      post = getPost()
+      if post?.body
+        @$('.editable').html post.body
+        @$('.html-editor').html post.body

   # Tags
   @$('input[data-role="tagsinput"]').tagsinput


Lazy load images; resize to parent container width if not retina

--- apps/about2/client/view.coffee
+++ apps/about2/client/view.coffee
@@ -1,9 +1,10 @@
 _ = require 'underscore'
 Backbone = require 'backbone'
 imagesLoaded = require 'imagesloaded'
-{ isTouchDevice } = require '../../../components/util/device.coffee'
+{ isRetina, isTouchDevice } = require '../../../components/util/device.coffee'
 mediator = require '../../../lib/mediator.coffee'
 ZoomView = require '../../../components/modal/zoom.coffee'
+{ resize } = require '../../../lib/resizer.coffee'

 module.exports = class AboutView extends Backbone.View
   events:
@@ -25,6 +26,7 @@
     @setupFlipHearts()
     @setupSkylineSlideshow()
     @setupGenes()
+    @setImages()

   zoomImage: (e) ->
     e.preventDefault()
@@ -140,3 +142,15 @@
       $(this).addClass 'is-active' if direction is 'down'
       $(this).removeClass 'is-active' if direction is 'up'
     , offset: '90%'
+
+
+  setImages: ->
+    @$('img').waypoint ->
+      $img = $(this)
+      src = $img.data 'src'
+      $parent = $img.parent()
+      $parent.imagesLoaded -> $.waypoints('refresh')
+      width = $parent.width()
+      src = if isRetina() then src else resize(src, width: width)
+      $img.attr 'src', src
+    , triggerOnce: true, offset: '150%'


Fix gulp task

Fix gulp task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -7,7 +7,7 @@
 coveralls = require 'gulp-coveralls'

-gulp.task 'test', (mochaErr) ->
+gulp.task 'test', (cb) ->
   mochaErr = null

   gulp.src ['index.js', 'lib/**/*.js']
@@ -21,7 +21,10 @@
           mochaErr = err
         .pipe istanbul.writeReports()
         .on 'end', ->
+          console.log 'end'
           cb(mochaErr)
+
+  undefined

 gulp.task 'coveralls', ['test'], ->


NPM: updating versions of modules

NPM: updating versions of modules

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -17,6 +17,7 @@
     br = browserify './smsgw/static/js/app.coffee'
     br.transform 'debowerify', global: yes
     br.transform 'coffee-reactify'
+    br.transform "envify"
     br.bundle()
         .pipe source 'bundle.js'
         .pipe gulp.dest './smsgw/static/build/'


Move bump task args where belong.

Move bump task args where belong.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -36,8 +36,6 @@

 args = yargs
   .alias 's', 'stage'
-  .alias 'p', 'patch'
-  .alias 'm', 'minor'
   .argv

 paths =
@@ -328,6 +326,7 @@
   runSequence 'build', 'run', done

 gulp.task 'bump', (done) ->
+  args = yargs.alias('p', 'patch').alias('m', 'minor').argv
   type = args.major && 'major' || args.minor && 'minor' || 'patch'
   # This prevents accidental major bump.
   return if type == 'major'


fix: adapt fixing of gulpfile.js to gulpfile.coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -170,9 +170,9 @@
 });
 ###
 gulp.task 'doc',(cb)->
-    child_process.exec("typedoc --out ./ci/docs"+branch+" --module commonjs --target es5 --name jThree ./jThree/src/",cb);
+    child_process.exec("./node_modules/.bin/typedoc --out ./ci/docs"+branch+" --module commonjs --target es5 --name jThree ./jThree/src/",cb);
     undefined
-
+

   # gulp.src tsSource
   #   .pipe typedoc


stop watching bower component dir - gulp.watch can't handle too many files

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -57,5 +57,4 @@
 gulp.task 'watch', ->
   gulp.watch ['./app/client/scripts/**'], ['jsr']
   gulp.watch ['./app/client/stylesheets/**'], ['cssr']
-  gulp.watch ['./app/client/components/**'], ['cssr', 'jsr']
   gulp.watch ['./app/views/**'], ['viewsr']


gulpfile

gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -75,9 +75,9 @@
 #****************************
 gulp.task "jade", ->
   YOUR_LOCALS = {}
-  gulp.src(pkg.settings.app+"/jade/*.jade")
-  .pipe($.plumber({errorHandler: $.notify.onError('<%= error.message %>')}))
-  .pipe($.data((file)->
+  gulp.src pkg.settings.app+"/jade/*.jade"
+  .pipe $.plumber({errorHandler: $.notify.onError('<%= error.message %>')})
+  .pipe $.data (file)->
     if file
       reg = /\/([A-Za-z_0-9]+?)\.jade/
       path = file.path.match(reg)[1]
@@ -88,10 +88,10 @@
         return {"title":data[path]["title"],"keyword":data[path]["keyword"],"disc":data[path]["disc"],"path":path}
       else
         return {"title":null,"keyword":null,"desc":null}
-  ))
-  .pipe(jade(
-    pretty: true
-  )).pipe gulp.dest(pkg.settings.dist)
+
+  .pipe(jade(pretty: true))
+  .pipe gulp.dest pkg.settings.dist
+  .on 'end' , reload

@@ -111,6 +111,7 @@
   # .pipe $.uglify()
   .pipe gulp.dest pkg.settings.dist+"js/" # Output directory
   .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/js/'
+  .on 'end' , reload

 # VanillaJS
@@ -123,6 +124,7 @@
   .pipe buffer()
   # .pipe $.uglify()
   .pipe gulp.dest pkg.settings.dist+"js/" # Output directory
+  .on 'end' , reload

@@ -144,9 +146,10 @@
     "rem": false,
   )
   .pipe gulp.dest pkg.settings.dist+"style"
-  .pipe $.header cssbanner, pkg : pkg
-  .pipe replace("../", "./")
-  .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name
+  # .pipe $.header cssbanner, pkg : pkg
+  # .pipe replace("../", "./")
+  # .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name
+  .on 'end' , reload

 #stylus
@@ -158,6 +161,7 @@
     compress: true
   .pipe $.pleeease()
   .pipe gulp.dest pkg.settings.dist+"style"
+  .on 'end' , reload

@@ -194,7 +198,7 @@
     }))
     .pipe gulp.dest pkg.settings.dist+'images'
     .pipe gulp.dest pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/images'
-    .pipe($.size({title: 'images'}));
+    .pipe $.size({title: 'images'})

 gulp.task "clear", (i_done) ->
   return $.cache.clearAll(i_done);
@@ -322,12 +326,7 @@
   gulp.watch "app/**/*.js", ["js"]
   gulp.watch "app/**/*.coffee", ["script"]

-
-  gulp.watch "dist/**/*.html", reload
-  gulp.watch "dist/**/*.css", reload
-  gulp.watch "dist/**/*.js", reload
   return
-

 # watch
@@ -340,10 +339,6 @@

   gulp.watch "app/**/*.jade", ["jade"]
-
-  # gulp.watch('app/**/*.jade', reload);
-  # gulp.watch('app/**/*.scss', reload);
-
   # gulp.watch "app/**/*.scss", ['styleguide']
   gulp.watch "app/**/*.scss", ["scss"]
   gulp.watch "app/**/*.js", ["js"]
@@ -353,4 +348,5 @@
   gulp.watch pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/**/*.php', reload
   gulp.watch pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/**/*.css', reload
   gulp.watch pkg.name+'/www/wordpress/wp-content/themes/'+pkg.name+'/**/*.js', reload
+
   return


fixes #457, cleaned up gulpfile

fixes #457, cleaned up gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,7 +1,6 @@
 gulp         = require("gulp")
 coffee       = require("gulp-coffee")
 less         = require("gulp-less")
-bump         = require("gulp-bump")
 clean        = require("gulp-clean")
 watch        = require("gulp-watch")
 exec         = require("gulp-exec")
@@ -19,12 +18,10 @@
     css : "app/assets/stylesheets/main.less"
     css_watch : "app/assets/stylesheets/**/*.less"
     js : "app/assets/javascripts/**/*.{coffee,js}"
-    version : "#{__dirname}/version"
   dest :
     js_tmp : "public/javascripts_tmp"
     js : "public/javascripts"
     css : "public/stylesheets"
-    version : "./{bower,package}.json"

 logger = ->
@@ -61,16 +58,6 @@
     )
     .pipe(gulp.dest(dest))
     .pipe(logger())
-
-
-bumpVersion = (src) ->
-  return src.on("data", (versionFile) ->
-    versionString = versionFile.contents.toString("utf8")
-    gulp.src(paths.dest.version)
-      .pipe(bump(version : versionString))
-      .pipe(gulp.dest("./"))
-      .pipe(logger())
-  )

@@ -120,18 +107,10 @@
   )
 )

-gulp.task("watch:version", ->
-  return bumpVersion(watch(glob : paths.src.version, name : "Version-Watcher"))
-)
-
-
 gulp.task("build:scripts", (callback) ->
   runSequence("compile:scripts:production", "combine:scripts:production", "clean:tmp", callback)
 )
 gulp.task("build:styles", ["compile:styles"])
-gulp.task("build:version", ->
-  return bumpVersion(gulp.src(paths.src.version))
-)

 gulp.task("build", (callback) ->
   runSequence(["install:bower", "clean:build"], ["build:scripts", "build:styles"], callback)
@@ -141,7 +120,6 @@

 gulp.task("debug:scripts", ["watch:scripts:development"])
 gulp.task("debug:styles", ["compile:styles", "watch:styles"])
-gulp.task("debug:version", ["watch:version"])

 gulp.task("debug", (callback) ->
   runSequence(["install:bower", "clean:build"], ["debug:scripts", "debug:styles"], callback)


Put back publishing

Put back publishing

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -84,9 +84,9 @@
   # queue.defer (callback) -> Async.series [testNode, testBrowsers], callback
   queue.defer (callback) -> copyLibraryFiles('packages/npm', ['component.json', 'bower.json'], callback)
   queue.defer (callback) -> copyLibraryFiles('packages/nuget/Content/Scripts', [], callback)
-  # queue.defer (callback) ->
-  #   gulp.src('packages/nuget/*.nuspec')
-  #     .pipe(nugetGulp())
-  #     .on('end', callback)
+  queue.defer (callback) ->
+    gulp.src('packages/nuget/*.nuspec')
+      .pipe(nugetGulp())
+      .on('end', callback)
   queue.await (err) -> process.exit(if err then 1 else 0)
   return # promises workaround: https://github.com/gulpjs/gulp/issues/455


bugfix: stop infinite pagination when the response is empty

solves #13

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -36,7 +36,11 @@
       else
         '/'+$scope.ponyExpressVersion

-    fetchAllPaginated = (field, action, page = 1, limit = 50)->
+    isEmptyArray = (x) ->
+      x instanceof Array and x.length == 0
+
+    fetchAllPaginated = (field, action, stop_when = isEmptyArray,
+        page = 1, limit = 50)->
       # construct a query for pagination
       # page == 0 means that we try to fetch without pagination
       #   (usually last attempt)
@@ -45,15 +49,17 @@
       Restangular.all(field).getList(query).then(
         # success handling
         (data) ->
-          console.log "fetched #{field}"
+          console.log "fetched #{field} (page = #{page}, limit = #{limit})"
           action(data)
           # if we fetched a valid page (page > 0) and got a result
           # then try fetching the next page
-          fetchAllPaginated(field, action, page + 1, limit) if page > 0
+          if page > 0 and not stop_when(data)
+            fetchAllPaginated(field, action, stop_when, page + 1, limit)
         # error handling
         , () ->
           # if we got an error on the first fetch, try again without pagination
-          return fetchAllPaginated(field, action, 0, limit) if page is 1
+          if page is 1
+            return fetchAllPaginated(field, action, stop_when, 0, limit)
           # otherwise we have a fetch error
           fetchError("fetch #{field} on page #{page}, limit #{limit}")
         )


やっとわかった

やっとわかった

--- scripts/line-beacon-listener.coffee
+++ scripts/line-beacon-listener.coffee
@@ -1,4 +1,3 @@
-
 module.exports = (robot) ->

   robot.router.post "/line-becon", (req, res) ->
@@ -7,6 +6,7 @@
       return

     data = if req.body.payload? then JSON.parse req.body.payload else req.body
-    robot.logger.error data.events.type
+    robot.logger.error data
+    robot.logger.error data.events[0].type
     robot.send {room: "C4LEAQHPW"}, "ビーコンに反応あり"
     res.end ""


Fix rendering of Line to svg

Fix rendering of Line to svg

The closing quote of the stroke attribute for the Line element had no space between it as the next element, stroke-width.  This was causing the SVG to be malformed.

This commit fixes the spacing between the stroke and stroke-width attributes when rendering a Line.

--- src/core/svgRenderer.coffee
+++ src/core/svgRenderer.coffee
@@ -95,7 +95,7 @@
       <line x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}'
         #{dashString}
         stroke-linecap='#{shape.capStyle}'
-        stroke='#{shape.color} 'stroke-width='#{shape.strokeWidth}' />
+        stroke='#{shape.color}' stroke-width='#{shape.strokeWidth}' />
       #{capString}
     </g>
   "
@@ -178,4 +178,4 @@
   "

-module.exports = {defineSVGRenderer, renderShapeToSVG}+module.exports = {defineSVGRenderer, renderShapeToSVG}


check that role isn't empty; fix #284

check that role isn't empty; fix #284

--- both/methods/user/userPermissions.coffee
+++ both/methods/user/userPermissions.coffee
@@ -25,7 +25,8 @@

 		if oldUser? && newUser?
 			for id in Object.keys(oldUser.roles)
-				Roles.addUsersToRoles newUserId, oldUser.roles[id][0], id
+				if oldUser.roles[id][0]
+					Roles.addUsersToRoles newUserId, oldUser.roles[id][0], id

 			Meteor.users.remove oldUser._id


Show message in history directly when current history has higher priority

--- views/components/info/alert.cjsx
+++ views/components/info/alert.cjsx
@@ -59,23 +59,30 @@
     thisPriority = e.detail.priority || 0
     update = !@stickyEnd || @stickyEnd < (new Date).getTime()
     update = update || !@stickyPriority || @stickyPriority <= thisPriority
+    if !@dontReserve
+      history = @state.history
+      history.push
{@lastMessage}

+      if history.length > 5 then history.shift()
+      @setState {history}
+    @lastMessage = e.detail.message
+    @dontReserve = e.detail.dontReserve
     if (update)
       @stickyPriority = thisPriority
       if e.detail.stickyFor
         @stickyEnd = (new Date).getTime() + e.detail.stickyFor
       else
         @stickyEnd = null
-      if !@dontReserve
-        history = @state.history
-        history.push
{@messageOld}

-        if history.length > 5 then history.shift()
-        @setState {history}
       @message = e.detail.message
       @messageOld = @message
-      @dontReserve = e.detail.dontReserve
       @messageType = e.detail.type
       @updateAlert()
       @handleThemeChange()
+    else if !@dontReserve
+      history = @state.history
+      history.push
{@lastMessage}

+      if history.length > 5 then history.shift()
+      @setState {history}
+      @dontReserve = true

   toggleHistory: ->
     @setState


remove debounce to solve the issue with typeahead being stuck

--- client/controllers/locationSelect2.coffee
+++ client/controllers/locationSelect2.coffee
@@ -21,7 +21,7 @@
       data.push { id: loc.geonameId, text: formatLocation(loc), item: loc }
     callback results: data
   # Retrieve locations from a server
-  @ajax = _.debounce (term, callback) ->
+  @ajax = (term, callback) ->
     $.ajax({
       url: "https://geoname-lookup.eha.io/api/lookup"
       data: {
@@ -41,7 +41,6 @@
           text: formatLocation(hit._source)
           item: hit._source
         }
-  , 600, true

 Template.locationSelect2.onRendered ->
   $input = @$("#" + @data.selectId)


fixing - operator implementation

fixing - operator implementation

--- a-lisp.coffee
+++ a-lisp.coffee
@@ -39,8 +39,8 @@
       sum
     )
     '-': new ALFunction( '-', ['&rest','values'], (args...)->
-      sum = 0
-      sum -= arg for arg in args when typeof arg == 'number'
+      sum = args[0]
+      sum -= arg for arg in args[1..] when typeof arg == 'number'
       sum
     )
     '*': new ALFunction( '*', ['&rest','values'], (args...)->


Don't log select queries any more

Don't log select queries any more

--- server/common/dal/queryQueue.coffee
+++ server/common/dal/queryQueue.coffee
@@ -7,7 +7,8 @@
   if nextQuery?
     queueInProgress = true
     result = []
-    console.log nextQuery.query
+    if nextQuery.query.indexOf("SELECT") isnt 0
+      console.log nextQuery.query
     conn = new tedious.Connection nextQuery.conn
     request = new tedious.Request nextQuery.query, (err, rowCount) ->
       conn.close()


Removes handlebars bound helper 'ifCond'

--- www/webapp/static/webapp/js/app/helpers/handlebars.coffee
+++ www/webapp/static/webapp/js/app/helpers/handlebars.coffee
@@ -148,40 +148,4 @@
       break unless bytes >= thresh
     bytes.toFixed(1) + " " + units[u]

-  humanFileSize(bytes)
-
-
-Em.Handlebars.registerHelper "ifCond", (v1, operator, v2, options) ->
-  ###
-  Returns true or false according to the logical operator
-
-  @v1: Object, String or Number
-  @v2: Object, String or Number
-  @operator: String
-
-  Usage:  {{#ifCond v1 '&&' v2}} {{else}} {{/ifCond}}
-  ###
-
-  switch operator
-    when "=="
-      (if (v1 is v2) then options.fn(this) else options.inverse(this))
-    when "!="
-      (if (v1 isnt v2) then options.fn(this) else options.inverse(this))
-    when "==="
-      (if (v1 is v2) then options.fn(this) else options.inverse(this))
-    when "!=="
-      (if (v1 isnt v2) then options.fn(this) else options.inverse(this))
-    when "and"
-      (if (v1 and v2) then options.fn(this) else options.inverse(this))
-    when "or"
-      (if (v1 or v2) then options.fn(this) else options.inverse(this))
-    when "<"
-      (if (v1 < v2) then options.fn(this) else options.inverse(this))
-    when "<="
-      (if (v1 <= v2) then options.fn(this) else options.inverse(this))
-    when ">"
-      (if (v1 > v2) then options.fn(this) else options.inverse(this))
-    when ">="
-      (if (v1 >= v2) then options.fn(this) else options.inverse(this))
-    else
-      (if eval_("" + v1 + operator + v2) then options.fn(this) else options.inverse(this))
+  humanFileSize(bytes)


Reset the version to Hydrogen

Reset the version to Hydrogen

--- app/js/app.coffee
+++ app/js/app.coffee
@@ -6,7 +6,7 @@

   $rootScope.marketing =
     name: "Geometry Zen"
-    version: "Helium"
+    version: "Hydrogen"
     tagLine: "Looking at the multiverse from a Geometric Algebra perspective"
     Repos: i18n.translate("Repo").ifPlural(2, "Repos").fetch()


Moving some team members to retired (sniff) status

--- app/lib/en.coffee
+++ app/lib/en.coffee
@@ -916,34 +916,29 @@
       schawinski: 'Kevin Schawinski - Co-Founder: Astronomer, ETH Zurich
Works on the formation and evolution of galaxies and their supermassive black holes. Usually trying to get more sleep. And coffee.'
       edmondson: 'Edd Edmondson - Astronomer, University of Portsmouth
Zookeeper, aiding in accessing Sloan data, and general helper-outer.'
       keel: '''Bill Keel - Astronomer, University of Alabama
Author of 'The Road to Galaxy Formation' and 'The Sky at Einstein's Feet'. With research interests in galaxy formation and evolution, dust content of galaxies, and active galactic nuclei, he couldn't stay away when Galaxy Zoo got started.'''
-      skibba: 'Ramin Skibba - Astronomer, UC San Diego
Leading the use of marked correlation functions to analyse the Galaxy Zoo morphologies.'
-      cardamone: '''Carie Cardamone - Astronomer, Brown University
An educator and astronomer, studying the Zoo's green peas, interested in understanding why galaxies grow they way they do. She's very enthusiastic about engaging the broader public in scientific research and the nature of science.'''
+      skibba: 'Ramin Skibba - Astronomer, UC San Diego
Leading the use of marked correlation functions to analyse the Galaxy Zoo morphologies. Keenly interested in science communication and science policy.'
+      cardamone: '''Carie Cardamone - Astronomer, Wheelock College
An educator and astronomer, studying the Zoo's green peas, interested in understanding why galaxies grow they way they do. She's very enthusiastic about engaging the broader public in scientific research and the nature of science.'''
       willett: 'Kyle Willett - Astronomer, University of Minnesota
  Studies the roles of both morphology and environment on galaxies and their active black holes. He is leading the data reduction and catalog creation from Galaxy Zoo 2 classifications, and is also working on building new tools to enable independent research by citizen science volunteers. In his spare time, he runs probably more than is healthy.'
-      casteels: 'Kevin Casteels - Astronomer and System Administrator, Simulation Curriculum Corp
  Interested in galaxy evolution and morphologies, specifically how they are affected by mergers and interactions.  Kevin is currently working with the makers of the Starry Night astronomy software, helping to develop new astronomy apps and keep the systems running!  In his spare time he is also a SCUBA instructor and loves the great outdoors.'
+      casteels: 'Kevin Casteels - Astronomer, University of Victoria
  Interested in galaxy evolution and morphologies, specifically how they are affected by mergers and interactions.  Kevin is currently working with the makers of the Starry Night astronomy software, helping to develop new astronomy apps and keep the systems running!  In his spare time he is also a SCUBA instructor and loves the great outdoors.'
       urry: 'Meg Urry - Physicist, Yale University
Works on supermassive black holes (growth history, unification, relativistic jets, blazars) and their interplay with galaxies. In her spare time, she writes for CNN.com and works to increase the participation of women and minorities in science.'
       nichol: '''Bob Nichol - Cosmologist, University of Portsmouth
An astronomer, astrophysicist and cosmologist with interests in computer science and statistics. A senior member (or 'Builder') of the Sloan Digital Sky Survey, the most ambitious digital survey of the sky ever undertaken. A long suffering supporter of Manchester City, suffering no more.'''
       fortson: '''Lucy Fortson - Astrophysicist, University of Minnesota
Interested in galaxy evolution, black holes and the jets of material beaming from the centers of active galactic nuclei. Started the Zooniverse effort at the Adler Planetarium, now bringing the light to the University of Minnesota. On the odd weekend, when she's not preparing lecture or writing grants, Lucy can be found hanging out with her husband and son at one of Minneapolis' fine dining establishments.'''
       maksym: '''Peter Maksym - Astronomer, University of Alabama
Interested in the un-lives and behaviors of black holes and their habitats, amongst other things.  He's currently working on analysis of the Voorwerpjes ("little Voorwerps") but can also be found looking for hapless stars getting ripped to shreds.'''
       masters: '''Karen Masters - Project Scientist: Astronomer, University of Portsmouth
  Interested in spiral galaxies of all kinds, red has always been her favourite colour. She's working to disentangle the 'true' red spirals, from those that are reddened because we see them edge on, and along the way learn about interstellar dust, and the nature of red spirals themselves. Bars also a speciality.'''
-      melvin: 'Tom Melvin - Astronomer, University of Portsmouth
A PhD student currently studying the formation and evolution of bars in disk galaxies.'
+      melvin: 'Tom Melvin - Astronomer, University of Portsmouth
Concentrated in his PhD thesis on studying the formation and evolution of bars in disk galaxies with Galaxy Zoo data.'
       szalay: '''Alex Szalay - Cosmologist, Johns Hopkins University
One of the creators of the SDSS SkyServer, and currently involved in building the Virtual Observatory. Plays some music in his 'spare' time.'''
       thomas: '''Daniel Thomas - Astronomer, University of Portsmouth
Interested in galaxies of any shape, age, colour and habitat in the universe. When he's not sunbathing, his research focuses on the stars in galaxies and what they tell us about galaxy formation and evolution.'''
       haussler: 'Boris Häußler - Astronomer, University of Nottingham
An expert on analysing Hubble Space Telescope images and automatic morphology methods.'
       hoyle: 'Ben Hoyle - Astronomer, University of Portsmouth
Currently developing exciting connections between Google Sky and Galaxy Zoo. Also working on using the lensing of distant quasars to learn about the properties of nearby Galaxy Zoo galaxies.'
       kaviraj: 'Sugata Kaviraj - Astronomer, University of Hertfordshire and University of Oxford
Works on the formation and evolution of galaxies.'
       raddick: 'Jordan Raddick - Science Education and Outreach Coordinator, Johns Hopkins University
A science writer and educator, creator of the science projects on the SDSS SkyServer web site, and an avid science fiction reader and writer. Jordan wishes he could visit galaxies in their natural habitat.'
-      wong: 'Ivy Wong - Astronomer, CSIRO'
+      wong: 'Ivy Wong - Astronomer, ICRAR/Perth
 A Super Science Fellow in Australia, Ivy studies why galaxies start and stop forming stars, how they grow supermassive black holes in their centre, and why they look like they do today, especially their radio properties.'

     dev:
       title: 'Zooniverse Development Team'
-      kapadia: 'Amit Kapadia
Amit Kapadia develops astronomical web applications. He is a citizen science developer for the crowd-sourcing based platform Zooniverse. New to the world of JavaScript he spends his days dreaming of conducting in-browser analyses on astronomical data sets. On occasion he flexes his modest sway to advocate for more modern solutions to data access in astronomy. He believes the scientific world needs to speak more closely with the tech world, and constantly strives to learn more from the latter. Amit is often found at the Adler Planetarium in Chicago.'
       carstensen: 'Brian Carstensen
Brian is a web developer working on the Zooniverse family of projects at the Adler Planearium. Brian has a degree in graphic design from Columbia College in Chicago, and worked in that field for a number of years before finding a niche in web development.'
-      miller: '''David Miller
As a visual communicator, David is passionate about tellings stories through clear, clean, and effective design. Before joining the Zooniverse team as Visual Designer, David worked for The Raindance Film Festival, the News 21 Initiative's Apart From War, Syracuse Magazine, and as a freelance designer for his small business, Miller Visual. David is a graduate of the S.I. Newhouse School of Public Communications at Syracuse University, where he studied Visual & Interactive Communications.'''
       paget: 'Ed Paget
Ed somehow went from studying Film at Northwestern University to joining the Zooniverse team as a software developer. Along the way, he picked up a little programming. In his spare time, he enjoys photography, chess, fencing, and growing his beard.'
       parrish: 'Michael Parrish
Michael has a degree in Computer Science and has been working with The Zooniverse for the past three years as a Software Developer. Aside from web development; new technologies, science, AI, reptiles, and coffee tend to occupy his attention.'
-      simpson: 'Robert Simpson
Zooniverse Researcher and developer at Oxford University. PI of the Milky Way Project.'
-      smith: 'Arfon Smith
As an undergraduate, Arfon studied Chemistry at the University of Sheffield before completing his Ph.D. in Astrochemistry at The University of Nottingham in 2006. He worked as a senior developer at the Wellcome Trust Sanger Institute (Human Genome Project) in Cambridge before joining the Galaxy Zoo team in Oxford. Over the past 3 years he has been responsible for leading the development of a platform for citizen science called Zooniverse. In August of 2011 he took up the position of Director of Citizen Science at the Adler Planetarium where he continues to lead the software and infrastructure development for the Zooniverse.'
-      whyte: '''Laura Whyte
Having spent her PhD trying to get computers to classify galaxies, Laura know only to well the true value of the contribution to science made by the Galaxy Zoo volunteers. Since leaving astronomy she has retrained as a Maths teacher and picked up a few programming skills. It was with great delight she joined the team at the Zooniverse, developing online education and research tools, allowing her to use bits and pieces of all the skills she's picked up over the last decade.'''

     czech:
       title: 'Galaxy Zoo Czech Support Team'
@@ -984,6 +979,11 @@
       murray: 'Phil Murray - Web Designer, Artist and Managing Director of Fingerprint Digital Media Responsible for the visual design of the Galaxy Zoo web site. Designer of www.banguniverse.com for Brian May, Patrick Moore and Chris Lintott. A keen musician and lapsed tennis and soccer player.'
       slosar: 'Anze Slosar - Cosmologist, Berkeley Center for Cosmological Physics
A theoretical physicist, who has has never sat behind a telescope, likes to drink good beer and wishes he were richer.'
       darg: 'Daniel Darg - Astronomer, University of Oxford
Working on measuring the number and properties of galaxy mergers identified in Galaxy Zoo.'
+      kapadia: 'Amit Kapadia
Amit Kapadia is a former citizen science developer for the crowd-sourcing based platform Zooniverse. New to the world of JavaScript he spends his days dreaming of conducting in-browser analyses on astronomical data sets. On occasion he flexes his modest sway to advocate for more modern solutions to data access in astronomy. He believes the scientific world needs to speak more closely with the tech world, and constantly strives to learn more from the latter.'
+      miller: '''David Miller
As a visual communicator, David is passionate about tellings stories through clear, clean, and effective design. Before joining the Zooniverse team as Visual Designer, David worked for The Raindance Film Festival, the News 21 Initiative's Apart From War, Syracuse Magazine, and as a freelance designer for his small business, Miller Visual. David is a graduate of the S.I. Newhouse School of Public Communications at Syracuse University, where he studied Visual & Interactive Communications.'''
+      simpson: 'Robert Simpson
Zooniverse Researcher and developer at Oxford University. PI of the Milky Way Project.'
+      smith: 'Arfon Smith
As an undergraduate, Arfon studied Chemistry at the University of Sheffield before completing his Ph.D. in Astrochemistry at The University of Nottingham in 2006. He worked as a senior developer at the Wellcome Trust Sanger Institute (Human Genome Project) in Cambridge before joining the Galaxy Zoo team in Oxford. He was responsible for leading the development of a platform for citizen science called Zooniverse, and was Director of Citizen Science at the Adler Planetarium where he led the software and infrastructure development for the Zooniverse.'
+      whyte: '''Laura Whyte
Having spent her PhD trying to get computers to classify galaxies, Laura knows only too well the true value of the contribution to science made by the Galaxy Zoo volunteers. Since leaving astronomy she retrained as a maths teacher and picked up a few programming skills. It was with great delight she joined the team at the Zooniverse, developing online education and research tools, allowing her to use bits and pieces of all the skills she's picked up over the last decade.'''

   examine:


Fix spec description for month_spec

Fix spec description for month_spec

--- spec/coffeescripts/models/month_spec.coffee
+++ spec/coffeescripts/models/month_spec.coffee
@@ -1,4 +1,4 @@
-describe 'LedgerWeb.Models.Month', ->
+describe 'LedgerWeb.Models.Transactions.Month', ->
   it 'populates transactions attribute as transactions collection', ->
     month = new LedgerWeb.Models.Transactions.Month
       month: '2013/12'


Making compiling write new abi and binaires, without overwriting deployed addresses. Fixes #18.

--- packages/truffle-core/lib/contracts.coffee
+++ packages/truffle-core/lib/contracts.coffee
@@ -137,6 +137,18 @@
         err = new Error("Compilation failed. See above.")
       callback(err)

+  @write_contracts: (config, description="contracts", callback) ->
+    mkdirp config.environments.current.directory, (err, result) ->
+      if err?
+        callback(err)
+        return
+
+      display_directory = "." + config.environments.current.contracts_filename.replace(config.working_dir, "")
+      console.log "Writing #{description} to #{display_directory}" unless config.grunt.option("quiet-deploy")
+      fs.writeFileSync(config.environments.current.contracts_filename, JSON.stringify(config.contracts.classes, null, 2), {flag: "w+"})
+      callback()
+
+
   @compile: (config, callback) ->
     async.series [
       (c) ->
@@ -146,8 +158,10 @@
           else
             c()
       (c) =>
-        @compile_all(config, callback)
-    ]
+        @compile_all(config, c)
+      (c) =>
+        @write_contracts(config, "contracts", c)
+    ], callback

   @deploy: (config, compile=true, done_deploying) ->
     coinbase = null
@@ -223,21 +237,12 @@

         interval = setInterval verify, 1000

-    ], (err) ->
+    ], (err) =>
       if err?
         done_deploying(err)
         return

-      mkdirp config.environments.current.directory, (err, result) ->
-        if err?
-          done_deploying(err)
-          return
-
-        display_directory = "." + config.environments.current.contracts_filename.replace(config.working_dir, "")
-        console.log "Writing contracts to #{display_directory}" unless config.grunt.option("quiet-deploy")
-        fs.writeFileSync(config.environments.current.contracts_filename, JSON.stringify(config.contracts.classes, null, 2), {flag: "w+"})
-
-        done_deploying()
+      @write_contracts(config, "contracts and deployed addresses", done_deploying)

 module.exports = Contracts


logic fixes

logic fixes

--- static/coffee/renderers/properties.coffee
+++ static/coffee/renderers/properties.coffee
@@ -140,7 +140,7 @@

     # if the attribute is not on this property object at all, log a bad request
     if not (attrname of @)
-      #console.log("requested unknown property '#{ attrname } on object: " + obj)
+      console.log("requested unknown property '#{ attrname } on object: " + obj)
       return

     # if the attribute specifies a field, and the field exists on the object, return that value
@@ -149,7 +149,7 @@
         return obj[@[attrname].field]

     # otherwise, if the attribute exists on the object, return that value
-    if obj[attrname]
+    if obj[attrname]?
       return obj[attrname]

     # finally, check for a default value on this property object that could be returned
@@ -257,7 +257,7 @@
     @enum(styleprovider, glyphspec, @text_align_name, "left right center")
     @enum(styleprovider, glyphspec, @text_baseline_name, "top middle bottom alphabetic hanging")
   font:(obj, font_size) ->
-    if font_size?
+    if not font_size?
       font_size = @select(@text_font_size_name,  obj)
     font       = @select(@text_font_name,       obj)
     font_style = @select(@text_font_style_name, obj)


Prioritize file type matching over first line matching

--- src/grammar-registry.coffee
+++ src/grammar-registry.coffee
@@ -36,21 +36,21 @@
       if score > highestScore or not bestMatch?
         bestMatch = grammar
         highestScore = score
-      else if score is highestScore and bestMatch?.bundledPackage
-        bestMatch = grammar unless grammar.bundledPackage
     bestMatch

   # Extended: Returns a {Number} representing how well the grammar matches the
   # `filePath` and `contents`.
   getGrammarScore: (grammar, filePath, contents) ->
+    return Infinity if @grammarOverrideForPath(filePath) is grammar.scopeName
+
     contents = fs.readFileSync(filePath, 'utf8') if not contents? and fs.isFileSync(filePath)

-    if @grammarOverrideForPath(filePath) is grammar.scopeName
-      2 + (filePath?.length ? 0)
-    else if @grammarMatchesContents(grammar, contents)
-      1 + (filePath?.length ? 0)
-    else
-      @getGrammarPathScore(grammar, filePath)
+    score = @getGrammarPathScore(grammar, filePath)
+    if not grammar.bundledPackage
+      score += 0.25
+    if @grammarMatchesContents(grammar, contents)
+      score += 0.125
+    score

   getGrammarPathScore: (grammar, filePath) ->
     return -1 unless filePath


queue: test for properly obeying backpressure

--- test/queue.coffee
+++ test/queue.coffee
@@ -7,8 +7,7 @@
 describe '_.queue', ->
   it 'accepts fn, calls it on each chunk, then passes the original chunk along', (done) ->
     input  = [{a:'1', b:'2'}, {c:'2', d:'3'}]
-    fn = (chunk, cb) -> setTimeout (-> cb null, chunk), 1000
-    spy = sinon.spy fn
+    spy = sinon.spy (chunk, cb) -> setTimeout (-> cb null, chunk), 10
     _(input).stream().queue(spy).value (result) ->
       assert.deepEqual result, input
       assert.equal spy.callCount, 2
@@ -16,20 +15,25 @@
       done()
     .run assert.ifError

-  it 'with low concurency accepts fn, calls it on each chunk, then passes the original chunk along', (done) ->
-    input  = [{a:'1', b:'2'}, {c:'2', d:'3'}]
-    fn = (chunk, cb) -> setTimeout (-> cb null, chunk), 1000
-    spy = sinon.spy fn
-    _(input).stream().queue(fn: spy, concurrency: 1).value (result) ->
-      assert.deepEqual result, input
-      assert.equal spy.callCount, 2
-      assert.deepEqual spy.args[i][0], input[i] for i in input.length
-      done()
-    .run assert.ifError
+  _([1, 2, 3]).each (concurrency) ->
+    it "stream properly obeys backpressure with concurrency #{concurrency}", (done) ->
+      input  = [{a:'1'}, {b:'2'}, {c:'3'}, {d:'4'}, {e:'5'}, {f:'6'}, {g:'7'}]
+      fn = (chunk, cb) -> setTimeout (-> cb null, chunk), 10
+      num_read = num_received = 0
+      queue_stream = _(input).stream().queue({fn, concurrency})
+      old_transform = queue_stream.stream()._transform
+      queue_stream.stream()._transform = ->
+        num_read++
+        old_transform.apply @, _(arguments).toArray()
+      queue_stream
+        .each (result) ->
+          num_received++
+          assert num_read - num_received <= concurrency
+        .run done

   it 'successfully handles an erorr in the queue', (done) ->
     input  = [{a:'1', b:'2'}, {c:'2', d:'3'}]
-    fn = (chunk, cb) -> setTimeout (-> cb 'some error', chunk), 1000
+    fn = (chunk, cb) -> setTimeout (-> cb 'some error', chunk), 10
     _(input).stream().queue(fn).run (err) ->
       assert.equal err, 'some error'
       done()


Cleaned up.

Cleaned up.

--- app/clipboard.coffee
+++ app/clipboard.coffee
@@ -13,7 +13,7 @@
 clipboard_client_by_name = (name) ->
   clipboard_clients().children "[name='#{name}']"

-client_xref = (name) ->
+@client_xref = (name) ->
   if ensure_element client_by_name name
     $ "<client>", name: name

@@ -30,9 +30,10 @@
   ($ ".arena").append containment = make_containment()
   VIEWS_TABLE_SELECTION = $.extend {}, VIEWS_TABLE, VIEWS_TABLE_EDIT, {button_add: do_nothing, button_edit: do_nothing}
   c = do (xreflist) ->
-        xml:      RECORDS["clipboard"]
-        schema:   "clipboard"
-        xreflist: -> with_mixin VIEWS_TABLE_SELECTION, xreflist
+    xml:      RECORDS["clipboard"]
+    schema:   "clipboard"
+    xreflist: -> with_mixin VIEWS_TABLE_SELECTION, xreflist
+  debugger
   containment.append_nested with_mixin c, render_record

 @button_selected_to_clipboard = (containment) ->


Devtools: refs removed for a deprecated view

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -343,11 +343,9 @@
     switch mode
       when 'home'
         @welcomePage.show()
-        KD.singletons.mainView.appSettingsMenuButton.hide()
         KD.utils.defer @welcomePage.lazyBound 'unsetClass', 'out'
       else
         @welcomePage.setClass 'out'
-        KD.singletons.mainView.appSettingsMenuButton.show()
         KD.utils.wait 500, @welcomePage.bound 'hide'

   saveAll:->


DevTools: Fix closeAllFiles and make sure there is a recently loaded file before switching to develop mode

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -138,20 +138,26 @@

         JSEditor.ready =>

-          JSEditor.loadLastOpenFile()
           JSEditor.codeMirrorEditor.on "change", \
             _.debounce (@lazyBound 'previewApp', no), 500

           JSEditor.on "RunRequested", @lazyBound 'previewApp', yes
           JSEditor.on "AutoRunRequested", @bound 'toggleLiveReload'
           JSEditor.on "FocusedOnMe", => @_lastActiveEditor = JSEditor
+          JSEditor.on "RecentFileLoaded", => @switchMode 'develop'

           @on 'saveMenuItemClicked', =>
             @_lastActiveEditor?.handleSave()
           @on 'saveAllMenuItemClicked', =>
             CSSEditor.handleSave(); JSEditor.handleSave()
           @on 'closeAllMenuItemClicked', =>
-            CSSEditor.closeFile(); JSEditor.closeFile()
+            delete @_lastActiveEditor
+            @switchMode 'home'
+            KD.utils.defer ->
+              PreviewPane.container.destroySubViews()
+              CSSEditor.closeFile(); JSEditor.closeFile()
+
+          JSEditor.loadLastOpenFile()

         CSSEditor.ready =>

@@ -339,7 +345,9 @@
     return  unless path

     @loadFile path, (err)=>
-      @storage.unsetKey @_lastFileKey  if err?
+      if err?
+      then @storage.unsetKey @_lastFileKey
+      else @emit "RecentFileLoaded"

   createEditor: (callback)->


terminal: hide message pane when running and started timeout callbacks are invoked

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -102,12 +102,14 @@
         return @setMessage "It seems you don't have a VM to use with Terminal."

       WebTermView.setTerminalTimeout vmName, 15000
-      , => @restoreTabs vmName
-      , (->)
+      , =>
+        @restoreTabs vmName
+        @messagePane.hide()
+      , =>
+        @messagePane.hide()
       , =>
         KD.mixpanel "Open Webterm, fail", {vmName}
         KD.logToExternalWithTime "oskite: Can't open Webterm", vmName
-

         @emit 'message', """

Couldn't connect to your VM.

@@ -246,10 +248,14 @@

     {vmName} = options

-    WebTermView.setTerminalTimeout vmName, 15000, ->
+    WebTermView.setTerminalTimeout vmName, 15000
+    , =>
       terminalView.connectToTerminal()
+      @messagePane.hide()
     , =>
-      KD.utils.defer => @addNewTab vmName
+      KD.utils.defer =>
+        @addNewTab vmName
+        @messagePane.hide()
     , =>
       KD.mixpanel "Open Webterm, fail", {vmName}
       KD.logToExternalWithTime "oskite: Can't open Webterm", vmName


team logo hot fix

team logo hot fix

--- client/app/lib/mainview.coffee
+++ client/app/lib/mainview.coffee
@@ -236,7 +236,6 @@

     unless logo
       @teamLogoWrapper.hide()
-      # @teamname.setClass 'no-logo'
     else
       @teamLogo.setClass 'team-logo'
       @teamLogoWrapper.show()
@@ -246,6 +245,15 @@

       @teamLogo.setAttribute 'class', ''

+      unless logo
+        @teamLogo.setAttribute 'src', ''
+        @teamname.setClass 'no-logo'
+        @teamLogoWrapper.hide()
+      else
+        @teamLogo.setAttribute 'src', logo
+        @teamLogo.setClass 'team-logo'
+        @teamLogoWrapper.show()
+        @teamname.unsetClass 'no-logo'

   createMiniWelcomeSteps: ->


Switch time-tracking to render hook

Switch time-tracking to render hook

--- plugins/time-tracking/time-tracking.coffee
+++ plugins/time-tracking/time-tracking.coffee
@@ -28,7 +28,7 @@
       @logger.info "Loading time tracking"
       @api.cursor.on 'rowChange', (@onRowChange.bind @)
       @onRowChange null, @api.cursor.row # Initial setup
-      @api.view.on 'renderLine', (@onRenderLine.bind @)
+      @api.view.addRenderHook 'infoElements', (@renderTime.bind @)
       @api.view.data.on 'afterDescendantRemoved', (@onDescendantRemoved.bind @)
       @api.view.data.on 'afterDescendantAdded', (@onDescendantAdded.bind @)
       @rowChanges = []
@@ -209,13 +209,13 @@
       else if seconds > 0
         "#{seconds}s"

-    onRenderLine: (row, renderArray, options) ->
+    renderTime: (elements, renderData) ->
       if do @shouldDisplayTime
-        time = @rowTime row
+        time = @rowTime renderData.row
         if time > 1000
-          @logger.info "Rendering time for row #{row.id} as #{time}"
-          renderArray.push virtualDom.h 'span', {
+          @logger.info "Rendering time for row #{renderData.row.id} as #{time}"
+          elements.push virtualDom.h 'span', {
             className: 'time'
           }, " " + (@printTime time)
-
+      elements
 )()


fix name issue with editable view titles

--- client/code/view/titles.coffee
+++ client/code/view/titles.coffee
@@ -27,7 +27,7 @@
     e.preventDefault()
     $a = @$el.find('.editable')
     @$el.find('input')
-      .val(@model.name())
+      .val(@model.get 'displayName')
       .css('width', $a.width() + 20)
       .show 0, ->
         @focus()


experimenting with title bar notification

--- client/controllers/Chat.coffee
+++ client/controllers/Chat.coffee
@@ -87,10 +87,33 @@
       updateUnreadCount()
       $(".message-text").focus()

+    alternateCount = 0
+    alternateInterval = null
+
+    alternateTitle = () ->
+
+      clearInterval( alternateInterval )
+      alternateCount = 0
+
+      alternateInterval = setInterval () ->
+        if alternateCount < 6
+          if document.title.indexOf("Parrot") > -1
+            document.title = "(#{ unreadCount }) <--"
+          else
+            document.title = "(#{ unreadCount }) Parrot"
+
+        if alternateCount >= 20
+          alternateCount = 0
+
+        alternateCount++
+      , 1000
+
     updateUnreadCount = () ->
       if unreadCount > 0
         document.title = "(#{ unreadCount }) Parrot";
+        alternateTitle()
       else
+        clearInterval( alternateInterval )
         document.title = "Parrot";

     drawLastMessageLine = () ->


Update ms.coffee

Update ms.coffee

--- app/locale/ms.coffee
+++ app/locale/ms.coffee
@@ -435,7 +435,7 @@
   common:
 #    default_title: "CodeCombat - Coding games to learn Python and JavaScript"
 #    default_meta_description: "Learn typed code through a programming game. Learn Python, JavaScript, and HTML as you solve puzzles and learn to make your own coding games and websites."
-#    back: "Back" # When used as an action verb, like "Navigate backward"
+    back: "Kembali" # When used as an action verb, like "Navigate backward"
 #    coming_soon: "Coming soon!"
 #    continue: "Continue" # When used as an action verb, like "Continue forward"
 #    next: "Next"
@@ -2074,7 +2074,7 @@
 #    course_title: "Course Editor"
 #    achievement_title: "Achievement Editor"
 #    poll_title: "Poll Editor"
-#    back: "Back"
+    back: "Kembali"
 #    revert: "Revert"
 #    revert_models: "Revert Models"
 #    pick_a_terrain: "Pick A Terrain"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/state.coffee
+++ test/state.coffee
@@ -13,9 +13,7 @@
     initialState: 'run'

   animation.include(StateMachine)
-
-  log animation()
-
+
   animation.update()
   animation.update()
   animation.update()


Ru translations (#5513)

Ru translations (#5513)

* Few sentences were translated

14, 39, 41, 42, 43, 57, 58, 76

* Update ru.coffee

Leading spaces removed to respect syntax 92

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -11,7 +11,7 @@
 #    built_for_teachers_subblurb2: "Learners construct mazes and use basic input handling to code their own games that can be shared with friends and family."
 #    built_for_teachers_subtitle3: "Web Development"
 #    built_for_teachers_subblurb3: "Using HTML, CSS, and jQuery, learners flex their creative muscles to program their own webpages with a custom URL to share with their classmates."
-#    century_skills_title: "21st Century Skills"
+    century_skills_title: "Навыки 21 Века"
 #    century_skills_blurb1: "Students Don't Just Level Up Their Hero, They Level Up Themselves"
 #    century_skills_quote1: "You mess up…so then you think about all of the possible ways to fix it, and then try again. I wouldn't be able to get here without trying hard."
 #    century_skills_subtitle1: "Critical Thinking"
@@ -36,11 +36,11 @@
 #    creativity_rigor_blurb2: "Every CodeCombat level is scaffolded based on millions of data points and optimized to adapt to each learner. Practice levels and hints help students when they get stuck, and challenge levels assess students' learning throughout the game."
 #    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
 #    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
-#    featured_partners_title1: "Featured In"
+    featured_partners_title1: "Зарекомендован в"
 #    featured_partners_title2: "Awards & Partners"
-#    featured_partners_blurb1: "CollegeBoard Endorsed Provider"
-#    featured_partners_blurb2: "Best Creativity Tool for Students"
-#    featured_partners_blurb3: "Top Pick for Learning"
+    featured_partners_blurb1: "Совет колледжа одобрил провайдера"
+    featured_partners_blurb2: "Лучшая креативная вещь для студентов"
+    featured_partners_blurb3: "Лучший выбор для обучения"
 #    featured_partners_blurb4: "Code.org Official Partner"
 #    featured_partners_blurb5: "CSforAll Official Member"
 #    featured_partners_blurb6: "Hour of Code Activity Partner"
@@ -54,8 +54,8 @@
 #    for_leaders_subblurb3: "Whether you want to build a Middle School coding elective, a CTE pathway, or an AP Computer Science Principles class, CodeCombat is tailored to suit your needs."
 #    for_leaders_subtitle4: "Real-World Skills"
 #    for_leaders_subblurb4: "Students build grit and develop a growth mindset through coding challenges that prepare them for the 500K+ open computing jobs."
-#    for_teachers_title: "For Teachers"
-#    for_teachers_blurb: "Tools to Unlock Student Potential"
+    for_teachers_title: "Для учителей"
+    for_teachers_blurb: "Инструменты для раскрытия потенциала ученика"
 #    for_teachers_subtitle1: "Project-Based Learning"
 #    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
 #    for_teachers_subtitle2: "Teacher Dashboard"
@@ -73,7 +73,7 @@
 #    global_subtitle4: "Countries"
 #    go_to_my_classes: "Go to my classes"
 #    go_to_my_courses: "Go to my courses"
-#    quotes_quote1: "Name any program online, I’ve tried it. None of them match up to CodeCombat. Any teacher who wants their students to learn how to code... start here!"
+    quotes_quote1: "Назовите любую онлайн программу, я попробовал её. Ни один из них не соответствует CodeCombat. Любой учитель, который хочет, чтобы их ученики научились кодировать ... начните здесь!"
 #    quotes_quote2: " I was surprised about how easy and intuitive CodeCombat makes learning computer science. The scores on the AP exam were much higher than I expected and I believe CodeCombat is the reason why this was the case."
 #    quotes_quote3: "CodeCombat has been the most beneficial for teaching my students real-life coding capabilities. My husband is a software engineer and he has tested out all of my programs. He put this as his top choice."
 #    quotes_quote4: "The feedback … has been so positive that we are structuring a computer science class around CodeCombat. The program really engages the students with a gaming style platform that is entertaining and instructional at the same time. Keep up the good work, CodeCombat!"


Update ru.coffee (#4810)

Update ru.coffee (#4810)

Please explain to me what  means spawnable (614 en.coffee) for correct translation

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -228,12 +228,12 @@
     signup_switch: "Хотите создать аккаунт?"

   signup:
-#    complete_subscription: "Complete Subscription"
+    complete_subscription: "Вы успешно подписались"
     create_student_header: "Создать учётную запись Ученика"
     create_teacher_header: "Создать учётную запись Учителя"
     create_individual_header: "Создать личную учётную запись"
     email_announcements: "Получать оповещения о новых уровнях и возможностях на CodeCombat"
-#    sign_in_to_continue: "Sign in or create an account to continue"
+    sign_in_to_continue: "Войдите или создайте учётную запись для продолжения"
     teacher_email_announcements: "Сообщайте мне о новых учебных материалах, планах и курсах!"
     creating: "Создаём учётную запись..."
     sign_up: "Регистрация"
@@ -418,7 +418,7 @@
   units:
     second: "секунда"
     seconds: "секунд(ы)"
-#    sec: "sec"
+    sec: "сек"
     minute: "минута"
     minutes: "минут(ы)"
     hour: "час"
@@ -607,22 +607,22 @@
     project_published_noty: "Ваш уровень опубликован!"
     keep_editing: "Продолжить редактировать"

-#  apis:
-#    methods: "Methods"
-#    events: "Events"
+  apis:
+    methods: "Методы"
+    events: "События"
 #    spawnable: "Spawnable"
-#    html: "HTML"
-#    math: "Math"
-#    array: "Array"
-#    object: "Object"
-#    string: "String"
-#    function: "Function"
-#    vector: "Vector"
-#    date: "Date"
-#    jquery: "jQuery"
-#    json: "JSON"
-#    number: "Number"
-#    webjavascript: "JavaScript"
+    html: "HTML"
+    math: "Математика"
+    array: "Массивы"
+    object: "Объект"
+    string: "Строка"
+    function: "Функция"
+    vector: "Вектор"
+    date: "Дата"
+    jquery: "jQuery"
+    json: "JSON"
+    number: "Цифра"
+    webjavascript: "JavaScript"

 #  amazon_hoc:
 #    title: "Keep Learning with Amazon!"
@@ -679,9 +679,9 @@
     damage_dealt: "Нанесено повреждений"
     difficulty: "Сложность"
     gold_collected: "Собрано золота"
-#    survival_time: "Survived"
-#    defeated: "Enemies Defeated"
-#    code_length: "Lines of Code"
+    survival_time: "Прожито"
+    defeated: "Врагов побеждено"
+    code_length: "Строчек кода"
 #    score_display: "__scoreType__: __score__"

   inventory:
@@ -695,9 +695,9 @@
     restricted: "(запрещён на этом уровне)"
     equip: "Надеть"
     unequip: "Снять"
-#    warrior_only: "Warrior Only"
-#    ranger_only: "Ranger Only"
-#    wizard_only: "Wizard Only"
+    warrior_only: "Только воин"
+    ranger_only: "Только рейнджер"
+    wizard_only: "Только волшебник"

   buy_gems:
     few_gems: "Немного самоцветов"
@@ -915,7 +915,7 @@
     editor_config_behaviors_description: "Автозавершение кавычек, квадратных и фигурных скобок."

   about:
-#    learn_more: "Learn More"
+    learn_more: "Узнать больше"
     main_title: "Чтобы научиться программировать, необходимо (много) писать код."
     main_description: "Задача CodeCombat в том, чтобы вы проходили обучение с улыбкой на лице."
     mission_link: "Миссия"
@@ -1096,7 +1096,7 @@
 #    learn_more_modal: "Teacher accounts on CodeCombat have the ability to monitor student progress, assign licenses and manage classrooms. Teacher accounts cannot be a part of a classroom - if you are currently enrolled in a class using this account, you will no longer be able to access it once you update to a Teacher Account."
     create_account: "Создать аккаунт учителя"
     create_account_subtitle: "Получите доступ к инструментам, доступным только учителям, для использования CodeCombat в класной комнате. Подготовьте класс, добавьте ваших учеников и наблюдайте за их прогрессом!"
-#    convert_account_title: "Update to Teacher Account"
+    convert_account_title: "Улучшить до аккаунта учителя"
     not: "Нет"

   versions:


Update ru.coffee

Update ru.coffee

With fixes from [M-r-A](https://github.com/M-r-A)

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -286,7 +286,7 @@
     projects_header: "Добавьте 3 проекта"
     projects_header_2: "Проекты (3 самых лучших)"
 #    projects_blurb: "Highlight your projects to amaze employers."
-    project_name: "Название Проекта"
+    project_name: "Название проекта"
 #    project_name_help: "What was the project called?"
     project_description: "Описание"
 #    project_description_help: "Briefly describe the project."
@@ -302,7 +302,7 @@
 #    what_blurb: "CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io."
 #    who: "Who Are the Players?"
 #    who_blurb: "CodeCombateers are software developers who enjoy using their programming skills to play games. They range from college seniors at top 20 engineering programs to 20-year industry veterans."
-    how: "Как Мы Находим Разработчиков?"
+    how: "Как мы находим разработчиков?"
 #    how_blurb: "We host competitive tournaments to attract competitive software engieneers. We then use in-house algorithms to identify the best players among the top 5% of tournament winners."
 #    why: "Why Hire Through Us?"
 #    why_blurb_1: "We will save you time. Every CodeCombateer we feaure is "
@@ -433,7 +433,7 @@
     cast_spell: "Произнести текущее заклинание."
 #    continue_script: "Continue past current script."
 #    skip_scripts: "Skip past all skippable scripts."
-    toggle_playback: "Включить проигрывание/паузу."
+    toggle_playback: "Переключить проигрывание/паузу."
     scrub_playback: "Перемотка назад и вперед во времени."
 #    single_scrub_playback: "Scrub back and forward through time by a single frame."
 #    scrub_execution: "Scrub through current spell execution."
@@ -514,7 +514,7 @@
     new_article_title: "Создать новую статью"
     new_thang_title: "Создать новый тип объектов"
     new_level_title: "Создать новый уровень"
-    new_article_title_login: "Войти чтобы создать новую статью"
+    new_article_title_login: "Войти, чтобы создать новую статью"
 #    new_thang_title_login: "Log In to Create a New Thang Type"
     new_level_title_login: "Войти чтобы создать новый уровень"
 #    new_achievement_title: "Create a New Achievement"
@@ -815,8 +815,8 @@
 #    prizes: "Prizes"
 #    total_value: "Total Value"
 #    in_cash: "in cash"
-    custom_wizard: "Изменить CodeCombat волшебника"
-    custom_avatar: "Изменить CodeCombat аватар"
+    custom_wizard: "Изменить волшебника CodeCombat"
+    custom_avatar: "Изменить аватар CodeCombat"
 #    heap: "for six months of \"Startup\" access"
 #    credits: "credits"
 #    one_month_coupon: "coupon: choose either Rails or HTML"


Update uk.coffee

Update uk.coffee

translated some more strings

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -440,9 +440,9 @@
     parents_blurb2: "За 9.99$ на місяць, вона отримуватиме нові завдання щотижня та персональні листи підтримки від професійних програмістів." # {change}
     parents_blurb3: "Жодного ризику: 100% гарантія повернення грошей, легке скасування абонементу одним кліком."
     payment_methods: "Платіжні методи"
-#    payment_methods_title: "Accepted Payment Methods"
+    payment_methods_title: "Платіжні методи, що приймаються"
     payment_methods_blurb1: "Наразі ми приймаємо кредитні картник та Alpiay."
-#    payment_methods_blurb2: "If you require an alternate form of payment, please contact"
+    payment_methods_blurb2: "Якщо Вам необхідно використати інший спосіб оплати, будь ласка, зв'яжіться з нами."
     stripe_description: "Щомісячний абонемент"
     subscription_required_to_play: "Аби грати в цьому рівні потрібен абонемент."
     unlock_help_videos: "Підпишіться, щоб відкрити усі навчальні відео."
@@ -455,7 +455,7 @@
     was_free_until: "У Вас був безкоштовний абонемент до "
     managed_subs: "Керовані абонементи"
     managed_subs_desc: "Додати абонементи для інших гравців (учнів, дітей тощо)"
-#    managed_subs_desc_2: "Recipients must have a CodeCombat account associated with the email address you provide."
+    managed_subs_desc_2: "Одержувачі повинні мати обліковий запис CodeCombat, пов'язаний з вказаною Вами адресою електронної пошти."
     group_discounts: "Групові знижки"
     group_discounts_1: "Ми також пропонуємо знижки для пакетних передплат."
     group_discounts_1st: "1-ий абонемент (включає Ваш)" # {change}
@@ -592,8 +592,8 @@

   teachers:
     title: "CodeCombat для вчителів" # {change}
-#    intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages."
-#    intro_2: "No experience required!"
+    intro_1: "CodeCombat - це онлайн гра, що вчить програмуванню. Студенти пишуть код на реальних мовах програмування."
+    intro_2: "Досвід не потрібен!"
     free_title: "Скільки це коштує?"
 #    cost_china: "CodeCombat in China is free for the first five levels, after which it costs $9.99 USD per month for access to our other 140+ levels on our exclusive China servers."
 #    free_1: "There are 80+ FREE levels which cover every concept."


support quote(only list and atom)

support quote(only list and atom)

--- lisp.coffee
+++ lisp.coffee
@@ -110,16 +110,16 @@
     isSP = SpecialForm.NAMES.indexOf(funname) != -1
     until @expects ')', false
       @skip()
-      args.push if isSP then @list() else @expr()
+      args.push @expr(isSP)

     @forwards ')'

     klass = if isSP then SpecialForm else CallFun
     return new klass(funname, args)

-  expr: ->
-    if @expects "'", false #value
-      @forwards "'"
+  expr: (isSP) ->
+    if @expects("'", false) or isSP #value
+      @forwards "'" if @expects "'", false
       if @expects '(', false #list
         return @list()
       else #atom
@@ -148,6 +148,8 @@
         switch expr.name
           when 'cond'
             ret = args.filter((arg) => !(@eval_expr(arg.values[0]) instanceof Nil))[0]?.values[1] || new Nil
+          when 'quote'
+            args[0]
       when 'CallFun'
         args = expr.args.map (arg) => @eval_expr(arg)
         switch expr.funname


Check that a user exists

Check that a user exists

--- app/pages/admin.cjsx
+++ app/pages/admin.cjsx
@@ -47,17 +47,17 @@

   render: ->

-      <ChangeListener target={@props.user}>{ =>
-        if @props.user?
+      {if @props.user?
+        <ChangeListener target={@props.user}>{ =>
           if @props.user.admin
             <AdminPage {...@props} />
           else

You are not an administrator

-        else
-

-
You’re not signed in.

-

-      }</ChangeListener>
+        }</ChangeListener>
+      else
+

+
You’re not signed in.

+
}


Hover effects for data points.

Hover effects for data points.

--- assets/js/lightcurve.coffee
+++ assets/js/lightcurve.coffee
@@ -70,6 +70,12 @@
                     cx: (d) => @xScale d.jd
                     cy: (d) => @yScale d.magnitude
                     r: '2'
+                .on('mouseover', ->
+                    d3.select(@).transition().duration(150).attr('r', '10')
+                )
+                .on('mouseout', ->
+                    d3.select(@).transition().duration(150).attr('r', '2')
+                )
             @xTitle.text 'Julian Date'
             @yTitle.text 'Magnitude'


provide endpoint for triggers list

provide endpoint for triggers list

--- example/server.coffee
+++ example/server.coffee
@@ -3,6 +3,7 @@
 morgan = require('morgan')
 pak = require(path.join(__dirname, '..', 'package.json'))
 imageList = require(path.join(__dirname, '..', 'src', 'data', 'images.json'))
+triggerList = require(path.join(__dirname, '..', 'src', 'data', 'triggers.json'))
 port = parseInt(process.argv[2], 10) or 8888
 staticDirectory = path.join(__dirname, 'static')


minor refactoring of test. paper model fully tested.

--- test/javascripts/models/paper_test.coffee
+++ test/javascripts/models/paper_test.coffee
@@ -4,10 +4,9 @@
   needs: ['model:user', 'model:declaration', 'model:figure', 'model:journal', 'model:phase', 'model:task', 'model:comment']

 test 'displayTitle displays short title if title is missing', ->
-  paper = null
   shortTitle = 'test short title'

-  Ember.run =>
+  paper = Ember.run =>
     paper = @store().createRecord 'paper',
       title: ''
       shortTitle: shortTitle
@@ -15,39 +14,36 @@
   equal paper.get('displayTitle'), shortTitle

 test 'displayTitle displays title if present', ->
-  paper = null
   title = 'Test Title'

-  Ember.run =>
-    paper = @store().createRecord 'paper',
+  paper = Ember.run =>
+    @store().createRecord 'paper',
       title: title
       shortTitle: 'test short title'

   equal paper.get('displayTitle'), title

 test 'allTasks returns all tasks for the paper', ->
-
-  paper = null
-  Ember.run =>
+  task1 = null; task2 = null
+  allTasks = Ember.run =>
     paper = @store().createRecord 'paper',
-      id: 1
       title: 'title'
       shortTitle: 'short title'
-
     phase = @store().createRecord 'phase',
-      id: 1
-      paperId: 1
+      paper: paper

     task1 = @store().createRecord 'task',
       title: 'task1'
-      phaseId: 1
+      phase: phase

     task2 = @store().createRecord 'task',
       title: 'task2'
-      phaseId: 1
+      phase: phase

+    paper.setProperties phase: phase
+    paper.get 'allTasks'

-  equal paper.get('allTasks'), ['task1', 'task2']
+  equal allTasks, [task1, task2]

 test 'Paper hasMany assignees as User', ->
   relationships = Ember.get ETahi.Paper, 'relationships'


To handle several tours on one page the click bindings need to be removed before new ones are added. If this does not happen than the first tour that was opened will reopen during the second tour.

--- bootstrap-tour.coffee
+++ bootstrap-tour.coffee
@@ -84,17 +84,17 @@
     # Start tour from current step
     start: (force = false) ->
       # Go to next step after click on element with class .next
-      $(document).on "click.bootstrap-tour", ".popover .next", (e) =>
+      $(document).off("click.bootstrap-tour",".popover .next").on "click.bootstrap-tour", ".popover .next", (e) =>
         e.preventDefault()
         @next()

       # Go to previous step after click on element with class .prev
-      $(document).on "click.bootstrap-tour", ".popover .prev", (e) =>
+      $(document).off("click.bootstrap-tour",".popover .pref").on "click.bootstrap-tour", ".popover .prev", (e) =>
         e.preventDefault()
         @prev()

       # End tour after click on element with class .end
-      $(document).on "click.bootstrap-tour", ".popover .end", (e) =>
+      $(document).off("click.bootstrap-tour",".popover .end").on "click.bootstrap-tour", ".popover .end", (e) =>
         e.preventDefault()
         @end()


Update various-pull-streams.coffee

Update various-pull-streams.coffee


Add check for when Language or Beautifier is not found in tests

--- spec/beautify-languages-spec.coffee
+++ spec/beautify-languages-spec.coffee
@@ -138,9 +138,15 @@
                         completionFun = (text) ->
                         #   logger.verbose(expectedTestPath, text) if ext is ".less"
                           expect(text instanceof Error).not.toEqual(true, text)
+                          return beautifyCompleted = true if text instanceof Error
                         #   if text instanceof Error
                         #     return beautifyCompleted = text # text == Error
-                          expect(typeof text).toEqual "string"
+
+                          expect(text).not.toEqual(null, "Language or Beautifier not found")
+                          return beautifyCompleted = true if text is null
+
+                          expect(typeof text).toEqual("string", "Text: #{text}")
+                          return beautifyCompleted = true if typeof text is "string"
                           # Check for beautification errors
                           if text isnt expectedContents
                             #   console.warn(allOptions, text, expectedContents)


Fixed auto-scroll to latest message when new activity appears.

--- app/assets/javascripts/backbone/helpers/channels.js.coffee
+++ app/assets/javascripts/backbone/helpers/channels.js.coffee
@@ -26,7 +26,7 @@
       $('.channels-pane').scrollTop($('.channels-pane').prop('scrollHeight'))

   @currentScrollPosition: (channelId) ->
-    $('channels-pane').scrollTop()
+    $('.channels-pane').scrollTop()

   @channelPane: (channelId) ->
     $("#channels-#{channelId}")


Revert 1d10e42

Revert 1d10e42

--- app/assets/javascripts/views/clouds/chat/message.js.coffee
+++ app/assets/javascripts/views/clouds/chat/message.js.coffee
@@ -80,10 +80,6 @@
     else
       content = content.replace(/\n$/ig,"").replace(/\n/ig,"
")

-    # Why not a comic sans message? :3c
-    if /\#ComicSansDale$/.test(content)
-      content = " #{content} "
-
     elem = $("

")
     elem.html(content)


shells: reorder methods for consistency

shells: reorder methods for consistency

--- coffee/src/shells/shell.coffee
+++ coffee/src/shells/shell.coffee
@@ -114,16 +114,16 @@
   controls: []

-  # Returns the view's total duration in seconds
-  duration: =>
-    @model.timeTotal()
-
-
   render: =>
     super
     if @options.readyOnRender
       @setMediaState 'ready'
     @
+
+
+  # Returns the view's total duration in seconds
+  duration: =>
+    @model.timeTotal()


Fixed code style in commit/pr 337b700

Fixed code style in commit/pr 337b700

--- main.coffee
+++ main.coffee
@@ -4,7 +4,7 @@
 os = require 'os'

 user = process.env.USER
-user = process.env.USERNAME if (os.platform() is "win32")
+user = process.env.USERNAME if os.platform() is "win32"

 awkward = vorpal()
        .delimiter user + '@awk'


Add defaults to the inner view models, so the UI doesn't choke on builds that are missing data

--- js/inner.coffee
+++ js/inner.coffee
@@ -1,6 +1,9 @@
 class Base

-  constructor: (json) ->
+  constructor: (json, defaults = {}) ->
+    for k,v of defaults
+      @[k] = ko.observable(v)
+
     for k,v of json
       @[k] = ko.observable(v)

@@ -16,7 +19,11 @@
 #TODO: next step is to add the vcs_url, which is why I was looking at the knockout.model and knockout.mapping plugin
 class Build extends Base
   constructor: (json) ->
-    super(json)
+    super json,
+      committer_email: null,
+      committer_name: null,
+      body: null,
+      subject: null

     @url = @komp =>
       "#{@project_path()}/#{@build_num()}"


added check for API KEYS in test script

added check for API KEYS in test script

--- test/tests.coffee
+++ test/tests.coffee
@@ -6,6 +6,10 @@
 {sparse}        = require '../src/sparse.coffee'
 jsonData        = require '../demo/data.json'
 server          = true
+
+if (typeof process.env.PARSE_APP_ID == 'undefined' or typeof process.env.PARSE_REST_KEY == 'undefined')
+  console.error 'Failure: PARSE_APP_ID and PARSE_REST_KEY are required to be set in your env vars to run tests'
+  process.exit 1

 describe 'sParse Test Suite', ->
   it 'should exist', =>


Start a new round when an old round ends.

--- src/match.coffee
+++ src/match.coffee
@@ -229,11 +229,14 @@

       if @_winner()
         @_reportResults lines
+        msg.send lines.join("\n")
         @_endMatch()
         return

     @round += 1
     msg.send lines.join("\n")
+    @state.reset()
+    @startRound msg

   # Internal: Someone tried a command in the wrong State. Chastise them appropriately.


artik5.coffee: Add jumpers instructions

artik5.coffee: Add jumpers instructions

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

--- artik5.coffee
+++ artik5.coffee
@@ -2,7 +2,7 @@
 { networkOptions, commonImg, instructions } = deviceTypesCommon

 BOARD_SHUTDOWN_ARTIK = 'The device has performed a shutdown. Press the power switch PWR SW to the off position.'
-SET_JUMPER_SD = 'Set SW2 dip switch to position 1:on, 2:on.'
+SET_JUMPER_SD = 'Set SW2 dip switch to position 1:on, 2:on.  Also, make sure jumpers J20 and J33 are set towards the edge of the board.'
 SET_JUMPER_EMMC = 'Set SW2 dip switch to position 1:off, 2:off.'
 BOARD_POWERON_ARTIK = 'Press the power switch PWR SW to the on position. Press and hold for 1 second the SW3 POWER push button.'


Update de-DE.coffee

Update de-DE.coffee

changed Master-Coder and Meisterprogrammierer to Master Coder

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -618,7 +618,7 @@
     subscribe_modal_title: "CodeCombat Premium"
     comparison_blurb: "Verbessere deine Fähigkeiten mit einem CodeCombat Abonnement" # {change}
     premium_pricing_prefix: "Hol dir Premium für nur"
-    premium_pricing_suffix: "und werde Master-Coder."
+    premium_pricing_suffix: "und werde Master Coder."
     premium: "Premium" # Make sure the following feature translations don't go onto two lines
     free: "Kostenlos"
     month: "Monat"
@@ -2166,7 +2166,7 @@

   code_play_create_account_modal:
     title: "DU HAST ES GESCHAFFT!" # This section is only needed in US, UK, Mexico, India, and Germany
-    body: "Du bist nun auf dem besten Weg, ein Meisterprogrammierer zu werden. Melde dich an und erhalte zusätzliche 100 EDELSTEINE sowie die Chance, 2.500 € UND ANDERE PREISE VON LENOVO ZU GEWINNEN."
+    body: "Du bist nun auf dem besten Weg, ein Master Coder zu werden. Melde dich an und erhalte zusätzliche 100 EDELSTEINE sowie die Chance, 2.500 € UND ANDERE PREISE VON LENOVO ZU GEWINNEN."
     sign_up: "MELDE DICH AN UND PROGRAMMIERE WEITER ▶"
     victory_sign_up_poke: "ERSTELLE EIN KOSTENLOSES KONTO, UM DEINEN CODE ZU SPEICHERN UND EINE CHANCE AUF DEN GEWINN VON PREISEN ZU HABEN!"
     victory_sign_up: "MELDE DICH AN FÜR EINE CHANCE AUF DEN GEWINN VON 2.500 €"


Code cleanup

Code cleanup

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -34,20 +34,8 @@
       height: ->
         $(".content").innerHeight() - parseInt($viewer.css('margin-bottom'), 10) - 5
       onLoad: (viewer) ->
-      #$.get "/40mmcube.gcode", (gcode) -> viewer.setGCode(gcode)
-      #viewer.loadModel("/ultimaker_platform.stl")
-      #viewer.loadModel("/40mmcube.stl")
-      #viewer.loadModel("/DNA_Righthanded.stl")
-      #viewer.loadModel("/CurveEdgeTest.amf")
-      #viewer.loadModel("/Rook_unzipped.amf")
-      #viewer.loadModel("/Sphere20Face.amf")
-      #viewer.loadModel("/cow-nonormals.obj")
-      #viewer.loadModel("/polysoup-fixed.stl")
-      #viewer.loadModel("/low-poly-ball.stl")
-      #viewer.loadModel("/10242-vert-ball.stl")
-      #viewer.loadModel("/40962-vert-ball.stl")
-      #viewer.loadModel("/high-poly-ball.stl")
-      #viewer.loadModel("/chair.stl")
+        #$.get "/40mmcube.gcode", (gcode) -> viewer.setGCode(gcode)
+        #viewer.loadModel("/ultimaker_platform.stl")

   #else
   if true


operations: syntax

operations: syntax

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -576,7 +576,7 @@
             fetchItemIds = (cb2) ->
                 t.getItemIds node, cb2

-        fetchItemIds, (err, ids) ->
+        fetchItemIds (err, ids) ->
             # Apply RSM
             ids = rsm.cropResults ids


Fix scrolling glitch caused by the last commit.

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -243,9 +243,13 @@
                 newArticleOffset = $("a[name='bottom']").offset()
             this.scrollDisabled = true
             objSelf = this
+            $(window).off("scroll", this._onScrollFn)
+            doneFn = () ->
+                objSelf.scrollDisabled = false
+                $(window).scroll(objSelf._onScrollFn)
             $('html, body').animate({
                 scrollTop: newArticleOffset.top - $("#top-nav-bar").height() - $("#ad-block").height() - $(".jumbotron").height()
-            }, 500, "swing", () -> objSelf.scrollDisabled = false)
+            }, 500, "swing", () -> setTimeout(doneFn, 0))
             e.preventDefault()

     _handleFeedKeys: (e) =>
@@ -303,9 +307,13 @@
                 newArticleOffset = $("a[name='bottom']").offset()
            this.scrollDisabled = true
            objSelf = this
+           $(window).off("scroll", this._onScrollFn)
+           doneFn = () ->
+               objSelf.scrollDisabled = false
+               $(window).scroll(objSelf._onScrollFn)
            $('html, body').animate({
                 scrollTop: newArticleOffset.top - $("#top-nav-bar").height() - $("#ad-block").height() - $(".jumbotron").height()
-           }, 500, "swing", () -> objSelf.scrollDisabled = false)
+           }, 500, "swing", () -> setTimeout(doneFn, 0))

     _initializeKeyboardNavigation: ->
         # Set up keyboard navigation
@@ -349,7 +357,7 @@
         # Initialize scroll handler (to enable "where you left off" scrolling if you're
         # switching between keyboard navigation and mouse navigation)
         objSelf = this
-        $(window).scroll(() ->
+        this._onScrollFn = () ->
             clearTimeout($.data(this, 'scrollTimer'));
             $.data(this, 'scrollTimer', setTimeout(() ->
                 if not objSelf.scrollDisabled


only copy known props

only copy known props

Maybe this is cause of memory leak?

--- configs/test/jest.setup.coffee
+++ configs/test/jest.setup.coffee
@@ -42,8 +42,7 @@
   combinedMatchers = Object.assign(chaiMatchers, originalMatchers)
   combinedMatchers

-
-for key, fn of originalExpect
-  global.expect[key] = fn
+for prop in ['extend', 'anything', 'any', 'arrayContaining', 'objectContaining', 'stringContaining', 'stringMatching']
+  global.expect[prop] = originalExpect[prop]

 require './matchers'


Fix config refactoring

Fix config refactoring

--- app/models/Level.coffee
+++ app/models/Level.coffee
@@ -120,7 +120,7 @@
           copy = $.extend true, {}, placeholderConfig
           programmableProperties = config?.programmableProperties ? []
           copy.programmableProperties = _.union programmableProperties, copy.programmableProperties ? []
-          config = _.merge copy, config
+          levelThangComponent.config = config = _.merge copy, config
         else if placeholderConfig.extraHUDProperties
           config.extraHUDProperties = _.union(config.extraHUDProperties ? [], placeholderConfig.extraHUDProperties)
         else if placeholderConfig.voiceRange  # Pull in voiceRange


typo in type signature for ExpOp

typo in type signature for ExpOp

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -73,7 +73,7 @@
         LogicalOrOp: null # :: Exprs -> Exprs -> LogicalOrOp
       ]
       MathsOps: [ null
-        ExpOp: null # :: Exprs -> Exprs -> PowOp
+        ExpOp: null # :: Exprs -> Exprs -> ExpOp
         DivideOp: null # :: Exprs -> Exprs -> DivideOp
         MultiplyOp: null # :: Exprs -> Exprs -> MultiplyOp
         RemOp: null # :: Exprs -> Exprs -> RemOp


windowcontroller: anything with a target is an external link

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -94,8 +94,10 @@
     # up with duplicate entries in history: e.g. /Activity and /Activity#
     # also so that we don't redirect the browser
     document.body.addEventListener 'click', (e)->
+      log e.target.target?.length
       isInternalLink = e.target?.nodeName.toLowerCase() is 'a' and\   # html nodenames are uppercase, so lowercase this.
-                       e.target.target isnt '_blank'                  # target _blank links should work as normal.
+                       e.target.target?.length is 0                      # targeted links should work as normal.
+                       # e.target.target isnt '_blank'                  # target _blank links should work as normal.
       if isInternalLink
         e.preventDefault()
         href = $(e.target).attr 'href'