Preliminary +add command

Preliminary +add command

--- bot.iced
+++ bot.iced
@@ -5,7 +5,9 @@

 DogeTipDiscussions = "http://steamcommunity.com/groups/DogeTip/discussions"
 DogeTipGroupID = "103582791435182182"
+
 pendingInvites = []
+pendingPayments = []

 # Connect to the database
 await MongoClient.connect "mongodb://192.168.1.115:27017/dogebot", defer(err, db)
@@ -19,7 +21,10 @@

 # Connect to Steam
 bot = new Steam.SteamClient()
-{credentials} = require "./auth.coffee"
+secrets = require "./auth.coffee"
+{credentials} = secrets
+{moolah} = secrets
+
 bot.logOn credentials
 bot.on "loggedOn", ->
 	console.log "Logged in as '#{credentials.accountName}'"
@@ -28,13 +33,52 @@

 	bot.joinChat DogeTipGroupID
 	bot.sendMessage DogeTipGroupID, "dogetippingbot is back online"
+# Functions
+getNameFromID = (steamID) ->
+	bot.users[steamID].playerName

 bot.on "chatMsg", (sourceID, message, type, chatterID) ->
-	if message == "Friends"
-		bot.sendMessage DogeTipGroupID, JSON.stringify(bot.friends)
+	if message is "Me"
+		bot.sendMessage DogeTipGroupID, bot.users[chatterID].playerName
+		bot.sendMessage DogeTipGroupID, chatterID
 bot.on "friendMsg", (chatterID, message, type) ->
 	# Private messages
 	return if message is ""
+	switch message.split(" ")[0] # The command part
+		when "+register"
+			name = getNameFromID chatterID
+			bot.sendMessage chatterID, "Welcome #{name}"
+			bot.sendMessage chatterID, "Reply '+add <AMOUNT> doge' to add dogecoins to your account"
+			bot.sendMessage chatterID, "Tip users with '+tip <STEAM NAME> <AMOUNT> doge'"
+		when "+add"
+			amount = message.split(" ")[1]
+			amount = parseInt amount, 10
+			if isNaN(amount)
+				return bot.sendMessage chatterID, "Invalid number of doge specified"
+			bot.sendMessage chatterID, "You'll be adding #{amount} doge"
+			options =
+				"url": "https://moolah.ch/api/pay"
+				"method": "GET"
+				"qs":
+					"guid": moolah.guid
+					"currency": "DOGE"
+					"amount": amount
+					"product": "Add Tipping Money"
+					"return": ""
+			requester options, (error, response, body) ->
+				unless !error and response.statusCode is 200
+					console.error "#{Date.now().toString()} - #{error}, #{response}, #{body}"
+					return bot.sendMessage chatterID, "Moolah ran into an error processing your request"
+				try
+					body = JSON.parse body
+				catch e
+					return bot.sendMessage chatterID, "Moolah returned invalid JSON"
+				bot.sendMessage chatterID, "Visit #{body.url} or send #{body.amount} #{body.currency} to #{body.address}"
+				pendingPayments.push {
+					"steamID": chatterID
+					"amount": amount
+					"tx": body.tx
+				}

 bot.on "friend", (steamID, Relationship) ->
 	if pendingInvites.indexOf(steamID) isnt -1


Align with PlatformDarwin.coffee

Align with PlatformDarwin.coffee

Signed-off-by: Bao Zhen <0c17ddceb3071d32d637bf884d4ad3d465c295fe@infthink.com>

--- lib/platform/PlatformLinux.coffee
+++ lib/platform/PlatformLinux.coffee
@@ -63,11 +63,11 @@
                 "--user-data-dir=/tmp/" + @deviceUUID
             ]

-        @chromeProcess.stdout.on 'data', (chunk) ->
+        @chromeProcess.stdout.on 'data', (chunk) =>
 #            Log.d "linux stdout: #{chunk}"
-        @chromeProcess.stderr.on 'data', (chunk) ->
+        @chromeProcess.stderr.on 'data', (chunk) =>
 #            Log.e "linux stderr: #{chunk}"
-        @chromeProcess.on 'exit', () ->
+        @chromeProcess.on 'exit', () =>
             Log.i "linux chrome exit!!!"

     stopApplication: (app) ->


show resolved culprits

show resolved culprits

--- bin/main.coffee
+++ bin/main.coffee
@@ -217,6 +217,7 @@

   jenkinsEmitter.on 'job.result.update', (result, jobName, buildNumber) ->
     if LOGGING then console.log '-> job.result.update'
+    if LOGGING then console.log 'Culprits for this state: ' + result.culprits
     text = switch result
       when 'SUCCESS', 'STABLE'
         sprintf(Labels.getRandom('onJobSwitchedToStable'), PhoneticHelper.improveJobName(jobName), buildNumber, Labels.getRandom(result))


count booleans to vsize

count booleans to vsize

--- _src/lib/node_cache.coffee
+++ _src/lib/node_cache.coffee
@@ -5,6 +5,7 @@
 _isFunction = require( "lodash/isFunction" )
 _isNumber = require( "lodash/isNumber" )
 _isObject = require( "lodash/isObject" )
+_isBoolean = require( "lodash/isBoolean" )
 _size = require( "lodash/size" )
 _template = require( "lodash/template" )

@@ -546,6 +547,8 @@
 		else if _isObject( value )
 			# if the data is an Object multiply each element with a defined default length
 			@options.objectValueSize * _size( value )
+		else if _isBoolean( value )
+			8
 		else
 			# default fallback
 			0


scroll to correct item in list

scroll to correct item in list

--- ember/app/routes/resumes/index.coffee
+++ ember/app/routes/resumes/index.coffee
@@ -47,6 +47,10 @@

     switchResume: (target) ->
       resume = target.get('controller.model')
+      target_div = Ember.$("a[href^='/resumes/"+resume.id+"']").parent()
+      console.log(target_div.position().top)
+      list = Ember.$('.resume_list')
+      list.scrollTop(list.scrollTop() + target_div.position().top - list.height()/2 + target_div.height()/2 )
       @transitionTo('resumes.index.resume', resume )

 `export default ResumesIndex`


Un-pending the break errors.

Un-pending the break errors.

--- test/errors.coffee
+++ test/errors.coffee
@@ -19,7 +19,7 @@
     catch err
       expect(err.description).match /'with' is not supported in CoffeeScript/

-  xit 'happens on break-less cases', ->
+  it 'happens on break-less cases', ->
     try
       js2coffee('switch (x) { case "a": b(); case "b": c(); }')
     catch err


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -1,6 +1,6 @@
 module "Follow"

 test "testing for equality", ->
-
+  ok true

 module()


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -33,6 +33,6 @@
   rightEnemy = GameObject
     x: 100
     y: 50
-    he
+    height: 1

 module()


Write test for real insert. Reformat a few things.

--- test/server.coffee
+++ test/server.coffee
@@ -21,38 +21,39 @@
     client.close()
     listenServer.stop()

-  it 'should start up and accept messages', (done) ->
-    spy = sinon.spy(listenServer, 'handleMessage')
+  describe 'messages', ->
+    it 'should start up and accept messages', (done) ->
+      spy = sinon.spy(listenServer, 'handleMessage')

-    message = new Buffer('Some bytes')
-    client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
-      delay 100, ->
-        assert(spy.called)
-        listenServer.handleMessage.restore()
-        done()
+      message = new Buffer('Some bytes')
+      client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
+        delay 100, ->
+          assert(spy.called)
+          listenServer.handleMessage.restore()
+          done()

-  it 'should silently ignore a buffer of invalid JSON', (done) ->
-    spy = sinon.spy(listenServer, 'incomingData')
+    it 'should silently ignore a buffer of invalid JSON', (done) ->
+      spy = sinon.spy(listenServer, 'incomingData')

-    message = new Buffer('Some bytes')
-    client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
-      delay 100, ->
-        assert(spy.callCount == 0)
-        listenServer.incomingData.restore()
-        done()
+      message = new Buffer('Some bytes')
+      client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
+        delay 100, ->
+          assert(spy.callCount == 0)
+          listenServer.incomingData.restore()
+          done()

-  it 'should accept a buffer of valid JSON', (done) ->
-    spy = sinon.spy(listenServer, 'incomingData')
+    it 'should accept a buffer of valid JSON', (done) ->
+      spy = sinon.spy(listenServer, 'incomingData')

-    data = {
-      "Thing": "It's a thing"
-    }
-    message = new Buffer(JSON.stringify(data))
-    client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
-      delay 100, ->
-        assert(spy.calledOnce)
-        listenServer.incomingData.restore()
-        done()
+      data = {
+        "Thing": "It's a thing"
+      }
+      message = new Buffer(JSON.stringify(data))
+      client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
+        delay 100, ->
+          assert(spy.calledOnce)
+          listenServer.incomingData.restore()
+          done()

   describe 'passphrases', ->
     data = null
@@ -142,3 +143,19 @@

           mongodb.Collection.prototype.insert.restore()
           done()
+
+    it 'inserts the data given if all other things are good', (done) ->
+      sinon.stub(mongodb.Collection.prototype, 'insert')
+      data =
+        'objectType' : 'User'
+        'oneThing'   : 'Thing'
+        'otherData'  : 'Something'
+
+      message = new Buffer(JSON.stringify(data))
+      client.send message, 0, message.length, portNumber, 'localhost', (err, bytes) ->
+        delay 100, ->
+          dataArg = mongodb.Collection.prototype.insert.args[0][0]
+          assert(dataArg.oneThing == 'Thing')
+          assert(dataArg.otherData == 'Something')
+          mongodb.Collection.prototype.insert.restore()
+          done()


Simple check for tutorial completed this session

--- app/classify/tutorial.cjsx
+++ app/classify/tutorial.cjsx
@@ -4,6 +4,8 @@
 md = new MarkdownIt
   linkify: true
   breaks: true
+
+completedThisSession = false

 module.exports = React.createClass
   displayName: 'tutorial'
@@ -21,7 +23,7 @@

       getCompletedAt.then (completedAt) =>
         if isNaN completedAt?.valueOf()
-          false
+          completedThisSession
         else
           # TODO: Check if the completion date is greater than the tutorial's modified_at date.
           # Return `null` to mean "Completed, but not with the most recent version".
@@ -62,6 +64,7 @@

   onFinish: ->
     @props.onFinish()
+    completedThisSession = true
     @props.user?.get('project_preferences', project_id: @props.project.id)
       .then ([projectPreferences]) =>
         projectPreferences ?= @props.api.type('project_preferences').create({


Update course id for 2UP

Update course id for 2UP

--- app/core/utils.ozar.coffee
+++ app/core/utils.ozar.coffee
@@ -111,7 +111,7 @@
 courseIDs =
   ONE_FREE_HOUR: '5d41d731a8d1836b5aa3cba1'
   CHAPTER_ONE: '5d8a57abe8919b28d5113af1'
-  CHAPTER_TWO: '5e2759cce8919b668b7737b1'
+  CHAPTER_TWO: '5e27600d1c9d440000ac3ee7'

 orderedCourseIDs = [
@@ -142,7 +142,8 @@

 # TODO update module names before shipping 2UP
 courseModules[courseIDs.CHAPTER_TWO] = {
-  '1': 'Temp module'
+  '1': 'Temp Module 1',
+  '2': 'Temp Module 2'
 }

 hourOfCodeOptions = {


A bit more relaxed text selection detection

--- js-library/app/js/jail_iframe/util/create.coffee
+++ js-library/app/js/jail_iframe/util/create.coffee
@@ -10,5 +10,5 @@
     text, siteUrl, siteTitle

 FactlinkJailRoot.textSelected = ->
-  # At least 4 words of at least 2 characters
-  /(\w{2,}[\s-_&\/#%.,;:!()]+){4}/.test window.document.getSelection().toString()
+  # At least 3 words of at least 2 characters, separated by at most 6 non-letter chars
+  /(\w{2,}\W{1,6}){3}/.test window.document.getSelection().toString()


debug application model

debug application model

--- server/models/application.coffee
+++ server/models/application.coffee
@@ -7,7 +7,7 @@
     slug: String
     state: String
     isStoppable: {type: Boolean, default: false}
-    date: {type: name: String, default: Date.now}
+    date: {type: Date, default: Date.now}
     icon: String
     git: String
     errormsg: String


fix integration test not terminating

fix integration test not terminating

--- spec/integration/backend.spec.coffee
+++ spec/integration/backend.spec.coffee
@@ -29,8 +29,11 @@
           "all steps finishing", TIMEOUT)

       beforeEach ->
+        backend = null
         finished = false
         errors = []
+        collection = null
+
         TestConfigReader = require('../test_config_reader')
         configReader = new TestConfigReader()


Use editor.update in tests

Use editor.update in tests

--- spec/text-editor-element-spec.coffee
+++ spec/text-editor-element-spec.coffee
@@ -269,23 +269,21 @@

   describe "on TextEditor::onDidChangeAutoHeight", ->
     it "changes the element's height", ->
-      element = new TextEditorElement
-      jasmine.attachToDOM(element)
-      expect(element.style.height).toBe('')
-      element.getModel().setAutoHeight(false)
+      editor = atom.workspace.buildTextEditor()
+      jasmine.attachToDOM(editor.element)
+      expect(editor.element.style.height).toBe('')

       waitsForPromise ->
-        atom.views.getNextUpdatePromise()
+        editor.update({autoHeight: false})

       runs ->
-        expect(element.style.height).toBe('100%')
-        element.getModel().setAutoHeight(true)
+        expect(editor.element.style.height).toBe('100%')

       waitsForPromise ->
-        atom.views.getNextUpdatePromise()
+        editor.update({autoHeight: true})

       runs ->
-        expect(element.style.height).toBe('')
+        expect(editor.element.style.height).toBe('')

   describe "events", ->
     element = null


Require selecting at least a few words before showing annotation button

--- js-library/app/js/jail_iframe/util/create.coffee
+++ js-library/app/js/jail_iframe/util/create.coffee
@@ -15,4 +15,5 @@
   FactlinkJailRoot.factlinkCoreEnvoy 'prepareNewFactlink', text, siteUrl, siteTitle

 FactlinkJailRoot.textSelected = ->
-  !!window.document.getSelection().toString()
+  # At least 4 words of at least 2 characters
+  /(\w{2,}[\s-_&\/#%]+){4}/.test window.document.getSelection().toString()


Also trim annotations created by selection

--- js-library/app/js/jail_iframe/util/create.coffee
+++ js-library/app/js/jail_iframe/util/create.coffee
@@ -5,7 +5,7 @@
     FactlinkJailRoot.createButton.hide()
     FactlinkJailRoot.off 'modalOpened', success

-  text = window.document.getSelection().toString()
+  text = window.document.getSelection().toString().trim()
   siteTitle = window.document.title
   siteUrl = FactlinkJailRoot.siteUrl()


product map doesnt start till you say so

--- slides/dot_map/coffee/vis.coffee
+++ slides/dot_map/coffee/vis.coffee
@@ -322,7 +322,7 @@
     worldplot.world(world)
     worldplot.stores(stores)
     plotData("#vis", data, worldplot)
-    worldplot.start()
+    # worldplot.start()

   queue()


Fixed indentation bug in the output generator

--- source/generator.coffee
+++ source/generator.coffee
@@ -40,17 +40,17 @@

   pop_scope = (code, force_closed, wrap) ->
     rv = i
-    var_names = (var_name for var_name, type of scope when type not in ['closure', 'argument', 'function'])
-    if var_names.length > 0 or wrap
-      if wrap
-        rv += '(function () {\n'
-        indent()
-        code = i + code.replace /\n/g, '\n  '
+    var_names = (var_name for var_name, type of scope when type not in ['closure', 'argument', 'function', 'class definition'])
+    if wrap
+      rv += '(function () {\n'
+      indent()
+      code = i + code.replace /\n/g, '\n  '
+    if var_names.length > 0
       rv += '  var ' + var_names.join(', ') + ';\n' if var_names.length > 0
     rv += code
-    if var_names.length > 0 or wrap
+    if wrap
       dedent()
-      rv += "\n#{i}})()\n" if wrap
+      rv += "\n#{i}})()\n"
     scope = scopes.pop() if scopes isnt []
     return rv


unsubstantial code formatting changes

unsubstantial code formatting changes

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -64,19 +64,19 @@
     # A bit of black magic is used here, for a later refactoring.
     prelude: (symbol, context, request, next) ->
         assert send = context.transit.bind context
-        assert shadow = Object.create this # spoofing
+        assert shadow = Object.create this # isolated
         try _.extend shadow, session: request.session
         try _.extend shadow, request: request or null
         _.extend shadow, __isolated: yes, __origin: @
         translation = @obtainTranslation # get provider
         setup = @setupTranslationTools # get installer
         execute = (a...) => translation.apply shadow, a
+        spoof = (l, c) -> c @_tmpLanguage, @_tmpMessages
         return execute undefined, (language, messages) =>
-            do -> context.translationLanguage = language
-            do -> context.translationMessages = messages
+            do -> assert context._tmpLanguage = language
+            do -> assert context._tmpMessages = messages
+            send spoof, (f) -> this.obtainTranslation = f
             send setup, (f) -> @setupTranslationTools = f
-            context.inline -> @obtainTranslation = (l, c) ->
-                c @translationLanguage, @translationMessages
             context.inline -> @setupTranslationTools()
             return do => next.call this, undefined


Builder: trigger build in client/Framework

--- Builder.coffee
+++ Builder.coffee
@@ -67,10 +67,22 @@
       spriteHelper = helper
       @buildClient options

+  buildFramework:->
+
+    @config ?= require('koding-config-manager').load("main.#{options.configFile}")
+    cmd = "cd client/Framework;gulp compile --buildVersion=#{@config.client.version} --outputDir=../../website/a/"
+    exec cmd, (err, stdout, stderr)->
+      console.warn "------------------------ FRAMEWORK COMPILED -------------------------- "
+      console.warn " To use watcher for Framework use following command in different tab:  "
+      console.log  " $ #{cmd} "
+      console.warn "------------------------ FRAMEWORK COMPILED -------------------------- "
+
   buildClient: (options) ->
     @config ?= require('koding-config-manager').load("main.#{options.configFile}")

     try fs.mkdirSync ".build"
+
+    @buildFramework()

     if @config.client.runtimeOptions.precompiledApi
       exec 'coffee ./bongo-api-builder/build.coffee -o .build/api.js', =>
@@ -78,6 +90,7 @@
     else
       fs.writeFileSync '.build/api.js', '', 'utf-8'
       @buildAndWatchClient options
+

   buildAndWatchClient: (options) ->


:green_heart: Fix broken specs due to change in method name

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -212,7 +212,7 @@
         expect(-> nextAnimationFrame()).not.toThrow()

       it 'renders the visible line decorations', ->
-        spyOn(minimapElement, 'drawLineDecorations').andCallThrough()
+        spyOn(minimapElement, 'drawLineDecoration').andCallThrough()

         minimap.decorateMarker(editor.markBufferRange([[1,0], [1,10]]), type: 'line', color: '#0000FF')
         minimap.decorateMarker(editor.markBufferRange([[10,0], [10,10]]), type: 'line', color: '#0000FF')
@@ -224,8 +224,8 @@
         runs ->
           nextAnimationFrame()

-          expect(minimapElement.drawLineDecorations).toHaveBeenCalled()
-          expect(minimapElement.drawLineDecorations.calls.length).toEqual(2)
+          expect(minimapElement.drawLineDecoration).toHaveBeenCalled()
+          expect(minimapElement.drawLineDecoration.calls.length).toEqual(3)

       it 'renders the visible highlight decorations', ->
         spyOn(minimapElement, 'drawHighlightDecoration').andCallThrough()


Add test for getCurrentPath bug

Add test for getCurrentPath bug

--- spec/project-manager-spec.coffee
+++ spec/project-manager-spec.coffee
@@ -24,3 +24,11 @@
         encoding: 'utf-8'
       fs.readFile ProjectManager.file(), options, (err, data) ->
         expect(err).toBe null
+
+    it "getCurrentPath existential operator issue is fixed", ->
+      projects = test: paths: [atom.project.getPath()]
+      result = ProjectManager.getCurrentProject projects
+      expect(result).not.toBe false
+      projects = test: {}
+      result = ProjectManager.getCurrentProject projects
+      expect(result).toBe false


Fixed issue #1841: Taskbar item race condition causes JS exception.

--- app/assets/javascripts/app/lib/spine/spine.coffee
+++ app/assets/javascripts/app/lib/spine/spine.coffee
@@ -417,6 +417,15 @@
     # ID change, need to do some shifting
     if atts.id and @id isnt atts.id
       @changeID(atts.id)
+
+    # temporary debugging of a possible race condition
+    if !@constructor.irecords[@id]
+      model     = @constructor.className
+      atts_json = JSON.stringify(atts)
+      console.notice "Can't refresh not exisint %s instance with ID %s and attributes %s. Possible race condition?", model, @id, atts_json
+      # prevent exception
+      return
+
     # go to the source and load attributes
     @constructor.irecords[@id].load(atts)
     @trigger('refresh', this)


View also respects URLs

View also respects URLs


Trying to remove resize when on iPhone

Trying to remove resize when on iPhone

--- app/assets/javascripts/static_pages.js.coffee
+++ app/assets/javascripts/static_pages.js.coffee
@@ -3,6 +3,13 @@
 # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

 # Responsive Action
+#This doesnt work well with the iPhone because it resizes at every touch
+
+isNotiPhone = () ->
+    return (
+        (navigator.platform.indexOf("iPhone") == -1) || (navigator.platform.indexOf("iPod") == -1)
+    )
+
 changeMenuWidth = (e) ->
 	if ($(window).width() < 767)
 		$(".menu").data("menuwidth", "80%")
@@ -36,7 +43,8 @@
 		$(".menu").css("left", "-20%")
 		$('.siteContainer').css('left', "0%")

-	$(window).bind("resize", changeMenuWidth)
+	if(isNotiPhone())
+		$(window).bind("resize", changeMenuWidth)

 	$('.btn-menu').click ->
 		closeSpeed = $('.menu').data('closespeed')


Add room field to notify in that room sensor's messages

--- sensor.coffee
+++ sensor.coffee
@@ -3,6 +3,8 @@
 class HubotSensor
   constructor: (@freq, active = true) ->
     @_id = null
+    @room = process.env.HUBOT_SENSORS_ROOM
+
     @setup() if active

   setup: () ->


:white_check_mark: jk in visual-line mode when soft wrapped

--- spec/motion-spec.coffee
+++ spec/motion-spec.coffee
@@ -117,6 +117,58 @@
           ensure 'kk',
             cursor: [0, 3]
             selectedText: "defg\n\nabcd"
+
+    describe "jk in softwrap", ->
+      beforeEach ->
+        editor.setSoftWrapped(true)
+        editor.setEditorWidthInChars(10)
+        set
+          text: """
+            1st line of buffer
+            2nd line of buffer, Very long line
+            3rd line of buffer
+
+            5th line of buffer\n
+            """
+          cursor: [0, 0]
+
+      describe "selection is not reversed", ->
+        it "screen position and buffer position is different", ->
+          ensure 'j', cursor: [1, 0], cursorBuffer: [0, 9]
+          ensure 'j', cursor: [2, 0], cursorBuffer: [1, 0]
+          ensure 'j', cursor: [3, 0], cursorBuffer: [1, 9]
+          ensure 'j', cursor: [4, 0], cursorBuffer: [1, 20]
+
+        it "jk move selection buffer-line wise", ->
+          ensure 'V', selectedText: '1st line of buffer\n'
+          ensure 'j', selectedText: '1st line of buffer\n2nd line of buffer, Very long line\n'
+          ensure 'j', selectedText: '1st line of buffer\n2nd line of buffer, Very long line\n3rd line of buffer\n'
+          ensure 'j', selectedText: '1st line of buffer\n2nd line of buffer, Very long line\n3rd line of buffer\n\n'
+          ensure 'j', selectedText: "1st line of buffer\n2nd line of buffer, Very long line\n3rd line of buffer\n\n5th line of buffer\n"
+          ensure 'k', selectedText: '1st line of buffer\n2nd line of buffer, Very long line\n3rd line of buffer\n\n'
+          ensure 'k', selectedText: '1st line of buffer\n2nd line of buffer, Very long line\n3rd line of buffer\n'
+          ensure 'k', selectedText: '1st line of buffer\n2nd line of buffer, Very long line\n'
+          ensure 'k', selectedText: '1st line of buffer\n'
+          ensure 'k', selectedText: '1st line of buffer\n' # do nothing
+      describe "selection is reversed", ->
+        it "screen position and buffer position is different", ->
+          ensure 'j', cursor: [1, 0], cursorBuffer: [0, 9]
+          ensure 'j', cursor: [2, 0], cursorBuffer: [1, 0]
+          ensure 'j', cursor: [3, 0], cursorBuffer: [1, 9]
+          ensure 'j', cursor: [4, 0], cursorBuffer: [1, 20]
+
+        it "jk move selection buffer-line wise", ->
+          set cursorBuffer: [4, 0]
+          ensure 'V', selectedText: '5th line of buffer\n'
+          ensure 'k', selectedText: "\n5th line of buffer\n"
+          ensure 'k', selectedText: "3rd line of buffer\n\n5th line of buffer\n"
+          ensure 'k', selectedText: "2nd line of buffer, Very long line\n3rd line of buffer\n\n5th line of buffer\n"
+          ensure 'k', selectedText: "1st line of buffer\n2nd line of buffer, Very long line\n3rd line of buffer\n\n5th line of buffer\n"
+          ensure 'j', selectedText: "2nd line of buffer, Very long line\n3rd line of buffer\n\n5th line of buffer\n"
+          ensure 'j', selectedText: "3rd line of buffer\n\n5th line of buffer\n"
+          ensure 'j', selectedText: "\n5th line of buffer\n"
+          ensure 'j', selectedText: "5th line of buffer\n"
+          ensure 'j', selectedText: "5th line of buffer\n" # do nothing

     describe "the l keybinding", ->
       beforeEach ->


Do not limit documents for non-server collections.

--- server.coffee
+++ server.coffee
@@ -68,14 +68,17 @@
   projection

 # TODO: Should we add retry?
-globals.Document._observerCallback = (f) ->
+globals.Document._observerCallback = (collection, f) ->
+  # We observe only collections without connection or server collections.
+  assert (Meteor.isClient and collection._connection is null) or (Meteor.isServer and collection._connection in [null, Meteor.server]), collection._connection
+
   return (obj, args...) ->
     try
       id = if _.isObject obj then obj._id else obj
-      # We call f only if the first character of id is in PREFIX.
-      # By that we allow each instance to operate only on a subset
-      # of documents, allowing simple coordination while scaling.
-      f obj, args... if id[0] in PREFIX
+      # We call f only if the first character of id is in PREFIX. By that we allow each instance to
+      # operate only on a subset of documents, allowing simple coordination while scaling.
+      # We call f always for collections without connection.
+      f obj, args... if collection._connection is null or id[0] in PREFIX
     catch e
       Log.error "PeerDB exception: #{ e }: #{ util.inspect args, depth: 10 }"
       Log.error e.stack
@@ -93,24 +96,24 @@
   if not updateAll and @ instanceof globals.Document._ReferenceField
     index = {}
     index["#{ @sourcePath }._id"] = 1
-    @sourceCollection._ensureIndex index if @sourceCollection._connection is Meteor.server
+    @sourceCollection._ensureIndex index if Meteor.isServer and @sourceCollection._connection is Meteor.server

     if @reverseName
       index = {}
       index["#{ @reverseName }._id"] = 1
-      @targetCollection._ensureIndex index if @targetCollection._connection is Meteor.server
+      @targetCollection._ensureIndex index if Meteor.isServer and @targetCollection._connection is Meteor.server

   initializing = true

   observers =
-    added: globals.Document._observerCallback (id, fields) =>
+    added: globals.Document._observerCallback @targetCollection, (id, fields) =>
       @updateSource id, fields if updateAll or not initializing

   unless updateAll
-    observers.changed = globals.Document._observerCallback (id, fields) =>
+    observers.changed = globals.Document._observerCallback @targetCollection, (id, fields) =>
       @updateSource id, fields

-    observers.removed = globals.Document._observerCallback (id) =>
+    observers.removed = globals.Document._observerCallback @targetCollection, (id) =>
       @removeSource id

   referenceFields = fieldsToProjection @fields
@@ -130,13 +133,13 @@

   queryFields = fieldsToProjection @fields
   @collection.find({}, fields: queryFields).observe
-    added: globals.Document._observerCallback (document) =>
+    added: globals.Document._observerCallback @collection, (document) =>
       @trigger document, null unless initializing

-    changed: globals.Document._observerCallback (newDocument, oldDocument) =>
+    changed: globals.Document._observerCallback @collection, (newDocument, oldDocument) =>
       @trigger newDocument, oldDocument

-    removed: globals.Document._observerCallback (oldDocument) =>
+    removed: globals.Document._observerCallback @collection, (oldDocument) =>
       @trigger null, oldDocument

   initializing = false
@@ -501,16 +504,16 @@

     unless updateAll
       for index in indexes
-        @Meta.collection._ensureIndex index if @Meta.collection._connection is Meteor.server
+        @Meta.collection._ensureIndex index if Meteor.isServer and @Meta.collection._connection is Meteor.server

     initializing = true

     observers =
-      added: globals.Document._observerCallback (id, fields) =>
+      added: globals.Document._observerCallback @Meta.collection, (id, fields) =>
         @_sourceUpdated id, fields if updateAll or not initializing

     unless updateAll
-      observers.changed = globals.Document._observerCallback (id, fields) =>
+      observers.changed = globals.Document._observerCallback @Meta.collection, (id, fields) =>
         @_sourceUpdated id, fields

     handle = @Meta.collection.find({}, fields: sourceFields).observeChanges observers
@@ -582,7 +585,7 @@
     # Setup observers only for local collections or server collection. Collections based
     # on a connection to some other primary collections should not have observers because
     # they should be setup at the location of primary collections, not here.
-    for document in @list when document.Meta.collection._connection in [null, Meteor.server]
+    for document in @list when (Meteor.isClient and document.Meta.collection._connection is null) or (Meteor.isServer and document.Meta.collection._connection in [null, Meteor.server])
       if updateAll
         # For fields we pass updateAll on.
         setupTargetObservers document.Meta.fields


Make upgrading the plugin non-destructive

You can enable these lines again while debugging if that is useful, but
normally a game should not reset when upgrading the plugin.

--- server.coffee
+++ server.coffee
@@ -11,8 +11,9 @@

 # Game update
 exports.onUpgrade = !->
-	initializeColors()
-	initializeGame()
+	# Normally nothing to do here
+	#initializeColors()
+	#initializeGame()

 # Config changes (by admin or plugin adder)
 exports.onConfig = (config) !->


Stop game after last disconnect if there are more

--- server.coffee
+++ server.coffee
@@ -105,7 +105,7 @@

   numberOfPlayers: ->
     Object.keys(@players).length
-
+
   twitterUsers: ->
     users = []
     users.push(username) for socket, username of @players
@@ -137,8 +137,8 @@
     line = []
     for x in [0..@width]
       line[x] = @grid[number][x]
-    line
-
+    line
+
   rowOfZeros: ->
     line = []
     for x in [0..@width]
@@ -160,8 +160,8 @@
   pipe.channel(gameID).trigger('moved', data, socketID)

 pipe.channels.on 'event:removed', (gameID, socketID, data) ->
-  game = games[gameID]
-  delete game.shapes[data.id]
+  game = games[gameID]
+  delete game.shapes[data.id]
   pipe.channel(gameID).trigger('inFinalPosition', data)

 pipe.channels.on 'event:blockAdded', (gameID, socketID, data) ->
@@ -201,6 +201,11 @@
   delete game.shapes[shapeID]
   pipe.channel(theGameID).trigger('purge', {id:shapeID})

+  if game.grid.numberOfPlayers() == 0
+    if gameIDs.length > 4
+      delete games[theGameID]
+      gameIDs.splice(gameIDs.indexOf(theGameID), 1);
+
 pipe.on 'connected', ->
   setInterval (->
     for gameID in gameIDs


less verbose logging when server is starting

--- server.coffee
+++ server.coffee
@@ -46,7 +46,7 @@
   n = t = 0
   (s) ->
     if (t1 = +new Date) - t > T then t = t1; n = 1
-    else if ++n < N then console.info process.uptime().toFixed(3) + ' ' + s
+    if ++n < N then console.info process.uptime().toFixed(3) + ' ' + s
     else if n == N then console.info '... logging temporarily suppressed'

 b64 = (s) -> Buffer(s).toString 'base64'
@@ -63,6 +63,13 @@
   log "preloaded html: #{html.length} bytes"
 fs.watch 'index.html', preloadHTML

+css = ''
+do preloadCSS = ->
+  css0 = cssFiles.map((f) -> fs.readFileSync f, 'utf8').join '\n'
+  css = cleanCSS.minify css0
+  log "preloaded css: #{Buffer(css0).length}→#{Buffer(css).length} bytes"
+cssFiles.forEach (f) -> fs.watch f, preloadCSS
+
 js = ''
 do preloadJS = ->
   js = ''
@@ -70,31 +77,27 @@
     f1 = "cache/#{f.replace /\//g, '_'}.uglified"
     if !fs.existsSync(f1) || fs.statSync(f1).mtime < fs.statSync(f).mtime
       if !fs.existsSync 'cache' then fs.mkdirSync 'cache'
-      s = fs.readFileSync f, 'utf8'
-      if /\.coffee$/.test f then log "compiling #{f}"; s = coffee.compile s, bare: 1
+      ib = fs.readFileSync f # input buffer
+      sizes = [ib.length]
+      s = ib + ''
+      if /\.coffee$/.test f
+        s = coffee.compile s, bare: 1
+        sizes.push Buffer(s).length
       if !/\.min\.js$/.test f
-        log "minifying #{f}"
         s1 = uglify.minify(
           s.replace(/^(?:.*require.*;\n)*/, '')
           fromString: true, mangle: true
         ).code
-        try fs.writeFileSync f1, s1 catch # ignore errors
-        log "  #{s.length} -> #{s1.length} bytes"
+        ob = Buffer s1 # output buffer
+        sizes.push ob.length
+        try fs.writeFileSync f1, ob catch # ignore errors
         s = s1
+      log "  #{f} #{sizes.join '→'} bytes"
     else
       s = fs.readFileSync f1, 'utf8'
     js += s + '\n'
   log "preloaded js: #{js.length} bytes"
 jsFiles.forEach (f) -> fs.watch f, preloadJS
-
-css = ''
-do preloadCSS = ->
-  log 'minifying CSS...'
-  css0 = cssFiles.map((f) -> fs.readFileSync f, 'utf8').join '\n'
-  css = cleanCSS.minify css0
-  log "  #{css0.length} -> #{css.length} bytes"
-  log "preloaded css: #{css.length} bytes"
-cssFiles.forEach (f) -> fs.watch f, preloadCSS

 ttf = fs.readFileSync 'apl385.ttf'
 ico = fs.readFileSync 'favicon.ico'
@@ -115,7 +118,7 @@
   key: fs.readFileSync 'ssl/key.pem'
   cert: fs.readFileSync 'ssl/cert.pem'
 server = https.createServer(httpsOptions, app).listen (httpsPort = 8443),
-  -> log "HTTPS server listening on :#{httpsPort}"
+  -> log "https server listening on :#{httpsPort}"

 client = net.connect {host: '127.0.0.1', port: 4502}, -> log 'interpreter connected'


disconnect on error

disconnect on error

--- server.coffee
+++ server.coffee
@@ -10,8 +10,11 @@

   client.pipe(through((chunk) =>
     tentacleTransformer.addData(chunk)
-    while (decoded = tentacleTransformer.toJSON())
-      console.log 'while looping with decoded = ', JSON.stringify(decoded,null,2)
+    try
+      while (decoded = tentacleTransformer.toJSON())
+        console.log 'while looping with decoded = ', JSON.stringify(decoded,null,2)
+    catch error
+      client.end()
   )).on 'data', (data) =>
     console.log "data: #{data}"


Fixed blacklist saving when unban

Fixed blacklist saving when unban

--- server.coffee
+++ server.coffee
@@ -624,6 +624,7 @@
     self = this
     delete self.blacklist[uid] if self.blacklist[uid]
     nodeirc.action("\u000303#{uid} has been unbanned.") if not self.mute
+    self.saveBlacklist()

   createRoutes: =>
     self = this


look only written models

look only written models

--- lib/client/javascripts/nali/collection.js.coffee
+++ lib/client/javascripts/nali/collection.js.coffee
@@ -20,13 +20,14 @@
     @

   onModelUpdated: ( extModel, model ) ->
-    if model in @
-      if @freezed or model.isCorrect @filters
-        @reorder()
-        @trigger 'update.model', model
-      else @remove model
-    else if not @freezed and model.isCorrect @filters
-      @add model
+    if model.written()
+      if model in @
+        if @freezed or model.isCorrect @filters
+          @reorder()
+          @trigger 'update.model', model
+        else @remove model
+      else if not @freezed and model.isCorrect @filters
+        @add model
     @

   onModelDestroyed: ( model ) ->
@@ -37,7 +38,7 @@
     for name, method of @model when /^_\w+$/.test( name ) and typeof method is 'function'
       do ( name, method ) =>
         @[ name = name[ 1.. ] ] = ( args... ) =>
-          @adaptation ( model ) -> model[ name ] args...
+          @each ( model ) -> model[ name ] args...
           @
     @


attach/detach object to bone

attach/detach object to bone

--- src/3d/BaseModel.coffee
+++ src/3d/BaseModel.coffee
@@ -27,6 +27,26 @@
     @mesh.traverse (object) ->
       object.visible = value
     @visible = value
+
+  # attach a mesh to a bone
+  #
+  # @param [String] boneName
+  # @param [Mesh] mesh
+  attachToBone: (boneName, mesh) ->
+    if @mesh instanceof THREE.SkinnedMesh
+      @mesh.traverse (object) ->
+        if object instanceof THREE.Bone && object.name == boneName
+          object.add mesh
+
+  # detach a mesh from a bone
+  #
+  # @param [String] boneName
+  # @param [Mesh] mesh
+  detachFromBone: (boneName, mesh) ->
+    if @mesh instanceof THREE.SkinnedMesh
+      @mesh.traverse (object) ->
+        if object instanceof THREE.Bone && object.name == boneName
+          object.remove mesh

   # Change the skin to a TextureManager texture
   #


comments. plumber to stop errors in tests from stopping gulp.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -27,7 +27,8 @@
 # to require modules for testing
 gulp.task 'build-test', ['clean'], ->
   testHeader = """
-# not done under karma webpack tests
+# no module.path under karma webpack tests
+# but we handle the paths in the webpack conf
 if module.paths
   module.paths.unshift process.cwd() + "/lib"

@@ -50,19 +51,24 @@
 # lint clean and build everything
 gulp.task 'build', ['lint', 'clean', 'build-lib', 'build-test']

+testFiles = ['test/helpers/**/*.js', 'test/spec/**/*.js']
 # helper to run coverage generation with given options
 runCoverage = (opts) ->
-  gulp.src(['test/helpers/**/*.js', 'test/spec/**/*.js'], read: false)
+  gulp.src(testFiles, read: false)
     .pipe($.coverage.instrument
       pattern: ['lib/**/*.js', '!node_modules']
       debugDirectory: 'debug')
+    .pipe($.plumber())
     .pipe($.mocha reporter: 'dot').on('error', (e) -> @emit 'end') # test errors dropped
+    .pipe($.plumber.stop())
     .pipe($.coverage.gather())
     .pipe($.coverage.format opts)

-runTest = (opts) ->
-  gulp.src(['test/helpers/**/*.js', 'test/spec/**/*.js'], read: false)
-    .pipe($.mocha opts).on('error', (e) -> @emit 'end') # test errors dropped
+runTest = (stream, opts) ->
+  stream
+    .pipe($.plumber())
+    .pipe($.mocha opts)
+    .pipe($.plumber.stop())

 # test and create coverage
 gulp.task 'coverage', ['build'], ->
@@ -76,18 +82,22 @@
   runCoverage(reporter: 'lcov')
     .pipe($.coveralls())

+# run tests but only show progress
 gulp.task 'test-quiet', ['build'], ->
-  runTest reporter: 'dot'
+  runTest gulp.src(testFiles), reporter: 'dot'

+# run and display all tests
 gulp.task 'test', ['build'], ->
-  runTest reporter: 'spec'
+  runTest gulp.src(testFiles), reporter: 'spec'

+# run tests from phantomjs with karma
 gulp.task 'karma', ['test-quiet'], (done) ->
   karma.start
     configFile: require('path').join __dirname, './karma.conf.js'
     singleRun: true
   , done

+# uses chrome and firefox
 gulp.task 'karma-gui', ['test-quiet'], (done) ->
   karma.start
     configFile: require('path').join __dirname, './karma.conf.js'
@@ -95,17 +105,20 @@
     singleRun: true
   , done

+# doesn't work... yet
 gulp.task 'watch-coverage', ['coverage'], (done) ->
   $.livereload.listen port: 35729
   require('opn') './coverage.html', null, util.log
-  $.watch('src/**/*.coffee', ['coverage'])
-  $.watch('./coverage.html').on 'change', $.livereload
+  gulp.watch('src/**/*.coffee', ['coverage'])
+  gulp.watch('./coverage.html').on 'change', $.livereload
   return

+# watch all coffee files and run tests for changes
 gulp.task 'watch', (done) ->
-  $.watch('src/**/*.coffee', ['test'])
+  gulp.watch 'src/**/*.coffee', ['test']
   return

+# default, test
 gulp.task 'default', ['test']


add build-json task

add build-json task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,3 +1,4 @@
+{exec} = require 'child_process'
 {Promise} = require 'es6-promise'
 borage = require 'borage'
 browserify = require 'browserify'
@@ -89,6 +90,7 @@
     @emit 'end'

 gulp.task 'build', [
+  'build-json'
   'build-html'
   'build-resource'
   'build-script'
@@ -119,6 +121,13 @@
   loadEntries('./data/nofile.md').forEach ({ paths, entry }) ->
     paths.forEach (path) ->
       prerender(baseDir + path, entry)
+
+gulp.task 'build-json', (done) ->
+  exec '$(npm bin)/kraken', (err, stdout, stderr) ->
+    return done(err) if err?
+    console.log stdout if stdout?.length > 0
+    console.error stderr if stderr?.length > 0
+    done()

 gulp.task 'build-resource', ['build-font'], ->
   gulp.src [


Minor code improvement

Minor code improvement

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -87,10 +87,9 @@

 # compile and zip PmB
 gulp.task 'compile-init', ->
-  version_filter = (x) -> "#{parseInt x}" is "#{x}" and x >= 0 and x <= 65535
   if argv.version is undefined or
      argv.version.split('.').length isnt 4 or
-     argv.version.split('.').filter(version_filter).length isnt 4
+     not argv.version.split('.').every((x) -> x is "#{parseInt x}" and 0 <= x <= 65535)
     throw 'You need to input a version number x.y.z.ddmm, each number between 0 - 65535'

   initDir compile_path


Fix clean

Fix clean

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -11,7 +11,7 @@
 libFiles = ['./lib/**/*.js']

 gulp.task 'clean-build', ->
-  gulp.src libFiles
+  gulp.src './lib/*'
     .pipe clean { force: true }

 gulp.task 'lint', ->


populated stylus build task

populated stylus build task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -179,7 +179,7 @@
       "#{source}/**/*.styl"
       "!#{source}/test/**/*"
       "!#{source}/test" ]
-
+    .pipe stylus()
     .pipe gulp.dest build

 ### Dist ###


Uglify

Uglify

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -104,7 +104,7 @@
     ))
     .on("error", displayError)
     .pipe(concat("bundle.js"))
-    # .pipe(uglify())
+    .pipe(uglify())
     .pipe gulp.dest("./app/scripts")
     .pipe( livereload() )


Build: uglify to not mangle class names

Build: uglify to not mangle class names

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -98,7 +98,7 @@
       entries : entryPath
     .pipe source entryPath
     .pipe gulpBuffer()
-    .pipe gulpif useUglify, uglify()
+    .pipe gulpif useUglify, uglify(mangle : no)
     .pipe rename "kd.#{version}js"
     .pipe gulp.dest "#{buildDir}/js"


Remove homePath from gulpfile.

Remove homePath from gulpfile.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -6,7 +6,6 @@
 $ = require('gulp-load-plugins')()

 remotePluginPath = 'razor-x/vimrc'
-pluginPath = "#{homePath}/.vim/bundle/vimrc"
 vimCmd = (cmd) ->
   "vim -N -u #{homePath}/.vimrc -c \"try | #{cmd} $* | finally | qall | endtry\" -U NONE -i NONE -V1 -e -s"


test: add 2 new gulp tasks for headless testing.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -217,16 +217,37 @@
     .pipe gulp.dest 'test'

+testFiles = [
+  './test/kd.libs.js'
+  './test/kd.test.js'
+]
+
 gulp.task 'karma', ['coffee-test'], ->

-  gulp.src ['./test/kd.*']
+  gulp.src testFiles
     .pipe karma
       configFile : 'karma.conf.js'
       action     : karmaAction

+gulp.task 'karma-headless', ['coffee-test'], ->
+
+  return gulp.src testFiles
+    .pipe karma
+      configFile : 'karma.conf.js'
+      action     : 'watch'
+      browsers   : ['PhantomJS']
+
+gulp.task 'karma-travis', ['coffee-test'], ->
+
+  return gulp.src testFiles
+    .pipe karma
+      configFile : 'karma.conf.js'
+      action     : 'run'
+      browsers   : ['PhantomJS']
+      reporters  : ['dots']

 gulp.task 'sauce', ->
-  gulp.src ['./test/kd.*']
+  gulp.src testFiles
     .pipe karma
       browsers   : [
         'sl_firefox_windows'


add comment

add comment

--- app/scripts/results.coffee
+++ app/scripts/results.coffee
@@ -498,7 +498,8 @@
         opts = @s.$parent.kwicTab.queryParams

         @resetView()
-        opts.ajaxParams.incremental = true
+        # example tab cannot handle incremental = true
+        opts.ajaxParams.incremental = false

         opts.ajaxParams.start = (@current_page - 1) * items_per_page
         opts.ajaxParams.end = (opts.ajaxParams.start + items_per_page - 1)


atom config bump

atom config bump

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -3,6 +3,7 @@
     'showIndentGuide': true
     'fontFamily': 'Menlo'
     'fontSize': 15
+    'invisibles': {}
   'core':
     'ignoredNames': [
       '.bundle'
@@ -32,4 +33,3 @@
     'showOnStartup': false
   'release-notes':
     'viewedVersion': '0.89.0'
-  'go-plus': {}


Update BaseView to handle default barStyle

--- Framework/Views/BaseView.coffee
+++ Framework/Views/BaseView.coffee
@@ -7,6 +7,7 @@
       backgroundGradient: root.app.settings.viewBackgroundGradient
       barColor: root.app.settings.viewTitleBarColor
       backgroundImage: root.app.settings.viewBackgroundImage
+      viewTitleBarStyle: root.app.settings.viewTitleBarStyle
     }, options)
     @applyStyle()


Dylan ruins everything.

Dylan ruins everything.

Somehow, I balme Maritime Admiralty Law and a gold fringed flag.

--- scripts/remember.coffee
+++ scripts/remember.coffee
@@ -76,7 +76,7 @@

     msg.send "My favorite memories are:\n#{sortedMemories[0..20].join(', ')}"

-  robot.respond /(me|random memory|memories)\s*(.*)?$/i, (msg) ->
+  robot.respond /(me|random memory)$/i, (msg) ->
     msg.finish()

     randomKey = if msg.match[2]


Battles: Minor refactor to #getWinner.

Battles: Minor refactor to #getWinner.

--- server/bw/battle.coffee
+++ server/bw/battle.coffee
@@ -382,14 +382,24 @@
         @message "#{loser.id}: #{oldRatings[1]} -> #{result[1].rating}"

   getWinner: ->
-    winner = null
+    winner     = null
+
+    # If each player has the same number of pokemon alive, return null.
+    teamLength = @players[0].team.getAlivePokemon().length
+    playerSize = @players.length
+    count      = 1
+    for i in [1...playerSize] by 1
+      count++  if teamLength == @players[i].team.getAlivePokemon().length
+    return null  if count == playerSize
+
+    # Otherwise, return the player with the most pokemon alive.
     length = 0
     for player in @players
       newLength = player.team.getAlivePokemon().length
       if newLength > length
         length = newLength
         winner = player
-    winner
+    return winner

   isOver: ->
     @finished || @players.any((player) -> player.team.getAlivePokemon().length == 0)


fix code-highlights status

fix code-highlights status

--- lib/minimap-quick-settings-view.coffee
+++ lib/minimap-quick-settings-view.coffee
@@ -11,7 +11,7 @@
       @input type: 'text', class: 'hidden-input', outlet: 'hiddenInput'
       @ol class: 'list-group mark-active', outlet: 'list', =>
         @li class: 'separator', outlet: 'separator'
-        @li class: (if atom.config.get('minimap.displayCodeHighlights') then 'active' else ''), outlet: 'codeHighlights', 'code-highlights'
+        @li class: '', outlet: 'codeHighlights', 'code-highlights'

   selectedItem: null

@@ -32,6 +32,7 @@
     @on 'core:cancel', @destroy
     @on 'core:validate', @toggleSelectedItem

+    @codeHighlights.toggleClass('active', @minimapView.displayCodeHighlights)
     @codeHighlights.on 'mousedown', (e) =>
       e.preventDefault()
       @minimapView.setDisplayCodeHighlights(!@minimapView.displayCodeHighlights)


prevValue not used outside if-statement

prevValue not used outside if-statement

--- resources/assets/coffee/_classes/account-edit.coffee
+++ resources/assets/coffee/_classes/account-edit.coffee
@@ -91,8 +91,6 @@

   update: (form) =>
-    prevValue = null
-
     if form.dataset.accountEditType == 'multi'
       data = @getMultiValue(form)
     else


add new link to root Neuron

add new link to root Neuron

--- app/assets/javascripts/admin/moi_tree.js.coffee
+++ app/assets/javascripts/admin/moi_tree.js.coffee
@@ -128,7 +128,7 @@
     # format:
     $popover.find(".popover-title").html(node.title)
     $newChildLink = $popover.find(".new-child-link")
-    if node.parent_id
+    if node.parent_id or node.id == @rootNeuron.id
       $newChildLink.show()
                    .attr("href", "/admin/neurons/new?parent_id=#{node.id}")
     else


Click link.

Click link.

--- app/assets/javascripts/filter_manager.js.coffee
+++ app/assets/javascripts/filter_manager.js.coffee
@@ -27,7 +27,7 @@
         element.hide()
         $("a[href^='##{element.attr('id')}']").parent().hide()
     if $("#tabs-floor li.active").css("display") == "none"
-      $("#tabs-floor li:visible").click()
+      $("#tabs-floor li:visible a").first().click()
   all_filter: ->
     return @all_filter_element if @all_filter_element?
     for filter in @filters


fixed error toasts animating again

fixed error toasts animating again

--- app/assets/javascripts/main/enhancements.coffee
+++ app/assets/javascripts/main/enhancements.coffee
@@ -252,7 +252,7 @@
         5000
       )

-    newTarget = $("div.alert-error:not(.highlighted)").addClass("highlighted")
+    newTarget = $("div.alert-danger:not(.highlighted)").addClass("highlighted")
     if newTarget.length then highlight(newTarget)

   $("#alert-container").on "DOMSubtreeModified", (event) ->


forcing height instead of width with forceReflow

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -1,15 +1,14 @@
 window.templates = {}

 window.forceReflow = (elem = document.documentElement) ->
-    # http://stackoverflow.com/questions/11297641/mobile-webkit-reflow-issue/11479118#11479118
-    width = elem.style.width
-    px = elem.offsetWidth + 1
-    elem.style.width = px+'px'
-
+    #http://stackoverflow.com/a/11478853/382982
+    hack = document.createElement("div")
+    hack.style.height = "101%"
+    document.body.appendChild(hack)
     setTimeout ->
-        elem.style.width = width
-        elem = null
-    0
+        document.body.removeChild(hack)
+        hack = null
+    , 0

 window.pluralize_like_count = (like_count) ->
     if like_count is 1 then 'like' else 'likes'


scrolling to unhappyMessage in scroll_to_error

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -65,7 +65,7 @@
         ($ '#mobile_layout_inner').css('padding-top', header_height)

     window.scroll_to_error = ($context) ->
-        error_y = $context.find('.unhappy').first().offset().top - header_height - 20
+        error_y = $context.find('.unhappyMessage').first().offset().top - header_height - 20
         scrollTo(0, error_y)

 Zepto ($) ->


GUI - do not store images twice in LocalStorage

--- source/javascripts/models/project.js.coffee
+++ source/javascripts/models/project.js.coffee
@@ -56,7 +56,10 @@
     @morpher.draw()

   morpherChange: =>
-    @save morpher: @morpher.toJSON(), {silent: true}
+    json = @morpher.toJSON()
+    for img, i in json.images
+      img.src = null
+    @save morpher: json, {silent: true}

   getCode: =>
     json = @morpher.toJSON()


only run customwmd when where's markdown

--- lms/static/coffee/src/customwmd.coffee
+++ lms/static/coffee/src/customwmd.coffee
@@ -1,5 +1,21 @@
 $ ->
-  converter = Markdown.getSanitizingConverter()
-  editor = new Markdown.Editor(converter)
-  #converter.hooks.chain "preConversion", removeMath
-  editor.run()
+
+  if Markdown?
+    mathRenderer = new MathJaxDelayRenderer()
+    removeMath = (text) -> text
+
+    replaceMath = (text) -> text
+
+    updateMathJax = ->
+      console.log "updating"
+      #mathRenderer.render
+      #  element: $("#wmd-preview")
+      MathJax.Hub.Queue(["Typeset", MathJax.Hub, "wmd-preview"])
+
+
+    converter = Markdown.getSanitizingConverter()
+    editor = new Markdown.Editor(converter)
+    converter.hooks.chain "preConversion", removeMath
+    converter.hooks.chain "postConversion", replaceMath
+    editor.hooks.chain "onPreviewRefresh", updateMathJax
+    editor.run()


temporary fix for bug

temporary fix for bug

--- user_status.coffee
+++ user_status.coffee
@@ -36,10 +36,20 @@

   # Add socket to open connections
   ipAddr = @_session.socket.headers?['x-forwarded-for'] || @_session.socket.remoteAddress
-  UserSessions.insert
-    _id: sessionId
-    userId: userId
-    ipAddr: ipAddr
+  ###
+    TODO serious bug here when sessionId already exists in local collection
+    Happens when userId exists but session has already been recorded with the same sessionId
+  ###
+  if UserSessions.findOne(sessionId)
+    UserSessions.update sessionId,
+      userId: userId
+      ipAddr: ipAddr
+  else
+    # Only this part should actually be necessary
+    UserSessions.insert
+      _id: sessionId
+      userId: userId
+      ipAddr: ipAddr
   UserStatus.emit("sessionLogin", userId, sessionId, ipAddr)

   Meteor.users.update userId,


further delays position container

further delays position container

--- autocomplete-client.coffee
+++ autocomplete-client.coffee
@@ -292,7 +292,7 @@
       Meteor.setTimeout =>
         @positionContainer()
         @ensureSelection()
-      , 150
+      , 300

     return showing

@@ -401,15 +401,15 @@

       if positionAbove
         ancestorHasTransform = el.parents().filter(->
-          trans = $(this).css("transform")
-          return trans != "none";
+          trans = $(this).css("transform")
+          return trans != "none";
         ).length > 0

         if ancestorHasTransform
           console.log "positioning dropdown above - abosolute"
           style.left = position.left
           style.top = -($results.height() + 3)
-        else
+        else
           console.log "positioning dropdown above - fixed"
           style.position = "fixed"
           style.left = offset.left


add a little doc string to help

add a little doc string to help

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -159,6 +159,7 @@
     # messages. These are the strings to use for displaying with
     # different languages. File format is YAML with the special
     # structure that embeds multiple languages in a single file.
+    # If second argument is a string, is treated as dir option.
     @translation: (location, options={}) ->
         options = dir: options if _.isString options
         assert not _.isEmpty cwd = try process.cwd()


fix a stupid replace

fix a stupid replace

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -65,13 +65,13 @@
     # one and the foreign (the one that is beign merged in). Exists
     # for backing up the consistent behavior when using composition.
     @composition: (destination) ->
-        assert currents = this.jscripts or Array()
-        previous = destination.jscripts or Array()
+        assert currents = this.remotes or Array()
+        previous = destination.remotes or Array()
         return unless destination.derives RToolkit
         assert previous? and try _.isArray previous
         assert merged = previous.concat currents
         assert merged = _.toArray _.unique merged
-        assert try destination.jscripts = merged
+        assert try destination.remotes = merged
         try super catch error; return this

     # A directive to mark the certain remote class or object to be
@@ -80,13 +80,13 @@
     # in all the remote classes that you need to the remote call site.
     # Refer to the remote compilation procedures for more information.
     @remote: (subject) ->
-        assert previous = @jscripts or Array()
+        assert previous = @remotes or Array()
         qualify = try subject.remote.compile
         noRemote = "the subject is not remote"
-        noPrevious = "invalid previous jscripts"
+        noPrevious = "invalid previous remotes"
         assert _.isArray(previous), noPrevious
         assert _.isFunction(qualify), noRemote
-        @jscripts = previous.concat subject
+        this.remotes = previous.concat subject

     # This server side method is called on the context prior to the
     # context being compiled and flushed down to the client site. The
@@ -97,8 +97,8 @@
         context.inline -> `assert = chai.assert`
         context.inline -> `assert(logger = log)`
         context.inline -> try logger.enableAll()
-        assert jscripts = @constructor.jscripts or []
-        assert uniques = _.unique jscripts or Array()
+        assert remotes = @constructor.remotes or []
+        assert uniques = _.unique remotes or Array()
         @inject context, blob for blob in uniques
         return do => next.call this, undefined


Use user's email address as enterprise.email

During registration, use the given email address only as public visible
profile email address. Set the internal notification email address
`enterprise.email` to the owner's email address. That can be changed
later.

--- app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
+++ app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
@@ -55,7 +55,6 @@
       excluded = [ 'address', 'country', 'id' ]
       for key, value of @enterprise when key not in excluded
         enterprise[key] = value
-      enterprise.email = enterprise.email_address
       enterprise.address_attributes = @enterprise.address if @enterprise.address?
       enterprise.address_attributes.country_id = @enterprise.country.id if @enterprise.country?
       enterprise


fix chart axies timezone issue

fix chart axies timezone issue

--- app/assets/javascripts/shortener/views/shortened_urls/shortened_urls_show.js.coffee
+++ app/assets/javascripts/shortener/views/shortened_urls/shortened_urls_show.js.coffee
@@ -79,7 +79,7 @@
         defaultTheme: false
       xaxis:
         mode: "time"
-        timeformat: "%b %y"
+        timezone: "browser"
       yaxis:
         tickDecimals: 0
     $.plot(@$(selector), data, $.extend(true, options, defaultOptions, @flotCommonOptions))


adds items to new budgets

adds items to new budgets

--- app/assets/javascripts/backend/budget.js.coffee
+++ app/assets/javascripts/backend/budget.js.coffee
@@ -1,5 +1,12 @@
 (($) ->
   'use strict'
+  $(document).ready ->
+    $("table").on 'cocoon:after-insert', (event, inserted) ->
+      # adds items to new budget
+      if inserted.hasClass("budget_nested_fields")
+        link_to_add_budget_item = inserted.find("a[data-association='item']")
+        $("input[id^='production_supports_attributes_'][id$='id'][type='hidden']").each ->
+          link_to_add_budget_item.click()
   # adds a budget item when adding a support
   $(document).on 'click keyup', "a[data-association='support']", ->
     $("a[data-association='item']").click()


Filter trips by lower-case name

Filter trips by lower-case name

--- app/assets/javascripts/events_planner.js.coffee
+++ app/assets/javascripts/events_planner.js.coffee
@@ -8,7 +8,7 @@
     revertDuration: 0

   planner.find('.trip-filter').keyup (event)->
-    query = $(event.target).val()
+    query = $(event.target).val().toLowerCase()

     match = ->
       $(this).text().toLowerCase().indexOf(query) > -1


OX-313 initial lookup table update

OX-313 initial lookup table update

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -95,7 +95,7 @@
       @flycam.setQuality(@model.user.quality)

       @model.binary.queue.set4Bit(@model.user.fourBit)
-      @model.binary.updateLookupTable(@model.user.brightness, @model.user.contrast)
+      @model.binary.updateLookupTable(@gui.settings.brightness, @gui.settings.contrast)

       @initMouse()
       @initKeyboard()


Update 'Last updated' value

Update 'Last updated' value

Refactored MapView and how it is rendered and rerendered. Merge StudentList into
Mapview#renderHeader().

--- app/assets/javascripts/views/map_view.js.coffee
+++ app/assets/javascripts/views/map_view.js.coffee
@@ -1,4 +1,4 @@
-template =
+templates =
   studentList: _.template """

@@ -13,17 +13,6 @@

   """
-
-StudentList = Backbone.View.extend
-  initialize: (options) ->
-    @currentAssignment = options.currentAssignment
-
-  render: ->
-    markup = template.studentList
-      lastUpdatedAt: @currentAssignment.get('last_updated_at')
-      currentStudentName: @currentAssignment.get('student_name')
-
-    @$el.html markup

 Wmsb.Views.MapView = Backbone.View.extend
   events:
@@ -43,50 +32,60 @@
     new google.maps.StyledMapType @styles, name: 'Boston Public Schools'

   initialize: (options) ->
-    @mapEl             = document.getElementById 'map-canvas'
-    @currentAssignment = @collection.find (assignment) ->
-      assignment.get('token') is cookie.get('current_assignment')
+    @header = @$('#header')
+    @mapEl  = document.getElementById 'map-canvas'

-    @listenTo @collection, 'reset', @updateMarker
+    @updateCurrentAssignment()

-    _.bindAll this
-
-  render: ->
-    @studentList = new StudentList
-      el: document.getElementById 'header'
-      collection: @collection
-      currentAssignment: @currentAssignment
-    @studentList.render()
-
     @map = new google.maps.Map @mapEl, {
       center: @currentAssignment.get('latLng')
       zoom: 14
       mapTypeControlOptions:
         mapTypeIds: ['map_style']
     }
-
     @map.mapTypes.set 'map_style', @styledMap()
     @map.setMapTypeId 'map_style'

-    @updateMarker()
+    @listenTo @collection, 'reset', @rerender

-    @intervalID = setInterval @refreshLocations, 60000
+    _.bindAll this

-  refreshLocations: ->
-    @collection.fetch
-      reset: true
+  updateCurrentAssignment: ->
+    @currentAssignment = @collection.find (assignment) ->
+      assignment.get('token') is cookie.get('current_assignment')

-  updateMarker: ->
+  render: ->
+    @renderHeader()
+    @renderMarker()
+
+    unless @intervalID?
+      @intervalID = setInterval @refreshLocations, 5000
+
+  rerender: ->
+    @updateCurrentAssignment()
+
+    @renderHeader()
+
     if @marker?
       @marker.setMap null

+    @renderMarker()
+
+  renderHeader: ->
+    markup = templates.studentList
+      lastUpdatedAt: @currentAssignment.get('last_updated_at')
+      currentStudentName: @currentAssignment.get('student_name')
+    @header.html markup
+
+  renderMarker: ->
     center = @currentAssignment.get 'latLng'
     @marker = new google.maps.Marker
       position: center
       map: @map
       title: @currentAssignment.get 'student_name'

-    @map.setCenter center
+  refreshLocations: ->
+    @collection.fetch reset: true

   updateCurrentStudent: (event) ->
     @currentAssignment = @collection.find (assignment) ->
@@ -94,4 +93,4 @@

     cookie.set 'current_assignment', @currentAssignment.get('token')

-    @updateMarker()
+    @render()


Comment clarification in imports.

Comment clarification in imports.

--- client/import.coffee
+++ client/import.coffee
@@ -47,7 +47,7 @@

 publicationHandles = {}

-# Observes files that go the documents assigned to
+# Observes files that have a publication on the server
 filesWithPublications = ImportingFile.documents.find
   'publicationId':
     $exists: true
@@ -55,6 +55,7 @@
   added: (id) ->
     publicationHandles[id] = Meteor.subscribe 'publications-by-ids', ImportingFile.documents.findOne(id).publicationId
   removed: (id) ->
+    assert.ok publicationHandles[id]
     publicationHandles[id].stop()

 verifyFile = (file, publicationId, samples) ->


Added support for sequential multi file upload. Fixes #429

--- client/import.coffee
+++ client/import.coffee
@@ -36,12 +36,22 @@
         finished: true
         publicationId: publicationId

+
+uploadQueue = async.queue (params, callback) ->
+  meteorFile = new MeteorFile params.file,
+    collection: ImportingFile.Meta.collection
+
+  meteorFile.upload params.file, 'upload-publication',
+    size: UPLOAD_CHUNK_SIZE,
+    publicationId: params.publicationId
+  ,
+    callback
+,
+  1 # simultaneous upload(s)
+
 uploadFile = (file, publicationId) ->
-  meteorFile = new MeteorFile file,
-    collection: ImportingFile.Meta.collection
-
-  meteorFile.upload file, 'upload-publication',
-    size: UPLOAD_CHUNK_SIZE,
+  uploadQueue.push
+    file: file
     publicationId: publicationId
   ,
     (error) ->
@@ -56,6 +66,7 @@
         $set:
           finished: true
           publicationId: publicationId
+

 testPDF = (file, fileContent, callback) ->
   PDFJS.getDocument(data: fileContent, password: '').then callback, (message, exception) ->


alteration of ill config for wms URLs and bugfix for metadata in ILL request URLs

--- noddy/service/oabutton/ill.coffee
+++ noddy/service/oabutton/ill.coffee
@@ -309,9 +309,13 @@
       # could be provided as: (unless other params are mandatory)
       # https://ambslibrary.share.worldcat.org/wms/cmnd/nd/discover/items/search?si0qs=0021-9231
       if user.service?.openaccessbutton?.ill?.config?.search and (opts.issn or opts.journal)
-        su = user.service.openaccessbutton.ill.config.search
-        #su += if su.indexOf('?') is -1 then '?' else '&' # as we don't know the param for different systems it will have to be provided ending in param=
-        su += if opts.issn then opts.issn else opts.journal
+          su = user.service.openaccessbutton.ill.config.search.split('?')[0] + '?ai0id=level3&ai0type=scope&offset=1&pageSize=10&si0in='
+          su += if opts.issn? then 'in%3A' else 'ti%3A'
+          su += '&si0qs=' + (opts.issn ? opts.journal)
+          su += '&sortDirection=descending&sortKey=librarycount&applicationId=nd&requestType=search&searchType=advancedsearch&eventSource=df-advancedsearch'
+        else
+          su = user.service.openaccessbutton.ill.config.search
+          su += if opts.issn then opts.issn else opts.journal
         vars.details+= '
Search URL:
' + su + '
'

       if not opts.forwarded
@@ -345,10 +349,11 @@
   if config?
     update = {}
     for k in ['ill_institution','ill_redirect_base_url','ill_redirect_params','method','sid','title','doi','pmid','pmcid','author','journal','issn','volume','issue','page','published','year','terms','book','other','cost','time','email','problem_email','subscription','search','autorun','autorunparams','intropara']
-      if k is 'ill_redirect_base_url' and config[k].indexOf('illiad.dll') isnt -1 and config[k].toLowerCase().indexOf('Action=') is -1
+      if k is 'ill_redirect_base_url' and config[k].indexOf('illiad.dll') isnt -1 and config[k].toLowerCase().indexOf('action=') is -1
         config[k] = config[k].split('?')[0]
         if config[k].indexOf('/openurl') is -1
-          config[k] = config[k].split('#')[0] + '/openurl' + (config[k].indexOf('#') is -1 ? '' : config[k].split('#')[1].split('?')[0])
+          config[k] = config[k].split('#')[0] + '/openurl'
+          config[k] += if config[k].indexOf('#') is -1 then '' else '#' + config[k].split('#')[1].split('?')[0]
         config[k] += '?genre=article'
       update[k] = config[k] if config[k]?
     if not user.service.openaccessbutton.ill?
@@ -401,45 +406,35 @@
     config[d] = defaults[d] if not config[d]

   url = if config.ill_redirect_base_url then config.ill_redirect_base_url else ''
-  if url.indexOf('/wms/') isnt -1
-    if meta.issn? or meta.journal?
-      url = url.split('?')[0] + '?ai0id=level3&ai0type=scope&offset=1&pageSize=10&si0in='
-      url += if meta.issn? then 'in%3A' else 'ti%3A'
-      url += '&si0qs=' + meta.issn ? meta.journal
-      url += '&sortDirection=descending&sortKey=librarycount&applicationId=nd&requestType=search&searchType=advancedsearch&eventSource=df-advancedsearch'
-      return url
-    else
-      return ''
-  else
-    url += if url.indexOf('?') is -1 then '?' else '&'
-    url += config.ill_redirect_params.replace('?','') + '&' if config.ill_redirect_params
-    url += config.sid + '=InstantILL&'
-    for k of meta
-      v = false
-      if k is 'author'
-        # need to check if config has aufirst and aulast or something similar, then need to use those instead,
-        # if we have author name parts
-        try
-          if typeof meta.author is 'string'
-            v = meta.author
-          else if _.isArray meta.author
-            v = ''
-            for author in meta.author
-              v += ', ' if v.length
-              if typeof author is 'string'
-                v += author
-              else if author.family
-                v += author.family + if author.given then ', ' + author.given else ''
+  url += if url.indexOf('?') is -1 then '?' else '&'
+  url += config.ill_redirect_params.replace('?','') + '&' if config.ill_redirect_params
+  url += config.sid + '=InstantILL&'
+  for k of meta
+    v = false
+    if k is 'author'
+      # need to check if config has aufirst and aulast or something similar, then need to use those instead,
+      # if we have author name parts
+      try
+        if typeof meta.author is 'string'
+          v = meta.author
+        else if _.isArray meta.author
+          v = ''
+          for author in meta.author
+            v += ', ' if v.length
+            if typeof author is 'string'
+              v += author
+            else if author.family
+              v += author.family + if author.given then ', ' + author.given else ''
+        else
+          if meta.author.family
+            v = meta.author.family + if meta.author.given then ', ' + meta.author.given else ''
           else
-            if meta.author.family
-              v = meta.author.family + if meta.author.given then ', ' + meta.author.given else ''
-            else
-              v = JSON.stringify meta.author
-      else if k not in ['started','ended','took','terms','book','other','cost','time','email','redirect','url','source','notes']
-        v = meta[k]
-      if v
-        url += (if config[k] then config[k] else k) + '=' + v + '&'
-    return url
+            v = JSON.stringify meta.author
+    else if k not in ['started','ended','took','terms','book','other','cost','time','email','redirect','url','source','notes']
+      v = meta[k]
+    if v
+      url += (if config[k] then config[k] else k) + '=' + v + '&'
+  return url

 API.service.oab.ill.terms = (uid) ->
   config = API.service.oab.ill.config uid


error message when failing to copy files for run_one_hook

--- code/instance.coffee
+++ code/instance.coffee
@@ -110,11 +110,15 @@
         {config_name: config.name, file: h}
       async.forEachSeries things, @_call_hook, callback

-   run_one_hook: (config_name, hook_name, callback) =>
-     config = new cf.Config config_name
-     hook = {config_name: config.name, file: hook_name}
-     @cpdir config.hooks_dir, (exit_code) =>
-       @_call_hook(hook, callback) if exit_code is 0
+  run_one_hook: (config_name, hook_name, callback) =>
+    config = new cf.Config config_name
+    hook = {config_name: config.name, file: hook_name}
+    @cpdir config.hooks_dir, (exit_code) =>
+      if exit_code is 0
+        process.stderr.write "Failed to copy files #{config.hooks_dir}\n"
+        callback 'cpdir error'
+      else
+        @_call_hook(hook, callback)

   #### Private methods ####


Using more standard convention for patched stack line numbers.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -259,7 +259,7 @@
     source = getSourceMapping fileName, line, column
     fileLocation =
       if source
-        "#{fileName}:#{source[0]}:#{source[1]}, <js>:#{line}:#{column}"
+        "#{fileName}:#{source[0]}:#{source[1]}"
       else
         "#{fileName}:#{line}:#{column}"


Bump the version to 1.0.4

Bump the version to 1.0.4

--- src/coffeescripts/toast.js.coffee
+++ src/coffeescripts/toast.js.coffee
@@ -8,7 +8,7 @@
 OPTIONS = ['duration', 'content', 'id', 'className', 'animate', 'allowHTML']

 class window.Toast
-  @VERSION: '1.0.3'
+  @VERSION: '1.0.4'

   constructor: (options = {}) ->
     @tc = new TransitionCallbacks(clearAll: true, timeout: 500)


fix label object reference

fix label object reference

--- scripts/gitter-activity-notifier.coffee
+++ scripts/gitter-activity-notifier.coffee
@@ -122,10 +122,10 @@
             title = body.issue.title
             user = body.sender.login
             # if issue has has prior recent changes, combine them and prevent duplicates
-            if action == "labeled" && body.label in validLabels
-                action = "added `#{body.label}` to"
-            else if action == "unlabeled" && body.label in validLabels
-                action = "removed `#{body.label}` from"
+            if action == "labeled" && body.label.name in validLabels
+                action = "added `#{body.label.name}` to"
+            else if action == "unlabeled" && body.label.name in validLabels
+                action = "removed `#{body.label.name}` from"

             if action !in disabledIssueActions
                 if store.issues[issueNum]


Adds blur event to triggers

Adds blur event to triggers

The change event is ... I don't know what's fired when.

--- app/assets/javascripts/views/staffplans/show.js.coffee
+++ app/assets/javascripts/views/staffplans/show.js.coffee
@@ -6,6 +6,7 @@
     "click a[data-change-page]": "changePage"
     "click a.add-client": "onAddClientClicked"
     "change input[data-model=\"Client\"]": "updateProjectTypeAheadOptionList"
+    "blur input[data-model=\"Client\"]": "updateProjectTypeAheadOptionList"

   updateProjectTypeAheadOptionList: (event) ->
     clientName = $(event.target).val()


Removed some unnecessary return statements

--- src/Overloadable.coffee
+++ src/Overloadable.coffee
@@ -146,7 +146,7 @@
             matcher = AbstractMatcher.getMatcher element
             if matcher.match(argument, element) then return true

-        return false
+        false

     compile: (matcherValue) ->
         flattenedArray = @flattenArray(matcherValue)
@@ -173,7 +173,7 @@

 class InstanceofMatcher extends AbstractMatcher
     match: (argument, overloadSignatureElement) ->
-        return argument instanceof overloadSignatureElement
+        argument instanceof overloadSignatureElement

     matcherFactory.registerMatcher "function", InstanceofMatcher


remove a console.log

remove a console.log

--- services/filestore/app/coffee/LocalFileWriter.coffee
+++ services/filestore/app/coffee/LocalFileWriter.coffee
@@ -53,5 +53,4 @@
 		if !key?
 			key = uuid.v1()
 		key = key.replace(/\//g,"-")
-		console.log Settings.path.uploadFolder, key
 		path.join(Settings.path.uploadFolder, key)


always create a new pack, never keep as op

--- services/track-changes/app/coffee/PackManager.coffee
+++ services/track-changes/app/coffee/PackManager.coffee
@@ -269,15 +269,10 @@

 	MAX_SIZE:  1024*1024 # make these configurable parameters
 	MAX_COUNT: 1024
-	MIN_COUNT: 100
-	KEEP_OPS:  100

 	convertDocsToPacks: (docs, callback) ->
 		packs = []
 		top = null
-		# keep the last KEEP_OPS as individual ops
-		docs = docs.slice(0,-PackManager.KEEP_OPS)
-
 		docs.forEach (d,i) ->
 			# skip existing packs
 			if d.pack?
@@ -294,7 +289,7 @@
 				top.v_end = d.v
 				top.meta.end_ts = d.meta.end_ts
 				return
-			else if sz < PackManager.MAX_SIZE
+			else
 				# create a new pack
 				top = _.clone(d)
 				top.pack = [ {v: d.v, meta: d.meta,  op: d.op, _id: d._id} ]
@@ -304,13 +299,7 @@
 				delete top.op
 				delete top._id
 				packs.push top
-			else
-				# keep the op
-				# util.log "keeping large op unchanged (#{sz} bytes)"
-
-		# only store packs with a sufficient number of ops, discard others
-		packs = packs.filter (packObj) ->
-			packObj.pack.length > PackManager.MIN_COUNT
+
 		callback(null, packs)

 	checkHistory: (docs, callback) ->


tidy up output; add comment

tidy up output; add comment

--- migration/010add_shortname.coffee
+++ migration/010add_shortname.coffee
@@ -1,5 +1,9 @@
 #!/usr/bin/env coffee
 # Created 2012-11-20
+# Adds a shortname to the User documents in a mongo database.
+# The database is specified on the command line.
+# The shortname is dervied from the apikey using froth,
+# except for the special cases which are hardcoded here.

 async = require 'async'
 mongoose = require 'mongoose'
@@ -53,7 +57,7 @@
           cb null, user
       if not shortname?
         shortname = special[user.apikey]
-      console.log user.apikey, "xx #{shortname} yy"
+      console.log user.apikey, shortname
       user.shortname = shortname
       user.save ->
         cb null, user


Visual search bar allows adding of facets

--- app/webroot/coffee/views/search/search.coffee
+++ app/webroot/coffee/views/search/search.coffee
@@ -44,6 +44,7 @@
     # <ctrl>-a to select all
     arcs.keys.map @,
       'ctrl+a': @selectAll
+      'enter': @search
       '?': @showHotkeys
       t: @scrollTop

@@ -59,7 +60,7 @@
     'click .sort-btn'          : 'setSort'
     'click .dir-btn'           : 'setSortDir'
     'click .search-page-btn'   : 'setPage'
-    'click .search-type'       : 'search'
+    'click .search-type'       : 'addFacet'

   ### More involved setups run by the initialize method ###

@@ -229,12 +230,15 @@
     results = new arcs.collections.ResultSet @search.getLast()
     @_render results: results.toJSON(), true

+  addFacet: (e) ->
+    e.preventDefault()
+    @search.vs.searchBox.addFacet(e.target.text,'',10)
+
   search: (e) ->
     e.preventDefault()
-
-    #query = e.target.text+",like,"+$("#search-box").val()
+
     query = [e.target.text, "like", $(".VS-search-inner").val()]
-    console.log(query)
+    console.log(@search.vs.searchBox)
     @search.run query,
       order: 'type'
       direction: @options.sortDir


fix: Trigger() is called on the singleton instance holder

--- app/web/caretaker-websocket/caretaker-websocket.coffee
+++ app/web/caretaker-websocket/caretaker-websocket.coffee
@@ -7,7 +7,7 @@
     @websocketDispatcher = websocketDispatcher

   trigger: (event, data) ->
-    websocketDispatcher.trigger event, data
+    @websocketDispatcher.instance.trigger event, data

   urlChanged: ->
     websocketDispatcher.instance = new WebSocketRails @url


refactor to "Block Regular Expressions" for CoffeeScript wordy assignment

--- assignments/coffeescript/wordy/example.coffee
+++ assignments/coffeescript/wordy/example.coffee
@@ -16,19 +16,20 @@
     Object.keys @BINARY_OPERATORS

   pattern: ->
-    operations  = " (#{@operators().join('|')}) "
+    operations = " (#{@operators().join('|')}) "

-    expression  = ''
-    expression += '(?:what is ([-+]?[\\d]+)'
-    expression += operations
-    expression += '([-+]?[\\d]+)(?:'
-    expression += operations
-    expression += '([-+]?[\\d]+))?)'
+    /// (?:
+      what[\x20]is[\x20] # what is
+      ([-+]?[\d]+)       # +/- number
+      #{operations}      # operator (plus, minus, etc.)
+      ([-+]?[\d]+)       # +/- number
+      (?:                # optional extra group
+        #{operations}    # operator (plus, minus, etc.)
+        ([-+]?[\d]+)     # +/- number
+      )?                 # end optional extra group
+    ) ///i

-    new RegExp(expression, 'i')
-
-  tooComplicated: ->
-    not @matches
+  tooComplicated: -> not @matches

   answer: =>
     throw @ERROR.tooComplicated if @tooComplicated()


open and save files as utf-8. fixes #23

open and save files as utf-8. fixes #23

--- src/fs.coffee
+++ src/fs.coffee
@@ -56,12 +56,17 @@

   # Open, read, and close a file, returning the file's contents.
   read: (path) ->
-    OSX.NSString.stringWithContentsOfFile(@absolute path).toString()
+    path = @absolute path
+    enc  = OSX.NSUTF8StringEncoding
+    OSX.NSString.stringWithContentsOfFile_encoding_error(path, enc, null)
+    .toString()

   # Open, write, flush, and close a file, writing the given content.
   write: (path, content) ->
-    str = OSX.NSString.stringWithString content
-    str.writeToFile_atomically @absolute(path), true
+    str  = OSX.NSString.stringWithUTF8String content
+    path = @absolute path
+    enc  = OSX.NSUTF8StringEncoding
+    str.writeToFile_atomically_encoding_error path, true, enc, null

   # Return the path name of the current working directory.
   workingDirectory: ->


Correctly check if a process is running.

--- src/cache/global.coffee
+++ src/cache/global.coffee
@@ -11,9 +11,9 @@

   # If pinging the server throws an error (ESRCH), then the process isn't running.
   catch ersch
-    return true
+    return false

-  return false
+  return true

 class Global extends Impromptu.Cache


refactor welcome page, + add Error.clear()

--- app/components/welcome-page/welcome-page.coffee
+++ app/components/welcome-page/welcome-page.coffee
@@ -1,14 +1,12 @@
 module.exports =
   url: '/'
   template: require('./welcome-page.html')
-  controller: ($scope, $auth, $location, Records) ->
+  controller: ($scope, $auth, $location, Records, Error, CurrentUser) ->

-    $scope.loading = true
-    $auth.validateUser()
-      .then ->
-        $scope.redirectToGroupPage()
-      .catch ->
-        $scope.loading = false
+    Error.clear()
+
+    $scope.$on 'auth:validation-success', (ev, user) ->
+      $scope.redirectToGroupPage()

     $scope.login = (formData) ->
       $scope.formError = ""


Handle when in-page entries are cleared

Handle when in-page entries are cleared

--- atom/browser/api/lib/navigation-controller.coffee
+++ atom/browser/api/lib/navigation-controller.coffee
@@ -14,22 +14,28 @@
     @history = []
     @currentIndex = -1
     @pendingIndex = -1
+    @inPageIndex = -1

     @webContents.on 'navigation-entry-commited', (event, url, inPage, replaceEntry) =>
-      console.log 'navigation-entry-commited', url, inPage, replaceEntry
+      if @inPageIndex > -1 and not inPage
+        # Navigated to a new page, clear in-page mark.
+        @inPageIndex = -1
+      else if @inPageIndex is -1 and inPage
+        # Started in-page navigations.
+        @inPageIndex = @currentIndex

       if @pendingIndex >= 0 # Go to index.
         @currentIndex = @pendingIndex
         @pendingIndex = -1
-        @history[@currentIndex] = {url, inPage}
+        @history[@currentIndex] = url
       else if replaceEntry # Non-user initialized navigation.
-        @history[@currentIndex] = {url, inPage}
+        @history[@currentIndex] = url
       else  # Normal navigation.
         @history = @history.slice 0, @currentIndex + 1  # Clear history.
         currentEntry = @history[@currentIndex]
-        if currentEntry?.url isnt url or currentEntry?.inPage isnt inPage
+        if currentEntry?.url isnt url
           @currentIndex++
-          @history.push {url, inPage}
+          @history.push url

   loadUrl: (url, options={}) ->
     @pendingIndex = -1
@@ -39,7 +45,7 @@
     if @currentIndex is -1
       ''
     else
-      @history[@currentIndex].url
+      @history[@currentIndex]

   stop: ->
     @pendingIndex = -1
@@ -68,20 +74,18 @@
   goBack: ->
     return unless @canGoBack()
     @pendingIndex = @getActiveIndex() - 1
-    pendingEntry = @history[@pendingIndex]
-    if pendingEntry.inPage
+    if @inPageIndex > -1 and @pendingIndex >= @inPageIndex
       @webContents._goBack()
     else
-      @webContents._loadUrl pendingEntry.url, {}
+      @webContents._loadUrl @history[@pendingIndex], {}

   goForward: ->
     return unless @canGoForward()
     @pendingIndex = @getActiveIndex() + 1
-    pendingEntry = @history[@pendingIndex]
-    if pendingEntry.inPage
+    if @inPageIndex > -1 and @pendingIndex >= @inPageIndex
       @webContents._goForward()
     else
-      @webContents._loadUrl pendingEntry.url, {}
+      @webContents._loadUrl @history[@pendingIndex], {}

   goToIndex: (index) ->
     return unless @canGoToIndex index


Fix VcsUrlMixin to return empty string for project name on null regex match.

--- js/inner/_obj.coffee
+++ js/inner/_obj.coffee
@@ -46,7 +46,8 @@
   obj.observables.vcs_url = obj.vcs_url

   obj.project_name = obj.komp ->
-    obj.vcs_url().match("^https?://[^/]+/(.*)")[1]
+    m = obj.vcs_url().match("^https?://[^/]+/(.*)")
+    if (m == null) then "" else m[1]

   # slashes aren't allowed in github org/user names or project names
   obj.org_name = obj.komp ->


Turn down the volume. D:

Turn down the volume. D:

--- avalonstar/assets/javascripts/notifier.coffee
+++ avalonstar/assets/javascripts/notifier.coffee
@@ -11,9 +11,9 @@

 # Sounds.
 soundDonation = new Audio('/static/audio/donation.ogg')
-soundDonation.volume = 0.7
+soundDonation.volume = 0.5
 soundSubscription = new Audio('/static/audio/subscription.ogg')
-soundSubscription.volume = 0.7
+soundSubscription.volume = 0.5

 subscribed = (data, added) ->
   if not running


remove conole log

remove conole log

--- js/outer/home.coffee
+++ js/outer/home.coffee
@@ -29,4 +29,3 @@
       headline: "Incredible support"
       teaser: "We respond to support requests immediately, every day. Most requests are responded to within an hour. No-one ever waits more than 12 hours for a response."
     ]
-    testF = console.log 'test'


updates versions.coffee

updates versions.coffee


fix selection of available projections

fix selection of available projections

--- app/scripts/models/choropleth_map_config.coffee
+++ app/scripts/models/choropleth_map_config.coffee
@@ -96,29 +96,9 @@
     ]

     availableProjections: [
-      name: 'USA (only US maps)'
-      value: 'albersUsa'
-    ,
+      # CONFORMAL
       name: 'Mercator'
       value: 'mercator'
-    ,
-      name: 'Orthographical'
-      value: 'orthographic'
-    ,
-      name: 'Plate carrée'
-      value: 'equirectangular'
-    ,
-      name: 'van der Grinten'
-      value: 'vanDerGrinten'
-    ,
-      name: 'Robinson'
-      value: 'robinson'
-    ,
-      name: 'Hammer'
-      value: 'hammer'
-    ,
-      name: 'Baker'
-      value: 'baker'
     ,
       name: 'Conic Conformal'
       value: 'conicConformal'
@@ -126,12 +106,35 @@
       name: 'Cylindrical Stereographic'
       value: 'cylindricalStereographic'
     ,
-      name: 'Eckert'
-      value: 'eckert1'
+      # EQUAL AREA
+      name: 'USA (only US maps)'
+      value: 'albersUsa'
     ,
-      name: 'Hill'
-      value: 'hill'
+      name: 'Conic Equal Area'
+      value: 'conicEqualArea'
     ,
-      name: 'Winkel'
+      name: 'Mollweide'
+      value: 'mollweide'
+    ,
+      # AZIMUTHAL
+      name: 'Orthographical'
+      value: 'orthographic'
+    ,
+      # EQUAL DISTANCE
+      name: 'Plate carrée'
+      value: 'equirectangular'
+    ,
+      # EQUAL SHORTEST DISTANCE
+      name: 'Gnomonic'
+      value: 'gnomonic'
+    ,
+      # COMPROMISE PROJECTIONS
+      name: 'Robinson'
+      value: 'robinson'
+    ,
+      name: 'Winkel triple'
       value: 'winkel3'
+    ,
+      name: 'van der Grinten'
+      value: 'vanDerGrinten'
     ]


Force Atom to use real tabs when editing Python

--- config.cson
+++ config.cson
@@ -117,6 +117,9 @@
 	editor:
 		softTabs: true
 		tabLength: 2
+".python.source":
+	editor:
+		softTabs: false
 ".ruby.source":
 	editor:
 		tabLength: 2


ensure valid project name

ensure valid project name

--- src/tf.coffee
+++ src/tf.coffee
@@ -115,6 +115,9 @@
     action = msg.match[1]
     projname = msg.match[2].replace /\//, "_"

+    unless fs.existsSync("#{basepath}/#{projname}")
+      return msg.send {room: msg.message.user.name}, "Invalid project name."
+
     exec "cd #{basepath}/#{projname}; terraform #{action} -no-color", (error, stdout, stderr) ->
       if stderr
         msg.send {room: msg.message.user.name}, "stderr:\n```\n#{stderr}\n```"
@@ -129,6 +132,9 @@

     action = msg.match[1]
     projname = msg.match[2].replace /\//, "_"
+
+    unless fs.existsSync("#{basepath}/#{projname}")
+      return msg.send {room: msg.message.user.name}, "Invalid project name."

     exec "cd #{basepath}/#{projname}; terraform #{action} -input=false -no-color", (error, stdout, stderr) ->
       if stderr
@@ -146,6 +152,9 @@
     ekey = msg.match[2]
     evalue = msg.match[3]

+    unless fs.existsSync("#{basepath}/#{projname}")
+      return msg.send {room: msg.message.user.name}, "Invalid project name."
+
     return msg.send {room: msg.message.user.name}, "`#{projname}` env set: `#{ekey}` = `#{evalue}`"

   robot.respond /tf env ([^\s]+) unset ([^\s]+)$/i, (msg) ->
@@ -155,4 +164,7 @@
     projname = msg.match[1].replace /\//, "_"
     ekey = msg.match[2]

+    unless fs.existsSync("#{basepath}/#{projname}")
+      return msg.send {room: msg.message.user.name}, "Invalid project name."
+
     return msg.send {room: msg.message.user.name}, "`#{projname}` env unset: `#{ekey}`"


Allowed for streaming image data to upload.

--- src/wx.coffee
+++ src/wx.coffee
@@ -610,7 +610,7 @@
         @send message "<MsgType><![CDATA[image]]></MsgType>
           <Image><MediaId><![CDATA[#{image}]]></MediaId></Image>"

-      if image.match(regex_media_id)
+      if typeof image == 'string' and image.match(regex_media_id)
         send image
       else
         wx.upload 'image', image, (err, res) =>


Use clearer names for times of matches

Use clearer names for times of matches

--- srcomp.coffee
+++ srcomp.coffee
@@ -20,15 +20,20 @@
     num: match.num
     type: match.type
     teams: match.teams
-    start: moment(match.start_time)
-    end: moment(match.end_time)
-    begin: moment(match.start_time).add(configuration.MATCH_START_OFFSET,
+    times:
+      period:
+        start: moment(match.start_time)
+        end: moment(match.end_time)
+      game:
+        start: moment(match.start_time).add(configuration.MATCH_START_OFFSET,
+                                            's')
+        end: moment(match.end_time).add(configuration.MATCH_START_OFFSET + 180,
                                         's')

 _calculateCurrentMatch = (matches) ->
   now = moment()
   active = (match) ->
-    match.start.isBefore(now) and match.end.isAfter(now)
+    match.times.period.start.isBefore(now) and match.times.period.end.isAfter(now)
   match for match in matches when active(match)

 class SRComp


Check for .war extension from isPathSupported

--- spec/common-spec.coffee
+++ spec/common-spec.coffee
@@ -27,6 +27,7 @@
     it "returns true for supported path extensions", ->
       expect(archive.isPathSupported('/a.zip')).toBe true
       expect(archive.isPathSupported('/a.jar')).toBe true
+      expect(archive.isPathSupported('/a.war')).toBe true
       expect(archive.isPathSupported('/a.tar')).toBe true
       expect(archive.isPathSupported('/a.tgz')).toBe true
       expect(archive.isPathSupported('/a.tar.gz')).toBe true


Re-throw XPathExceptions in order to give a more reasonable error message.

--- application/chrome/content/wesabe/xpath/Pathway.coffee
+++ application/chrome/content/wesabe/xpath/Pathway.coffee
@@ -23,13 +23,19 @@
   # @public
   #
   first: (document, scope) ->
-    result = document.evaluate(
-               wesabe.untaint(@value),
-               wesabe.untaint(scope || document),
-               null,
-               XPathResult.ANY_TYPE,
-               null,
-               null)
+    try
+      result = document.evaluate(
+                 wesabe.untaint(@value),
+                 wesabe.untaint(scope || document),
+                 null,
+                 XPathResult.ANY_TYPE,
+                 null,
+                 null)
+    catch err
+      if err instanceof XPathException
+        throw new Error("#{err.message} (XPath = #{@value})")
+
+      throw err

     return result && wesabe.taint(result.iterateNext())


force middleware title

force middleware title

--- spec/notice_spec.coffee
+++ spec/notice_spec.coffee
@@ -19,12 +19,12 @@

         send = notice.create 'origin name'

-        send.use (done, msg) ->
+        send.use title: 'testing 1', (done, msg) ->
             msg._type.should.equal 'event'
             msg.modified = true
             done()

-        send.use (done, msg) ->
+        send.use title: 'testing 2', (done, msg) ->
             throw 'π' if msg.thisFails
             done()


fix(spec): remove unused spec helpers

fix(spec): remove unused spec helpers

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -334,11 +334,6 @@

 addCustomMatchers = (spec) ->
   spec.addMatchers
-    toBeInstanceOf: (expected) ->
-      notText = if @isNot then " not" else ""
-      this.message = => "Expected #{jasmine.pp(@actual)} to#{notText} be instance of #{expected.name} class"
-      @actual instanceof expected
-
     toHaveLength: (expected) ->
       if not @actual?
         this.message = => "Expected object #{@actual} has no length method"
@@ -347,28 +342,6 @@
         notText = if @isNot then " not" else ""
         this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
         @actual.length == expected
-
-    toExistOnDisk: (expected) ->
-      notText = this.isNot and " not" or ""
-      @message = -> return "Expected path '" + @actual + "'" + notText + " to exist."
-      fs.existsSync(@actual)
-
-    toHaveFocus: ->
-      notText = this.isNot and " not" or ""
-      if not document.hasFocus()
-        console.error "Specs will fail because the Dev Tools have focus. To fix this close the Dev Tools or click the spec runner."
-
-      @message = -> return "Expected element '" + @actual + "' or its descendants" + notText + " to have focus."
-      element = @actual
-      element = element.get(0) if element.jquery
-      element is document.activeElement or element.contains(document.activeElement)
-
-    toShow: ->
-      notText = if @isNot then " not" else ""
-      element = @actual
-      element = element.get(0) if element.jquery
-      @message = -> return "Expected element '#{element}' or its descendants#{notText} to show."
-      element.style.display in ['block', 'inline-block', 'static', 'fixed']

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


Imporve rename.

Imporve rename.

--- src/FileManager/FileDirective.coffee
+++ src/FileManager/FileDirective.coffee
@@ -123,18 +123,16 @@
       # -------------------Rename-----------------------
       scope.changeName = ($event = {}, force) ->
         if force or $event.keyCode == 13
-          found = false
-          for file in fileManager.fileTree
-            if file.metadata.name == scope.newName and
-            file._id  != scope.file._id
-              found = true
-              break
+          # If the name is the same than the actual
+          if scope.file.metadata.name == scope.newName
+            scope.file.nameEditable = false
+            return false

-          if not found
+          name = fileManager.uniqueName(scope.newName)
+          if name == scope.newName
             scope.file.rename(scope.newName)
             scope.file.nameEditable = false
           else
-            name = fileManager.uniqueName(scope.newName)
             Prompt.ask('This name already exist', "#{name} ?").then(
               ->
                 scope.newName = name


Update ja.coffee (#6044)

Update ja.coffee (#6044)

Several translations

--- app/locale/ja.coco.coffee
+++ app/locale/ja.coco.coffee
@@ -5,7 +5,7 @@
 #    meta_keywords: "CodeCombat, python, javascript, Coding Games"
     meta_description: "プログラミングゲームを通してコードを学ぼう。Python、JavaScript、HTMLをパズルを解くように学んで、あなた自身のゲームやウェブサイト作りを学びましょう。"
 #    meta_og_url: "https://codecombat.com"
-#    become_investor: "to become an investor in CodeCombat"
+    become_investor: "CodeCombatの投資家になる"
 #    built_for_teachers_title: "A Coding Game Built with Teachers in Mind"
 #    built_for_teachers_blurb: "Teaching kids to code can often feel overwhelming. CodeCombat helps all educators teach students how to code in either JavaScript or Python, two of the most popular programming languages. With a comprehensive curriculum that includes six computer science units and reinforces learning through project-based game development and web development units, kids will progress on a journey from basic syntax to recursion!"
 #    built_for_teachers_subtitle1: "Computer Science"
@@ -34,7 +34,7 @@
 #    classroom_in_box_blurb3: "Inspire all school leaders to create a world-class computer science program."
 #    classroom_in_box_blurb4: ""
 #    covid19_banner_blurb: "We are offering free access to our full curriculum to educators and schools affected by school closures."
-#    click_here: "Click here"
+    click_here: "ここをクリック"
 #    creativity_rigor_title: "Where Creativity Meets Rigor"
 #    creativity_rigor_subtitle1: "Make coding fun and teach real-world skills"
 #    creativity_rigor_blurb1: "Students type real Python and JavaScript while playing games that encourage trial-and-error, critical thinking, and creativity. Students then apply the coding skills they’ve learned by developing their own games and websites in project-based courses."
@@ -215,7 +215,7 @@
   modal:
     close: "閉じる"
     okay: "OK"
-#    cancel: "Cancel"
+    cancel: "取り消し"

   not_found:
     page_not_found: "ページが見つかりません"
@@ -869,9 +869,9 @@
     restricted: "(このレベルでは制限品)"
     equip: "装備する"
     unequip: "装備を外す"
-#    warrior_only: "Warrior Only"
-#    ranger_only: "Ranger Only"
-#    wizard_only: "Wizard Only"
+    warrior_only: "Warrior のみ"
+    ranger_only: "Ranger のみ"
+    wizard_only: "Wizard のみ"

   buy_gems:
     few_gems: "少し購入"