update UpdatesManager tests

update UpdatesManager tests

--- services/track-changes/test/unit/coffee/UpdatesManager/UpdatesManagerTests.coffee
+++ services/track-changes/test/unit/coffee/UpdatesManager/UpdatesManagerTests.coffee
@@ -265,50 +265,74 @@
 		it "should call the callback", ->
 			@callback.called.should.equal true

-	# describe "getDocUpdates", ->
-	# 	beforeEach ->
-	# 		@updates = ["mock-updates"]
-	# 		@options = { to: "mock-to", limit: "mock-limit" }
-	# 		@MongoManager.getDocUpdates = sinon.stub().callsArgWith(2, null, @updates)
-	# 		@UpdatesManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(2)
-	# 		@UpdatesManager.getDocUpdates @project_id, @doc_id, @options, @callback
-
-	# 	it "should process outstanding updates", ->
-	# 		@UpdatesManager.processUncompressedUpdatesWithLock
-	# 			.calledWith(@project_id, @doc_id)
-	# 			.should.equal true
-
-	# 	it "should get the updates from the database", ->
-	# 		@MongoManager.getDocUpdates
-	# 			.calledWith(@doc_id, @options)
-	# 			.should.equal true
-
-	# 	it "should return the updates", ->
-	# 		@callback
-	# 			.calledWith(null, @updates)
-	# 			.should.equal true
-
-	# describe "getDocUpdatesWithUserInfo", ->
-	# 	beforeEach ->
-	# 		@updates = ["mock-updates"]
-	# 		@options = { to: "mock-to", limit: "mock-limit" }
-	# 		@updatesWithUserInfo = ["updates-with-user-info"]
-	# 		@UpdatesManager.getDocUpdates = sinon.stub().callsArgWith(3, null, @updates)
-	# 		@UpdatesManager.fillUserInfo = sinon.stub().callsArgWith(1, null, @updatesWithUserInfo)
-	# 		@UpdatesManager.getDocUpdatesWithUserInfo @project_id, @doc_id, @options, @callback
-
-	# 	it "should get the updates", ->
-	# 		@UpdatesManager.getDocUpdates
-	# 			.calledWith(@project_id, @doc_id, @options)
-	# 			.should.equal true
-
-	# 	it "should file the updates with the user info", ->
-	# 		@UpdatesManager.fillUserInfo
-	# 			.calledWith(@updates)
-	# 			.should.equal true
-
-	# 	it "should return the updates with the filled details", ->
-	# 		@callback.calledWith(null, @updatesWithUserInfo).should.equal true
+	describe "getDocUpdates", ->
+		beforeEach ->
+			@updates = ["mock-updates"]
+			@options = { to: "mock-to", limit: "mock-limit" }
+			@PackManager.getOpsByVersionRange = sinon.stub().callsArgWith(4, null, @updates)
+			@UpdatesManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(2)
+			@UpdatesManager.getDocUpdates @project_id, @doc_id, @options, @callback
+
+		it "should process outstanding updates", ->
+			@UpdatesManager.processUncompressedUpdatesWithLock
+				.calledWith(@project_id, @doc_id)
+				.should.equal true
+
+		it "should get the updates from the database", ->
+			@PackManager.getOpsByVersionRange
+				.calledWith(@project_id, @doc_id, @options.from, @options.to)
+				.should.equal true
+
+		it "should return the updates", ->
+			@callback
+				.calledWith(null, @updates)
+				.should.equal true
+
+	describe "getDocUpdatesWithUserInfo", ->
+		beforeEach ->
+			@updates = ["mock-updates"]
+			@options = { to: "mock-to", limit: "mock-limit" }
+			@updatesWithUserInfo = ["updates-with-user-info"]
+			@UpdatesManager.getDocUpdates = sinon.stub().callsArgWith(3, null, @updates)
+			@UpdatesManager.fillUserInfo = sinon.stub().callsArgWith(1, null, @updatesWithUserInfo)
+			@UpdatesManager.getDocUpdatesWithUserInfo @project_id, @doc_id, @options, @callback
+
+		it "should get the updates", ->
+			@UpdatesManager.getDocUpdates
+				.calledWith(@project_id, @doc_id, @options)
+				.should.equal true
+
+		it "should file the updates with the user info", ->
+			@UpdatesManager.fillUserInfo
+				.calledWith(@updates)
+				.should.equal true
+
+		it "should return the updates with the filled details", ->
+			@callback.calledWith(null, @updatesWithUserInfo).should.equal true
+
+	describe "processUncompressedUpdatesForProject", ->
+		beforeEach (done) ->
+			@doc_ids = ["mock-id-1", "mock-id-2"]
+			@UpdatesManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(2)
+			@RedisManager.getDocIdsWithHistoryOps = sinon.stub().callsArgWith(1, null, @doc_ids)
+			@UpdatesManager.processUncompressedUpdatesForProject @project_id, () =>
+				@callback()
+				done()
+
+		it "should get all the docs with history ops", ->
+			@RedisManager.getDocIdsWithHistoryOps
+				.calledWith(@project_id)
+				.should.equal true
+
+		it "should process the doc ops for the each doc_id", ->
+			for doc_id in @doc_ids
+				@UpdatesManager.processUncompressedUpdatesWithLock
+					.calledWith(@project_id, doc_id)
+					.should.equal true
+
+		it "should call the callback", ->
+			@callback.called.should.equal true
+

 	# describe "getProjectUpdates", ->
 	# 	beforeEach ->
@@ -333,28 +357,6 @@
 	# 			.calledWith(null, @updates)
 	# 			.should.equal true

-	# describe "processUncompressedUpdatesForProject", ->
-	# 	beforeEach (done) ->
-	# 		@doc_ids = ["mock-id-1", "mock-id-2"]
-	# 		@UpdatesManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(2)
-	# 		@RedisManager.getDocIdsWithHistoryOps = sinon.stub().callsArgWith(1, null, @doc_ids)
-	# 		@UpdatesManager.processUncompressedUpdatesForProject @project_id, () =>
-	# 			@callback()
-	# 			done()
-
-	# 	it "should get all the docs with history ops", ->
-	# 		@RedisManager.getDocIdsWithHistoryOps
-	# 			.calledWith(@project_id)
-	# 			.should.equal true
-
-	# 	it "should process the doc ops for the each doc_id", ->
-	# 		for doc_id in @doc_ids
-	# 			@UpdatesManager.processUncompressedUpdatesWithLock
-	# 				.calledWith(@project_id, doc_id)
-	# 				.should.equal true
-
-	# 	it "should call the callback", ->
-	# 		@callback.called.should.equal true

 	# describe "getProjectUpdatesWithUserInfo", ->
 	# 	beforeEach ->
@@ -571,200 +573,200 @@
 	# 				op: "mock-op-2"
 	# 			}]

-	# describe "_summarizeUpdates", ->
-	# 	beforeEach ->
-	# 		@now = Date.now()
-	# 		@user_1 = { id: "mock-user-1" }
-	# 		@user_2 = { id: "mock-user-2" }
-
-	# 	it "should concat updates that are close in time", ->
-	# 		result = @UpdatesManager._summarizeUpdates [{
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_1
-	# 				start_ts: @now + 20
-	# 				end_ts:   @now + 30
-	# 			v: 5
-	# 		}, {
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_2
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 			v: 4
-	# 		}]
-
-	# 		expect(result).to.deep.equal [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 4
-	# 					toV: 5
-	# 			meta:
-	# 				users: [@user_1, @user_2]
-	# 				start_ts: @now
-	# 				end_ts:   @now + 30
-	# 		}]
-
-	# 	it "should leave updates that are far apart in time", ->
-	# 		oneDay = 1000 * 60 * 60 * 24
-	# 		result = @UpdatesManager._summarizeUpdates [{
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_2
-	# 				start_ts: @now + oneDay
-	# 				end_ts:   @now + oneDay + 10
-	# 			v: 5
-	# 		}, {
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_1
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 			v: 4
-	# 		}]
-	# 		expect(result).to.deep.equal [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 5
-	# 					toV: 5
-	# 			meta:
-	# 				users: [@user_2]
-	# 				start_ts: @now + oneDay
-	# 				end_ts:   @now + oneDay + 10
-	# 		}, {
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 4
-	# 					toV: 4
-	# 			meta:
-	# 				users: [@user_1]
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 		}]
-
-	# 	it "should concat onto existing summarized updates", ->
-	# 		result = @UpdatesManager._summarizeUpdates [{
-	# 			doc_id: "doc-id-2"
-	# 			meta:
-	# 				user: @user_1
-	# 				start_ts: @now + 20
-	# 				end_ts:   @now + 30
-	# 			v: 5
-	# 		}, {
-	# 			doc_id: "doc-id-2"
-	# 			meta:
-	# 				user: @user_2
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 			v: 4
-	# 		}], [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 6
-	# 					toV: 8
-	# 			meta:
-	# 				users: [@user_1]
-	# 				start_ts: @now + 40
-	# 				end_ts:   @now + 50
-	# 		}]
-	# 		expect(result).to.deep.equal [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					toV: 8
-	# 					fromV: 6
-	# 				"doc-id-2":
-	# 					toV: 5
-	# 					fromV: 4
-	# 			meta:
-	# 				users: [@user_1, @user_2]
-	# 				start_ts: @now
-	# 				end_ts:   @now + 50
-	# 		}]
-
-	# 	it "should include null user values", ->
-	# 		result = @UpdatesManager._summarizeUpdates [{
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_1
-	# 				start_ts: @now + 20
-	# 				end_ts:   @now + 30
-	# 			v: 5
-	# 		}, {
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: null
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 			v: 4
-	# 		}]
-	# 		expect(result).to.deep.equal [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 4
-	# 					toV: 5
-	# 			meta:
-	# 				users: [@user_1, null]
-	# 				start_ts: @now
-	# 				end_ts:   @now + 30
-	# 		}]
-
-	# 	it "should include null user values, when the null is earlier in the updates list", ->
-	# 		result = @UpdatesManager._summarizeUpdates [{
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: null
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 			v: 4
-	# 		}, {
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_1
-	# 				start_ts: @now + 20
-	# 				end_ts:   @now + 30
-	# 			v: 5
-	# 		}]
-	# 		expect(result).to.deep.equal [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 4
-	# 					toV: 5
-	# 			meta:
-	# 				users: [null, @user_1]
-	# 				start_ts: @now
-	# 				end_ts:   @now + 30
-	# 		}]
-
-	# 	it "should roll several null user values into one", ->
-	# 		result = @UpdatesManager._summarizeUpdates [{
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: @user_1
-	# 				start_ts: @now + 20
-	# 				end_ts:   @now + 30
-	# 			v: 5
-	# 		}, {
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: null
-	# 				start_ts: @now
-	# 				end_ts:   @now + 10
-	# 			v: 4
-	# 		}, {
-	# 			doc_id: "doc-id-1"
-	# 			meta:
-	# 				user: null
-	# 				start_ts: @now + 2
-	# 				end_ts:   @now + 4
-	# 			v: 4
-	# 		}]
-	# 		expect(result).to.deep.equal [{
-	# 			docs:
-	# 				"doc-id-1":
-	# 					fromV: 4
-	# 					toV: 5
-	# 			meta:
-	# 				users: [@user_1, null]
-	# 				start_ts: @now
-	# 				end_ts:   @now + 30
-	# 		}]
+	describe "_summarizeUpdates", ->
+		beforeEach ->
+			@now = Date.now()
+			@user_1 = { id: "mock-user-1" }
+			@user_2 = { id: "mock-user-2" }
+
+		it "should concat updates that are close in time", ->
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_1.id
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_2.id
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}]
+
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						fromV: 4
+						toV: 5
+				meta:
+					user_ids: [@user_1.id, @user_2.id]
+					start_ts: @now
+					end_ts:   @now + 30
+			}]
+
+		it "should leave updates that are far apart in time", ->
+			oneDay = 1000 * 60 * 60 * 24
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_2.id
+					start_ts: @now + oneDay
+					end_ts:   @now + oneDay + 10
+				v: 5
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_1.id
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}]
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						fromV: 5
+						toV: 5
+				meta:
+					user_ids: [@user_2.id]
+					start_ts: @now + oneDay
+					end_ts:   @now + oneDay + 10
+			}, {
+				docs:
+					"doc-id-1":
+						fromV: 4
+						toV: 4
+				meta:
+					user_ids: [@user_1.id]
+					start_ts: @now
+					end_ts:   @now + 10
+			}]
+
+		it "should concat onto existing summarized updates", ->
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-2"
+				meta:
+					user_id: @user_1.id
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
+			}, {
+				doc_id: "doc-id-2"
+				meta:
+					user_id: @user_2.id
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}], [{
+				docs:
+					"doc-id-1":
+						fromV: 6
+						toV: 8
+				meta:
+					user_ids: [@user_1.id]
+					start_ts: @now + 40
+					end_ts:   @now + 50
+			}]
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						toV: 8
+						fromV: 6
+					"doc-id-2":
+						toV: 5
+						fromV: 4
+				meta:
+					user_ids: [@user_1.id, @user_2.id]
+					start_ts: @now
+					end_ts:   @now + 50
+			}]
+
+		it "should include null user values", ->
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_1.id
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user_id: null
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}]
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						fromV: 4
+						toV: 5
+				meta:
+					user_ids: [@user_1.id, null]
+					start_ts: @now
+					end_ts:   @now + 30
+			}]
+
+		it "should include null user values, when the null is earlier in the updates list", ->
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-1"
+				meta:
+					user_id: null
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_1.id
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
+			}]
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						fromV: 4
+						toV: 5
+				meta:
+					user_ids: [null, @user_1.id]
+					start_ts: @now
+					end_ts:   @now + 30
+			}]
+
+		it "should roll several null user values into one", ->
+			result = @UpdatesManager._summarizeUpdates [{
+				doc_id: "doc-id-1"
+				meta:
+					user_id: @user_1.id
+					start_ts: @now + 20
+					end_ts:   @now + 30
+				v: 5
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user_id: null
+					start_ts: @now
+					end_ts:   @now + 10
+				v: 4
+			}, {
+				doc_id: "doc-id-1"
+				meta:
+					user_id: null
+					start_ts: @now + 2
+					end_ts:   @now + 4
+				v: 4
+			}]
+			expect(result).to.deep.equal [{
+				docs:
+					"doc-id-1":
+						fromV: 4
+						toV: 5
+				meta:
+					user_ids: [@user_1.id, null]
+					start_ts: @now
+					end_ts:   @now + 30
+			}]


Removed fakeSetTimeout

Removed fakeSetTimeout

--- spec/pinned-tabs-spec.coffee
+++ spec/pinned-tabs-spec.coffee
@@ -1,12 +1,5 @@
 PinnedTabs = require '../lib/pinned-tabs.coffee'
 {CommandRegistry, TextEditor} = require 'atom'
-
-
-fakeSetTimeout = (funcToCall, millis) ->
-  if jasmine.Clock.installed.setTimeout.apply
-    return jasmine.Clock.installed.setTimeout.apply this, arguments
-  else
-    return jasmine.Clock.installed.setTimeout funcToCall, millis

 describe 'PinnedTabs', ->


:white_check_mark: #241 add spec for gn and gN

--- spec/text-object-spec.coffee
+++ spec/text-object-spec.coffee
@@ -1,4 +1,5 @@
 {getVimState, dispatch, TextData} = require './spec-helper'
+globalState = require '../lib/global-state'

 describe "TextObject", ->
   [set, ensure, keystroke, editor, editorElement, vimState] = []
@@ -1345,3 +1346,139 @@
         ensure 'vae', selectedText: text
         ensure 'escape', selectedText: ''
         ensure 'jjvae', selectedText: text
+
+  describe 'SearchMatchForward, SearchBackwards', ->
+    text = """
+      0 xxx
+      1 abc xxx
+      2   xxx yyy
+      3 xxx abc
+      4 abc\n
+      """
+    beforeEach ->
+      set text: text, cursor: [0, 0]
+      ensure ['/', search: 'abc'], cursor: [1, 2], mode: 'normal'
+      expect(globalState.lastSearchPattern).toEqual /abc/g
+
+    describe 'gn from normal mode', ->
+      it 'select ranges matches to last search pattern and extend selection', ->
+        ensure 'gn',
+          cursor: [1, 5]
+          mode: ['visual', 'characterwise']
+          selectionIsReversed: false
+          selectedText: 'abc'
+        ensure 'gn',
+          selectionIsReversed: false
+          mode: ['visual', 'characterwise']
+          selectedText: """
+            abc xxx
+            2   xxx yyy
+            3 xxx abc
+            """
+        ensure 'gn',
+          selectionIsReversed: false
+          mode: ['visual', 'characterwise']
+          selectedText: """
+            abc xxx
+            2   xxx yyy
+            3 xxx abc
+            4 abc
+            """
+        ensure 'gn', # Do nothing
+          selectionIsReversed: false
+          mode: ['visual', 'characterwise']
+          selectedText: """
+            abc xxx
+            2   xxx yyy
+            3 xxx abc
+            4 abc
+            """
+    describe 'gN from normal mode', ->
+      beforeEach ->
+        set cursor: [4, 3]
+      it 'select ranges matches to last search pattern and extend selection', ->
+        ensure 'gN',
+          cursor: [4, 2]
+          mode: ['visual', 'characterwise']
+          selectionIsReversed: true
+          selectedText: 'abc'
+        ensure 'gN',
+          selectionIsReversed: true
+          mode: ['visual', 'characterwise']
+          selectedText: """
+            abc
+            4 abc
+            """
+        ensure 'gN',
+          selectionIsReversed: true
+          mode: ['visual', 'characterwise']
+          selectedText: """
+            abc xxx
+            2   xxx yyy
+            3 xxx abc
+            4 abc
+            """
+        ensure 'gN', # Do nothing
+          selectionIsReversed: true
+          mode: ['visual', 'characterwise']
+          selectedText: """
+            abc xxx
+            2   xxx yyy
+            3 xxx abc
+            4 abc
+            """
+    describe 'as operator target', ->
+      it 'delete next occurence of last sarch pattern', ->
+        ensure 'dgn',
+          cursor: [1, 2]
+          mode: 'normal'
+          text: """
+            0 xxx
+            1  xxx
+            2   xxx yyy
+            3 xxx abc
+            4 abc\n
+            """
+        ensure '.',
+          cursor: [3, 5]
+          mode: 'normal'
+          text: """
+            0 xxx
+            1  xxx
+            2   xxx yyy
+            3 xxx_
+            4 abc\n
+            """.replace('_', ' ') # To prevent trailing space remved on save.
+        ensure '.',
+          cursor: [4, 1]
+          mode: 'normal'
+          text: """
+            0 xxx
+            1  xxx
+            2   xxx yyy
+            3 xxx_
+            4 \n
+            """.replace('_', ' ') # To prevent trailing space remved on save.
+      it 'change next occurence of last sarch pattern', ->
+        ensure 'cgn',
+          cursor: [1, 2]
+          mode: 'insert'
+          text: """
+            0 xxx
+            1  xxx
+            2   xxx yyy
+            3 xxx abc
+            4 abc\n
+            """
+        keystroke 'escape'
+        set cursor: [4, 0]
+        ensure 'cgN',
+          cursor: [3, 6]
+          mode: 'insert'
+          text: """
+            0 xxx
+            1  xxx
+            2   xxx yyy
+            3 xxx_
+            4 abc\n
+            """.replace('_', ' ') # To prevent trailing space remved on save.


We need the dom-to-xhtml plugin, otherwise parsererror results after the first edit.

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -65,6 +65,7 @@
         'oer/definition'
         'oer/multipart'
         'oer/copy'
+        'common/dom-to-xhtml'
       ]

       # This whole thing is what's needed to:


Fixed Compiler's warnings #54

Fixed Compiler's warnings #54

--- wzk/ui/grid/Grid.coffee
+++ wzk/ui/grid/Grid.coffee
@@ -1,3 +1,5 @@
+goog.provide 'wzk.ui.grid.Grid'
+
 goog.require 'goog.dom.DomHelper'
 goog.require 'goog.object'
 goog.require 'goog.ui.Dialog'
@@ -75,7 +77,7 @@

   ###*
     @protected
-    @param {Element} el
+    @param {Element|undefined} el
     @param {number} total
     @param {number} count
   ###


fix_2

--- server/api/setOutcome.coffee
+++ server/api/setOutcome.coffee
@@ -49,11 +49,10 @@
 storeToDatabase = (req, res) ->
     submit = await Submit.findById(req.params.submitId)
     problemId = submit.problem
-    problem = await Problem.findById(problemId)
-    logger.info("Store to database #{req.params.submitId} #{problemId} #{problem?._id}")
+    material = await Material.findById(problemId + lang)
+    logger.info("Store to database #{req.params.submitId} #{problemId} #{material._id}")
     user = await User.findByIdWithTelegram(submit.user)
     lang = GROUPS[user.userList].lang
-    material = await Material.findById(problemId + lang)

     msg = generateMsg(lang, req.body.result, material.title, material._id)

@@ -75,7 +74,7 @@
                 _id: "_#{rndId}r#{req.params.submitId}"
                 submit: req.params.submitId
                 problemId: problemId
-                problemName: problem.name
+                problemName: material._id
                 userId: submit.user
                 text: comment
                 time: new Date()
@@ -88,6 +87,7 @@
     dirtyResults = {}
     dirtyUsers = {}
     await setDirty(submit, dirtyResults, dirtyUsers)
+    await User.updateUser(submit.user, dirtyResults)

     await notifyUser submit.user, msg


remove nonsensical value

remove nonsensical value

Former-commit-id: 6dc141acdb6aacd40775b56d84592c28f30b1075 [formerly 34e58bd8c8cd80bf96fbdec0527f1d4bcee60714] [formerly f5975edc5f9331cfc4c4a4a0ee02edbb21d14c16 [formerly 0c634a0f00ec870ae97ecda4e5e6ef3de2763a4d]] [formerly 9990ee5a2dac7649e14a02b62eade48d40c0576a [formerly 8f020860d6c6f299b9c386cd3adf034ab3be5605] [formerly 15213f31804bc13c8c2e64a32c291c717d78e6e8 [formerly 15213f31804bc13c8c2e64a32c291c717d78e6e8 [formerly 63f77d4380410b6e2413a9c570bbed37b10d8c0b]]]]
Former-commit-id: 50946865b4839ab495e3d7eeae667effd0a1991f [formerly c06d50ffe792e8e96cb486f1d781dad11f852e24] [formerly 734221d47388824af5f469007d69430561df3197 [formerly bb96270571113f21df48a4e99799172b7a293348]]
Former-commit-id: e181819ceed304401af9c652d0cf9cfe42db062b [formerly 32abe05345d0658e29c3a363710a27ada4e7a676]
Former-commit-id: d401e5e62d8dd5f63413ce117327fa3ceb875c5b
Former-commit-id: f1a1ce961b916a96918448f1ff4800697ddc9b67

--- @client/swapables.coffee
+++ @client/swapables.coffee
@@ -45,12 +45,12 @@

 sorted_proposals = (cluster) ->
   options = customization("cluster_options['#{cluster.name}']") || {}
-  _.clone(cluster.proposals).sort((a,b) ->
-    x_a = proposal_support(a) - 10000 + (if options.editor_icons \
-                                         and proposal_editor(a) then 1 else 0)
-    x_b = proposal_support(b) - 10000 + (if options.editor_icons \
-                                         and proposal_editor(b) then 1 else 0)
-    return x_b - x_a)
+  _.clone(cluster.proposals).sort (a,b) ->
+    x_a = proposal_support(a) + (if options.editor_icons \
+                                  and proposal_editor(a) then 1 else 0)
+    x_b = proposal_support(b) + (if options.editor_icons \
+                                  and proposal_editor(b) then 1 else 0)
+    return x_b - x_a

 window.SimpleHomepage = ReactiveComponent
   displayName: 'SimpleHomepage'


Fix typo

Fix typo

--- src/assets/scripts/coffee/onload/smoothScroll.coffee
+++ src/assets/scripts/coffee/onload/smoothScroll.coffee
@@ -3,7 +3,7 @@
   options =
     totalTime: 500
     refreshRate: 15
-    disableClass: '.js--smoothScroll-off'
+    disableClass: '.js--smoothScroll--off'

   links = document.links
   if (!links.length) then return


improved _s

improved _s

--- src/coffee/helpers/project-credentials-config.coffee
+++ src/coffee/helpers/project-credentials-config.coffee
@@ -1,7 +1,8 @@
 Q = require 'q'
 fs = require 'q-io/fs'
 {_} = require 'underscore'
-_s = require 'underscore.string'
+
+_.mixin require('underscore.string')

 csv = require 'csv'

@@ -36,7 +37,7 @@
         if exists
           fs.read path, 'r'
           .then (contents) =>
-            if _s.endsWith(normalizedPath, ".json")
+            if _.endsWith(normalizedPath, ".json")
               @_readJsonConfig "#{contents}"
             else
               @_readCsvConfig "#{contents}"
@@ -83,7 +84,7 @@
         throw new Error("Can't find credentials for project '#{credentials.project_key}'.")

   _normalizePath: (path) ->
-    if not path? or _s.isBlank(path)
+    if not path? or _.isBlank(path)
       throw new Error("Path is empty!")

     path.replace "~", @_getUserHome()


trying to fix cache issues

trying to fix cache issues

--- server/startup/avatar.coffee
+++ server/startup/avatar.coffee
@@ -41,8 +41,9 @@
 		res.setHeader 'Content-Disposition', 'inline'

 		if not file?
-			res.setHeader 'content-type', 'image/svg+xml'
-			res.setHeader 'Cache-Control', 'public, max-age=31536000'
+			res.setHeader 'Content-Type', 'image/svg+xml'
+			res.setHeader 'Cache-Control', 'public, max-age=0'
+			res.setHeader 'Expires', '-1'
 			res.setHeader 'Last-Modified', "Thu, 01 Jan 2015 00:00:00 GMT"

 			reqModifiedHeader = req.headers["if-modified-since"];
@@ -92,8 +93,10 @@
 				res.end()
 				return

+		res.setHeader 'Cache-Control', 'public, max-age=0'
+		res.setHeader 'Expires', '-1'
 		res.setHeader 'Last-Modified', file.uploadDate?.toUTCString() or new Date().toUTCString()
-		res.setHeader 'content-type', 'image/jpeg'
+		res.setHeader 'Content-Type', 'image/jpeg'
 		res.setHeader 'Content-Length', file.length

 		file.readStream.pipe res


Remove test that is not used

Remove test that is not used

--- spec/atom/window-spec.coffee
+++ spec/atom/window-spec.coffee
@@ -80,17 +80,6 @@

         expect(atom.native.resetMainMenu).toHaveBeenCalled()

-      it "adds the menu item to the main menu with a key equivalent", ->
-        # This fucking test...
-        # handler = jasmine.createSpy('menuItemHandler')
-        # window.bindMenuItem 'Submenu > Item', 'meta-1', handler
-        # $(window).focus()
-
-        # keyEvent = OSX.NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode OSX.NSKeyDown, OSX.NSEvent.mouseLocation, OSX.NSCommandKeyMask, OSX.GetCurrentEventTime, $atomController.window.windowNumber, null, "1", "1", false, 18
-        # OSX.NSApp.sendEvent keyEvent
-
-        # expect(handler).toHaveBeenCalled()
-
       it "causes the given action to be invoked when the menu item is selected", ->
         handler = jasmine.createSpy('menuItemHandler')
         window.bindMenuItem 'Submenu > Item', null, handler


Added some more tests for differing page size parameter

--- spec/leaderboard_spec.coffee
+++ spec/leaderboard_spec.coffee
@@ -117,6 +117,12 @@
     for index in [0...Leaderboard.DEFAULT_PAGE_SIZE + 1]
       @leaderboard.rank_member("member#{index}", index, null, (reply) -> )

+    @leaderboard.total_pages(5, (reply) ->
+      reply.should.equal(6))
+
+    @leaderboard.total_pages(null, (reply) ->
+      reply.should.equal(2))
+
     @leaderboard.total_pages(Leaderboard.DEFAULT_PAGE_SIZE, (reply) ->
       reply.should.equal(2)
       done())


Add spec

Add spec

--- spec/text-buffer-spec.coffee
+++ spec/text-buffer-spec.coffee
@@ -238,6 +238,13 @@
         buffer.setTextViaDiff(newText)
         expect(buffer.getText()).toBe newText

+    describe "when the buffer contains carriage returns for newlines", ->
+      fit "can replace the contents of the buffer", ->
+        buffer = new TextBuffer("first\rsecond\rlast")
+        newText = "new first\rnew last"
+        buffer.setTextViaDiff(newText)
+        expect(buffer.getText()).toBe newText
+
   describe "::insert(position, text, normalizeNewlinesn)", ->
     it "inserts text at the given position", ->
       buffer = new TextBuffer("hello world")


remove coffeescript version

remove coffeescript version

--- vendor/assets/javascripts/geofsh.js.coffee
+++ vendor/assets/javascripts/geofsh.js.coffee
@@ -1,22 +0,0 @@
-geolocSuccess = (position) ->
-  geolat = position.coords.latitude
-  geolng = position.coords.longitude
-  $("input#latitude").val geolat
-  $("input#longitude").val geolng
-
-geolocError = ->
-  hideGeoLoc()
-  "Geolocation Unsuccessful"
-
-hideGeoLoc = ->
-  $(".geoloc-hide").parent().hide()
-
-getPosition = ->
-  if navigator.geolocation
-    navigator.geolocation.getCurrentPosition geolocSuccess, geolocError
-  else
-    hideGeoLoc()
-    "Geolocation not supported"
-
-$ ->
-  getPosition()


add Main.setTextObject

add Main.setTextObject

--- app/vis.coffee
+++ app/vis.coffee
@@ -127,6 +127,8 @@
         lineObject._layout = line_layout
         lineObject

+    # getLineObject = @getLineObject
+
     positionLines = (line, index, array) =>
         return line if index is 0

@@ -217,7 +219,7 @@
                     RADIUS * Math.sin(radians) + node.position.y
                 )
                 if circle_node.position?
-                    check = pos.y.toFixed(3) is circle_node.position.y.toFixed(3)
+                    # check = pos.y.toFixed(3) is circle_node.position.y.toFixed(3)
                     # assert(check)
                 else
                     circle_node.position = pos
@@ -262,6 +264,14 @@
         i = d3.interpolate(0, 0.5)
         getTransitionPromise(i)(text_object)

+    setTextObject: (node) =>
+        text_object = @getLineObject(node.val)
+        text_object.children.forEach (mesh) ->
+            mesh.material.uniforms.opacity.value = 0
+        text_object.position.copy(node.position)
+        node._text_object = text_object
+        node
+
     addNetwork: (network) =>
         network_object = new THREE.Object3D()
         network_object.scale.multiplyScalar(SCALE_TEXT)
@@ -273,11 +283,8 @@
         traverse = (node) =>
             return if ! node.position?

-            text_object = @getLineObject(node.val)
-            text_object.children.forEach (mesh) ->
-                mesh.material.uniforms.opacity.value = 0
-            text_object.position.copy(node.position)
-            node._text_object = text_object
+            node = @setTextObject(node)
+            text_object = node._text_object

             network_object.add text_object


added some dutch translations

added some dutch translations

--- app/locale/nl-NL.coffee
+++ app/locale/nl-NL.coffee
@@ -301,7 +301,7 @@
     tip_beta_launch: "CodeCombat lanceerde zijn beta versie in Oktober, 2013."
     tip_think_solution: "Denk aan de oplossing, niet aan het probleem."
     tip_theory_practice: "In theorie is er geen verschil tussen de theorie en de praktijk; in de praktijk is er wel een verschil. - Yogi Berra"
-    tip_error_free: "Er zijn twee manieren om fout-vrije code te schrijven, maar enkele de derde manier werkt. - Alan Perlis"
+    tip_error_free: "Er zijn twee manieren om fout-vrije code te schrijven, maar enkel de derde manier werkt. - Alan Perlis"
     tip_debugging_program: "Als debuggen het proces is om bugs te verwijderen, dan moet programmeren het proces zijn om ze erin te stoppen. - Edsger W. Dijkstra"
     tip_forums: "Ga naar de forums en vertel ons wat je denkt!"
     tip_baby_coders: "Zelfs babies zullen in de toekomst een Tovenaar zijn."
@@ -403,7 +403,7 @@
   subscribe:
 #    comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
     feature1: "meer dan 110+ basislevels over 4 werelden"
-#    feature2: "10 powerful new heroes with unique skills!"
+    feature2: "10 sterke nieuw helden met unieke vaardigheden!"
     feature3: "70+ bonuslevels"
 #    feature4: "3500 bonus gems every month!"
 #    feature5: "Video tutorials"
@@ -424,8 +424,8 @@
     parent_button: "Vraag het aan je ouder"
     parent_email_description: "We zullen ze een email sturen zodat zij een CodeCombat-abonnement voor je kunnen kopen."
     parent_email_input_invalid: "Emailaddress ongeldig."
-#    parent_email_input_label: "Parent email address"
-#    parent_email_input_placeholder: "Enter parent email"
+    parent_email_input_label: "Emailadres van ouders"
+    parent_email_input_placeholder: "Vul het emailadres van de ouders in"
     parent_email_send: "Stuur email"
     parent_email_sent: "Email verzonden!!"
     parent_email_title: "Wat is het emailadres van je ouders?"
@@ -493,10 +493,10 @@
     programming_language_description: "Welke programmeertaal wil je gebruiken?"
     default: "standaard"
     experimental: "Experimenteel"
-#    python_blurb: "Simple yet powerful, great for beginners and experts."
-#    javascript_blurb: "The language of the web. (Not the same as Java.)"
-#    coffeescript_blurb: "Nicer JavaScript syntax."
-#    clojure_blurb: "A modern Lisp."
+    python_blurb: "simpel maar sterk, geweldig voor beginners en experts."
+    javascript_blurb: "De taal van het web. (Niet hetzelfde als Java!)"
+    coffeescript_blurb: "JavaScript, met betere syntax."
+    clojure_blurb: "Lisp, maar modern."
 #    lua_blurb: "Game scripting language."
 #    io_blurb: "Simple but obscure."
     status: "Status"
@@ -508,7 +508,7 @@
 #    attack: "Damage" # Can also translate as "Attack"
     health: "Gezondheid"
     speed: "Snelheid"
-#    regeneration: "Regeneration"
+    regeneration: "Herstel"
 #    range: "Range" # As in "attack or visual range"
 #    blocks: "Blocks" # As in "this shield blocks this much damage"
 #    backstab: "Backstab" # As in "this dagger does this much backstab damage"
@@ -529,21 +529,21 @@
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
 #    read_only: "read-only"
     action: "Actie"
-#    spell: "Spell"
+    spell: "toverspreuk"
     action_name: "naam"
 #    action_cooldown: "Takes"
 #    action_specific_cooldown: "Cooldown"
 #    action_damage: "Damage"
 #    action_range: "Range"
-#    action_radius: "Radius"
+    action_radius: "Straal"
     action_duration: "Tijd"
     example: "Voorbeeld"
     ex: "bv" # Abbreviation of "example"
-#    current_value: "Current Value"
-#    default_value: "Default value"
-#    parameters: "Parameters"
+    current_value: "Huidige waarde"
+    default_value: "Standaard waarde"
+    parameters: "Parameters"
 #    returns: "Returns"
-#    granted_by: "Granted by"
+    granted_by: "Verleend door"

   save_load:
     granularity_saved_games: "Opgeslagen"
@@ -564,7 +564,7 @@
     editor_config_keybindings_default: "Standaard (Ace)"
     editor_config_keybindings_description: "Voeg extra shortcuts toe van de gebruikelijke editors."
 #    editor_config_livecompletion_label: "Live Autocompletion"
-#    editor_config_livecompletion_description: "Displays autocomplete suggestions while typing."
+    editor_config_livecompletion_description: "Toont suggesties tijdens het typen."
     editor_config_invisibles_label: "Toon onzichtbare"
     editor_config_invisibles_description: "Toon onzichtbare (spatie) karakters."
     editor_config_indentguides_label: "Toon inspringing regels"
@@ -659,13 +659,13 @@

   teachers_survey:
 #    title: "Teacher Survey"
-#    must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
-#    retrieving: "Retrieving information..."
-#    being_reviewed_1: "Your application for a free trial subscription is being"
-#    being_reviewed_2: "reviewed."
-#    approved_1: "Your application for a free trial subscription was"
-#    approved_2: "approved."
-#    approved_3: "Further instructions have been sent to"
+    must_be_logged: "Je moet eerst ingelogd zijn. Maak een account aan of log in in het menu hierboven."
+    retrieving: "Informatie ophalen..."
+    being_reviewed_1: "Je aanvraag voor een gratis proefabonnement wordt"
+    being_reviewed_2: "beoordeeld."
+    approved_1: "Je aanvraag voor een gratis proefabonnement is"
+    approved_2: "goedgekeurd."
+    approved_3: "Meer informatie is verzonden naar"
     denied_1: "Uw aanvraag voor een testaccount is"
     denied_2: "afgewezen."
     contact_1: "Neem contact op"
@@ -765,7 +765,7 @@
     press_enter: "toets enter"
     escape: "Escape"
     shift: "Shift"
-#    run_code: "Run current code."
+    run_code: "Voer huidige code uit."
 #    run_real_time: "Run in real time."
 #    continue_script: "Continue past current script."
 #    skip_scripts: "Skip past all skippable scripts."
@@ -792,7 +792,7 @@
 #    social_github: "Check out all our code on GitHub"
 #    social_blog: "Read the CodeCombat blog on Sett"
 #    social_discource: "Join the discussion on our Discourse forum"
-#    social_facebook: "Like CodeCombat on Facebook"
+#    social_facebook: "Like CodeCombat op Facebook"
 #    social_twitter: "Follow CodeCombat on Twitter"
 #    social_gplus: "Join CodeCombat on Google+"
 #    social_hipchat: "Chat with us in the public CodeCombat HipChat room"
@@ -843,89 +843,89 @@
 #    last_played: "Last played"
 #    leagues_explanation: "Play in a league against other clan members in these multiplayer arena instances."

-#  courses:
-#    course: "Course"
-#    courses: "courses"
-#    not_enrolled: "You are not enrolled in this course."
-#    visit_pref: "Please visit the"
-#    visit_suf: "page to enroll."
-#    select_class: "Select one of your classes"
-#    unnamed: "*unnamed*"
-#    select: "Select"
-#    unnamed_class: "Unnamed Class"
+  courses:
+    course: "Cursus"
+    courses: "cursussen"
+    not_enrolled: "Je bent niet ingeschreven voor deze cursus."
+    visit_pref: "Ga naar"
+    visit_suf: "de pagina om in te schrijven."
+    select_class: "Selecteer een vn je klassen"
+    unnamed: "Naamloos*"
+    select: "Selecteer"
+    unnamed_class: "Naamloze klas"
 #    edit_settings: "edit class settings"
 #    edit_settings1: "Edit Class Settings"
-#    progress: "Class Progress"
-#    add_students: "Add Students"
-#    stats: "Statistics"
-#    total_students: "Total students:"
-#    average_time: "Average level play time:"
-#    total_time: "Total play time:"
-#    average_levels: "Average levels completed:"
-#    total_levels: "Total levels completed:"
-#    furthest_level: "Furthest level completed:"
-#    concepts_covered: "Concepts Covered"
-#    students: "Students"
-#    students1: "students"
-#    expand_details: "Expand details"
-#    concepts: "Concepts"
-#    levels: "levels"
-#    played: "Played"
-#    play_time: "Play time:"
-#    completed: "Completed:"
-#    invite_students: "Invite students to join this class."
-#    enter_emails: "Enter student emails to invite, one per line"
-#    send_invites: "Send Invites"
-#    title: "Title"
-#    description: "Description"
-#    languages_available: "Select programming languages available to the class:"
-#    all_lang: "All Languages"
-#    show_progress: "Show student progress to everyone in the class"
-#    creating_class: "Creating class..."
-#    purchasing_course: "Purchasing course..."
-#    buy_course: "Buy Course"
-#    buy_course1: "Buy this course"
-#    create_class: "Create Class"
-#    select_all_courses: "Select 'All Courses' for a 50% discount!"
-#    all_courses: "All Courses"
-#    number_students: "Number of students"
-#    enter_number_students: "Enter the number of students you need for this class."
-#    name_class: "Name your class"
-#    displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
-#    buy: "Buy"
-#    purchasing_for: "You are purchasing a license for"
-#    creating_for: "You are creating a class for"
-#    for: "for" # Like in 'for 30 students'
+    progress: "klas-voortgang"
+    add_students: "Voeg leerlingen toe"
+    stats: "Statistieken"
+    total_students: "Totaal aantal leerlingen:"
+    average_time: "Gemiddelde speeltijd per level:"
+    total_time: "Totale speeltijd:"
+    average_levels: "Gemiddeld aantal levels voltooid:"
+    total_levels: "Totaal aantal levels voltooid:"
+    furthest_level: "Hoogste level behaald:"
+    concepts_covered: "Concepten behandeld"
+    students: "leerlingen"
+    students1: "leerlingen"
+    expand_details: "detailts uitbereiden"
+    concepts: "Concepten"
+    levels: "levels"
+    played: "Gespeeld"
+    play_time: "speeltijd:"
+    completed: "voltooid:"
+    invite_students: "Nodig leerlingen uit voor deze klas."
+    enter_emails: "Voer emailadres van leerlingen in om uit te nodigen, een per regel"
+    send_invites: "Verstuur uitnodigingen"
+    title: "Titel"
+    description: "beschrijving"
+    languages_available: "Selecteer beschikbare programmeertalen voor de klas:"
+    all_lang: "Alle programmeertalen"
+    show_progress: "Toon leerlingenvoortgang aan de hele klas"
+    creating_class: "Aanmaken klas..."
+    purchasing_course: "Aanschaffen cursus..."
+    buy_course: "Cursus kopen"
+    buy_course1: "Koop deze cursus"
+    create_class: "Maak klas aan"
+    select_all_courses: "Selecteer 'Alle cursussen' voor 50% korting!"
+    all_courses: "Alle cursussen"
+    number_students: "Aantal leerlingen"
+    enter_number_students: "Voer het aantal leerlingen in die je voor de klas nodig hebt."
+    name_class: "Geef je klas een naam"
+    displayed_course_page: "Dit wordt getoond op de cursuspagina aan u en uw leerlingen. Het kan later worden veranderd."
+    buy: "Kopen"
+    purchasing_for: "U koopt een licentie voor"
+    creating_for: "U maakt een klas voor"
+    for: "voor" # Like in 'for 30 students'
 #    receive_code: "Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class."
 #    free_trial: "Free trial for teachers!"
 #    get_access: "to get individual access to all courses for evalutaion purposes."
-#    questions: "Questions?"
-#    faq: "Courses FAQ"
-#    question: "Q:" # Like in 'Question'
-#    question1: "What's the difference between these courses and the single player game?"
-#    answer: "A:" # Like in 'Answer'
-#    answer1: "The single player game is designed for individuals, while the courses are designed for classes."
+    questions: "Vragdn?"
+    faq: "Cursus FAQ"
+    question: "Vraag:" # Like in 'Question'
+    question1: "Wat is het verschil tussen deze cursussen en de single player game?"
+    answer: "Antwoord:" # Like in 'Answer'
+    answer1: "De single player game is ontwikkeld voor individuelen, terwijl de cusrussen zijn ontwikkeld voor de klas."
 #    answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
-#    teachers_click: "Teachers Click Here"
-#    students_click: "Students Click Here"
-#    courses_on_coco: "Courses on CodeCombat"
-#    designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
-#    more_in_less: "Learn more in less time"
-#    no_experience: "No coding experience necesssary"
-#    easy_monitor: "Easily monitor student progress"
-#    purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!"
-#    see_the: "See the"
-#    more_info: "for more information."
-#    choose_course: "Choose Your Course:"
+    teachers_click: "Leerkrachten klik hier"
+    students_click: "Leerlingen klik hier"
+    courses_on_coco: "Cursussen van CodeCombat"
+    designed_to: "Cursussen zijn ontwikkeld om te introduceren in informaticaconcepten met CodeCombat's plezier en interessante omgevingen. CodeCombat levels zijn gesturctureerd rond belangrijke onderwerpen om voorstrevend leren te bemoedigen, gedurende 5 uur."
+    more_in_less: "Leer meer in minder tijd"
+    no_experience: "Geen programmeerervaring nodif"
+    easy_monitor: "leerlingenproces makkelijk te bekijken"
+    purchase_for_class: "Koop een cursus voor de hele klas. Het is makkelijk je leerlingen in te schrijven!"
+    see_the: "Bekijk de"
+    more_info: "voor meer informatie."
+    choose_course: "Kies je cursus:"
 #    enter_code: "Enter an unlock code"
 #    enter_code1: "Enter unlock code"
-#    enroll: "Enroll"
+    enroll: "Inschrijven"
 #    pick_from_classes: "Pick from your current classes"
 #    enter: "Enter"
-#    or: "Or"
-#    topics: "Topics"
+    or: "Of"
+    topics: "Onderwerpen"
 #    hours_content: "Hours of content:"
-#    get_free: "Get FREE course"
+    get_free: "Ontvang een GRATIS cursus"

   classes:
     archmage_title: "Tovenaar"
@@ -962,8 +962,8 @@
     indoor: "Binnen"
     desert: "Woestijn"
     grassy: "Grassig"
-#    mountain: "Mountain"
-#    glacier: "Glacier"
+    mountain: "Berg"
+    glacier: "Gletsjer"
     small: "Klein"
     large: "Groot"
     fork_title: "Kloon naar nieuwe versie"
@@ -973,7 +973,7 @@
     wiki: "Wiki"
     live_chat: "Live Chat"
 #    thang_main: "Main"
-#    thang_spritesheets: "Spritesheets"
+    thang_spritesheets: "Spritesheets"
     thang_colors: "Kleuren"
     level_some_options: "Enkele opties?"
     level_tab_thangs: "Elementen"
@@ -986,7 +986,7 @@
     level_tab_thangs_all: "Alles"
     level_tab_thangs_conditions: "Start Condities"
     level_tab_thangs_add: "Voeg element toe"
-#    level_tab_thangs_search: "Search thangs"
+    level_tab_thangs_search: "Zoek dingen"
     add_components: "Voeg Componenten Toe"
     component_configs: "Componenten Configuraties"
     config_thang: "Double click om een thang te configureren"
@@ -1033,14 +1033,14 @@
     tasks: "Taken"
 #    clear_storage: "Clear your local changes"
 #    add_system_title: "Add Systems to Level"
-#    done_adding: "Done Adding"
+    done_adding: "Klaar met toevoegen"

   article:
     edit_btn_preview: "Voorbeeld"
     edit_article_title: "Wijzig Artikel"

 #  polls:
-#    priority: "Priority"
+    priority: "Prioriteit"

   contribute:
     page_title: "Bijdragen"
@@ -1089,9 +1089,9 @@
     diplomat_launch_url: "release in oktober"
     diplomat_introduction_suf: "dan is het wel dat er een enorme belangstelling is voor CodeCombat in andere landen, vooral Brazilië! We zijn een groep van vertalers aan het creëren dat ijverig de ene set woorden in de andere omzet om CodeCombat zo toegankelijk mogelijk te maken in de hele wereld. Als jij het leuk vindt glimpsen op te vangen van aankomende content en deze levels zo snel mogelijk naar je landgenoten te krijgen, dan is dit de klasse voor jou."
     diplomat_attribute_1: "Vloeiend Engels en de taal waar naar je wilt vertalen kunnen spreken. Wanneer je moeilijke ideeën wilt overbrengen, is het belangrijk beide talen goed te begrijpen!"
-#    diplomat_i18n_page_prefix: "You can start translating our levels by going to our"
+    diplomat_i18n_page_prefix: "Je kent beginnen met vertalen door te serven naar"
     diplomat_i18n_page: "vertalingen pagina"
-#    diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
+    diplomat_i18n_page_suffix: ", of onze interface en website op GitHub."
     diplomat_join_pref_github: "Vind van jouw taal het locale bestand "
     diplomat_github_url: "op GitHub"
     diplomat_join_suf_github: ", edit het online, en submit een pull request. Daarnaast kun je hieronder aanvinken als je up-to-date wilt worden gehouden met nieuwe internationalisatie-ontwikkelingen."
@@ -1135,7 +1135,7 @@
     rank_submitted: "Verzonden voor Beoordeling"
     rank_failed: "Beoordeling mislukt"
     rank_being_ranked: "Spel wordt Beoordeeld"
-#    rank_last_submitted: "submitted "
+    rank_last_submitted: "Verzonden "
     help_simulate: "Helpen spellen te simuleren?"
     code_being_simulated: "Uw nieuwe code wordt gesimuleerd door andere spelers om te beoordelen. Dit wordt vernieuwd zodra nieuwe matches binnenkomen."
     no_ranked_matches_pre: "Geen beoordeelde wedstrijden voor het"
@@ -1156,7 +1156,7 @@
     fight: "Aanvallen!"
     watch_victory: "Aanschouw je overwinning!"
     defeat_the: "Versla de"
-#    tournament_started: ", started"
+    tournament_started: ", begonnen"
     tournament_ends: "Toernooi eindigt"
     tournament_ended: "Toernooi geeindigd"
     tournament_rules: "Toernooi regels"
@@ -1168,11 +1168,11 @@
     rules: "Regels"
     winners: "Winnaars"
 #    league: "League"
-#    red_ai: "Red AI" # "Red AI Wins", at end of multiplayer match playback
-#    blue_ai: "Blue AI"
-#    wins: "Wins" # At end of multiplayer match playback
-#    humans: "Red" # Ladder page display team name
-#    ogres: "Blue"
+    red_ai: "KI rood" # "Red AI Wins", at end of multiplayer match playback
+    blue_ai: "KI blauw"
+    wins: "Wint" # At end of multiplayer match playback
+    humans: "Rood" # Ladder page display team name
+    ogres: "Blauw"

   user:
     stats: "Statistieken"
@@ -1180,7 +1180,7 @@
 #    multiplayer_title: "Multiplayer Levels"
     achievements_title: "Prestaties"
     last_played: "Laatst Gespeeld"
-#    status: "Status"
+    status: "Status"
     status_completed: "Voltooid"
     status_unfinished: "Onvoltooid"
     no_singleplayer: "Nog geen Singleplayer spellen gespeeld."
@@ -1196,60 +1196,60 @@
     achievement: "Prestatie"
 #    category_contributor: "Contributor"
 #    category_ladder: "Ladder"
-#    category_level: "Level"
+    category_level: "Level"
     category_miscellaneous: "Diversen"
-#    category_levels: "Levels"
+    category_levels: "Levels"
     category_undefined: "Geen categorie"
     current_xp_prefix: ""
-#    current_xp_postfix: " in total"
+    current_xp_postfix: " in totaal"
     new_xp_prefix: ""
     new_xp_postfix: " verdiend"
     left_xp_prefix: ""
     left_xp_infix: " tot level "
     left_xp_postfix: ""

-#  account:
-#    recently_played: "Recently Played"
-#    no_recent_games: "No games played during the past two weeks."
-#    payments: "Payments"
-#    prepaid: "Prepaid"
-#    purchased: "Purchased"
-#    sale: "Sale"
-#    subscription: "Subscription"
-#    invoices: "Invoices"
-#    service_apple: "Apple"
-#    service_web: "Web"
-#    paid_on: "Paid On"
-#    service: "Service"
-#    price: "Price"
+  account:
+    recently_played: "Onlangs gespeeld"
+    no_recent_games: "De afgelopen 2 weken geen spellen gespeeld."
+    payments: "Betalingen"
+    prepaid: "Prepaid"
+    purchased: "Gekocht"
+    sale: "Verkoop"
+    subscription: "abonnement"
+    invoices: "Facturen"
+    service_apple: "Apple"
+    service_web: "Web"
+    paid_on: "Betaald op"
+    service: "Service"
+    price: "Prijs"
 #    gems: "Gems"
-#    active: "Active"
-#    subscribed: "Subscribed"
-#    unsubscribed: "Unsubscribed"
-#    active_until: "Active Until"
-#    cost: "Cost"
-#    next_payment: "Next Payment"
-#    card: "Card"
-#    status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
+    active: "Actief"
+    subscribed: "Ingeschreven"
+    unsubscribed: "Uitgeschreven"
+    active_until: "Actief tot"
+    cost: "Kosten"
+    next_payment: "Volgende betaling"
+    card: "Kaart"
+    status_unsubscribed_active: "Je bent niet ingeschreven en krijgt geen factuur, maar je account is nu nog actief."
 #    status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"

-#  account_invoices:
-#    amount: "Amount in US dollars"
-#    declined: "Your card was declined"
-#    invalid_amount: "Please enter a US dollar amount."
-#    not_logged_in: "Log in or create an account to access invoices."
-#    pay: "Pay Invoice"
-#    purchasing: "Purchasing..."
-#    retrying: "Server error, retrying."
-#    success: "Successfully paid. Thanks!"
-
-#  account_prepaid:
-#    purchase_code: "Purchase a Subscription Code"
-#    purchase_amount: "Amount"
-#    purchase_total: "Total"
-#    purchase_button: "Submit Purchase"
-#    your_codes: "Your Codes:"
-#    redeem_codes: "Redeem a Subscription Code"
+  account_invoices:
+    amount: "Aantal in Amerikaanse dollars"
+    declined: "Je kaart is geweigerd"
+    invalid_amount: "Voer alstublieft het aantal US dollar in."
+    not_logged_in: "Log in of maak een account om bij facturen te komen."
+    pay: "Betaal factuur"
+    purchasing: "Aanschaffen..."
+    retrying: "Server error, probeer opnieuw."
+    success: "Betaling succesvol. Dank u wel!"
+
+  account_prepaid:
+    purchase_code: "koop een prepaidcode"
+    purchase_amount: "Aantal"
+    purchase_total: "Totaal"
+    purchase_button: "Verzend betaling"
+    your_codes: "Je codes:"
+    redeem_codes: "Prepaidcode inwisselen"

   loading_error:
     could_not_load: "Fout bij het laden van de server"
@@ -1263,7 +1263,7 @@
     bad_input: "Slechte input."
     server_error: "Fout van de server."
     unknown: "Onbekende fout."
-#    error: "ERROR"
+    error: "ERROR"

   resources:
     sessions: "Sessies"
@@ -1297,49 +1297,49 @@
     top_simulators: "Top Simulatoren"
 #    source_document: "Source Document"
     document: "Document"
-#    sprite_sheet: "Sprite Sheet"
+    sprite_sheet: "Spritesheet"
     employers: "Werkgevers"
     candidates: "Candidaten"
-#    candidate_sessions: "Candidate Sessions"
+    candidate_sessions: "Candidaatsessies"
     user_remark: "Gebruiker Opmerking"
     user_remarks: "Gebruiken Opmerkingen"
     versions: "Versies"
     items: "Items"
-#    hero: "Hero"
+    hero: "Held"
     heroes: "Helden"
     achievement: "Prestatie"
-#    clas: "CLAs"
+    clas: "klas"
 #    play_counts: "Play Counts"
-#    feedback: "Feedback"
+    feedback: "Feedback"
     payment_info: "Betalings Informatie"
-#    campaigns: "Campaigns"
-#    poll: "Poll"
-#    user_polls_record: "Poll Voting History"
-
-#  concepts:
-#    advanced_strings: "Advanced Strings"
-#    algorithms: "Algorithms"
-#    arguments: "Arguments"
-#    arithmetic: "Arithmetic"
-#    arrays: "Arrays"
-#    basic_syntax: "Basic Syntax"
-#    boolean_logic: "Boolean Logic"
-#    break_statements: "Break Statements"
-#    classes: "Classes"
-#    continue_statements: "Continue Statements"
-#    for_loops: "For Loops"
-#    functions: "Functions"
-#    graphics: "Graphics"
-#    if_statements: "If Statements"
+    campaigns: "Campagnes"
+    poll: "Stemming"
+    user_polls_record: "Stemgeschiedenis"
+
+  concepts:
+    advanced_strings: "geavanceerde Strings"
+    algorithms: "Algoritmes"
+    arguments: "Argumenten"
+    arithmetic: "Rekenkunde"
+    arrays: "Arrays"
+    basic_syntax: "Basis Syntaxis"
+    boolean_logic: "Booleaanse Logica"
+    break_statements: "Break Statements"
+    classes: "Klassen"
+    continue_statements: "Continue Statements"
+    for_loops: "For Loops"
+    functions: "Functies"
+    graphics: "Graphics"
+    if_statements: "If Statements"
 #    input_handling: "Input Handling"
-#    math_operations: "Math Operations"
+    math_operations: "Wiskundige operaties"
 #    object_literals: "Object Literals"
-#    parameters: "Parameters"
-#    strings: "Strings"
-#    variables: "Variables"
-#    vectors: "Vectors"
+    parameters: "Parameters"
+    strings: "Strings"
+    variables: "Variabelen"
+    vectors: "Vectors"
 #    while_loops: "Loops"
-#    recursion: "Recursion"
+    recursion: "Recursie"

   delta:
     added: "Toegevoegd"
@@ -1351,8 +1351,8 @@
 #    merge_conflict_with: "MERGE CONFLICT WITH"
     no_changes: "Geen veranderingen"

-#  guide:
-#    temp: "Temp"
+  guide:
+    temp: "Temp"

 #  temp:
 #    ace_of_coders_tournament: "New: play in the Ace of Coders tournament now!"
@@ -1360,12 +1360,12 @@
   multiplayer:
     multiplayer_title: "Multiplayer Instellingen" # We'll be changing this around significantly soon. Until then, it's not important to translate.
     multiplayer_toggle: "multiplayer inschakelen"
-#    multiplayer_toggle_description: "Allow others to join your game."
+    multiplayer_toggle_description: "Sta anderen toe mee te doen met je spel."
     multiplayer_link_description: "Geef deze url aan iemand om hem/haar te laten meedoen met jou."
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
     multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
-#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
+    multiplayer_sign_in_leaderboard: "Log in of maak een account om je score te verzenden."

   legal:
     page_title: "Legaal"
@@ -1548,9 +1548,9 @@
 #    player_code: "Player Code"

   employers:
-#    deprecation_warning_title: "Sorry, CodeCombat is not recruiting right now."
-#    deprecation_warning: "We are focusing on beginner levels instead of finding expert developers for the time being."
-#    hire_developers_not_credentials: "Hire developers, not credentials." # We are not actively recruiting right now, so there's no need to add new translations for the rest of this section.
+    deprecation_warning_title: "Sorry, CodeCombat is werft nu geen personeel."
+    deprecation_warning: "We focussen ons op dit moment op beginnerslevels in plaats van het zoeken naar ervarem ontwikkelaars."
+    hire_developers_not_credentials: "Hire developers, not credentials." # We are not actively recruiting right now, so there's no need to add new translations for the rest of this section.
 #    get_started: "Get Started"
 #    already_screened: "We've already technically screened all our candidates"
 #    filter_further: ", but you can also filter further:"


Fixing localization issues. (#4378)

Fixing localization issues. (#4378)

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

* Update pt-BR.coffee

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -452,7 +452,7 @@
     failing: "Falta"
     reload: "Recarregar"
     reload_title: "Recarregar Todo o Código?"
-    reload_really: "Você tem certeza que quer reiniciar o estágio?"
+    reload_really: "Você tem certeza que quer reiniciar a fase?"
     reload_confirm: "Recarregar Tudo"
     test_level: "Nível de Tese"
     victory: "Vitória"
@@ -869,81 +869,81 @@
     mission_description_1: "Programação é mágica. É a habilidade de criar coisas apartir da pura imaginação. Criamos o CodeCombat para dar aos alunos a sensação de poder de um mago na ponta de seus dedos enquanto digita códigos."
     mission_description_2: "Como resultado, isso os permite aprender mais rápido também. MUITO mais rápido. É como conversar ao invés de ler um manual. Nós queremos trazer essa conversa para todas escolas e para todos estudantes, porque todos deveriam ter a chance de aprender a mágica da programação."
     team_title: "Conheça o time do CodeCombat"
-#    team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our GitHub contributors, because we value growth and learning in our team."
-    nick_title: "Cofundador" # {change}
+    team_values: "Valorizamos o diálogo aberto e respeitoso, onde a melhor ideia ganha. Nossas decisões são fundamentadas na pesquisa de clientes e nosso processo está focado na entrega de resultados tangíveis para eles. Todos põem a mão na massa, do nosso CEO aos nossos colaboradores no GitHub, porque valorizamos o crescimento e a aprendizagem em nossa equipe."
+    nick_title: "Cofundador, CEO" # {change}
     nick_blurb: "Guru Motivacional"
-    matt_title: "Cofundador" # {change}
-    cat_title: "Chefe Artesão" # {change}
-    cat_blurb: "Corta-vento"
-    scott_title: "Cofundador" # {change}
+    matt_title: "Cofundador, CTO" # {change}
+    cat_title: "Designer de Jogos" # {change}
+    cat_blurb: "Airbender"
+    scott_title: "Cofundado, Engenheiro de Software" # {change}
     scott_blurb: "O Sensato"
-#    maka_title: "Customer Advocate"
-#    maka_blurb: "Storyteller"
-    rob_title: "Engenheiro Compilador" # {change}
-    rob_blurb: "Os paranauês dos códigos"
-    josh_c_title: "Game Designer"
-    josh_c_blurb: "Desenha jogos"
-    robin_title: "UX Design & Pesquisa" # {change}
-#    robin_blurb: "Ships things"
-    josh_title: "Game Designer"
-    josh_blurb: "O chão vai tremer"
-#    phoenix_title: "Software Engineer"
-#    nolan_title: "Territory Manager"
-#    elliot_title: "Partnership Manager"
-#    elliot_blurb: "Mindreader"
-#    lisa_title: "School Specialist"
-#    lisa_blurb: "A gritty one"
-#    sean_title: "Territory Manager"
-#    liz_title: "Territory Manager"
+    maka_title: "Defensor dos Clientes"
+    maka_blurb: "Contador de Histórias"
+    rob_title: "Engenheiro de Software" # {change}
+    rob_blurb: "Programa muitas coisas"
+    josh_c_title: "Designer de Jogos"
+    josh_c_blurb: "Projeta Jogos"
+    robin_title: "Gerente de Produto" # {change}
+    robin_blurb: "Envia as coisas"
+    josh_title: "Designer de Jogos"
+    josh_blurb: "O chão é larva"
+    phoenix_title: "Engenheira de Software"
+    nolan_title: "Gerente de Território"
+    elliot_title: "Gerente de Parceria"
+    elliot_blurb: "Leitor de Mentes"
+    lisa_title: "Pedagoga"
+    lisa_blurb: "A Decidida"
+    sean_title: "Gerente de Território"
+    liz_title: "Gerente de Território"
     retrostyle_title: "Ilustração"
     retrostyle_blurb: "Games estilo Retrô"
     jose_title: "Músico"
-    jose_blurb: "Sou descolado"
-#    community_title: "...and our open-source community"
-#    community_subtitle: "Over 500 contributors have helped build CodeCombat, with more joining every week!"
-#    community_description_3: "CodeCombat is a"
-#    community_description_link_2: "community project"
-#    community_description_1: "with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our"
-#    community_description_link: "contribute page"
-#    community_description_2: "for more info."
-#    number_contributors: "Over 450 contributors have lent their support and time to this project."
-#    story_title: "Our story so far"
-#    story_subtitle: "Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game."
+    jose_blurb: "Descolado"
+    community_title: "... e nossa comunidade open-source"
+    community_subtitle: "Mais de 500 colaboradores ajudaram a construir o CodeCombat, com mais se juntando toda semana!"
+    community_description_3: "O CodeCombat é um "
+    community_description_link_2: "projeto da comunidade"
+    community_description_1: ", com centenas de jogadores que se voluntariam para criar níveis, contribuem para o nosso código para adicionar recursos, corrigir bugs, testam o jogo e até mesmo traduzir o jogo em até 50 idiomas até agora. Funcionários, colaboradores e o site ganham compartilhando ideias e compartilhando esforços, assim como a comunidade open source em geral. O site é construído em vários projetos open source, e nós somos open source para dar a comunidade e fornecer aos jogadores curiosos um projeto familiar para explorar e experimentar. Qualquer um pode se juntar à comunidade CodeCombat! Confira nossa "
+    community_description_link: "página de contribuições"
+    community_description_2: "para mais informação."
+    number_contributors: "Over 450 contributors have lent their support and time to this project. = Mais de 450 colaboradores deram o seu apoio e tempo a este projeto."
+    story_title: "Nosso história até agora"
+    story_subtitle: "Desde 2013, o CodeCombat cresceu de um mero conjunto de esboços para um jogo vivo e próspero."
 #    story_statistic_1a: "5,000,000+"
-#    story_statistic_1b: "total players"
-#    story_statistic_1c: "have started their programming journey through CodeCombat"
-#    story_statistic_2a: "We’ve been translated into over 50 languages — our players hail from"
-#    story_statistic_2b: "200+ countries"
-#    story_statistic_3a: "Together, they have written"
-#    story_statistic_3b: "1 billion lines of code and counting"
-#    story_statistic_3c: "across many different programming languages"
-#    story_long_way_1: "Though we've come a long way..."
-#    story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
-#    story_long_way_2: "we still have much to do before we complete our quest, so..."
-#    jobs_title: "Come work with us and help write CodeCombat history!"
-#    jobs_subtitle: "Don't see a good fit but interested in keeping in touch? See our \"Create Your Own\" listing."
-#    jobs_benefits: "Employee Benefits"
-#    jobs_benefit_4: "Unlimited vacation"
-#    jobs_benefit_5: "Professional development and continuing education support – free books and games!"
-#    jobs_benefit_6: "Medical (gold), dental, vision, commuter"
-#    jobs_benefit_7: "Sit-stand desks for all"
-#    jobs_benefit_9: "10-year option exercise window"
-#    jobs_benefit_10: "Maternity leave: 10 weeks paid, next 6 @ 55% salary"
-#    jobs_benefit_11: "Paternity leave: 10 weeks paid"
+    story_statistic_1b: "jogadores no total"
+    story_statistic_1c: "Começaram sua jornada de programação através do CodeCombat"
+    story_statistic_2a: "Nós fomos traduzidos para mais de 50 idiomas - nossos jogadores são provenientes de"
+    story_statistic_2b: "200+ países"
+    story_statistic_3a: "Juntos, eles escreveram"
+    story_statistic_3b: "1 bilhão de linhas de código e contando"
+    story_statistic_3c: "através de muitas linguagens de programação diferentes"
+    story_long_way_1: "Embora tenhamos percorremos um longo caminho..."
+    story_sketch_caption: "O primeiro esboço do Nick, que descreve um jogo de programação em ação."
+    story_long_way_2: "ainda temos muito o que fazer antes de completar a nossa missão, então ..."
+    jobs_title: "Venha trabalhar conosco e ajudar a escrever a história do CodeCombat!"
+    jobs_subtitle: "Não se encaixa em nenhum, mas está interessado em manter contato? Veja a listagem “Crie o seu”."
+    jobs_benefits: "Benefícios do empregado"
+    jobs_benefit_4: "Férias ilimitadas"
+    jobs_benefit_5: "Desenvolvimento profissional e apoio à educação continuada - livros e jogos gratuitos!"
+    jobs_benefit_6: "Apoio médico"
+    jobs_benefit_7: "Mesa para todos"
+    jobs_benefit_9: "Opção de 10 anos de exercício"
+    jobs_benefit_10: "Licença maternidade: 10 semanas pagas, próximas 6 semanas 55% do salário"
+    jobs_benefit_11: "Licença paternidade: 10 semanas pagas"
     learn_more: "Aprenda Mais"
-    jobs_custom_title: "Crie seu próprio"
-#    jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
-    jobs_custom_contact_1: "Envie-nos uma nota em"
-#    jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"
+    jobs_custom_title: "Crie o seu"
+    jobs_custom_description: "Você é apaixonado pelo CodeCombat, mas não vê um emprego listado que corresponda às suas qualificações? Escreva-nos e mostre como você acha que pode contribuir com a nossa equipe. Gostaríamos de ouvir de você!"
+    jobs_custom_contact_1: "Envie-nos um recado em"
+    jobs_custom_contact_2: "se apresentando e podemos entrar em contato no futuro!"
 #    contact_title: "Press & Contact"
     contact_subtitle: "Precisa de mais informações? Entre em contato conosco em"
-    screenshots_title: "Game Screenshots"
+    screenshots_title: "Screenshots do Jogo"
     screenshots_hint: "(clique para ver em tamanho grande)"
-#    downloads_title: "Download Assets & Information"
-    about_codecombat: "Sobre CodeCombat"
+    downloads_title: "Download de Espólio e Informações"
+    about_codecombat: "Sobre o CodeCombat"
 #    logo: "Logo"
     screenshots: "Screenshots"
-#    character_art: "Character Art"
+    character_art: "Arte do personagem"
     download_all: "Baixar Tudo"
     previous: "Anterior"
 #    location_title: "We're located in downtown SF:"
@@ -996,15 +996,15 @@
     subtitle: "Comece a ensinar hoje! Você poderá criar uma turma, adicionar alunos e monitorar o progresso à medida que eles aprendem ciência da computação."
     email_exists: "Existe um usuário com este email."
     phone_number: "Número de telefone"
-    phone_number_help: "Onde podemos contactá-lo durante o dia de trabalho?"
+    phone_number_help: "Onde podemos contatá-lo durante o dia de trabalho?"
     primary_role_label: "Função Principal"
 #    role_default: "Select Role"
     primary_role_default: "Selecione Função Principal"
     purchaser_role_default: "Selecione Função de Comprador"
     tech_coordinator: "Coordenador de Tecnologia"
     advisor: "Orientador" # {change}
-    principal: "Principal"
-    superintendent: "Superintendente"
+    principal: "Diretor"
+    superintendent: "Inspetor"
     parent: "Pais"
     purchaser_role_label: "Função de Comprador"
     influence_advocate: "Influência/Advogado"
@@ -1019,13 +1019,13 @@
     city: "Cidade"
     state: "Estado"
     country: "País"
-    num_students_help: "Quantos alunos usarão CodeCombat?"
+    num_students_help: "Quantos alunos usarão o CodeCombat?"
     num_students_default: "Selecione Intervalo"
     education_level_label: "Nível de educação dos alunos"
     education_level_help: "Escolha os que se aplicam."
     elementary_school: "Ensino Fundamental 1"
     high_school: "Ensino Médio"
-    please_explain: "Explique"
+    please_explain: "(Explique)"
     middle_school: "Ensino Fundamental 2"
     college_plus: "Ensino Superior"
 #    referrer: "How did you hear about us?"


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -69,7 +69,7 @@
     change_hero: "Alterar Herói" # Go back from choose inventory to choose hero
     choose_inventory: "Equipar Itens"
     buy_gems: "Comprar Gemas"
-#    campaign_desert: "Desert Campaign"
+    campaign_desert: "Campanha do Deserto"
     campaign_forest: "Campanha da Floresta"
     campaign_dungeon: "Campanha da Masmorra"
     subscription_required: "Subscrição Necessária"
@@ -373,7 +373,7 @@
     regeneration: "Regeneração"
     range: "Alcance" # As in "attack or visual range"
     blocks: "Bloqueia" # As in "this shield blocks this much damage"
-#    backstab: "Backstab" # As in "this dagger does this much backstab damage"
+    backstab: "Colateral" # As in "this dagger does this much backstab damage"
     skills: "Habilidades"
     available_for_purchase: "Disponível para Aquirir" # Shows up when you have unlocked, but not purchased, a hero in the hero store
     level_to_unlock: "Nível para desbloquear:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
@@ -811,21 +811,21 @@
     recently_played: "Jogados Recentemente"
     no_recent_games: "Sem jogos jogados nas passadas duas semanas."
     payments: "Pagamentos"
-#    purchased: "Purchased"
-#    subscription: "Subscription"
+    purchased: "Adquirido"
+    subscription: "Subscrição"
     service_apple: "Apple"
     service_web: "Web"
     paid_on: "Pago Em"
     service: "Serviço"
     price: "Preço"
     gems: "Gemas"
-#    active: "Active"
-#    subscribed: "Subscribed"
-#    unsubscribed: "Unsubscribed"
-#    active_until: "Active Until"
-#    cost: "Cost"
-#    next_payment: "Next Payment"
-#    card: "Card"
+    active: "Activa"
+    subscribed: "Subscrito"
+    unsubscribed: "Não Subscrito"
+    active_until: "Ativa Até"
+    cost: "Custo"
+    next_payment: "Próximo Pagamento"
+    card: "Cartão"
     status_unsubscribed_active: "Não estás subscrito e não te vamos cobrar, mas a tua conta ainda está ativa, por agora."
     status_unsubscribed: "Ganha acesso a novos níveis, heróis, itens e gemas de bónus com uma subscrição do CodeCombat!"

@@ -886,7 +886,7 @@
     clas: "CLAs"
     play_counts: "Número de Jogos"
     feedback: "Feedback"
-#    payment_info: "Payment Info"
+    payment_info: "Informações de Pagamento"

   delta:
     added: "Adicionados/as"
@@ -923,7 +923,7 @@
     privacy_title: "Privacidade"
     privacy_description: "Nós não vamos vender nenhuma das tuas informações pessoais."
     security_title: "Segurança"
-#    security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
+    security_description: "Nós lutamos para manter as tuas informações pessoais seguras. Sendo um projeto open source, o nosso sítio tem o código disponível, pelo que qualquer pessoa pode rever e melhorar os nossos sistemas de segurança."
     email_title: "E-mail"
     email_description_prefix: "Nós não te inundaremos com spam. Através das"
     email_settings_url: "tuas definições de e-mail"


change to .peekRecords and .isEvery

change to .peekRecords and .isEvery

--- app/controllers/build.coffee
+++ app/controllers/build.coffee
@@ -14,7 +14,7 @@

   jobsLoaded: (->
     if jobs = @get('build.jobs')
-      jobs.everyBy('config')
+      jobs.isEvery('config')
   ).property('build.jobs.@each.config')

   loading: (->


Minor fix.

Minor fix.

--- src/MappedCollection2.coffee
+++ src/MappedCollection2.coffee
@@ -213,7 +213,7 @@
     # Extracts the rule definition.
     if $_.isFunction definition
       ctx = {
-        rule: name
+        name: name
         key: undefined
         val: undefined
         singleton


Patch out dynamic announcement loader issues

--- app/core/ModuleLoader.coffee
+++ app/core/ModuleLoader.coffee
@@ -151,6 +151,8 @@
         dep = @expand(rootFolder, dep)
         continue if dep is 'memwatch'
         continue if _.string.startsWith(dep, 'ace/')
+        #https://staging.codecombat.com/f6c93e8617aaec9d337682513af46eb1094caf8d/javascripts/app/templates/play/modal/announcements.js
+        continue if _.string.startsWith(dep, 'templates/play/modal/announcements')
         dependencies.push(dep)
         console.debug dep if LOG
     console.groupEnd() if LOG


social: minor fix due to change from group() to aggregate()

--- workers/social/lib/social/models/activeitems.coffee
+++ workers/social/lib/social/models/activeitems.coffee
@@ -52,7 +52,7 @@
       instances = []
       daisy queue = items.map (item) ->
         ->
-          klass.one _id: item.sourceId, (err, instance)->
+          klass.one _id: item._id, (err, instance)->
             instances.push instance
             queue.next()


fixed sort when fetching invitations to be bulk approved (regression from recent refactorings)

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -950,7 +950,7 @@
   sendSomeInvitations: permit 'send invitations',
     success: (client, count, options, callback)->
       selector   = group: @slug, status: 'pending'
-      selOptions = limit: count, sort: requestedAt: -1
+      selOptions = limit: count, sort: requestedAt: 1

       JInvitationRequest = require '../invitationrequest'
       JInvitationRequest.some selector, selOptions, (err, requests)->


check uniqueness of group member relationships

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1,5 +1,5 @@
 {Module} = require 'jraphical'
-_ = require 'underscore'
+{difference} = require 'underscore'

 module.exports = class JGroup extends Module

@@ -890,12 +890,13 @@
       if delegate.getId().equals accountId
         return callback new KodingError 'You cannot transfer ownership to yourself, concentrate and try again!'

-      # double check that client is really the owner
-      @fetchMyRoles client, (err, roles)=>
+      Relationship.one {
+        targetId: delegate.getId(),
+        sourceId: @getId(),
+        as      : 'owner'
+      }, (err, owner)=>
         return callback err if err
-
-        if not 'owner' in roles
-          return callback new KodingError 'You must be owner to perform this action!'
+        return callback new KodingError 'You must be the owner to perform this action!' if not owner

         JAccount.one _id:accountId, (err, account)=>
           return callback err if err
@@ -909,11 +910,56 @@
               callback err

             # give rights to new owner
-            queue = _.difference(['member', 'admin', 'owner'], newOwnersRoles).map (role)=>=>
+            queue = difference(['member', 'admin'], newOwnersRoles).map (role)=>=>
               @addMember account, role, (err)->
                 return kallback err if err
                 queue.fin()

             dash queue, =>
-              # remove ownership of previous owner
-              @removeOwner delegate, kallback
+              # transfer ownership
+              owner.update $set: targetId: accountId, kallback
+
+  ensureUniquenessOfRoleRelationship:(target, options, fallbackRole, roleUnique, callback)->
+    unless callback
+      callback   = roleUnique
+      roleUnique = no
+
+    if 'string' is typeof options
+      as = options
+    else if options and options.as
+      {as} = options
+    else
+      as = fallbackRole
+
+    selector =
+      targetName : target.bongo_.constructorName
+      sourceId   : @getId()
+      sourceName : @bongo_.constructorName
+      as         : as
+
+    if not roleUnique
+      selector['targetId'] = target.getId()
+
+    Relationship.count selector, (err, count)->
+      if err then callback err
+      else if count is 0 then callback null
+      else callback new KodingError 'This relationship already exists'
+
+  oldAddMember = @::addMember
+  addMember:(target, options, callback)->
+    @ensureUniquenessOfRoleRelationship target, options, 'member', (err)=>
+      if err then callback err
+      else oldAddMember.call @, target, options, callback
+
+  oldAddAdmin = @::addAdmin
+  addAdmin:(target, options, callback)->
+    @ensureUniquenessOfRoleRelationship target, options, 'admin', (err)=>
+      if err then callback err
+      else oldAddAdmin.call @, target, options, callback
+
+  oldAddOwner = @::addOwner
+  addOwner:(target, options, callback)->
+    @ensureUniquenessOfRoleRelationship target, options, 'owner', yes, (err)=>
+      if err then callback err
+      else oldAddOwner.call @, target, options, callback
+


Asset store delete name

Asset store delete name

--- app/assets/javascripts/ems/active_admin/components/jquery.aa.asset-store.js.coffee
+++ app/assets/javascripts/ems/active_admin/components/jquery.aa.asset-store.js.coffee
@@ -5,7 +5,7 @@

     @options = $.extend( {}, defaults, options );
     @el = $(el)
-    @items = @el.find('.asset-item')
+    @items = @el.find('.asset-item')

     @_init()

@@ -28,7 +28,7 @@
       if not hid.length then return false
       console?.log hid.attr('name')
       name = hid.attr('name').split('[')
-      name.splice(-2,1)
+      name.splice(-1,1)
       name[name.length] = suffix + "]"
       $el.data 'assetstore-delete', hid.attr( {'name': name.join('[') } )
       @._bindCheck( $el )


Refactor

Refactor

--- app/assets/javascripts/rglossa/react/components/centre/main_area_bottom.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/main_area_bottom.jsx.coffee
@@ -9,45 +9,44 @@
     statechart: React.PropTypes.object.isRequired
     corpus: React.PropTypes.object.isRequired

-  rowWithSidebar: (results, currentResultPageNo) ->
+
+  mainComponent: ->
+    if @props.statechart.pathContains('start')
+      `<StartMain
+          store={this.props.store}
+          statechart={this.props.statechart}
+          corpus={this.props.corpus} />`
+    else
+      searchId = @props.statechart.getArgumentValue('searchId')
+      results = if searchId then @props.store.find('search', searchId) else null
+      currentResultPageNo = @props.statechart.getArgumentValue('currentResultPageNo')
+      `<ResultsMain
+          statechart={this.props.statechart}
+          results={results}
+          currentResultPageNo={currentResultPageNo} />`
+
+
+  rowWithSidebar: ->
     `

         METADATA_CATEGORIES

-        {this.props.statechart.pathContains('start')
-          ? <StartMain
-              store={this.props.store}
-              statechart={this.props.statechart}
-              corpus={this.props.corpus} />
-          : <ResultsMain
-              statechart={this.props.statechart}
-              results={results}
-              currentResultPageNo={currentResultPageNo} />}
+        {this.mainComponent()}

`

-  rowWithoutSidebar: (results, currentResultPageNo) ->
+
+  rowWithoutSidebar: ->
     `

-        {this.props.statechart.pathContains('start')
-          ? <StartMain
-              store={this.props.store}
-              statechart={this.props.statechart}
-              corpus={this.props.corpus} />
-          : <ResultsMain
-              statechart={this.props.statechart}
-              results={results}
-              currentResultPageNo={currentResultPageNo} />}
+        {this.mainComponent()}

`

+
   render: ->
-    searchId = @props.statechart.getArgumentValue('searchId')
-    results = if searchId then @props.store.find('search', searchId) else null
-    currentResultPageNo = @props.statechart.getArgumentValue('currentResultPageNo')
-
     if true
-      @rowWithSidebar(results, currentResultPageNo)
+      @rowWithSidebar()
     else
-      @rowWithoutSidebar(results, currentResultPageNo)
+      @rowWithoutSidebar()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -174,7 +174,7 @@
             .attr("transform", "translate(" + width/2 + "," + height/2 + ")")

         # end Build pie
-        console.log("update pie", dataSet);
+        console.log("update pie", data);

         #create a marker element if it doesn't already exist
         defs = svg.select("defs")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -137,7 +137,7 @@
         )

     update: (data) ->
-        console.log("update pie", data);
+        #console.log("update pie", data);

         that = this;

@@ -161,8 +161,8 @@
             .attr("cy", 3)
             .attr("r", 3);
         }
-        //Create/select <g> elements to hold the different types of graphics
-        //and keep them in the correct drawing order
+        #Create/select <g> elements to hold the different types of graphics
+        #and keep them in the correct drawing order
         pathGroup = this.svg.select("g.piePaths");
         if (pathGroup.empty() ){
             pathGroup = this.svg.append("g")


Agent pie: test globalizing variables

Agent pie: test globalizing variables

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -3,10 +3,7 @@
     @accessor 'data'

     ready: ->
-        @container()
-
-    container: ->
-        return $(@node).parent()
+        @container = $(@node).parent()

     onData: (data) ->
         #$(@node).fadeOut().fadeIn()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -284,7 +284,7 @@
             #.attrTween("d", pieTween)
             .attrTween("d", (d,i,a) ->
                 i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-                return (t) ->
+                return t ->
                     b = i(t)
                     return arc(b)
             )


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -443,6 +443,7 @@
     console.log 'update pie', dataSet
     #that = this
     piedata = pie(dataSet)
+    piedata = piedata.filter(pd) -> pd.value isnt 0
     console.log 'update pie', piedata
     #create a marker element if it doesn't already exist
     defs = svg.select('defs')


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -187,10 +187,10 @@
         #    .attr("height", height)
         #    .append("svg:g")
         #    .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
-        svg = d3.select(@node).append("svg:svg")
+        svg = d3.select(@node).append("svg")
             .attr("width", width)
             .attr("height", height)
-            .append("svg:g")
+            .append("g")
             .attr("transform", "translate(" + width/2 + "," + height/2 + ")")

         # end Build pie


Use koding-counter to create incremental IDs.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -57,25 +57,33 @@
       return callback err  if err
       account.fetchUser (err, user) =>
         return callback err  if err
-        if type is 'group'
-          name = "#{groupSlug}~#{(new Date()).getTime()}"
-        else if type is 'user'
-          name = "#{groupSlug}~#{user.username}-#{(new Date()).getTime()}"
-        vm = new JVM {
-          name    : name
-          users   : [{ id: user.getId(), sudo: yes }]
-          groups  : [{ id: group.getId() }]
-          usage
+        name = "#{groupSlug}~"
+        if type is 'user'
+          name = "#{name}#{user.username}-"
+
+        nameFactory = (require 'koding-counter') {
+          db          : JVM.getClient()
+          counterName : name
+          offset      : 0
         }
-        vm.save (err) =>
+
+        nameFactory.next (err, uid)=>
           return callback err  if err
-          group.addVm vm, (err)=>
+          vm = new JVM {
+            name    : "#{name}#{uid}"
+            users   : [{ id: user.getId(), sudo: yes }]
+            groups  : [{ id: group.getId() }]
+            usage
+          }
+          vm.save (err) =>
             return callback err  if err
-            if type is 'group'
-              @addVmUsers vm, group, ->
+            group.addVm vm, (err)=>
+              return callback err  if err
+              if type is 'group'
+                @addVmUsers vm, group, ->
+                  callback null, vm
+              else
                 callback null, vm
-            else
-              callback null, vm

   @addVmUsers = (vm, group, callback)->
     group.fetchMembers (err, members)->


test: cleanup variable naming

test: cleanup variable naming

--- test/index.coffee
+++ test/index.coffee
@@ -8,7 +8,7 @@
 BASE_LEFT = ({a: i, b: i + 1} for i in [1..3])
 BASE_RIGHT = ({a: i, b: i + 2} for i in [1..3])

-configs = [
+BASE_CONFIGS = [
   on: 'a'
   left: BASE_LEFT
   right: BASE_RIGHT
@@ -147,14 +147,14 @@
       .map((el={}) -> (JSON.stringify el[key] for key in _(el).keys().sort()))
       .flatten().value().join ';'

-make_configs = (config) ->
-  mod_configs = [config]
+create_configs_from_base = (config) ->
+  new_configs = [config]
   # If on is a string, also try the hash version of on to verify it's the same
-  mod_configs.push _.extend {}, config, {on: _.object config.on, config.on} if _(config.on).isString()
-  # If type isn't specified, assume that the expected values are for type=full and create tests
-  # for the other types by filtering it
-  mod_configs = _(mod_configs).chain()
-    .map (mod_config) ->
+  new_configs.push _.extend {}, config, {on: _.object config.on, config.on} if _(config.on).isString()
+  # The expected values are for type=full. Create tests for the other types by filtering the
+  # expected values
+  new_configs = _(new_configs).chain()
+    .map (new_config) ->
       # If we don't specify type, generate an expected array for each type based on the original
       type_spec =
         left: (expected_pair) -> expected_pair[0]?
@@ -162,23 +162,23 @@
         inner: (expected_pair) -> expected_pair[0]? and expected_pair[1]?
         full: -> true
       _(type_spec).map (filter, type) ->
-        _.extend {}, mod_config, {type},
-          if mod_config.expected then {expected: _(mod_config.expected).filter filter} else {}
+        _.extend {}, new_config, {type},
+          if new_config.expected then {expected: _(new_config.expected).filter filter} else {}
     .flatten()
     .value()

 describe 'joins', ->
-  _(configs).each (config, i) ->
-    _(make_configs config).each (mod_config, j) ->
+  _(BASE_CONFIGS).each (base_config, i) ->
+    _(create_configs_from_base base_config).each (config, j) ->
       test_num = "#{i + 1}.#{j + 1}"
       if config.error
         asserts = (err, results) -> assert.equal err?.message, config.error
       else
         asserts = (err, results) ->
           assert.ifError err
-          assert.deepEqual sort_pairs(results), sort_pairs(mod_config.expected)
-      it "##{test_num} #{if config.error then 'fails' else 'joins'} #{JSON.stringify mod_config}", (done) ->
+          assert.deepEqual sort_pairs(results), sort_pairs(config.expected)
+      it "##{test_num} #{if config.error then 'fails' else 'joins'} #{JSON.stringify config}", (done) ->
         [left, right] = _([config.left, config.right]).map (arr) -> _(arr).stream().stream()
-        _(join left, right, {on: mod_config.on, type: mod_config.type}).stream().run (err, results) ->
+        _(join left, right, _(config).pick 'on', 'type').stream().run (err, results) ->
           asserts err, results
           done()


Replace backtick-quote with double quotes in error messages


Added support for HTTP PUT in the Campfire adapter. Added the ability to change room topic in the Campfire adapter.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -16,8 +16,8 @@
     @send user, strings.map((str) -> "#{user.name}: #{str}")...

   topic: (user, strings...) ->
-    @bot.Room(user.room).speak "Changing topic: #{strings.join}"
-    @bot.topic(strings.join("\n"))
+    @bot.Room(user.room).topic strings.join(" / "), (err, data) =>
+      @robot.logger.error "Campfire error: #{err}" if err?

   run: ->
     self = @
@@ -113,7 +113,8 @@
       @message text, "PasteMessage", callback

     topic: (text, callback) ->
-      self.put "/room/#{id}", text, callback
+      body = {room: {topic: text}}
+      self.put "/room/#{id}", body, callback

     sound: (text, callback) ->
       @message text, "SoundMessage", callback
@@ -195,6 +196,8 @@
     @request "PUT", path, body, callback

   request: (method, path, body, callback) ->
+    logger = @robot.logger
+
     headers =
       "Authorization" : @authorization
       "Host"          : @domain
@@ -208,7 +211,7 @@
       "method" : method
       "headers": headers

-    if method is "POST"
+    if method is "POST" || method is "PUT"
       if typeof(body) isnt "string"
         body = JSON.stringify body

@@ -238,7 +241,7 @@
         logger.error "Campfire response error: #{err}"
         callback err, { }

-    if method is "POST"
+    if method is "POST" || method is "PUT"
       request.end(body, 'binary')
     else
       request.end()


Logging

Logging

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -50,6 +50,13 @@
       unless bot.info.id == author.id
         self.receive new Robot.LeaveMessage(author)

+    bot.on "TimestampMessage",  ->
+      author = bot.Me
+      author.room = room
+      console.log "Timestamp"
+      console.log author.room
+
+
     bot.Me (err, data) ->
       bot.info = data.user
       bot.name = bot.info.name


refactor

refactor

--- src/adaptor/wordpress.coffee
+++ src/adaptor/wordpress.coffee
@@ -7,6 +7,7 @@
 debugWp = require('debug')('wordpress')

 class Wordpress
+  MAX_EXPECTED_WP_POSTS: 1e+6

   constructor: (@config) ->
     @wordpress = wporg.createClient
@@ -41,17 +42,13 @@
       threadToPosts[thread].push post

     for thread, posts of threadToPosts
-      if posts.length > 1
-        for extra_post_id in posts[1..-1]
+      @threadToPost[thread] = posts.shift
+      if posts.length > 0
+        for extra_post_id in posts
           do (extra_post_id) =>
             @wordpress.deletePost extra_post_id, (error, posts) =>
-              if error
-                throw error
-              else
-                @debug "Deleted extra post ##{extra_post_id}"
-
-    for thread, post of threadToPosts
-      @threadToPost[thread] = post[0]
+              throw error if error
+              @debug "Deleted extra post ##{extra_post_id}"

     @debug "Thread to post mapping:"
     @debug pjson @threadToPost


standards. making sure extractMatchedMetrics is safe

--- src/alert_distributor.coffee
+++ src/alert_distributor.coffee
@@ -48,7 +48,7 @@
   # Parse out event data from StatsD packet
   parsePacket: (packet) ->
     for event in packet.toString().split('\n') or []
-      continue if !event
+      continue unless event
       [name, data] = event.split ':'
       [metric, type] = data.split '|'
       metric = Number metric if _.isNumbery metric
@@ -104,7 +104,7 @@
   extractMatchedMetrics: (metricsEvent, metrics) ->
     {type, name, key} = metricsEvent
     subMetrics = metrics?[type]
-    return unless subMetrics
+    return [] unless subMetrics

     fetchMetricsProperty = (metricsObj, metricsKey) ->
       if metricsKey? and metricsKey of metricsObj


multiplatform keybindings #17

multiplatform keybindings #17

--- keymaps/atom-lupa.cson
+++ keymaps/atom-lupa.cson
@@ -7,9 +7,14 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-'atom-workspace':
+'.platform-darwin atom-workspace':
   'cmd-.': 'atom-lupa:focus'
-  'cmd-alt-l': 'atom-lupa:toggle'
+  'cmd-alt-l': 'atom-lupa:toggle'
+
+'.platform-linux atom-workspace, .platform-win32 atom-workspace':
+  'ctrl-.': 'atom-lupa:focus'
+  'ctrl-alt-l': 'atom-lupa:toggle'
+

 # TODO don't duplicate selector
 '.lupa-structure .lupa-entity':


Add some tests

Add some tests


Change core-input-decorators' color

Change core-input-decorators' color

--- assets/scripts/script.coffee
+++ assets/scripts/script.coffee
@@ -17,6 +17,9 @@
   $(".optime-tmpl").each( (it) ->
     $.templates this.id,"#"+this.id
   )
+
+  # Setup styles
+  CoreStyle.g.paperInput.focusedColor = "#0f9d58" # From toggle button

   # Set cards' animation methods
   meta = document.createElement 'core-meta'


Fix next and previous links generation, lifting errors

--- src/core/RouteHandler.coffee
+++ src/core/RouteHandler.coffee
@@ -10,7 +10,6 @@
 @copyright 2015 Microbrew.it
 ###
 class RouteHandler
-	constructor: ->

 	# Logger instance, use this instead of console.log (see core/Logger)
 	logger: logger
@@ -52,7 +51,7 @@
 			route.redirect = '/login'
 			auth = 'session'
 		else
-			auth =
+			auth =
 				mode: 'try'
 				strategy: 'session'

@@ -61,12 +60,12 @@
 			method: route.method
 			path: route.path

-			config:
+			config:
 				handler: route.handler

 				auth: auth

-				plugins:
+				plugins:
 					'hapi-auth-cookie':
 						redirectTo: route.redirect
 		}
@@ -122,7 +121,8 @@
 	# @option req [Object/Bool] token The token object or false
 	# @option req [Object/Bool] user The logged in user object or false
 	# @option req [Object] header The HTTP headers from the request
-	# @option req [Object] params Combination of url-params (e.g fermentables/{id} adds id) and GET params (e.g size = 50). (Examples combined results in object {id: something, size: 50})
+	# @option req [Object] params Combination of url-params (e.g fermentables/{id} adds id)
+	# and GET params (e.g size = 50). (Examples combined results in object {id: something, size: 50})
 	# @option req [Function] reply The HAPI reply function
 	# @param [Function] reply The HAPI reply function
 	onRequest: (req, reply) ->
@@ -130,26 +130,36 @@

 	# Default GET handler
 	# Called with same params as @onRequest
+	# @note Only called if return value from onRequest is falsy
+	# @see onRequest
 	onGet: (req, reply) ->
 		@logger.warn "#{@constructor.name}.onGet not implemented (path: #{req.raw.path})"

 	# Default POST handler
 	# Called with same params as @onRequest
+	# @note Only called if return value from onRequest is falsy
+	# @see onRequest
 	onPost: (req, reply) ->
 		@logger.warn "#{@constructor.name}.onPost not implemented (path: #{req.raw.path})"

 	# Default DELETE handler
 	# Called with same params as @onRequest
+	# @note Only called if return value from onRequest is falsy
+	# @see onRequest
 	onDelete: (req, reply) ->
 		@logger.warn "#{@constructor.name}.onDelete not implemented (path: #{req.raw.path})"

 	# Default PUT handler
 	# Called with same params as @onRequest
+	# @note Only called if return value from onRequest is falsy
+	# @see onRequest
 	onPut: (req, reply) ->
 		@logger.warn "#{@constructor.name}.onPut not implemented (path: #{req.raw.path})"

 	# Default PATCH handler
 	# Called with same params as @onRequest
+	# @note Only called if return value from onRequest is falsy
+	# @see onRequest
 	onPatch: (req, reply) ->
 		@logger.warn "#{@constructor.name}.onPatch not implemented (path: #{req.raw.path})"

@@ -175,18 +185,17 @@
 	_getHeaders: (req) ->
 		return req.headers

-	# Get payload/GET  parameters
+	# Get payload/GET parameters
 	# @param [Object] req HAPI HTTP request object
 	# @return [Object] params GET params concated with path params (e.g {id})
 	# @private
 	_getParams: (req) ->
 		params = {}

-		for key,val of req.params
-			params[key] = val
-		for key,val of req.query
+		for key, val of req.params
 			params[key] = val
-		console.info 'PARAMS:', params
+		for key, val of req.query
+			params[key] = val

 		return params

@@ -202,18 +211,19 @@
 		query.from ?= 0

 		next = JSON.parse JSON.stringify query # clone
-		next.from = parseInt(query.from, 10) + parseInt(query.size,10)
-
-		nextLink = pathname + '?'
-		for key, val of next
-			nextLink += "#{key}=#{val}&"
-
-
+		next.from = parseInt(query.from, 10) + parseInt(query.size, 10)
+
+		if query.size <= currentResults
+			nextLink = pathname + '?'
+			for key, val of next
+				nextLink += "#{key}=#{val}&"
+		else
+			nextLink = undefined

 		prev = JSON.parse JSON.stringify query
-		prev.from = parseInt(query.from, 10) - parseInt(query.size,10)
-
-		if prev.from >= 0 and query.size <= currentResults
+		prev.from = parseInt(query.from, 10) - parseInt(query.size, 10)
+
+		if prev.from >= 0
 			prevLink = pathname + '?'
 			for key, val of prev
 				prevLink += "#{key}=#{val}&"


adding in a comment on why a second click is needed

--- test-integration/helpers/ui/exercise-selector.coffee
+++ test-integration/helpers/ui/exercise-selector.coffee
@@ -31,6 +31,7 @@
     for i in [0...numExercises]
       @el.inactiveExerciseCard().findElement().isDisplayed().then =>
         @el.inactiveExerciseCard().waitClick()
+        #BUG add in a second click event to make sure it sticks, not sure why this is needed
         @el.inactiveExerciseCard().waitClick()

   startReview: () ->


update navbar spec

update navbar spec

--- test/components/navbar/user-actions-menu.spec.coffee
+++ test/components/navbar/user-actions-menu.spec.coffee
@@ -17,8 +17,7 @@
     it 'should have expected dropdown menu', (done) ->
       Testing.renderComponent( UserActionsMenu, props: {courseId: courseModel.id} ).then ({dom}) =>
         dropdownItems = dom.querySelectorAll('li')
-        roleItems = Array.prototype.slice.call(dropdownItems, 0, -4)
-
+        roleItems = Array.prototype.slice.call(dropdownItems, 1, -4)
         labels = _.pluck(@roleTestParams.menu, 'label')
         labels.push 'Browse the Book'
         expect(_.pluck(roleItems, 'innerText')).to.deep.equal(labels)