added output for new tcp connections for h1

--- src/zombie/resources.coffee
+++ src/zombie/resources.coffee
@@ -45,6 +45,8 @@
     @pipeline = Resources.pipeline.slice()
     @urlMatchers = []
     @spdy_agents = []
+    @h1_avail_connections = {}
+    @h1_total_connections = {}
     @h1_agent = undefined
     @h1s_agent = undefined

@@ -627,24 +629,36 @@
         req = HTTP2.raw.request httpRequest
       else
         req = HTTP2.request httpRequest
-    else if protocol == 'http/1.1' && httpRequest.plain
-      if @resources.browser.tcpLimit > 0
-        if ! @resources.h1_agent
-          @resources.h1_agent = new HTTP.Agent({
-              maxSockets: @resources.browser.tcpLimit
-            })
-        httpRequest.agent = @resources.h1_agent
-
-      req = HTTP.request httpRequest
-    else if protocol == 'http/1.1' && ! httpRequest.plain
-      if @resources.browser.tcpLimit > 0
-        if ! @resources.h1s_agent
-          @resources.h1s_agent = new HTTPS.Agent({
-              maxSockets: @resources.browser.tcpLimit
-            })
-        httpRequest.agent = @resources.h1s_agent
-
-      req = HTTPS.request httpRequest
+    else if protocol == 'http/1.1'
+
+      if ! @resources.h1_total_connections[httpRequest.host+httpRequest.port]
+        @resources.h1_avail_connections[httpRequest.host+httpRequest.port] = 0
+        @resources.h1_total_connections[httpRequest.host+httpRequest.port] = 1
+        @resources.browser.emit("newConnection", {}, httpRequest.host, httpRequest.port)
+      else if @resources.h1_avail_connections[httpRequest.host+httpRequest.port] > 0
+        @resources.h1_avail_connections[httpRequest.host+httpRequest.port] = @resources.h1_avail_connections[httpRequest.host+httpRequest.port] - 1
+      else if @resources.browser.tcpLimit == 0 || @resources.browser.tcpLimit > @resources.h1_total_connections[httpRequest.host+httpRequest.port]
+        @resources.h1_total_connections[httpRequest.host+httpRequest.port] = @resources.h1_total_connections[httpRequest.host+httpRequest.port] + 1
+        @resources.browser.emit("newConnection", {}, httpRequest.host, httpRequest.port)
+
+      if httpRequest.plain
+        if @resources.browser.tcpLimit > 0
+          if ! @resources.h1_agent
+            @resources.h1_agent = new HTTP.Agent({
+                maxSockets: @resources.browser.tcpLimit
+              })
+          httpRequest.agent = @resources.h1_agent
+
+        req = HTTP.request httpRequest
+      else
+        if @resources.browser.tcpLimit > 0
+          if ! @resources.h1s_agent
+            @resources.h1s_agent = new HTTPS.Agent({
+                maxSockets: @resources.browser.tcpLimit
+              })
+          httpRequest.agent = @resources.h1s_agent
+
+        req = HTTPS.request httpRequest
     else if protocol == 'spdy'
       cagent = null
       for agent in @resources.spdy_agents
@@ -678,6 +692,8 @@

     req.on("response", (response)=>
       #console.log 'BEGIN RESPONSE'
+      if protocol == 'http/1.1'
+        @resources.h1_avail_connections[httpRequest.host+httpRequest.port] = @resources.h1_avail_connections[httpRequest.host+httpRequest.port] + 1

       ccat = new Concat((bdy)=>
         #console.log 'RESPONSE '+Date.now()+' '+request.url+' '+bdy.length


Ignore gaze from jasmine-node

Ignore gaze from jasmine-node

This is only used when jasmine-node is being launched from the CLI
with --autotest/--watch which the bundled spec runner does not use
or expose.

This reduces all bundled paths to be under 200 characters relative
to the root install location.

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -60,6 +60,7 @@
       path.join('normalize-package-data', 'test')
       path.join('npm', 'test')
       path.join('jasmine-reporters', 'ext')
+      path.join('jasmine-node', 'node_modules', 'gaze')
       path.join('build', 'Release', 'obj.target')
       path.join('build', 'Release', 'obj')
       path.join('build', 'Release', '.deps')


Don't bundle bootstrap examples

Don't bundle bootstrap examples

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -46,6 +46,7 @@
       path.join('less', 'dist')
       path.join('less', 'test')
       path.join('bootstrap', 'docs')
+      path.join('bootstrap', 'examples')
       path.join('spellchecker', 'vendor')
       path.join('xmldom', 'test')
       path.join('vendor', 'apm')


Sort party list in descending order

Sort party list in descending order

--- static/js/party-list.coffee
+++ static/js/party-list.coffee
@@ -21,7 +21,7 @@

     initialize: ->
         @collection = new PartyList
-        @collection.comparator = 'member_count'
+        @collection.comparator = (model) -> return -model.get('member_count')
         # This gets triggered when the content has fetch'ed from JSON
         @listenTo @collection, "reset", @render
         @collection.fetch


nitrogen8mm-dwe: Add notice for booting rootfs from USB

Since labels are used to mount diverse partitions,
we need to add a warning that only one storage
can contain a balena image at a time. It's either
the USB stick, or the eMMC, but not both at the same
time.

Signed-off-by: Alexandru Costache <2d2dfcfad7b9832058fd41884a8955d042a85275@balena.io>

--- nitrogen8mm-dwe.coffee
+++ nitrogen8mm-dwe.coffee
@@ -12,6 +12,13 @@
 	CAPACITOR_DRAIN
 	BOARD_POWERON
 	WRITE_BALENA_UBOOT
+]
+
+imageDownloadAlerts: [
+	{
+		type: 'warning'
+		message: 'To run the hostOS from a USB stick, please first ensure latest imx-boot has been written to /dev/mmcblk0boot0, and the emmc - /dev/mmcblk0 - does not contain any balena image or balena partitions.'
+	}
 ]

 module.exports =


changed settings path

changed settings path


Coffee cleanup. Brackets be lame yo.

Coffee cleanup. Brackets be lame yo.

--- client/accounts-config.coffee
+++ client/accounts-config.coffee
@@ -9,20 +9,19 @@
     passwordSignupFields: 'EMAIL_ONLY'
     showSignupCode: false
     showOtherLoginServices: true
-    extraSignUpFields: [ {
+    extraSignUpFields: [
       field: 'name'
       label: 'Your name'
       placeholder: 'Your name'
       type: 'text'
       required: true
-    },
-    {
+    ,
       field: 'phone'
       label: 'Your phone number'
       placeholder: 'Your phone number'
       type: 'text'
       required: false
-    }]
+    ]
     fluidLayout: false
     useContainer: true
     signInAfterRegistration: true


Added how it works section

Added how it works section

--- client/components/Root.coffee
+++ client/components/Root.coffee
@@ -76,7 +76,7 @@
                                 для остальных <Link to="/pay">платные</Link>.

-                                <Link to="/material/0">Подробная информация про курс</Link>

+                                <Link to="/material/0">Подробная информация про курс</Link>{" | "}
                                 <Link to="/material/module-20927_5">FAQ для школьников</Link>

                         </Tab.Pane>
@@ -92,7 +92,7 @@
                                 Занятия проходят заочно. Занятия <Link to="/pay">платные</Link>.

-                                <Link to="/material/0">Подробная информация про курс</Link>

+                                <Link to="/material/0">Подробная информация про курс</Link>{" | "}
                                 <Link to="/material/module-20927_7">FAQ для студентов и выпускников</Link>

                         </Tab.Pane>
@@ -100,6 +100,35 @@

         </Tab.Container>
+
Как это работает?

+

+            Темы разбиты по уровням по возрастанию сложности: от основ синтаксиса до продвинутых алгоритмов.
+            По большинству тем есть теоретические материалы (статьи, советы, видеолекции) и задачи.
+

+

+            Вы изучаете теорию и решаете задачи на своем любимом языке программирования.
+            Задачи отправляете на сайт и они автоматически проверяются.
+

+

+            Через минуту вы узнаете, правильное решение у вас или неправильное.
+            Если правильное — двигаетесь дальше; если неправильное, думаете, как это исправить.
+

+

+            Все неправильные решения я вижу и могу вам подсказать, в чем у вас ошибка.
+            Все правильные решения я тоже вижу и просматриваю глазами — насколько оптимально они написаны.
+

+

+            Если решение написано достаточно хорошо, я его засчитываю.
+            Могу еще написать небольшие комментарии по вашему коду.
+

+

+            Если решение написано не очень хорошо, я его не засчитываю — «игнорирую».
+            К игнорированным решениям я пишу комментарий, и вам надо будет переделать решение.
+

+

+            Когда ваше решение зачтено, вы получаете доступ к «лучшим решениям»,
+            чтобы видеть, как эту задачу решали другие ученики.
+

 export default Root = (props) ->


filetree: prevent calling `stopWatching` method of `Finder` if it doesn't exist

--- client/ide/fs/fsfolder.coffee
+++ client/ide/fs/fsfolder.coffee
@@ -41,7 +41,7 @@
   remove:(callback)->
     @off 'fs.delete.finished'
     @on  'fs.delete.finished', =>
-      finder = KD.getSingleton 'finderController'
+      return  unless finder = KD.getSingleton 'finderController'
       finder.stopWatching @path

     super callback, yes


Cleaning.

Cleaning.

--- client/lib/meteor-file.coffee
+++ client/lib/meteor-file.coffee
@@ -5,4 +5,4 @@
   # Modify the read method. Disallow reading to continue if import is canceled.
   return callback 'canceled' if @collection.findOne(@_id)?.canceled

-  return parentRead.apply @, arguments
+  parentRead.apply @, arguments


change how comment are saved to the database

--- client/views/blog/blog.coffee
+++ client/views/blog/blog.coffee
@@ -28,11 +28,14 @@

       # load existing comments
       existingComments = []
-      @data.comments.forEach((section)->
-        existingComments.push(
-          sectionId: section.sectionId.toString()
-          comments: section.comments
-        )
+      @data.comments.forEach((comment)->
+        comment.comment.id = comment._id
+        if existingComments[comment.sectionId - 1]
+          existingComments[comment.sectionId - 1].comments.push comment.comment
+        else
+          existingComments[comment.sectionId - 1] =
+            sectionId: comment.sectionId.toString()
+            comments: [comment.comment]
       )

       # add side comments
@@ -40,17 +43,24 @@

       # side comments events
       sideComments.on 'commentPosted', (comment) ->
-        attrs =
-          authorAvatarUrl: comment.authorAvatarUrl
-          authorName: comment.authorName
-          comment: comment.comment
-        if getComment(comment.sectionId)
-          newComment = getComment(comment.sectionId)
-          newComment.comments.push attrs
-          newComment.update comments: newComment.comments
+        if settings.allowAnonymous or Meteor.user()
+          attrs =
+            slug: Session.get('slug')
+            sectionId: comment.sectionId
+            comment:
+              authorAvatarUrl: comment.authorAvatarUrl
+              authorName: comment.authorName
+              authorId: comment.authorId
+              comment: comment.comment
+          commentId = Comment.create attrs
+          comment.id = commentId
+          sideComments.insertComment(comment)
         else
-          Comment.create slug: Session.get('slug'), sectionId: comment.sectionId, comments: [attrs]
-        sideComments.insertComment(comment)
+            comment.id = -1
+            sideComments.insertComment
+              sectionId: comment.sectionId
+              authorName: comment.authorName
+              comment: 'Please login to post comments'

 Template.blogShowBody.rendered = ->


test(authorizationUri): refactor for clarity

--- test/anvilClientSpec.coffee
+++ test/anvilClientSpec.coffee
@@ -409,17 +409,16 @@

     describe 'with an options argument', ->

-      {uri} = {}
-
-      before ->
+      it 'should set the optional endpoint', ->
         uri = anvil.authorizationUri({
           endpoint: 'connect/google'
         })
-
-      it 'should set the optional endpoint', ->
         uri.should.contain '/connect/google?'

       it 'should set default authorization params', ->
+        uri = anvil.authorizationUri({
+          endpoint: 'connect/google'
+        })
         uri.should.contain 'response_type=code'
         uri.should.contain "client_id=#{config.client_id}"
         uri.should.contain "redirect_uri=#{encodeURIComponent(config.redirect_uri)}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -37,7 +37,7 @@
     else if index is 3
       equals obj, obj4

-  equals indexSum,
+  equals indexSum, 6

 test "#find", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -20,7 +20,7 @@

   indexSum = 0

-  engine.each (obj, index) ->
+  engine.each  (obj, index) ->
     if index is 0
       equals obj, obj1
     else if index is 1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -17,6 +17,7 @@

   equals engine.find('.findable').length, 2

+
 test "#first", ->
   engine = Engine()


cleanup for easy comparison of init strategy

--- lib/base.coffee
+++ lib/base.coffee
@@ -13,15 +13,17 @@
 } = require './utils'
 swrap = require './selection-wrapper'

-Input = null
-selectList = null
-getEditorState = null # set by Base.init()
-CSON = null
+[
+  Input
+  selectList
+  getEditorState
+  CSON # set by Base.init()
+] = []
 LazyLoadedLibs = {}

-serializeCommandTable = (specByKlass) ->
+serializeCommandTable = (commandTable) ->
   CSON ?= require 'season'
-  data = CSON.stringify(specByKlass)
+  data = CSON.stringify(commandTable)
   CommandTablePath = require.resolve('./command-table.cson')
   atom.workspace.open(CommandTablePath).then (editor) ->
     editor.setText(data)
@@ -263,27 +265,35 @@
   # -------------------------
   @init: (service, commandTable) ->
     {getEditorState} = service
-
-    registerCommandFromSpec = (spec) ->
-      {name, commandScope, commandName} = spec
-      atom.commands.add commandScope, commandName, (event) ->
-        vimState = getEditorState(@getModel()) ? getEditorState(atom.workspace.getActiveTextEditor())
-        if vimState? # Possibly undefined See #85
-          vimState.operationStack.run(name)
-        event.stopPropagation()
-
-    @subscriptions = new CompositeDisposable()
-
-    @commandTable = require('./command-table')
-    # @commandTable = null
-
-    if @commandTable?
-      console.log 'lazy strategy' unless atom.inSpecMode()
-      for name, spec of @commandTable when spec.isCommand
-        @subscriptions.add(registerCommandFromSpec(spec))
+    isDevMode = atom.inDevMode()
+    registerCommandFromTable = (table) =>
+      @subscriptions = new CompositeDisposable()
+      for name, spec of table when spec.isCommand
+        @subscriptions.add(@registerCommandFromSpec(spec))
       @subscriptions
-    else
-      console.log 'non lazy strategy'
+
+    readTableAndRegisterCommands = =>
+      console.time('lazy strategy') if isDevMode
+      @commandTable = require('./command-table')
+      subs = registerCommandFromTable(@commandTable)
+      console.timeEnd('lazy strategy') if isDevMode
+      subs
+
+    registerCommandOldStyle = =>
+      console.time('old style') if isDevMode
+      [
+        './operator', './operator-insert', './operator-transform-string',
+        './motion', './motion-search', './text-object', './misc-command'
+      ].forEach(require)
+
+      @subscriptions = new CompositeDisposable()
+      for __, klass of @getRegistries() when klass.isCommand()
+        @subscriptions.add(klass.registerCommand())
+      console.timeEnd('old style') if isDevMode
+      @subscriptions
+
+    registerAndGenerateCommandTable = =>
+      console.time('register and gen-cmd-table') if isDevMode
       files = [
         './operator', './operator-insert', './operator-transform-string',
         './motion', './motion-search', './text-object', './misc-command'
@@ -292,15 +302,12 @@
       filenameByKlass = {}
       for file in files
         beforeRequire = _.keys(@getRegistries())
-        # console.time(file)
         require(file)
-        # console.timeEnd(file)
         afterRequire = _.keys(@getRegistries())
-        addedKlass = _.difference(afterRequire, beforeRequire)
-        for klass in addedKlass
+        for klass in _.difference(afterRequire, beforeRequire)
           filenameByKlass[klass] = file

-      for __, klass of @getRegistries()# when klass.isCommand()
+      for __, klass of @getRegistries()
         @commandTable[klass.name] = spec = {
           name: klass.name
           file: filenameByKlass[klass.name]
@@ -310,9 +317,13 @@
         }

       # serializeCommandTable(@commandTable)
-      for name, spec of @commandTable when spec.isCommand
-        @subscriptions.add(registerCommandFromSpec(spec))
-      @subscriptions
+      subs = registerCommandFromTable(@commandTable)
+      console.timeEnd('register and gen-cmd-table') if isDevMode
+      subs
+
+    return readTableAndRegisterCommands()
+    # return registerCommandOldStyle()
+    # return registerAndGenerateCommandTable()

   registries = {Base}
   @extend: (@command=true) ->


Remove RequestDownload from GetRPCMethodsResponse

Because RequestDownload method is no longer supported.

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -695,7 +695,7 @@
           })
           res = soap.response({
             id : rpc.id
-            acsResponse : {name: 'GetRPCMethodsResponse', methodList: ['Inform', 'GetRPCMethods', 'TransferComplete', 'RequestDownload']}
+            acsResponse : {name: 'GetRPCMethodsResponse', methodList: ['Inform', 'GetRPCMethods', 'TransferComplete']}
             cwmpVersion : sessionContext.cwmpVersion
           })
           writeResponse(sessionContext, res)


Use path property

Use path property

--- lib/file.coffee
+++ lib/file.coffee
@@ -39,7 +39,7 @@
     repo = atom.project.getRepo()
     if repo?
       @subscribe repo, 'status-changed', (changedPath, status) =>
-        @updateStatus(repo) if changedPath is @getPath()
+        @updateStatus(repo) if changedPath is @path
       @subscribe repo, 'statuses-changed', =>
         @updateStatus(repo)


Create url for avatar download

Create url for avatar download

--- lib/file.coffee
+++ lib/file.coffee
@@ -1,5 +1,7 @@
+uploadPath = "~/uploads"
+
 store = new FS.Store.FileSystem "images",
-	path: "~/uploads"
+	path: uploadPath
 	fileKeyMaker: (fileObj) ->
 		filename = fileObj.name()
 		filenameInStore = fileObj.name({store: 'images'})
@@ -13,4 +15,26 @@
 	insert: ->
 		return true
 	update: ->
-		return true+		return true
+	download: ->
+		return true
+
+Meteor.startup ->
+	if Meteor.isServer
+		FS.HTTP.mount ['/avatar/:filename'], ->
+			self = this
+			opts = FS.Utility.extend({}, self.query || {}, self.params || {})
+
+			collectionName = opts.collectionName
+
+			collection = FS._collections['images']
+
+			file = if collection? then collection.findOne({ "copies.images.key": opts.filename }) else null
+
+			return {
+				collection: collection
+				file: file
+				storeName: 'images'
+				download: opts.download
+				filename: opts.filename
+			}


WIP: adapt the "mark single entry as read" functionality to angularjs.

--- app/assets/javascripts/modules/ng-controllers.js.coffee
+++ app/assets/javascripts/modules/ng-controllers.js.coffee
@@ -57,7 +57,7 @@
     feed_removed_from_folder $rootScope.current_feed, folder_id

     # Tell the model that no feed is currently selected.
-    $rootScope.current_feed = null
+    unset_current_feed()

     $http.delete(path)
     .error ->
@@ -83,7 +83,7 @@
       .success (data)->
         $scope.loading_entries = false
         $scope.feeds.push data
-        $scope.load_feed data
+        $scope.read_feed data
         find_folder('all').unread_entries += data.unread_entries
       .error (data, status)->
         $scope.loading_entries = false
@@ -183,6 +183,7 @@
   #--------------------------------------------

   $scope.read_all_entries = ->
+    unset_open_entry()
     load_feed $rootScope.current_feed, true

   #--------------------------------------------
@@ -190,6 +191,7 @@
   #--------------------------------------------

   $scope.refresh_feed = ->
+    unset_open_entry()
     $scope.loading_entries = true

     $http.put("/feeds/#{$rootScope.current_feed.id}.json")
@@ -218,7 +220,20 @@
   #--------------------------------------------

   $scope.read_entry = (entry)->
-    alert 'test'
+    if $rootScope.open_entry == entry
+      # User is closing the open entry, do nothing
+      alert "closing #{entry.title}"
+      unset_open_entry()
+    else
+      set_open_entry entry
+      if !entry.read
+        # User is opening an unread entry
+        alert "reading #{entry.title}"
+      else
+        # User is opening a read entry
+        alert "opening read #{entry.title}"
+
+    alert $rootScope.open_entry.id

   #--------------------------------------------
   # Load a feed's entries
@@ -233,7 +248,7 @@
       $scope.entries = data["entries"]
       feed.unread_entries = data["unread_entries"]
     .error (data,status)->
-      $rootScope.current_feed = null
+      unset_current_feed()
       $scope.loading_entries = false
       if status == 404
         $scope.error_no_entries = true
@@ -286,6 +301,7 @@
   #--------------------------------------------

   set_current_feed = (feed)->
+    unset_open_entry()
     $rootScope.current_feed = feed

   #--------------------------------------------
@@ -293,6 +309,21 @@
   #--------------------------------------------

   unset_current_feed = ->
+    unset_open_entry()
     $rootScope.current_feed = null

+  #--------------------------------------------
+  # Store the currently open entry in the global scope
+  #--------------------------------------------
+
+  set_open_entry = (entry)->
+    $rootScope.open_entry = entry
+
+  #--------------------------------------------
+  # Unset the currently open entry in the global scope
+  #--------------------------------------------
+
+  unset_open_entry = ->
+    $rootScope.open_entry = null
+
 ]


Move the CoffeeScript.tokens mock into its own batch to isolate it from other tests

--- test/test_1.5.0_plus.coffee
+++ test/test_1.5.0_plus.coffee
@@ -2,21 +2,27 @@
 vows = require 'vows'
 assert = require 'assert'
 CoffeeScript = require 'coffee-script'
-CoffeeScript.old_tokens = CoffeeScript.tokens
-CoffeeScript.tokens = (text) ->
-    CoffeeScript.updated_tokens_called = true
-    tokens = CoffeeScript.old_tokens(text)
-    for token in tokens
-        if typeof token[2] is "number"
-            if token[0] is 'INDENT' or token[1] is 'OUTDENT'
-                token[2] = {first_line: token[2] - 1, last_line: token[2]}
-            else
-                token[2] = {first_line: token[2], last_line: token[2]}
-        token
 coffeelint = require path.join('..', 'lib', 'coffeelint')

-
-vows.describe("CoffeeScript 1.5.0+").addBatch({
+vows.describe("CoffeeScript 1.5.0+")
+.addBatch({
+    "Setup": ->
+        CoffeeScript.old_tokens = CoffeeScript.tokens
+        CoffeeScript.tokens = (text) ->
+            CoffeeScript.updated_tokens_called = true
+            tokens = CoffeeScript.old_tokens(text)
+            for token in tokens
+                if typeof token[2] is "number"
+                    if token[0] is 'INDENT' or token[1] is 'OUTDENT'
+                        token[2] =
+                            first_line: token[2] - 1
+                            last_line: token[2]
+                    else
+                        token[2] =
+                            first_line: token[2]
+                            last_line: token[2]
+                token
+}).addBatch({

     "lineNumber has become an object" :


open files with module definitions by click

--- lib/init.coffee
+++ lib/init.coffee
@@ -50,6 +50,9 @@
     (e) ->
         path = e.target.getAttribute('data-path')
         console.log("path, open file:", path)
+        if path
+            atom.workspace.open(path)
+
 )

 getFileFor = (name) ->


Use project.relativizePath

Use project.relativizePath

Let Atom do the hard work of figuring out whether the current file is part of a project, and if so what the path of that project is.

--- lib/init.coffee
+++ lib/init.coffee
@@ -41,9 +41,9 @@

     projectPath = (textEditor) ->
       editorPath = textEditor.getPath()
-      for projectPathCandidate in atom.project.getPaths()
-        if editorPath.indexOf(projectPathCandidate) == 0
-          return projectPathCandidate
+      projPath = atom.project.relativizePath(editorPath)[0]
+      if projPath?
+        return projPath
       null

     isMixProject = (textEditor) ->


add layer to screen conversion test

add layer to screen conversion test

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -1144,3 +1144,17 @@
 			a1.isAnimating.should.equal false
 			a2.isAnimating.should.equal false
 			a3.isAnimating.should.equal true
+
+
+	describe "Point conversion", ->
+
+		it "should correctly convert points to Screen", ->
+
+			point =
+				x: 200
+				y: 300
+
+			layer = new Layer point: point
+			screenPoint = layer.convertPointToScreen()
+			screenPoint.x.should.equal point.x
+			screenPoint.y.should.equal point.y


Add test to show non resetting of defaults

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -11,7 +11,10 @@

 	describe "Defaults", ->

-		it "should set defaults", ->
+		it "should reset nested defaults defaults", ->
+			Framer.Defaults.DeviceComponent.animationOptions.curve = "spring"
+			Framer.resetDefaults()
+			Framer.Defaults.DeviceComponent.animationOptions.curve.should.equal "ease-in-out"

 			Framer.Defaults =
 				Layer:


finally worked

finally worked

--- lib/main.coffee
+++ lib/main.coffee
@@ -76,11 +76,42 @@
     {container, orientation} = paneAxis
     new paneAxis.constructor({container, orientation, children: paneAxis.getChildren()})

+  copyRoot: (root) ->
+    newRoot = @copyPaneAxis(root)
+    root.destroy()
+    for paneAxis in @getAllAxis(newRoot)
+      # unsubscribe before copy
+      for child in paneAxis.getChildren()
+        paneAxis.unsubscribeFromChild(child)
+
+      newPaneAxis = @copyPaneAxis paneAxis
+      paneAxis.parent.replaceChild paneAxis, newPaneAxis
+      paneAxis.destroy()
+    newRoot
+
   # [FIXME]
-  # Occasionally blank pane, remain on original pane position.
+  # Occasionally blank pane remain on original pane position.
   # Clicking this blank pane cause Uncaught Error: Pane has bee destroyed.
-  # This issue is already reported to
-  #  https://github.com/atom/atom/issues/4643
+  # This issue is already reported to https://github.com/atom/atom/issues/4643
+  #
+  # [NOTE]
+  # This code is result of try&error to get desirble result.
+  #  * I don't understand why @copyRoot is necessary, but without copying PaneAxis,
+  #    it seem to detatch View element and not worked as expected.
+  #  * Simply changing order of children by splicing children of PaneAxis or same
+  #    kind of direct manupilation to Pane or PaneAxis won't work, it easily bypassing
+  #    event system and you got buch of Exception.
+  #  * I wanted to use `thisPane` instead of `new Pane() and @movePane`, but I gave up to
+  #    solve lot of Exception caused by removeChild(thisPane). So I took @movePane() approarch.
+  #
+  # I know current implementation is not ideal and not clean.
+  # There must be better way.
+  # But for now I took 'try&erro&pick code that worked' approarch.
+  #
+  # [TODO]
+  # Understand Pane, PaneAxis, PaneContainer and its corresponding ViewElement and surrounding
+  # Event callback.
+  # Improve this function without using @copyRoot?
   very: (direction) ->
     return if @getPanes().length is 1
     thisPane = @getActivePane()
@@ -90,13 +121,10 @@
     @Pane      ?= thisPane.constructor
     @PaneAxis  ?= root.constructor

+    parent.removeChild(thisPane, true)
     if root.getOrientation() isnt orientation
       @debug "Different orientation"
-      for child in root.getChildren()
-        root.unsubscribeFromChild(child)
-
-      origRoot = @copyPaneAxis root
-      root     = new @PaneAxis({container, orientation, children: [origRoot]})
+      root = new @PaneAxis({container, orientation, children: [@copyRoot(root)]})

     newPane = new @Pane()
     switch direction
@@ -107,9 +135,8 @@
     @movePane thisPane, newPane

     container.setRoot(root) if root isnt paneInfo.root
+    # container.destroyEmptyPanes()
     @reparent(axis) for axis in @getAllAxis(root)
-
-    container.destroyEmptyPanes()
     newPane.activateItemAtIndex index
     newPane.activate()

@@ -120,15 +147,9 @@
         @getAllAxis child, list
     return list

-
-  isEqualOrientationAxis: (axis, orientation) ->
-    (axis instanceof @PaneAxis) and (axis.getOrientation() is orientation)
-
   reparent: (axis) ->
     parent = axis.getParent()
-    # console.log "parent: #{parent.getOrientation()}, me: #{axis.getOrientation()}"
     if parent.getOrientation() is axis.getOrientation()
-      # console.log "match!"
       children   = axis.getChildren()
       firstChild = children.shift()
       firstChild.setFlexScale()
@@ -137,7 +158,6 @@
         parent.insertChildAfter(firstChild, children.shift())
       axis.destroy()
       parent
-

   # Utility
   getPanes: ->


Ensure packageManager is still defined after promise resolution

It can be nulled out via package deactivation. A previous approach to
this issue was causing test failures.

--- lib/main.coffee
+++ lib/main.coffee
@@ -68,9 +68,10 @@
   consumeStatusBar: (statusBar) ->
     packageManager ?= new PackageManager()
     packageManager.getOutdated().then (updates) ->
-      PackageUpdatesStatusView = require './package-updates-status-view'
-      statusView = new PackageUpdatesStatusView()
-      statusView.initialize(statusBar, packageManager, updates)
+      if packageManager?
+        PackageUpdatesStatusView = require './package-updates-status-view'
+        statusView = new PackageUpdatesStatusView()
+        statusView.initialize(statusBar, packageManager, updates)

   consumeSnippets: (snippets) ->
     if typeof snippets.getUnparsedSnippets is "function"


wipe #4 confirmed no increasing editor subscription.

Intentionally not deleting key for leakChecking.

Confirmed no increasing `@editorSubscriptions`

--- lib/main.coffee
+++ lib/main.coffee
@@ -47,8 +47,8 @@
         return if @isLocked()
         @handleCursorMoved event

-      # @editorSubscriptions[editor.id].add editor.onDidDestroy =>
-      #   @editorSubscriptions[editor.id].dispose()
+      @editorSubscriptions[editor.id].add editor.onDidDestroy =>
+        @editorSubscriptions[editor.id].dispose()
       #   delete @editorSubscriptions[editor.id]

     @subscriptions.add atom.workspace.observeActivePaneItem (item) =>


:horse_racing: Set hasSeenDeprecatedNotification to true early

To prevent unnecessary packageManager.getInstalled() calls.

If you don't have any deprecated packages to begin with you won't be
installing any since the UI prohibits it.

--- lib/main.coffee
+++ lib/main.coffee
@@ -78,6 +78,8 @@
     settingsView = new SettingsView(params)

   showDeprecatedNotification: (packages) ->
+    localStorage.setItem('hasSeenDeprecatedNotification', true)
+
     deprecatedPackages = packages.user.filter ({name, version}) ->
       atom.packages.isDeprecatedPackage(name, version)
     return unless deprecatedPackages.length
@@ -99,4 +101,3 @@
           atom.commands.dispatch(atom.views.getView(atom.workspace), 'settings-view:view-installed-packages')
           notification.dismiss()
       }]
-    localStorage.setItem('hasSeenDeprecatedNotification', true)


Activity: race condition fixed

Activity: race condition fixed

--- client/Social/Activity/views/activitycontentpane.coffee
+++ client/Social/Activity/views/activitycontentpane.coffee
@@ -27,15 +27,13 @@

   setContent: (items) ->
     @listController.removeAllItems()
-    for item in items
-      KD.utils.defer => @listController.addItem item
+    items.forEach (item) => KD.utils.defer => @listController.addItem item
     @listController.hideLazyLoader()
     @isLoaded = yes
     @emit 'ContentIsShown'

   appendContent: (items) ->
-    for item in items
-      KD.utils.defer => @listController.addItem item
+    items.forEach (item) => KD.utils.defer => @listController.addItem item
     @listController.hideLazyLoader()

   getLoadedCount: -> @listController.getItemCount()


- rewrite of example upper bound test

- rewrite of example upper bound test

--- test/upperbound.test.coffee
+++ test/upperbound.test.coffee
@@ -10,19 +10,20 @@
     afterEach =>
         try @server.close()

+    startHTTPServer = (http, f) =>
+        @server = http.createServer(f).listen(@port)
+
     it "can add extra HTTP response headers", (done) =>
         p = new Policy()
             .on("ServerResponse.writeHead")
-                .do((response) ->
-                    response.setHeader "X-NodeSentry", "What Else?!")
-            .build()
+                .do((response) -> response.setHeader "X-NodeSentry", "What Else?!")

         msg = "Hello World\n"
-        http = safe_require "http", p
-        @server = http.createServer((req, res) ->
+        http = safe_require "http", p.build()
+        startHTTPServer(http, (req, res) ->
             res.writeHead 200, {'Content-Type': 'text/plain'}
             res.end msg
-        ).listen(@port)
+        )

         http.get "http://localhost:#{@port}/", (res) ->
             res.statusCode.should.equal 200


:bug: Forgot to remove .then from filterProjects

--- lib/util.coffee
+++ lib/util.coffee
@@ -117,7 +117,7 @@
                 dbResolve(rows)

         .then (rows) ->
-          filterProjects(rows).then(resolve).catch(reject)
+          resolve(filterProjects(rows))

     else
       # Atom 1.5 to 1.6
@@ -147,7 +147,7 @@

         ), (err, rows) ->
           return reject(err) if err
-          filterProjects(rows).then(resolve).catch(reject)
+          resolve(filterProjects(rows))

 # shim atom.packages.serialize in <= 1.6
 packageStatesSerialize = () ->


Commenting out cli test for --stdio option

--- tests/functional/cli.coffee
+++ tests/functional/cli.coffee
@@ -39,9 +39,9 @@
       argv['config-file'].should.equal 'sample.yml'
       argv.f.should.equal 'sample.yml'

-    it 'option stdio', ->
-      argv = cli.argv stdio: true
-      argv.stdio.should.be.true
+    # it 'option stdio', ->
+    #   argv = cli.argv stdio: true
+    #   argv.stdio.should.be.true

     it 'option base', ->
       argv = cli.argv base: '/some/dir'


Added t.matches to tester.

Added t.matches to tester.

--- tests/support/tester.coffee
+++ tests/support/tester.coffee
@@ -74,6 +74,10 @@
           if actual isnt expected then @fail key, {type: 'equal', actual, expected}
           else @pass key

+        matches: (key, actual, regex) ->
+          if not actual.match(regex) then @fail key, {type: 'matches', actual, regex}
+          else @pass key
+
         ok: (key, value) ->
           if !!!value then @fail key, {type: 'ok'}
           else @pass key
@@ -136,6 +140,10 @@
             log "Expected vs #{red}actual#{reset}:"
             log v.expected
             log red + v.actual + reset
+          else if v.type is 'matches'
+            log "Regex vs #{red}actual#{reset}:"
+            log v.regex
+            log red + v.actual + reset
         for k, v of t.expected
           timedout++
           log "#{red}✖ #{k} (not reached)#{reset}"


TRACKER.parse conditionally accepts asymmetric patterns

--- src/MKTS.coffee
+++ src/MKTS.coffee
@@ -323,10 +323,11 @@
 @TRACKER._tracker_pattern = tracker_pattern

 #-----------------------------------------------------------------------------------------------------------
-@TRACKER.parse = ( pattern ) =>
+@TRACKER.parse = ( pattern, settings ) =>
   left_fence  = null
   name        = null
   right_fence = null
+  symmetric   = settings?[ 'symmetric' ] ? yes
   #.........................................................................................................
   if ( not pattern? ) or pattern.length is 0
     throw new Error "pattern must be non-empty, got #{rpr pattern}"
@@ -345,9 +346,10 @@
       throw new Error "fence '.' can not have right fence, got #{rpr pattern}"
   #.........................................................................................................
   else
-    ### Except for dot fence, must always have no fence or both fences ###
-    if ( left_fence? and not right_fence? ) or ( right_fence? and not left_fence? )
-      throw new Error "unmatched fence in #{rpr pattern}"
+    ### Except for dot fence, must always have no fence or both fences in case `symmetric` is set ###
+    if symmetric
+      if ( left_fence? and not right_fence? ) or ( right_fence? and not left_fence? )
+        throw new Error "unmatched fence in #{rpr pattern}"
   #.........................................................................................................
   if left_fence? and left_fence isnt '.'
     ### Complain about unknown left fences ###


Do not assign a default cart id

Do not assign a default cart id

--- spec/javascripts/factories/cart_item_factory.js.coffee
+++ spec/javascripts/factories/cart_item_factory.js.coffee
@@ -6,7 +6,6 @@
   i.price = 10
   i.quantity = 1
   i.group = Faker.Lorem.words(1).join(' ')
-  i.cart_id = 1

 Factory.define 'cartItemWithChildren', class: EmberCart.CartItem, (i)->
   apply(i,


fix in CSS mod

fix in CSS mod

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -95,11 +95,11 @@
     byCSSSelector:
         '.translate.sort-cards-by': 'Sortiere Karten nach'
         '.squad-name': 'unbenannte Staffel'
-        '.unreleased-content-used': '

Diese Liste verwendet noch nicht veröffentlichte Inhalte!'
-        '.epic-content-used': '

Diese Staffel verwendet epischen Inhalt!'
-        '.illegal-epic-too-many-small-ships': '

Du kannst nicht mehr als 12 kleine Schiffe einsetzen!'
-        '.illegal-epic-too-many-large-ships': '

Du kannst nicht mehr als 6 große Schiffe einsetzen!'
-        '.collection-invalid':

Du kannst diese Liste nicht mit deiner Sammlung aufstellen!'
+        '.unreleased-content-used': '

 Diese Liste verwendet noch nicht veröffentlichte Inhalte!'
+        '.epic-content-used': '

 Diese Staffel verwendet epischen Inhalt!'
+        '.illegal-epic-too-many-small-ships': '

 Du kannst nicht mehr als 12 kleine Schiffe einsetzen!'
+        '.illegal-epic-too-many-large-ships': '

 Du kannst nicht mehr als 6 große Schiffe einsetzen!'
+        '.collection-invalid': '

 Du kannst diese Liste nicht mit deiner Sammlung aufstellen!'
         '.game-type-selector option[value="custom"]': 'Benutzerdefiniert'
         '.game-type-selector option[value="epic"]': 'Episch'
         '.game-type-selector option[value="team-epic"]': 'Team Episch'


fixed missing trips when clicking on tasks :+1:

--- mrfogg-front/app/coffee/controllers/main.coffee
+++ mrfogg-front/app/coffee/controllers/main.coffee
@@ -105,15 +105,14 @@
         trip_unset = true
     if trip_unset
         $rootScope.tripId = 1
-    if trip_unset or changed
-        resource.getTrips($rootScope.userid).then (result) ->
-            $scope.triplist = result
-            $scope.mytrips = _.remove($scope.triplist, (trip) ->
-                return trip.id == $rootScope.tripId
-            )
-            $rootScope.mytrip = $scope.mytrips[0]
-            resource.getTasks($rootScope.tripId).then (result) ->
-                $rootScope.tasklist = result._attrs
+    resource.getTrips($rootScope.userid).then (result) ->
+        $scope.triplist = result
+        $scope.mytrips = _.remove($scope.triplist, (trip) ->
+            return trip.id == $rootScope.tripId
+        )
+        $rootScope.mytrip = $scope.mytrips[0]
+        resource.getTasks($rootScope.tripId).then (result) ->
+            $rootScope.tasklist = result._attrs

     return


test for syntax errors

test for syntax errors

--- tests/test_builder.coffee
+++ tests/test_builder.coffee
@@ -664,3 +664,24 @@
         hash3 = manifest.modules[0].hash

         expect(hash3).not.to.equal(hash2)
+
+describe 'Building modules with syntax errors', ->
+    beforeEach ->
+        mock(yaml.safeLoad("""
+            testimonial:
+                a.js: var 55;
+                spa.yaml: |
+                    root: "/testimonial/"
+                    extensions:
+                        - .js
+                    manifest: "manifest.json"
+                    default_loader: junk
+            """))
+
+    it 'should successfully build', ->
+        builder = spa.Builder.from_config("/testimonial/spa.yaml")
+
+        expect(builder.build.bind(builder))
+            .to.throw(spa.ModuleTypeError)
+            .that.has.property("path")
+            .that.equals(path.resolve("/testimonial/a.js"))


chore(server): simple grunt task for runnin mocha tests

--- build/config/shell.coffee
+++ build/config/shell.coffee
@@ -8,5 +8,8 @@
   startServer:
     command: "./script/start-server.sh"

+  testServer:
+    command: "mocha --compilers coffee:coffee-script --watch --reporter spec server/test"
+
   testCi:
     command: "./script/test-ci.sh"


JS: Move UI tests out from under general UI suite

--- tests_javascript/test_ui.coffee
+++ tests_javascript/test_ui.coffee
@@ -93,20 +93,28 @@
         jQuery.get '/navbar-test2'
         assert.strictEqual (@fixture.find '#badge-payouts').text(), ''

-    suite 'Localization', () ->

-        test 'Should retrieve translation files for the current locale'
+suite 'Localization', () ->

-        test 'Should return the existing promise for an in-progress setup'
+    setup () ->
+        @sandbox = sinon.sandbox.create()
+        @sandbox.useFakeServer()

-        test 'Should translate strings to the current locale'
+    teardown () ->
+        @sandbox.restore()

-        test 'Should format currency for ISK for the current locale'
+    test 'Should retrieve translation files for the current locale'

-        test 'Should format percentages for the current locale'
+    test 'Should return the existing promise for an in-progress setup'

-        test 'Should format decimal numbers for the current locale'
+    test 'Should translate strings to the current locale'

-        test 'Should format dates in a short format for the current locale'
+    test 'Should format currency for ISK for the current locale'

-        test 'Should format dates in a medium format for the current locale'
+    test 'Should format percentages for the current locale'
+
+    test 'Should format decimal numbers for the current locale'
+
+    test 'Should format dates in a short format for the current locale'
+
+    test 'Should format dates in a medium format for the current locale'


no other sorting unless table header is clicked

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -115,8 +115,6 @@

             if not @sortAscending
                 requests = requests.reverse()
-        else
-            requests.reverse()

         if @state in ['all', 'active', 'activeDeploy', 'noDeploy']
             for request in requests


UGGGGGH

UGGGGGH

--- tests/youtube-info.coffee
+++ tests/youtube-info.coffee
@@ -10,5 +10,5 @@
     room = helper.createRoom()
     room.user.say 'alice', 'https://www.youtube.com/watch?v=ePoi0_zSnYk'

-    it 'should be able to find title', ->
-        assert room.messages.length > 0
+  it 'should be able to find title', ->
+    assert room.messages.length > 0


Don't reload search results if they are cached

--- src/scripts/models/search-results.coffee
+++ src/scripts/models/search-results.coffee
@@ -42,7 +42,10 @@
       return @

     load: (options) ->
-      if options.query isnt @query or options.url isnt @searchUrl
+      query = options.query or @query
+      url = options.url or @searchUrl
+
+      if query isnt @query or url isnt @searchUrl
         @config(options)
         @fetch(options)


fix long polling in the local server case

--- src/scripts/services/dapiServices.coffee
+++ src/scripts/services/dapiServices.coffee
@@ -8,7 +8,8 @@
   reconnectInterval : 30000
   enableXDR: true
   timeout : 60000
-  fallbackTransport: 'streaming' # Server might has issues with 'long-polling'
+  pollingInterval : 5000
+  fallbackTransport: 'long-polling' # Server might has issues with 'long-polling'
   headers:
     api_key: apiKey
   #onError: (resp) =>


Color back + left/right arrow added

Color back + left/right arrow added

--- site/app.coffee
+++ site/app.coffee
@@ -448,7 +448,8 @@
             @nonInfluenzaWeekSeason = @nonInfluenzaWeekSeason+1
           @loadEpidata(@currentEpweek)
         else
-          alert("This is the lastest data we had! Please check back next week!")
+          wk = @maxEpiweek % 100
+          alert("Week" + wk + " is the lastest data we had! Please check back next week!")
       )
     window.onpopstate = (e) => @backToHome()
     $('#back_arrow').click((e) -> window.history.back())


Legend back

Legend back

--- site/app.coffee
+++ site/app.coffee
@@ -852,7 +852,7 @@
     ctx.setLineDash([1, 0])
     ctx.strokeStyle = 'green'
     # credible interval
-    for i in [0..numWeeks]
+    for i in [0..numWeeks-1]
       if @chartData[i].epiweek == @currentEpweek
         current = @chartData[i]
         centerX = i2x(i)
@@ -863,12 +863,12 @@
         ctx.lineTo(centerX, downY)
         ctx.stroke()
         ctx.beginPath()
-        ctx.moveTo(i2x(numWeeks-3), upY)
-        ctx.lineTo(2*centerX-i2x(numWeeks-3), upY)
+        ctx.moveTo(centerX-2*(i2x(2)-i2x(1)), upY)
+        ctx.lineTo(centerX+2*(i2x(2)-i2x(1)), upY)
         ctx.stroke()
         ctx.beginPath()
-        ctx.moveTo(i2x(numWeeks-3), downY)
-        ctx.lineTo(2*centerX-i2x(numWeeks-3), downY)
+        ctx.moveTo(centerX-2*(i2x(2)-i2x(1)), downY)
+        ctx.lineTo(centerX+2*(i2x(2)-i2x(1)), downY)
         ctx.stroke()
         ctx.beginPath()
         ctx.arc(centerX, centerY, 2, 0, 2 * Math.PI, false)


PR feedbacks

PR feedbacks

--- src/sprint/states/edit/controller.coffee
+++ src/sprint/states/edit/controller.coffee
@@ -6,13 +6,12 @@
   sprintUtils
   projectUtils
   Project
-  project
   Sprint
   sprint
   bdc
 ) ->
+
   $scope.editedSprint = sprint
-  $scope.project = project

   TrelloClient.get "/boards/#{$scope.project.boardId}/lists"
   .then (response) ->


Fix location from cursor target node

Fix location from cursor target node

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -132,7 +132,7 @@
         index = Number(index)
         offset = Number(offset)
         if node.nodeType is Node.TEXT_NODE
-          offset += containerOffset
+          offset += containerOffset unless nodeIsCursorTarget(node)
         else
           offset += 1 unless containerOffset is 0
         return {index, offset}
@@ -157,11 +157,9 @@
     node = null
     for offset, nodes of @getNodeRecords()[location.index]
       candidate = nodes[nodes.length - 1]
-      if offset > location.offset
-        break
+      break if offset > location.offset
       node = candidate
-      if offset is location.offset and candidate.nodeType is Node.TEXT_NODE and candidate.textContent is Trix.ZERO_WIDTH_SPACE
-        break
+      break if offset is location.offset and nodeIsCursorTarget(node)
     node

   getLocationRangeAtPoint: ([clientX, clientY]) ->
@@ -196,6 +194,13 @@
         clientY -= document.body.scrollTop

       [clientX, clientY]
+
+  nodeIsCursorTarget = (node) ->
+    return unless node
+    if node.nodeType is Node.TEXT_NODE
+      node.textContent is Trix.ZERO_WIDTH_SPACE
+    else
+      nodeIsCursorTarget(node.firstChild)

   getDOMSelection = ->
     selection = window.getSelection()


Consider start and end containers rather than the focused node when adjusting selections

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -77,14 +77,20 @@
   adjustSelectionInDirection: (direction) ->
     selection = window.getSelection()

+    containerAndOffsetAreValid = (container, offset) ->
+      node = DOM.findNodeForContainerAtOffset(container, offset)
+      if node.trixCursorTarget
+        node.nodeType is Node.TEXT_NODE and offset is 0
+      else
+        DOM.closestElementNode(node).isContentEditable
+
     selectionIsValid = ->
-      node = DOM.findNodeForContainerAtOffset(selection.focusNode, selection.focusOffset)
-      if node.trixCursorTarget
-        node.nodeType is Node.TEXT_NODE and selection.focusOffset is 0
-      else
-        DOM.closestElementNode(node).isContentEditable
+      range = selection.getRangeAt(0)
+      containerAndOffsetAreValid(range.startContainer, range.startOffset) and
+        containerAndOffsetAreValid(range.endContainer, range.endOffset)

     alter = if selection.isCollapsed then "move" else "extend"
+
     until selectionIsValid()
       previousSelection = focusNode: selection.focusNode, focusOffset: selection.focusOffset
       selection.modify(alter, direction, "character")


Optimized book list re-ordering on sort key change.

--- public/main.coffee
+++ public/main.coffee
@@ -25,10 +25,13 @@
       else
         b[@props.sort_by]
     React.DOM.ul className: "book-list",
-      BookListItem(key: id, book: book) for id, book of sortedBooks
+      BookListItem(key: book.vpl_id, book: book) for id, book of sortedBooks

 BookListItem = React.createClass
   displayName: "BookListItem"
+
+  shouldComponentUpdate: (nextProps, nextState) ->
+    @props.book isnt nextProps.book

   render: ->
     { vpl_id, title, vpl_url, author, availability, available, availability_url, holds, vpl_rating, isbn, img, goodreads_id, goodreads_url, goodreads_rating, amazon_url, amazon_rating, goodreads_categories, description, fiction } = @props.book


:art:

:art:

--- src/atom.coffee
+++ src/atom.coffee
@@ -16,6 +16,20 @@
 StorageFolder = require './storage-folder'
 getWindowLoadSettings = require './get-window-load-settings'

+Workspace = require './workspace'
+PaneContainer = require './pane-container'
+PaneAxis = require './pane-axis'
+Pane = require './pane'
+Project = require './project'
+TextEditor = require './text-editor'
+TextBuffer = require 'text-buffer'
+Gutter = require './gutter'
+PaneElement = require './pane-element'
+PaneContainerElement = require './pane-container-element'
+PaneAxisElement = require './pane-axis-element'
+TextEditorElement = require './text-editor-element'
+{createGutterView} = require './gutter-component-helpers'
+
 # Essential: Atom global for dealing with packages, themes, menus, and the window.
 #
 # An instance of this class is always available as the `atom` global.
@@ -154,6 +168,11 @@
     DeserializerManager = require './deserializer-manager'
     @deserializers = new DeserializerManager()
     @deserializeTimings = {}
+    @registerDeserializers()
+
+    ViewRegistry = require './view-registry'
+    @views = new ViewRegistry
+    @registerViewProviders()

     NotificationManager = require './notification-manager'
     @notifications = new NotificationManager
@@ -172,9 +191,8 @@

     CommandRegistry = require './command-registry'
     @commands = new CommandRegistry
-
-    ViewRegistry = require './view-registry'
-    @views = new ViewRegistry
+    registerDefaultCommands = require './register-default-commands'
+    registerDefaultCommands(@commands)

     PackageManager = require './package-manager'
     @packages = new PackageManager({devMode, configDirPath, resourcePath, safeMode, @config})
@@ -200,23 +218,10 @@
     GrammarRegistry = require './grammar-registry'
     @grammars = new GrammarRegistry({@config})

-    @registerDeserializersAndViewProviders()
-
-    registerDefaultCommands = require './register-default-commands'
-    registerDefaultCommands(@commands)
-
   setConfigSchema: ->
     @config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}

-  registerDeserializersAndViewProviders: ->
-    Workspace = require './workspace'
-    PaneContainer = require './pane-container'
-    PaneAxis = require './pane-axis'
-    Pane = require './pane'
-    Project = require './project'
-    TextEditor = require './text-editor'
-    TextBuffer = require 'text-buffer'
-
+  registerDeserializers: ->
     @deserializers.add(Workspace)
     @deserializers.add(PaneContainer)
     @deserializers.add(PaneAxis)
@@ -225,13 +230,7 @@
     @deserializers.add(TextEditor)
     @deserializers.add(TextBuffer)

-    Gutter = require './gutter'
-    PaneElement = require './pane-element'
-    PaneContainerElement = require './pane-container-element'
-    PaneAxisElement = require './pane-axis-element'
-    TextEditorElement = require './text-editor-element'
-    {createGutterView} = require './gutter-component-helpers'
-
+  registerViewProviders: ->
     @views.addViewProvider PaneContainer, (model) ->
       new PaneContainerElement().initialize(model)
     @views.addViewProvider PaneAxis, (model) ->


:memo: Update comment

:memo: Update comment

--- src/auth.coffee
+++ src/auth.coffee
@@ -1,10 +1,10 @@
 keytar = require 'keytar'

 module.exports =
-  # Get the GitHub API token from the keychain
+  # Get the Atom.io API token from the keychain.
   #
-  # * callback: A function to call with an error as the first argument and a
-  #             string token as the second argument.
+  # callback - A function to call with an error as the first argument and a
+  #            string token as the second argument.
   getToken: (callback) ->
     if token = process.env.ATOM_ACCESS_TOKEN
       callback(null, token)


Just use the text container content box, as that's basically what the whole range hack does

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -97,21 +97,18 @@
     unless FactlinkJailRoot.can_haz.client_buttons
       @$el.addClass 'fl-button-hide-default'

-  _textContainer: ($el) ->
+  $textContainer: ($el) ->
     for el in $el.parents()
-      return el if window.getComputedStyle(el).display == 'block'
+      return $(el) if window.getComputedStyle(el).display == 'block'
     console.error 'FactlinkJailRoot: No text container found for ', $el

   _updatePosition: ->
     top = @$nearEl.offset().top + @$nearEl.outerHeight()/2 - @frame.$el.outerHeight()/2

-    textContainer = @_textContainer(@$nearEl)
-    range = document.createRange()
-    range.setStartBefore textContainer
-    range.setEndAfter textContainer
-    rangeBox = FactlinkJailRoot.rangeBox(range)
+    $textContainer = @$textContainer(@$nearEl)
+    textContainerOffset = $textContainer.offset()

-    left = rangeBox.left + rangeBox.width
+    left = textContainerOffset.left + $textContainer.width()
     left = Math.min left, $(window).width() - @frame.$el.outerWidth()

     @_showAtCoordinates top, left


Change getFriends route to friends (POST -> GET), change addFriend route to friend

--- app/core/user.coffee
+++ app/core/user.coffee
@@ -185,10 +185,10 @@
         Room.newRoom req, res, { }

     getFriends : (req, res, next) =>
-        param = req.body
+        param = req.params
         if param?
-            if param._id?
-                Db.get 'user', param._id, (user) =>
+            if param.id?
+                Db.get 'user', param.id, (user) =>
                     if user.friends? and user.friends.length isnt 0
                         i = 0
                         @requestUser user.friends, i, [], (friends) =>
@@ -258,11 +258,11 @@
         '/quickLogin'   : exports.User.quickLogin
         '/quickJoin'    : exports.User.quickJoin
         '/join'         : exports.User.join
-        '/addFriend'   : exports.User.addFriend
-        '/getFriends'  : exports.User.getFriends
+        '/friend'       : exports.User.addFriend

     get :
         '/create'       : exports.User.createRoom
+        '/friends/:id'  : exports.User.getFriends

     put :
         '/user'   : exports.User.updateUser


Split up member import jobs.

Split up member import jobs.

--- jobs/import-members.coffee
+++ jobs/import-members.coffee
@@ -5,6 +5,7 @@

 await = require 'await'
 soap = require 'soap'
+ObjectId = require('mongodb').ObjectID
 membersSvcUri = "./wsdl/Members.svc.xml"

 # Data returned by node-soap that was nil in the output is represented
@@ -54,7 +55,7 @@

   baseUri + member.lastName + member.firstName + assemblyId + ".jpg"

-persistMember = (session, member, db, promise) ->
+persistMember = (session, member, db, callback) ->
   assemblyIdForMember = Number(member.Id)

   memberDetails =
@@ -81,24 +82,39 @@
     upsert: true,
     safe: true
   , (err, doc) ->
-    if err?
-      promise.fail(err)
-    else
-      promise.keep('member', doc)
+    callback(err)

-module.exports = (jobs, db) ->
+module.exports = (jobs, db) -> soap.createClient membersSvcUri, (err, client) ->
+  throw err if err
+
+  jobs.process 'persist member', 20, (job, callback) ->
+    # Ensure object IDs are in the correct format.
+    job.data.session._id = new ObjectId(job.data.session._id)
+
+    persistMember(job.data.session, job.data.member, db, callback)
+
+  jobs.process 'import member', 20, (job, callback) ->
+    callback("Individual member import not currently supported.")
+
+  jobs.process 'import all members for session', 5, (job, callback) ->
+    getMembersArgs =
+      SessionId: job.data.session.assemblyId
+
+    client.MemberService.BasicHttpBinding_MemberFinder.GetMembersBySession getMembersArgs, (err, result, raw) ->
+      if err
+        callback(err)
+      else
+        members = result.GetMembersBySessionResult.MemberListing
+
+        members.map (member) ->
+          jobs.create('persist member', member: member, session: job.data.session).save()
+
+        callback()
+
   jobs.process 'import members', (job, callback) ->
-    soap.createClient membersSvcUri, (err, client) -> ifSuccessful err, callback, ->
-      db.collection("sessions").find().toArray (err, results) -> ifSuccessful err, callback, ->
-        results.forEach (session) ->
-          getMembersArgs =
-            SessionId: session.assemblyId
+    db.collection("sessions").find().toArray (err, results) -> ifSuccessful err, callback, ->
+      results.forEach (session) ->
+        job.log "Kueing up import members for session job for " + session._id
+        jobs.create('import all members for session', session: session).save()

-          client.MemberService.BasicHttpBinding_MemberFinder.GetMembersBySession getMembersArgs, (err, result, raw) -> ifSuccessful err, callback, ->
-            members = result.GetMembersBySessionResult.MemberListing
-
-            promises = members.map (member) ->
-              await('member').run (promise) ->
-                persistMember(session, member, db, promise)
-
-            await.all(promises).onkeep((got) -> callback()).onfail(() -> callback([].slice.call(arguments).join('\n')))
+      callback()


Update to new file path

Update to new file path

--- spec/ListenRuntime.coffee
+++ spec/ListenRuntime.coffee
@@ -1,7 +1,7 @@
 noflo = require 'noflo'
 chai = require 'chai' unless chai
 ListenRuntime = require '../components/ListenRuntime.coffee'
-BaseRuntime = require '../node_modules/fbp-protocol-client/src/base'
+BaseRuntime = require '../node_modules/fbp-protocol-client/lib/base'

 describe 'ListenRuntime component', ->
   component = null


:green_heart: Avoid relying on className in specs

So that we're free to optimize Style Recalculation.

--- spec/git-diff-spec.coffee
+++ spec/git-diff-spec.coffee
@@ -33,7 +33,7 @@
       editor.insertText('a')
       advanceClock(editor.getBuffer().stoppedChangingDelay)
       expect(editorView.rootElement.querySelectorAll('.git-line-modified').length).toBe 1
-      expect(editorView.rootElement.querySelector('.git-line-modified')).toHaveClass('line-number-0')
+      expect(editorView.rootElement.querySelector('.git-line-modified')).toHaveData("buffer-row", 0)

   describe "when the editor has added lines", ->
     it "highlights the added lines", ->
@@ -43,7 +43,7 @@
       editor.insertText('a')
       advanceClock(editor.getBuffer().stoppedChangingDelay)
       expect(editorView.rootElement.querySelectorAll('.git-line-added').length).toBe 1
-      expect(editorView.rootElement.querySelector('.git-line-added')).toHaveClass('line-number-1')
+      expect(editorView.rootElement.querySelector('.git-line-added')).toHaveData("buffer-row", 1)

   describe "when the editor has removed lines", ->
     it "highlights the line preceeding the deleted lines", ->
@@ -52,7 +52,7 @@
       editor.deleteLine()
       advanceClock(editor.getBuffer().stoppedChangingDelay)
       expect(editorView.rootElement.querySelectorAll('.git-line-removed').length).toBe 1
-      expect(editorView.rootElement.querySelector('.git-line-removed')).toHaveClass('line-number-4')
+      expect(editorView.rootElement.querySelector('.git-line-removed')).toHaveData("buffer-row", 4)

   describe "when a modified line is restored to the HEAD version contents", ->
     it "removes the diff highlight", ->
@@ -83,7 +83,7 @@

       runs ->
         expect(editorView.rootElement.querySelectorAll('.git-line-modified').length).toBe 1
-        expect(editorView.rootElement.querySelector('.git-line-modified')).toHaveClass('line-number-0')
+        expect(editorView.rootElement.querySelector('.git-line-modified')).toHaveData("buffer-row", 0)

   describe "when the project paths change", ->
     it "doesn't try to use the destroyed git repository", ->


grunt update - test travis CI

grunt update - test travis CI

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -1,5 +1,4 @@
 module.exports = (grunt) ->
-
   grunt.initConfig
     pkg : grunt.file.readJSON('package.json')
     uglify :


Improvements

Improvements

--- cake/tasks/cordova.coffee
+++ cake/tasks/cordova.coffee
@@ -6,20 +6,23 @@

 module.exports = class Cordova extends Exec
   command: './node_modules/.bin/cordova'
-  cordovaPath: './build/cordova'
+  cordovaPath: 'build/cordova'
   platforms: ['android', 'ios']

   init: =>
     @_initialize()
   initAndroid: =>
-    @_initialize()
-    @addAndroid()
+    @_initialize =>
+      @addAndroid()
+      process.exit()
   initIOS: =>
-    @_initialize()
-    @addIOS()
+    @_initialize =>
+      @addIOS()
+      process.exit()
   initAll: =>
-    @_initialize()
-    @addAll()
+    @_initialize =>
+      @addAll()
+      process.exit()

   addAndroid: => @_add 'android'
   addIOS:     => @_add 'ios'
@@ -37,14 +40,14 @@
   emulateIOS:     => @_emulate 'ios'
   emulateAll:     => @_emulate @platforms...

-  _initialize: ->
+  _initialize: (callback) ->
     wrench.rmdirSyncRecursive @cordovaPath, ->
     args = ['create', @cordovaPath]

     packageNamePrompt = (callback) =>
       commander.prompt 'Package name (optional): ', (name) =>
         name = _s.clean name
-        name = "#{name.charAt(0).toLowerCase}#{name.slice(1)}"
+        name = "#{name.charAt(0).toLowerCase()}#{name.slice(1)}"
         if name isnt ''
           args.push name
           appNamePrompt callback
@@ -59,14 +62,18 @@

     packageNamePrompt =>
       @exec args
+      if callback
+        callback()
+      else
+        process.exit()

   _add: (platforms...) ->
     platforms.unshift 'add'
     onExit = =>
-      for platform in platforms
-        fs.createReadStream("../gitignore/#{platform}.gitignore")
+      for platform in platforms when platform isnt 'add'
+        fs.createReadStream("./cake/gitignore/#{platform}.gitignore")
           .pipe(fs.createWriteStream("#{@cordovaPath}/platforms/#{platform}/.gitignore"))
-    @exec(platforms, onExit, cwd: @cordovaPath)
+    @exec(platforms, onExit, cwd: @cordovaPath, env: process.env)

   _remove: (platforms...) ->
     platforms.unshift 'remove'


Inherit KONFIG environment variables in run script

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

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -23,7 +23,7 @@
     env = ''

     add = (name, value) ->
-      env += "export #{name}='#{JSON.stringify value}'\n"
+      env += "export #{name}=${#{name}:-#{JSON.stringify value}}\n"

     for name, value of KONFIG.ENV when name not in options.exclude
       add name, value


dev true makes infinite loop much longer

--- app/lib/Angel.coffee
+++ app/lib/Angel.coffee
@@ -7,6 +7,7 @@
 GoalManager = require 'lib/world/GoalManager'
 {sendContactMessage} = require 'core/contact'
 errors = require 'core/errors'
+utils = require 'core/utils'

 reportedLoadErrorAlready = false

@@ -33,6 +34,14 @@
       @infiniteLoopIntervalDuration *= 10
       @infiniteLoopTimeoutDuration *= 10
       @abortTimeoutDuration *= 10
+
+    if utils.getQueryVariable('dev')
+      # Set a very long timeout so that debugging can be done without infinite loop detection
+      # breaking the debugger.
+      @infiniteLoopIntervalDuration *= 100000
+      @infiniteLoopTimeoutDuration *= 100000
+      @abortTimeoutDuration *= 100000
+
     @initialized = false
     @running = false
     @allLogs = []


arrow_spacing: add test for empty functions

--- test/test_arrow_spacing.coffee
+++ test/test_arrow_spacing.coffee
@@ -163,6 +163,24 @@
             errors = coffeelint.lint(source, config)
             assert.isEmpty(errors, 0)

+    'Handle an empty arrow at end of expression' :
+        topic : ->
+            '''
+            (x = ->)
+            {x: ->}
+            (x: ->)
+            '''
+
+        'when spacing is required around arrow operator' : (source) ->
+            config = { "arrow_spacing": { "level": "error" } }
+            errors = coffeelint.lint(source, config)
+            assert.equal(errors.length, 0)
+
+        'when spacing is not required around arrow operator' : (source) ->
+            config = { "arrow_spacing": { "level": "ignore" } }
+            errors = coffeelint.lint(source, config)
+            assert.isEmpty(errors, 0)
+
     'Handle a nested arrow at end of file' :
         topic : ->
             'class A\n  f: ->'


update fr.coffee v0.7 (#4994)

update fr.coffee v0.7 (#4994)

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -1746,76 +1746,76 @@
 #    teacher_quest_reach_gamedev: "Explore new worlds!"
 #    teacher_quest_reach_gamedev_step1: "[Get licenses](/teachers/licenses) so that your students can explore new worlds, like Game Development and Web Development!"
     teacher_quest_done: "Voulez-vous que vos étudiants apprennent encore plus à coder? Restez en contact avec nos [spécialistes scolaires](mailto:schools@codecombat.com) aujourd'hui!"
-#    teacher_quest_keep_going: "Keep going! Here's what you can do next:"
-#    teacher_quest_more: "See all quests"
-#    teacher_quest_less: "See fewer quests"
-#    refresh_to_update: "(refresh the page to see updates)"
-#    view_project_gallery: "View Project Gallery"
-#    office_hours: "Teacher Webinars"
+    teacher_quest_keep_going: "Continuez d'avancer ! Voici ce que vous pouvez faire ensuite :"
+    teacher_quest_more: "Voir toutes les quêtes"
+    teacher_quest_less: "Voir moins de quêtes"
+    refresh_to_update: "(rafraîchir la page pour voir les mises à jour)"
+    view_project_gallery: "View Project Gallery"
+    office_hours: "webinar pour enseignants"
     office_hours_detail: "Apprenez comment continuer avec vos étudiants pendant qu'ils créent des jeux et embarquez dans leur aventure de programmation! Venez et assistez à notre"
-#    office_hours_link: "teacher webinar"
-#    office_hours_detail_2: "sessions."
+    office_hours_link: "webinar pour enseignants"
+    office_hours_detail_2: "sessions."
     success: "Succès"
-#    in_progress: "In Progress"
-#    not_started: "Not Started"
+    in_progress: "En cours"
+    not_started: "Pas commencé" #N'a pas commencé
     mid_course: "Moitié du Cours"
     end_course: "Fin du Cours"
-#    none: "None detected yet"
-#    explain_open_ended: "Note: Students are encouraged to solve this level creatively — one possible solution is provided below."
+    none: "Aucun n'a encore été détecté"
+    explain_open_ended: "Note : Les élèves sont encouragés à résoudre ce niveau de façon créative - une solution possible est fournie ci-dessous."
     level_label: "Niveau:"
     time_played_label: "Temps joué :"
-#    back_to_resource_hub: "Back to Resource Hub"
-#    back_to_course_guides: "Back to Course Guides"
-#    print_guide: "Print this guide"
-#    combo: "Combo"
-#    combo_explanation: "Students pass Combo challenge levels by using at least one listed concept. Review student code by clicking the progress dot."
-#    concept: "Concept"
+    back_to_resource_hub: "Retour au centre de ressources"
+    back_to_course_guides: "Retour aux guides de cours"
+    print_guide: "Imprimer ce guide"
+    combo: "Combo"
+    combo_explanation: "Les élèves réussissent les niveaux de défi Combo en utilisant au moins un des concepts énumérés. Passez en revue le code étudiant en cliquant sur le point de progression."
+    concept: "Concept"

   share_licenses:
-#    share_licenses: "Share Licenses"
-#    shared_by: "Shared By:"
-#    add_teacher_label: "Enter exact teacher email:"
-#    add_teacher_button: "Add Teacher"
-#    subheader: "You can make your licenses available to other teachers in your organization. Each license can only be used for one student at a time."
-#    teacher_not_found: "Teacher not found. Please make sure this teacher has already created a Teacher Account."
-#    teacher_not_valid: "This is not a valid Teacher Account. Only teacher accounts can share licenses."
+    share_licenses: "Licences d'actions"
+    shared_by: "Partagé par:"
+    add_teacher_label: "Entrez le courriel exact de l'enseignant :"
+    add_teacher_button: "Ajouter un enseignant"
+    subheader: "Vous pouvez mettre vos licences à la disposition des autres enseignants de votre organisation. Chaque licence ne peut être utilisée que pour un seul étudiant à la fois."
+    teacher_not_found: "Enseignant introuvable. Veuillez vous assurer que cet enseignant a déjà créé un compte enseignant."
+    teacher_not_valid: "Ce n'est pas un compte d'enseignant valide. Seuls les comptes enseignants peuvent partager des licences."
     already_shared: "Vous avez déjà partagé ces licences avec ce Professeur."
-#    teachers_using_these: "Teachers who can access these licenses:"
-#    footer: "When teachers revoke licenses from students, the licenses will be returned to the shared pool for other teachers in this group to use."
-#    you: "(you)"
-#    one_license_used: "(1 license used)"
+    teachers_using_these: "Les enseignants qui peuvent accéder à ces licences :"
+    footer: "Lorsque les enseignants révoquent les licences des élèves, les licences seront retournées au pool partagé pour que les autres enseignants de ce groupe puissent les utiliser."
+    you: "(vous)"
+    one_license_used: "(1 licence utilisée)"
     licenses_used: "(__licensesUsed__ licences utilisées)"
     more_info: " Plus d informations"

   sharing:
     game: "Jeu"
     webpage: "Page web"
-#    your_students_preview: "Your students will click here to see their finished projects! Unavailable in teacher preview."
-#    unavailable: "Link sharing not available in teacher preview."
-#    share_game: "Share This Game"
-#    share_web: "Share This Webpage"
-#    victory_share_prefix: "Share this link to invite your friends & family to"
-#    victory_share_prefix_short: "Invite people to"
-#    victory_share_game: "play your game level"
-#    victory_share_web: "view your webpage"
+    your_students_preview: "Vos élèves cliqueront ici pour voir leurs projets terminés ! Non disponible dans l'aperçu du enseignant."
+    unavailable: "Le partage de liens n'est pas disponible dans l'aperçu du enseignant."
+    share_game: "Partager ce jeu"
+    share_web: "Partager cette page Web"
+    victory_share_prefix: "Partagez ce lien pour inviter vos amis et votre famille à"
+    victory_share_prefix_short: "Invitez les gens à"
+    victory_share_game: "Jouez à votre niveau de jeu"
+    victory_share_web: "voir votre page web"
     victory_share_suffix: "."
-#    victory_course_share_prefix: "This link will let your friends & family"
-#    victory_course_share_game: "play the game"
-#    victory_course_share_web: "view the webpage"
-#    victory_course_share_suffix: "you just created."
+    victory_course_share_prefix: "Ce lien permettra à vos amis et à votre famille"
+    victory_course_share_game: "jouer le jeu"
+    victory_course_share_web: "voir la page web"
+    victory_course_share_suffix: "que vous venez de créer."
     copy_url: "Copier l'URL"
-#    share_with_teacher_email: "Send to your teacher"
+    share_with_teacher_email: "Envoyer à votre enseignant(e)"

   game_dev:
     creator: "Créateur"

   web_dev:
-#    image_gallery_title: "Image Gallery"
+    image_gallery_title: "Image Gallery"
     select_an_image: "Sélectionnez une image que vous voulez utiliser"
-#    scroll_down_for_more_images: "(Scroll down for more images)"
-#    copy_the_url: "Copy the URL below"
-#    copy_the_url_description: "Useful if you want to replace an existing image."
-#    copy_the_img_tag: "Copy the  tag"
+    scroll_down_for_more_images: "(Défilement vers le bas pour plus d'images)"
+    copy_the_url: "Copiez l'URL ci-dessous"
+    copy_the_url_description: "Utile si vous voulez remplacer une image existante."
+    copy_the_img_tag: "Copiez la balise ."
     copy_the_img_tag_description: "Utile si vous voulez insérer une nouvelle image."
     copy_url: "Copier l'URL"
     copy_img: "Copier "
@@ -2060,7 +2060,7 @@
     tournament_blurb: "Écrire du code, collecter de l'or, construire des armées, écraser les ennemis, gagner des prix, et mettre à jour votre carrière dans notre tournoi $ 40,000 Greed! Découvrez les détails"
     tournament_blurb_criss_cross: "Gagnez des offres, construisez des chemins, déjouez les adversaires, emparez-vous des gemmes, et améliorez votre carrière dans notre tournoi Criss-Cross! Découvrez les détails"
     tournament_blurb_zero_sum: "Libérez votre créativité en programmation dans l'accumulation d'or et de tactiques de combat dans ce combat épique entre le sorcier rouge et bleu. Le tournoi a commencé vendredi le 27 mars et se déroulera jusqu'à lundi le 6 avril à 5PM PDT. Battez-vous pour le plaisir et l'honneur! Aller voir les détails."
-#    tournament_blurb_ace_of_coders: "Battle it out in the frozen glacier in this domination-style mirror match! The tournament began on Wednesday, September 16 and will run until Wednesday, October 14 at 5PM PDT. Check out the details"
+    tournament_blurb_ace_of_coders: "Combattez-les dans le glacier gelé dans ce match en miroir de style domination ! Le tournoi a débuté le mercredi 16 septembre et se poursuivra jusqu'au mercredi 14 octobre à 17h PDT. Voir les détails"
     tournament_blurb_blog: "Sur notre blog"
     rules: "Règles"
     winners: "Gagnants"
@@ -2087,13 +2087,13 @@
     favorite_postfix: "."
     not_member_of_clans: "Ne fais partie d'aucun clan pour l'instant."
     certificate_view: "voir le certificat"
-#    certificate_click_to_view: "click to view certificate"
-#    certificate_course_incomplete: "course incomplete"
-#    certificate_of_completion: "Certificate of Completion"
-#    certificate_endorsed_by: "Endorsed by"
-#    certificate_stats: "Course Stats"
-#    certificate_lines_of: "lines of"
-#    certificate_levels_completed: "levels completed"
+    certificate_click_to_view: "cliquez pour voir le certificat"
+    certificate_course_incomplete: "cours incomplet"
+    certificate_of_completion: "Certificat d'achèvement"
+    certificate_endorsed_by: "Approuvé par"
+    certificate_stats: "Statistiques des cours"
+    certificate_lines_of: "lignes de"
+    certificate_levels_completed: "niveaux atteints"
     certificate_for: "Pour"

   achievements:
@@ -2135,8 +2135,8 @@
     email_sent: "Email envoyé ! Vérifiez votre boîte de réception."
     verifying_email: "Vérifiez votre adresse e-mail..."
     successfully_verified: "Vous avez vérifié votre adresse e-mail avec succès !"
-#    verify_error: "Something went wrong when verifying your email :("
-#    unsubscribe_from_marketing: "Unsubscribe __email__ from all CodeCombat marketing emails?"
+    verify_error: "Quelque chose a mal tourné lors de la vérification de votre email :("
+    unsubscribe_from_marketing: "Se désabonner __email__ de tous les emails marketing CodeCombat ?"
     unsubscribe_button: "Oui, se désabonner"
     unsubscribe_failed: "Échec"
     unsubscribe_success: "Succès"
@@ -2154,22 +2154,22 @@
   account_prepaid:
     purchase_code: "Acheter un code d'abonnement"
     purchase_code1: "Les codes d'abonnement peuvent seulement être racheter pour ajouter des comptes prenium à un ou plusieurs comptes de CodeCombat" # {change}
-#    purchase_code2: "Each CodeCombat account can only redeem a particular Subscription Code once."
-#    purchase_code3: "Subscription Code months will be added to the end of any existing subscription on the account."
-#    purchase_code4: "Subscription Codes are for accounts playing the Home version of CodeCombat, they cannot be used in place of Student Licenses for the Classroom version."
+    purchase_code2: "Chaque compte CodeCombat ne peut échanger qu'une seule fois un Code d'Abonnement particulier."
+    purchase_code3: "Les mois de code d'abonnement seront ajoutés à la fin de tout abonnement existant sur le compte."
+    purchase_code4: "Les codes d'abonnement sont pour les comptes jouant la version Home de CodeCombat, ils ne peuvent pas être utilisés à la place des licences étudiants pour la version Classroom."
     purchase_code5: "Pour plus d'informations sur les licences étudiants, veuillez contacter"
     users: "Utilisateurs"
     months: "Mois"
     purchase_total: "Total"
     purchase_button: "Soumettre un achat"
     your_codes: "Vos codes"
-#    redeem_codes: "Redeem a Subscription Code"
+    redeem_codes: "Échanger un code d'abonnement"
     prepaid_code: "Code prépayé"
-#    lookup_code: "Lookup prepaid code"
-#    apply_account: "Apply to your account"
-#    copy_link: "You can copy the code's link and send it to someone."
+    lookup_code: "Code de recherche prépayé"
+    apply_account: "Appliquer à votre compte"
+    copy_link: "Vous pouvez copier le lien du code et l'envoyer à quelqu'un."
     quantity: "Quantité"
-#    redeemed: "Redeemed"
+    redeemed: "Rachetés"
     no_codes: "Pas de code soumis"
     you_can1: "Vous pouvez"
     you_can2: "achetez un code pré-payé"
@@ -2178,14 +2178,14 @@
   loading_error:
     could_not_load: "Erreur de chargement du serveur" # {change}
     connection_failure: "La connexion a échoué."
-#    connection_failure_desc: "It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page."
-#    login_required: "Login Required"
-#    login_required_desc: "You need to be logged in to access this page."
+    connection_failure_desc: "On dirait que vous n'êtes pas connecté à Internet ! Vérifiez votre connexion réseau et rechargez cette page."
+    login_required: "Login requis"
+    login_required_desc: "Vous devez être connecté pour accéder à cette page."
     unauthorized: "Vous devez être identifié pour faire cela. Avez-vous désactivé les cookies ?"
     forbidden: "Vous n'avez pas la permission."
-#    forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!"
+    forbidden_desc: "Oh non, il n'y a rien qu'on puisse vous montrer ici ! Assurez-vous d'être connecté au bon compte, ou visitez l'un des liens ci-dessous pour revenir à la programmation !"
     not_found: "Introuvable."
-#    not_found_desc: "Hm, there’s nothing here. Visit one of the following links to get back to programming!"
+    not_found_desc: "Hm, il n'y a rien ici. Visitez l'un des liens suivants pour revenir à la programmation !"
     not_allowed: "Méthode non autorisée."
     timeout: "Délai de connexion au serveur écoulé." # {change}
     conflict: "Conflit de ressources."
@@ -2193,7 +2193,7 @@
     server_error: "Erreur serveur."
     unknown: "Erreur inconnue"
     error: "ERREUR"
-#    general_desc: "Something went wrong, and it’s probably our fault. Try waiting a bit and then refreshing the page, or visit one of the following links to get back to programming!"
+    general_desc: "Quelque chose a mal tourné, et c'est probablement de notre faute. Essayez d'attendre un peu puis de rafraîchir la page, ou visitez l'un des liens suivants pour revenir à la programmation !"

   resources:
     level: "Niveau"
@@ -2216,7 +2216,7 @@
 #    algorithm_search_binary: "Algorithm Search Binary"
 #    algorithm_search_graph: "Algorithm Search Graph"
 #    algorithm_sort: "Algorithm Sort"
-#    algorithm_sum: "Algorithm Sum"
+    algorithm_sum: "Somme des algorithmes"
     arguments: "Paramètres"
     arithmetic: "Arithmétique"
     array_2d: "Tableaux 2D"
@@ -2294,12 +2294,12 @@
 #    queues: "Data Structures - Queues"
     reading_docs: "Lire la docs"
     recursion: "Récursivité"
-#    return_statements: "Return Statements"
+    return_statements: "Relevés de retour"
 #    stacks: "Data Structures - Stacks"
     strings: "Chaînes de caractères"
 #    strings_concatenation: "String Concatenation"
 #    strings_substrings: "Substring"
-#    trees: "Data Structures - Trees"
+    trees: "Structures de données - Arbres"
     variables: "Variables"
     vectors: "Vecteurs"
     while_condition_loops: "Boucles tant que avec les conditions"
@@ -2378,7 +2378,7 @@
     cla_url: "CLA"
     contributor_description_suffix: "auxquelles vous devez vous soumettre avant de contribuer."
     code_title: "Code - MIT" # {change}
-#    client_code_description_prefix: "All client-side code for codecombat.com in the public GitHub repository and in the codecombat.com database, is licensed under the"
+    client_code_description_prefix: "Tout le code côté client de codecombat.com dans le référentiel public GitHub et dans la base de données de codecombat.com, est sous licence en vertu de la norme"
     mit_license_url: "MIT"
     code_description_suffix: "Cela inclut le code dans Systèmes et Composants qui est rendu disponible par CodeCombat dans le but de créer des niveaux."
     art_title: "Art/Musique - Creative Commons "
@@ -2449,15 +2449,15 @@
     december: "Décembre"

   code_play_create_account_modal:
-    title: "Tu l'as fait !" # This section is only needed in US, UK, Mexico, India, and Germany
-#    body: "You are now on your way to becoming a master coder. Sign up to receive an extra 100 Gems & you will also be entered for a chance to win $2,500 & other Lenovo Prizes."
+    title: "Tu l'as fait !" # Cette section n'est nécessaire qu'aux États-Unis, au Royaume-Uni, au Mexique, en Inde et en Allemagne.
+    body: "Vous êtes maintenant en voie de devenir un maître codeur. Inscrivez-vous pour recevoir un prix supplémentaire 100 gemmes  & vous aurez également la chance de gagner 2 500 $ et d'autres prix Lenovo  pour une chance de ."
     sign_up: "Inscrivez-vous et continuez à coder ▶"
     victory_sign_up_poke: "Créez un compte gratuit pour sauvegarder votre code et courez la chance de gagner des prix !"
     victory_sign_up: "Inscrivez-vous et soyez inscrit à win $2,500"

-#  server_error:
-#    email_taken: "Email already taken"
-#    username_taken: "Username already taken"
+  server_error:
+    email_taken: "Email déjà pris"
+    username_taken: "Nom d'utilisateur déjà pris"

   esper:
 #    line_no: "Line $1: "
@@ -2639,7 +2639,7 @@
     page_heading: "Apprenez à vos élèves à construire leur propre jeu d'arcade !"
     get_started: "Commencer:"
     download_lesson: "Télécharger le plan de leçon :"
-#    lesson_copy: "This PDF will take you through how to introduce and close out the Hour of Code lesson, the learning objectives, key terms and concepts, and discussion questions to inspire more coding curiosity."
+    lesson_copy: "Ce PDF vous montrera comment présenter et clôturer la leçon de l'Heure du code, les objectifs d'apprentissage, les termes et concepts clés et les questions de discussion pour inspirer plus de curiosité sur le codage."
     download_pdf: "Télécharger le PDF"
     ready_play: "Prêt à jouer ?"
     play_activity: "Activité de jeu"
@@ -2647,7 +2647,7 @@
     about_copy: "CodeCombat est un programme d'informatique basé sur le jeu pour enseigner le vrai Python et JavaScript. Le programme d'études conforme aux normes de CodeCombat s'articule autour d'un jeu que les élèves adorent. Plus de 12 millions d'étudiants ont appris à coder sur CodeCombat !"
     point1: "✓ Échafaudés"
     point2: "✓ Différencié"
-#    point3: "✓ Formative and Summative Assessments"
+    point3: "✓ Évaluations formative et sommative"
     point4: "✓ Cours par projet"
     point5: "✓ Suivi des étudiants"
     point6: "✓ Plans de cours complets"


Update ja.coffee

Update ja.coffee

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -79,8 +79,8 @@
     adjust_volume: "音量を調整する"
     campaign_multiplayer: "マルチプレイ・アリーナ"
     campaign_multiplayer_description: "コーディングで他のプレイヤーに対して格闘しましょう"
-#    campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
-#    campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."
+    campaign_old_multiplayer: "(非推奨)旧マルチプレイ・アリーナ"
+    campaign_old_multiplayer_description: "より文化的だった時代の遺跡。 シュミレーションは古いため実行されなく, ヒーローもいないマルチプレイ・アリーナです。"

   share_progress_modal:
     blurb: "あなたは進歩している！ CodeCombatでどんなに学んだかについて、誰かに伝えてください。"