Capture combined fleet packet from game api

--- views/index.cjsx
+++ views/index.cjsx
@@ -56,11 +56,12 @@
 parseBattlePacket = (packet) ->
   isCombined = packet.poi_is_combined
   isCarrier = packet.poi_is_carrier
+  uri = packet.poi_uri

   battleType = null
   # Normal Fleet
   if not isCombined
-    switch packet?.poi_uri
+    switch uri
       # Battle, Air battle
       when '/kcsapi/api_req_sortie/battle', '/kcsapi/api_req_practice/battle', '/kcsapi/api_req_sortie/airbattle'
         battleType = 'normal'
@@ -69,26 +70,26 @@
       when '/kcsapi/api_req_battle_midnight/battle', '/kcsapi/api_req_practice/midnight_battle', '/kcsapi/api_req_battle_midnight/sp_midnight'
         battleType = 'night'
         stageFlow = [StageType.Shelling]
-  # Carrier Task Force (空母機動部隊)
+  # Carrier Task Force
   if isCombined and isCarrier
-    switch packet?.poi_uri
+    switch uri
       # Battle, Air battle
-      when 'api_req_combined_battle/battle', 'api_req_combined_battle/airbattle'
+      when '/kcsapi/api_req_combined_battle/battle', '/kcsapi/api_req_combined_battle/airbattle'
         battleType = 'carrier'
         stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.Shelling]
       # Night battle
-      when 'api_req_combined_battle/midnight_battle'
+      when '/kcsapi/api_req_combined_battle/midnight_battle'
         battleType = 'night'
         stageFlow = [StageType.Shelling]
-  # Surface Task Force (水上打撃部隊)
+  # Surface Task Force
   if isCombined and not isCarrier
-    switch packet?.poi_uri
+    switch uri
       # Battle, Air battle
-      when 'api_req_combined_battle/battle_water', 'api_req_combined_battle/airbattle'
+      when '/kcsapi/api_req_combined_battle/battle_water', '/kcsapi/api_req_combined_battle/airbattle'
         battleType = 'surface'
         stageFlow = [StageType.AerialCombat, StageType.AerialCombat, StageType.Support, StageType.TorpedoSalvo, StageType.Shelling, StageType.Shelling, StageType.Shelling, StageType.TorpedoSalvo, StageType.Shelling]
       # Night battle
-      when 'api_req_combined_battle/midnight_battle'
+      when '/kcsapi/api_req_combined_battle/midnight_battle'
         battleType = 'night'
         stageFlow = [StageType.Shelling]

@@ -134,7 +135,30 @@
     {isCombined, isCarrier, battleComment, battlePackets, battlePacketsNonce, battleNonce, battleType, battleFlow} = @state
     isStateChanged = false

-    # Game states
+    # Combined Fleet Status
+    switch path
+      when '/kcsapi/api_port/port'
+        switch body.api_combined_flag
+          when 1  # 1=機動部隊
+            isStateChanged = true
+            isCombined = true
+            isCarrier = true
+          when 2  # 2=水上部隊
+            isStateChanged = true
+            isCombined = true
+            isCarrier = false
+          else
+            isStateChanged = true
+            isCombined = false
+            isCarrier = false
+      # Oh fuck. Someone sorties with No.3/4 fleet when having combined fleet.
+      when '/kcsapi/api_req_map/start'
+        if isCombined and parseInt(postBody.api_deck_id) != 1
+          isStateChanged = true
+          isCombined = false
+          isCarrier = false
+
+    # Battle Comment
     switch path
       when '/kcsapi/api_req_map/start', '/kcsapi/api_req_map/next'
         isStateChanged = true
@@ -154,6 +178,7 @@
     isBattle = false
     timestamp = new Date().getTime()
     switch path
+      # Normal fleet
       when '/kcsapi/api_req_sortie/battle', '/kcsapi/api_req_practice/battle', '/kcsapi/api_req_sortie/airbattle'
         isBattle = true
         isCombined = false
@@ -164,24 +189,60 @@
           oldBody = @state.battlePackets.shift()
           oldBody.api_hougeki = body.api_hougeki
           body = oldBody
-          isBattle = true
-          isCombined = false
-          sortieID = body.api_dock_id - 1
-          combinedID = null
           # Dont update packet metadata
           path = body.poi_uri
           timestamp = body.poi_timestamp
           battleComment = body.poi_comment
-        else
-          isBattle = true
-          isCombined = false
-          sortieID = body.api_deck_id - 1
-          combinedID = null
+        isBattle = true
+        isCombined = false
+        sortieID = if body.api_dock_id? then body.api_dock_id - 1 else body.api_deck_id - 1
+        combinedID = null
       when '/kcsapi/api_req_battle_midnight/sp_midnight'
         isBattle = true
         isCombined = false
         sortieID = body.api_deck_id - 1
         combinedID = null
+      # Carrier Task Force
+      when '/kcsapi/api_req_combined_battle/battle'
+        isBattle = true
+        isCombined = true
+        isCarrier = true
+        sortieID = body.api_deck_id - 1
+        combinedID = 1
+      # Surface Task Force
+      when '/kcsapi/api_req_combined_battle/battle_water'
+        isBattle = true
+        isCombined = true
+        isCarrier = false
+        sortieID = body.api_deck_id - 1
+        combinedID = 1
+      # Combined fleet shared api
+      when '/kcsapi/api_req_combined_battle/airbattle'
+        isBattle = true
+        isCombined = true
+        isCarrier = isCarrier
+        sortieID = body.api_deck_id - 1
+        combinedID = 1
+      when '/kcsapi/api_req_combined_battle/midnight_battle'
+        if @state.battlePackets[0]?
+          oldBody = @state.battlePackets.shift()
+          oldBody.api_hougeki = body.api_hougeki
+          body = oldBody
+          # Dont update packet metadata
+          path = body.poi_uri
+          timestamp = body.poi_timestamp
+          battleComment = body.poi_comment
+        isBattle = true
+        isCombined = true
+        isCarrier = isCarrier
+        sortieID = body.api_deck_id - 1
+        combinedID = 1
+      when '/kcsapi/api_req_combined_battle/sp_midnight'
+        isBattle = true
+        isCombined = true
+        isCarrier = isCarrier
+        sortieID = body.api_deck_id - 1
+        combinedID = 1

     if isBattle
       isStateChanged = true
@@ -196,7 +257,7 @@
         {battleType, battleFlow} = parseBattlePacket body
         battleNonce = updateNonce battleNonce

-    # update state
+    # Update State
     if isStateChanged
       @setState
         isCombined: isCombined


email type for auth

email type for auth

Former-commit-id: 2bdd6a762e8d1022f22aa83f2ed5b3b483b4feb3 [formerly e8cb6a49e0cc525737556d154f20941ccdc38f6c] [formerly f2d50a3616ede635032e013de859625b54201d83 [formerly 1004347d23191a3f994af350079f2d68036175da]] [formerly 2e2878e38673df9386f0aaa5e94c58241ecbac3b [formerly 6e8d3f719b61f9a748fa746f9fc4a497ae5753c8] [formerly 8718f9e51665c9a152d0640f51454b3b541e45bc [formerly 8718f9e51665c9a152d0640f51454b3b541e45bc [formerly 5a47eeec69c5c49c98df7e7235c13971e70b7357]]]]
Former-commit-id: 042e41b325d042797a3e4658953a632d9b27df24 [formerly d040933d56efaf9fc26eed355ca392e11947d10c] [formerly 4433ca21dfe9b1b8e3ea41eda113b74b414568ea [formerly d0f9f5d4016040327c512ce67134a02d94ad5e3f]]
Former-commit-id: e4fd705594925fd8fec1d2779e5d16339c4ad72c [formerly 1fb7bc17fcf1c75b9827f8e6049c3c66c9a594bd]
Former-commit-id: d505ebcedd4b6cbe58f858e69118efbea9660814
Former-commit-id: 40dd3ca6c0fa22251fd063aa78e58ac662c251ea

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -100,7 +100,7 @@
       when 'login'
         [ @headerAndBorder goal, 'Introduce Yourself',
             @body [['Hi, I log in as:',
-                    [ @inputBox('email', 'email@address'),
+                    [ @inputBox('email', 'email@address', 'email'),
                       DIV(null),
                       @inputBox('password', "password", 'password'),
                       @resetPasswordLink() ]]].concat @userQuestionInputs()
@@ -119,7 +119,7 @@
           pledges_field = ['I pledge to:', pledges]

         if auth.form == 'create account'
-          email_field = @inputBox('email', 'email@address')
+          email_field = @inputBox('email', 'email@address', 'email')
           footer = @footerForRegistrationAndLogin()
         else
           email_field = DIV
@@ -146,7 +146,7 @@
         [ @headerAndBorder null, 'Your Profile',
             @body [
               ['Hi, I log in as:',
-                [ @inputBox('email', 'email@address'),
+                [ @inputBox('email', 'email@address', 'email'),
                   @inputBox('password', "password", 'password')]
               ],
               ['My name is:', @inputBox('name', 'first and last name')],


Changing help and get_jobs' outputs to be based on the terminal width

--- src/queue_tea.coffee
+++ src/queue_tea.coffee
@@ -124,7 +124,9 @@
     for job, i in jobs
       name = job.file_name
       id = job.id.toString()
-      console.log "  #{i}) #{name} #{" ".padRight(".", 72-name.length)} job ##{job.id.pad(5)}"
+      prefix = "  #{i}) #{name} "
+      suffix = "job ##{job.id.pad(5)}  "
+      console.log "#{prefix.padRight(".", @cli.width - suffix.length - prefix.length - 1)} #{suffix}"
     console.log ""
     @cli.rl.prompt()
     @cli.rl.resume()
@@ -166,7 +168,7 @@
   _appendHelp: ->
     help = """
       Help
-      #{"".padLeft("-", 80)}
+      #{"".padLeft("-", @cli.width)}
       The following commands are available on your printer:\n\n
     """
     # The following commands are available on your printer,


getElementById fix (can't have a parent domain)

--- src/quo.query.coffee
+++ src/quo.query.coffee
@@ -13,7 +13,7 @@
         else if TAG_SELECTOR.test(selector)
             elements = domain.getElementsByTagName(selector)
         else if ID_SELECTOR.test(selector)
-            elements = domain.getElementById selector.replace("#", "")
+            elements = document.getElementById selector.replace("#", "")
             unless elements then elements = []
         else
             elements = domain.querySelectorAll selector


fixed test cases

fixed test cases

--- test/index.coffee
+++ test/index.coffee
@@ -25,8 +25,8 @@

       it 'should register respond listeners', ->

-        expect(@robot.respond).to.have.been.calledWith(/add (.*)|add/i)
-        expect(@robot.respond).to.have.been.calledWith(/rem (.*)|rem/i)
+        expect(@robot.respond).to.have.been.calledWith(/add (.*)$|add$/i)
+        expect(@robot.respond).to.have.been.calledWith(/rem (.*)$|rem$/i)

     describe 'commands', ->

@@ -34,27 +34,27 @@

         it 'should match with input strings', ->

-          expect('add').to.match(/add (.*)|add/i)
-          expect('add me').to.match(/add (.*)|add/i)
-          expect('add abc').to.match(/add (.*)|add/i)
+          expect('add').to.match(/add (.*)$|add$/i)
+          expect('add me').to.match(/add (.*)$|add$/i)
+          expect('add abc').to.match(/add (.*)$|add$/i)

       describe '!rem', ->

         it 'should match with input strings', ->

-          expect('rem').to.match(/rem (.*)|rem/i)
-          expect('rem me').to.match(/rem (.*)|rem/i)
-          expect('rem abc').to.match(/rem (.*)|rem/i)
+          expect('rem').to.match(/rem (.*)$|rem$/i)
+          expect('rem me').to.match(/rem (.*)$|rem$/i)
+          expect('rem abc').to.match(/rem (.*)$|rem$/i)

-  describe 'community module', ->
+  describe 'general module', ->

     describe 'listeners', ->

       it 'should register respond listeners', ->

-        expect(@robot.respond).to.have.been.calledWith(/status|games/i)
-        expect(@robot.respond).to.have.been.calledWith(/previous|lastgame/i)
-        expect(@robot.respond).to.have.been.calledWith(/(top|today) (maps|players)/i)
+        expect(@robot.respond).to.have.been.calledWith(/status$|games$/i)
+        expect(@robot.respond).to.have.been.calledWith(/previous$|lastgame$/i)
+        expect(@robot.respond).to.have.been.calledWith(/(top|today) (.*)$/i)

     describe 'commands', ->

@@ -62,24 +62,24 @@

         it 'should match with input strings', ->

-          expect('status').to.match(/status|games/i)
-          expect('games').to.match(/status|games/i)
+          expect('status').to.match(/status$|games$/i)
+          expect('games').to.match(/status$|games$/i)

       describe '!previous', ->

         it 'should match with input strings', ->

-          expect('previous').to.match(/previous|lastgame/i)
-          expect('lastgame').to.match(/previous|lastgame/i)
+          expect('previous').to.match(/previous$|lastgame$/i)
+          expect('lastgame').to.match(/previous$|lastgame$/i)

       describe '!top', ->

         it 'should match with input strings', ->

-          expect('top maps').to.match(/(top|today) (maps|players)/i)
-          expect('top players').to.match(/(top|today) (maps|players)/i)
-          expect('today maps').to.match(/(top|today) (maps|players)/i)
-          expect('today players').to.match(/(top|today) (maps|players)/i)
+          expect('top maps').to.match(/(top|today) (.*)$/i)
+          expect('top players').to.match(/(top|today) (.*)$/i)
+          expect('today maps').to.match(/(top|today) (.*)$/i)
+          expect('today players').to.match(/(top|today) (.*)$/i)

   describe 'rcon module', ->

@@ -87,9 +87,9 @@

       it 'should register respond listeners', ->

-        expect(@robot.respond).to.have.been.calledWith(/rcon (say|message|msg) (.*) to (.*)/i)
-        expect(@robot.respond).to.have.been.calledWith(/rcon send (list|the list|roster|players) to (.*)/i)
-        expect(@robot.respond).to.have.been.calledWith(/rcon (change map|changelevel|map) on (.*) to (.*)/i)
+        expect(@robot.respond).to.have.been.calledWith(/rcon (say|message|msg) (.*) to (.*)$/i)
+        expect(@robot.respond).to.have.been.calledWith(/rcon send (list|the list|roster|players) to (.*)$/i)
+        expect(@robot.respond).to.have.been.calledWith(/rcon (change map|changelevel|map) on (.*) to (.*)$/i)

     describe 'commands', ->

@@ -97,26 +97,26 @@

         it 'should match with input strings', ->

-          expect('rcon say abc to abc').to.match(/rcon (say|message|msg) (.*) to (.*)/i)
-          expect('rcon message abc to abc').to.match(/rcon (say|message|msg) (.*) to (.*)/i)
-          expect('rcon msg abc to abc').to.match(/rcon (say|message|msg) (.*) to (.*)/i)
+          expect('rcon say abc to abc').to.match(/rcon (say|message|msg) (.*) to (.*)$/i)
+          expect('rcon message abc to abc').to.match(/rcon (say|message|msg) (.*) to (.*)$/i)
+          expect('rcon msg abc to abc').to.match(/rcon (say|message|msg) (.*) to (.*)$/i)

       describe '!rcon send', ->

         it 'should match with input strings', ->

-          expect('rcon send list to abc').to.match(/rcon send (list|the list|roster|players) to (.*)/i)
-          expect('rcon send the list to abc').to.match(/rcon send (list|the list|roster|players) to (.*)/i)
-          expect('rcon send roster to abc').to.match(/rcon send (list|the list|roster|players) to (.*)/i)
-          expect('rcon send players to abc').to.match(/rcon send (list|the list|roster|players) to (.*)/i)
+          expect('rcon send list to abc').to.match(/rcon send (list|the list|roster|players) to (.*)$/i)
+          expect('rcon send the list to abc').to.match(/rcon send (list|the list|roster|players) to (.*)$/i)
+          expect('rcon send roster to abc').to.match(/rcon send (list|the list|roster|players) to (.*)$/i)
+          expect('rcon send players to abc').to.match(/rcon send (list|the list|roster|players) to (.*)$/i)

       describe '!rcon map', ->

         it 'should match with input strings', ->

-          expect('rcon change map on abc to abc').to.match(/rcon (change map|changelevel|map) on (.*) to (.*)/i)
-          expect('rcon changelevel on abc to abc').to.match(/rcon (change map|changelevel|map) on (.*) to (.*)/i)
-          expect('rcon map on abc to abc').to.match(/rcon (change map|changelevel|map) on (.*) to (.*)/i)
+          expect('rcon change map on abc to abc').to.match(/rcon (change map|changelevel|map) on (.*) to (.*)$/i)
+          expect('rcon changelevel on abc to abc').to.match(/rcon (change map|changelevel|map) on (.*) to (.*)$/i)
+          expect('rcon map on abc to abc').to.match(/rcon (change map|changelevel|map) on (.*) to (.*)$/i)

   describe 'admin module', ->

@@ -124,12 +124,12 @@

       it 'should register respond listeners', ->

-        expect(@robot.respond).to.have.been.calledWith(/sg|new/i)
-        expect(@robot.respond).to.have.been.calledWith(/cg|kill/i)
-        expect(@robot.respond).to.have.been.calledWith(/format (.*)/i)
-        expect(@robot.respond).to.have.been.calledWith(/(random (.*) map)|(map (.*))/i)
-        expect(@robot.respond).to.have.been.calledWith(/server (.*)/i)
-        expect(@robot.respond).to.have.been.calledWith(/change (.*) to (.*)/i)
+        expect(@robot.respond).to.have.been.calledWith(/sg$|new$/i)
+        expect(@robot.respond).to.have.been.calledWith(/cg$|kill$/i)
+        expect(@robot.respond).to.have.been.calledWith(/format ([0-9]*)$/i)
+        expect(@robot.respond).to.have.been.calledWith(/(random (.*) map)$|(map (.*))$/i)
+        expect(@robot.respond).to.have.been.calledWith(/server (.*)$/i)
+        expect(@robot.respond).to.have.been.calledWith(/change (.*) to (.*)$/i)

     describe 'commands', ->

@@ -137,37 +137,37 @@

         it 'should match with input strings', ->

-          expect('sg').to.match(/sg|new/i)
-          expect('new').to.match(/sg|new/i)
+          expect('sg').to.match(/sg$|new$/i)
+          expect('new').to.match(/sg$|new$/i)

       describe '!cg', ->

         it 'should match with input strings', ->

-          expect('cg').to.match(/cg|kill/i)
-          expect('kill').to.match(/cg|kill/i)
+          expect('cg').to.match(/cg$|kill$/i)
+          expect('kill').to.match(/cg$|kill$/i)

       describe '!format', ->

         it 'should match with input strings', ->

-          expect('format 5').to.match(/format (.*)/i)
+          expect('format 5').to.match(/format ([0-9]*)$/i)

       describe '!map', ->

         it 'should match with input strings', ->

-          expect('random abc map').to.match(/(random (.*) map)|(map (.*))/i)
-          expect('map abc').to.match(/(random (.*) map)|(map (.*))/i)
+          expect('random abc map').to.match(/(random (.*) map)$|(map (.*))$/i)
+          expect('map abc').to.match(/(random (.*) map)$|(map (.*))$/i)

       describe '!server', ->

         it 'should match with input strings', ->

-          expect('server abc').to.match(/server (.*)/i)
+          expect('server abc').to.match(/server (.*)$/i)

       describe '!change', ->

         it 'should match with input strings', ->

-          expect('change abc to abc').to.match(/change (.*) to (.*)/i)
+          expect('change abc to abc').to.match(/change (.*) to (.*)$/i)


doc: add rebuild command usage

doc: add rebuild command usage

--- src/rebuilder.coffee
+++ src/rebuilder.coffee
@@ -2,6 +2,7 @@

 _ = require 'underscore'
 require 'colors'
+optimist = require 'optimist'

 config = require './config'
 Command = require './command'
@@ -14,6 +15,19 @@
   constructor: ->
     @atomNodeDirectory = path.join(config.getAtomDirectory(), '.node-gyp')
     @atomNpmPath = require.resolve('npm/bin/npm-cli')
+
+  parseOptions: (argv) ->
+    options = optimist(argv)
+    options.usage """
+
+      Usage: apm rebuild
+
+      Rebuild all the modules currently installed in the node_modules folder
+      in the current working directory.
+    """
+    options.alias('h', 'help').describe('help', 'Print this usage message')
+
+  showHelp: (argv) -> @parseOptions(argv).showHelp()

   run: ({callback}) ->
     new Installer().installNode (error) =>


collab/participants: user list is actives + connecteds

--- client/activity/lib/models/collaborationchannelparticipants.coffee
+++ client/activity/lib/models/collaborationchannelparticipants.coffee
@@ -29,10 +29,11 @@
   computePreviewParticipants: (participants) ->

     if @state.videoActive
-      filtered = participants.filter (participant) =>
-        participant.profile.nickname in @state.videoParticipants
-
-      return filtered.toList()
+      filterList = getOrderedVideoParticipants @state
+      actives = participants.filter (participant) =>
+        participant.profile.nickname in filterList
+
+      return actives.toList()

     super

@@ -49,11 +50,11 @@
   computeHiddenParticipants: (participants) ->

     if @state.videoActive
-      filtered = participants.filter (participant) =>
-        {nickname} = participant.profile
-        return @state.videoParticipants.indexOf(nickname) is -1
-
-      return filtered.toList()
+      filterList = getOrderedVideoParticipants @state
+      inactives = participants.filter (participant) =>
+        not (participant.profile.nickname in filterList)
+
+      return inactives.toList()

     super

@@ -232,3 +233,25 @@
     callback?()

+###*
+ * Orders given state's connected participants in a way that publishing users
+ * come first.
+ *
+ * @param {object} state
+ * @param {array.<string>} state.videoParticipants
+ * @param {array.<string>} state.connectedParticipants
+ * @return {array.<string>} orderedList
+###
+getOrderedVideoParticipants = (state) ->
+
+  actives = state.videoParticipants
+
+  # connectedParticipants has already have the active participant ids.
+  # that's why we are diff'ing to get actives b
+  connecteds = _.difference state.connectedParticipants, state.videoParticipants
+
+  # all the diffing and other stuff is for showing video active
+  # participants first and not publishing but connected people after those.
+  filterList = actives.concat connecteds
+
+


minor

minor

--- client/app/Applications/Account.kdapplication/AppController.coffee
+++ client/app/Applications/Account.kdapplication/AppController.coffee
@@ -70,13 +70,12 @@
       """

     # SET UP SPLIT VIEW AND TOGGLERS
-    @split = split = new SplitView
+    mainView.addSubView @split = split = new SplitView
       domId     : "account-split-view"
-      sizes     : [188,null]
-      views     : [navView,wrapperView]
-      minimums  : [null,null]
+      sizes     : [188, null]
+      views     : [navView, wrapperView]
+      minimums  : [null, null]
       resizable : yes
-    mainView.addSubView split

     [panel0, panel1] = split.panels


First Multi Load Test

First Multi Load Test

--- test/streams/load.coffee
+++ test/streams/load.coffee
@@ -1,4 +1,54 @@
 #
 # # Load Stream Tests
 #
+path      = require 'path'
+should    = require 'should'

+
+
+stubsdir  = path.join process.cwd(), 'build', 'test', 'stubs'
+
+
+describe 'LoadStream()', ->
+  stub_files    = [
+    path.join stubsdir, 'nodeps', 'main.coffee'
+    path.join stubsdir, 'nodeps', 'manifest.json'
+  ]
+  LoadStream    = null
+  before -> {LoadStream} = require '../../lib/streams/load'
+
+
+  it 'should load multiple files in a single stream', (done) ->
+    d = ''
+    s = new LoadStream stub_files
+    s.pipe require('fs').createWriteStream 'build/foo'
+    s.on 'data', (chunk) -> d += chunk
+    s.on 'end', ->
+      d.should.equal expected
+      return done()
+
+    expected = """
+    {
+      "name": "Stub",
+      "path": ".",
+      "source": {
+        "blocks": {
+          "app": {
+            "files": [
+              "./main.coffee"
+             ]
+          }
+        }
+      }
+    }
+
+    (function() {
+      (function() {
+        return new KDNotificationView({
+          title: 'Stub'
+        });
+      })();
+
+    }).call(this);
+
+    """


fix $scan

fix $scan

--- alight/core.coffee
+++ alight/core.coffee
@@ -978,12 +978,18 @@

     mainLoop = 10
     try
+        result =
+            total: 0
+            obTotal: 0
+            changes: 0
+            extraLoop: false
+            src: ''
+
         while mainLoop
             mainLoop--

             root.$system.extraLoop = false

-            result = {}
             if root.$system.useObserver
                 scan_core2 root, result
             else


cleunp

cleunp

--- lib/motions.coffee
+++ lib/motions.coffee
@@ -263,7 +263,7 @@

 class MoveToAbsoluteLine extends MoveToLine
   moveCursor: (cursor, count) ->
-    console.log "called!! #{count}"
+    # console.log "called!! #{count}"
     cursor.setBufferPosition([@getDestinationRow(count), Infinity])
     cursor.moveToFirstCharacterOfLine()
     cursor.moveToEndOfLine() if cursor.getBufferColumn() is 0


Add test about class this

Add test about class this

--- test/type_checker.coffee
+++ test/type_checker.coffee
@@ -407,3 +407,18 @@
       hash.set "", 1
       hash.get 1
       """
+
+
+    test 'access this in class', ->
+      class X
+        foo :: Number
+        constructor: ->
+          @foo = 3
+
+    test 'throw access this in class', ->
+      throws -> CoffeeScript.parse """
+      class X
+        foo :: Number
+        constructor: ->
+          @foo = 'fuga'
+      """


the typeof intrinsics are readonly and return ejsval bools

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -44,13 +44,13 @@

         init_string_literal:   -> @module.getOrInsertExternalFunction "_ejs_string_init_literal",       types.void, [types.string, types.EjsValue.pointerTo(), types.EjsPrimString.pointerTo(), types.jschar.pointerTo(), types.int32]

-        typeof_is_object:      -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_object",       types.EjsValue, [types.EjsValue]
-        typeof_is_function:    -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_function",     types.EjsValue, [types.EjsValue]
-        typeof_is_string:      -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_string",       types.EjsValue, [types.EjsValue]
-        typeof_is_number:      -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_number",       types.EjsValue, [types.EjsValue]
-        typeof_is_undefined:   -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_undefined",    types.EjsValue, [types.EjsValue]
-        typeof_is_null:        -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_null",         types.EjsValue, [types.EjsValue]
-        typeof_is_boolean:     -> @module.getOrInsertExternalFunction "_ejs_op_typeof_is_boolean",      types.EjsValue, [types.EjsValue]
+        typeof_is_object:      -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_object",       types.EjsValue, [types.EjsValue]
+        typeof_is_function:    -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_function",     types.EjsValue, [types.EjsValue]
+        typeof_is_string:      -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_string",       types.EjsValue, [types.EjsValue]
+        typeof_is_number:      -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_number",       types.EjsValue, [types.EjsValue]
+        typeof_is_undefined:   -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_undefined",    types.EjsValue, [types.EjsValue]
+        typeof_is_null:        -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_null",         types.EjsValue, [types.EjsValue]
+        typeof_is_boolean:     -> returns_ejsval_bool only_reads_memory @module.getOrInsertExternalFunction "_ejs_op_typeof_is_boolean",      types.EjsValue, [types.EjsValue]

         undefined:             -> @module.getOrInsertGlobal           "_ejs_undefined",                 types.EjsValue
         "true":                -> @module.getOrInsertGlobal           "_ejs_true",                      types.EjsValue


runtime: Also specify fine-grained capabilities

Introduced in version 0.6 of protocol

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -124,6 +124,10 @@
             "protocol:network"
             "protocol:component"
             "protocol:runtime"
+            'graph:readonly'
+            'network:control'
+            'network:status'
+            'network:data'
             "component:getsource"
         ]
         r =


fix bug where joyent_count wasn't being set to zero when unchecked on edit

--- views/application.js.coffee
+++ views/application.js.coffee
@@ -81,7 +81,7 @@

       $('#attending_count').slideDown('fast').find('input[type=text]').val(size).select()
     else
-      $('#attending_count').slideUp('fast')
+      $('#attending_count').find('input[type=text]').val(0).slideUp('fast')

   if $('.body._teams_').length > 0
     getAttendingCount: ->


read dyno size from config var

read dyno size from config var

--- lib/spawner.coffee
+++ lib/spawner.coffee
@@ -35,7 +35,7 @@
     data["ps_env[#{key}]"] = val for key, val of options.env # only add desired env
     data["attach"] = "true"
     data["command"] = command
-    data["size"] = 8
+    data["size"] = (process.env.DYNO_SIZE || "2")

     request = restler.post "https://#{host}/apps/#{app}/ps",
       headers:


namespaced Movable

namespaced Movable

--- src/rotatable.coffee
+++ src/rotatable.coffee
@@ -8,16 +8,15 @@
   y: 0
   rotationalVelocity: Math.PI / 64

-
 player.I.rotation
 # => 0

-player.update()
+player.update(1)

 player.I.rotation
 # => 0.04908738521234052 # Math.PI / 64

-player.update()
+player.update(1)

 player.I.rotation
 # => 0.09817477042468103 # 2 * (Math.PI / 64)


window.appManager global converted to a KD.singleton

--- core/utils.coffee
+++ core/utils.coffee
@@ -476,7 +476,7 @@

     KD.remote.api.JStatusUpdate.create body : status, (err,reply)=>
       unless err
-        appManager.tell 'Activity', 'ownActivityArrived', reply
+        KD.getSingleton("appManager").tell 'Activity', 'ownActivityArrived', reply
       else
         new KDNotificationView type : "mini", title : "There was an error, try again later!"


Worlds simplest bloody fix.

Worlds simplest bloody fix.

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -66,6 +66,8 @@
       @targetPage = document.querySelector targetPage
     else if targetPage
       @targetPage = targetPage
+
+    return if @targetPage is @currentPage

     if @os.android and @os.version < '4'
       @iScroll = new iScroll @targetPage.querySelector('.scrollview-inner')


Fixes to the commands so that instead of this:
[bot name] how am i? - details about the current channel + you
[bot name] sentiment - lists sentiment stats
[bot name] where's happiness? - which rooms are happiest?
[bot name] where's stress? - which rooms are stressed out?
[bot name] who's happy? - who's happiest?
[bot name] who's stressed? - who's stressed out?
chatbot adapter - Reply with the adapter

we now use hubot so we get a nicer output:
chatbot adapter - Reply with the adapter
chatbot how am i? - details about the current channel + you
chatbot sentiment - lists sentiment stats
chatbot where's happiness? - which rooms are happiest?
chatbot where's stress? - which rooms are stressed out?
chatbot who's happy? - who's happiest?
chatbot who's stressed? - who's stressed out?

--- src/sentiment.coffee
+++ src/sentiment.coffee
@@ -3,12 +3,12 @@
 #   All bot commands can be sent via PM as well.
 #
 # Commands:
-#   [bot name] sentiment - lists sentiment stats
-#   [bot name] how am i? - details about the current channel + you
-#   [bot name] who's happy? - who's happiest?
-#   [bot name] who's stressed? - who's stressed out?
-#   [bot name] where's happiness? - which rooms are happiest?
-#   [bot name] where's stress? - which rooms are stressed out?
+#   hubot sentiment - lists sentiment stats
+#   hubot how am i? - details about the current channel + you
+#   hubot who's happy? - who's happiest?
+#   hubot who's stressed? - who's stressed out?
+#   hubot where's happiness? - which rooms are happiest?
+#   hubot where's stress? - which rooms are stressed out?
 #
 # Author:
 #   Michi Kono


Change max limit to 40 to prevent maxing out memmory

--- coffee/collection.coffee
+++ coffee/collection.coffee
@@ -26,7 +26,7 @@
   fields = navn: true, endret: true if req.usr isnt 'NRK' # @TODO Hack for NRK river

   options =
-    limit: Math.min((parseInt(req.query.limit) or 20), 100)
+    limit: Math.min((parseInt(req.query.limit) or 20), 40)
     skip: parseInt(req.query.skip) or 0
     sort: 'endret'


contestant pre

contestant pre

--- scripts/contestant-msg.coffee
+++ scripts/contestant-msg.coffee
@@ -16,7 +16,7 @@
   currentTeam.people (err, people) ->
     return callback(err) if err
     emailable = (person for person in people when /@/.test(person.email))
-    util.log "Sending 'contestant_pre_1hr' to '#{team.name}'".yellow
+    util.log "Sending 'contestant_start' to '#{team.name}'".yellow
     async.forEach emailable, msgPerson, callback

 msgPerson = (person, callback) ->
@@ -32,7 +32,7 @@

   postageapp.sendMessage
     recipients: address
-    template: 'contestant_pre_1hr'
+    template: 'contestant_start'
     variables:
       team: " #{currentTeam.name}"
       slug: " #{currentTeam.slug}"


testsuite: Fix non-visible chars breaking CoffeeScript compile

--- src/testsuite.coffee
+++ src/testsuite.coffee
@@ -42,7 +42,6 @@
   else
     throw new Error 'fbpspec.testsuite: Loading over HTTP not supported on node.js'

-
 getFileSync = (test, callback) ->
   fs = require 'fs'
   path = require 'path'
@@ -59,7 +58,7 @@
 exports.getSuitesSync = getSuitesSync = (tests) ->
   tests = [ tests ] if not Array.isArray tests

-  suites = []
+  suites = []
   for test in tests
     suites = suites.concat getFileSync(test)
   return suites


Converted dashboard data fetcher to coffee


no need for test artifact headline

no need for test artifact headline

--- client/testclient.coffee
+++ client/testclient.coffee
@@ -13,6 +13,5 @@
 require('wiki-client/test/search')

 $ ->
-  $('

 Testing artifacts:
').appendTo('body')
   mocha.run()


Verify if isCordova before change status bar style

--- client/views/main.coffee
+++ client/views/main.coffee
@@ -70,10 +70,12 @@

 	logged: ->
 		if Meteor.userId()?
-			StatusBar.styleDefault()
+			if Meteor.isCordova
+				StatusBar.styleDefault()
 			return true
 		else
-			StatusBar.styleLightContent()
+			if Meteor.isCordova
+				StatusBar.styleLightContent()
 			return false

 	subsReady: ->


added event loop monitor

added event loop monitor

--- services/filestore/app.coffee
+++ services/filestore/app.coffee
@@ -13,6 +13,7 @@
 Metrics = require "metrics-sharelatex"
 Metrics.initialize("filestore")
 Metrics.open_sockets.monitor(logger)
+Metrics.event_loop?.monitor(logger)

 app.configure ->
 	app.use express.bodyParser()


Update dispatch message

Update dispatch message

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -162,8 +162,7 @@
   # or if they have a pending confirmation.
   # or if they are premium
   # or if we are already showing them a message for exceeding the limit
-  # or if they have less than 5 private connections
-
+
   shouldReturn = !current_user.id or
     current_user.get('is_pending_confirmation') or
     current_user.get('is_premium') or
@@ -171,13 +170,15 @@

   return if shouldReturn

-  messages =
-    utility: """
-      Are.na Premium gives you unlimited space for private content. Get 3 months free when you upgrade today with the coupon code summer.
-    """
-    lifestyle: """
-      We're having a summer sale to celebrate 3 years of Are.na Premium, our first step towards a self-sustaining, ad-free community. Get 3 months free when you upgrade with the coupon code summer.
-    """
+  model = new Backbone.Model
+    id: 'arena_dispatch'
+    title: '📬'
+    body: "Are.na Dispatch is a biweekly selection of channels and blog posts delivered to your inbox.
Subscribe here</>."
+
+  messageView = new MessageView model: model
+
+  if messageView.isRenderable()
+    $('body').append messageView.render().$el

 showLimitMessage = ->


Refactoring js code from requerejs to class CS

--- app/js/main.coffee
+++ app/js/main.coffee
@@ -1,9 +1,12 @@
 control = new Control()
 CanvasControl = control.getCanvas()

-(CanvasControl, CanvasInput, imgLoader, jsonLoader, TileField, pathfind, EffectLoader, utils) ->
+
+
+init = (CanvasControl, CanvasInput, imgLoader, jsonLoader, TileField, pathfind, EffectLoader, utils) ->
   # -- FPS --------------------------------
   #TODO: сделать дравинг в зависимости от размера экрана
+
   launch = ->
     jsonLoader([
       gameScheme.map
@@ -222,3 +225,5 @@
     imageFiles: 'imageFiles.json'
   launch()
   return
+
+init(CanvasControl)


fix bug in image browse (mimetype)

fix bug in image browse (mimetype)

--- webapp/views/ThingEdit.coffee
+++ webapp/views/ThingEdit.coffee
@@ -130,7 +130,7 @@
       console.log "set image #{url}"
       $(selector, self.$el).attr 'src', url

-    window.open "filebrowse.html?type=#{encodeURIComponent(mimetype) ? 'image%2F'}&mediahubCallback=#{@callback}", '_blank', "width=#{0.8*screen.width}, height=#{0.7*screen.height}, menubar=no, location=no, status=no, toolbar=no"
+    window.open "filebrowse.html?type=#{encodeURIComponent(mimetype ? 'image/')}&mediahubCallback=#{@callback}", '_blank', "width=#{0.8*screen.width}, height=#{0.7*screen.height}, menubar=no, location=no, status=no, toolbar=no"

   remove: () =>


Increase worker pool

Increase worker pool

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -64,11 +64,11 @@
   authWorker    :
     login       : 'prod-authworker'
     queueName   : socialQueueName+'auth'
-    numberOfWorkers: 1
+    numberOfWorkers: 2
     watch       : yes
   social        :
     login       : 'prod-social'
-    numberOfWorkers: 1
+    numberOfWorkers: 20
     watch       : yes
     queueName   : socialQueueName
   cacheWorker   :


Sync with schema

Sync with schema

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -93,6 +93,7 @@
     useStaticFileServer: no
     staticFilesBaseUrl: "http://#{version}.x.koding.com"
     runtimeOptions:
+      logToExternal : 1
       resourceName: socialQueueName
       suppressLogs: no
       version   : version


Fix some slideshare issue when cannot fetching image. Fix slide number

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -402,12 +402,18 @@
         return $('.slidesContainer .jsplBgColorBigfoot').length

     getCurrentSlideNumber: () ->
-      1 + parseInt($('.goToSlideLabel > input', @container).val())
+      n = parseInt($('.goToSlideLabel > input', @container).val())
+      if n<=1
+        return 1
+      return n

     getCurrentSlideScreenshot: () ->
-      if $('.slide_container > .slide').eq(this.getCurrentSlideNumber()).find('img').length > 0
-        return $('.slide_container > .slide').eq(this.getCurrentSlideNumber()).find('img').prop('src')
-      return ''
+      n = this.getCurrentSlideNumber()
+      if $('.slide_container > .slide').eq(n-1).find('img').length > 0
+        url = $('.slide_container > .slide').eq(n-1).find('img').prop('src')
+        if url.indexOf('image')>=1
+          return url
+      return "http://placehold.it/320&text=#{n}"

     jump: (num) ->
       e = $.Event 'keyup'


add comment about automatically leaving rooms

--- services/real-time/app/coffee/RoomManager.coffee
+++ services/real-time/app/coffee/RoomManager.coffee
@@ -27,6 +27,8 @@

     leaveProjectAndDocs: (client) ->
         # what rooms is this client in? we need to leave them all
+        # FIXME: socket.io will cause us to leave the rooms, so we only need
+        # to manage our channel subscriptions
         for id in @_roomsClientIsIn(client)
             entity = IdMap.get(id)
             @_leave client, entity, id


FSItem: latest version of oskite.exec is expecting an object instead string

--- client/Finder/fs/fsitem.coffee
+++ client/Finder/fs/fsitem.coffee
@@ -53,7 +53,7 @@

         command = "#{ commandPrefix } #{ escapeFilePath sourceItem.path } #{ escapeFilePath actualPath }"

-        kite.exec(command)
+        kite.exec({command})

         .then(handleStdErr())

@@ -103,7 +103,7 @@
         else
           "zip -r #{escapeFilePath actualPath} #{escapeFilePath file.path}"

-      kite.exec command
+      kite.exec {command}

     .then(handleStdErr())

@@ -143,7 +143,7 @@
         else
           "cd #{escapeFilePath file.parentPath};unzip -o #{escapeFilePath file.name} -d #{escapeFilePath actualPath}"

-      kite.exec(command)
+      kite.exec({command})

     .then(handleStdErr())


client: show friendly err message instead of 'CPU limit reached'

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -410,7 +410,11 @@
       err   = JSON.parse err.message
       title = err.message  if err.message

-    title ?= "VM start failed. Please try again, later"
+    if /CPU limit reached/.test title
+      title = "You've exceeded the allowed number of concurrent vms. Please upgrade."
+    else
+      title = "Your vm failed to start. Please try again later."
+
     new KDNotificationView {title}

   pistachio: ->


Convert artworks, images, and image sets with width/height

--- client/apps/edit/client.coffee
+++ client/apps/edit/client.coffee
@@ -13,71 +13,95 @@
 EditDisplay = require './components/display/index.coffee'
 SectionList = React.createFactory require './components/section_list/index.coffee'
 HeroSection = React.createFactory require './components/hero_section/index.coffee'
+async = require 'async'

 @init = ->
   article = new Article sd.ARTICLE
-  channel = new Channel sd.CURRENT_CHANNEL
-  new EditLayout el: $('#layout-content'), article: article, channel: channel
-  new EditHeader el: $('#edit-header'), article: article
-  new EditDisplay el: $('#edit-display'), article: article
-  new EditAdmin el: $('#edit-admin'), article: article, channel: channel
-
-  React.render(
-    SectionList(sections: article.sections)
-    $('#edit-sections')[0]
+  convertSections article, =>
+    channel = new Channel sd.CURRENT_CHANNEL
+    new EditLayout el: $('#layout-content'), article: article, channel: channel
+    new EditHeader el: $('#edit-header'), article: article
+    new EditDisplay el: $('#edit-display'), article: article
+    new EditAdmin el: $('#edit-admin'), article: article, channel: channel
+    React.render(
+      SectionList(sections: article.sections)
+      $('#edit-sections')[0]
+    )
+    if article.get('hero_section') != null or channel.hasFeature 'hero'
+      React.render(
+        HeroSection(section: article.heroSection, hasSection: article.get('section_ids')?.length > 0)
+        $('#edit-hero-section')[0]
+      )
+
+convertSections = (article, callback) ->
+  async.parallel(
+    article.sections.map (section) ->
+      (cb) ->
+        if section.get('type') is 'image'
+          convertImages section, cb
+        else if section.get('type') is 'artworks'
+          convertArtworks section, cb
+        else if section.get('type') is 'image_set'
+          convertImageSet section, cb
+        else
+          cb()
+  , =>
+    callback()
   )
-  if article.get('hero_section') != null or channel.hasFeature 'hero'
-    React.render(
-      HeroSection(section: article.heroSection, hasSection: article.get('section_ids')?.length > 0)
-      $('#edit-hero-section')[0]
-    )

-  convertToImageCollection = (callback) ->
-    console.log @props.sections
-    async.eachSeries @props.sections.models, (section, cb) ->
-      if section.get('type') is 'image'
-        @convertImages section, cb
-      else if section.get('type') is 'artwork'
-        @convertArtworks section, cb
-      else
-        cb()
-    , =>
-      callback()
+convertImageSet = (section, callback) ->
+  async.parallel(
+    for image in section.get('images')
+      (cb) ->
+        img = new Image()
+        img.src = image.image or image.url # image or artwork
+        img.onload = ->
+          image.width = img.width
+          image.height = img.height
+          cb()
+  , =>
+    callback()
+  )

-  convertImages = (section, callback) ->
-    (img = new Image(src: section.get('url'))).onload ->
-      image = {
-        type: 'image_collection'
-        images: [{
-          type: section.get('type')
-          url: section.get('url')
-          caption: section.get('caption')
-        }]
-        layout: section.get('layout') or 'overflow_fillwidth'
+convertImages = (section, callback) ->
+  img = new Image()
+  img.src = section.get('url')
+  img.onload = ->
+    image = {
+      type: 'image_collection'
+      images: [{
+        type: 'image'
+        url: section.get('url')
+        caption: section.get('caption')
         width: img.width
         height: img.height
-      }
-      section.clear()
-      section.set image
-      console.log image
-      callback()
+      }]
+      layout: section.get('layout') or 'overflow_fillwidth'
+    }
+    section.clear()
+    section.set image
+    callback()

-  convertArtworks = (section, callback) ->
-    images = []
-    async.eachSeries section.artworks, (artwork, cb) ->
-      (img = new Image(src: section.get('image'))).onload ->
-        artwork.type = 'artwork'
-        artwork.width = img.width
-        artwork.height = img.height
-        images.push artwork
-        cb()
-    , =>
-      image = {
-        type: 'image_collection'
-        images: images
-        layout: section.get('layout') or 'overflow_fillwidth'
-      }
-      section.clear()
-      section.set artworks
-      console.log image
-      callback()
+convertArtworks = (section, callback) ->
+  images = []
+  async.parallel(
+    for artwork in section.get('artworks')
+      (cb) ->
+        img = new Image()
+        img.src = artwork.image
+        img.onload = ->
+          artwork.type = 'artwork'
+          artwork.width = img.width
+          artwork.height = img.height
+          images.push artwork
+          cb()
+  , =>
+    image = {
+      type: 'image_collection'
+      images: images
+      layout: section.get('layout') or 'overflow_fillwidth'
+    }
+    section.clear()
+    section.set image
+    callback()
+  )


Change mongohq.com to compose.io

Change mongohq.com to compose.io

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -121,7 +121,7 @@
               cb_datasets: 0
               switch_url: "https://scraperwiki.com/switch/" + real.shortName
               recurly_url: "https://scraperwiki.recurly.com/accounts/" + real.recurlyAccount
-              mongo_url: "https://app.mongohq.com/mongohq-scraperwiki-com/mongo/cu-live-eu/collections/users?query=%7B%22shortName%22%3A%22" + real.shortName + "%22%7D&fields=%7B%7D&sort=%7B%7D&limit=10"
+              mongo_url: "https://app.compose.io/mongohq-scraperwiki-com/mongo/cu-live-eu/collections/users?query=%7B%22shortName%22%3A%22" + real.shortName + "%22%7D&fields=%7B%7D&sort=%7B%7D&limit=10"

             _.each datasets, (dataset) ->
               date = moment(dataset.createdDate, 'YYYY-MM-DD HH:mm:ssZ').unix()


Do not add dependencies

Do not add dependencies

--- views/services/plugin-manager.cjsx
+++ views/services/plugin-manager.cjsx
@@ -319,6 +319,8 @@
       for packs in data[0]
         dump = false
         packName = packs[0].split('@')[0]
+        if packName.indexOf("poi-plugin") == -1
+          continue
         packVersion = packs[0].split('@')[1]
         for plugin_, index in validPlugins
           if packName == plugin_.packageName


video embed was causing errors when iframe isnt the direct child of the HTML component

--- src/helpers/html-videos.coffee
+++ src/helpers/html-videos.coffee
@@ -17,7 +17,7 @@

     wrapper = document.createElement("div")
     wrapper.className = "frame-wrapper embed-responsive #{getRatioClass(frame)}"
-    dom.replaceChild(wrapper, frame)
+    frame.parentNode.replaceChild(wrapper, frame)
     wrapper.appendChild(frame)
   )


Fixing issue from https://github.com/apiaryio/dredd/pull/511#issuecomment-224866725.

--- src/hooks-worker-client.coffee
+++ src/hooks-worker-client.coffee
@@ -308,20 +308,19 @@

         @emitter.on uuid, messageHandler

-    @runner.hooks.afterAll (transactions, hookCallback) =>
-
+    @runner.hooks.afterAll((transactions, hookCallback) =>
       # This is needed for transaction modification integration tests:
       # https://github.com/apiaryio/dredd-hooks-template/blob/master/features/execution_order.feature
-      if process.env['TEST_DREDD_HOOKS_HANDLER_ORDER'] == "true"
-        console.error 'FOR TESTING ONLY'
-        unless mods = transactions[0]?.hooks_modifications?
-          throw new Error "Hooks must modify transaction.hooks_modifications"
-        for mod, index in mods
-          console.error "#{index} #{mod}"
-        console.error 'FOR TESTING ONLY'
-
-
-      @stop hookCallback
+      if process.env.TEST_DREDD_HOOKS_HANDLER_ORDER is 'true'
+        console.error('FOR TESTING ONLY')
+        modifications = transactions[0]?.hooks_modifications or []
+        unless modifications.length
+          throw new Error('Hooks must modify transaction.hooks_modifications')
+        for modification, index in modifications
+          console.error("#{index} #{modification}")
+        console.error('FOR TESTING ONLY')
+      @stop(hookCallback)
+    )

     callback()


Ensure that time is monotonic when running autocompile

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -80,8 +80,11 @@
 			return if autoCompileTimeout

 			timeSinceLastCompile = Date.now() - $scope.recompiledAt
-
-			if timeSinceLastCompile >= AUTO_COMPILE_TIMEOUT
+			# If time is non-monotonic, assume that the user's system clock has been
+			# changed and continue with recompile
+			isTimeNonMonotonic = timeSinceLastPoll < 0;
+
+			if isTimeNonMonotonic || timeSinceLastCompile >= AUTO_COMPILE_TIMEOUT
 				if (!ide.$scope.hasLintingError)
 					$scope.recompile(isBackgroundAutoCompile: true)
 			else


Add question mark to restore modal

Add question mark to restore modal

--- services/web/public/coffee/track-changes/TrackChangesManager.coffee
+++ services/web/public/coffee/track-changes/TrackChangesManager.coffee
@@ -64,7 +64,7 @@
 			date = moment(change.get("start_ts")).format("Do MMM YYYY, h:mm:ss a")
 			modal = new Modal({
 				title: "Restore document"
-				message: "Are you sure you want to restore #{name} to before the changes on #{date}"
+				message: "Are you sure you want to restore #{name} to before the changes on #{date}?"
 				buttons: [{
 					text: "Cancel"
 				}, {


Fix id of example profile

Fix id of example profile

--- www/coffee/controllers.coffee
+++ www/coffee/controllers.coffee
@@ -29,7 +29,7 @@
       profiles:
         '0':
           name: "Profile 1"
-          id: 1
+          id: 0
           location: 12
         '1':
           name: "Profile 2"


Hide the start port when loading subgraphs as components

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -97,6 +97,7 @@
     graphSocket.send @components[name]
     graphSocket.disconnect()
     delete graph.inPorts.graph
+    delete graph.inPorts.start
     callback graph

   registerComponent: (packageId, name, cPath, callback) ->


add language for nginx and a linter for ruby

--- atom/packages.cson
+++ atom/packages.cson
@@ -22,7 +22,9 @@
   "language-docker"
   "language-elixir"
   "language-lisp"
+  "language-nginx"
   "language-rust"
+  "linter-ruby"
   "linter-rust"
   "linter"
   "minimap-color-highlight"


Add several packages to Atom package list

--- atom/packages.cson
+++ atom/packages.cson
@@ -2,9 +2,12 @@
   "editorconfig"
   "file-icons"
   "highlight-selected"
+  "language-docker"
+  "language-typescript-grammars-only"
   "linter"
   "linter-coffeelint"
   "linter-csslint"
+  "linter-docker"
   "linter-flake8"
   "linter-htmlhint"
   "linter-js-yaml"
@@ -14,6 +17,8 @@
   "linter-php"
   "linter-scss-lint"
   "linter-shellcheck"
+  "linter-tslint"
+  "linter-write-good"
   "linter-xmllint"
   "minimap"
   "omni-ruler"


Fix Python docstring snippet

Fix Python docstring snippet


adding objects by class name only

adding objects by class name only

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -66,4 +66,4 @@
     loadLevel I.currentLevelName

   reloadLevel: ->
-    self.
+    self.restartLevel


remove versioning from canonical link

remove versioning from canonical link

--- tutor/src/components/book-content-mixin.cjsx
+++ tutor/src/components/book-content-mixin.cjsx
@@ -170,13 +170,15 @@

   updateCanonicalLink: ->
     cnxId = @props.cnxId or @getCnxId?()
+    # leave versioning out of canonical link
+    canonicalCNXId = _.first(cnxId.split('@'))
     {courseId} = Router.currentParams()
     {webview_url} = CourseStore.get(courseId)
     baseWebviewUrl = _.first(webview_url.split('/contents/'))

     # webview actually links to webview_url as it's canonical url.
     # will need to ask them why.
-    @linkNode.href = "#{baseWebviewUrl}/contents/#{cnxId}"
+    @linkNode.href = "#{baseWebviewUrl}/contents/#{canonicalCNXId}"

   removeCanonicalLink: ->
     # document.head.


Add tests for transactions table labels.

--- spec/javascripts/dot_ledger/views/transactions/table_spec.js.coffee
+++ spec/javascripts/dot_ledger/views/transactions/table_spec.js.coffee
@@ -15,3 +15,23 @@
   it "can be rendered", ->
     view = createView()
     expect(view.render).not.toThrow()
+
+  it "renders the date label", ->
+    view = createView().render()
+    expect(view.$el).toContainText('Date')
+
+  it "renders the name label", ->
+    view = createView().render()
+    expect(view.$el).toContainText('Name')
+
+  it "renders the category label", ->
+    view = createView().render()
+    expect(view.$el).toContainText('Category')
+
+  it "renders the spent label", ->
+    view = createView().render()
+    expect(view.$el).toContainText('Spent')
+
+  it "renders the received label", ->
+    view = createView().render()
+    expect(view.$el).toContainText('Received')


Removing '?' to meet expectations of js spec

--- spec/javascripts/unit/order_cycle_spec.js.coffee
+++ spec/javascripts/unit/order_cycle_spec.js.coffee
@@ -336,7 +336,7 @@
       inject ($injector, _$httpBackend_)->
         Enterprise = $injector.get('Enterprise')
         $httpBackend = _$httpBackend_
-        $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json?').respond [
+        $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json').respond [
           {id: 1, name: 'One', supplied_products: [1, 2], is_primary_producer: true}
           {id: 2, name: 'Two', supplied_products: [3, 4]}
           {id: 3, name: 'Three', supplied_products: [5, 6], sells: 'any'}
@@ -412,7 +412,7 @@
       inject ($injector, _$httpBackend_)->
         EnterpriseFee = $injector.get('EnterpriseFee')
         $httpBackend = _$httpBackend_
-        $httpBackend.whenGET('/admin/enterprise_fees/for_order_cycle.json?').respond [
+        $httpBackend.whenGET('/admin/enterprise_fees/for_order_cycle.json').respond [
           {id: 1, name: "Yayfee", enterprise_id: 1}
           {id: 2, name: "FeeTwo", enterprise_id: 2}
           ]


Remove warnings

Remove warnings

--- views/battle-detail-area.cjsx
+++ views/battle-detail-area.cjsx
@@ -51,21 +51,20 @@
     <ProgressBar className="hp-bar" bsStyle={getHpStyle percent} now={percent} label={label} />

-# <DamageInfo damage={damage} isCritical={isCritical} />
 DamageInfo = React.createClass
   render: ->

     {
       elements = []
-      elements.push getAttackTypeName @props.type
-      elements.push " ("
+      elements.push {getAttackTypeName @props.type}
+      elements.push {" ("}
       for damage, i in @props.damage
         if damage == 0
           damage = "miss"
-        elements.push {damage}
-        elements.push ", "
+        elements.push {damage}
+        elements.push {", "}
       elements.pop()  # Remove last comma
-      elements.push ")"
+      elements.push {")"}
       elements
     }


Allow comments to be edited

Allow comments to be edited

--- web/scripts/codeland.coffee
+++ web/scripts/codeland.coffee
@@ -253,9 +253,10 @@
     if gameData.code.comments
         # Also ensures newlines in the data are properly commented out
         one= '// '+ ((gameData.code.comments.join('\n')).replace(/\n/g,'\n// '))
-        # gameData.code.initial = one + '\n' + gameData.code.initial
-        gameData.code.prefix = one + '\n'
-        gameData.code.show = true
+        gameData.code.initial = one + '\n' + gameData.code.initial
+        # To prevent comments from being edited
+        #gameData.code.prefix = one + '\n'
+        #gameData.code.show = true
     return

 root.getGameDescriptions = ->


fix accidentally removed line

fix accidentally removed line

--- lib/split-diff.coffee
+++ lib/split-diff.coffee
@@ -9,13 +9,14 @@
   isEnabled: false
   diffViewEditor1: null
   diffViewEditor2: null
-  editorSubscriptions: nul
+  editorSubscriptions: null
   #TODO(mike): serialize/save ignore whitespace setting
   isWhitespaceIgnored: false

   activate: (state) ->
     @subscriptions = new CompositeDisposable()

+    @subscriptions.add atom.commands.add 'atom-workspace',
       'split-diff:diffPanes': => @diffPanes()
       'split-diff:disable': => @disable()
       'split-diff:toggleIgnoreWhitespace': => @toggleIgnoreWhitespace()


Go down on the lights again, fixes #1

Go down on the lights again, fixes #1

--- components/StateToColor.coffee
+++ components/StateToColor.coffee
@@ -60,12 +60,12 @@

   teamToRgb: (state) ->
     if state.team is 'RESISTANCE'
-      return [0, 0, 128]
+      return [0, 0, 96]
     if state.team is 'ALIENS'
-      return [0, 128, 0]
+      return [0, 96, 0]

     # Neutral or third faction?
-    return [128, 128, 128]
+    return [96, 96, 96]

   stateToRgb: (state) ->
     base = @teamToRgb state
@@ -78,12 +78,12 @@
     unless @states[idx]
       @states[idx] = base
     other = base.slice()
-    random = -> Math.floor(Math.random() * 128) + 1
+    random = -> Math.floor(Math.random() * 96) + 1
     switch state.state
       when 'ownerchange'
-        other = [128,128,128]
+        other = [96,96,96]
       when 'attack'
-        other = [128, 0, 0]
+        other = [96, 0, 0]
       when 'disco'
         other = [random(), random(), random()]
       else


change thumbnail creation cooldown length

--- init.coffee
+++ init.coffee
@@ -54,7 +54,7 @@
                     percent = Math.floor((thumb_files / total_files) * 100)
                     # Emit thumb creation progress
                     socket.emit 'progress', {"percent": percent}
-                    setTimeout callback, 1000
+                    setTimeout callback, 200
             , cb

         else


Use input element to test focus after cancel

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -209,15 +209,18 @@
           rootView.attachToDom()
           rootView.getActiveEditor().destroyActiveEditSession()

+          inputView = $$ -> @input id: "div-view"
+          rootView.append(inputView)
+          inputView.focus()
+
           rootView.trigger 'fuzzy-finder:toggle-file-finder'
           expect(finderView.hasParent()).toBeTruthy()
-          expect(rootView.isFocused).toBeFalsy()
           expect(finderView.miniEditor.isFocused).toBeTruthy()

           finderView.cancel()

           expect(finderView.hasParent()).toBeFalsy()
-          expect($(document.activeElement).view()).toBe rootView
+          expect(document.activeElement).toBe inputView[0]
           expect(finderView.miniEditor.isFocused).toBeFalsy()

   describe "cached file paths", ->


added 'touchstart' event for mobile

added 'touchstart' event for mobile

--- components/layout/index.coffee
+++ components/layout/index.coffee
@@ -35,8 +35,9 @@

     $('.nav-items a.is-active').removeClass('is-active')
     $(".nav-items a[data-language='#{language}'").addClass('is-active')
+    console.log language

-  $('body').on 'click', '.js-language-toggle', toggleLanguageOnClick
+  $('body').on 'click, touchstart', '.js-language-toggle', toggleLanguageOnClick

   $('body').on 'click', '.header-section-nav a', (e)->
     section = $(e.currentTarget).data 'section'


Fix test

Fix test

--- spec/search-model-spec.coffee
+++ spec/search-model-spec.coffee
@@ -19,7 +19,7 @@
       subject.setPattern('items')
       expect(spy).toHaveBeenCalled()
       expect(spy.mostRecentCall.args[0]).toEqual subject
-      expect(spy.mostRecentCall.args[1]).toEqual regex: subject.regex
+      expect(spy.mostRecentCall.args[1].regex).toEqual subject.regex

   describe "setOption()", ->
     it "kicks out an event", ->


Fixed indentation.

Fixed indentation.

--- app/assets/javascripts/app/controllers/reset_password.js.coffee
+++ app/assets/javascripts/app/controllers/reset_password.js.coffee
@@ -21,7 +21,7 @@
     @render()

   render: (params) ->
-   configure_attributes = [
+    configure_attributes = [
       { name: 'username', display: 'Enter your username or email address', tag: 'input', type: 'text', limit: 100, null: false, class: 'input span4',  },
     ]

@@ -94,7 +94,7 @@
     )

   render_success: =>
-   configure_attributes = [
+    configure_attributes = [
       { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 100, null: false, class: 'input span4',  },
     ]


Uses <i> and <b> rather than textile in the rich text editor

--- app/assets/javascripts/pages/admin/features/rich_text.js.coffee
+++ app/assets/javascripts/pages/admin/features/rich_text.js.coffee
@@ -3,10 +3,10 @@
     ta = new jRichTextArea(this)

     ta.toolbar.addButton("Bold", ->
-      @textArea.wrapSelection "*"
+      @textArea.wrapSelection "", ""

     ).addButton("Italics", ->
-      @textArea.wrapSelection "_"
+      @textArea.wrapSelection "", ""

     ).addButton("Heading 2", ->
       @textArea.wrapSelection "h2. ", ""
@@ -38,4 +38,3 @@
         @textArea.replaceSelection "!" + response + "!"
       else
         @textArea.replaceSelection "!" + selection + "!"
-


don't wait so long

don't wait so long

--- app/assets/javascripts/views/_shared/date_driven_view.js.coffee
+++ app/assets/javascripts/views/_shared/date_driven_view.js.coffee
@@ -64,7 +64,7 @@

     @windowResizedTimeout = window.setTimeout =>
       @delayedOnWindowResized()
-    , 500
+    , 200

   delayedOnWindowResized: =>
     @setWeekIntervalAndToDate()


Don't re-define window.dock.bounce,badge These were added to the main slack client.

--- app/assets/javascripts/notifications.coffee
+++ app/assets/javascripts/notifications.coffee
@@ -1,16 +1,17 @@
-# TS.client.ui.active_highlight_count
-# TS.model.prefs.mac_ssb_bounce, "short" or "long"
-# Optimistically, these might be useful from other scripts, so we'll export them?
-window.dock = {}
+# The bounce functions have been added to the main application so we can
+# not expose any native functions. see expose-window-apis.js in hackable-slack-client.
+# these will be removed "soon"
+unless window.dock?
+  window.dock = {}

-window.dock.bounce = ->
-  preference = TS.model.prefs.mac_ssb_bounce
-  return unless preference is "long" or preference is "short"
-  type = if TS.model.prefs.mac_ssb_bounce == "short" then "informational" else "critical"
-  host.ipc.send('bounce', { type: type })
+  window.dock.bounce = ->
+    preference = TS.model.prefs.mac_ssb_bounce
+    return unless preference is "long" or preference is "short"
+    type = if TS.model.prefs.mac_ssb_bounce == "short" then "informational" else "critical"
+    host.ipc.send('bounce', { type: type })

-window.dock.badge = (message) ->
-  host.ipc.send('badge', { badge_text: message })
+  window.dock.badge = (message) ->
+    host.ipc.send('badge', { badge_text: message })

 # At boot, mark all channels as read once.
 window.channelsViewedAt = {}


removed device_type from all devicew

removed device_type from all devicew

--- device-config-schema.coffee
+++ device-config-schema.coffee
@@ -33,10 +33,6 @@
         description: "Device id retrieved using Wink API"
         type: "string"
         required: true
-      device_type:
-        description: "Device type retrieved using Wink API"
-        type: "string"
-        required: true
       pubnub_channel:
         description: "Pubnub channel retrieved using Wink API"
         type: "string"
@@ -59,10 +55,6 @@
     properties:
       device_id:
         description: "Device id retrieved using Wink API"
-        type: "string"
-        required: true
-      device_type:
-        description: "Device type retrieved using Wink API"
         type: "string"
         required: true
       pubnub_channel:
@@ -89,10 +81,6 @@
         description: "Device id retrieved using Wink API"
         type: "string"
         required: true
-      device_type:
-        description: "Device type retrieved using Wink API"
-        type: "string"
-        required: true
       pubnub_channel:
         description: "Pubnub channel retrieved using Wink API"
         type: "string"


Add PHP var_dump snippet

Add PHP var_dump snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -266,7 +266,11 @@

   'display errors':
     'prefix': 'derr'
-    'body': "ini_set('display_errors', '1');"
+    'body': "ini_set( 'display_errors', '1' );"
+
+  'var_dump':
+    'prefix': 'vd'
+    'body': "var_dump( $1 );"

 # Inline PHP Blocks (meant to be intermixed with HTML)

@@ -290,7 +294,7 @@

   'else (inline)':
     'prefix': 'pelse'
-    'body': "<?php else$1: ?>$2"
+    'body': "<?php else: ?>$2"

   'else if (inline)':
     'prefix': 'pelseif'


remove colours from user filter

remove colours from user filter

--- resources/assets/coffee/react/beatmap-discussions/user-filter.coffee
+++ resources/assets/coffee/react/beatmap-discussions/user-filter.coffee
@@ -50,16 +50,11 @@

   renderItem: ({ cssClasses, children, item, onClick }) =>
-    userGroup = @userGroup(item)
-    cssClasses += " beatmap-discussions-user-filter__item--#{userGroup}" if userGroup?
-
     a
       className: cssClasses
       href: BeatmapDiscussionHelper.url user: item?.id, true
       key: item?.id
       onClick: onClick
-      style:
-        color: item.colour if !@isOwner(item)
       children


added suffixes

added suffixes

--- web/coffee/certificate.coffee
+++ web/coffee/certificate.coffee
@@ -7,6 +7,16 @@
         return cb(resp.data)
       when 0
         apiNotify(resp)
+suffix = (i) ->
+  j = i % 10
+  k = i % 100
+  if j == 1 and k != 11
+    return i + 'st'
+  if j == 2 and k != 12
+    return i + 'nd'
+  if j == 3 and k != 13
+    return i + 'rd'
+  i + 'th'

 $ ->
   $("#certificate-get").on "click", (e) ->
@@ -14,9 +24,11 @@
     load_team (data) ->
       if data.place is null
         data.place = ""
+      else
+        data.place = suffix(data.place)
       if data.score is null
         data.score = ""
-      dd = getPdf(data.team_name, data.score.toString(), data.place.toString())
+      dd = getPdf(data.team_name, data.score.toString(), data.place)
       console.log(dd)
       pdfMake.createPdf(dd).open()


Add author metadata to docpad

Add author metadata to docpad

--- docpad.coffee
+++ docpad.coffee
@@ -4,10 +4,13 @@
         site:
             url: "http://greg-net.net"
             title: "Greg-net.net"
+            author: "Gregory Karastergios"
             description: """
                 Greg's site of stuff
                 """
             keywords: """
                 Greg, tech, stuff, software
                 """
+
+
 }


Try without cleanurls

Try without cleanurls

--- docpad.coffee
+++ docpad.coffee
@@ -194,9 +194,6 @@
   # Plugins

   plugins:
-    cleanurls:
-      enabled: true
-
     markedOptions:
       gfm: true


tests

tests

--- vendor/assets/javascripts/planning_view.js.coffee
+++ vendor/assets/javascripts/planning_view.js.coffee
@@ -104,10 +104,11 @@
   selector = '#planning-view__container .planning-view__row-wrapper'
   for i in document.styleSheets
     rules = i[if document.all then 'rules' else 'cssRules']
-    for r in rules
-      if r.selectorText == selector
-        window.planning_view_wrapper_css_rule = r.style
-        break
+    if rules
+      for r in rules
+        if r.selectorText == selector
+          window.planning_view_wrapper_css_rule = r.style
+          break
   if window.planning_view_wrapper_css_rule == undefined
     css = "#{selector} { margin-left: 0%; #{style}: #{value} }"
     head = document.head || document.getElementsByTagName('head')[0]
@@ -122,7 +123,7 @@

     for i in document.styleSheets
       rules = i[if document.all then 'rules' else 'cssRules']
-      if rules.title && rules.title.title 'planning-view__style'
+      if rules && rules.title.title 'planning-view__style'
         for r in rules
           if r.selectorText == selector
             window.planning_view_wrapper_css_rule = r.style


Remove constant.cson

Remove constant.cson

--- constant.cson
+++ constant.cson
@@ -1,62 +0,0 @@
-# Contributors
-contributors: [
-    name: 'Season千'
-    avatar: 'http://7xkd7e.com1.z0.glb.clouddn.com/season.jpg'
-    link: 'http://www.pixiv.net/member.php?id=3991162'
-  ,
-    name: 'Magica'
-    avatar: 'https://avatars0.githubusercontent.com/u/6753092?v=3&s=460'
-    link: 'http://weibo.com/maginya'
-  ,
-    name: 'Yunze'
-    avatar: 'https://avatars3.githubusercontent.com/u/3362438?v=3&s=460'
-    link: 'http://weibo.com/myzwillmake'
-  ,
-    name: 'Chiba'
-    avatar: 'https://avatars1.githubusercontent.com/u/6986642?v=3&s=460'
-    link: 'http://weibo.com/chibaheit'
-  ,
-    name: 'KochiyaOcean'
-    avatar: 'https://avatars3.githubusercontent.com/u/8194131?v=3&s=460'
-    link: 'http://www.kochiyaocean.org'
-  ,
-    name: '马里酱'
-    avatar: 'https://avatars3.githubusercontent.com/u/10855724?v=3&s=460'
-    link: 'http://www.weibo.com/1791427467'
-  ,
-    name: '吴钩霜雪明'
-    avatar: 'https://avatars2.githubusercontent.com/u/6861365?v=3&s=460'
-    link: 'http://www.weibo.com/jenningswu'
-  ,
-    name: 'Rui'
-    avatar: 'https://avatars3.githubusercontent.com/u/11897118?v=3&s=460'
-    link: 'https://github.com/ruiii'
-  ,
-    name: 'Artoria'
-    avatar: 'https://avatars1.githubusercontent.com/u/1183568?v=3&s=460'
-    link: 'http://www.weibo.com/pheliox'
-  ,
-    name: 'Alvin'
-    avatar: 'https://avatars3.githubusercontent.com/u/13615512?v=3&s=460'
-    link: 'https://github.com/alvin-777'
-  ,
-    name: 'Ayaphis'
-    avatar: 'https://avatars2.githubusercontent.com/u/11754367?v=3&s=400'
-    link: 'https://github.com/ayaphis'
-  ,
-    name: 'ZYC'
-    avatar: 'https://avatars3.githubusercontent.com/u/13725370?v=3&s=460'
-    link: 'http://weibo.com/zyc43'
-  ,
-    name: 'Dazzy Ding'
-    avatar: 'https://avatars1.githubusercontent.com/u/3337921'
-    link: 'http://dazzyd.org/'
-  ,
-    name: 'taroxd'
-    avatar: 'https://avatars3.githubusercontent.com/u/6070540?v=3&s=460'
-    link: 'http://github.com/taroxd'
-  ,
-    name: 'edwardaaaa',
-    avatar: 'https://avatars1.githubusercontent.com/u/11089376?v=3&s=460'
-    link: 'https://github.com/edwardaaaa'
-]


Added tests for the new functionality

Added tests for the new functionality

--- test/api.spec.coffee
+++ test/api.spec.coffee
@@ -90,3 +90,52 @@
       username: 'Foo'
       text: 'Hello!'
       icon_emoji: ':saxophone:'
+
+  it 'slack.send with icon_url', ->
+    slack.send
+      text: 'Hello!'
+      icon_url: 'http://something.com/icon.png'
+
+    expect(slack.request).toHaveBeenCalledWith
+      channel: '#general'
+      username: 'Robot'
+      text: 'Hello!'
+      icon_url: 'http://something.com/icon.png'
+
+  it 'slack.send with attachments and extra fields', ->
+    slack.send
+      text: 'Hello!'
+      attachments: [
+        {
+          fallback: 'Fallback'
+          fields: [
+            { title: 'CPU %', value: '90%', short: true }
+            { title: 'RAM %', value: '47%', short: true }
+          ]
+        }
+      ]
+      fields:
+        IP: '123.123.123.123'
+        sha: '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
+
+    expect(slack.request).toHaveBeenCalledWith
+      channel: '#general'
+      username: 'Robot'
+      text: 'Hello!'
+      icon_emoji: ':bell:'
+      attachments: [
+        {
+          fallback: 'Fallback'
+          fields: [
+            { title: 'CPU %', value: '90%', short: true }
+            { title: 'RAM %', value: '47%', short: true }
+          ]
+        },
+        {
+          fallback: 'Alert details'
+          fields: [
+            { title: 'IP', value: '123.123.123.123', short: true }
+            { title: 'sha', value: '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12', short: false }
+          ]
+        }
+      ]


remove empty items in pattern

remove empty items in pattern

--- extension/packages/options.coffee
+++ extension/packages/options.coffee
@@ -45,7 +45,10 @@

 validatePatterns = (event) ->
   input = event.target
-  # TODO implements filter empty and duplicate items
+  input.value = input.value.split(',')
+                    .map((pattern) -> pattern.trim())
+                    .filter((pattern) -> pattern != '')
+                    .join(',')
   input.valueToPreference()

 observe = ->


fix partial loading

fix partial loading

Signed-off-by: Adam Stokes <0a364f4bf549cc82d725fa7fd7ed34404be64079@ubuntu.com>

--- cli.coffee
+++ cli.coffee
@@ -38,17 +38,17 @@
   .then((posts) ->
     console.log(logSym.info, "Generating individual posts.")
     for post in posts
-      save.single('build', hb.templates['single'], post)
+      save.single('build', config.templates['single'], post)
     return posts)
   .then((posts) ->
     console.log(logSym.info, "Generating feed.")
-    save.collection('build', hb.templates['feed'], 'feed.xml', posts)
+    save.collection('build', config.templates['feed'], 'feed.xml', posts)
     return posts)
   .then((posts) ->
     console.log(logSym.info, "Generating sitemap.")
-    save.collection('build', hb.templates['sitemap'], 'sitemap.xml', posts)
+    save.collection('build', config.templates['sitemap'], 'sitemap.xml', posts)
     return posts)
   .then((posts) ->
     console.log(logSym.info, "Generating index")
-    return save.collection('build', hb.templates['home'], 'index.html', posts))
+    return save.collection('build', config.templates['home'], 'index.html', posts))
   .catch((e) -> console.log "Problem: #{e}")


fix: request 404

fix: request 404

--- src/latestVersion.coffee
+++ src/latestVersion.coffee
@@ -16,5 +16,7 @@
     .catch (e) ->
         if e.code is 'TIMEOUT'
             Promise.reject new Error "Request to #{getRegistry(mirror)} timeout. Please use an alternative registry by -m <mirror>"
+        else if e.code is 'UNWANTED_STATUS_CODE'
+            Promise.resolve ''
         else
             Promise.reject e


avoid possible loops

avoid possible loops

--- src/lib/broadcast.coffee
+++ src/lib/broadcast.coffee
@@ -127,8 +127,8 @@
                         minLngE6:        Math.round(@options.region.SouthWest.Lng * 1e6)
                         maxLatE6:        Math.round(@options.region.NorthEast.Lat * 1e6)
                         maxLngE6:        Math.round(@options.region.NorthEast.Lng * 1e6)
-                        minTimestampMs:  TSmin - 2000
-                        maxTimestampMs:  TSmax + 2000
+                        minTimestampMs:  TSmin - 10
+                        maxTimestampMs:  TSmax + 10
                         chatTabGet:      @options.type
                     instance: @options.instanceId
                     status:   STATUS_PENDING
@@ -235,7 +235,7 @@
         else

             # records are in descend order.
-            maxTimestamp = parseInt(response[response.length - 1][1]) + 2000
+            maxTimestamp = parseInt(response[response.length - 1][1]) + 10

             @tasks[taskId].data.maxTimestampMs = maxTimestamp
             @tasks[taskId].status = STATUS_NOTCOMPLETE


Implement configure tests

Implement configure tests

--- test/node_gyp.coffee
+++ test/node_gyp.coffee
@@ -36,14 +36,35 @@

 describe 'grunt-node-gyp', ->
 	describe 'configure', ->
-		it 'should configure a release build by default', ->
-			;
+		it 'should configure a release build by default', (done) ->
+			linkBindingGyp()

-		it 'should configure a debug build if the debug option is passed', ->
-			;
+			execGruntTask 'configure', (err) ->
+				return done(err) if err

-		it 'should fail if there is no binding.gyp', ->
-			;
+				configGypi = fs.readFileSync './build/config.gypi', {encoding: 'utf8'}
+				if configGypi.indexOf('"default_configuration": "Release"') < 0
+					return done(new Error 'expected config.gypi to be configured for release build')
+
+				done()
+
+		it 'should configure a debug build if the debug option is passed', (done) ->
+			linkBindingGyp()
+
+			execGruntTask 'configureDebug', (err) ->
+				return done(err) if err
+
+				configGypi = fs.readFileSync './build/config.gypi', {encoding: 'utf8'}
+				if configGypi.indexOf('"default_configuration": "Debug"') < 0
+					return done(new Error 'expected config.gypi to be configured for debug build')
+
+				done()
+
+		it 'should fail if there is no binding.gyp', (done) ->
+			unlinkBindingGyp()
+
+			execGruntTask 'configure', (err) ->
+				if err then done() else done(new Error 'expected configure to fail')

 	describe 'build', ->
 		it 'should build a release build by default', ->


OPCE4: fixed missing spaces and bad index in tests + added test for deltas

--- test/rte-test.coffee
+++ test/rte-test.coffee
@@ -28,9 +28,9 @@

   it 'insert words correctly', ->
     rte1 = new Rte "is"
-    rte1.insertWords(0, ["This"])
+    rte1.insertWords(0, ["This "])
     rte1.val().should.equal "This is"
-    rte1.insertWords(2, ["sparta", "!"])
+    rte1.insertWords(2, [" sparta ", "!"])
     rte1.val().should.equal "This is sparta !"

   it 'delete words correctly', ->
@@ -44,9 +44,9 @@

     rte1 = new Rte "There is a mistake in this sentence! there"
     rte1.deleteWords(7, 8)
-    rte1.val().should.equal "There is a mistake in this sentence!"
+    rte1.val().should.equal "There is a mistake in this sentence! "

-  it 'insert characters correctly at relative position', ->
+  it 'insert characters correctly at relative positions', ->
     rte1 = new Rte "I lot the gam"
     rte1.insert({startPos: {word:1, pos:2}}, 's')
     rte1.val().should.equal "I lost the gam"
@@ -62,7 +62,7 @@

   it 'not delete space after word when selection ends at last character', ->
     rte1 = new Rte "I am yjs here!"
-    sel = new Selection(5, 8, rte1)
+    sel = new Selection({word:2, pos:0}, {word:2, pos:3})
     rte1.deleteSel(sel)
     rte1.val().should.equal "I am  here!"

@@ -118,15 +118,34 @@

 describe 'Selection object should', ->
   sel = rte = null
+  it 'be initialized with two parameters', ->
+    [a, b, c, d] = [1, 2, 3, 4]
+    sel = new Selection({word: 1, pos: 2}, {word: 3, pos: 4})
+
+    sel.startPos.word.should.equal a
+    sel.startPos.pos.should.equal b
+    sel.endPos.word.should.equal c
+    sel.endPos.pos.should.equal d

   it 'be initialized with three parameters', ->
     rte = new Rte "Zero One two three four five"
-    sel = new Selection 0, 1, rte
+    sel = new Selection 1, 7, rte

     sel.should.have.property('startPos')
     sel.should.have.deep.property('startPos.word', 0)
-    sel.should.have.deep.property('startPos.pos', 0)
+    sel.should.have.deep.property('startPos.pos', 1)

     sel.should.have.property('endPos')
-    sel.should.have.deep.property('endPos.word', 1)
-    sel.should.have.deep.property('endPos.pos', 2)
+    sel.should.have.deep.property('endPos.word', 0)
+    sel.should.have.deep.property('endPos.pos', 1)
+
+  it 'be initialized with four parameters', ->
+    sel = new Selection 0, 1, 2, 3
+
+    sel.should.have.property('startPos')
+    sel.should.have.deep.property('startPos.word', 0)
+    sel.should.have.deep.property('startPos.pos', 1)
+
+    sel.should.have.property('endPos')
+    sel.should.have.deep.property('endPos.word', 2)
+    sel.should.have.deep.property('endPos.pos', 3)


fix love.math tests

fix love.math tests

--- test/src/math.coffee
+++ test/src/math.coffee
@@ -18,7 +18,7 @@

       it 'should return values between -1 and 1', ->
         for x in [0..10]
-          expect(noise(x)).to.be.above(-1).and.below(1)
+          expect(noise(x)).to.be.within(-1, 1)

       it 'should return similar values for similar inputs', ->
         expect(Math.abs(noise(0.1) - noise(0.101))).to.be.below(0.1).and.above(0)
@@ -38,7 +38,7 @@
       it 'should return values between -1 and 1', ->
         for x in [0..10]
           for y in [0..10]
-            expect(noise(x, y)).to.be.above(-1).and.below(1)
+            expect(noise(x, y)).to.be.within(-1, 1)

       it 'should return similar values for similar inputs', ->
         expect(Math.abs(noise(0.1, 0.2) - noise(0.101, 0.201))).to.be.below(0.1).and.above(0)
@@ -58,7 +58,7 @@
       it 'should return values between -1 and 1', ->
         for i in [0..10]
           for j in [0..10]
-            expect(noise(i / 5, j / 5, i + j)).to.be.above(-1).and.below(1)
+            expect(noise(i / 5, j / 5, i + j)).to.be.within(-1, 1)

       it 'should return similar values for similar inputs', ->
         expect(Math.abs(noise(0.1, 0.2, 0.3) - noise(0.101, 0.201, 0.301))).to.be.below(0.1).and.above(0)
@@ -78,7 +78,7 @@
       it 'should return values between -1 and 1', ->
         for i in [0..10]
           for j in [0..10]
-            expect(noise(i / 5, j / 5, i + j, i - j)).to.be.above(-1).and.below(1)
+            expect(noise(i / 5, j / 5, i + j, i - j)).to.be.within(-1, 1)

       it 'should return similar values for similar inputs', ->
         expect(Math.abs(noise(0.1, 0.2, 0.3, 0.4) - noise(0.101, 0.201, 0.301, 0.401))).to.be.below(0.1).and.above(0)
@@ -95,12 +95,12 @@

     describe 'when called with no arguments', ->
       it 'should return a value between 0 and 1', ->
-        expect(random()).to.be.above(0).and.below(1)
+        expect(random()).to.be.within(0, 1)

     describe 'when called with one argument', ->
-      it 'should return a value between 0 and the argument', ->
-        expect(random(10)).to.be.above(0).and.below(10)
+      it 'should return a value between 1 and the argument, inclusively', ->
+        expect(random(10)).to.be.within(1, 10 + 1)

     describe 'when called with two arguments', ->
-      it 'should return a value between the two arguments', ->
-        expect(random(5, 10)).to.be.above(5).and.below(10)
+      it 'should return a value between the two arguments, inclusively', ->
+        expect(random(5, 10)).to.be.within(5, 10 + 1)


Fix taxons page with new Spree.ajax calls

This was missing a necessary {"X-Spree-Token": Spree.api_key }, which
was causing the API call to error. Also cleaned up the rest of the ajax
calls in the file.

--- backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
@@ -28,12 +28,11 @@
   Spree.ajax
     type: "POST",
     dataType: "json",
-    url: base_url.toString(),
+    url: base_url,
     data: {
       "taxon[name]": name,
       "taxon[parent_id]": new_parent.prop("id"),
       "taxon[child_index]": position,
-      token: Spree.api_key
     },
     error: handle_ajax_error,
     success: (data,result) ->
@@ -65,10 +64,9 @@
       Spree.ajax
         type: "POST",
         dataType: "json",
-        url: delete_url.toString(),
+        url: delete_url,
         data: {
           _method: "delete",
-          token: Spree.api_key
         },
         error: handle_ajax_error
     else
@@ -82,7 +80,7 @@
     root.base_url = Spree.routes.taxonomy_taxons_path

     Spree.ajax
-      url: Spree.url(base_url.path().replace("/taxons", "/jstree")).toString(),
+      url: base_url.replace("/taxons", "/jstree"),
       success: (taxonomy) ->
         last_rollback = null

@@ -90,6 +88,7 @@
           json_data:
             data: taxonomy,
             ajax:
+              headers: { "X-Spree-Token": Spree.api_key }
               url: (e) ->
                 "#{base_url}/#{e.prop('id')}/jstree"
           themes:


Fix active nodes

Fix active nodes

--- web/source/javascripts/angular-app.js.coffee
+++ web/source/javascripts/angular-app.js.coffee
@@ -121,7 +121,18 @@
       voronoiPatches = svg.selectAll('.voronoi-patch').data(nodes)
         .enter().append('a').attr('class', 'voronoi-patch').attr('xlink:href', (d) -> d.url).append('path')
       # voronoiPatches = node.append('path').attr('class', 'voronoi-patch')
-      voronoiPatches.on('mouseover', (d) -> d.active = true; console.log('active'); updateActive()).on('mouseout', (d) -> d.active = false; updateActive())
+      voronoiPatches
+        .on('mouseover', (d) ->
+          d.active = true
+          d.fixed = true
+          force.start()
+          updateActive()
+        )
+        .on('mouseout', (d) ->
+          d.active = false
+          d.fixed = false
+          updateActive()
+        )

       updateScale = ->
         diameter = _.max _.map(pairwise(d3.geom.hull().x((node) -> node.x).y((node) -> node.y)(nodes)), (pair) ->


Calculator style tweaks and underscore-min inclusion fix

--- spec.js.coffee
+++ spec.js.coffee
@@ -8,7 +8,7 @@
 #= require ../../app/assets/javascripts/lib
 #= require jquery
 #= require jquery_ujs
-#= require underscore-min
+#= require ../../vendor/assets/javascripts/underscore-min
 #= require_tree ./support
 #= require_tree ./
 #= require_tree ../../app/assets/javascripts/widgets


relative asset paths

relative asset paths

--- src/App.coffee
+++ src/App.coffee
@@ -14,7 +14,7 @@
 class App

 	constructor: () ->
-		@level = 2;
+		@level = 1;

 		@filter = []

@@ -32,16 +32,16 @@

 	preload: () =>
 		# load the map's tilesheet
-		@game.load.image('tiles', '/assets/tiles.png?' + new Date());
+		@game.load.image('tiles', './assets/tiles.png?' + new Date());

 		# load the Tiled map (with timestamp for cache busting)
-		@game.load.tilemap 'map_01', '/assets/map_0' + @level + '.json?' + new Date(), null, Phaser.Tilemap.TILED_JSON
+		@game.load.tilemap 'map_01', './assets/map_0' + @level + '.json?' + new Date(), null, Phaser.Tilemap.TILED_JSON

 		# load the tile sheet again (not a new request) to use individual tiles as Sprites
-		@game.load.spritesheet('sprites', '/assets/tiles.png?' + new Date(), 8, 8, 16);
+		@game.load.spritesheet('sprites', './assets/tiles.png?' + new Date(), 8, 8, 16);

 		# load the demo Phaser filter, a noise effect
-		@game.load.script('filter-filmgrain', '/assets/filters/filmgrain.js');
+		@game.load.script('filter-filmgrain', './assets/filters/filmgrain.js');

 		return


Log webhook requests.

Log webhook requests.

--- src/app.coffee
+++ src/app.coffee
@@ -25,6 +25,8 @@

 app.post '/webhook', (req, res, next) ->
   webhook.onRequest req
+
+  console.log 'webhook requested', req.body.repository.full_name
   res.end()

 app.post '/post', (req, res, next) ->


window "caption" configurable through localStorage

--- client/ide.coffee
+++ client/ide.coffee
@@ -77,11 +77,23 @@
     return

   host = port = null
-  displayName = ''
+  displayName = '' # apparently this is the WSID
   updateTitle = ->
-    s = displayName; if host then s += ' - ' + host; if port then s += ':' + port
-    i = D.remoteIdentification || {}; if i.pid then s += " (PID: #{i.pid})"
-    $('title').text s; return
+    tt = localStorage.windowTitle || '{WSID} - {HOST}:{PORT} (PID: {PID})'
+    ri = D.remoteIdentification || {}
+    $('title').text tt.replace /\{(\w+)\}/g, (g0, g1) ->
+      switch g1.toUpperCase()
+        when 'WSID'  then displayName
+        when 'HOST'  then host || ''
+        when 'PORT'  then port || ''
+        when 'PID'   then ri.pid || ''
+        when 'CHARS' then (ri.arch || '').split('/')[0] || ''
+        when 'BITS'  then (ri.arch || '').split('/')[1] || ''
+        when 'VER_A' then (ri.version || '').split('.')[0] || ''
+        when 'VER_B' then (ri.version || '').split('.')[1] || ''
+        when 'VER_C' then (ri.version || '').split('.')[2] || ''
+        else g0
+    return

   die = -> # don't really, just pretend
     if !isDead then isDead = 1; $ide.addClass 'disconnected'; for _, widget of wins then widget.die()


better association detection (2)

better association detection (2)

--- src/chinchilla/context.coffee
+++ src/chinchilla/context.coffee
@@ -13,7 +13,7 @@
     # @param [String] name name of association
     association: (name) ->
       assoc = @property(name)
-      assoc if _.isPlainObject(assoc)
+      assoc if _.isPlainObject(assoc) && assoc.type && assoc.type.match(/^(http|https)\:/)

     # @param [String] name name of member action
     member_action: (name) ->


removing unused classes

removing unused classes

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -5,22 +5,9 @@
   this.Bokeh = Bokeh
 safebind = Continuum.safebind

-class DeferredSVGView extends Continuum.DeferredView
-  # ###class : DeferredSVGView
-  # overrides make, so we create SVG elements with the appropriate namespaceURI
-  # instances of this class should have some svg tagName
-  tagName : 'svg'
-
-  make: (tagName, attributes, content) ->
-    el = document.createElementNS("http://www.w3.org/2000/svg", tagName)
-    if (attributes)
-      $(el).attr(attributes)
-    if (content)
-      $(el).html(content)
-    return el
-
-class PlotWidget extends DeferredSVGView
-  tagName : 'g'
+
+class PlotWidget extends Continuum.DeferredView
+  tagName : 'div'
   initialize : (options) ->
     super(options)
     @plot_id = options.plot_id
@@ -123,16 +110,6 @@

   render : ->
     super()
-    '''
-    trans_string = "scale(#{@options.scale}, #{@options.scale})"
-    trans_string += "translate(#{@mget('border_space')}, #{@mget('border_space')})"
-    @d3plot.attr('transform', trans_string)
-    d3el = d3.select(@el)
-    d3el.attr('width', @options.scale * @mget('outerwidth'))
-      .attr('height', @options.scale * @mget('outerheight'))
-      .attr('x', @model.position_x())
-      .attr('y', @model.position_y())
-    '''

     row_heights =  @model.layout_heights()
     col_widths =  @model.layout_widths()


Fix incorrect params hash to Iron Router path method

--- emails.coffee
+++ emails.coffee
@@ -20,9 +20,9 @@
   baseUrl: (path) ->
     Utils.joinUrl(Mailer.settings.baseUrl, path)

-  emailUrlFor: ->
+  emailUrlFor: (route, params) ->
     if Router
-      Utils.joinUrl Mailer.settings.baseUrl, Router.path.apply(Router, arguments)
+      Utils.joinUrl Mailer.settings.baseUrl, Router.path.call(Router, route, params.hash)

 MailerClass = (options) ->
   check options, Match.ObjectIncluding(


Reset memoized sorting RegExps on query change

--- coffee/storage_freak.coffee
+++ coffee/storage_freak.coffee
@@ -27,6 +27,7 @@

       for re in @sort_criteria_res
         regexp_result = regexp_sort.call(@, re, a, b)
+        re.lastIndex = 0
         return regexp_result if regexp_result

       lexicographical_sort(a, b)
@@ -147,6 +148,7 @@
       for id, name of @_data
         results.push({id: id, name: name}) if match_func(re, name)

+      @_resetSortingREs()
       results = results.sort ((a, b) -> sort_func(query, a, b))
       results.slice 0, @config.maxResultsNum

@@ -226,6 +228,9 @@
       re_p = @_createQueryRE(query, 'prefix')

       [re_abs, re_t, re_p]
+
+    _resetSortingREs: ->
+      @sort_criteria_res = null

     ## REFACTOR THOSE
     ## MAYBE REMOVE THOSE??


CLI/package close #71 making it possible to use 'lovely.json' file name instead of 'package.json'

--- cli/package.coffee
+++ cli/package.coffee
@@ -1,7 +1,7 @@
 #
 # Package files parser/validator
 #
-# Copyright (C) 2011 Nikolay Nemshilov
+# Copyright (C) 2011-2012 Nikolay Nemshilov
 #

@@ -20,7 +20,7 @@
   data.license     || errors.push("miss the 'license' field")

   data.version.match(/^\d+\.\d+\.\d+$/) ||
-    errors.push("'version' should match the 'd+.d+.d+' format")
+    errors.push("'version' should match the '\d+.\d+.\d+' format")

   if errors.length
     print "Failed to parse the 'package.json' file:\n".red +
@@ -31,13 +31,15 @@
 # Reads the package data out fo the given dreictory
 #
 exports.read = read = (directory) ->
+  fs          = require('fs')
   directory or= process.cwd()

-  data = require("fs").readFileSync("#{directory}/package.json")
-  data = JSON.parse(data.toString())
+  for name in ['lovely', 'package']
+    if fs.existsSync("#{directory}/#{name}.json")
+      data = fs.readFileSync("#{directory}/#{name}.json")
+      data = JSON.parse(data.toString())

-  validate(data)
-
+  if data then validate(data) else print_error("could not find package.json or lovely.json files in this directory")
   data

 #


don't try to save window state in floating windows

--- client/init.coffee
+++ client/init.coffee
@@ -2,13 +2,13 @@
   D.nwjs = process?
   if D.nwjs
     nww = require('nw.gui').Window.get()
-    # restore window state:
-    winProps = 'x y width height isFullscreen'.split ' '
-    do -> if winInfo = (try JSON.parse localStorage.winInfo) then for p in winProps then nww[p] = winInfo[p]
+    if !opener # restore window state:
+      winProps = 'x y width height isFullscreen'.split ' '
+      do -> if winInfo = (try JSON.parse localStorage.winInfo) then for p in winProps then nww[p] = winInfo[p]
     nww.show()
     nww.on 'close', ->
-      # save window state:
-      winInfo = {}; (for p in winProps then winInfo[p] = nww[p]); localStorage.winInfo = JSON.stringify winInfo
+      if !opener # save window state:
+        winInfo = {}; (for p in winProps then winInfo[p] = nww[p]); localStorage.winInfo = JSON.stringify winInfo
       window.onbeforeunload?(); window.onbeforeunload = null; nww.close true; return
     D.zoomIn    = -> nww.zoomLevel++;   return
     D.zoomOut   = -> nww.zoomLevel--;   return


Trim x-axis to end of chart

Trim x-axis to end of chart

--- src/index.cjsx
+++ src/index.cjsx
@@ -49,7 +49,7 @@

     if @props.xAxis
       xAxis = d3.svg.axis()
-        .scale(d3.scale.linear().range([@props.width, 0]))
+        .scale(d3.scale.linear().range([@props.width-1, 0]))
         .orient("bottom")

       chart.append("g")


增加配置路由的注释，并改成 coffee的代码

增加配置路由的注释，并改成 coffee的代码

--- config.coffee
+++ config.coffee
@@ -1,110 +1,100 @@
+###
+  路由规则
+  1. routers每个item都是一个restful路由，包含crud的四种方法
+  2. paths优先原则，paths需要指定全路径，路由处理不会组合新的path。paths.all可以指定所有paths的路径，但如果有具体的处理方法，则具体的方法优先。如paths.get将会优先于paths.all。同时paths可以加#{rootAPI}作为变量，这个将会替换为config文件中的rootAPI
+
+    {
+      #paths优先，比path的优先级高
+      paths: {
+        #如果没有指定具体的curd，则会使用all这个路由
+        all: '#{rootAPI}someURL'
+        #这个会优先于all
+        get: '/asset/:project_id(\\d+)/:filename'
+      },
+      #指定一个path，这个和paths是互斥的
+      path: 'commit'
+      #用于指定将要处理的业务逻辑文件，对应biz文件夹下的具体文件
+      biz: 'commit'
+      #指定允许匿名的方法
+      anonymity: ['post']
+      #为删除指定方法，则put/get将不会被处理
+      method: delete: 'deleteMethod', put: false, get: false
+    },
+###
 module.exports =
-  dbpath: './db.sqlite',
-  assets: './assets',
-  uploads: './uploads',
-  rootAPI: '/api/',
+  dbpath: './db.sqlite'
+  assets: './assets'
+  uploads: './uploads'
+  rootAPI: '/api/'
   routers: [
     {
-      comment: '项目',
-      path: 'project',
+      #项目相关的路由
+      #路由地址
+      path: 'project'
+      #处理的业务逻辑
       biz: 'project'
     },
     {
-      comment: '提交commit，用于git或svn提交commit时，自动获取commit并分析',
-      path: 'commit',
-      biz: 'commit',
-      anonymity: ['post'],
-      method: {
-        delete: false,
-        put: false,
-        get: false
-      }
+      #提交commit，用于git或svn提交commit时，自动获取commit并分析
+      path: 'commit'
+      biz: 'commit'
+      anonymity: ['post']
+      method: delete: false, put: false, get: false
     },
     {
-      comment: '素材',
-      path: 'project/:project_id(\\d+)/asset',
-      biz: 'asset',
-      method: {
-        post: 'uploadFile',
-        delete: false,
-        put: false
-      }
+      #素材
+      path: 'project/:project_id(\\d+)/asset'
+      biz: 'asset'
+      method: post: 'uploadFile', delete: false, put: false
     },
     {
-      comment: '#查看素材',
+      ##查看素材
       paths:{
         get: '/asset/:project_id(\\d+)/:filename'
       },
-      biz: 'asset',
-      method: {
-        get: 'readFile',
-        put: false,
-        post: false,
-        delete: false
-      }
+      biz: 'asset'
+      method: get: 'readFile', put: false, post: false, delete: false
     },{
-      comment: '#issue',
+      #issue相关
       path: 'project/:project_id(\\d+)/issue',
       biz: 'issue'
     },
     {
-      comment: '针对issue的评论',
-      path: 'issue/:issue_id(\\d+)/comment',
-      biz: 'comment',
-      method: {
-        put: false
-      }
+      #针对issue的评论
+      path: 'issue/:issue_id(\\d+)/comment'
+      biz: 'comment'
+      method: put: false
     },
     {
-      comment: '#建立或者解除asset与issue的关系',
-      path: 'issue/:issue_id(\\d+)/asset',
-      biz: 'asset_issue_relation',
-      method: {
-        put: false
-      }
+      #建立或者解除asset与issue的关系
+      path: 'issue/:issue_id(\\d+)/asset'
+      biz: 'asset_issue_relation'
+      method: put: false
     },
     {
-      comment: '#更改issue的状态，仅能更新',
-      path: 'issue/status',
-      biz: 'issue',
-      method: {
-        get: false,
-        delete: false,
-        post: false,
-        put: 'changeStatus'
-      }
+      #更改issue的状态，仅能更新
+      path: 'issue/status'
+      biz: 'issue'
+      method: get: false, delete: false,  post: false, put: 'changeStatus'
     },
     {
-      comment: '#获取项目状态，及修改项目状态的路由',
-      path: 'project/status',
-      biz: 'project',
-      method: {
-        get: 'getStatus',
-        delete: false,
-        post: false,
-        put: 'changeStatus'
-      }
+      #获取项目状态，及修改项目状态的路由
+      path: 'project/status'
+      biz: 'project'
+      method: get: 'getStatus', delete: false, post: false, put: 'changeStatus'
     },
     {
       paths: {
+        #指定all，则所有curd都采用这个地址，不会做任何处理
         all: '#{rootAPI}mine'
       },
-      biz: 'member',
-      method: {
-        post: 'signUp',
-        put: 'signIn',
-        delete: 'signOut',
-        get: 'getMember'
-      },
-      anonymity: ['post', 'put'],
-      comment: {
-        summary: '用户模块',
-        method: {
-          post: '用户注册',
-          put: '用户登录',
-          delete: '注销',
-          get: '获取用户信息，用于判断用户是否登录'
-        }
-      }
+      biz: 'member'
+      method:  post: 'signUp', put: 'signIn', delete: 'signOut', get: 'currentMember'
+      anonymity: ['post', 'put']
+    },{
+      #用于获取用户的信息，一般用于管理或者用户的profile
+      path: 'member'
+      biz: 'member'
+      method: get: 'allMember', put: false, delete: false, post: false
     }
   ]


Remove debug code

Remove debug code

--- denorm.coffee
+++ denorm.coffee
@@ -164,7 +164,6 @@
             J.fetching.parseQs qsString for qsString in watchedQsStrings
         )

-        time = new Date()
         mergedWatchedQuerySpecs = J.fetching.getMerged watchedQuerySpecs
         # mergedWatchedQuerySpecs = watchedQuerySpecs