Ensure win32 has parity with macOS for yardstick specs

--- spec/lines-yardstick-spec.coffee
+++ spec/lines-yardstick-spec.coffee
@@ -78,10 +78,16 @@
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 38, top: 0})
-      if process.platform is 'darwin' # One pixel off on left on Win32
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.875, top: 28})
+
+      switch process.platform
+        when 'darwin'
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.875, top: 28})
+        when 'win32'
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 42, top: 14})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 71, top: 14})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 280, top: 28})

     it "reuses already computed pixel positions unless it is invalidated", ->
       atom.styles.addStyleSheet """
@@ -134,28 +140,40 @@

       editor.setText(text)

-      return unless process.platform is 'darwin' # These numbers are 15 higher on win32 and always integer
-      expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
-      expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
-      expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
-
-    if process.platform is 'darwin' # Expectations fail on win32
-      it "handles lines containing a mix of left-to-right and right-to-left characters", ->
-        editor.setText('Persian, locally known as Parsi or Farsi (زبان فارسی), the predominant modern descendant of Old Persian.\n')
-
-        atom.styles.addStyleSheet """
-        * {
-          font-size: 14px;
-          font-family: monospace;
-        }
-        """
-
-        lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
-        linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 126, top: 0})
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 521, top: 0})
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 487, top: 0})
-        expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 873.625, top: 0})
+      switch process.platform
+        when 'darwin'
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
+        when 'win32'
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 245
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 252
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 259
+
+    it "handles lines containing a mix of left-to-right and right-to-left characters", ->
+      editor.setText('Persian, locally known as Parsi or Farsi (زبان فارسی), the predominant modern descendant of Old Persian.\n')
+
+      atom.styles.addStyleSheet """
+      * {
+        font-size: 14px;
+        font-family: monospace;
+      }
+      """
+
+      lineTopIndex = new LineTopIndex({defaultLineHeight: editor.getLineHeightInPixels()})
+      linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
+
+      switch process.platform
+        when 'darwin'
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 126, top: 0})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 521, top: 0})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 487, top: 0})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 873.625, top: 0})
+        when 'win32'
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 15))).toEqual({left: 120, top: 0})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 62))).toEqual({left: 496, top: 0})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 58))).toEqual({left: 464, top: 0})
+          expect(linesYardstick.pixelPositionForScreenPosition(Point(0, Infinity))).toEqual({left: 832, top: 0})

   describe "::screenPositionForPixelPosition(pixelPosition)", ->
     it "converts pixel positions to screen positions", ->
@@ -176,9 +194,14 @@
       expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
       expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
       expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
-      return unless process.platform is 'darwin' # Following tests are 1 pixel off on Win32
-      expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
-      expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
+
+      switch process.platform
+        when 'darwin'
+          expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
+          expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])
+        when 'win32'
+          expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 30])
+          expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 34])

     it "overshoots to the nearest character when text nodes are not spatially contiguous", ->
       atom.styles.addStyleSheet """


Actually register the trace.

Actually register the trace.

--- spec/navigation-view-spec.coffee
+++ spec/navigation-view-spec.coffee
@@ -26,6 +26,7 @@

   afterEach ->
     Stacktrace.getActivated()?.deactivate()
+    Stacktrace.clearRegistry()

   it 'attaches itself to the workspace', ->
     expect(view).not.toBeNull()
@@ -33,6 +34,7 @@
   describe 'with an active stacktrace', ->

     beforeEach ->
+      trace.register()
       trace.activate()

     it 'should be visible', ->
@@ -43,12 +45,10 @@
       expect(text).toEqual('Boom')

     it 'navigates back to the trace on a click', ->
-      promise = view.backToTrace()
-
-      waitsForPromise -> promise
+      waitsForPromise -> view.backToTrace()

       runs ->
-        expect(atom.workspaceView.getActiveView().hasClass '.stacktrace.traceview').toBeTruthy()
+        expect(atom.workspaceView.getActiveView().hasClass 'traceview').toBeTruthy()

     it 'deactivates the trace', ->
       view.deactivateTrace()


correct use of getCourseRole

correct use of getCourseRole

--- tutor/src/helpers/analytics.coffee
+++ tutor/src/helpers/analytics.coffee
@@ -42,7 +42,7 @@

   # Task steps are viewed by both teacher and student with no difference in params
   viewTaskStep:         ({courseId}) ->
-    role = CurrentUserStore.getCourseRole(courseId, true)
+    role = CurrentUserStore.getCourseRole(courseId, true).type
     "/#{role}/task-step/#{courseId}"


Correctly handle devMode

Correctly handle devMode

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -8,12 +8,12 @@
 console.log = require 'nslog'

 start = ->
+  args = parseCommandLine()
+
   setupUncaughtExceptionHandler()
-  setupAtomHome()
+  setupAtomHome(args)
   setupCompileCache()
   return if handleStartupEventWithSquirrel()
-
-  args = parseCommandLine()

   addPathToOpen = (event, pathToOpen) ->
     event.preventDefault()
@@ -56,21 +56,23 @@
 setupCrashReporter = ->
   crashReporter.start(productName: 'Atom', companyName: 'GitHub')

-isPortableInstall = ->
+isPortableInstall = (args) ->
   return false unless process.platform is 'win32'
   return false unless (process and process.type)
+  return false if args.devMode or args.testMode

   ourPath = process.execPath.toLowerCase()
   return (ourPath.indexOf(process.env.LOCALAPPDATA.toLowerCase()) is 0)

-setupAtomHome = ->
+setupAtomHome = (args) ->
   return if process.env.ATOM_HOME
+
   atomHome = path.join(app.getHomeDir(), '.atom')
   try
     atomHome = fs.realpathSync(atomHome)
     fs.statSync(atomHome)
   catch
-    atomHome = path.join(path.dirname(process.execPath), '.atom') if isPortableInstall()
+    atomHome = path.join(path.dirname(process.execPath), '.atom') if isPortableInstall(args)

   process.env.ATOM_HOME = atomHome


Fix wrong model and collection name

Fix wrong model and collection name

--- test/crud.test.coffee
+++ test/crud.test.coffee
@@ -10,7 +10,7 @@
   PostWithStringId = null
   PostWithStringKey = null
   PostWithNumberUnderscoreId = null
-  Names = null
+  Name = null

   before () ->
     db = getDataSource()
@@ -88,9 +88,9 @@
       content: { type: String }
     });

-    Names = db.define('Names', {}, {
+    Name = db.define('Name', {}, {
       arangodb: {
-        collection: 'names'
+        collection: 'Names'
       }
     });

@@ -433,14 +433,14 @@
   # MEMO: Import data present into data/users/names_100000.json before running this test.
   it 'cursor should returns all documents more then max single default size (1000) ', (done) ->

-    Names.find (err, names) ->
+    Name.find (err, names) ->
       should.not.exist(err)
       names.length.should.be.equal(100000)
       done()

   it 'cursor should returns all documents more then max single default cursor size (1000) and respect limit filter ', (done) ->

-    Names.find {limit: 1002}, (err, names) ->
+    Name.find {limit: 1002}, (err, names) ->
       should.not.exist(err)
       names.length.should.be.equal(1002)
       done()


tweak

tweak

--- game_of_life.coffee
+++ game_of_life.coffee
@@ -29,9 +29,7 @@
       if alive then cell.attr fill: alive_color else cell.attr fill: "#ffffff"

     world.draw = ->
-      for j in [0...num_rows]
-        for k in [0...num_columns]
-          @drawCell(j, k, world[j][k].alive)
+      @drawCell(j, k, world[j][k].alive) for k in [0...num_columns] for j in [0...num_rows]

     createRaphaelCell = (row, column, height, width) ->
       cell = paper.rect(column * width, row * height, width, height)


Remove --one flag from package spec command

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -54,14 +54,14 @@
       if process.platform in ['darwin', 'linux']
         options =
           cmd: appPath
-          args: ['--one', '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
+          args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"]
           opts:
             cwd: packagePath
             env: _.extend({}, process.env, ATOM_PATH: rootDir)
       else if process.platform is 'win32'
         options =
           cmd: process.env.comspec
-          args: ['/c', appPath, '--one', '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log"]
+          args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log"]
           opts:
             cwd: packagePath
             env: _.extend({}, process.env, ATOM_PATH: rootDir)


fix(spectron): Run against built version of the app to avoid timeouts

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -32,12 +32,19 @@
 module.exports = (grunt) ->

   grunt.registerTask 'run-spectron-specs', 'Run spectron specs', ->
-    rootDir = path.resolve('.')
-    appPath = path.resolve('./electron/Electron.app/Contents/MacOS/Electron')
+    shellAppDir = grunt.config.get('nylasGruntConfig.shellAppDir')
+    executablePath = path.join(shellAppDir, 'Contents', 'MacOS', 'Nylas')

     done = @async()
     npmPath = path.resolve "./build/node_modules/.bin/npm"
-    grunt.log.writeln 'App exists: ' + fs.existsSync(appPath)
+
+    if process.platform isnt 'darwin'
+      grunt.log.error("run-spectron-specs only works on Mac OS X at the moment.")
+      done(false)
+
+    if not fs.existsSync(executablePath)
+      grunt.log.error("run-spectron-specs requires the built version of the app at #{executablePath}")
+      done(false)

     process.chdir('./spectron')
     grunt.log.writeln "Current dir: #{process.cwd()}"
@@ -50,8 +57,8 @@
       else
         appArgs = [
           'test'
-          "APP_PATH=#{appPath}"
-          "APP_ARGS=#{rootDir}"
+          "APP_PATH=#{executablePath}"
+          "APP_ARGS="
         ]
         executeTests cmd: npmPath, args: appArgs, grunt, (succeeded) ->
           process.chdir('..')


fix indent

fix indent

--- gist-content.coffee
+++ gist-content.coffee
@@ -42,18 +42,18 @@
     console.log "ids", ids
     return
   # log to elastic search
-    summary =
-      script: "content"
-      timeouts: timeouts
-      filename: metaFile
-      ranAt: new Date()
-    client.index
-      index: 'bbindexer'
-      type: 'scripts'
-      body: summary
-    , (err, response) ->
-      console.log "indexed"
-      process.exit()
+  summary =
+    script: "content"
+    timeouts: timeouts
+    filename: metaFile
+    ranAt: new Date()
+  client.index
+    index: 'bbindexer'
+    type: 'scripts'
+    body: summary
+  , (err, response) ->
+    console.log "indexed"
+    process.exit()

 gistCloner = (gist, gistCb) ->
   # I wanted to actually clone all the repositories but it seems to be less reliable.


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -19,7 +19,7 @@
   player.update()

   player.I.x
-
+  # =>


GR-LIST: Fix issue where selected granules persist after their layer is removed

--- app/assets/javascripts/modules/map/leaflet-plugins/granule_layer.js.coffee
+++ app/assets/javascripts/modules/map/leaflet-plugins/granule_layer.js.coffee
@@ -337,8 +337,15 @@
       map.off 'edsc.stickygranule', @_onStickyGranule
       @_resultsSubscription.dispose()
       @_results = null
-      @_granuleFocusLayer?.onRemove(@_map)
-      @_granuleStickyLayer?.onRemove(@_map)
+      @_granuleFocusLayer?.onRemove(map)
+      @_granuleFocusLayer = null
+      @_granuleStickyLayer?.onRemove(map)
+      @_granuleStickyLayer = null
+
+      if @_restoreBounds
+        map.fitBounds(@_restoreBounds)
+        @_restoreBounds = null
+

     url: ->
       super() if @_hasGibs


Tabs in index.coffee changed to spaces

Tabs in index.coffee changed to spaces

--- index.coffee
+++ index.coffee
@@ -4,5 +4,5 @@

 generator = new ExpressionGenerator()
 for i in [1...30]
-	expression = generator.generate i
-	console.log i, expression, InputSolver.compute expression
+  expression = generator.generate i
+  console.log i, expression, InputSolver.compute expression


Support for optional number of arguments.

--- index.coffee
+++ index.coffee
@@ -13,6 +13,7 @@

 represent = (ast, contentType, options, cb) ->
+  if typeof options is 'function' then cb = options
   key = selectFormat contentType, Object.keys formats

   if key


Renderer 'New' static method support added

--- index.coffee
+++ index.coffee
@@ -626,7 +626,7 @@

 		rendererCtor = Renderer[elem.name.split('.')[0]]
 		if rendererCtor?
-			r = "new Renderer.#{elem.name}(_c, #{json})\n"
+			r = "Renderer.#{elem.name}.New(_c, #{json})\n"
 		else
 			r = "Renderer.Component.getCloneFunction(#{elem.name}, '#{elem.name}')(_c, #{json})\n"
 		if visibleId


cleanup

cleanup

--- index.coffee
+++ index.coffee
@@ -283,7 +283,7 @@
 #
 headerLogin = (req, res, next) ->
   return next() if req.path is opts.logoutPath
-  if token = req.get('X-Access-Token')
+  if token = req.get('X-Access-Token') or req.query.access_token
     req.login new opts.CurrentUser(accessToken: token), next
   else
     next()


i care deeply about how files end

i care deeply about how files end

--- index.coffee
+++ index.coffee
@@ -1 +1 @@
-module.exports = require './lib/quest'+module.exports = require './lib/quest'


Update category list again when games load

Since categories without installed games are hidden, no categories were
shown if categories loaded before games did.

--- src/client/games.coffee
+++ src/client/games.coffee
@@ -206,6 +206,7 @@
     .toArray()
     .do (d) ->
       global.Games = d
+      updateCategorySelect()
     .flatMap _.identity
     .do makeGamesStreamObserver()
     .observeOn Rx.Scheduler.currentThread


Change menu item text to "Show/Hide Invisible Files"

--- client/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
+++ client/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
@@ -231,10 +231,10 @@
       Collapse                    :
         action                    : 'collapse'
         separator                 : yes
-      'Hide dot Files'            :
+      'Hide Invisible Files'      :
         action                    : 'hideDotFiles'
         separator                 : yes
-      'Show dot Files'            :
+      'Show Invisible Files'      :
         action                    : 'showDotFiles'
         separator                 : yes
       'New File'                  :
@@ -252,9 +252,9 @@

     fc = KD.getSingleton 'finderController'
     if fc.isNodesHiddenFor fileData.vmName
-      delete items['Hide dot Files']
-    else
-      delete items['Show dot Files']
+      delete items['Hide Invisible Files']
+    else
+      delete items['Show Invisible Files']

     return items


Minor improvements of the grammar for Linguist

A large update is planned later...

--- grammars/fstar.cson
+++ grammars/fstar.cson
@@ -2,16 +2,16 @@

 'name': 'F*'

-'fileTypes': ['fst', 'fsi', 'fs', 'fs7', 'fsti']
+'fileTypes': [ 'fst', 'fsti', 'fs7' ]

 'patterns': [
   {
     'name'  : 'keyword.control.fstar'
-    'match' : '\\b(assume|assert|let|in|if|then|else|fun|rec|function)\\b'
+    'match' : '\\b(assume|admit|assert|let|in|if|then|else|fun|rec|function)\\b'
   }
   {
     'name'  : 'keyword.other.fstar'
-    'match' : '\\b(opaque|logic|requires|ensures|modifies|match|with|and|kind|\
+    'match' : '\\b(opaque|noeq|inline_for_extraction|logic|requires|ensures|modifies|match|with|and|kind|\
                   type|val|open|module|effect|default|new_effect|sub_effect|\
                   total|private|begin|end|decreases)\\b'
   }
@@ -33,7 +33,7 @@
   }
   {
       'name'  : 'constant.language.monad.fstar'
-      'match' : '\\b(Pure|PURE|Tot|STATE|ST|St|ALL|All|EXN|Exn|Ex|\
+      'match' : '\\b(Pure|PURE|Tot|STATE|ST|St|Stack|ALL|All|EXN|Exn|Ex|\
                      DIV|Div|GHOST|Ghost|GTot|Lemma)\\b'
   }
   {


Footnote support

Footnote support

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -143,7 +143,7 @@
       # { include: '#commandDefinitions' }
       { include: '#metaFontStyling' } # eg. \emph
       # { include: '#marginpar' }
-      # { include: '#footnote' }
+      { include: '#metaFootnote' }
       # { include: '#metaReferences' } # maybe include \ref \label as well?
       # { include: '#verbScantokens' } # not sure what this is for
       # { include: '#constants' } # words that represent constants
@@ -354,6 +354,34 @@
       }
     ]
   }
+  metaFootnote: {
+    patterns: [
+      {
+        match: '(\\\\)footnotemark(?![a-zA-Z@])'
+        captures:
+          0:
+            name: 'support.function.footnote.footnotemark.latex'
+          1:
+            name: 'punctuation.definition.function.latex'
+      }
+      {
+        begin: '((\\\\)footnote(?:text)?)\\s*(\\{)'
+        beginCaptures:
+          1:
+            name: 'support.function.footnote.latex'
+          2:
+            name: 'punctuation.definition.function.latex'
+          3:
+            name: 'punctuation.definition.argument.begin.latex'
+        end: '\\}'
+        endCaptures:
+          0:
+            name: 'punctuation.definition.argument.end.latex'
+        contentName: 'meta.footnote.latex'
+        patterns: [{ include: '$self' }]
+      }
+    ]
+  }
 # Other
   escapedCharacter: {
     comment: 'Finds escaped characters like \$ and \%. Uses this regex so unique capture groups can be used.'


correct order of arguments passed to a function call

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -453,15 +453,17 @@
         'name': 'punctuation.definition.parameters.end.stata'
     'patterns': [
       {
-        'include': '$self'
+        'include': '#builtin_functions'
+      }
+      {
+        'include': '#other_functions'
       }
       {
         'match': '[A-Za-z_][A-Za-z0-9_]{0,31}'
         'name': 'variable.parameter.function.stata'
       }
       {
-        'match': ','
-        'name': 'punctuation.definition.variable.begin.stata'
+        'include': '$self'
       }
     ]
   'other_functions':
@@ -478,15 +480,17 @@
         'name': 'punctuation.definition.parameters.end.stata'
     'patterns': [
       {
-        'include': '$self'
+        'include': '#builtin_functions'
+      }
+      {
+        'include': '#other_functions'
       }
       {
         'match': '[A-Za-z_][A-Za-z0-9_]{0,31}'
         'name': 'variable.parameter.function.stata'
       }
       {
-        'match': ','
-        'name': 'punctuation.definition.variable.begin.stata'
+        'include': '$self'
       }
     ]
   'builtin_types':


More comments tweaks. See #90

More comments tweaks. See #90

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -866,12 +866,16 @@
         'beginCaptures':
           '0':
             'name': 'punctuation.definition.comment.begin.stata'
-        'end': '(\\*/\\*[^\\n]*)|(\\*/)'
+        'end': '(\\*/\\s+\\*[^\\n]*)|(\\*/(?!\\*))'
         'endCaptures':
           '0':
             'name': 'punctuation.definition.comment.end.stata'
         'name': 'comment.block.stata'
         'patterns': [
+          {
+            'match': '\\*/\\*'
+            'comment': 'this ends and restarts a comment block. but need to catch this so that it doesn\'t start _another_ level of comment blocks'
+          }
           {
             'include': '#docblockr-comment'
           }
@@ -921,9 +925,6 @@
         'patterns': [
           {
             'include': '#docblockr-comment'
-          }
-          {
-            'include': '#comments'
           }
         ]
       }


fix test

fix test

--- test/chickendance_test.coffee
+++ test/chickendance_test.coffee
@@ -4,8 +4,8 @@

 expect = chai.expect

-describe 'Business Cat:', ->
-  businesscat_module = require('../src/businesscat')
+describe 'Chicken dance:', ->
+  businesscat_module = require('../src/chickendance')

   beforeEach ->
     @robot =
@@ -16,7 +16,7 @@
       random: sinon.spy()
     @businesscat_module = businesscat_module(@robot)

-  describe 'summon a business cat', ->
+  describe 'display chicken dance gif', ->

     it 'registers a respond listener', ->
       expect(@robot.respond).to.have.been.calledWith(/chicken dance/)


CLI the 'activate' command fix to make it overwrite existing links

--- cli/commands/activate.coffee
+++ cli/commands/activate.coffee
@@ -40,7 +40,7 @@
   if !path.existsSync("#{location}/#{version}")
     print_error "Could't find version '#{version}' of '#{package}'"

-  system("cd #{location}; ln -s #{version} active")
+  system("cd #{location}; rm -f active; ln -s #{version} active")

 exports.help = (args)->


Fixed Android APK path

Fixed Android APK path

--- cli/tasks/run/android.coffee
+++ cli/tasks/run/android.coffee
@@ -10,7 +10,7 @@

 module.exports = (options, callback = ->) ->
     callbackCalled = false
-    mode = if options.release then 'release' else 'develop'
+    mode = if options.release then 'release' else 'debug'

     logLine = log.line().timer().repeat().loading 'Installing APK...'
     packageFile = JSON.parse fs.readFileSync('./package.json', 'utf-8')
@@ -21,7 +21,7 @@
         sdkDir = process.env.ANDROID_HOME

     adbPath = "#{sdkDir}/platform-tools/adb"
-    apkFileName = 'app-universal-debug.apk'
+    apkFileName = "app-universal-#{mode}.apk"

     if options.deviceSerialNumber
         adbPath += " -s #{options.deviceSerialNumber}"
@@ -29,12 +29,12 @@
     adb = logcat = null

     dir = "build/android/app/build/outputs/apk"
-    cmd = "#{adbPath} install -r #{dir}/#{apkFileName}"
+    cmd = "#{adbPath} install -r #{dir}/#{mode}/#{apkFileName}"
     adb = cp.exec cmd, (err) ->
         if err
             logLine.error 'Cannot install APK', err
             logLine.stop()
-            apkFiles = cp.execSync "ls -l #{dir}"
+            apkFiles = cp.execSync "ls -l #{dir}/*"
             log.debug "Available APK files: #{apkFiles}"
             callback err
             return


[master] printer

[master] printer

--- utils/round-robin-dispatcher.coffee
+++ utils/round-robin-dispatcher.coffee
@@ -40,7 +40,7 @@

   status: ->
     s = _.sum @counter
-    console.log ((i / s).toFixed(2) for i in @counter).join ' '
+    process.stdout.write ((i / s).toFixed(2) for i in @counter).join ' '

 module.exports = RoundRobinDispatcher


fix is_el_visible & performance improvement for sense_viewport_visibility_changes

--- @client/viewport_visibility_sensor.coffee
+++ @client/viewport_visibility_sensor.coffee
@@ -3,11 +3,11 @@
 update_scheduled = false

-window.is_el_visible = (el, scrollY, innerHeight, slop) ->
+window.is_el_visible = (el, scrollY, viewport_height, slop) ->
   slop ?= 1
   slop -= 1
   el_top = -scrollY
-  h = innerHeight
+  vh = viewport_height

   parent = el
   while (true)
@@ -16,13 +16,15 @@
       break
     parent = parent.offsetParent

-  el_bottom = el_top + h
+  el_bottom = el_top + vh

-  topVisible =    -slop * h < el_top && el_top < h * 2 * slop
-  bottomVisible = -slop * h < el_bottom && el_bottom < h * 2 * slop
+  topVisible =    el_top    > -slop * vh  &&  el_top    < vh + slop * vh
+  bottomVisible = el_bottom > -slop * vh  &&  el_bottom < vh + slop * vh
+
   visible = topVisible || bottomVisible

   visible
+

 sense_viewport_visibility_changes = ->
@@ -32,16 +34,55 @@
     scrollY = window.scrollY
     innerHeight = window.innerHeight

-    # if performance tuning, this could be improved by doing a binary search in els
-    # (assuming sorted by offsetY) to find the visible elements more efficiently
-    for el in els
-      slop = parseInt(el.getAttribute('data-receive-viewport-visibility-updates') or 1)
-      visible = is_el_visible el, scrollY, innerHeight, slop
+    el_sets = {}
+    for el in els
+      slop = parseFloat(el.getAttribute('data-receive-viewport-visibility-updates') or 1)
+      name = el.getAttribute 'data-visibility-name'
+      key = "#{name}-#{slop}"

-      state = fetch el.getAttribute 'data-component'
-      if !state.in_viewport? || state.in_viewport != visible
-        state.in_viewport = visible
-        save state
+      el_sets[key] ?= []
+      el_sets[key].push el
+
+
+    # Use previous visibility values to find one visible element, then figure out which elements
+    # around it are also visible. We assume that the elements are sorted by global
+    # offset Y here. This is usually true, but we can get in trouble. Set
+    # data-visibility-name if there's a problem with the assumption.
+    for __, els of el_sets
+      visibility = {}
+      # slop will be same for all elements in this set
+      slop = parseFloat(els[0].getAttribute('data-receive-viewport-visibility-updates') or 1)
+      len = els.length
+      start_here = Math.floor(len / 2)
+      found_visible = false
+      for el, idx in els
+        state = fetch el.getAttribute 'data-component'
+        if state.in_viewport
+          visibility[idx] = is_el_visible els[idx], scrollY, innerHeight, slop
+          if visibility[idx]
+            start_here = idx
+            found_visible = true
+
+
+      # look for visible elements around this element
+      for step in [1,-1]
+        inc = start_here
+
+        while inc >= 0 && inc < len
+          visibility[inc] ?= is_el_visible els[inc], scrollY, innerHeight, slop
+          if !visibility[inc] && found_visible
+            break
+          inc += step
+
+      # set visibility of elements
+      for el, idx in els
+        visible = !!visibility[idx]
+        state = fetch el.getAttribute 'data-component'
+        # el.setAttribute 'data-in-viewport', visible
+        if !state.in_viewport? || state.in_viewport != visible
+          state.in_viewport = visible
+          save state
+

   update_scheduled = false


Refactoring and improving resetWatchers watcher-selector (broken)

--- denorm.coffee
+++ denorm.coffee
@@ -89,7 +89,17 @@
                     'selector._id.*DOLLAR*in': $in: [instanceId]
                 ]

-            addClauses = (selectorPrefix, oldSubValues, newSubValues) =>
+            # We'll collect all the changed subFieldNames for later
+            changedSubFieldSpecSet = {} # changedSubFieldSpec: true
+
+            addSelectorClauses = (fieldSpecPrefix, oldSubValues, newSubValues) =>
+                ###
+                    1. Append clauses to subFieldSelectorMatcher so it restricts the
+                       set of matched watchers to ones that ever returned this doc.
+
+                    2. Mutate changedSubFieldNameSet for later
+                ###
+
                 # TODO: Handle more selectors besides just default (equality) and $in
                 # e.g. $gt, $gte, $lt, $lte

@@ -100,56 +110,76 @@
                     subFieldNameSet[subFieldName] = true

                 for subFieldName of subFieldNameSet
-                    if selectorPrefix is 'selector'
-                        selectorPath = "selector.#{J.Model.escapeDot subFieldName}"
-                    else
-                        selectorPath = "#{selectorPrefix}*DOT*#{J.Model.escapeDot subFieldName}"
-
                     oldValue = oldSubValues[subFieldName]
                     newValue = newSubValues[subFieldName]

+                    if not EJSON.equals oldValue, newValue
+                        changedSubFieldSpecSet[subFieldName] = true
+
+                    fieldSpec = fieldSpecPrefix.concat [subFieldName]
+                    selectorKey = "selector.#{fieldSpec.map(J.Model.escapeDot).join('*DOT*')}"
+
                     term = $or: [{}, {}]
-                    term.$or[0][selectorPath] =
+                    term.$or[0][selectorKey] =
                         $in: [null]
-                    term.$or[1]["#{selectorPath}.*DOLLAR*in"] =
+                    term.$or[1]["#{selectorKey}.*DOLLAR*in"] =
                         $elemMatch: $in: []

                     if oldValue?
-                        term.$or[0][selectorPath].$in.push oldValue
-                        term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push oldValue
+                        term.$or[0][selectorKey].$in.push oldValue
+                        term.$or[1]["#{selectorKey}.*DOLLAR*in"].$elemMatch.$in.push oldValue
                         if _.isArray oldValue then for elem in oldValue
-                            term.$or[0][selectorPath].$in.push elem
-                            term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push elem
+                            term.$or[0][selectorKey].$in.push elem
+                            term.$or[1]["#{selectorKey}.*DOLLAR*in"].$elemMatch.$in.push elem
                     if newValue? and not EJSON.equals oldValue, newValue
-                        term.$or[0][selectorPath].$in.push newValue
-                        term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push newValue
+                        term.$or[0][selectorKey].$in.push newValue
+                        term.$or[1]["#{selectorKey}.*DOLLAR*in"].$elemMatch.$in.push newValue
                         if _.isArray newValue then for elem in newValue
-                            term.$or[0][selectorPath].$in.push elem
-                            term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push elem
+                            term.$or[0][selectorKey].$in.push elem
+                            term.$or[1]["#{selectorKey}.*DOLLAR*in"].$elemMatch.$in.push elem

                     subFieldSelectorMatcher.push term

                     if J.util.isPlainObject(oldValue) or J.util.isPlainObject(newValue)
-                        addClauses(
-                            selectorPath
+                        addSelectorClauses(
+                            fieldSpec
                             if J.util.isPlainObject(oldValue) then oldValue else {}
                             if J.util.isPlainObject(newValue) then newValue else {}
                         )

-            addClauses 'selector', mutableOldValues, mutableNewValues
+            addSelectorClauses [], mutableOldValues, mutableNewValues
+
+            for changedSubFieldSpec of changedSubFieldSpecSet
+                oldValue = J.util.getField mutableOldValues, changedSubFieldSpec.join('.')
+                newValue = J.util.getField mutableNewValues, changedSubFieldSpec.join('.')
+
+                # TODO

             watcherMatcher =
                 modelName: modelName
                 $or: [
                     selector: $in: [null, instanceId]
                 ,
-                    $and: subFieldSelectorMatcher
+                    $and: [
+                        # The watcher's selector has a shot at ever matching
+                        # the old or new document
+                        $and: subFieldSelectorMatcher
+                    ,
+                        $or: [
+                            # The watcher's set of docIds might have changed
+                            # because its selector mentions a changed subfield
+                            $or: changedSubfieldSelectorMatcher
+                        ,
+                            # The watcher's set of docIds might have changed
+                            # because its sort key mentions a changed subfield
+                            $or: changedSubfieldSortSpecMatcher
+                        ,
+                            # The watcher's set of field values might have changed
+                            # because its projection mentions a changed subfield
+                            $or: changedSubfieldProjectionMatcher
+                        ]
+                    ]
                 ]
-
-            # TODO: Add this to watcherMatcher:
-            #   fields: // smart logic that rules out watchers that
-            #              don't care about any of the fields that
-            #              were changed from oldValues to newValues

             watcherMatcher

@@ -225,7 +255,7 @@
                         if numWatchersReset
                             console.log "    <#{watcherModelName}>.#{watcherReactiveName}:
                                 #{numWatchersReset} watchers reset"
-                            # console.log "selector: #{JSON.stringify selector, null, 4}"
+                            console.log "selector: #{JSON.stringify selector, null, 4}"
             )


Denorm dependency tracking bugfix

Denorm dependency tracking bugfix

--- denorm.coffee
+++ denorm.coffee
@@ -69,10 +69,16 @@

         # Makes sure every fieldSpec in he watching-selector is consistent
         # with either oldValues or newValues
-        subFieldSelectorMatcher =
+        subFieldSelectorMatcher = [
             selector: $type: 3 # object
-        subFieldSelectorMatcher["selector._id"] = $in: [null, instanceId]
-        subFieldSelectorMatcher["selector._id*DOT*#{J.Model.escapeDot '$in'}"] = $in: [null, instanceId]
+        ]
+
+        subFieldSelectorMatcher.push
+            $or: [
+                'selector._id': $in: [null, instanceId]
+            ,
+                'selector._id.*DOLLAR*in': $in: [instanceId]
+            ]

         addClauses = (selectorPrefix, oldSubValues, newSubValues) =>
             subFieldNameSet = {}
@@ -90,14 +96,18 @@
                 oldValue = oldSubValues[subFieldName]
                 newValue = newSubValues[subFieldName]

-                subFieldSelectorMatcher[selectorPath] = $in: [null]
-                subFieldSelectorMatcher["#{selectorPath}*DOT*#{J.Model.escapeDot '$in'}"] = $in: [null]
+                term = $or: [{}, {}]
+                term.$or[0][selectorPath] = $in: [null]
+                term.$or[1]["#{selectorPath}.*DOLLAR*in"] = $in: []
+
                 if oldValue?
-                    subFieldSelectorMatcher[selectorPath].$in.push oldValue
-                    subFieldSelectorMatcher["#{selectorPath}*DOT*#{J.Model.escapeDot '$in'}"].$in.push oldValue
+                    term.$or[0][selectorPath].$in.push oldValue
+                    term.$or[1]["#{selectorPath}.*DOLLAR*in"].$in.push oldValue
                 if newValue? and not EJSON.equals oldValue, newValue
-                    subFieldSelectorMatcher[selectorPath].$in.push newValue
-                    subFieldSelectorMatcher["#{selectorPath}*DOT*#{J.Model.escapeDot '$in'}"].$in.push newValue
+                    term.$or[0][selectorPath].$in.push newValue
+                    term.$or[1]["#{selectorPath}.*DOLLAR*in"].$in.push newValue
+
+                subFieldSelectorMatcher.push term

                 if J.util.isPlainObject(oldValue) or J.util.isPlainObject(newValue)
                     addClauses(
@@ -126,7 +136,7 @@
                             $or: [
                                 selector: $in: [null, instanceId]
                             ,
-                                subFieldSelectorMatcher
+                                $and: subFieldSelectorMatcher
                             ]

                     console.log "***<#{watcherModelName}>.#{reactiveName}***"


Proper test for ObjectExpression references

--- test/object-expression.coffee
+++ test/object-expression.coffee
@@ -25,13 +25,15 @@
                                 type: 'Identifier'
                                 name: 'foo'
                             value:
-                                type: 'Literal'
-                                value: 'bar'
-                                raw: 'bar'
+                                type: 'Identifier'
+                                name: 'a'
                         }]
                 }]
             }]

         scope = escope.analyze(ast).scopes[0]
-
-        # TODO - Verify results.  What am I looking for?
+        expect(scope.variables).to.have.length(1)
+        expect(scope.references).to.have.length(2)
+        expect(scope.variables[0].name).to.be.equal('a')
+        expect(scope.references[0].identifier.name).to.be.equal('a')
+        expect(scope.references[1].identifier.name).to.be.equal('a')


Rename command bank to code bank (#89)

Rename command bank to code bank (#89)

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -775,7 +775,7 @@
     watch_a_video: "Watch a video on __concept_name__"
     concept_unlocked: "Concept Unlocked"
     use_at_least_one_concept: "Use at least one concept: "
-    command_bank: "Command Bank"
+    command_bank: "Code Bank" # {change}
     learning_goals: "Learning Goals"
     start: "Start"
     vega_character: "Vega Character"


Update Brian job title

Update Brian job title

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -1109,7 +1109,7 @@
     chelsea_title: "Operations Manager"
     claire_title: "Executive Assistant"
     bobby_title: "Game Designer"
-    brian_title: "Lead Game Designer"
+    brian_title: "Senior Game Design Manager" # {change}
     andrew_title: "Software Engineer"
     stephanie_title: "Customer Support Specialist"
     rob_title: "Sales Development Representative"


change comments

change comments

--- app/models/task.coffee
+++ app/models/task.coffee
@@ -1,9 +1,8 @@

 # DestroyTask corresponding to given condition
-# TODO optimise deletion : each deletion requires on request.
 Task.destroySome = (condition, callback) ->

-    # Replace this with async lib call.
+    # TODO Replace this with async lib call.
     wait = 0
     error = null
     done = (err) ->


add call the callback...

add call the callback...

--- app/models/task.coffee
+++ app/models/task.coffee
@@ -236,7 +236,7 @@
                     previousTask.save callback
                 else
                     task.previousTask = null
-                    task.save()
+                    task.save callback
         else
             callback null


Update fi.coffee

Update fi.coffee

--- app/locale/fi.coffee
+++ app/locale/fi.coffee
@@ -47,7 +47,7 @@
 #    get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science."
 #    request_demo: "Request a Demo"
 #    setup_a_class: "Set Up a Class"
-    have_an_account: "Onko sinulla jo käyttäjä?"
+    have_an_account: "Onko sinulla jo käyttäjätunnus?"
     logged_in_as: "Olet kirjautunut sisään käyttäjänä"
 #    computer_science: "Our self-paced courses cover basic syntax to advanced concepts"
 #    show_me_lesson_time: "Show me lesson time estimates for:"
@@ -60,13 +60,13 @@
 #    winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
 #    run_class: "Everything you need to run a computer science class in your school today, no CS background required."
 #    goto_classes: "Go to My Classes"
-#    view_profile: "View My Profile"
-#    view_progress: "View Progress"
-#    go_to_courses: "Go to My Courses"
+    view_profile: "Tarkastele profiiliani"
+    view_progress: "Tarkastele edistymistäni"
+    go_to_courses: "Siirry omiin kursseihin"
     want_coco: "Haluatko CodeCombat:in kouluusi?"

   nav:
-#    map: "Map"
+    map: "Kartta"
     play: "Tasot" # The top nav bar entry where players choose which levels to play
     community: "Yhteisö"
     courses: "Kurssit"
@@ -152,60 +152,60 @@
     adjust_volume: "Aänenvoimakkuus"
     campaign_multiplayer: "Moninpeli Areenat"
     campaign_multiplayer_description: "... joissa mittaat ohjelmointitaitosi toisia pelaajia vastaan."
-#    brain_pop_done: "You’ve defeated the Ogres with code! You win!"
-#    brain_pop_challenge: "Challenge yourself to play again using a different programming language!"
-#    replay: "Replay"
-#    back_to_classroom: "Back to Classroom"
-
-#  code:
-#    if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
-#    else: "else"
-#    elif: "else if"
-#    while: "while"
-#    loop: "loop"
-#    for: "for"
-#    break: "break"
-#    continue: "continue"
-#    pass: "pass"
-#    return: "return"
-#    then: "then"
-#    do: "do"
-#    end: "end"
-#    function: "function"
-#    def: "define"
-#    var: "variable"
-#    self: "self"
-#    hero: "hero"
-#    this: "this"
-#    or: "or"
-#    "||": "or"
-#    and: "and"
-#    "&&": "and"
-#    not: "not"
-#    "!": "not"
-#    "=": "assign"
-#    "==": "equals"
+    brain_pop_done: "Pieksit örkit koodilla! Voitto!"
+    brain_pop_challenge: "Haasta itsesi ja pelaa uudelleen eri ohjelmointikielellä!"
+    replay: "Uudelleentoisto"
+    back_to_classroom: "Takaisin luokkahuoneeseen"
+
+  code:
+    if: "jos" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
+    else: "muussa tapauksessa"
+    elif: "else if - muussa tapauksessa, jos"
+    while: "niin kauan kuin"
+    loop: "silmukka"
+    for: "näille"
+    break: "keskeytä silmukka"
+    continue: "ohita ja jatka silmukkaa"
+    pass: "pass"
+    return: "palauta"
+    then: "sitten"
+    do: "tee näin"
+    end: "loppu"
+    function: "funktio"
+    def: "määrittele"
+    var: "muuttuja"
+    self: "itse"
+    hero: "sankari"
+    this: "tämä"
+    or: "tai (jompi kumpi)"
+    "||": "tai (jompi kumpi)"
+    and: "ja (sekä että)"
+    "&&": "ja (sekä että)"
+    not: "ei"
+    "!": "ei"
+    "=": "aseta"
+    "==": "ovat samat (yhtäsuuret)"
 #    "===": "strictly equals"
-#    "!=": "does not equal"
-#    "!==": "does not strictly equal"
-#    ">": "is greater than"
-#    ">=": "is greater than or equal"
-#    "<": "is less than"
-#    "<=": "is less than or equal"
-#    "*": "multiplied by"
-#    "/": "divided by"
-#    "+": "plus"
-#    "-": "minus"
-#    "+=": "add and assign"
-#    "-=": "subtract and assign"
-#    True: "True"
-#    true: "true"
-#    False: "False"
-#    false: "false"
-#    undefined: "undefined"
+    "!=": "ovat eri (erisuuruuset)"
+#    "!==": "not strictly equals"
+    ">": "suurempi kuin"
+    ">=": "suurempi tai yhtäsuuri kuin"
+    "<": "pienempi kuin"
+    "<=": "pienempi tai yhtäsuuri kuin"
+    "*": "kertomerkki"
+    "/": "jakoviiva"
+    "+": "plus"
+    "-": "miinus"
+    "+=": "summaa ja aseta muuttujalle uusi arvo"
+    "-=": "vähennä ja aseta muuttujalle uusi arvo"
+    True: "Totta"
+    true: "totta"
+    False: "Epätotta"
+    false: "epätotta"
+    undefined: "määrittelemätön"
 #    null: "null"
 #    nil: "nil"
-#    None: "None"
+    None: "Olematon"

   share_progress_modal:
     blurb: "Edistyt loistavasti! Kerro ystävillesi mitä olet oppinut CodeCombat:n avulla."
@@ -305,7 +305,7 @@
 #    teacher_list_resource_hub_4: "Resource Hub"
 #    teacher_list_resource_hub_5: "for curriculum guides, activities, and more!"
 #    teacher_additional_questions: "That’s it! If you need additional help or have questions, reach out to __supportEmail__."
-#    dont_use_our_email_silly: "Don't put our email here! Put your parent's email."
+    dont_use_our_email_silly: "Laita vanhempiesi sähköposti tähän. Ei omaasi."

   recover:
     recover_account_title: "Palauta tili"
@@ -322,17 +322,17 @@

   common:
     back: "Takaisin" # When used as an action verb, like "Navigate backward"
-#    go_back: "Go Back"
+    go_back: "Siirry takaisin"
     coming_soon: "Tulossa pian!"
     continue: "Jatka" # When used as an action verb, like "Continue forward"
     next: "Seuraava"
 #    default_code: "Default Code"
     loading: "Ladataan..."
 #    overview: "Overview"
-#    processing: "Processing..."
-#    solution: "Solution"
-#    table_of_contents: "Table of Contents"
-#    intro: "Intro"
+    processing: "Prosessoidaan..."
+    solution: "Ratkaisu"
+    table_of_contents: "Sisällysluettelo"
+    intro: "Johdanto"
     saving: "Tallennetaan..."
     sending: "Lähetetään..."
     send: "Lähetä"
@@ -430,7 +430,7 @@
     years: "vuotta"

   play_level:
-#    back_to_map: "Back to Map"
+    back_to_map: "Takaisin karttaan"
 #    directions: "Directions"
     edit_level: "Muokkaa tasoa"
 #    explore_codecombat: "Explore CodeCombat"
@@ -572,15 +572,15 @@
 #    tip_good_idea: "The best way to have a good idea is to have a lot of ideas. - Linus Pauling"
 #    tip_programming_not_about_computers: "Computer Science is no more about computers than astronomy is about telescopes. - Edsger Dijkstra"
 #    tip_mulan: "Believe you can, then you will. - Mulan"
-#    project_complete: "Project Complete!"
-#    share_this_project: "Share this project with friends or family:"
-#    ready_to_share: "Ready to publish your project?"
-#    click_publish: "Click \"Publish\" to make it appear in the class gallery, then check out what your classmates built! You can come back and continue to work on this project. Any further changes will automatically be saved and shared with your classmates."
-#    already_published_prefix: "Your changes have been published to the class gallery."
-#    already_published_suffix: "Keep experimenting and making this project even better, or see what the rest of your class has built! Your changes will automatically be saved and shared with your classmates."
-#    view_gallery: "View Gallery"
-#    project_published_noty: "Your level has been published!"
-#    keep_editing: "Keep Editing"
+    project_complete: "Projekti valmis!"
+    share_this_project: "Jaa tämä projekti ystävien tai perheen kanssa:"
+    ready_to_share: "Oletko valmis julkaisemaan projektisi?"
+    click_publish: "Klikkaa \"Publish\" jotta se ilmestyy luokan galleriaan, käy sitten katsomassa mitä luokkakaverisi ovat rakentaneet! Voit tulla takaisin ja jatkaa tätä projektia. Tulevat muutoksesi tallennetaan ja jaetaan automaattisesti luokkatoveriesi kanssa."
+    already_published_prefix: "Muutoksesi ollaan tallennettu luokan galleriaan."
+    already_published_suffix: "Jatka kokeilemista ja tee tästä projektista jopa vielä parempi tai käy katsomassa mitä luokkakaverisi ovat rakentaneet! Tulevat muutoksesi tallennetaan ja jaetaan automaattisesti luokkatoveriesi kanssa."
+    view_gallery: "Katso galleriaa"
+    project_published_noty: "Tasosi on julkaistu!"
+    keep_editing: "Jatka muokkaamista"

 #  play_game_dev_level:
 #    created_by: "Created by {{name}}"
@@ -838,13 +838,13 @@
     restricted_to_certain_heroes: "Voit pelata tason vain tietyillä sankareilla."

   skill_docs:
-#    function: "function" # skill types
-#    method: "method"
-#    snippet: "snippet"
-#    number: "number"
-#    array: "array"
-#    object: "object"
-#    string: "string"
+    function: "funktio" # skill types
+    method: "metodi"
+    snippet: "pätkä"
+    number: "numero"
+    array: "taulukko"
+    object: "olio"
+    string: "merkkijono"
     writable: "kirjoitettava" # Hover over "attack" in Your Skills while playing a level to see most of this
     read_only: "vain luettavaksi"
     action: "Toiminto"
@@ -1290,11 +1290,11 @@
     teacher: "Opettaja"
     arena: "Areena"
     available_levels: "Saatavilla olevat tasot"
-#    started: "started"
-#    complete: "complete"
+    started: "aloitettu"
+    complete: "valmis"
 #    practice: "practice"
-#    required: "required"
-#    welcome_to_courses: "Adventurers, welcome to Courses!"
+    required: "pakollinen"
+    welcome_to_courses: "Seikkailijat, tervetuloa kursseille!"
     ready_to_play: "Valmiina pelaamaan?"
     start_new_game: "Aloita uusi peli"
 #    play_now_learn_header: "Play now to learn"


add more translations

add more translations

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -479,7 +479,7 @@
     reload_title: "Recommencer tout le code ?"
     reload_really: "Êtes-vous sûr de vouloir recommencer ce niveau et retourner au début ?"
     reload_confirm: "Tout recommencer"
-#    test_level: "Test Level"
+    test_level: "Niveau de Test"
     victory: "Victoire"
     victory_title_prefix: ""
     victory_title_suffix: " Terminé"
@@ -513,8 +513,8 @@
     skip_tutorial: "Passer (esc)"
     keyboard_shortcuts: "Raccourcis clavier"
     loading_start: "Démarrer niveau"
-#    loading_start_combo: "Start Combo Challenge"
-#    loading_start_concept: "Start Concept Challenge"
+    loading_start_combo: "Démarrer le Défi Combinaison"
+    loading_start_concept: "Démarrer le Défi Concept"
     problem_alert_title: "Corriger votre code"
     time_current: "Maintenant :"
     time_total: "Max :"
@@ -590,37 +590,37 @@
     tip_self_taught: "Je me suis enseigné 90% de ce que j'ai appris. Et c'est normal! - Hank Green"
     tip_luna_lovegood: "Ne t'en fais pas, tu es aussi sain que moi. - Luna Lovegood"
     tip_good_idea: "La meilleure façon d'avoir une bonne idée est d'avoir beaucoup d'idées. - Linus Pauling"
-#    tip_programming_not_about_computers: "Computer Science is no more about computers than astronomy is about telescopes. - Edsger Dijkstra"
-#    tip_mulan: "Believe you can, then you will. - Mulan"
-#    project_complete: "Project Complete!"
-#    share_this_project: "Share this project with friends or family:"
-#    ready_to_share: "Ready to publish your project?"
-#    click_publish: "Click \"Publish\" to make it appear in the class gallery, then check out what your classmates built! You can come back and continue to work on this project. Any further changes will automatically be saved and shared with your classmates."
-#    already_published_prefix: "Your changes have been published to the class gallery."
-#    already_published_suffix: "Keep experimenting and making this project even better, or see what the rest of your class has built! Your changes will automatically be saved and shared with your classmates."
-#    view_gallery: "View Gallery"
-#    project_published_noty: "Your level has been published!"
-#    keep_editing: "Keep Editing"
-
-#  apis:
-#    methods: "Methods"
-#    events: "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"
-
-#  amazon_hoc:
-#    title: "Keep Learning with Amazon!"
+    tip_programming_not_about_computers: "L'Informatique a aussi peu de rapport avec les ordinateurs que l'astronomie avec les téléscopes. - Edsger Dijkstra"
+    tip_mulan: "Crois que tu peux, et tu pourras. - Mulan"
+    project_complete: "Projet Completé!"
+    share_this_project: "Partage ce Projet avec tes amis ou ta famille:"
+    ready_to_share: "Prêt à publier ton projet?"
+    click_publish: "Clic \"Publier\" pour le faire apparaître dans la gallerie de la classe, puis regarde ce que tes camarades de classe ont construit! Tu peux revenir travailler sur ce projet. Tout changement sera automatiquement sauvé et partagé avec tes camarades de classe."
+    already_published_prefix: "Tes changements ont été publiés dans la gallerie de la classe."
+    already_published_suffix: "Continue à expérimenter et améliorer ce projet, ou regarde ce que le reste de ta classe a construit! Tes changements seront automatiquement sauvés et partagés avec tes camarades de classe."
+    view_gallery: "Vue de la Galerie"
+    project_published_noty: "Votre niveau a été publié!"
+    keep_editing: "Continuer l'édition"
+
+  apis:
+    methods: "Méthodes"
+    events: "Evénements"
+    spawnable: "Instanciable"
+    html: "HTML"
+    math: "Math"
+    array: "Tableau"
+    object: "Objet"
+    string: "Chaîne de caractères"
+    function: "Fonction"
+    vector: "Vecteur"
+    date: "Date"
+    jquery: "jQuery"
+    json: "JSON"
+    number: "Nombre"
+    webjavascript: "JavaScript"
+
+  amazon_hoc:
+    title: "Continuer ) apprendre avec Amazon!"
 #    congrats: "Congratulations on conquering that challenging Hour of Code!"
 #    educate_1: "Now, keep learning about coding and cloud computing with AWS Educate, an exciting, free program from Amazon for both students and teachers. With AWS Educate, you can earn cool badges as you learn about the basics of the cloud and cutting-edge technologies such as gaming, virtual reality, and Alexa."
 #    educate_2: "Learn more and sign up here"
@@ -631,19 +631,19 @@
 #    future_eng_5: "program which creates learning and work opportunities for all K-12 students in the United States who wish to pursue computer science."

   play_game_dev_level:
-#    created_by: "Created by {{name}}"
-#    created_during_hoc: "Created during Hour of Code"
-#    restart: "Restart Level"
+    created_by: "Crée par {{name}}"
+    created_during_hoc: "Crée durant l'Heure de Code"
+    restart: "Recommencer le Niveau"
     play: "Jouer le niveau"
-#    play_more_codecombat: "Play More CodeCombat"
-#    default_student_instructions: "Click to control your hero and win your game!"
-#    goal_survive: "Survive."
-#    goal_survive_time: "Survive for __seconds__ seconds."
-#    goal_defeat: "Defeat all enemies."
-#    goal_defeat_amount: "Defeat __amount__ enemies."
-#    goal_move: "Move to all the red X marks."
-#    goal_collect: "Collect all the items."
-#    goal_collect_amount: "Collect __amount__ items."
+    play_more_codecombat: "Jouer à plus de Combats de code"
+    default_student_instructions: "Cliquer pour contrôler votre héro et remporter la victoire!"
+    goal_survive: "Survivre."
+    goal_survive_time: "Survivre pendant __seconds__ secondes."
+    goal_defeat: "Battre tous les  ennemis."
+    goal_defeat_amount: "Battre __amount__ ennemis."
+    goal_move: "Se déplacer en passant par toutes les marques rouges X."
+    goal_collect: "Collecter tous les eléments."
+    goal_collect_amount: "Collecter __amount__ eléments."

   game_menu:
     inventory_tab: "Inventaire"
@@ -668,16 +668,16 @@
     day: "Aujourd'hui"
     week: "Cette semaine"
     all: "Tous les temps"
-#    latest: "Latest"
+    latest: "Dernier"
     time: "Temps"
     damage_taken: "Dégât subis"
     damage_dealt: "Dégât infligés"
     difficulty: "Difficulté"
     gold_collected: "Or collecté"
-#    survival_time: "Survived"
-#    defeated: "Enemies Defeated"
-#    code_length: "Lines of Code"
-#    score_display: "__scoreType__: __score__"
+    survival_time: "a survécu"
+    defeated: "Ennemis Battus"
+    code_length: "Lignes de Code"
+    score_display: "__scoreType__: __score__"

   inventory:
     equipped_item: "Equipé"
@@ -706,19 +706,19 @@
     prompt_button: "Entrer dans la boutique"
     recovered: "Gemmes précédemment achetées récupérées. Merci de rafraîchir la page."
     price: "x{{gems}} / mo"
-#    buy_premium: "Buy Premium"
-#    purchase: "Purchase"
-#    purchased: "Purchased"
-
-#  earn_gems:
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Keep playing to earn more!"
+    buy_premium: "Acheter la mise à niveau Premium"
+    purchase: "achat"
+    purchased: "Acheté"
+
+  earn_gems:
+    prompt_title: "Pas assez de gemmes"
+    prompt_body: "Continue à jouer pour en gagner plus!"

   subscribe:
-#    best_deal: "Best Deal!"
-#    confirmation: "Congratulations! You now have a CodeCombat Premium Subscription!"
-#    premium_already_subscribed: "You're already subscribed to Premium!"
-#    subscribe_modal_title: "CodeCombat Premium"
+    best_deal: "Meilleure Proposition!"
+    confirmation: "Félicitations! Vous avez maintenant souscrit à CodeCombat Premium !"
+    premium_already_subscribed: "Vous avez déjà souscrit à l'option Premium!"
+    subscribe_modal_title: "CodeCombat Premium"
     comparison_blurb: "Aiguisez vos compétences avec un abonnement CodeCombat !" # {change}
     must_be_logged: "Vous devez être identifié. Veuillez créer un compte ou vous identifier depuis le menu ci-dessus."
     subscribe_title: "Abonnement" # Actually used in subscribe buttons, too
@@ -731,7 +731,7 @@
     sorry_to_see_you_go: "Désolé de te voir partir ! Fais-nous savoir ce que nous aurions pu faire mieux"
     unsubscribe_feedback_placeholder: "Nom de zeus, qu'avons nous fait ?"
     stripe_description: "Inscription mensuelle"
-#    buy_now: "Buy Now"
+    buy_now: "Acheter Maintenant"
     subscription_required_to_play: "Vous avez besoin d'un abonnement pour jouer à ce niveau."
     unlock_help_videos: "Abonnez vous pour débloquer tous les tutoriels vidéo."
     personal_sub: "Abonnement individuel" # Accounts Subscription View below
@@ -740,7 +740,7 @@
     will_be_cancelled: "Ceci va être annulé"
     currently_free: "Vous avez un abonnement gratuit en ce moment"
     currently_free_until: "Vous avez un abonnement gratuit jusqu'à"
-#    free_subscription: "Free subscription"
+    free_subscription: "Inscirption gratuite"
     was_free_until: "Vous aviez un abonnement gratuit jusqu'à"
     managed_subs: "Gestion des abonnements"
     subscribing: "S'inscrit..."
@@ -748,49 +748,49 @@
     unsubscribing: "Desincription en cours..." # {change}
     subscribe_prepaid: "Cliquer S'abonner pour utiliser du code prépayé"
     using_prepaid: "Utiliser le code prépayé pour un abonnement mensuel"
-#    feature_level_access: "Access 300+ levels available"
-#    feature_heroes: "Unlock exclusive heroes and pets"
-#    feature_learn: "Learn to make games and websites"
-#    month_price: "$__price__"
-#    first_month_price: "Only $__price__ for your first month!"
-#    lifetime: "Lifetime Access"
-#    lifetime_price: "$__price__"
-#    year_subscription: "Yearly Subscription"
-#    year_price: "$__price__/year"
-#    support_part1: "Need help with payment or prefer PayPal? Email"
-#    support_part2: "support@codecombat.com"
-
-#  announcement:
-#    now_available: "Now available for subscribers!"
-#    subscriber: "subscriber"
-#    cuddly_companions: "Cuddly Companions!" # Pet Announcement Modal
-#    kindling_name: "Kindling Elemental"
-#    kindling_description: "Kindling Elementals just want to keep you warm at night. And during the day. All the time, really."
-#    griffin_name: "Baby Griffin"
-#    griffin_description: "Griffins are half eagle, half lion, all adorable."
-#    raven_name: "Raven"
-#    raven_description: "Ravens are excellent at gathering shiny bottles full of health for you."
-#    mimic_name: "Mimic"
-#    mimic_description: "Mimics can pick up coins for you. Move them on top of coins to increase your gold supply."
-#    cougar_name: "Cougar"
-#    cougar_description: "Cougars like to earn a PhD by Purring Happily Daily."
-#    fox_name: "Blue Fox"
-#    fox_description: "Blue foxes are very clever and love digging in the dirt and snow!"
-#    pugicorn_name: "Pugicorn"
-#    pugicorn_description: "Pugicorns are some of the rarest creatures and can cast spells!"
-#    wolf_name: "Wolf Pup"
-#    wolf_description: "Wolf pups excel in hunting, gathering, and playing a mean game of hide-and-seek!"
-#    ball_name: "Red Squeaky Ball"
-#    ball_description: "ball.squeak()"
-#    collect_pets: "Collect pets for your heroes!"
-#    each_pet: "Each pet has a unique helper ability!"
-#    upgrade_to_premium: "Become a {{subscriber}} to equip pets."
-#    play_second_kithmaze: "Play {{the_second_kithmaze}} to unlock the Wolf Pup!"
-#    the_second_kithmaze: "The Second Kithmaze"
-#    keep_playing: "Keep playing to discover the first pet!"
-#    coming_soon: "Coming soon"
-#    ritic: "Ritic the Cold" # Ritic Announcement Modal
-#    ritic_description: "Ritic the Cold. Trapped in Kelvintaph Glacier for countless ages, finally free and ready to tend to the ogres that imprisoned him."
+    feature_level_access: "Accès à plus de 300 niveaux disponibles"
+    feature_heroes: "Débloquer des Héros et des animaux de compagnie exclusifs"
+    feature_learn: "Apprendre à faire des jeux et des sites web"
+    month_price: "__price__$"
+    first_month_price: "Seulement __price__$ pour votre premier mois!"
+    lifetime: "Accès à vie"
+    lifetime_price: "__price__$"
+    year_subscription: "Souscription Annuelle"
+    year_price: "__price__$/an"
+    support_part1: "Besoin d'aide pour le paiement ou préféres-tu Paypal? Mél"
+    support_part2: "support@codecombat.com"
+
+  announcement:
+    now_available: "Maintenant disponible pour les abonnés!"
+    subscriber: "s'abonner"
+    cuddly_companions: "Les Companions du doudou!" # Pet Announcement Modal
+    kindling_name: "Elémentaire d'embrasement"
+    kindling_description: "Les Elémentaires d'embrasement veulent juste vous réchauffer la nuit. Et durant le jour.Tout le temps, vraiment."
+    griffin_name: "Bébé Griffon"
+    griffin_description: "Les Griffons sont moitié aigle, moitié lion, complètement adorables."
+    raven_name: "Corbeau"
+    raven_description: "Les corbeaux sont excellents dans la récupération de magnifiques bouteilles pleines de santé pour vous."
+    mimic_name: "Singe"
+    mimic_description: "Les Singes peuvent prendre des pièces pour vous. Déplacez-les au dessus des pièces pour augmenter votre approvisionnement en or."
+    cougar_name: "Cougar"
+    cougar_description: "Les Cougars aiment bien suivre un Doctorat en ronronnant joyeusement tous les jours."
+    fox_name: "Renard bleu"
+    fox_description: "Les Renards bleus sont très intelligents et aiment creuser dans la saleté et la neige!"
+    pugicorn_name: "Carlicorne" #carlicorne est la fusion de carlin (pug en anglais), et licorne (unicorn en anglais)
+    pugicorn_description: "Les Carlicornes sont parmi les créatures les plus rareset peuvent lancer des sorts!"
+    wolf_name: "Louveteau"
+    wolf_description: "Les louveteaux excellent dans la chasse, la récupération, et le jeu de cache-cache!"
+    ball_name: "Ball grinçante rouge"
+    ball_description: "ball.squeak()"
+    collect_pets: "Collectionne les animaux de compagnie pour ton héros!"
+    each_pet: "Chaque animal de compagnie a une capacité d'aide unique!"
+    upgrade_to_premium: "Devient un {{subscriber}} pour équiper les animaux de compagnie."
+    play_second_kithmaze: "Joue {{the_second_kithmaze}} pour débloquer le louveteau!"
+    the_second_kithmaze: "Le Second Kithmaze"
+    keep_playing: "Continue à jouer pour découvrir le premier animal de compagnie!"
+    coming_soon: "bientôt disponible"
+    ritic: "Ritic le glacial" # Ritic Announcement Modal
+    ritic_description: "Ritic le glacial. Piégé dans le Glacier de Kelvintaph depuis des temps immémoriaux, finalement libre prêt à s'occuper des ogres qui l'ont emprisonné."
 #    ice_block: "A block of ice"
 #    ice_description: "There appears to be something trapped inside..."
 #    blink_name: "Blink"
@@ -835,7 +835,7 @@
     javascript_blurb: "Le langage du web. (Pas le même que Java.)"
     coffeescript_blurb: "Une syntaxe Javascript plus agréable."
     lua_blurb: "Langage de script de jeu."
-#    java_blurb: "(Subscriber Only) Android and enterprise."
+    java_blurb: "(Abonnés seulement) Android et entreprise."
     status: "Statut"
     weapons: "Arme"
     weapons_warrior: "Epées - Courte portée, pas de magie"


load using versionedId

load using versionedId

--- src/components/app.cjsx
+++ src/components/app.cjsx
@@ -20,11 +20,11 @@

   componentWillMount: ->
     @props.location.startListening(@onHistoryChange)
-    {view, id} = @props.location.getCurrentUrlParts()
-    if id is 'new'
+    {view, versionedId} = @props.location.getCurrentUrlParts()
+    if versionedId is 'new'
       @setState(newId: @createNewRecord(view))
     else
-      @loadRecord(view, id)
+      @loadRecord(view, versionedId)

   loadRecord: (type, id) ->
     return unless type and id
@@ -39,11 +39,11 @@

   onHistoryChange: (location) ->
     @setState(location: location)
-    {view, id} = @props.location.getCurrentUrlParts()
-    if id is 'new'
+    {view, versionedId} = @props.location.getCurrentUrlParts()
+    if versionedId is 'new'
       @setState(newId: @createNewRecord(view))
     else
-      @loadRecord(view, id)
+      @loadRecord(view, versionedId)

   onNewRecord: (type, ev) ->
     ev.preventDefault()


Add comment

Add comment

--- src/Commander.coffee
+++ src/Commander.coffee
@@ -220,7 +220,8 @@
           register.call @
     @

-
+  # Makes expression observe a given subselector
+  # inside nodes found by the query
   bindRootSubselector: (root, o, subselector) ->
     root.subselector = subselector
     root.spawn = (id, node, originalId, q) =>


flattened selector commands, cleaner spec logging, scoping still broken from sel flattening

--- spec/scoper.coffee
+++ spec/scoper.coffee
@@ -42,9 +42,9 @@
   # var hoisting raw commands
   # ====================================================================

-  describe "var hoisting raw commands", ->
-
-    hoistTest 'hoist 1 level root before',
+  describe "/ var hoisting raw commands", ->
+
+    hoistTest '/ hoist 1 level root before',
         commands:
           [
             ['==',['get','foo'],100]
@@ -66,7 +66,7 @@
           ]

-    hoistTest 'hoist 1 level root after',
+    hoistTest '/ hoist 1 level root after',
         commands:
           [
             ['rule',['.','box'],
@@ -87,7 +87,7 @@
             ['==',['get','foo'],100]
           ]

-    hoistTest 'hoist 2 level before',
+    hoistTest '/ hoist 2 level before',
         commands:
           [
             ['==',['get','foo'],0]
@@ -119,7 +119,7 @@
           ]

-    hoistTest 'hoist 2 level root after',
+    hoistTest '/ hoist 2 level root after',
         commands:
           [
             ['rule',['.','box'],
@@ -151,7 +151,7 @@
           ]

-    hoistTest 'DONT already hoisted 2 level root after',
+    hoistTest '/ DONT already hoisted 2 level root after',
         commands:
           [
             ['rule',['.','box'],
@@ -183,7 +183,7 @@
           ]

-    hoistTest 'conditionals',
+    hoistTest '/ conditionals',
         commands:
           [
             ['==',['get','foo'],0]
@@ -256,9 +256,9 @@
   # virtual hoisting raw commands
   # ====================================================================

-  describe "virtual hoisting raw commands", ->
-
-    hoistTest 'hoist 1 level root before',
+  describe "/ virtual hoisting raw commands", ->
+
+    hoistTest '/ hoist 1 level root before',
         commands:
           [
             ['==',['get',['virtual','zone'],'foo'],100]
@@ -279,7 +279,7 @@
             ]
           ]

-    hoistTest 'hoist 1 level root after',
+    hoistTest '/ hoist 1 level root after',
         commands:
           [
             ['rule',['.','box'],
@@ -300,7 +300,7 @@
             ['==',['get',['virtual','zone'],'foo'],100]
           ]

-    hoistTest 'virtual defined in ruleset selector',
+    hoistTest '/ virtual defined in ruleset selector',
         commands:
           [
             ['rule',['.','box'],
@@ -338,18 +338,18 @@
           ]

-    hoistTest 'Dont hoist root',
-        commands:
-          [
-            ['==',['get',['virtual','zone'],'foo'],100]
-          ]
-      ,
-        commands:
-          [
-            ['==',['get',['virtual','zone'],'foo'],100]
-          ]
-
-    hoistTest 'Dont consider ruleset selector child scope',
+    hoistTest '/ Dont hoist root',
+        commands:
+          [
+            ['==',['get',['virtual','zone'],'foo'],100]
+          ]
+      ,
+        commands:
+          [
+            ['==',['get',['virtual','zone'],'foo'],100]
+          ]
+
+    hoistTest '/ Dont consider ruleset selector child scope',
         commands:
           [
             ['==',['get',['virtual','zone'],'foo'],100]
@@ -370,7 +370,7 @@
             ]
           ]

-    hoistTest '4 level',
+    hoistTest '/ 4 level',
         commands:
           [
             ['rule', ['.','ready']
@@ -424,9 +424,9 @@
   # manual & auto hoisting source equivalence
   # ====================================================================

-  describe "manual & auto hoisting source equivalence", ->
-
-    equivalent "1 level basic",
+  describe "/ manual & auto hoisting source equivalence", ->
+
+    equivalent "/ 1 level basic",
       """
         foo == bar;
         .box {
@@ -440,7 +440,7 @@
         }
       """

-    equivalent "3 level with virtuals",
+    equivalent "/ 3 level with virtuals",
       """
         .wrap {
           my-size == 100;
@@ -480,7 +480,7 @@
         }
       """

-    equivalent "3 level moderate",
+    equivalent "/ 3 level moderate",
       """

         @if foo > 20 {


Update ko.coffee (#5645)

Update ko.coffee (#5645)

* Update ko.coffee

* Update ko.coffee

* Update ko.coffee

* Update ko.coffee

--- app/locale/ko.coffee
+++ app/locale/ko.coffee
@@ -1,34 +1,34 @@
 module.exports = nativeDescription: "한국어", englishDescription: "Korean", translation:

   new_home:
-#    title: "CodeCombat - Coding games to learn Python and JavaScript"
-#    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."
-#    built_for_teachers_title: "A Coding Game Built with Teachers in Mind"
-#    built_for_teachers_blurb: "Teaching kids to code can often feel overwhelming. CodeCombat helps all educators teach students how to code in either JavaScript or Python, two of the most popular programming languages. With a comprehensive curriculum that includes six computer science units and reinforces learning through project-based game development and web development units, kids will progress on a journey from basic syntax to recursion!"
-#    built_for_teachers_subtitle1: "Computer Science"
-#    built_for_teachers_subblurb1: "Starting with our free Introduction to Computer Science course, students master core coding concepts such as while/for loops, functions, and algorithms."
-#    built_for_teachers_subtitle2: "Game Development"
-#    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_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"
-#    century_skills_subblurb1: "With coding puzzles that are naturally scaffolded into increasingly challenging levels, CodeCombat's programming game ensures kids are always practicing critical thinking."
-#    century_skills_quote2: "Everyone else was making mazes, so I thought, ‘capture the flag’ and that’s what I did."
-#    century_skills_subtitle2: "Creativity"
-#    century_skills_subblurb2: "CodeCombat encourages students to showcase their creativity by building and sharing their own games and webpages."
-#    century_skills_quote3: "If I got stuck on a level. I would work with people around me until we were all able to figure it out."
-#    century_skills_subtitle3: "Collaboration"
-#    century_skills_subblurb3: "Throughout the game, there are opportunities for students to collaborate when they get stuck and to work together using our pair programming guide."
-#    century_skills_quote4: "I’ve always had aspirations of designing video games and learning how to code ... this is giving me a great starting point."
-#    century_skills_subtitle4: "Communication"
-#    century_skills_subblurb4: "Coding requires kids to practice new forms of communication, including communicating with the computer itself and conveying their ideas using the most efficient code."
-#    classroom_in_box_title: "We Strive To:"
-#    classroom_in_box_blurb1: "Engage every student so that they believe coding is for them."
-#    classroom_in_box_blurb2: "Empower any educator to feel confident when teaching coding."
-#    classroom_in_box_blurb3: "Inspire all school leaders to create a world-class computer science program."
+    title: "CodeCombat - Python과 JavaScript를 배울수 있는 코딩 게임"
+    meta_description: "프로그래밍 게임을 통해 타이핑된 코드를 배우십시오. 퍼즐을 풀면서 Python, JavaScript 및 HTML을 배우고 자신만의 코딩 게임 및 웹 사이트를 만드는 방법을 배우십시오."
+    built_for_teachers_title: "선생님들의 마음을 생각한 코딩 게임"
+    built_for_teachers_blurb: "아이들에게 코드를 가르치는 것은 종종 어렵게 느껴질 수 있습니다. CodeCombat은 모든 교육자가 학생들에게 가장 인기 있는 프로그래밍 언어 중 하나인 JavaScript 또는 Python으로 코드를 짤수있는 방법을 가르칠 수 있도록 도와줍니다. 6개의 컴퓨터 사이언스 유닛과 프로젝트기반 게임 개발 및 웹 개발 유닛들을 통해 학습을 증진시키는 종합적인 교육과정을 통하여, 아이들은 기본 구문을 시작으로 재구성으로의 여정을 진행할 것입니다!"
+    built_for_teachers_subtitle1: "컴퓨터 사이언스 (Computer Science)"
+    built_for_teachers_subblurb1: "컴퓨터 사이언스 소개 무료강좌를 시작으로 학생들은 루프, 기능, 알고리즘 등의 핵심 코딩 개념을 숙달합니다."
+    built_for_teachers_subtitle2: "게임 개발 (Game Development)"
+    built_for_teachers_subblurb2: "학생들은 미로를 구성하고 기본 입력 처리를 사용하여 친구 및 가족과 공유할 수 있는 자신의 게임을 코드화합니다."
+    built_for_teachers_subtitle3: "웹 개발 (Web Development)"
+    built_for_teachers_subblurb3: "HTML, CSS 및 jQuery를 사용하여 학생들은 자신의 웹 페이지를 친구들과 공유할 사용자 지정 URL로 프로그래밍하기 위해 창의력을 발휘합니다."
+    century_skills_title: "21세기 기술들"
+    century_skills_blurb1: "학생들은 단순히 그들의 영웅을 레벨업 시키는것이 아니라, 그들 스스로를 레벨업 시킵니다"
+    century_skills_quote1: "당신은 망쳤죠... 그러면 가능한 모든 방법을 생각해보고 다시 시도해보세요. 열심히 노력하지 않고는 이곳에 도착할 수 없을 거예요"
+    century_skills_subtitle1: "비판적 사고"
+    century_skills_subblurb1: "코딩 퍼즐은 자연스럽게 점점 더 도전적인 수준으로 되기 때문에, CodeCombat의 프로그래밍 게임은 아이들이 항상 비판적 사고를 연습하도록 보장합니다."
+    century_skills_quote2: "다른 사람들은 모두 미로를 만들고 있었기 때문에, 저는 '깃발 잡기'를 생각했고,다른사람과 다르게 저는 그것을 만들었죠!"
+    century_skills_subtitle2: "창의성"
+    century_skills_subblurb2: "CodeCombat은 학생들이 자신만의 게임과 웹 페이지를 만들고 공유함으로써 그들의 창의성을 보여주도록 장려합니다."
+    century_skills_quote3: "만약 제가 레벨에 갇혔다면요. 우리 모두가 해결할 수 있을 때까지 저는 제 주변 사람들과 함께 일할거에요."
+    century_skills_subtitle3: "협력"
+    century_skills_subblurb3: "게임 내내, 학생들이 게임이 막혔을 때 협력할 수 있고, 저희 페어 프로그래밍 가이드를 사용하여 함께 일할 수 있는 기회가 있습니다."
+    century_skills_quote4: "저는 항상 비디오 게임을 디자인하고 코딩하는 법을 배우고 싶었습니다. 이것은 저에게 좋은 출발을 할 수 있게 도와줍니다."
+    century_skills_subtitle4: "소통"
+    century_skills_subblurb4: "코딩은 아이들이 컴퓨터 자체와 통신하고 가장 효율적인 코드를 사용하여 아이디어를 전달하는 등 새로운 형태의 의사소통을 요구 합니다."
+    classroom_in_box_title: "저희는 노력합니다:"
+    classroom_in_box_blurb1: "모든 학생들이 코딩이 자신 스스로를 위한 것이라고 생각하게 합니다."
+    classroom_in_box_blurb2: "선생님이 코딩을 가르칠 때 자신감을 갖도록 합니다."
+    classroom_in_box_blurb3: "모든 학교 지도자들에게 세계적인 수준의 컴퓨터 공학 프로그램을 만들게 수 있게 영감을 줍니다."
 #    creativity_rigor_title: "Where Creativity Meets Rigor"
 #    creativity_rigor_subtitle1: "Make coding fun and teach real-world skills"
 #    creativity_rigor_blurb1: "Students type real Python and JavaScript while playing games that encourage trial-and-error, critical thinking, and creativity. Students then apply the coding skills they’ve learned by developing their own games and websites in project-based courses."
@@ -36,8 +36,8 @@
 #    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_title2: "Awards & Partners"
+    featured_partners_title1: "특집 기사"
+    featured_partners_title2: "수상 & 파트너"
 #    featured_partners_blurb1: "CollegeBoard Endorsed Provider"
 #    featured_partners_blurb2: "Best Creativity Tool for Students"
 #    featured_partners_blurb3: "Top Pick for Learning"
@@ -66,18 +66,18 @@
 #    for_teachers_subblurb4: "Engage all learners in a diverse classroom with practice levels that adapt to each student's learning needs."
 #    game_based_blurb: "CodeCombat is a game-based computer science program where students type real code and see their characters react in real time."
 #    get_started: "Get started"
-#    global_title: "Join Our Global Community of Learners and Educators"
-#    global_subtitle1: "Learners"
-#    global_subtitle2: "Lines of Code"
-#    global_subtitle3: "Teachers"
-#    global_subtitle4: "Countries"
+    global_title: "저희의 세계적인 선생님과 학생 커뮤니티에 참여하세요"
+    global_subtitle1: "학생들"
+    global_subtitle2: "작성된 코드 줄"
+    global_subtitle3: "선생님들"
+    global_subtitle4: "국가들"
 #    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_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!"
-#    see_example: "See example"
+    see_example: "예제 보기"
     slogan: "코딩을 배우는 가장 매력적인 방법!" # {change}
 #    teach_cs1_free: "Teach CS1 Free"
 #    teachers_love_codecombat_title: "Teachers Love CodeCombat"
@@ -89,7 +89,7 @@
 #    classroom_edition: "Classroom Edition:"
     learn_to_code: "코딩 배우기:"
     play_now: "지금 시작하기"
-#    im_an_educator: "I'm an Educator"
+    im_an_educator: "교사입니다"
     im_a_teacher: "선생님입니다"
     im_a_student: "학생입니다"
     learn_more: "더 알아보기"
@@ -133,13 +133,13 @@
     get_started_title: "오늘 수업을 준비해보세요"
     get_started_subtitle: "수업을 준비하고, 학생을 추가하고, 학생들의 컴퓨터 공학 실력 향상을 관찰해보세요."
     request_demo: "데모 요청하기"
-#    request_quote: "Request a Quote"
+    request_quote: "수업 견적을 봐보기"
     setup_a_class: "수업 준비하기"
     have_an_account: "계정이 있으십니까?"
     logged_in_as: "당신은 다음 계정으로 로그인되어있습니다."
 #    computer_science: "Our self-paced courses cover basic syntax to advanced concepts"
     ffa: "모든 학생들에게 무료"
-#    coming_soon: "More coming soon!"
+    coming_soon: "기대해 주세요!"
 #    courses_available_in: "Courses are available in JavaScript and Python. Web Development courses utilize HTML, CSS, and jQuery."
 #    boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
 #    winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
@@ -147,10 +147,10 @@
     goto_classes: "내 수업들 바로가기"
     view_profile: "내 프로필 보기"
     view_progress: "진행 상황 보기"
-#    go_to_courses: "Go to My Courses"
+    go_to_courses: "내 코스로 가기"
     want_coco: "학교에서 코드컴뱃을 해 보고 싶나요?"
-#    educator: "Educator"
-#    student: "Student"
+    educator: "교사"
+    student: "학생"

   nav:
 #    educators: "Educators"
@@ -166,7 +166,7 @@
     my_account: "내 계정"
     profile: "프로필"
     home: "홈"
-    contribute: "참여하기"
+    contribute: "기여하기"
     legal: "법"
     privacy: "프라이버시"
     about: "소개"
@@ -175,7 +175,7 @@
     twitter_follow: "팔로우"
     my_classrooms: "나의 클래스"
     my_courses: "나의 코스"
-#    my_teachers: "My Teachers"
+    my_teachers: "나의 선생님"
     careers: "채용"
     facebook: "페이스북"
     twitter: "트위터"
@@ -188,18 +188,18 @@
     open_source: "오픈 소스 (GitHub)"
     support: "지원"
     faqs: "자주 묻는 질문"
-#    copyright_prefix: "Copyright"
+    copyright_prefix: "저작권(Copyright)"
 #    copyright_suffix: "All Rights Reserved."
     help_pref: "도움이 필요하신가요? 이메일을 보내주세요."
     help_suff: "저희가 도와드리겠습니다."
 #    resource_hub: "Resource Hub"
 #    apcsp: "AP CS Principles"
-#    parent: "Parents"
+    parent: "부모님들"

   modal:
     close: "닫기"
     okay: "확인"
-#    cancel: "Cancel"
+    cancel: "취소"

   not_found:
     page_not_found: "페이지를 찾을 수 없습니다"
@@ -246,7 +246,7 @@
     poll: "투표" # Tooltip on poll button from /play
     next: "다음" # Go from choose hero to choose inventory before playing a level
     change_hero: "영웅 교체" # Go back from choose inventory to choose hero
-#    change_hero_or_language: "Change Hero or Language"
+    change_hero_or_language: "영웅과 프로그래밍 언어 교체"
     buy_gems: "젬 구매"
     subscribers_only: "가입을 해야 합니다."
     subscribe_unlock: "해제하려면 가입을 해 주세요."
@@ -265,7 +265,7 @@
     replay: "다시 하기"
     back_to_classroom: "교실로 돌아가기"
     teacher_button: "교사용"
-#    get_more_codecombat: "Get More CodeCombat"
+    get_more_codecombat: "CodeCombat을 더 알아보기"

 #  code:
 #    if: "if" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -318,7 +318,7 @@
 #    None: "None"

   share_progress_modal:
-    blurb: "당신은 큰 진전을 보이고 있습니다! 당신이 코드컴뱃으로 얼마나 많이 배웠는지 부모님께 자랑하십시오."
+    blurb: "당신은 큰 진전을 보이고 있습니다! 당신이 코드컴뱃으로 얼마나 많이 배웠는지 부모님께 자랑하세요."
     email_invalid: "이메일 주소가 올바르지 않습니다."
     form_blurb: "아래에 부모님의 이메일 주소를 입력하세요. 부모님께 보여드릴게요!"
     form_label: "이메일 주소 입력"
@@ -348,15 +348,15 @@
     creating: "계정을 생성 중입니다..."
     sign_up: "계정 생성"
     log_in: "비밀번호로 로그인"
-#    login: "Login"
+    login: "로그인"
     required: "진행하기 전에 로그인이 필요합니다."
     login_switch: "이미 계정이 있으신가요?"
     optional: "옵션"
     connected_gplus_header: "Google+ 계정으로 연결되었습니다!"
-#    connected_gplus_p: "Finish signing up so you can log in with your Google+ account."
+    connected_gplus_p: "계정생성이 완료되었습니다. 당신은 Google+ account로 로그인 할 수 있습니다."
     connected_facebook_header: "페이스북 계정으로 연결되었습니다!"
-#    connected_facebook_p: "Finish signing up so you can log in with your Facebook account."
-#    hey_students: "Students, enter the class code from your teacher."
+    connected_facebook_p: "계정생성이 완료되었습니다. 당신은 Facebook account로 로그인 할 수 있습니다."
+    hey_students: "학생여러분, 선생님으로부터 받은 교실 코드를 입력하세요"
     birthday: "생일"
 #    parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
 #    classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help."
@@ -445,7 +445,7 @@
 #    processing: "Processing..."
     solution: "해결책"
 #    table_of_contents: "Table of Contents"
-#    intro: "Intro"
+    intro: "인트로"
     saving: "저장중입니다..."
     sending: "보내는 중입니다..."
     send: "전송"
@@ -466,9 +466,9 @@
     submit_changes: "변경사항 제출"
     save_changes: "변경사항 저장"
     required_field: "필수"
-#    submit: "Submit"
-#    replay: "Replay"
-#    complete: "Complete"
+    submit: "제출"
+    replay: "다시하기"
+    complete: "완료"

   general:
     and: "그리고"
@@ -857,10 +857,10 @@
     prompt_body: "더 얻기 위해 계속 플레이 하세요."

   subscribe:
-#    best_deal: "Best Deal!"
-#    confirmation: "Congratulations! You now have a CodeCombat Premium Subscription!"
-#    premium_already_subscribed: "You're already subscribed to Premium!"
-#    subscribe_modal_title: "CodeCombat Premium"
+    best_deal: "특가!"
+    confirmation: "축하드려요! 당신은 이제 CodeCombat 프리미엄 구독자입니다.!"
+    premium_already_subscribed: "당신은 이미 프리미엄을 구독 중 입니다!"
+    subscribe_modal_title: "CodeCombat 프리미엄"
     comparison_blurb: "코드컴뱃을 구독하셔서 당신의 스킬을 날카롭게하십시오!" # {change}
     must_be_logged: "로그인부터 먼저 하셔야합니다.메뉴에서 계정을 만들거나 로그인해주세요."
     subscribe_title: "구독" # Actually used in subscribe buttons, too
@@ -882,7 +882,7 @@
     will_be_cancelled: "에 취소됩니다"
     currently_free: "현재 무료 구독입니다"
     currently_free_until: "현재 가입된 상태입니다"
-#    free_subscription: "Free subscription"
+    free_subscription: "무료 구독"
     was_free_until: "당신은 현재까지 무료로 가입 했습니다"
     managed_subs: "관리 구독"
     subscribing: "구독중..."
@@ -1358,8 +1358,8 @@
     beautify: "서식을 표준화하여 코드를 아름답게합니다."
     maximize_editor: "코드 에디터 최대/최소."

-#  cinematic:
-#    click_anywhere_continue: "click anywhere to continue"
+  cinematic:
+    click_anywhere_continue: "아무 곳이나 클릭하세요."

   community:
     main_title: "코드 컴뱃 커뮤니티"
@@ -1514,7 +1514,7 @@
 #    start: "Start"
 #    last_level: "Last level played"
 #    not_you: "Not you?"
-#    continue_playing: "Continue Playing"
+    continue_playing: "이어하기"
 #    option1_header: "Invite Students by Email"
 #    remove_student1: "Remove Student"
 #    are_you_sure: "Are you sure you want to remove this student from this class?"


Update sk.coffee (#4189)

Update sk.coffee (#4189)

Translation: signup

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -241,19 +241,19 @@
     connected_facebook_p: "Ukonči registráciu cez svoj účet na Facebooku."
     facebook_exists: "Už máš účet zaregistrovaný cez Facebook!"
     hey_students: "Študenti, vložte kód, ktorý vám dal váš učiteľ."
-#    birthday: "Birthday"
-#    parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions."
-#    classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help."
-#    checking: "Checking..."
-#    account_exists: "This email is already in use:"
-#    sign_in: "Sign in"
-#    email_good: "Email looks good!"
-#    name_taken: "Username already taken! Try {{suggestedName}}?"
-#    name_available: "Username available!"
-#    name_is_email: "Username may not be an email"
-#    choose_type: "Choose your account type:"
-#    teacher_type_1: "Teach programming using CodeCombat!"
-#    teacher_type_2: "Set up your class"
+    birthday: "Narodeniny"
+    parent_email_blurb: "Vieme, že sa nemôžeš dočkať a chceš začať programovať! Tvoji rodičia dostanú email s inštrukciami na vytvorenie tvojho konta.  Napíš email na {{email_link}}, ak máš ďalšie otázky."
+    classroom_not_found: "Neexistuje žiadna trieda s týmto kódom. Skontroluj si kód alebo požiadaj o pomoc učiteľa."
+    checking: "Kontrolujem.."
+    account_exists: "Tento email sa už používa:"
+    sign_in: "Prihlás sa"
+    email_good: "Email vyzerá dobre!"
+    name_taken: "Uživateľské meno je už používané! Skús {{suggestedName}}?"
+    name_available: "Uživateľské meno je dostupné!"
+    name_is_email: "Uživateľské meno nemôže byť email."
+    choose_type: "Vyber si typ účtu:"
+    teacher_type_1: "Uč programovať pomocou hry CodeCombat!"
+    teacher_type_2: "Set up your class"
 #    teacher_type_3: "Access Course Guides"
 #    teacher_type_4: "View student progress"
 #    signup_as_teacher: "Sign up as a Teacher"


Update tr.coffee

Update tr.coffee

--- app/locale/tr.coffee
+++ app/locale/tr.coffee
@@ -137,8 +137,8 @@
     next: "İleri" # Go from choose hero to choose inventory before playing a level
     change_hero: "Kahramanı Değiştir" # Go back from choose inventory to choose hero
     buy_gems: "Değerli Taş Satın Al"
-#    subscribers_only: "Subscribers Only!"
-#    subscribe_unlock: "Subscribe to Unlock!"
+    subscribers_only: "Sadece Aboneler!"
+    subscribe_unlock: "Kilidini açmak için Abone ol!"
 #    subscriber_heroes: "Subscribe today to immediately unlock Amara, Hushbaum, and Hattori!"
 #    subscriber_gems: "Subscribe today to purchase this hero with gems!"
     anonymous: "Anonim Oyuncu"


Update vi.coffee

Update vi.coffee

--- app/locale/vi.coffee
+++ app/locale/vi.coffee
@@ -326,7 +326,7 @@
 #    tip_documented_bug: "A documented bug is not a bug; it is a feature."
     tip_impossible: "Sự việc đều tưởng như bất khả thi cho tới khi nó được hoàn thành. - Nelson Mandela"
 ##    tip_talk_is_cheap: "Talk is cheap. Show me the code. - Linus Torvalds"
-    tip-talk_is_cheap: "Lời nói rất rẻ tiền. Hãy cho tôi thấy code của bạn. - Linus Torvalds"
+    tip_talk_is_cheap: "Lời nói rất rẻ tiền. Hãy cho tôi thấy code của bạn. - Linus Torvalds"
 #    tip_first_language: "The most disastrous thing that you can ever learn is your first programming language. - Alan Kay"
     tip_hardware_problem: "Câu hỏi: Cần bao nhiêu lập trình viên để thay 1 bóng đèn?  Trá lời: 0 người. Đó là vấn đề về phần cứng."
 ##    tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."


WIP: SheetRunner working for aggregations

--- src/main/code/SheetCodeGenerator.coffee
+++ src/main/code/SheetCodeGenerator.coffee
@@ -21,9 +21,8 @@
 callArgList = (items) -> '(' + items.join(', ') + ')'

 createFunction = (functionBody) ->
-  functionCreateArgs = [null].concat 'operations','_ctx', functionBody
 #  console.log 'functionBody', functionBody
-  result = new (Function.bind.apply(Function, functionCreateArgs))
+  result = new (Function.bind.apply(Function, [null, functionBody]))
   console.log 'createFunction', result
   result

@@ -151,7 +150,7 @@
         varDecls.push "#{name} = #{getCodeAndAccumulateFunctions expr.children[i], aggregationNames }"
         items.push "#{name}: #{name}"

-      "function() { var #{varDecls.join(',\n    ')};\nreturn {#{items.join(', ')}}; }()"
+      "function() { var #{varDecls.join(',\n    ')};\nreturn {#{items.join(', ')}}; }.bind(this)()"

     when expr instanceof Sequence
       items = (getCodeAndAccumulateFunctions(e) for e in expr.children)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -14,6 +14,8 @@
   equals "Math".constantize(), Math, "look up a constant"
   equals "Number".constantize(), Number, "look up a constant"
   equals "Math.TAU".constantize(), Math.TAU, "namespaced constants work too"
+
+test "#exte"

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


Correct comment

Correct comment

--- src/objects/file.proto.coffee
+++ src/objects/file.proto.coffee
@@ -26,7 +26,7 @@
 exports.$ = ->
   fso.GetFile @

-# mtime or older than...
+# File size
 exports.sz = ->
   @$().Size


Filter out full frontal due to it not being ready

--- node_modules/application/controllers/showsController.coffee
+++ node_modules/application/controllers/showsController.coffee
@@ -8,6 +8,9 @@
 # All shows
 controller.shows = (req, res) ->
   await Show.findAll(defer(shows))
+
+  shows = shows.filter (show) ->
+    return show unless show.name is 'full-frontal' or show.name is 'full-frontal-july-29,-2014'

   data =
     shows: shows


retry loading of stats if first guess for path fails

--- script.coffee
+++ script.coffee
@@ -1,4 +1,5 @@
 window.Loxgraph = {}
+Loxgraph.statPrefix = ''

 class Loxgraph.Graph
   constructor: (options) ->
@@ -49,7 +50,10 @@
     $("##{@div}").width($(window).width() - $("##{@div}").css('margin').replace(/[^-\d\.]/g, '')*4)

 class Loxgraph.Stat
-  @loadFile: -> $.ajax { url: '/stats', dataType: 'html' }
+  @loadFile: (url) ->
+    $.ajax({ url: url, dataType: 'html' }).then null, ->
+      Loxgraph.statPrefix = '/'
+      $.ajax { url: Loxgraph.statPrefix + url, dataType: 'html' }

   @parseHtml: (htmlDoc) =>
     stats = {}
@@ -94,7 +98,7 @@
       ractive.add 'current'
       $.ajax(
         dataType: 'xml'
-        url: "/stats/#{queue[0].url}"
+        url: Loxgraph.statPrefix + "stats/#{queue[0].url}"
       ).done (data) =>
         @parseXML queue[0].sourceId, data
         @processQueue queue.slice(1), callback
@@ -115,7 +119,7 @@
       ractive.set 'loading', false # hides progress bar, shows menu

   @go: ->
-    @loadFile().then(@parseHtml).then(@buildArray)
+    @loadFile('stats/').then(@parseHtml).then(@buildArray)

 Loxgraph.Stat.go()


Add forceMetadata parameter to runForUserAndProblem

--- server/cron/downloadSubmits.coffee
+++ server/cron/downloadSubmits.coffee
@@ -210,14 +210,14 @@
         await callable(system)

-export runForUserAndProblem = (userId, problemId, onNewSubmit) ->
+export runForUserAndProblem = (userId, problemId, onNewSubmit, forceMetadata) ->
     try
         registeredUser = await RegisteredUser.findByKeyWithPassword(userId)
         problem = await Problem.findById(problemId)
         system = getTestSystem(problem.testSystemData.system)
         logger.info "runForUserAndProblem", system.id(), userId, problemId
         systemDownloader = await system.submitDownloader(registeredUser, problem, 100)
-        await (new SubmitDownloader(systemDownloader, 1, 10, false, onNewSubmit, problem.testSystemData.system)).run()
+        await (new SubmitDownloader(systemDownloader, 1, 10, forceMetadata, onNewSubmit, problem.testSystemData.system)).run()
         logger.info "Done runForUserAndProblem", system.id(), userId, problemId
     catch e
         logger.error "Error in runForUserAndProblem", e


removing console logs

removing console logs

--- server/lib/konnector_poller.coffee
+++ server/lib/konnector_poller.coffee
@@ -37,7 +37,6 @@
             delete timeouts[konnector.slug]
         if konnector.importInterval isnt 'none'
             @create konnector
-        console.log timeouts

     create: (konnector) ->
         # dirty hack for bypassing timeout limit
@@ -60,7 +59,6 @@
         log.info "Next check of konnector #{konnector.slug} on " +
         "#{nextUpdate.format(format)}"
         timeouts[konnector.slug] = setTimeout @checkImport.bind(@, konnector, interval), interval
-        console.log timeouts

     checkImport: (konnector, interval) ->


change server message

change server message

--- server.coffee
+++ server.coffee
@@ -11,5 +11,6 @@
     host = process.env.HOST || "127.0.0.1"
     server = app()
     server.listen port, host, ->
-        msg = "Compound server listening on %s:%d within %s environment"
-        console.log msg, host, port, server.set('env')
+        msg = "Compound server listening on #{host}:#{port} within " +
+                "#{server.set('env')} environment"
+        console.log msg