mp4: Add DEBUG flag

mp4: Add DEBUG flag

--- mp4.coffee
+++ mp4.coffee
@@ -11,6 +11,8 @@
 READ_BUFFER_TIME = 3.0
 QUEUE_BUFFER_TIME = 1.5

+DEBUG = false
+
 getCurrentTime = ->
   time = process.hrtime()
   return time[0] + time[1] / 1e9
@@ -21,18 +23,21 @@
       @open filename

   open: (filename) ->
-    startTime = process.hrtime()
+    if DEBUG
+      startTime = process.hrtime()
     @fileBuf = fs.readFileSync filename  # up to 1GB
     @bits = new Bits @fileBuf
-    diffTime = process.hrtime startTime
-    console.log "took #{(diffTime[0] * 1e9 + diffTime[1]) / 1000000} ms to read"
+    if DEBUG
+      diffTime = process.hrtime startTime
+      console.log "took #{(diffTime[0] * 1e9 + diffTime[1]) / 1000000} ms to read"

   close: ->
     @bits = null

   parse: ->
-    startTime = process.hrtime()
     @tree = { boxes: [] }
+    if DEBUG
+      startTime = process.hrtime()
     @boxes = []
     while @bits.has_more_data()
       box = Box.parse @bits, null  # null == root box
@@ -43,9 +48,10 @@
 #      process.stdout.write box.dump 0, 1
       @tree.boxes.push box.getTree()
       @boxes.push box
-    diffTime = process.hrtime startTime
-    console.log "took #{(diffTime[0] * 1e9 + diffTime[1]) / 1000000} ms to parse"
-    console.log "EOF"
+    if DEBUG
+      diffTime = process.hrtime startTime
+      console.log "took #{(diffTime[0] * 1e9 + diffTime[1]) / 1000000} ms to parse"
+      console.log "EOF"

     for child in @moovBox.children
       if child instanceof TrackBox  # trak


When showing/hiding read entries and other actions that refresh feeds, also refresh folders.

Better to make sure that folders are kept in sync between client and server. Most of the time a 304 will be returned, which is a small load on the server.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -228,7 +228,7 @@
       entriesPaginationSvc.reset_entries()
       $rootScope.feeds_loaded = false
       feedsPaginationSvc.set_busy false
-      load_feeds()
+      load_data()

     #---------------------------------------------
     # Set to false a flag that makes only feeds with unread entries and
@@ -241,7 +241,7 @@
       $rootScope.feeds_loaded = false
       cleanupSvc.hide_read_feeds()
       feedsPaginationSvc.set_busy false
-      load_feeds()
+      load_data()

     #---------------------------------------------
     # Load feeds and folders via AJAX into the root scope.
@@ -291,7 +291,7 @@
     load_folder_feeds: (folder)->
       # If passed folder is "all", load all feeds in a paginated fashion.
       if folder=="all"
-        load_feeds()
+        load_data()
       # If any other folder is passed, load feeds in that folder only (not paginated)
       else
         load_folder_feeds folder


fix bug

fix bug

--- site/app.coffee
+++ site/app.coffee
@@ -285,27 +285,26 @@
       if loc?
         $('#canvas_map').css('cursor','pointer')
         @renderMap()
-        if loc != 'AK' and loc != 'HI'
-          ctx = @canvasMap[0].getContext('2d')
-          ctx.font = 12 + 'px sans-serif'
-          ctx.fillStyle = '#eee'
-          current = @mapData[loc]
-          ili = '' + (Math.round(current.value * 100) / 100)
-          if '.' in ili
-            ili += '00'
-            idx = ili.indexOf('.')
-            ili = ili.slice(0, idx + 3)
-          else
-            ili += '.00'
-          std = '' + (Math.round(current.std * 100) / 100)
-          if '.' in std
-            std += '00'
-            idx = std.indexOf('.')
-            std = std.slice(0, idx + 3)
-          else
-            std += '.00'
-          ili = '(' + ili + '±' + std + ')%'
-          ctx.fillText(loc+" "+ili+"", e.offsetX, e.offsetY)
+        ctx = @canvasMap[0].getContext('2d')
+        ctx.font = 12 + 'px sans-serif'
+        ctx.fillStyle = '#eee'
+        current = @mapData[loc]
+        ili = '' + (Math.round(current.value * 100) / 100)
+        if '.' in ili
+          ili += '00'
+          idx = ili.indexOf('.')
+          ili = ili.slice(0, idx + 3)
+        else
+          ili += '.00'
+        std = '' + (Math.round(current.std * 100) / 100)
+        if '.' in std
+          std += '00'
+          idx = std.indexOf('.')
+          std = std.slice(0, idx + 3)
+        else
+          std += '.00'
+        ili = '(' + ili + '±' + std + ')%'
+        ctx.fillText(loc+" "+ili+"", e.offsetX, e.offsetY)
       else
         $('#canvas_map').css('cursor','auto')
         @renderMap())


clearer docstring

clearer docstring

--- web-path.coffee
+++ web-path.coffee
@@ -1,11 +1,16 @@
 ###
  copyright (c) 2014 Jess Austin <jess.austin@gmail.com>, MIT license

- Node's built-in path module doesn't deal with trailing slashes the way that
- web browsers and servers do. This module provides three functions: join,
- resolve, and relative. Each of these is equivalent to its respective function
- in the built-in path module, except that it does deal with trailing slashes
- the way that web browsers and servers do.
+ Node's built-in path module doesn't know how trailing slashes are used on the
+ web. The replacement functions in this module know how to deal with trailing
+ slashes. For example:
+
+    > path.resolve('/a/b/c', 'd.html');
+    '/a/b/c/d.html'
+    > webPath.resolve('/a/b/c', 'd.html');
+    '/a/b/d.html'
+    > webPath.resolve('/a/b/c/', 'd.html');
+    '/a/b/c/d.html'
 ###

 path = require 'path'
@@ -13,8 +18,8 @@
 # replace anything other than two periods after the last slash with a single
 # period
 trimBackToSlash = (str) ->    # this would be a one-liner if we had look-behind
-  str.replace /(?:^)(?!\.\.$)[^/]*$/, '.'         # there was no slash
-    .replace /(?:\/)(?!\.\.$)[^/]*$/, '/.'        # keep the slash
+  str.replace /(?:^)(?!\.\.$)[^/]*$/, '.'                  # there was no slash
+    .replace /(?:\/)(?!\.\.$)[^/]*$/, '/.'                 # keep the slash

 module.exports =
   join: (paths...) ->


Update Tests

Update Tests

--- test/tests/TextLayerTest.coffee
+++ test/tests/TextLayerTest.coffee
@@ -18,7 +18,6 @@
 			text.fontWeight.should.equal 400
 			text.lineHeight.should.equal 1.25
 			text.fontStyle.should.equal "normal"
-			text.size.should.eql width: 161, height: 50
 			text.style.fontFamily.should.equal "-apple-system, 'SF UI Text', 'Helvetica Neue'"

 		it "should not set the default fontFamily default if the fontFamily property is set", ->
@@ -35,17 +34,20 @@

 		it "should auto size the layer the size of the text", ->
 			text = new TextLayer
+				fontFamily: "Arial"
 				text: shortText
-			text.size.should.eql width: 267, height: 50
+			text.size.should.eql width: 249, height: 50

 		it "should auto size the layer based on the Screen width", ->
 			text = new TextLayer
+				fontFamily: "Arial"
 				text: mediumText
 			text.width.should.equal Screen.width
 			text.height.should.equal 150

 		it "should auto size the layer if the width is set explicitly", ->
 			text = new TextLayer
+				fontFamily: "Arial"
 				text: mediumText
 				width: 100
 			text.width.should.equal 100
@@ -53,6 +55,7 @@

 		it "should not auto size the layer the size the layer if it is explictly set", ->
 			text = new TextLayer
+				fontFamily: "Arial"
 				text: mediumText
 				width: 123
 				height: 456
@@ -60,12 +63,14 @@

 		it "should not auto size the layer when changing text after explictly setting width", ->
 			text = new TextLayer
+				fontFamily: "Arial"
 			text.width = 123
 			text.text = longText
 			text.width.should.equal 123

 		it "should not auto size the layer when changing text after explictly setting height", ->
 			text = new TextLayer
+				fontFamily: "Arial"
 			text.height = 456
 			text.text = longText
 			text.height.should.equal 456
@@ -73,23 +78,26 @@
 		it "should auto size the layer based on it's parent", ->
 			layer = new Layer width: 150
 			text = new TextLayer
+				fontFamily: "Arial"
 				text: mediumText
 				parent: layer
 			text.width.should.equal 150
-			text.height.should.equal 550
+			text.height.should.equal 500

 		it "should auto size the layer when its parent is set afterwards", ->
 			layer = new Layer width: 150
 			text = new TextLayer
+				fontFamily: "Arial"
 				text: mediumText
 			text.parent = layer
 			text.width.should.equal 150
-			text.height.should.equal 550
+			text.height.should.equal 500

 		it "should adjust its size on when a new text is set", (done) ->
 			text = new TextLayer
+				fontFamily: "Arial"
 			text.on "change:height", ->
-				text.size.should.eql width: 400, height: 2750
+				text.size.should.eql width: 400, height: 2600
 				done()
 			text.text = longText


Adding cache-control header to prevent browser from caching redirect responses

--- src/server/http_server.coffee
+++ src/server/http_server.coffee
@@ -63,7 +63,7 @@
                 bserver = browsers.create(app)
                 id = req.session.browserID = bserver.id
             res.writeHead 301,
-                'Location' : "#{mountPointNoSlash}/browsers/#{id}/index.html"
+                {'Location' : "#{mountPointNoSlash}/browsers/#{id}/index.html",'Cache-Control' : "max-age=0, must-revalidate"}
             res.end()

         # Route to connect to a virtual browser.


project/metrics: number of processes, fixing some output

--- src/smc-project/kucalc.coffee
+++ src/smc-project/kucalc.coffee
@@ -48,16 +48,19 @@

 exports.compute_status = compute_status = (cb) ->
     status =
-        memory   : {rss: 0}
-        disk_MB  : 0
-        cpu      : {}
-        start_ts : start_ts
+        memory    : {rss: 0}
+        disk_MB   : 0
+        cpu       : {}
+        start_ts  : start_ts
+        processes : {}
     async.parallel([
         (cb) ->
             compute_status_disk(status, cb)
         (cb) ->
             #compute_status_memory(status, cb)
             cgroup_stats(status, cb)
+        (cb) ->
+            processes_info(status, cb)
         (cb) ->
             compute_status_tmp(status, cb)
     ], (err) ->
@@ -68,6 +71,24 @@
     disk_usage "$HOME", (err, x) ->
         status.disk_MB = x
         cb(err)
+
+processes_info = (status, cb) ->
+    cols = ['pid','lstart','time','rss','args']
+    misc_node.execute_code
+        command : 'ps'
+        args    : ['--no-header', '-o', cols.join(','), '-u', 'user']
+        bash    : false
+        cb      : (err, out) ->
+            if err or out.exit_code != 0
+                cb(err)
+            else
+                cnt = -1  # no need to account for the ps process itself!
+                # TODO parsing anything out of ps is really hard :-(
+                for line in out.stdout.split('\n')
+                    if line.length > 0
+                        cnt += 1
+                status.processes.count = cnt
+                cb()

 # NOTE: we use tmpfs for /tmp, so RAM usage is the **sum** of /tmp and what
 # processes use.
@@ -182,10 +203,13 @@
     kucalc_project_start_time{#{labels}} #{current_status.cpu?.usage ? 0.0}
     # HELP kucalc_project_memory_usage_ki
     # TYPE kucalc_project_memory_usage_ki gauge
-    kucalc_project_memory_usage_ki #{current_status.memory?.rss ? 0.0}
+    kucalc_project_memory_usage_ki{#{labels}} #{current_status.memory?.rss ? 0.0}
     # HELP kucalc_project_memory_limit_ki
     # TYPE kucalc_project_memory_limit_ki gauge
-    kucalc_project_memory_limit_ki #{current_status.memory?.limit ? 0.0}
+    kucalc_project_memory_limit_ki{#{labels}} #{current_status.memory?.limit ? 0.0}
+    # HELP kucalc_project_running_processes_total
+    # TYPE kucalc_project_running_processes_total gauge
+    kucalc_project_running_processes_total{#{labels}} #{current_status.processes?.count ? 0}
     """

 # called inside raw_server


screw these balls

screw these balls

--- src/Ball.coffee
+++ src/Ball.coffee
@@ -29,13 +29,16 @@
     @game.physics.arcade.enable(@sprite)
     if !@isRemote
       @onGainOwnership()
+    @sprite.rotation = 0 if isNaN(@sprite.rotation)

   onLoseOwnership:->
+    @sprite.rotation = 0 if isNaN(@sprite.rotation)
     @sprite.body.drag.set(0, 0)
     @sprite.body.collideWorldBounds = false
     @sprite.body.bounce.set(0, 0)

   onGainOwnership:->
+    @sprite.rotation = 0 if isNaN(@sprite.rotation)
     @sprite.body.drag.set(DRAG, DRAG)
     @sprite.body.collideWorldBounds = true
     @sprite.body.bounce.set(0.9, 0.9)
@@ -50,6 +53,7 @@
     Date.now() - @kickTime

   setState:(state)->
+    @sprite.rotation = 0 if isNaN(@sprite.rotation)
     if !@spawned
       @sprite.position.x = state.x
       @sprite.position.y = state.y
@@ -68,15 +72,14 @@
     @angular = state.angular
     @angle = state.angle
     if @sprite.body?
-      @sprite.rotation = 0 if _.isNaN?(@sprite.rotation)
       @sprite.animations.play(@anim)
       @sprite.body.angularVelocity = @angular
       if @angular == 0
         @sprite.angle = @angle

   getState:->
+    @sprite.rotation = 0 if isNaN(@sprite.rotation)
     if @sprite.body?
-      @sprite.rotation = 0 if _.isNaN?(@sprite.rotation)
       @sprite.animations.play(@anim)
       @sprite.body.angularVelocity = @angular
       if @angular == 0
@@ -139,7 +142,9 @@
         @angle = 180
     else
       @angular = velocity.x * 7
-      @angle = @sprite.angle
+      @angle = 0
+      if !isNaN(@sprite.angle)
+        @angle = @sprite.angle
       if Math.abs(@angular) < 10
         @angular = 0
         @angle *= 0.4


es6 prep #norelease

es6 prep #norelease

--- resource/bibtex/reference.coffee
+++ resource/bibtex/reference.coffee
@@ -643,7 +643,7 @@
     else
       value = ('' + text).replace(/([\\{}])/g, '\\$1')
     # TODO: @Exporter.unicode -> Translator.unicode ?
-    value = value.replace(/[^\x21-\x7E]/g, ((chr) -> '\\%' + ('00' + chr.charCodeAt(0).toString(16).slice(-2)))) if not @Exporter.unicode
+    value = value.replace(/[^\x21-\x7E]/g, ((chr) -> "\\%#{'00' + chr.charCodeAt(0).toString(16).slice(-2)}" )) if not @Exporter.unicode
     return value

   hasCreator: (type) -> (@item.creators || []).some((creator) -> creator.creatorType == type)


add alias method

add alias method

--- src/Code.coffee
+++ src/Code.coffee
@@ -431,6 +431,18 @@
 }

 /**
+ * //alias
+ * This method posts a message to a channel.
+ * @param {string} channelId Channel to send message to. Can be a public channel, private group or IM channel. Can be an encoded ID, or a name.
+ * @param {string} text Text of the message to send. See below for an explanation of formatting.
+ * @param {Object} option optional please see https://api.slack.com/methods/chat.postMessage
+ * @return {Object} api result
+ */
+function postMessage(channelId, text , option) {
+    throw new Error("this method should not call direct, please call create method.")
+}
+
+/**
  * This method updates a message in a channel.
  * @param {string} channelId Channel containing the message to be updated.
  * @param {Number} timestamp Timestamp of the message to be updated.
@@ -721,18 +733,15 @@
     throw new Error("this method should not call direct, please call create method.")
 }
 `
-do(exports=@)=>
+do(exports=@)->
     class SlackApp
         constructor : (@team, @clientId, @clientSecret, @scopes,@option={})->
             @BASE_URI="https://slack.com"
             @API_ENDPOINT="#{@BASE_URI}/api/"
             @AUTH_ENDPOINT="#{@BASE_URI}/oauth/authorize"
             @prop = PropertiesService.getUserProperties()
-
             @option.name = "" if !@option.name
             @prop.setProperty("SLACK_credencial#{@option.name}", JSON.stringify({"access_token" : @option.token})) if @option.token
-
-
         apiTest : (params={})=>
             @fetch_("api.test", params)

@@ -775,6 +784,9 @@

         chatPostMessage : (channelId, text , option={})=>
             @fetch_("chat.postMessage", _.extend({channel : channelId, text : text}, option))
+
+        postMessage : (channelId, text , option={})=>
+            @chatPostMessage(channelId, text , option)

         chatUpdate : (channelId, timestamp, text)=>
             @fetch_("chat.update", {channel : channelId, text : text, ts : timestamp})
@@ -875,12 +887,10 @@

         getCredencial_ : (name)=>
             return @cache[name] if @cache && @cache[name]
-
             credencial = @prop.getProperty("SLACK_credencial#{@option.name}")
             return null if !credencial?
             @cache = JSON.parse credencial
             return @cache[name]
-
         # authorize : ()=>
         #     res = @uox => UrlFetchApp.fetch "#{@API_ENDPOINT}/oauth.access" ,
         #         method : "post"
@@ -890,7 +900,6 @@
         #             scope : @scopes.join(",")
         #     @prop.setProperty("SLACK_credencial#{@option.name}", res.getContentText())
         #     return JSON.parse(res.getContentText()).access_token
-
         saveAccessToken : (code)=>
             res = @uox => UrlFetchApp.fetch "#{@API_ENDPOINT}oauth.access" ,
                 method : "post"
@@ -901,13 +910,10 @@
                     code : code
             @prop.setProperty("SLACK_credencial#{@option.name}", res.getContentText())
             return JSON.parse(res.getContentText()).access_token
-
         getAuthorizeUrl : (callback,optArg) =>
             redirectUri = @getCallbackURL(callback, optArg)
             @prop.setProperty("SLACK_redirectURI#{@option.name}", redirectUri)
             "#{@AUTH_ENDPOINT}?client_id=#{@clientId}&scope=#{@scopes.join(',')}&response_type=code&redirect_uri=#{redirectUri}"
-
-
         getCallbackURL : (callback,optArg = {}) ->
             url = ScriptApp.getService().getUrl()
             if url.indexOf("/exec") >= 0
@@ -931,5 +937,4 @@
                 throw e
               Utilities.sleep 1000
               count++
-
     exports.SlackApp = SlackApp


Issue #46: Test for alerts db entry.

Issue #46: Test for alerts db entry.

--- test/unit/alertsTest.coffee
+++ test/unit/alertsTest.coffee
@@ -317,6 +317,20 @@
 					contactSpy.withArgs('email', 'two@openhim.org', plainTemplate, htmlTemplate).calledOnce.should.be.true
 					done()

+		it "should store an alert log item in mongo for each alert generated", (done) ->
+			contactSpy = sinon.spy()
+			testTransactions[0].save (err) ->
+				return done err if err
+				alerts.alertingTask buildJobStub(dateFrom), mockContactHandler(contactSpy), ->
+					contactSpy.called.should.be.true
+					Alert.find {}, (err, results) ->
+						return done err if err
+						results.length.should.be.exactly 2
+						resultUsers = results.map (result) -> result.user
+						resultUsers.should.containEql testUser1.email
+						resultUsers.should.containEql testUser2.email
+						done()
+
 		it "should contact users using their specified method", (done) ->
 			contactSpy = sinon.spy()
 			testTransactions[3].save (err) ->


Removed useless event binding

Removed useless event binding

--- js-library/app/js/jail_iframe/classes/highlight.coffee
+++ js-library/app/js/jail_iframe/classes/highlight.coffee
@@ -26,11 +26,7 @@
     $elements.on 'mouseleave', @_onUnhover

   _onClick: =>
-    FactlinkJailRoot.on 'modalOpened', @_onModalOpened, @
     FactlinkJailRoot.openFactlinkModal @id
-
-  _onModalOpened: ->
-    FactlinkJailRoot.off 'modalOpened', @_onModalOpened, @

   _onHover: =>
     @show_button.startHovering()


delete old autocomplete

delete old autocomplete

--- web/code.coffee
+++ web/code.coffee
@@ -154,72 +154,6 @@
         R.div {key:tag}, (formatAmount(amount) + ': ' + tag)
       '=> ' + formatAmount(total)

-
-AutoC = React.createClass
-  displayName: 'AutoC'
-
-  getInitialState: -> {sel:null, text:'', focus:false}
-
-  render: ->
-    words = @state.text.split(/\s+/)
-    word = words[words.length - 1]
-    @filteredOptions = @props.options.filter (opt) ->
-      word.length > 0 and opt.indexOf(word) == 0
-
-    R.div {className:'autoc'},
-      R.input {ref:'input', autoComplete:false, onChange:@onChange, onKeyDown:@onKeyDown, onFocus:@onFocus, onBlur:@onBlur, value:@state.text}
-      if @filteredOptions.length > 0 and @state.focus
-        R.div {className:'dropdown'},
-          for o, i in @filteredOptions
-            className = 'item'
-            className += ' sel' if i == @state.sel
-            do (o) =>
-              R.div {key:i, className, onMouseDown:(=> @complete(o); return)},
-                o
-
-  onChange: ->
-    text = @refs.input.getDOMNode().value
-    @setState {text}
-    return
-
-  onKeyDown: (e) ->
-    return if e.shiftKey or e.altKey or e.metaKey
-    sel = @state.sel
-    if e.key == 'ArrowDown' or e.key == 'Tab'
-      sel = if sel? then sel + 1 else 0
-    else if e.key == 'ArrowUp'
-      sel-- if sel?
-    else if e.key == 'Enter'
-      if sel?
-        @complete(@filteredOptions[sel])
-        sel = null
-      else if @props.onCommit
-        if @props.onCommit(@state.text)
-          @setState {text:''}
-    else
-      return
-    e.preventDefault()
-    sel = null unless @filteredOptions.length
-    if sel?
-      sel = 0 if sel < 0
-      sel = @filteredOptions.length - 1 if sel >= @filteredOptions.length
-    @setState {sel}
-    return
-
-  onFocus: ->
-    @setState {focus:true}
-    return
-
-  onBlur: ->
-    @setState {sel:null, focus:false}
-    return
-
-  complete: (text) ->
-    words = @state.text.split(/\s+/)
-    words[words.length - 1] = text
-    text = words.join(' ') + ' '
-    @setState {text}
-    return

 window.App = React.createClass
   displayName: 'App'


test DrafterClassStub::make

test DrafterClassStub::make

--- test/unit/dredd-test.coffee
+++ test/unit/dredd-test.coffee
@@ -3,7 +3,7 @@
 proxyquire = require('proxyquire').noCallThru()

 fsStub = require 'fs'
-Drafter = require 'drafter'
+DrafterClassStub = require 'drafter'
 requestStub = require 'request'
 loggerStub = require '../../src/logger'

@@ -11,7 +11,7 @@
 blueprintAstToRuntimeStub = sinon.spy blueprintAstToRuntime

 Dredd = proxyquire '../../src/dredd', {
-  'drafter': Drafter
+  'drafter': DrafterClassStub
   'request': requestStub
   './blueprint-ast-to-runtime': blueprintAstToRuntimeStub
   'fs': fsStub
@@ -24,11 +24,11 @@
   dredd = {}

   beforeEach () ->
-    # sinon.spy Drafter::, 'make'
+    sinon.spy DrafterClassStub::, 'make'
     sinon.spy fsStub, 'readFile'

   afterEach () ->
-    # Drafter::make.restore()
+    DrafterClassStub::make.restore()
     fsStub.readFile.restore()

   describe 'with legacy configuration', () ->
@@ -82,13 +82,12 @@
         dredd.runner.executeTransaction.restore()
         done()

-    it.skip 'should parse blueprint to ast', (done) ->
-      # invalid - because Drafter is always instantiated
-      dredd = new Dredd(configuration)
-      sinon.stub dredd.runner, 'executeTransaction', (transaction, callback) ->
-        callback()
-      dredd.run (error) ->
-        # assert.ok Drafter::make.called
+    it 'should parse blueprint to ast', (done) ->
+      dredd = new Dredd(configuration)
+      sinon.stub dredd.runner, 'executeTransaction', (transaction, callback) ->
+        callback()
+      dredd.run (error) ->
+        assert.ok DrafterClassStub::make.called
         dredd.runner.executeTransaction.restore()
         done()


Fix upper-bound policy example

Fix upper-bound policy example

--- test/upperbound.test.coffee
+++ test/upperbound.test.coffee
@@ -15,7 +15,6 @@
         policyObj = (new Policy()).build()
         policyObj.onGet = (wTgt, name, wRec, dTgt, ret) ->
             funcCall = "#{dTgt.constructor.name.toString()}.#{name}"
-            # add it right before the headers get
             if funcCall == "ServerResponse.writeHead"
                 dTgt.setHeader "X-NodeSentry", "What Else?!"
             return ret


TrollMode: Append troll indicator to username

--- client/Main/CommonViews/linkviews/profilelinkview.coffee
+++ client/Main/CommonViews/linkviews/profilelinkview.coffee
@@ -41,7 +41,7 @@
     # only admin can see troll users
     if KD.checkFlag "super-admin"
       trollField = if @getData().isExempt then " (T)" else ""
-      @troll.updatePartial trollField
+      @troll.updatePartial trollField  if @troll

     super fields

@@ -49,5 +49,5 @@
     {profile} = @getData()
     JView::pistachio.call this,
       if profile.firstName is "" and profile.lastName is ""
-      then "{{#(profile.nickname)}} {{>@troll}}"
-      else "{{#(profile.firstName)+' '+#(profile.lastName)}} {{>@troll}}"
+      then "{{#(profile.nickname)}} {{> @troll}}"
+      else "{{#(profile.firstName)+' '+#(profile.lastName)}} {{> @troll}}"


SidebarMachineBox: Toggle list if machine is mine or accepted.

--- client/app/lib/activity/sidebar/sidebarmachinebox.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinebox.coffee
@@ -63,7 +63,11 @@
     @addSubView @machineItem = new NavigationMachineItem {}, machineData

     @machineItem.on 'click', =>
-      if @isMachineRunning() then @toggleList()
+      if @isMachineRunning()
+        if @machine.isMine() then @toggleList()
+        else
+          if @machine.isApproved() then @toggleList()
+
       else kd.singletons.router.handleRoute @machineItem.machineRoute


Stack template: inUse calculation fix

Stack template: inUse calculation fix

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -50,8 +50,7 @@

     stackTemplates.map (template) ->
       template.isDefault = template._id in (currentGroup.stackTemplates or [])
-      template.inUse = template.isDefault or
-        Boolean stacks.find (stack) -> stack.baseStackId is template._id
+      template.inUse     = Boolean stacks.find (stack) -> stack.baseStackId is template._id

     if viewType is 'group'
       stackTemplates = stackTemplates.filter (template) -> template.accessLevel is 'group'


Fix logs

Fix logs

--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -29,7 +29,7 @@
       isExclusive : yes

     @notificationChannel.on 'message', (notification)=>
-      console.log 'notification arrived'
+      log 'Notification has arrived', notification
       @emit "NotificationHasArrived", notification
       if notification.contents
         @emit notification.event, notification.contents
@@ -38,7 +38,7 @@
     @on 'UsernameChanged', ({username, oldUsername}) ->
       # FIXME: because of this (https://app.asana.com/0/search/6604719544802/6432131515387)
     @on 'GuestTimePeriodHasEnded', ()->
-      #todo add a notification to user
+      # todo add a notification to user
       $.cookie 'clientId', erase: yes

       new KDModalView


MigrateFromSolo: keep active migration info in local storage

--- client/migratefromsolo/lib/migratefromsoloappview.coffee
+++ client/migratefromsolo/lib/migratefromsoloappview.coffee
@@ -7,6 +7,8 @@

 module.exports = class MigrateFromSoloAppView extends kd.ModalView
+
+  ACTIVE_MIGRATION = 'active-migration'

   constructor: (options = {}, data) ->

@@ -19,6 +21,8 @@

     @machines = null
     @credential = null
+
+    @storage = kd.getSingleton('localStorageController').storage 'migrator'

     @mainHeader = new kd.CustomHTMLView
       tagName: 'header'
@@ -62,7 +66,6 @@
         showError err  if err

       @destroy()
-

     @mainFooter = new kd.CustomHTMLView
       tagName: 'footer'
@@ -131,6 +134,7 @@

     { computeController } = kd.singletons
     { slug } = getGroup()
+    eventName = "migrate-#{slug}"

     @stepHeader.updatePartial 'A New Stack is Being Created for Your VMs'
     @stepDescription.updatePartial 'Once your VM’s are transferred they will be located under the stacks menu in a new stack called “Migrated Stack Template”.'
@@ -144,6 +148,19 @@
     @progressBar.show()
     @statusText.show()

+    handler = ({ percentage }) =>
+      @progressBar.updateBar percentage
+
+      if percentage >= 100
+        @storage.unsetKey ACTIVE_MIGRATION
+        computeController.off eventName, handler
+        EnvironmentFlux.actions.loadPrivateStackTemplates().then =>
+          kd.utils.wait 500, @bound 'switchToFinishedView'
+
+    followEvent = ({ eventName, eventId }) =>
+      @storage.setValue ACTIVE_MIGRATION, { eventName, eventId }
+      computeController.eventListener.addListener eventName, eventId
+      computeController.on eventName, handler
     computeController.getKloud().migrate(
       provider: 'aws'
       groupName: slug
@@ -151,20 +168,8 @@
       identifier: @credential.identifier
     ).then ({ eventId }) =>

-      eventName = "migrate-#{slug}"
-
-      splitted = eventId.split '-'
-      computeController.eventListener.addListener eventName, splitted.last
-
-      handler = ({ percentage }) =>
-        @progressBar.updateBar percentage
-
-        if percentage >= 100
-          computeController.off eventName, handler
-          EnvironmentFlux.actions.loadPrivateStackTemplates().then =>
-            kd.utils.wait 500, @bound 'switchToFinishedView'
-
-      computeController.on eventName, handler
+      eventId = (eventId.split '-').last
+      followEvent { eventName, eventId }

     .catch kd.warn


Add some roles used by GMail to link hints, reduce no. of comparisons

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -658,9 +658,12 @@
     isClickable ||= @checkForAngularJs element

     # Check for attributes that make an element clickable regardless of its tagName.
-    if (element.hasAttribute("onclick") or
-        element.getAttribute("role")?.toLowerCase() in ["button" , "tab" , "link"] or
-        element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"])
+    if element.hasAttribute("onclick") or
+        (role = element.getAttribute "role") and role.toLowerCase() in [
+          "button" , "tab" , "link", "checkbox", "menuitem", "menuitemcheckbox", "menuitemradio"
+        ] or
+        (contentEditable = element.getAttribute "contentEditable") and
+          contentEditable.toLowerCase() in ["", "contentEditable", "true"]
       isClickable = true

     # Check for jsaction event listeners on the element.


Simplify filtered hint matching.

Simplify filtered hint matching.

- Reduce the number of passes over hint markers from two to one.
- Filter out non-matching hints before sorting.
- Do not trim the search string twice.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -474,13 +474,13 @@

   # Filter link hints by search string, renumbering the hints as necessary.
   filterLinkHints: (hintMarkers) ->
-    linkSearchString = @linkTextKeystrokeQueue.join("").trim().toLowerCase()
-    do (scoreFunction = @scoreLinkHint linkSearchString) ->
-      linkMarker.score = scoreFunction linkMarker for linkMarker in hintMarkers
-    matchingHintMarkers = hintMarkers[..].sort (a,b) ->
-      if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score
-
-    matchingHintMarkers = (linkMarker for linkMarker in matchingHintMarkers when 0 < linkMarker.score)
+    scoreFunction = @scoreLinkHint @linkTextKeystrokeQueue.join ""
+    matchingHintMarkers = hintMarkers
+      .filter (linkMarker) ->
+        linkMarker.score = scoreFunction linkMarker
+        0 < linkMarker.score
+      .sort (a, b) ->
+        if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score

     if matchingHintMarkers.length == 0 and @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length
       # We don't accept typed text which doesn't match any hints.
@@ -496,7 +496,7 @@
   # Assign a score to a filter match (higher is better).  We assign a higher score for matches at the start of
   # a word, and a considerably higher score still for matches which are whole words.
   scoreLinkHint: (linkSearchString) ->
-    searchWords = linkSearchString.trim().split @splitRegexp
+    searchWords = linkSearchString.trim().toLowerCase().split @splitRegexp
     (linkMarker) =>
       text = linkMarker.linkText.trim()
       linkWords = linkMarker.linkWords ?= text.toLowerCase().split @splitRegexp


get csrf token from /dev/csrf not /register

--- services/web/test/acceptance/coffee/helpers/User.coffee
+++ services/web/test/acceptance/coffee/helpers/User.coffee
@@ -161,13 +161,10 @@

 	getCsrfToken: (callback = (error) ->) ->
 		@request.get {
-			url: "/register"
+			url: "/dev/csrf"
 		}, (err, response, body) =>
 			return callback(err) if err?
-			csrfMatches = body.match("window.csrfToken = \"(.*?)\";")
-			if !csrfMatches?
-				return callback(new Error("no csrf token found"))
-			@csrfToken = csrfMatches[1]
+			@csrfToken = body
 			@request = @request.defaults({
 				headers:
 					"x-csrf-token": @csrfToken


Tabs order; more tests.

Tabs order; more tests.

--- tests/unit_tests/completion_test.coffee
+++ tests/unit_tests/completion_test.coffee
@@ -404,8 +404,19 @@
     @tabRecency = new TabRecency()
     @tabRecency.add 3
     @tabRecency.add 2
+    @tabRecency.add 9
     @tabRecency.add 1
+    @tabRecency.remove 9
     @tabRecency.add 4
+
+  should "have entries for active tabs", ->
+    assert.isTrue @tabRecency.cache[1]
+    assert.isTrue @tabRecency.cache[2]
+    assert.isTrue @tabRecency.cache[3]
+    assert.isTrue @tabRecency.cache[4]
+
+  should "not have entries for removed tabs", ->
+    assert.isFalse @tabRecency.cache[9]

   should "give a high score to the most recent tab", ->
     assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 1
@@ -416,6 +427,17 @@
     assert.isTrue @tabRecency.recencyScore(1) > @tabRecency.recencyScore 4
     assert.isTrue @tabRecency.recencyScore(2) > @tabRecency.recencyScore 4
     assert.isTrue @tabRecency.recencyScore(3) > @tabRecency.recencyScore 4
+
+  should "rank tabs by recency", ->
+    assert.isTrue @tabRecency.recencyScore(3) < @tabRecency.recencyScore 2
+    assert.isTrue @tabRecency.recencyScore(2) < @tabRecency.recencyScore 1
+    @tabRecency.add 3
+    @tabRecency.add 4 # Making 3 the most recent tab which isn't the current tab.
+    assert.isTrue @tabRecency.recencyScore(1) < @tabRecency.recencyScore 3
+    assert.isTrue @tabRecency.recencyScore(2) < @tabRecency.recencyScore 3
+    assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 3
+    assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 1
+    assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 2

 # A convenience wrapper around completer.filter() so it can be called synchronously in tests.
 filterCompleter = (completer, queryTerms) ->


privatemessage: remove delete conversation settings from menu

--- client/Social/Activity/views/privatemessage/settingsview.coffee
+++ client/Social/Activity/views/privatemessage/settingsview.coffee
@@ -37,27 +37,6 @@
           title      : 'Leave'
           callback   : @bound 'leaveConversation'

-    @addMenuItem 'Delete Conversation', =>
-      @deleteModal = KDModalView.confirm
-        title        : 'Are you sure'
-        content      : 'Delete this conversation?'
-        ok           :
-          title      : 'Remove'
-          callback   : @bound 'deleteConversation'
-
-  deleteConversation: ->
-    @prepareModal @deleteModal
-
-    channelId = @getData().getId()
-
-    {channel} = KD.singletons.socialapi
-
-    channel.delete {channelId}, (err) =>
-      return @handleModalError @deleteModal, err
-
-      @deleteModal.destroy()
-      KD.singletons.router.handleRoute '/Activity/Public'
-
   leaveConversation: ->
     @prepareModal @leaveModal


Remove redundant code block

Remove redundant code block

--- client/finder/lib/filetree/controllers/nfindercontroller.coffee
+++ client/finder/lib/filetree/controllers/nfindercontroller.coffee
@@ -48,11 +48,6 @@

     @watchers = {}

-    # this is here for when user login/register after opening Ace App
-    # to refresh filetree
-    mainController = kd.getSingleton("mainController")
-    mainController.on "accountChanged.to.loggedIn", @bound 'reset'
-
     if options.useStorage

       @appStorage.ready =>


Switch the Activity app back to default

Switch the Activity app back to default

--- client/landing/app/MainApp/maincontroller/maincontroller.coffee
+++ client/landing/app/MainApp/maincontroller/maincontroller.coffee
@@ -146,8 +146,8 @@
     mainView = @mainViewController.getView()
     @loginScreen.slideUp =>
       @mainViewController.sidebarController.accountChanged account
-      #appManager.openApplication "Activity", yes
-      appManager.openApplication "Chat", yes
+      appManager.openApplication "Activity", yes
+      #appManager.openApplication "Chat", yes
       @mainViewController.getView().decorateLoginState yes

   goToPage:(pageInfo)=>


try edx openended

try edx openended

--- common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
@@ -238,7 +238,7 @@
           if(options[i].length > 0) {
             var value = options[i].replace(/^\s+|\s+$/g,'');
             var lower_option = value.toLowerCase();
-            type = lower_option.match(/(peer|self|ai)/gmi)
+            type = lower_option.match(/(peer|self|ai|edx)/gmi)
             if(type != null) {
               type = type[0]
               var min_max = value.match(/\{\n?([^\]]*)\}/gmi);
@@ -256,6 +256,9 @@
               } else if (type=="peer") {
                 config = "peer_grading.conf"
                 groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});
+              } else if (type=="edx") {
+                config = "edX"
+                groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});
               } else if (type=="ai") {
                                        config = "ml_grading.conf"
                                        groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});


Change POST for errors

Change POST for errors

--- src/run.coffee
+++ src/run.coffee
@@ -94,7 +94,8 @@
     console.log ''+data
     return
   ls.stderr.on 'data', (data) ->
-    logback({id: id, name: parsed.name, code: '300', location: parsed.public, data: data})
+    @host = 'http://localhost:8080/api/tubs/log'
+    logback({id: id, name: parsed.name, code: '300', location: parsed.public, data: data}, @host, 'POST')
     console.log 'Errors: ' + data
     return
   ls.on 'close', (code) ->


silly fix

silly fix

--- modules/gui/platforms/node/lib/HttpContext.coffee
+++ modules/gui/platforms/node/lib/HttpContext.coffee
@@ -182,11 +182,11 @@
                     return done(err) if err
                     setImmediate ()=>
                         ctx.getCompiledCode (err, script)=>
-                            return done(err) if err
+                            return fn(err) if err

                             if _retry && !(@__SCRIPT = script)
                                 return fn new Error 'libloader problem'
-
+
                             @_createContext fn, true

             ##


Cleaned up filtering, extracted methods

Cleaned up filtering, extracted methods

--- core/app/backbone/views/activities/activities_group_view.coffee
+++ core/app/backbone/views/activities/activities_group_view.coffee
@@ -65,6 +65,16 @@
     UserPopoverContentView.makeTooltip @, @model.user(),
       selector: '.js-activity-group-user'

+  activityMadeRedundantBy: (newActivity, oldActivity) -> false
+  newActivityIsRedundant: (newActivity) ->
+    return false unless @collection.models.length > 1
+    @activityMadeRedundantBy newActivity.get('activity'),
+      @collection.models[@collection.length - 2].get('activity')
+
+  appendHtml: (collectionView, itemView, index) ->
+    return if @newActivityIsRedundant(itemView.model)
+    super
+
 class UserFactActivitiesGroupView extends UserActivitiesGroupView
   template: 'activities/user_fact_activities_group'

@@ -91,7 +101,5 @@
   @actions: ["followed_user"]
   actions: -> UsersFollowedGroupView.actions

-  appendHtml: (collectionView, itemView, index) ->
-    return if @collection.models.length > 1 &&
-      itemView.model.get('activity').followed_user.username == @collection.models[@collection.length - 2].get('activity').followed_user.username
-    super
+  activityMadeRedundantBy: (newActivity, oldActivity) ->
+    newActivity.followed_user.username == oldActivity.followed_user.username


Fix highlighting a FactRelation when added as evidence

--- core/app/backbone/views/fact_relations/add_evidence_view.coffee
+++ core/app/backbone/views/fact_relations/add_evidence_view.coffee
@@ -48,7 +48,7 @@

   createFactRelation: (fact_relation)->
     @hideError()
-    @collection.add fact_relation
+    @collection.add fact_relation, highlight: true
     @inputRegion.switchTo('search_view')
     @inputRegion.getView('search_view').reset()
     fact_relation.save {},


fixed foldUp and unfold

fixed foldUp and unfold

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -371,14 +371,12 @@
     anchor = @_getLonghandAnchor args[1]
     options = extendObj args[2], @_methodDefaults[method]

-    if anchor isnt @lastAnchor or (method is 'foldUp' and @lastAngle isnt 0)
-
+    if anchor isnt @lastAnchor or (method is 'foldUp' and @lastAngle isnt 0) or @isFoldedUp
       @reset =>
         @_showStage anchor

         setTimeout =>
-
-          if method is 'foldUp' or method is 'unfold'
+          if method is 'foldUp'
             args.shift()

           @[method].apply @, args
@@ -472,6 +470,9 @@

   reset: (callback) ->
+    if @isFoldedUp
+      return @unfold callback
+
     for panel, i in @panels[@lastAnchor]
       panel.style[css.transform] = @_transform 0
       if @shading
@@ -583,27 +584,24 @@
     return unless normalized
     anchor = normalized[1]
     @isFoldedUp = true
-    limit = @panels[anchor].length - 1
-
-    for i in [limit..1] by -1
-      delay = (limit - i) * @settings.speed * .6
-      angle = 100
-
-      do (i, delay, angle) =>
-
-        setTimeout =>
-
-          unless i is limit
-            do (i) =>
-              setTimeout =>
-                @panels[anchor][i].style.display = 'none'
-              , delay * .8
-
-          @panels[anchor][i].style[css.transform] = @_transform angle
-          if @shading
-            @_setShader i, anchor, angle
-
-        , delay
+    i = @panels[anchor].length - 1
+    angle = 100
+
+    nextPanel = =>
+      @panels[anchor][i].addEventListener css.transitionEnd, onTransitionEnd, false
+      @panels[anchor][i].style[css.transform] = @_transform angle
+      if @shading
+        @_setShader i, anchor, angle
+
+    onTransitionEnd = (e) =>
+      @panels[anchor][i].removeEventListener css.transitionEnd, onTransitionEnd, false
+      @panels[anchor][i].style.display = 'none'
+      if --i is 0
+        @_callback callback
+      else
+        setTimeout nextPanel, 0
+
+    nextPanel()

   unfold: (callback) ->
@@ -612,20 +610,26 @@
         callback()

     @isFoldedUp = false
-    for panel, i in @panels[@lastAnchor]
-      unless i is 0
-        delay = i * @settings.speed * .6
-        console.log delay
-
-        do (i, delay) =>
-
-          setTimeout =>
-            @panels[@lastAnchor][i].style.display = 'block'
-            @panels[@lastAnchor][i].style[css.transform] = @_transform 0
-            if @shading
-              @_setShader i, @lastAnchor, 0
-
-          , delay
+    i = 1
+    angle = 0
+
+    nextPanel = =>
+      @panels[@lastAnchor][i].style.display = 'block'
+      setTimeout =>
+        @panels[@lastAnchor][i].addEventListener css.transitionEnd, onTransitionEnd, false
+        @panels[@lastAnchor][i].style[css.transform] = @_transform angle
+        if @shading
+          @_setShader i, @lastAnchor, angle
+      , 0
+
+    onTransitionEnd = (e) =>
+      @panels[@lastAnchor][i].removeEventListener css.transitionEnd, onTransitionEnd, false
+      if ++i is @panels[@lastAnchor].length
+        callback()
+      else
+        setTimeout nextPanel, 0
+
+    nextPanel()


set correct perspective origins so 90 transforms can hide element

--- oridomi.coffee
+++ oridomi.coffee
@@ -273,11 +273,14 @@
     stage.style[css.perspective] = @settings.perspective + 'px'
     stage.style[css.transformStyle] = 'preserve-3d'

+    # Each stage needs its own perspective origin so 90 degree folds hide the element.
+    perspectiveOrigins = ['0% 50%', '100% 50%', '50% 0%', '50% 100%']
     # Loop through the anchors list and create a stage and empty panel set for each.
-    for anchor in @_anchors
+    for anchor, n in @_anchors
       @panels[anchor] = []
-      @stages[anchor] = stage.cloneNode false
-      @stages[anchor].className = 'oridomi-stage-' + anchor
+      stage = @stages[anchor] = stage.cloneNode false
+      stage.className = 'oridomi-stage-' + anchor
+      stage.style[css.perspectiveOrigin] = perspectiveOrigins[n]

     # If shading is enabled, create an object literal to hold shaders.
     if @shading


Only remove if event is present

Only remove if event is present

--- components/connect_v2/integration/block_collection/view.coffee
+++ components/connect_v2/integration/block_collection/view.coffee
@@ -25,8 +25,8 @@
     this

   remove: (e) ->
-    e.stopPropagation()
-    e.preventDefault()
+    e?.stopPropagation()
+    e?.preventDefault()

     @trigger 'remove'
     invoke @subViews, 'remove'


fix no indenticons (github avatars) bug

fix no indenticons (github avatars) bug

--- js/inner/user.coffee
+++ js/inner/user.coffee
@@ -22,7 +22,6 @@
     plan: null
     parallelism: 1
     gravatar_id: null
-    github_id: null
     github_oauth_scopes: []
     repo_filter: ""
     unauthenticated_email: null
@@ -69,8 +68,8 @@
       @komp =>
         if @gravatar_id() and @gravatar_id() isnt ""
           url = "https://secure.gravatar.com/avatar/#{@gravatar_id()}?s=#{size}"
-          if @github_id()
-            hash = CryptoJS.MD5(@github_id().toString()).toString()
+          if @login
+            hash = CryptoJS.MD5(@login.toString()).toString()
             d = URI.encode("https://identicons.github.com/#{hash}.png")
             url += "&d=#{d}"
           url


Standardize BaseClient

Standardize BaseClient

 - Event names are now in the past tense
 - Clients should implement #getProfile returning an object like
   { username: 'username', balance: balanceInSatoshis }

--- js/martingala.coffee
+++ js/martingala.coffee
@@ -23,7 +23,7 @@
     @stopLoss = stopLoss
     @minRolls = +minRolls
     @updateOdds()
-    @client.one 'balanceUpdate', @recalcInitialBet.bind(@)
+    @client.one 'balanceUpdated', @recalcInitialBet.bind(@)
     @client.init()

   updateOdds: ->
@@ -35,7 +35,7 @@
       minRolls = _.min([ @maxRolls(@client.MINBET)[0], @minRolls ])
       @_initialBet = @client.MINBET
       @_initialBet += 1 until @maxRolls(@_initialBet + 1)[0] < minRolls
-    @emit('initialBetRecalc', @initialBet)
+    @emit('betRecalculated', @initialBet)
     @initialBet

   inspect: ->
@@ -43,7 +43,7 @@

   run: (@totalBets = 0) ->
     @running = true
-    @symmetricStreak = 0
+    @symmetricStreak ||= 0
     @streak ||= 0
     @client.startRound().then(@placeBet.bind(@))

@@ -110,7 +110,7 @@
       count += 1
     [ count, total ]

-class BaseClient extends Evented
+window.BaseClient = class BaseClient extends Evented
   MINBET: 100

   @accessor 'balance',
@@ -121,16 +121,26 @@
     @balance = 0

   init: ->
-    @updateBalance().then => setInterval(@updateBalance.bind(@), 5000)
+    @updateProfile().then => setInterval(@updateBalance.bind(@), 5000)
+
+  getProfile: ->
+    rej(new Error("Clients should implement #getProfile method. Should return an object like { username: 'username', balance: balanceInSatoshis }"))

   updateBalance: (force) ->
     diff = Date.now() - ( @lastBalanceAt || 0 )
     return r(@balance) unless force || diff >= 3000
-    @getBalance().then(@setBalance.bind(@))
+    @getProfile().then (p) => @setBalance(p.balance)
+
+  updateProfile: ->
+    @getProfile().then (p) =>
+      @setBalance(p.balance)
+      @username = p.username
+      @emit('profileUpdated', p)
+      p

   setBalance: (@_balance) ->
     @lastBalanceAt = Date.now()
-    @emit('balanceUpdate', @balance)
+    @emit('balanceUpdated', @balance)
     @_balance

   lastGameFailed: ->
@@ -148,12 +158,13 @@
     payout = +(@MAXROLL / rollBelow * kept).toFixed(5)
     [ rollBelow, payout ]

-window.SatoshiDiceClient = class SatoshiDiceClient extends BaseClient
+window.SatoshiDice = class SatoshiDice extends BaseClient
   HOUSEEDGE: 1.9
   MAXROLL: 65536.0

-  getBalance: ->
-    @call('userbalance').then (res) -> res.balanceInSatoshis
+  getProfile: ->
+    @call('userbalance').then (res) ->
+      { username: res.nick, balance: res.balanceInSatoshis }

   startRound: ->
     @call('startround').then((@round) =>)
@@ -183,13 +194,14 @@
       dataType: 'jsonp')
     r(jqXHR)

-window.PrimediceClient = class PrimediceClient extends BaseClient
+window.Primedice = class Primedice extends BaseClient
   HOUSEEDGE: 1
   MAXROLL: 100.0

-  getSelf: -> @call('users/1')
-
-  getBalance: -> @getSelf().then (res) -> res.user.balance
+  getProfile: ->
+    @call('users/1').then (res) ->
+      { username: res.user.username, balance: res.user.balance }
+
   startRound: -> r()

   placebet: (bet, rollBelow) ->


fix a syntax error in samples.

fix a syntax error in samples.

--- sample/dungfungpuo.recipe.coffee
+++ sample/dungfungpuo.recipe.coffee
@@ -10,5 +10,5 @@
     'https://github.com/lotem/rime-forge/blob/master/dungfungpuo/dungfungpuo.dict.yaml'
   ]
   setup: ->
-    @installSchema 'dungfungpuo'
+    @installSchema 'dungfungpuo',
       enable: true


デモ時は7○3✕

デモ時は7○3✕

--- src/sub.coffee
+++ src/sub.coffee
@@ -7,7 +7,7 @@
 form =
   programName: '7○3✕'
   playerNames: ['テスト太郎', 'クイズ花子']
-  rule: new MaruBatsuRule(3, 1)
+  rule: new MaruBatsuRule(7, 3)

 window.addEventListener 'DOMContentLoaded', ->


 Merge branch 'master.ccValidBug' into 'master'

Master.Cc Valid Bug

--- client/Main/payment/paymentmethodentryform.coffee
+++ client/Main/payment/paymentmethodentryform.coffee
@@ -58,13 +58,6 @@
           event           : 'blur'
           rules           :
             maxLength     : 2
-            regExp        : do ->
-              remainingMonths = KD.utils
-                .getMonthOptions()
-                  .slice((new Date).getMonth() - 1)
-                .map((item)-> item.title)
-                .join '|'
-              return ///#{remainingMonths}///
           messages        :
             regExp        : "Expiration month should be 2 digits and between 01 to 12"
         nextElementFlat   :


Test reading from download stream after a slight delay

--- tests/models/os.spec.coffee
+++ tests/models/os.spec.coffee
@@ -41,6 +41,19 @@
 							m.chai.expect(result).to.equal('Lorem ipsum dolor sit amet')
 							done()

+				it 'should stream the download after a slight delay', (done) ->
+					os.download
+						network: 'ethernet'
+						appId: 95
+					.then (stream) ->
+						return Promise.delay(200).return(stream)
+					.then (stream) ->
+						result = ''
+						stream.on 'data', (chunk) -> result += chunk
+						stream.on 'end', ->
+							m.chai.expect(result).to.equal('Lorem ipsum dolor sit amet')
+							done()
+
 			describe 'given invalid parameters', ->

 				beforeEach (done) ->


Default history and nominations limit is now 10

--- src/aotw.coffee
+++ src/aotw.coffee
@@ -136,7 +136,7 @@
         if msg.match[1] != "history"
             limit = msg.match[1].split(" ")[1]
         else
-            limit = 9999
+            limit = 10

         if @storage.history && @storage.history.length > 0
             msg.send "Total of #{@storage.history.length} previous AOTWs"
@@ -168,7 +168,7 @@
         if msg.match[1] != "nominations"
             limit = msg.match[1].split(" ")[1]
         else
-            limit = 9999
+            limit = 10

         if @storage.nominations && @storage.nominations.length > 0
             msg.send "Total of #{@storage.nominations.length} nominations"


Reorder properties by usage

Reorder properties by usage

--- src/atom.coffee
+++ src/atom.coffee
@@ -103,38 +103,38 @@
   Section: Properties
   ###

+  # Public: A {CommandRegistry} instance
+  commands: null
+
+  # Public: A {Config} instance
+  config: null
+
   # Public: A {Clipboard} instance
   clipboard: null

-  # Public: A {Config} instance
-  config: null
-
   # Public: A {ContextMenuManager} instance
   contextMenu: null

+  # Public: A {MenuManager} instance
+  menu: null
+
+  # Public: A {KeymapManager} instance
+  keymaps: null
+
+  # Public: A {Project} instance
+  project: null
+
+  # Public: A {Syntax} instance
+  syntax: null
+
+  # Public: A {PackageManager} instance
+  packages: null
+
+  # Public: A {ThemeManager} instance
+  themes: null
+
   # Public: A {DeserializerManager} instance
   deserializers: null
-
-  # Public: A {KeymapManager} instance
-  keymaps: null
-
-  # Public: A {CommandRegistry} instance
-  commands: null
-
-  # Public: A {MenuManager} instance
-  menu: null
-
-  # Public: A {PackageManager} instance
-  packages: null
-
-  # Public: A {Project} instance
-  project: null
-
-  # Public: A {Syntax} instance
-  syntax: null
-
-  # Public: A {ThemeManager} instance
-  themes: null

   # Public: A {Workspace} instance
   workspace: null


Add support for IE detection from 11 upwards

--- src/base.coffee
+++ src/base.coffee
@@ -20,11 +20,33 @@
         pad.substring(0, pad.length - str.length) + str

     getIeVersion: () ->
-        isInternetExplorer = ->
-            window.navigator.appName is "Microsoft Internet Explorer"
+        # From https://codepen.io/gapcode/pen/vEJNZN
+        # courtesy of Mario

-        if not isInternetExplorer()
-            return false
+        ua = window.navigator.userAgent
+        # Test values; Uncomment to check result …
+        # IE 10
+        # ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)';
+        # IE 11
+        # ua = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
+        # Edge 12 (Spartan)
+        # ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0';
+        # Edge 13
+        # ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
+        msie = ua.indexOf 'MSIE '
+        if msie > 0
+            # IE 10 or older => return version number
+            return parseInt ua.substring(msie + 5, ua.indexOf('.', msie)), 10

-        matches = new RegExp(" MSIE ([0-9]+)\\.([0-9])").exec window.navigator.userAgent
-        return parseInt matches[1]
+        trident = ua.indexOf 'Trident/'
+        if trident > 0
+            # IE 11 => return version number
+            rv = ua.indexOf 'rv:'
+            return parseInt ua.substring(rv + 3, ua.indexOf('.', rv)), 10
+
+        edge = ua.indexOf 'Edge/'
+        if edge > 0
+            # Edge (IE 12+) => return version number
+            return parseInt ua.substring(edge + 5, ua.indexOf('.', edge)), 10
+        # other browser
+        false


Re-added mistakenly removed class

Re-added mistakenly removed class

--- core/app/backbone/views/evidence/add_evidence_form_view.coffee
+++ core/app/backbone/views/evidence/add_evidence_form_view.coffee
@@ -1,5 +1,5 @@
 class window.AddEvidenceFormView extends Backbone.Marionette.Layout
-  className: 'evidence-box discussion-evidenceish discussion-evidenceish-box opinion-help'
+  className: 'add-evidence-form evidence-box discussion-evidenceish discussion-evidenceish-box opinion-help'
   template: 'evidence/add_evidence_form'

   regions:


Fix date showing for students without last activity

fixes VICE-2212
flag=none

Test Plan:
 - Create a new course
 - Add a new enrollment to the course
 - View Users page
 > Should see an empty last activity field for the new user

Change-Id: I22695e28a49c0c518837b68b5f13d3290e75a610
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/277050
Tested-by: Service Cloud Jenkins <9144042a601061f88f1e1d7a1753ea3e2972119d@instructure.com>
Reviewed-by: Caleb Guanzon <54fa1f30d451e4f3c341a8f3c47c9215fd0be9c6@instructure.com>
QA-Review: Caleb Guanzon <54fa1f30d451e4f3c341a8f3c47c9215fd0be9c6@instructure.com>
Product-Review: Caleb Guanzon <54fa1f30d451e4f3c341a8f3c47c9215fd0be9c6@instructure.com>

--- ui/shared/handlebars-helpers/index.coffee
+++ ui/shared/handlebars-helpers/index.coffee
@@ -198,10 +198,11 @@

   # convert an event date and time to a string using the given date and time format specifiers
   tEventToString : (date = '', i18n_date_format = 'short', i18n_time_format = 'tiny') ->
-    I18nObj.t 'time.event',
-      defaultValue: '%{date} at %{time}',
-      date: I18nObj.l "date.formats.#{i18n_date_format}", date
-      time: I18nObj.l "time.formats.#{i18n_time_format}", date
+    if (date)
+      I18nObj.t 'time.event',
+        defaultValue: '%{date} at %{time}',
+        date: I18nObj.l "date.formats.#{i18n_date_format}", date
+        time: I18nObj.l "time.formats.#{i18n_time_format}", date

   # formats a date as a string, using the given i18n format string
   strftime : (date = '', fmtstr) ->


Remove Point and Range classes now provided by telepath

--- lib/go-to-line-view.coffee
+++ lib/go-to-line-view.coffee
@@ -1,7 +1,7 @@
 {View} = require 'space-pen'
 Editor = require 'editor'
 $ = require 'jquery'
-Point = require 'point'
+{Point} = require 'telepath'

 module.exports =
 class GoToLineView extends View


UPI update

UPI update

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -63,26 +63,28 @@

   activate: (state) ->
     @process = new GhcModiProcess
+    @disposables = null

   deactivate: ->
     @process?.destroy()
     @process = null
     @completionBackend = null
+    @disposables?.dispose?()
+    @disposables = null

   provideCompletionBackend: ->
     @completionBackend ?= new CompletionBackend @process
     @completionBackend

-  consumeUPI: (upi) ->
-    disposables = new CompositeDisposable
-    disposables.add upi.disposables
+  consumeUPI: (service) ->
+    upi = service.registerPlugin @disposables = new CompositeDisposable

     upi.setMessageTypes
       error: {}
       warning: {}
       lint: {}

-    disposables.add atom.commands.add 'atom-workspace',
+    @disposables.add atom.commands.add 'atom-workspace',
       'haskell-ghc-mod:shutdown-backend': =>
         @process?.killProcess?()

@@ -98,7 +100,7 @@
       .catch ->
         typeTooltip(args...)

-    disposables.add atom.commands.add 'atom-text-editor[data-grammar~="haskell"]',
+    @disposables.add atom.commands.add 'atom-text-editor[data-grammar~="haskell"]',
       'haskell-ghc-mod:check-file': ({target}) =>
         editor = target.getModel()
         @process.doCheckBuffer editor.getBuffer(), (res) ->
@@ -168,7 +170,7 @@
         else
           Promise.reject ignore: true #this won't set backend status

-    disposables.add upi.onDidSaveBuffer (buffer) =>
+    @disposables.add upi.onDidSaveBuffer (buffer) =>
       if atom.config.get('haskell-ghc-mod.onSaveCheck') and
          atom.config.get('haskell-ghc-mod.onSaveLint')
         upi.clearMessages ['error', 'warning', 'lint']
@@ -183,10 +185,10 @@
         @process.doLintBuffer buffer, (res) ->
           upi.setMessages res, ['lint']

-    disposables.add @process.onBackendActive ->
+    @disposables.add @process.onBackendActive ->
       upi.setStatus status: 'progress'

-    disposables.add @process.onBackendIdle ->
+    @disposables.add @process.onBackendIdle ->
       upi.setStatus status: 'ready'

     @process.onBackendActive
@@ -197,7 +199,7 @@
       {label: 'Stop Backend', command: 'haskell-ghc-mod:shutdown-backend'}
     ]

-    disposables.add atom.contextMenu.add
+    @disposables.add atom.contextMenu.add
       'atom-text-editor[data-grammar~="haskell"]': [
         'label': 'Ghc-Mod'
         'submenu': [
@@ -215,7 +217,7 @@
         ]
       ]

-    disposables
+    @disposables

   provideLinter: ->
     return unless atom.config.get 'haskell-ghc-mod.useLinter'


:bug: Add hasLinter check in LinterRegistry

--- lib/linter-registry.coffee
+++ lib/linter-registry.coffee
@@ -20,6 +20,7 @@
     catch e then helpers.error(e)

   deleteLinter: (linter) ->
+    return unless @hasLinter(linter)
     @linters.splice(@linters.indexOf(linter), 1)

   lint: ({onChange, editorLinter}) ->


Change temporary file to display commits from log

Previously commits were displayed by writing to a file named
'atom_git_plus_commit.diff'. This would fail if one was working in
multiple windows at the same time.

Now each commit is using a unique file named #{objectHash}.diff.

--- lib/models/git-show.coffee
+++ lib/models/git-show.coffee
@@ -7,8 +7,8 @@

 git = require '../git'

-showCommitFilePath = ->
-  Path.join Os.tmpDir(), "atom_git_plus_commit.diff"
+showCommitFilePath = (objectHash) ->
+  Path.join Os.tmpDir(), "#{objectHash}.diff"

 showObject = (repo, objectHash, file) ->
   args = ['show']
@@ -21,16 +21,22 @@
   git.cmd
     args: args
     cwd: repo.getWorkingDirectory()
-    stdout: (data) -> prepFile data
+    stdout: (data) -> prepFile data, objectHash

-prepFile = (text) ->
-  fs.writeFileSync showCommitFilePath(), text, flag: 'w+'
-  showFile()
+prepFile = (text, objectHash) ->
+  fs.writeFileSync showCommitFilePath(objectHash), text, flag: 'w+'
+  showFile(objectHash)

-showFile = ->
+showFile = (objectHash) ->
+  disposables = new CompositeDisposable
   split = if atom.config.get('git-plus.openInPane') then atom.config.get('git-plus.splitPane')
   atom.workspace
-    .open(showCommitFilePath(), split: split, activatePane: true)
+    .open(showCommitFilePath(objectHash), split: split, activatePane: true)
+    .done (textBuffer) =>
+      if textBuffer?
+        disposables.add textBuffer.onDidDestroy =>
+          disposables.dispose()
+          try fs.unlinkSync showCommitFilePath(objectHash)

 class InputView extends View
   @content: ->


Use class instead of id

Use class instead of id

--- views/components/main/parts/miniship.cjsx
+++ views/components/main/parts/miniship.cjsx
@@ -297,7 +297,7 @@
           [

               <OverlayTrigger placement="top" overlay={
-                <Popover id="shipPop">
+                <Popover className="ship-pop">

                       <Slotitems data={ship.api_slot.concat(ship.api_slot_ex || -1)} onslot={ship.api_onslot} maxeq={ship.api_maxeq} />


fix #988 and add comments

fix #988 and add comments


Workers: Models: Account: Call callback everytime.

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -643,6 +643,8 @@
     user.update { $set: { emailFrequency: current } }, (err) ->
       return callback err  if err

+      callback null
+
       emailFrequency =
         global    : current.global
         marketing : current.marketing


Add Scott to dummyadming

Add Scott to dummyadming

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -925,8 +925,8 @@

   dummyAdmins = [ "sinan", "devrim", "gokmen", "chris", "fatihacet", "arslan",
                   "sent-hil", "kiwigeraint", "cihangirsavas", "leventyalcin",
-                  "leeolayvar", "stefanbc", "szkl", "alfredo", "canthefason",
-                  "nitin" ]
+                  "leeolayvar", "stefanbc", "szkl", "canthefason", "nitin",
+                  "rsbrown"]

   userIsExempt: (callback)->
     # console.log @isExempt, this


Remove forgotten console.log

Remove forgotten console.log

--- workers/social/lib/social/models/datadog.coffee
+++ workers/social/lib/social/models/datadog.coffee
@@ -15,7 +15,7 @@
       static           :
         sendEvent      : (signature Object, Function)

-  console.log {api_key, app_key}   = KONFIG.datadog
+  {api_key, app_key}   = KONFIG.datadog
   DogApi               = new dogapi {
     api_key, app_key
   }


Sidebar: Skip collaboration filter if there is no channel id

--- workers/social/lib/social/models/sidebar.coffee
+++ workers/social/lib/social/models/sidebar.coffee
@@ -128,6 +128,8 @@

           ->

+            return filterQueue.fin()  unless workspace.channelId
+
             isOwner  = isMachineOwner  user, machine
             isShared = isMachineShared user, machine
             skip     = workspace.channelId and (isOwner or isShared)


Simplified errors

Simplified errors

--- rest_controller.coffee
+++ rest_controller.coffee
@@ -38,7 +38,7 @@
         return res.json({result: json}) if (req.query.$count or req.query.$exists)
         unless json
           if req.query.$one
-            return res.status(404).send(error: "Model not found with id: #{req.query.id}")
+            return res.status(404).send()
           else
             res.json(json)

@@ -60,8 +60,8 @@
       cursor = @model_type.cursor(req.params.id)
       cursor = cursor.whiteList(@white_lists.show) if @white_lists.show
       cursor.toJSON (err, json) =>
-        return res.status(404).send(error: err.toString()) if err
-        return res.status(404).send(error: "Model not found with id: #{req.params.id}") unless json
+        return res.status(500).send(error: err.toString()) if err
+        return res.status(404).send() unless json
         json = _.pick(json, @white_lists.show) if @white_lists.show

         @render req, json, (err, json) =>
@@ -92,8 +92,8 @@
     try
       json = JSONUtils.parse(if @white_lists.update then _.pick(req.body, @white_lists.update) else req.body)
       @model_type.find req.params.id, (err, model) =>
-        return res.status(404).send(error: err.toString()) if err
-        return res.status(404).send(error: "Model not found with id: #{req.params.id}") unless model
+        return res.status(500).send(error: err.toString()) if err
+        return res.status(404).send() unless model
         model.save model.parse(json), {
           success: =>
             json = model.toJSON()
@@ -111,10 +111,10 @@
     try
       @model_type.exists req.params.id, (err, exists) =>
         return res.status(500).send(error: err.toString()) if err
-        return res.status(404).send(error: "Model not found with id: #{req.params.id}") unless exists
+        return res.status(404).send() unless exists

         @model_type.destroy {id: req.params.id}, (err) ->
-          return res.status(500).send(error: "Model not deleted with id: #{req.params.id}") if err
+          return res.status(500).send(error: err.toString()) if err
           res.status(200).send()

     catch err


translate menu_linux.cson into zh-tw

translate menu_linux.cson into zh-tw

--- def/zh-tw/menu_linux.cson
+++ def/zh-tw/menu_linux.cson
@@ -1,296 +1,296 @@
 Menu:
   "&File":
-    value: "File(&F)"
+    value: "檔案(&F)"
     submenu:
       "New &Window":
-        value: "New Window(&W)"
+        value: "開新視窗(&W)"
       "&New File":
-        value: "New File(&N)"
+        value: "新增文件(&N)"
       "&Open File…":
-        value: "Open File(&O)..."
+        value: "開啟(&O)..."
       "Open Folder…":
-        value: "Open Folder..."
+        value: "開啟資料夾..."
       "Add Project Folder…":
-        value: "Add Project Folder..."
+        value: "新增專案資料夾..."
       "Reopen Last &Item":
-        value: "Reopen Last Item(&I)"
+        value: "重新開啟上一個分頁(&I)"
       "&Save":
-        value: "Save(&S)"
+        value: "存檔(&S)"
       "Save &As…":
-        value: "Save As(&A)..."
+        value: "另存新檔(&A)..."
       "Save A&ll":
-        value: "Save All(&L)"
+        value: "儲存所有文件(&L)"
       "&Close Tab":
-        value: "Close Tab(&C)"
+        value: "關閉分頁(&C)"
       "Close &Pane":
-        value: "Close Pane(&P)"
+        value: "關閉窗格(&P)"
       "Clos&e Window":
-        value: "Close Window(&E)"
+        value: "關閉視窗(&E)"
       "Quit":
-        value: "Quit"
+        value: "結束 Atom"
       "Close All Tabs":
-        value: "Close All Tabs"
+        value: "關閉所有分頁"
   "&Edit":
-    value: "Edit(&E)"
+    value: "編輯(&E)"
     submenu:
       "&Undo":
-        value: "Undo(&U)"
+        value: "復原(&U)"
       "&Redo":
-        value: "Redo(&R)"
+        value: "重作(&R)"
       "&Cut":
-        value: "Cut(&C)"
+        value: "剪下(&C)"
       "C&opy":
-        value: "Copy(&O)"
+        value: "複製(&O)"
       "Copy Pat&h":
-        value: "Copy Path(&H)"
+        value: "複製檔案路徑(&H)"
       "&Paste":
-        value: "Paste(&P)"
+        value: "貼上(&P)"
       "Select &All":
-        value: "Select All(&A)"
+        value: "全選(&A)"
       "&Toggle Comments":
-        value: "Toggle Comments(&T)"
+        value: "註解/取消註解(&T)"
       Lines:
-        value: "Lines"
+        value: "行"
         submenu:
           "&Indent":
-            value: "Indent(&I)"
+            value: "增加縮排(&I)"
           "&Outdent":
-            value: "Outdent(&O)"
+            value: "減少縮排(&O)"
           "&Auto Indent":
-            value: "Auto Indent(&A)"
+            value: "自動縮排(&A)"
           "Move Line &Up":
-            value: "Move Line Up(&U)"
+            value: "將此行向上移動(&U)"
           "Move Line &Down":
-            value: "Move Line Down(&D)"
+            value: "將此行向下移動(&D)"
           "Du&plicate Lines":
-            value: "Duplicate Lines(&P)"
+            value: "複製(&P)"
           "D&elete Line":
-            value: "Delete Line(&E)"
+            value: "刪除(&E)"
           "&Join Lines":
-            value: "Join Lines(&J)"
+            value: "多行合併(&J)"
       Columns:
-        value: "Columns"
+        value: "欄"
         submenu:
           "Move Selection &Left":
-            value: "Move Selection Left(&L)"
+            value: "將選取範圍向左移(&L)"
           "Move Selection &Right":
-            value: "Move Selection Right(&R)"
+            value: "將選取範圍向右移(&R)"
       Text:
-        value: "Text"
+        value: "文字"
         submenu:
           "&Upper Case":
-            value: "Upper Case(&U)"
+            value: "轉為大寫(&U)"
           "&Lower Case":
-            value: "Lower Case(&L)"
+            value: "轉為小寫(&L)"
           "Delete to End of &Word":
-            value: "Delete to End of Word(&W)"
+            value: "刪除至下一個詞尾(&W)"
           "Delete to Previous Word Boundary":
-            value: "Delete to Previous Word Boundary"
+            value: "刪除至上一個詞語邊界"
           "Delete to Next Word Boundary":
-            value: "Delete to Next Word Boundary"
+            value: "刪除至下一個詞語邊界"
           "&Delete Line":
-            value: "Delete Line(&D)"
+            value: "刪除行(&D)"
           "&Transpose":
-            value: "Transpose(&T)"
+            value: "顛倒(&T)"
       Folding:
-        value: "Folding"
+        value: "折疊程式碼"
         submenu:
           "&Fold":
-            value: "Fold(&F)"
+            value: "折疊(&F)"
           "&Unfold":
-            value: "Unfold(&U)"
+            value: "展開(&U)"
           "Unfold &All":
-            value: "Unfold All(&A)"
+            value: "展開全部(&A)"
           "Fol&d All":
-            value: "Fold All(&D)"
+            value: "折疊全部(&D)"
           "Fold Level 1":
-            value: "Fold Level 1"
+            value: "折疊至第 1 層縮排"
           "Fold Level 2":
-            value: "Fold Level 2"
+            value: "折疊至第 2 層縮排"
           "Fold Level 3":
-            value: "Fold Level 3"
+            value: "折疊至第 3 層縮排"
           "Fold Level 4":
-            value: "Fold Level 4"
+            value: "折疊至第 4 層縮排"
           "Fold Level 5":
-            value: "Fold Level 5"
+            value: "折疊至第 5 層縮排"
           "Fold Level 6":
-            value: "Fold Level 6"
+            value: "折疊至第 6 層縮排"
           "Fold Level 7":
-            value: "Fold Level 7"
+            value: "折疊至第 7 層縮排"
           "Fold Level 8":
-            value: "Fold Level 8"
+            value: "折疊至第 8 層縮排"
           "Fold Level 9":
-            value: "Fold Level 9"
+            value: "折疊至第 9 層縮排"
       "&Preferences":
-        value: "Preferences(&P)"
+        value: "偏好設定(&P)"
       "Config…":
-        value: "Config..."
+        value: "設定檔..."
       "Init Script…":
-        value: "Init Script..."
+        value: "開啟初始指令..."
       "Keymap…":
-        value: "Keymap..."
+        value: "開啟快捷鍵設定..."
       "Snippets…":
-        value: "Snippets..."
+        value: "開啟程式碼片段..."
       "Stylesheet…":
-        value: "Stylesheet..."
+        value: "開啟 Atom 樣式表..."
       "Reflow Selection":
-        value: "Reflow Selection"
+        value: "重新編排選取區域"
       Bookmark:
-        value: "Bookmark"
+        value: "書籤"
         submenu:
           "View All":
-            value: "View All"
+            value: "檢視所有書籤"
           "Toggle Bookmark":
-            value: "Toggle Bookmark"
+            value: "切換書籤"
           "Jump to Next Bookmark":
-            value: "Jump to Next Bookmark"
+            value: "移至下一個書籤"
           "Jump to Previous Bookmark":
-            value: "Jump to Previous Bookmark"
+            value: "移至上一個書籤"
       "Select Encoding":
-        value: "Select Encoding"
+        value: "選擇編碼"
       "Go to Line":
-        value: "Go to Line"
+        value: "移動至指定行數"
       "Select Grammar":
-        value: "Select Grammar"
+        value: "切換語法"
   "&View":
-    value: "View(&V)"
+    value: "檢視(&V)"
     submenu:
       "Toggle &Full Screen":
-        value: "Toggle Full Screen(&F)"
+        value: "切換全螢幕(&F)"
       "Toggle Menu Bar":
-        value: "Toggle Menu Bar"
+        value: "顯示/隱藏功能表欄"
       Panes:
-        value: "Panes"
+        value: "窗格"
         submenu:
           "Split Up":
-            value: "Split Up"
+            value: "向上分割窗格 ↑"
           "Split Down":
-            value: "Split Down"
+            value: "向下分割窗格 ↓"
           "Split Left":
-            value: "Split Left"
+            value: "向左分割窗格 ←"
           "Split Right":
-            value: "Split Right"
+            value: "向右分割窗格 →"
           "Focus Next Pane":
-            value: "Focus Next Pane"
+            value: "移至下一個窗格"
           "Focus Previous Pane":
-            value: "Focus Previous Pane"
+            value: "移至上一個窗格"
           "Focus Pane Above":
-            value: "Focus Pane Above"
+            value: "移至上方窗格 ↑"
           "Focus Pane Below":
-            value: "Focus Pane Below"
+            value: "移至下方窗格 ↓"
           "Focus Pane On Left":
-            value: "Focus Pane On Left"
+            value: "移至左方窗格 ←"
           "Focus Pane On Right":
-            value: "Focus Pane On Right"
+            value: "移至右方窗格 →"
           "Close Pane":
-            value: "Close Pane"
+            value: "關閉窗格"
       Developer:
-        value: "Developer"
+        value: "開發人員"
         submenu:
           "Open In &Dev Mode…":
-            value: "Open In Dev Mode(&D)..."
+            value: "在開發人員模式中開啟(&D)..."
           "&Reload Window":
-            value: "Reload Window(&R)"
+            value: "重新載入視窗(&R)"
           "Run Package &Specs":
-            value: "Run Package Specs(&S)"
+            value: "執行擴充套件規格測試(&S)"
           "Toggle Developer &Tools":
-            value: "Toggle Developer Tools(&T)"
+            value: "開發人員工具(&T)"
       "&Increase Font Size":
-        value: "Increase Font Size(&I)"
+        value: "放大字型(&I)"
       "&Decrease Font Size":
-        value: "Decrease Font Size(&D)"
+        value: "縮小字型(&D)"
       "Re&set Font Size":
-        value: "Reset Font Size(&S)"
+        value: "重設字型大小(&S)"
       "Toggle Soft &Wrap":
-        value: "Toggle Soft Wrap(&W)"
+        value: "自動換行(&W)"
       "Toggle Command Palette":
-        value: "Toggle Command Palette"
+        value: "切換指令面板"
       "Toggle Tree View":
-        value: "Toggle Tree View"
+        value: "切換 Tree View"
   "&Selection":
-    value: "Selection(&S)"
+    value: "選取區域(&S)"
     submenu:
       "Add Selection &Above":
-        value: "Add Selection Above(&A)"
+        value: "向上新增游標(&A)"
       "Add Selection &Below":
-        value: "Add Selection Below(&B)"
+        value: "向下新增游標(&B)"
       "S&plit into Lines":
-        value: "Split into Lines(&P)"
+        value: "建立多個游標(&P)"
       "Single Selection":
-        value: "Single Selection"
+        value: "單一游標"
       "Select to &Top":
-        value: "Select to Top(&T)"
+        value: "選取至文件頂端(&T)"
       "Select to Botto&m":
-        value: "Select to Bottom(&M)"
+        value: "選取至文件底端(&M)"
       "Select &Line":
-        value: "Select Line(&L)"
+        value: "選取行(&L)"
       "Select &Word":
-        value: "Select Word(&W)"
+        value: "選取詞(&W)"
       "Select to Beginning of W&ord":
-        value: "Select to Beginning of Word(&O)"
+        value: "選取至詞首(&O)"
       "Select to Beginning of L&ine":
-        value: "Select to Beginning of Line(&I)"
+        value: "選取至行首(&I)"
       "Select to First &Character of Line":
-        value: "Select to First Character of Line(&C)"
+        value: "選取至行內第一個字元(&C)"
       "Select to End of Wor&d":
-        value: "Select to End of Word(&D)"
+        value: "選取至詞尾(&D)"
       "Select to End of Lin&e":
-        value: "Select to End of Line(&E)"
+        value: "選取至行尾(&E)"
       "Select Inside Brackets":
-        value: "Select Inside Brackets"
+        value: "選取括號內容"
   "F&ind":
-    value: "Find(&I)"
+    value: "尋找(&I)"
     submenu:
       "Find in Buffer":
-        value: "検索"
+        value: "在文件中尋找"
       "Replace in Buffer":
-        value: "置換"
+        value: "在文件中取代"
       "Select Next":
-        value: "Select Next"
+        value: "選擇下一個項目"
       "Select All":
-        value: "Select All"
+        value: "選擇所有項目"
       "Toggle Find in Buffer":
-        value: "Toggle Find in Buffer"
+        value: "切換文件中尋找面板"
       "Find in Project":
-        value: "Find in Project"
+        value: "在專案資料夾中尋找"
       "Toggle Find in Project":
-        value: "Toggle Find in Project"
+        value: "切換專案資料夾中尋找面板"
       "Find Next":
-        value: "Find Next"
+        value: "尋找下一個項目"
       "Find Previous":
-        value: "Find Previous"
+        value: "尋找上一個項目"
       "Replace Next":
-        value: "Replace Next"
+        value: "取代下一個項目"
       "Replace All":
-        value: "Replace All"
+        value: "全部取代"
       "Clear History":
-        value: "Clear History"
+        value: "清除歷史記錄"
       "Find Buffer":
-        value: "Find Buffer"
+        value: "尋找編輯中文件"
       "Find File":
-        value: "Find File"
+        value: "尋找檔案"
       "Find Modified File":
-        value: "Find Modified File"
+        value: "尋找修改過的檔案"
   "&Packages":
-    value: "Packages(&P)"
+    value: "擴充套件(&P)"
   "&Help":
-    value: "Help(&H)"
+    value: "擴充套件(&H)"
     submenu:
       "View &Terms of Use":
-        value: "View Terms of Use(&T)"
+        value: "使用條款(&T)"
       "View &License":
-        value: "View License(&L)"
+        value: "使用條款(&L)"
       "&Documentation":
-        value: "Documentation(&D)"
+        value: "說明文件(&D)"
       "Frequently Asked Questions":
-        value: "Frequently Asked Questions"
+        value: "常見問題"
       "Community Discussions":
-        value: "Community Discussions"
+        value: "社群討論區"
       "Report Issue":
-        value: "Report Issue"
+        value: "回報問題"
       "Search Issues":
-        value: "Search Issues"
+        value: "搜尋問題"
       "About Atom":
-        value: "About Atom"
+        value: "關於 Atom"
       "Welcome Guide":
-        value: "Welcome Guide"
+        value: "歡迎畫面與指南"


remove duplicate entry

remove duplicate entry

--- .atom/config.cson
+++ .atom/config.cson
@@ -13,7 +13,6 @@
     displayMode: "always"
   core:
     disabledPackages: [
-      "linter-rubocop"
       "rubocop-auto-correct"
       "linter-rubocop"
     ]


atom: hideVcsIngoredFiles=true

atom: hideVcsIngoredFiles=true

--- .atom/config.cson
+++ .atom/config.cson
@@ -186,6 +186,7 @@
       autoClose: true
   "tree-view":
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
   "vim-mode": {}
   "vim-mode-plus":
     incrementalSearch: true


Fixes atom notes path.

Fixes atom notes path.

--- .atom/config.cson
+++ .atom/config.cson
@@ -5,6 +5,7 @@
   "atom-isort":
     isortPath: "~/bin/isort"
   "atom-notes":
+    directory: "~/Dropbox/nv"
     extensions: [
       ".md"
       ".markdown"
@@ -119,7 +120,7 @@
     pep8ExecutablePath: "/usr/local/bin/pep8"
   "linter-ui-default":
     hidePanelWhenEmpty: true
-    panelHeight: 126
+    panelHeight: 127
     showPanel: true
     showProviderName: true
   "linter-write-good":


Add default value for mass to computeDuration

--- framer/Animators/SpringCurveValueConverter.coffee
+++ framer/Animators/SpringCurveValueConverter.coffee
@@ -20,7 +20,7 @@
 # Tries to compute the duration of a spring,
 # but can't for certain velocities and if dampingRatio >= 1
 # In those cases it will return null
-exports.computeDuration = (tension, friction, velocity = 0) ->
+exports.computeDuration = (tension, friction, velocity = 0, mass = 1) ->
 	dampingRatio = computeDampingRatio(tension, friction)
 	undampedFrequency = Math.sqrt(tension / mass)
 	# This is basically duration extracted out of the envelope functions


GroupStackSettings: fix cancel/new stack template logic

--- client/admin/lib/views/groupstacksettingsv2.coffee
+++ client/admin/lib/views/groupstacksettingsv2.coffee
@@ -26,7 +26,7 @@
       defineStackView = @addSubView new DefineStackView {}, { stackTemplate }

       defineStackView.on ['Cancel', 'Completed'], (stackTemplate) ->
-        initialView.reload()  if requiresReload
+        initialView.reload()  if requiresReload and stackTemplate
         initialView.show()
         @destroy()


fixed logout bug

fixed logout bug

--- client/coffeescript/templates/notifications.coffee
+++ client/coffeescript/templates/notifications.coffee
@@ -1,6 +1,6 @@
 _.extend( Template.notifications,
   count: ->
-    Notifications.find( timestamp: { '$gt': Meteor.user().notify } ).count()
+    Notifications.find( timestamp: { '$gt': Meteor.user()?.notify } ).count()
   notifications: ->
     groups = {}
     notifs = Notifications.find({},{sort:{timestamp:-1}}).fetch()


form workflow: WIP a few fair tests

form workflow: WIP a few fair tests

--- client/landing/app/CommonViews/formworkflow.coffee
+++ client/landing/app/CommonViews/formworkflow.coffee
@@ -1,4 +1,111 @@
 class FormWorkflow extends KDView
+
+  make = ->
+    fw = new FormWorkflow
+    fw.on 'DataCollected', -> log arguments
+    fw
+
+  assert = (truth) ->
+    unless !!truth
+
+
+      { stack } = new Error
+
+      debugger
+
+      throw {
+        message: "Assertion error!", stack
+      }
+
+  tests = [
+    ->
+      @requireData [
+        'foo'
+        'bar'
+      ]
+
+      @collectData { foo: 42 }
+
+      assert not @isSatisfied()
+
+      @collectData { bar: 0 }
+
+      assert @isSatisfied()
+
+    ->
+      @requireData @all(
+        'foo'
+        'bar'
+      )
+
+      @collectData { foo: 42 }
+
+      assert not @isSatisfied()
+
+      @collectData { bar: 0 }
+
+      assert @isSatisfied()
+
+    ->
+      @requireData @any(
+        'foo'
+        'bar'
+      )
+
+      @collectData { foo: 42 }
+
+      assert @isSatisfied()
+
+      @collectData { bar: 0 }
+
+      assert @isSatisfied()
+
+      @clearData 'foo'
+
+      assert @isSatisfied()
+
+      @clearData 'bar'
+
+      assert not @isSatisfied()
+    ->
+      @requireData @all(
+        'foo'
+        'bar'
+        @any('baz', 'qux')
+      )
+
+      @collectData { foo: 42 }
+
+      assert not @isSatisfied()
+
+      @collectData { bar: 0 }
+
+      assert not @isSatisfied()
+
+      @collectData { baz: 16 }
+
+      assert @isSatisfied()
+
+      @clearData 'baz'
+
+      assert not @isSatisfied()
+
+      @collectData { qux: -88 }
+
+      assert @isSatisfied()
+
+      @clearData 'bar'
+
+      assert not @isSatisfied()
+
+
+  ]
+
+  @runTests = ->
+    test.call make()  for test in tests
+
+    return this
+

   constructor: (options = {}, data) ->
     super options, data
@@ -17,6 +124,11 @@
     @collector.addRequirement gate

     return this
+
+  getData: -> @data
+
+  isSatisfied: ->
+    @collector.gate.isSatisfied()

   collectData: (data) ->
     @collector.collectData data
@@ -78,7 +190,7 @@
   all: (fields...) -> new All fields
   any: (fields...) -> new Any fields

-  class Collector extends KDEventEmitter
+  @Collector = class Collector extends KDEventEmitter

     constructor: (@gate = new Gate) ->
       super
@@ -86,12 +198,12 @@
       @gate.on 'report', (state) => switch state
         when 'Satisfied'
           @emit 'DataCollected', @data
-        when 'Dissatisfied' then #ignore
+        when 'Dissatisfied' then # ignore

     addRequirement: (requirement) ->
       @gate.addField requirement

-    getData: -> @gate.getData()
+    getData: -> @data

     collectData: (data) ->
       @defineKey key, val  for own key, val of data
@@ -104,7 +216,7 @@
       @data[key] = value
       @gate.satisfy key

-  class Satisfier extends KDEventEmitter
+  @Satisfier = class Satisfier extends KDEventEmitter

     isSatisfied: -> @satisfied ?= no

@@ -153,8 +265,8 @@
           when 'Satisfied'
             @emit 'RequirementSatisfied', field
             satisfier.satisfy()
-          when 'Canceled'
-            satisfier.cancel()
+          when 'Canceled' then # ignore
+            # satisfier.cancel()

       return this

@@ -187,18 +299,18 @@

       return this

-    signal: -> @compliment no
+    kill: -> @compliment no

     compliment: (value) -> value

     isSatisfied: ->
       for own _, field of @fields
-        return @signal()  unless @compliment field.isSatisfied()
+        return @kill()  unless @compliment field.isSatisfied()

       for child in @children
-        return @signal()  unless @compliment child.isSatisfied()
-
-      return !@signal()
+        return @kill()  unless @compliment child.isSatisfied()
+
+      return !@kill()

     toString: -> "gate-#{@id}"

@@ -212,4 +324,4 @@
     compliment: (value) -> !value

     # Any#createSatisfier returns a singleton satisfier :)
-    createSatisfier: -> @safisfier ?= super()
+    createSatisfier: -> @satisfier ?= super()


Some small style fixes

Some small style fixes

--- src/game.coffee
+++ src/game.coffee
@@ -15,8 +15,6 @@

 		@state = 'main-menu'

-		# @events = new EventEmitter2
-
 		@onAny (a...) ->
 			winston.silly "Event: '#{@event}'; ", a

@@ -33,9 +31,7 @@

 		(require './messages')(@)

-		#@events
-		@
-		.on 'key.c', (ch, key) =>
+		@on 'key.c', (ch, key) =>
 			@quit() if key.ctrl

 		.on 'log.add', (str) ->
@@ -64,9 +60,7 @@

 		@transitionToMap (MapGenerator.generateBigRoom 80, 25), 2, 2

-		#@events
-		@
-		.on 'key.z', =>
+		@on 'key.z', =>
 			newMap = (MapGenerator.generateCellularAutomata 80, 21)
 			[startX, startY] = []

@@ -138,9 +132,7 @@
 			(next) =>
 				switch @state
 					when 'main-menu'
-						#@events
-						@
-						.once 'key.s', =>
+						@once 'key.s', =>
 							@goState 'game'
 							next()


Don't allow a book to be published if not publishing all its pages

--- src/scripts/modules/media/editbar/modals/publish.coffee
+++ src/scripts/modules/media/editbar/modals/publish.coffee
@@ -21,7 +21,8 @@
     events:
       'submit form': 'onSubmit'
       'change [name="license"]': 'onToggleAcceptLicense'
-      'change .collection-checkbox': 'toggleCollection'
+      'change .collection-checkbox': 'toggleBook'
+      'change .publish-contents input': 'togglePage'

     initialize: () ->
       super()
@@ -64,7 +65,12 @@
       else
         @$el.find('.btn-submit').addClass('disabled')

-    toggleCollection: (e) ->
+    toggleBook: (e) ->
       if $(e.currentTarget).is(':checked')
         # Ensure everything else is checked
         @$el.find('.publish-contents').find('input').prop('checked', true)
+
+    togglePage: (e) ->
+      if not $(e.currentTarget).is(':checked')
+        # Uncheck the book
+        @$el.find('.collection-checkbox').prop('checked', false)


fix(projectCredentials): fix dep in test

--- src/spec/helpers/project-credentials-config.spec.coffee
+++ src/spec/helpers/project-credentials-config.spec.coffee
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 Promise = require 'bluebird'
-{ProjectCredentialsConfig} = require '../../../lib/main'
+{ProjectCredentialsConfig} = require '../../lib/main'

 describe 'ProjectCredentialsConfig', ->


fill-last-page also active when list.length == 0

--- gem/vendor/assets/javascripts/atPagination.coffee
+++ gem/vendor/assets/javascripts/atPagination.coffee
@@ -34,8 +34,8 @@
             $scope.pages = for x in [0..($scope.numberOfPages - 1)]
               x
           else
-            $scope.numberOfPages = 0
-            $scope.pages = []
+            $scope.numberOfPages = 1
+            $scope.pages = [0]
           $scope.list = $scope.list

       $scope.fromPage = () ->
@@ -45,7 +45,7 @@
       $scope.getFillerArray = () ->
         if $scope.currentPage == $scope.numberOfPages - 1
           itemCountOnLastPage = $scope.list.length % $scope.itemsPerPage
-          if itemCountOnLastPage != 0
+          if itemCountOnLastPage != 0 || $scope.list.length == 0
             fillerLength = $scope.itemsPerPage - itemCountOnLastPage - 1
             x for x in [($scope.list.length)..($scope.list.length + fillerLength)]
           else


OX-252 increase maximum zoom step, to change edit MAX_ZOOM_STEP in flycam

--- app/assets/javascripts/oxalis/model/flycam.coffee
+++ app/assets/javascripts/oxalis/model/flycam.coffee
@@ -12,6 +12,7 @@
 MAX_TEXTURE_OFFSET = 31     # maximum difference between requested coordinate and actual texture position
 ZOOM_DIFF          = 0.1
 MAX_ZOOM_TRESHOLD  = 2
+MAX_ZOOM_STEP      = 4

 class Flycam2d

@@ -45,7 +46,7 @@

   zoomOut : (planeID) ->
     # Make sure the max. zoom Step will not be exceded
-    if @zoomSteps[planeID] < 3 + @maxZoomStepDiff - ZOOM_DIFF
+    if @zoomSteps[planeID] < MAX_ZOOM_STEP + @maxZoomStepDiff - ZOOM_DIFF
       @setZoomStep(planeID, @zoomSteps[planeID] + ZOOM_DIFF)

   zoomInAll : ->


No need to escape HTML manually anymore

No need to escape HTML manually anymore

--- app/assets/javascripts/task/ansi_colors.js.coffee
+++ app/assets/javascripts/task/ansi_colors.js.coffee
@@ -1,21 +1,6 @@
 #= require ansi_stream
 #= require ./tty

-AnsiStream.strip = (text) ->
-  text.replace(/(?:(?:\u001b\[)|\u009b)(?:(?:[0-9]{1,3})?(?:(?:;[0-9]{0,3})*)?[A-M|f-m])|\u001b[A-M]/g, '')
-
-entityMap =
- "&": "&"
- "<": "<"
- ">": ">"
- '"': '"'
- "'": '''
- "/": '/'
-
-escapeHtml = (string) ->
-  String(string).replace(/[&<>"'\/]/g, (s) -> entityMap[s])
-
 stream = new AnsiStream()
-
 TTY.appendFormatter (chunk) ->
-  stream.process(escapeHtml(chunk))
+  stream.process(chunk)


Closes #1703; Create direct rooms correctly in incoming hook

--- packages/rocketchat-integrations/server/api/api.coffee
+++ packages/rocketchat-integrations/server/api/api.coffee
@@ -60,7 +60,7 @@

 				if not room
 					Meteor.runAsUser user._id, ->
-						Meteor.call 'createDirectMessage', roomUser._id
+						Meteor.call 'createDirectMessage', roomUser.username
 						room = RocketChat.models.Rooms.findOne(rid)

 			else


Add comment noting duplication of postFactlinkObject in core.

--- app/js/jail_iframe/util/postFactlinkObject.coffee
+++ app/js/jail_iframe/util/postFactlinkObject.coffee
@@ -1,4 +1,8 @@
+# Note: this file is duplicated between core and js-lib
+# the implementations may differ slightly due to the differing release schedules
+
 msgPrefix = 'FL$msg*)k`dC:'
+
 window.Factlink || window.Factlink = {}

 Factlink.createFrameProxy = (target_window) -> (method_name) -> (args...) ->


Stop scrolling animation when page is switched

Prevent its callbacks from being called.

--- resources/assets/coffee/react/profile-page/main.coffee
+++ resources/assets/coffee/react/profile-page/main.coffee
@@ -138,6 +138,7 @@
     for own _name, timeout of @timeouts
       clearTimeout timeout

+    $(window).stop()
     @removeListeners()


More camels

More camels

--- resources/assets/coffee/react/ranking/paginator.coffee
+++ resources/assets/coffee/react/ranking/paginator.coffee
@@ -49,15 +49,15 @@

   render: =>
     page = @props.page
-    max_pages = @props.pages - 1
+    maxPages = @props.pages - 1
     range = 8 # number of pages (excluding current page) to show at a time

     leftPages = Math.floor(range/2)
     if page < leftPages
       leftPages = page

-    if page > max_pages - Math.floor(range/2) # 4
-      leftPages = page - (max_pages - range)
+    if page > maxPages - Math.floor(range/2) # 4
+      leftPages = page - (maxPages - range)

     rightPages = Math.min(range, range - leftPages)


fix video playing

fix video playing

--- project/coffee/view/components/ObjectsList.coffee
+++ project/coffee/view/components/ObjectsList.coffee
@@ -23,6 +23,10 @@
         null

     beforeChange : (event, slick, currentSlide, nextSlide) =>
+        @$el.find('video').each ->
+            @.pause()
+            @.currentTime = 0
+
         @trigger 'beforeChange'
         null


Fix layout bug and output messages even when github rate limit is exceeded

--- src/jira.coffee
+++ src/jira.coffee
@@ -159,7 +159,7 @@
       .then (json) ->
         id = json.id
         message = """
-          *[#{json.key}] - #{json.fields.summary}*
+          *[#{json.key}] - #{json.fields.summary.trim()}*
           #{jiraUrl}/browse/#{ticket}
           Status: #{json.fields.status.name}
           Assignee: #{lookupUserWithJira json.fields.assignee}
@@ -202,6 +202,7 @@
     ).then (messages) ->
       msg.send message for message in messages
     .catch (error) ->
+      msg.send message for message in messages
       msg.send "*[Error]* #{error}"

   robot.respond commandsPattern, (msg) ->


Updated tests for options definitions in shared objects test

--- test/sharedObjectsTest.coffee
+++ test/sharedObjectsTest.coffee
@@ -54,4 +54,25 @@

 			expect(shared.defaultOptions()).to.deep.equal mock

-	return+
+	describe '@optionsDefinitions', ->
+
+		it 'should be a predefined object', ->
+			expect(shared.optionsDefinitions).to.be.an 'object'
+
+		it 'should have default values for each option', ->
+			for option in shared.optionsDefinitions
+				testIfdefaultValue = option.hasOwnProperty 'defaultValue'
+				expect(testIfdefaultValue).to.be.true
+
+		it 'should have a command definition for each option', ->
+			for option in shared.optionsDefinitions
+				testIfCommand = option.hasOwnProperty 'commands'
+				expect(testIfCommand).to.be.true
+
+		it 'should have a command description for each option', ->
+			for option in shared.optionsDefinitions
+				testIfCommandDesc = option.hasOwnProperty 'commandDescription'
+				expect(testIfCommandDesc).to.be.true
+
+	return


Standardise gulpfile

Standardise gulpfile


Fix 'diff' task for fail test on Travis CI

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -106,17 +106,8 @@
   casperChild = spawn('casperjs', ['test'].concat(tests))
   casperChild.stdout.on 'data', (data) ->
     gutil.log('CasperJS:', data.toString().slice(0, -1))
-
   casperChild.on 'close', (code) ->
-    exitCode = code
-    process.emit('exit') if exitCode != 0
-
-exitCode = 0
-process.on 'exit', ->
-  process.nextTick ->
-    msg = "gulp '#{gulp.seq}' failed."
-    gutil.log(gutil.colors.red(msg))
-    process.exit(exitCode)
+    throw new Error("CasperJS exited with code #{code}") if code

 # Runners
 gulp.task('compile:tmp', -> runSequence('clean:tmp', 'coffee:src:tmp', 'coffee:spec:tmp', 'stylus:src', 'uglify:tmp'))


Update: ヘッダーを出力するように

Update: ヘッダーを出力するように

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -7,6 +7,12 @@
 coffee = require "gulp-coffee"
 uglify = require "gulp-uglify"

+packageJson = require "./package.json"
+banner = [
+  "/*!"
+  " * <%= packageJson.name %> v<%= packageJson.version %>  <%= packageJson.license %> License"
+  " */"
+].join "\n"
 src = "./src"
 bin = "./bin"

@@ -15,7 +21,8 @@
     .pipe(plumber({errorHandler: notify.onError("Error: <%= error.message %>")}))
     .pipe(coffee())
     .pipe(concat("shortQuery.js"))
-    .pipe(uglify())
+    .pipe(uglify({preserveComments:"license"}))
+    .pipe(header(banner, {packageJson: packageJson}))
     .pipe(gulp.dest(bin))

 gulp.task "default", ["coffee"]


Small fix

Small fix

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,5 +1,4 @@
 gulp = require('gulp')
-grep = require('gulp-grep-stream')
 watch = require('gulp-watch')
 mocha = require('gulp-mocha')
 gutil = require('gulp-util')


fix test task

fix test task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -53,7 +53,7 @@

 gulp.task 'default', ['clean', 'build']

-gulp.task 'test', ['build-test'], ->
+gulp.task 'test', ['build-dev', 'build-test'], ->
   gulp.src './.tmp/test/**/*.js'
   .pipe mocha()


explicit reload after `jade` or `md` changes

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -50,9 +50,11 @@
 gulp.task 'default', ['preview'], ->
   # preview with browserSync
   browserSync.init({server: "build", port:3000})
-  gulp.watch "assets/**/*.{js,coffee}", ['js:build:preview']
+  gulp.watch "./assets/**/*.{js,coffee}", ['js:build:preview']
   gulp.watch "./assets/stylus/**/*.styl", ['css:build:preview']
-  gulp.watch "templates/**/*.jade", ['build_wintersmith']
+  gulp.watch "./templates/**/*.jade", ['preview:jade']
+  gulp.watch "./contents/**/*.md", ['preview:md']
+

 gulp.task "build:clean", ->
@@ -62,6 +64,8 @@
 gulp.task "build_log", ->
   return gutil.log gutil.colors.white.bgBlue("Build..."), "Complete"

+gulp.task 'preview:jade', ['build_wintersmith'], reload
+gulp.task 'preview:md', ['build_wintersmith'], reload

 # =================================================================
 # css build tasks


the default gulp task should be to test

the default gulp task should be to test

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -6,7 +6,9 @@
 espruino = require './gulp-espruino/src/gulp-espruino'
 miniTest = require './src/deploy/gulp-mini-test'

-gulp.task 'default', ->
+gulp.task 'default', ['test']
+
+gulp.task 'deploy', ->
   gulp.src('./src/main/**/*.coffee')
     .pipe(coffee(bare: true).on('error', gutil.log))
     .pipe(concat('app.js'))


Clean up metafiles

Clean up metafiles

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -38,13 +38,13 @@
 gulp.task 'dist', ['js', 'clean-dist'], ->
 	gulp.src './build/*'
 		.pipe header("/* vtex.js #{pkg.version} */\n")
-		.pipe rename extname: "-#{pkg.version}.js"
 		.pipe gulp.dest './dist'
 		.pipe rename extname: ".min.js"
 		.pipe uglify outSourceMap: true
 		.pipe gulp.dest './dist'
 	gulp.src './build/*'
-		.pipe concat("vtex-#{pkg.version}.js")
+		.pipe concat("vtex.js")
+		.pipe gulp.dest './dist'
 		.pipe header("/* vtex.js #{pkg.version} */\n")
 		.pipe rename extname: '.min.js'
 		.pipe uglify outSourceMap: true


Added gulp watch

Added gulp watch

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -24,4 +24,7 @@
         console.log(stderr)
         cb(err)

+gulp.task 'watch', ['default'], ->
+    gulp.watch ['./src/*.coffee', './src/test.js'], ['default']
+
 gulp.task 'default', ['jqtree', 'build_test', 'lib']