Update test urls to reflect the new parameter

--- test/hubot-confluence_test.coffee
+++ test/hubot-confluence_test.coffee
@@ -135,7 +135,7 @@
       ]
     base = "https://#{test_host}:#{test_port}"
     path = "/wiki/rest/api/content/search"
-    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
+    params = "os_authType=basic&cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
     full = "#{path}?#{params}"
     nock(base).get(full).reply(200, JSON.stringify(body))
     adapter.on "send", (envelope, strings) ->
@@ -164,11 +164,11 @@
       ]
     base = "https://#{test_host}:#{test_port}"
     path = "/wiki/rest/api/content/search"
-    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
+    params = "os_authType=basic&cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
     full = "#{path}?#{params}"
     nock(base).get(full).reply(200, JSON.stringify({}))
     path = "/wiki/rest/api/content/search"
-    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20text~%22foo%22"
+    params = "os_authType=basic&cql=type%3Dpage%20and%20space%3Dbar%20and%20text~%22foo%22"
     full = "#{path}?#{params}"
     nock(base).get(full).reply(200, JSON.stringify(body))
     adapter.on "send", (envelope, strings) ->
@@ -184,11 +184,11 @@
       ]
     base = "https://#{test_host}:#{test_port}"
     path = "/wiki/rest/api/content/search"
-    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
+    params = "os_authType=basic&cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
     full = "#{path}?#{params}"
     nock(base).get(full).reply(200, JSON.stringify({}))
     path = "/wiki/rest/api/content/search"
-    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20text~%22foo%22"
+    params = "os_authType=basic&cql=type%3Dpage%20and%20space%3Dbar%20and%20text~%22foo%22"
     full = "#{path}?#{params}"
     nock(base).get(full).reply(200, JSON.stringify({}))
     adapter.on "send", (envelope, strings) ->


refactoring: incognito mode transition

refactoring: incognito mode transition

A cleaner definition of the (de)activation of the extension. Now a
dedicated methode is used to reactivate the extension (when before we
used a mod of the initialisation method).

--- src.coffee/background.coffee
+++ src.coffee/background.coffee
@@ -1,12 +1,11 @@
 window.mem0r1es = {} if not window.mem0r1es?

 class window.mem0r1es.Background
-  init : (sendResponse = null) ->
-    console.log "activating mem0r1es"
+  init : () ->
+    console.log "initializing mem0r1es"
     #open and initialize the database
-    if not @state?
-      @storageManager = new mem0r1es.StorageManager()
-      @storageManager.openDB()
+    @storageManager = new mem0r1es.StorageManager()
+    @storageManager.openDB()

     #Create the icon for the popup
     @icon = new mem0r1es.Icon()
@@ -15,22 +14,27 @@
     @navigationListener = new mem0r1es.NavigationListener()

     #Initialize the DSL processor
-    if not @state?
-      @dslProcessor = new mem0r1es.DSLProcessor @storageManager
+    @dslProcessor = new mem0r1es.DSLProcessor @storageManager

     #Initialize the document preprocessor pool
-    if not @state?
-      @DocumentPreprocessorPool = new mem0r1es.DocumentPreprocessorPool @storageManager
+    @DocumentPreprocessorPool = new mem0r1es.DocumentPreprocessorPool @storageManager

     #Initialize the navigation listener
     @userStudyToolbox = new mem0r1es.UserStudyToolbox @storageManager

-    if not @state?
-      @setupMessageListener()
+    #Initialize the message listener
+    @setupMessageListener()
+
+    @state = "on"
+
+  activate : (sendResponse = null) ->
+    console.log "reactivating mem0r1es"
+    @icon = new mem0r1es.Icon()
+    @navigationListener = new mem0r1es.NavigationListener()
     @state = "on"
     if sendResponse?
       sendResponse {status : "ok", text: "Go incognito"}
-
+
   deactivate : (sendResponse = null) ->
     console.log "deactivating mem0r1es"
     #modify the icon for the popup
@@ -48,7 +52,7 @@
     if @state is "on"
       @deactivate sendResponse
     else if @state is "off"
-      @init sendResponse
+      @activate sendResponse

   getState : (sendResponse) ->
     if @state is "on"
@@ -77,15 +81,23 @@
     chrome.extension.onMessage.addListener((request, sender, sendResponse) =>
       console.log "redirecting a message to #{request.module}"
       switch(request.module)
-        when "storageManager" then @storageManager.onMessage request.message, sender, sendResponse
-        when "navigationListener" then @navigationListener.onMessage request.message, sender, sendResponse
-        when "documentPreprocessor" then @DocumentPreprocessorPool.onMessage request.message, sender, sendResponse if @state is "on"
-        when "DSLProcessor" then @dslProcessor.onMessage request.message, sender, sendResponse
-        when "background" then @onMessage request.message, sender, sendResponse
-        when "userStudyToolbox" then @userStudyToolbox.onMessage request.message, sender, sendResponse
+        when "storageManager"
+          @storageManager.onMessage request.message, sender, sendResponse
+        when "navigationListener"
+          @navigationListener.onMessage request.message, sender, sendResponse
+        when "documentPreprocessor"
+          if @state is "on"
+            @DocumentPreprocessorPool.onMessage request.message, sender, sendResponse
+        when "DSLProcessor"
+          @dslProcessor.onMessage request.message, sender, sendResponse
+        when "background"
+          @onMessage request.message, sender, sendResponse
+        when "userStudyToolbox"
+          @userStudyToolbox.onMessage request.message, sender, sendResponse
         else console.log "Could not redirect the message from the popup user interface. #{request.module} is not a valid module"
       #the listener must return true if the response needs to be sent asynchronously
       return true)
+    console.log "Message listener ready"
     return

 background = new mem0r1es.Background


Code generator: refactoring

Code generator: refactoring

--- src/main/code/JsCodeGenerator.coffee
+++ src/main/code/JsCodeGenerator.coffee
@@ -94,6 +94,7 @@
   isStreamFunction = (functionCall) -> functionInfo[functionCall.functionName]?.kind == 'stream'
   isTransformStreamFunction = (functionCall) -> functionInfo[functionCall.functionName]?.kind == 'transformStream'
   isStreamReturnFunction = (functionCall) -> functionInfo[functionCall.functionName]?.returnKind == 'streamReturn'
+  returnsStream = (functionCall) -> isStreamFunction(functionCall) or isTransformStreamFunction(functionCall) or isStreamReturnFunction(functionCall)

   getCodeAndAccumulateFunctions = (expr, localNames) ->
     allLocalNames = incomingLocalNames[..].concat localNames
@@ -151,54 +152,33 @@

     when expr instanceof FunctionCall and expr.functionName == 'in' then '_in'

-    when expr instanceof FunctionCall and isTransformFunction expr
-      functionName = expr.functionName
-      accumulateFunctionName functionName
-      accumulateCombineName new Name functionName
-      arg1 = getCodeAndAccumulateFunctions expr.children[0]
-      arg2 = applyTransformFunction expr.children[1]
-      fromContext(functionName) + argList [arg1, arg2]
-
-    when expr instanceof FunctionCall and isStreamFunction expr
-      functionName = expr.functionName
-      accumulateFunctionName functionName
-      args = (getStreamCodeAndAccumulateFunctions(e) for e in expr.children)
-      lsCode = fromContext functionName + argList args
-      lsName = localStreamName functionName
-      accumulateLocalStream lsName, lsCode
-      accumulateCombineName new Name lsName, true
-      fromContext lsName
-
-    when expr instanceof FunctionCall and isTransformStreamFunction expr
-      functionName = expr.functionName
-      accumulateFunctionName functionName
-      arg1 = getStreamCodeAndAccumulateFunctions expr.children[0]
-      arg2 = applyTransformFunction expr.children[1]
-      lsCode = fromContext functionName + argList [arg1, arg2]
-      lsName = localStreamName functionName
-      accumulateLocalStream lsName, lsCode
-      accumulateCombineName new Name lsName, true
-      fromContext lsName
-
-    when expr instanceof FunctionCall and isStreamReturnFunction expr
-      functionName = expr.functionName
-      accumulateFunctionName functionName
-      args = (getCodeAndAccumulateFunctions(e) for e in expr.children)
-      lsCode = fromContext functionName + argList args
-      lsName = localStreamName functionName
-      accumulateLocalStream lsName, lsCode
-      accumulateCombineName new Name lsName, true
-      fromContext lsName
+    when expr instanceof Input
+      "operations.input(\"#{expr.inputName}\")"

     when expr instanceof FunctionCall
       functionName = expr.functionName
       accumulateFunctionName functionName
-      accumulateCombineName new Name functionName
-      args = (getCodeAndAccumulateFunctions(e) for e in expr.children)
-      fromContext(functionName) + argList args

-    when expr instanceof Input
-      "operations.input(\"#{expr.inputName}\")"
+      if returnsStream expr
+        args =  switch
+          when isStreamFunction expr then (getStreamCodeAndAccumulateFunctions(e) for e in expr.children)
+          when isTransformStreamFunction expr then [getStreamCodeAndAccumulateFunctions(expr.children[0]), applyTransformFunction(expr.children[1])]
+          when isStreamReturnFunction expr then (getCodeAndAccumulateFunctions(e) for e in expr.children)
+
+        lsCode = fromContext functionName + argList args
+        lsName = localStreamName functionName
+        accumulateLocalStream lsName, lsCode
+        accumulateCombineName new Name lsName, true
+        fromContext lsName
+
+      else
+        accumulateCombineName new Name functionName
+        args =
+            if isTransformFunction expr
+                [getCodeAndAccumulateFunctions(expr.children[0]), applyTransformFunction(expr.children[1])]
+            else
+                (getCodeAndAccumulateFunctions(e) for e in expr.children)
+        fromContext(functionName) + argList args

     else
       throw new Error("JsCodeGenerator: Unknown expression: " + expr?.constructor.name)


Increase timeout for register tests

Increase timeout for register tests

--- test/register_sign_in.spec.coffee
+++ test/register_sign_in.spec.coffee
@@ -8,7 +8,7 @@
 timeoutSet = (a, b) -> setTimeout(b, a)

 describe "registration", ->
-  this.timeout(20000)
+  this.timeout(40000)
   before (done) ->
     common.startUp (server, browser) =>
       @server = server


Version encoded in javascript shall be configurable

--- bin/render.coffee
+++ bin/render.coffee
@@ -37,7 +37,7 @@
                 options.comment = true
             render(args[0], depth + '    ', options)
         version: ->
-            "0.0.1"
+            options.version
         include_and_minify: (args) ->
             if args.length > 1 and args[1].indexOf('c') isnt -1
                 options.comment = true
@@ -73,13 +73,16 @@
     switches = [
         ['-p', '--pretty', 'Prettify javascript']
         ['-m', '--minify', 'Minify javascript']
+        ['-s', '--set-version [VERSION]', 'Set the value of version tag']
     ]
-    options = {minify: false, toplevel: true}
+    options = {minify: false, toplevel: true, version: 'unknown'}
     parser = new optparse.OptionParser(switches)
     parser.on 'pretty', ->
                    options.beautify = true
     parser.on 'minify', ->
                    options.minify = true
+    parser.on 'set-version', (_, version) ->
+                   options.version = version
     filenames = parser.parse(process.ARGV.slice(2))

     content = for filename in filenames


when selecting a mirror, correctly query /packages with the selected mirror id

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -256,7 +256,8 @@
     # set packages list when selecting a mirror
     ps = allPackages2
     scope.selectMirror(allMirrors1[0])
-    paginateResponse @httpBackend, '/v1/packages?outdated=true&mirror='+allMirrors1[0].id, ps, () -> scope.fetchPackages()
+    opts = {'query': [['mirror',allMirrors1[0].id],['outdated','true']]}
+    dontPaginateResponse @httpBackend, '/v1/packages', ps, (() -> scope.fetchPackages()), opts
     # results
     expect(scope.packages.length).toBe(ps.length)
     # test both packages


reenabling tests

reenabling tests

Former-commit-id: ca7864992f66f8c7bf56113503278785d223f487 [formerly b08ab56f6bb3627b585b2e8744c25d82a86d0a90] [formerly 21ee6dcbc2fa870fd056c5317019b39bc0da54bc [formerly 771bc753db50befd988debb877edff7a4747a34f]] [formerly 60abab58f26f4c0b4e0da41f725e4061bb92078d [formerly 364d04a17b3566963430ed76c30255a233d33ee8] [formerly fd6c361dfd293df00e90c9e2c8e5eb77964e2719 [formerly fd6c361dfd293df00e90c9e2c8e5eb77964e2719 [formerly 711512574d74f1812383c8d07a8ae5638e266878]]]]
Former-commit-id: 4cb0d39dd5ca2e5e03b8e707015eafb943fcd798 [formerly a50b2ddcd5884cf47701380e3240b1b8bb54a995] [formerly fbe3170b6440c2ffe90d2f951b925a73dd328b36 [formerly e9379392318af61d41752a6183d3749ea3a1fb01]]
Former-commit-id: 19d1d45ce929f5cafe1daf27a899be10f12d8e05 [formerly 9d6234f221b94162333b3c181786aae2ad3935e0]
Former-commit-id: 8c3f59edcbb5f576b6386b809352845f78a2b7d0
Former-commit-id: 0e414c7ea2b25c0d2d9b0ed8b639fa93f16d0a09

--- test/tests/lurker_scenario.coffee
+++ test/tests/lurker_scenario.coffee
@@ -18,185 +18,185 @@
 # used in these tests when a specific proposal is needed to be referenced
 example_proposal = "wash_i_522"

-# casper.test.begin 'Lurker can poke around homepage', 13, (test) ->
-
-#   #TODO: loop here, once with logged in user, once without
-#   casper.start "http://localhost:8787/", ->
-
-#     casper.then ->
-#       casper.wait 1000, ->
-#         @HTMLStep "Homepage can be mucked about"
-
-#         @HTMLCapture 'body',
-#           caption: 'Full homepage, different sizes'
-#           sizes: [ [1200, 900], [600, 500], [1024, 768] ]
-
-#         @HTMLCapture '[data-role="proposal"]',
-#           caption: 'One of the proposals'
-
-#         @HTMLCapture '#proposals-container',
-#           caption: 'Active proposals'
-
-#         assert_homepage_loaded test
-
-#     casper.then ->
-#       @HTMLStep "load some more proposals"
-
-#       @click '#proposals-container [data-target="load-proposals"]'
-#       @wait 10000, ->
-#         @HTMLCapture '#proposals-container',
-#           caption: 'Active proposals'
-
-#         test.assertExists '[data-target="proposallist:page"]', 'pagination is shown after loading proposals'
-#         @HTMLCapture '.proposals-operations',
-#           caption: 'Proposals pagination after loading more'
-
-#         @click '#proposals-container-completed [data-target="load-proposals"]'
-#         @wait 10000, ->
-#           test.assertExists '#proposals-container-completed [data-target="proposallist:page"]', 'pagination for inactive proposals is shown after loading inactive proposals'
-
-#     casper.then ->
-#       casper.open("http://localhost:8787/#{example_proposal}").wait 5000, ->
-#         @HTMLStep "Homepage can be accessed from different page"
-#         test.assertExists '[data-target="go-home"]', 'Opportunity to navigate to homepage'
-#         @click '[data-target="go-home"]'
-#         @wait 1000, ->
-#           assert_homepage_loaded test
-
-
-#   casper.run ->
-#     test.done()
-
-
-
-# casper.test.begin 'Lurker can poke around a proposal results page', 35, (test) ->
-
-#   #TODO: loop here, once with logged in user, once without
-#   casper.start "http://localhost:8787/", ->
-
-#     #TODO: loop, once going to active proposal, next to an inactive proposal
-#     #TODO: loop, proposal with activity and without activity
-
-#     casper.then ->
-#       @wait 1000, ->
-#         @HTMLStep 'Results page can be accessed from homepage'
-#         test.assertExists '[data-role="proposal"] .peer-reasons[data-state="points-collapsed"]', 'Peer reasons exists in collapsed form'
-#         @click '[data-role="proposal"]:first-of-type .peer-reasons[data-state="points-collapsed"]:first-of-type'
-#         @wait 5000, ->
-#           @HTMLCapture 'body',
-#             caption : 'The results page'
-
-#           assert_in_results_state test
-
-#     casper.then ->
-
-#       casper.open("http://localhost:8787/#{example_proposal}").wait 5000, ->
-#         @HTMLStep "Results page can be accessed from crafting page"
-#         test.assertExists '[data-target="view-results"]', 'Opportunity to navigate to results page'
-#         @click '[data-target="view-results"]'
-#         @wait 1000, ->
-#           assert_in_results_state test
-
-#     casper.then ->
-#       casper.open("http://localhost:8787/#{example_proposal}/results").wait 5000, ->
-#         @HTMLStep "Results page can be directly opened"
-#         assert_in_results_state test
-
-
-#     execute_histogram_tests = (state) =>
-#       @HTMLStep "#{state} histogram"
-
-#       if state == 'hover'
-#         casper.mouse.move '.histogram-bar:first-of-type .bar-people'
-#       else
-#         @mouse.click '.histogram-bar:first-of-type .bar-people'
-#         @mouse.move 'body'
-
-#       @wait 250, ->
-#         test.assertSelectorHasText '.pointlist-header-label', 'Pros', "Pros present in pros header when #{state}"
-#         test.assertSelectorHasText '.pointlist-header-label', 'upport', "Supporter is present in pros header when #{state}"
-
-#         @HTMLCapture '[data-role="proposal"]',
-#           caption : "#{state} histogram bar"
-
-#     # hover over histogram; then click
-#     casper.then ->
-#       execute_histogram_tests 'hover'
-
-#     casper.then ->
-#       execute_histogram_tests 'click'
-
-
-#     casper.then ->
-#       @HTMLStep 'hover over a point'
-
-#       @mouse.move 'body'
-#       point_includers = @evaluate ->
-#         return _.uniq($('[data-role="point"]:first').attr("includers").split(',')).length
-
-#       total_avatars = @evaluate -> return $(".histogram .avatar:visible").length
-
-#       @mouse.move '[data-role="point"]'
-#       @wait 200, ->
-#         @HTMLCapture '[data-role="proposal"]',
-#           caption : "Hovering over a point"
-
-#         includers_hidden = @evaluate ->
-#           hidden = -> $(this).css('opacity') == '0' || $(this).css('visibility') == 'hidden'
-#           return $(".histogram .avatar:visible").filter(hidden).length
-
-#         test.assertEqual total_avatars - includers_hidden, point_includers, 'Only includers shown on point hover'
-
-
-#     casper.then ->
-#       test_open_point test
-
-#     casper.then ->
-#       test_browsing_points test
-
-#   casper.run ->
-#     test.done()
-
-
-# casper.test.begin 'Lurker can poke around the proposal crafting page', 33, (test) ->
-
-#   #TODO: loop here, once with logged in user, once without
-
-#   casper.start "http://localhost:8787", ->
-#     @wait(1000).then ->
-#       @HTMLStep "Crafting page can be navigated to from homepage"
-#       test.assertExists '[data-role="proposal"] [data-target="craft-position"]', 'Opportunity to add one\'s position'
-#       @click '[data-role="proposal"]:first-of-type [data-target="craft-position"]:first-of-type'
-#       @wait 5000, ->
-#         assert_in_crafting_state test
-
-#     casper.then ->
-#       casper.open("http://localhost:8787/#{example_proposal}/results").wait 5000, ->
-#         @HTMLStep "Crafting page can be accessed from results page"
-
-#         test.assertExists '[data-role="proposal"] [data-target="craft-position"]', 'Opportunity to add one\'s position'
-#         @click '[data-role="proposal"] [data-target="craft-position"]:first-of-type'
-#         @wait 5000, ->
-#           assert_in_crafting_state test
-
-#     casper.then ->
-#       casper.open("http://localhost:8787/#{example_proposal}").wait 5000, ->
-#         @HTMLStep "Crafting page can be directly opened"
-
-#         @HTMLCapture 'body',
-#           caption: 'Full crafting page, different sizes'
-#           sizes: [ [1200, 900], [600, 500], [1024, 768] ]
-
-#         assert_in_crafting_state test
-
-#     casper.then ->
-#       test_open_point test
-
-#     casper.then ->
-#       test_browsing_points test
-
-#   casper.run ->
-#     test.done()
+casper.test.begin 'Lurker can poke around homepage', 13, (test) ->
+
+  #TODO: loop here, once with logged in user, once without
+  casper.start "http://localhost:8787/", ->
+
+    casper.then ->
+      casper.wait 1000, ->
+        @HTMLStep "Homepage can be mucked about"
+
+        @HTMLCapture 'body',
+          caption: 'Full homepage, different sizes'
+          sizes: [ [1200, 900], [600, 500], [1024, 768] ]
+
+        @HTMLCapture '[data-role="proposal"]',
+          caption: 'One of the proposals'
+
+        @HTMLCapture '#proposals-container',
+          caption: 'Active proposals'
+
+        assert_homepage_loaded test
+
+    casper.then ->
+      @HTMLStep "load some more proposals"
+
+      @click '#proposals-container [data-target="load-proposals"]'
+      @wait 10000, ->
+        @HTMLCapture '#proposals-container',
+          caption: 'Active proposals'
+
+        test.assertExists '[data-target="proposallist:page"]', 'pagination is shown after loading proposals'
+        @HTMLCapture '.proposals-operations',
+          caption: 'Proposals pagination after loading more'
+
+        @click '#proposals-container-completed [data-target="load-proposals"]'
+        @wait 10000, ->
+          test.assertExists '#proposals-container-completed [data-target="proposallist:page"]', 'pagination for inactive proposals is shown after loading inactive proposals'
+
+    casper.then ->
+      casper.open("http://localhost:8787/#{example_proposal}").wait 5000, ->
+        @HTMLStep "Homepage can be accessed from different page"
+        test.assertExists '[data-target="go-home"]', 'Opportunity to navigate to homepage'
+        @click '[data-target="go-home"]'
+        @wait 1000, ->
+          assert_homepage_loaded test
+
+
+  casper.run ->
+    test.done()
+
+
+
+casper.test.begin 'Lurker can poke around a proposal results page', 35, (test) ->
+
+  #TODO: loop here, once with logged in user, once without
+  casper.start "http://localhost:8787/", ->
+
+    #TODO: loop, once going to active proposal, next to an inactive proposal
+    #TODO: loop, proposal with activity and without activity
+
+    casper.then ->
+      @wait 1000, ->
+        @HTMLStep 'Results page can be accessed from homepage'
+        test.assertExists '[data-role="proposal"] .peer-reasons[data-state="points-collapsed"]', 'Peer reasons exists in collapsed form'
+        @click '[data-role="proposal"]:first-of-type .peer-reasons[data-state="points-collapsed"]:first-of-type'
+        @wait 5000, ->
+          @HTMLCapture 'body',
+            caption : 'The results page'
+
+          assert_in_results_state test
+
+    casper.then ->
+
+      casper.open("http://localhost:8787/#{example_proposal}").wait 5000, ->
+        @HTMLStep "Results page can be accessed from crafting page"
+        test.assertExists '[data-target="view-results"]', 'Opportunity to navigate to results page'
+        @click '[data-target="view-results"]'
+        @wait 1000, ->
+          assert_in_results_state test
+
+    casper.then ->
+      casper.open("http://localhost:8787/#{example_proposal}/results").wait 5000, ->
+        @HTMLStep "Results page can be directly opened"
+        assert_in_results_state test
+
+
+    execute_histogram_tests = (state) =>
+      @HTMLStep "#{state} histogram"
+
+      if state == 'hover'
+        casper.mouse.move '.histogram-bar:first-of-type .bar-people'
+      else
+        @mouse.click '.histogram-bar:first-of-type .bar-people'
+        @mouse.move 'body'
+
+      @wait 250, ->
+        test.assertSelectorHasText '.pointlist-header-label', 'Pros', "Pros present in pros header when #{state}"
+        test.assertSelectorHasText '.pointlist-header-label', 'upport', "Supporter is present in pros header when #{state}"
+
+        @HTMLCapture '[data-role="proposal"]',
+          caption : "#{state} histogram bar"
+
+    # hover over histogram; then click
+    casper.then ->
+      execute_histogram_tests 'hover'
+
+    casper.then ->
+      execute_histogram_tests 'click'
+
+
+    casper.then ->
+      @HTMLStep 'hover over a point'
+
+      @mouse.move 'body'
+      point_includers = @evaluate ->
+        return _.uniq($('[data-role="point"]:first').attr("includers").split(',')).length
+
+      total_avatars = @evaluate -> return $(".histogram .avatar:visible").length
+
+      @mouse.move '[data-role="point"]'
+      @wait 200, ->
+        @HTMLCapture '[data-role="proposal"]',
+          caption : "Hovering over a point"
+
+        includers_hidden = @evaluate ->
+          hidden = -> $(this).css('opacity') == '0' || $(this).css('visibility') == 'hidden'
+          return $(".histogram .avatar:visible").filter(hidden).length
+
+        test.assertEqual total_avatars - includers_hidden, point_includers, 'Only includers shown on point hover'
+
+
+    casper.then ->
+      test_open_point test
+
+    casper.then ->
+      test_browsing_points test
+
+  casper.run ->
+    test.done()
+
+
+casper.test.begin 'Lurker can poke around the proposal crafting page', 33, (test) ->
+
+  #TODO: loop here, once with logged in user, once without
+
+  casper.start "http://localhost:8787", ->
+    @wait(1000).then ->
+      @HTMLStep "Crafting page can be navigated to from homepage"
+      test.assertExists '[data-role="proposal"] [data-target="craft-position"]', 'Opportunity to add one\'s position'
+      @click '[data-role="proposal"]:first-of-type [data-target="craft-position"]:first-of-type'
+      @wait 5000, ->
+        assert_in_crafting_state test
+
+    casper.then ->
+      casper.open("http://localhost:8787/#{example_proposal}/results").wait 5000, ->
+        @HTMLStep "Crafting page can be accessed from results page"
+
+        test.assertExists '[data-role="proposal"] [data-target="craft-position"]', 'Opportunity to add one\'s position'
+        @click '[data-role="proposal"] [data-target="craft-position"]:first-of-type'
+        @wait 5000, ->
+          assert_in_crafting_state test
+
+    casper.then ->
+      casper.open("http://localhost:8787/#{example_proposal}").wait 5000, ->
+        @HTMLStep "Crafting page can be directly opened"
+
+        @HTMLCapture 'body',
+          caption: 'Full crafting page, different sizes'
+          sizes: [ [1200, 900], [600, 500], [1024, 768] ]
+
+        assert_in_crafting_state test
+
+    casper.then ->
+      test_open_point test
+
+    casper.then ->
+      test_browsing_points test
+
+  casper.run ->
+    test.done()

 example_user = 262
 example_user_with_proposal = 1


Issue #592: Added tests

Issue #592: Added tests

--- test/unit/mediatorsAPITest.coffee
+++ test/unit/mediatorsAPITest.coffee
@@ -360,3 +360,38 @@
         return

       throw new Error 'Failed'
+
+    it "should reject config that is NOT defined as an array but has an array value", ->
+      try
+        mediators.validateConfig(
+          [
+            param: "param1"
+            type: "string"
+            array: false
+          ],
+          param1: [
+            "v1"
+            "v2"
+          ]
+        )
+      catch err
+        return
+
+      throw new Error 'Failed'
+
+    it "should reject config that is NOT defined as an array but has an array value ('array' undefined - default behaviour)", ->
+      try
+        mediators.validateConfig(
+          [
+            param: "param1"
+            type: "string"
+          ],
+          param1: [
+            "v1"
+            "v2"
+          ]
+        )
+      catch err
+        return
+
+      throw new Error 'Failed'


unique subjects

unique subjects

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -279,7 +279,7 @@
     console.log 'start image\t\t', subject.zooniverse_id

     # Ensure unique ids for upcoming subjects
-    Subject.instances = _.uniq(Subject.instances, true)
+    Subject.instances = _.uniq(Subject.instances)

     # Reset classification variables
     @reset()


fix disable marking sufraces during play

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -488,7 +488,7 @@

   onClickPlay: ->
     return if @el.hasClass 'playing'  # play only once at a time
-    @disableMarkingSurfaces
+    @disableMarkingSurfaces()
     @playButton.attr 'disabled', true
     @el.addClass 'playing'


on my way to fixing disable/enableMarkingSurfaces()

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -95,7 +95,7 @@
     whatKind:
       enter: ->
         # console.log "STATE: \'whatKind/enter\'"
-        @disableMarkingSurfaces()
+        # @disableMarkingSurfaces()

         # reset asteroid/artifact selector
         for e in @el.find('input[name="classifier-type"]')
@@ -114,7 +114,7 @@
         @activateFrame 0
         # create new asteroid
         @currAsteroid = new Sighting({type:"asteroid"})
-        @enableMarkingSurfaces()
+        # @enableMarkingSurfaces()
         @el.find('.asteroid-classifier').show()
         @finishButton.hide()
         @doneButton.show()
@@ -122,22 +122,22 @@
         @doneButton.prop 'disabled', true

       exit: ->
-        @disableMarkingSurfaces()
+        # @disableMarkingSurfaces()
         @el.find('.asteroid-classifier').hide()
         @doneButton.hide()
         @finishButton.show()

     artifactTool:
       enter: ->
-        @enableMarkingSurfaces()
+        # @enableMarkingSurfaces()
         @el.find('.artifact-classifier').show()
       exit: ->
-        @disableMarkingSurfaces()
+        # @disableMarkingSurfaces()
         @el.find('.artifact-classifier').hide()

   constructor: ->
     super
-    @asteroidMarkedInFrame = []
+    @asteroidMarkedInFrame = [ null, null, null, null ]
     @playTimeouts = []
     @el.attr tabindex: 0
     @el.attr 'flicker', "true"
@@ -163,14 +163,17 @@
       @markingSurfaceList[i].svgRoot.attr 'id', "classifier-svg-root-#{i}"
       @fourUpContainer.append @markingSurfaceList[i].el
       @markingSurfaceList[i].on 'create-tool', (tool) =>
+        console.log 'tool created'
         if @state is 'asteroidTool'
           # enforce one mark per frame
           if @asteroidMarkedInFrame[@currFrameIdx]
-            # console.log 'frame already marked!'
+            console.log 'frame already marked!'
             @destroyMarksInFrame @currFrameIdx, @currAsteroid.id
           else
             # console.log 'frame was empty'
             @el.find(".asteroid-frame-complete-#{@currFrameIdx}").prop 'checked', true
+            console.log 'frame was empty'
+            @el.find(".asteroid-frame-complete-#{@currFrameIdx+1}").prop 'checked', true
             @asteroidMarkedInFrame[@currFrameIdx] = true
           # enable 'done' button only if all frames marked
           numFramesComplete = 0
@@ -181,6 +184,7 @@
             @doneButton.prop 'disabled', false
         tool.controls.controller.setMark(@currFrameIdx, @currAsteroid.id)

+    @setState 'whatKind'
     User.on 'change', @onUserChange
     Subject.on 'fetch', @onSubjectFetch
     Subject.on 'select', @onSubjectSelect
@@ -188,7 +192,6 @@
     for surface in @markingSurfaceList
       surface.on "create-mark", @onCreateMark

-    @disableMarkingSurfaces()

   renderTemplate: =>
     super
@@ -257,8 +260,8 @@
     surface.reset() for surface in @markingSurfaceList

   disableMarkingSurfaces: =>
-    console.log "disabled should have been called" #TODO
-
+    surface.disable() for surface in @markingSurfaceList
+
   enableMarkingSurfaces: =>
     surface.enable() for surface in @markingSurfaceList

@@ -327,11 +330,14 @@
       element.style.height = newSize + "px"

   destroyMarksInFrame: (frame_idx, curr_ast_id) ->
-    console.log "Destroy marks in frame: ", frame_idx
+    console.log "Destroy marks for current asteroid (", curr_ast_id, ") in frame: ", frame_idx
     for surface in @markingSurfaceList
       for theMark in surface.marks
-        if theMark?.frame is frame_idx and theMark?.asteroid_id is @currAsteroid.id
-          theMark?.destroy()
+        # console.log 'current mark: ', theMark, ' frame: ', theMark.frame, ' asteroid_id: ', theMark.asteroid_id
+        if theMark.frame is frame_idx and theMark.asteroid_id is @currAsteroid.id
+          console.log 'BOOYAH'
+        #   console.log 'destroying mark: ', @theMark
+          theMark.destroy()

   onClickAsteroidNotVisible: ->
     console.log 'onClickAsteroidNotVisible: '


fix typo

fix typo

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -183,7 +183,7 @@

     clickEvent =
       event: 'courseOptedOut'
-      value: out_out
+      value: opt_out
       timestamp: (new Date).toUTCString()
     @recordedClickEvents.push clickEvent


Make dict proptype a function stub

Make dict proptype a function stub

--- proptypes.coffee
+++ proptypes.coffee
@@ -14,7 +14,8 @@

 J.PropTypes =
-    dict: {name: "J.PropTypes.dict"}
+    dict: (params) ->
+        # TODO

     elem: (componentSpec = null) ->
         # TODO


Update hal.coffee

Update hal.coffee

.

--- scripts/hal.coffee
+++ scripts/hal.coffee
@@ -14,3 +14,6 @@

         robot.hear /도모 (.*)=상 (.*)데스/i, (res) ->
                 res.send "도모 #{res.match[2]}=상 #{res.match[1]}데스"
+
+        robot.hear /하이쿠를 읊어라/i, (res) ->
+                res.send "아이에에에! 사요나라!"


Page model has timestamps

Page model has timestamps

--- models/page.coffee
+++ models/page.coffee
@@ -3,4 +3,5 @@

   Page = bookshelf.Model.extend({
     tableName: 'pages'
+    hasTimestamps: true
   })


Base User model to encapsulate shared behavior for CurrentUser + LoggedOutUser

--- models/user.coffee
+++ models/user.coffee
@@ -1,15 +1,11 @@
-sd                = require('sharify').data
-Backbone          = require 'backbone'
-ArtworkCollection = require './artwork_collection.coffee'
+_         = require 'underscore'
+Backbone  = require 'backbone'
+Geo       = require './mixins/geo.coffee'

+# Base User model for shared functionality between
+# CurrentUser and LoggedOutUser
 module.exports = class User extends Backbone.Model
+  _.extend @prototype, Geo

-  urlRoot: "#{sd.API_URL}/api/v1/user"
-
-  # Should only be run after the user has been fetched and has an id
-  initializeDefaultArtworkCollection: (options) ->
-    unless @get('artworkCollections')?.length > 0
-      @set artworkCollections: [new ArtworkCollection(userId: @get('id'))]
-    @defaultArtworkCollection().fetch(options) unless @defaultArtworkCollection.fetched
-
-  defaultArtworkCollection: -> @get('artworkCollections')[0]
+  refresh: (options = {}) ->
+    @fetch _.extend(url: '/user/refresh', options)


Fix mispelling

Fix mispelling

--- packages/rocketchat-lib/server/functions/Notifications.coffee
+++ packages/rocketchat-lib/server/functions/Notifications.coffee
@@ -16,8 +16,8 @@
 			[roomId, e] = eventName.split('/')

 			user = Meteor.users.findOne @userId, {fields: {username: 1}}
-			if RocketChat.cache.Subscription.findByIndex('rid,u._id', [roomId, @userId]).fetch()?
-				subscriptions = RocketChat.cache.Subscription.findByIndex('rid', roomId).fetch()
+			if RocketChat.cache.Subscriptions.findByIndex('rid,u._id', [roomId, @userId]).fetch()?
+				subscriptions = RocketChat.cache.Subscriptions.findByIndex('rid', roomId).fetch()
 				for subscription in subscriptions
 					RocketChat.Notifications.notifyUser(subscription.u._id, eventName, args...)


Port PR #5 into azarasi

Port PR #5 into azarasi

--- modules/rng.coffee
+++ modules/rng.coffee
@@ -28,10 +28,10 @@
     # Not actually random but ¯\_(ツ)_/¯
     @registerCommand 'rate', ({ msg, args, l })=>
       return unless args
+      hash = 0
       for i in [0 ... args.length]
         chr   = args.charCodeAt(i)
         hash  = ((hash << 5) - hash) + chr
-        hash |= 0
       rate = Math.ceil(((hash & 0xFF) / 255) * 10)
       msg.reply l.gen(l.rng.rate, args, rate)


Remove unnecessary constructor

Remove unnecessary constructor

--- src/chart-d3-doughnut.coffee
+++ src/chart-d3-doughnut.coffee
@@ -2,8 +2,5 @@
 class Chart.D3Doughnut extends Chart.D3Pie
   'use strict'

-  constructor: (selectors, data, options) ->
-    super(selectors, data, options)
-
   getInnerRadius: (outerRadius, options) ->
     ~~(outerRadius * (options.percentageInnerCutout / 100))


Fix datasources pagination

Fix datasources pagination

--- src/client/datasource.coffee
+++ src/client/datasource.coffee
@@ -103,6 +103,7 @@
         transportDfd.then (obj, headers) =>
           if _.isArray(obj)
             @paginationLinks = parseLinkHeader(headers['Link'])
+            console.log @paginationLinks
             dfd.resolve (new Backbone.Collection obj)
           else
             dfd.resolve (new Backbone.Model obj)
@@ -133,14 +134,14 @@
     # Change datasource path to the path of the previous page.
     previous: ->
       unless @isFirst()
-        @def.path = @paginationLinks.prev
+        _.extend(@def.params, parseQueryString(@paginationLinks.prev))

     # Go to next page.
     #
     # Change datasource path to the path of the next page.
     next: ->
       unless @isLast()
-        @def.path = @paginationLinks.next
+        _.extend(@def.params, parseQueryString(@paginationLinks.next))

     # Sort the datasource by a given field in a given direction.
     #


Fix error handling for long geonames requests

--- src/controllers/index.coffee
+++ src/controllers/index.coffee
@@ -116,9 +116,10 @@
     place_ids = []
     counter = 0
     start_time = new Date().getTime()
+    sent_requests_count = 0
     for photo in photos
       place_id = photo.place_id
-     # console.log place_id
+      # console.log place_id

       if place_ids.indexOf(place_id) != -1
         #console.log 'place_id is dublicated'
@@ -131,26 +132,25 @@
       req_uri = 'http://api.geonames.org/countrySubdivisionJSON?username='+geonames_username+'&lat='+photo.latitude+'&lng='+photo.longitude+'&lang=ru&\
       uri=' + photo.url_z + '&res_url=' + res_url

-      console.log 'Geonames request: ' + req_uri
+      console.log sent_requests_count #'Geonames request: ' + req_uri
       try
         request.get req_uri, (error, rsp, data) ->
           if error
             console.log 'Error: ' + error
-            return
-
-          console.log 'I recieved response from geonames:'
-          counter += 1
-          img_url = (rsp.client._httpMessage.path.match /uri\=.+&/)[0].slice 4, -1
-          #console.log img_url
-          res_url = (rsp.client._httpMessage.path.match /res_url\=.+/)[0].slice 8
-          #console.log res_url
-          geo_photo =
-            url: img_url,
-            country: JSON.parse(data).countryName,
-            res_url: res_url,
-          #console.log geo_photo
-          if geo_photo.country != undefined
-            result.push geo_photo
+          else
+            counter += 1
+            console.log counter
+            img_url = (rsp.client._httpMessage.path.match /uri\=.+&/)[0].slice 4, -1
+            #console.log img_url
+            res_url = (rsp.client._httpMessage.path.match /res_url\=.+/)[0].slice 8
+            #console.log res_url
+            geo_photo =
+              url: img_url,
+              country: JSON.parse(data).countryName,
+              res_url: res_url,
+            #console.log geo_photo
+            if geo_photo.country != undefined
+              result.push geo_photo

           #console.log "[result]: " + result.length + " [photos]: " + photos.length + " photo.url=" + photo.url_z

@@ -158,17 +158,19 @@
           if (new Date(). getTime() - start_time) > 5000
             console.log 'waiting for too long... i will try again'
             get_from_flickr = true
-            if get_from_db == true
+            if get_from_db == true and not res.headerSent
               res.send result

           else if counter == place_ids.length
             get_from_flickr = true
             console.log "Photos from flickr are ready; db status: " + get_from_db
-            if get_from_db == true
+            if get_from_db == true and not res.headerSent
               res.send result

       catch e
         console.log e
+        if not res.headerSent
+          res.send result

     console.log "I've sent all geo requests and now waiting for responses"


Add id_token to a response.

Add id_token to a response.

--- src/core/oauth/oauth2.coffee
+++ src/core/oauth/oauth2.coffee
@@ -71,6 +71,7 @@
 					result =
 						access_token: response.access_token
 						token_type: response.body.token_type
+						id_token: response.body.id_token
 						expires_in: expire
 						base: @_provider.baseurl
 						request: requestclone


removing normalizeKey method

removing normalizeKey method

--- src/color.coffee
+++ src/color.coffee
@@ -120,10 +120,7 @@
     rgbMap = ((channel * 255).round() for channel in [r, g, b])

     return rgbMap.concat(a)
-
-  normalizeKey = (key) ->
-    key.toString().toLowerCase().split(' ').join('')
-
+
   channelize = (color, alpha) ->
     return color.channels() if color.channels?
     if Object.isArray color


Use env var for setting limit and parameterLimit to express's urlenconder

--- src/robot.coffee
+++ src/robot.coffee
@@ -408,11 +408,13 @@
   #
   # Returns nothing.
   setupExpress: ->
-    user    = process.env.EXPRESS_USER
-    pass    = process.env.EXPRESS_PASSWORD
-    stat    = process.env.EXPRESS_STATIC
-    port    = process.env.EXPRESS_PORT or process.env.PORT or 8080
-    address = process.env.EXPRESS_BIND_ADDRESS or process.env.BIND_ADDRESS or '0.0.0.0'
+    user       = process.env.EXPRESS_USER
+    pass       = process.env.EXPRESS_PASSWORD
+    stat       = process.env.EXPRESS_STATIC
+    port       = process.env.EXPRESS_PORT or process.env.PORT or 8080
+    address    = process.env.EXPRESS_BIND_ADDRESS or process.env.BIND_ADDRESS or '0.0.0.0'
+    limit      = process.env.EXPRESS_LIMIT or '100kb'
+    paramLimit = process.env.EXPRESS_PARAMETER_LIMIT or 1000

     express = require 'express'
     multipart = require 'connect-multiparty'
@@ -427,7 +429,7 @@
     app.use express.query()

     app.use express.json()
-    app.use express.urlencoded()
+    app.use express.urlencoded({limit: limit, parameterLimit: paramLimit})
     # replacement for deprecated express.multipart/connect.multipart
     # limit to 100mb, as per the old behavior
     app.use multipart(maxFilesSize: 100 * 1024 * 1024)


Add `route` function on Robot

Add `route` function on Robot

This allows users to specify routes and callbacks. Those callbacks will
be called when the matched route is requested.

--- src/robot.coffee
+++ src/robot.coffee
@@ -23,10 +23,21 @@
     @listeners   = []
     @loadPaths   = []
     @enableSlash = false
+    connect      = require 'connect'
+    @connect     = connect()

     @logger      = new Log process.env.HUBOT_LOG_LEVEL or "info"

     @loadAdapter adapterPath, adapter if adapter?
+
+  # Public: Specify a router and callback to register as Connect middleware.
+  #
+  # route    - A String of the route to match.
+  # callback - A Function that is called when the route is requested
+  #
+  # Returns nothing.
+  route: (route, callback) ->
+    @connect.use route, callback

   # Public: Adds a Listener that attempts to match incoming messages based on
   # a Regex.
@@ -241,6 +252,7 @@
     matchedUsers

   run: ->
+    @connect.listen process.env.PORT
     @adapter.run()

   shutdown: ->


parseDate: YY -> 20YY

parseDate: YY -> 20YY

--- src/utils.coffee
+++ src/utils.coffee
@@ -9,6 +9,7 @@
   match = generalRE.exec str
   if match
     [__, day, __, month, __, year] = match
+    if year and year.length == 2 then year = "20#{year}"
     date = new Date(
       year or currentDate.getUTCFullYear()
       if month then month-1 else currentDate.getUTCMonth()


Fix distinct

Fix distinct

--- demo_server/src/server/sequence.coffee
+++ demo_server/src/server/sequence.coffee
@@ -38,7 +38,14 @@
     distinct: ->
         neu = []
         for v in @asArray()
-            unless v in neu then neu.push v
+            # The operator in doesn't work for objects
+            foundCopy = false
+            for w in neu
+                if v.eq(w).asJSON() is true
+                    foundCopy = true
+                    break
+            if foundCopy is false
+                neu.push v
         new RDBArray neu

     map: (mapping) ->


* using the default-type value instead

* using the default-type value instead

--- src/attribute-type.coffee
+++ src/attribute-type.coffee
@@ -32,7 +32,7 @@
     set:
       type: 'Function'

-  @defaultType: Type('String')
+  #@defaultType: Type('String')
   # _assign: (aOptions)->
   #   if isString aOptions
   #     vType = aOptions


Update file parser: catch files without an extension, generalize regex

--- parsers/file_parser.coffee
+++ parsers/file_parser.coffee
@@ -13,15 +13,17 @@
   failures: []

 RegExplorer =
-  sameDirMarkdownLink:  /href="(?!http)(?!\/\/)(?!\.\.)(.*\.md)/
-  otherDirMarkdownLink: /href="(?!http)(\.\..*?\.md)/
-  relativeImageLink:    /.*"(\.\.\/images.*?\.png|jpg)/
+  # not beginning with http or mailto or a ? (? bc `marked` parses some regex in their code as links…)
+  # not beginning with "// (markdown shortcut link)
+  # capture below
+  #  any chars
+  #  capture the anchor link (which is optional)
+  #  capture the final double quote
+  filePath: /href="(?!\?|http|mailto)(?!\/\/)((?!#)(.*?)((#.*?)?)("))/

 FileParser =
-  relativeImages: []
-
-  sameDirMarkdowns:  []
-  otherDirMarkdowns: []
+  imagePaths: []
+  markdownPaths:  []

   currentPath: ''

@@ -38,20 +40,13 @@
     _.each markdown, (line) =>
       line = line.replace(/&/g, '&')

-      sameDirMarkdownFileMatch = RegExplorer.sameDirMarkdownLink.exec(line)
-      otherDirMarkdownFileMatch = RegExplorer.otherDirMarkdownLink.exec(line)
-      relativeImageFileMatch = RegExplorer.relativeImageLink.exec(line)
-
-      @relativeImages.push relativeImageFileMatch[1] if relativeImageFileMatch
-      @sameDirMarkdowns.push sameDirMarkdownFileMatch[1] if sameDirMarkdownFileMatch
-      @otherDirMarkdowns.push otherDirMarkdownFileMatch[1] if otherDirMarkdownFileMatch
-
-      @files = @relativeImages.concat(@sameDirMarkdowns, @otherDirMarkdowns)
+      filePathMatch = RegExplorer.filePath.exec(line)
+      @files.push filePathMatch[2] if filePathMatch

     @checkFiles(@files, @currentPath, file.fullPath)

     failed = true if App.failures.length
-    @relativeImages = @sameDirMarkdowns = @otherDirMarkdowns = []
+    @files = []

     failed


Change dist location

Change dist location

--- .gruntconfig.coffee
+++ .gruntconfig.coffee
@@ -1,6 +1,6 @@
 module.exports =
   app: "app"
-  dist: "../public"
+  dist: "dist"
   tmpPath: ".tmp"
   scriptlang:"coffee" #js|coffee
   templatelang:"jade" #swig|jade


Enable BringCast, reorder WindowsPhone list

--- src/coffee/clients.coffee
+++ src/coffee/clients.coffee
@@ -195,22 +195,23 @@

   windowsPhone: [
     {
-      title: 'Podcasts'
-      scheme: 'podcast:'
-      icon: 'windowsphone/podcasts@2x.png'
+      title: 'BringCast'
+      scheme: 'bringcast:'
+      icon: 'windowsphone/bringcast@2x.png'
+      install: 'http://windowsphone.com/s?appId=e5abef38-d413-e011-9264-00237de2db9e'
+      http: true
     },
     {
       title: 'Podcast Picker'
       scheme: 'podcastpicker://'
       icon: 'windowsphone/podcastpicker@2x.png'
       install: 'http://windowsphone.com/s?appId=79b72069-b656-47d2-bab1-fa2d4061825e'
-    }#,
-    #{
-      #title: 'BringCast'
-      #scheme: 'bringcast:'
-      #icon: 'windowsphone/bringcast@2x.png'
-      #install: 'http://windowsphone.com/s?appId=e5abef38-d413-e011-9264-00237de2db9e'
-    #}
+    },
+    {
+      title: 'Podcasts'
+      scheme: 'podcast:'
+      icon: 'windowsphone/podcasts@2x.png'
+    }
   ]

   windows7: [


Fixed handling of asset compile to handle async nature of copy taks

--- src/commands/watch.coffee
+++ src/commands/watch.coffee
@@ -87,16 +87,18 @@
   compile: =>
     paths = @config.paths
     fs_utils.write @fileList, @config, @plugins, (error, result) =>
-      assetError = null
-      assetPaths = if paths.assets instanceof Array then paths.assets else [paths.assets]
-      assetPaths.map (assetPath) ->
-        fs_utils.copyIfExists assetPath, paths.public, yes, (error) => assetError = error if assetError?
-
-      logger.error "Asset compilation failed: #{assetError}" if assetError?
-      logger.info "compiled."
-      logger.debug "compilation time: #{Date.now() - @start}ms"
-      @watcher.close() unless @persistent
-      @onCompile null, result
+      assets = path.assets.concat()
+      copyAssets = (error) =>
+        if error?
+          logger.error "Asset compilation failed: #{error}"
+        else if assets.length == 0
+          logger.info "compiled."
+          logger.debug "compilation time: #{Date.now() - @start}ms"
+          @watcher.close() unless @persistent
+          @onCompile null, result
+        else
+          fs_utils.copyIfExists assets.shift(), paths.public, yes, copyAssets
+      copyAssets()

   watch: ->
     @initServer()


Better vars naming

Better vars naming

--- neo4jdriver.coffee
+++ neo4jdriver.coffee
@@ -267,15 +267,15 @@

     return new Neo4jData response

-  __getCursor: (request, callback, reactive) ->
+  __getCursor: (task, callback, reactive) ->
     unless callback
       return __wait (fut) =>
-        @__batch request, (error, data) ->
+        @__batch task, (error, data) ->
           console.error error if error
           fut.return new Neo4jCursor data
         , reactive
     else
-      @__batch request, (error, data) ->
+      @__batch task, (error, data) ->
         callback error, new Neo4jCursor data
       , reactive
       return @


Fix the max reference.

Fix the max reference.

--- app/javascripts/directives.coffee
+++ app/javascripts/directives.coffee
@@ -188,7 +188,7 @@
           #
           # TODO - Obtain this value from the time series object properties.
           #
-          if newVal <= scope.timeSeries.image.slices.size
+          if newVal <= scope.timeSeries.image.volumes.size
             # sliderTimeSeries.noUiSlider.set(newVal)
             updateImage(newVal)


RegExp fix

RegExp fix

--- app/scripts/lib/formatters.coffee
+++ app/scripts/lib/formatters.coffee
@@ -88,7 +88,7 @@
     ]

   YT_PREVIEW_FORMATTER:
-    [/(?:[^<]+)<\/a>/g
+    [/([^<]+)<\/a>/g
     , (_m, id, link) ->
       ""
     ]


Added some comments for future directions.

tl;dr: it was a mistake to use the simple algorithm. Should have gone with
double-Dijkstra.

--- maze.coffee
+++ maze.coffee
@@ -16,7 +16,8 @@
     # Generate maze using growing tree algorithm:
     @growing_tree()
     # Choose maximally distant cells for start and finish to make it
-    # interesting:
+    # interesting. # FIXME use double-Dijkstra instead of this simple
+    # algorithm - it also works for mazes with cycles
     maxpath_info = @maxpath((0 for _ in @dimensions), [])
     @start = maxpath_info.cell1
     @finish = maxpath_info.cell2
@@ -43,6 +44,11 @@
       cell = choice(active_cells)
       unvisited_neighbors =
         (n for n in @neighbors(cell) when not (n of visited_cells))
+      # TODO Only using *unvisited* neighbors rules out cycles - but you would
+      # occasionally expect cycles in a "natural" maze. We could make picking a
+      # visited neighbor merely improbable instead of impossible. But we first
+      # need a more general longest-simple-path-finding algorithm (double
+      # Dijkstra).
       if unvisited_neighbors.length > 0
         neighbor = choice(unvisited_neighbors)
         passage = [cell, neighbor]


Correct data type

Correct data type


Wait to observe buffer markers until Marker::onDidChange called

Signed-off-by: Nathan Sobo <2e8aa918660411855c6d44d5bb2da677aa033255@github.com>

--- src/marker.coffee
+++ src/marker.coffee
@@ -48,6 +48,7 @@
   oldTailBufferPosition: null
   oldTailScreenPosition: null
   wasValid: true
+  hasChangeObservers: false

   ###
   Section: Construction and Destruction
@@ -57,14 +58,8 @@
     @emitter = new Emitter
     @disposables = new CompositeDisposable
     @id = @bufferMarker.id
-    @oldHeadBufferPosition = @getHeadBufferPosition()
-    @oldHeadScreenPosition = @getHeadScreenPosition()
-    @oldTailBufferPosition = @getTailBufferPosition()
-    @oldTailScreenPosition = @getTailScreenPosition()
-    @wasValid = @isValid()

     @disposables.add @bufferMarker.onDidDestroy => @destroyed()
-    @disposables.add @bufferMarker.onDidChange (event) => @notifyObservers(event)

   # Essential: Destroys the marker, causing it to emit the 'destroyed' event. Once
   # destroyed, a marker cannot be restored by undo/redo operations.
@@ -102,6 +97,14 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidChange: (callback) ->
+    unless @hasChangeObservers
+      @oldHeadBufferPosition = @getHeadBufferPosition()
+      @oldHeadScreenPosition = @getHeadScreenPosition()
+      @oldTailBufferPosition = @getTailBufferPosition()
+      @oldTailScreenPosition = @getTailScreenPosition()
+      @wasValid = @isValid()
+      @disposables.add @bufferMarker.onDidChange (event) => @notifyObservers(event)
+      @hasChangeObservers = true
     @emitter.on 'did-change', callback

   # Essential: Invoke the given callback when the marker is destroyed.


Still broken. There's a lot more code, though!

--- src/markov.coffee
+++ src/markov.coffee
@@ -28,27 +28,84 @@
 Util = require 'util'

 class MarkovModel
+  sentinel = ' '
+
   constructor: (@storage, @ply) ->

-  transitions: (phrase) ->
-    words = (word for word in phrase.split /\s+/ when word.length > 0)
+  _words: (phrase) ->
+    (word for word in phrase.split /\s+/ when word.length > 0)
+
+  _random: (max) ->
+    Math.floor(Math.random() * (max + 1))
+
+  _chooseWeighted: (choices) ->
+    total = 0
+    total += freq for key, freq of choices
+    chosen = @._random(total)
+
+    acc = 0
+    for key, freq of choices
+      acc += freq
+      return key if acc >= chosen
+
+    throw "Bad choice: #{chosen}"
+
+  _encode: (key) ->
+    encoded = for part in key
+      if part then "#{part.length}#{part}" else "0"
+    encoded.join('')
+
+  _decode: (key) ->
+    results = []
+    index = 0
+    while index < key.length
+      length = parseInt key.charAt(index)
+      part = key.slice(index + 1, index + 1 + length)
+      results.push part
+      index += length
+    results
+
+  _transitions: (phrase) ->
+    words = @._words(phrase)
     words.unshift null for i in [1..@ply]
     words.push null for i in [1..@ply]
     for i in [0..words.length - @ply - 1]
-      { from: words.slice(i, i + @ply), to: words[i + @ply] }
+      { from: @._encode(words.slice(i, i + @ply)), to: words[i + @ply] or sentinel }

   learn: (phrase) ->
-    for t in this.transitions(phrase)
+    for t in @._transitions(phrase)
       ts = @storage[t.from] ?= {}
       ts[t.to] = (ts[t.to] or 0) + 1

+  generate: (seed, max) ->
+    words = @._words(seed)
+
+    key = words.slice(words.length - @ply, words.length)
+    if key.length < @ply
+      key.unshift null for i in [1..@ply - key.length]
+
+    chain = []
+    chain.push words...
+
+    for i in [words.length..max]
+      next = @._chooseWeighted @storage[@._encode(key)]
+      break if next is sentinel
+
+      chain.push next
+      key.shift()
+      key.push next
+
+    chain.join ' '
+
 module.exports = (robot) ->

-  model = new MarkovModel(robot.brain.data, 1)
+  robot.brain.data.markov ?= {}
+  model = new MarkovModel(robot.brain.data.markov, 2)

   # The robot hears ALL. You cannot run.
   robot.hear /.+$/, (msg) ->
-    tokens = model.learn msg.match[0]
+    return if /^hubot:?/i.test msg.match[0]
+    model.learn msg.match[0]

   robot.respond /markov(\s+(.+))?$/i, (msg) ->
-    msg.reply "yup yup: #{msg.match[2]}"
+    msg.reply model.generate msg.match[2] or '', 10


only add public dir on deploy

only add public dir on deploy

--- bin/deploy.coffee
+++ bin/deploy.coffee
@@ -14,8 +14,7 @@
   code

 run "npm run build"
-run "git add ."
-run "git add --force --no-ignore-removal public"
+run "git add --force --no-ignore-removal public/"
 run "git commit -m 'deploy'"
 deploy_result = run "git push --force git@heroku.com:#{HEROKU_APP}.git HEAD:master", continue_on_failure: true
 exit deploy_result


font experimentation

font experimentation

--- src/game/states/story.coffee
+++ src/game/states/story.coffee
@@ -11,6 +11,10 @@

         @t.y = @t.y - 0.45

+        #@newColour = @t.font;
+        #@newColour.fill = '#8B0000';
+        #@t.setStyle @newColour;
+
     onDown: ->

         @game.state.start 'game'


fix connect plugin bug

fix connect plugin bug

--- src/module.coffee
+++ src/module.coffee
@@ -38,10 +38,9 @@
       throw new Error 'Widget.connect: lack of class property "className"'
       return

-    @::_connectedClasses.push(cls)
+    @_connectedClasses = [] unless @_connectedClasses
+    @_connectedClasses.push(cls)
     @[cls.className] = cls if cls.className
-
-  _connectedClasses: []

   _init: ->

@@ -50,7 +49,7 @@
   constructor: (opts) ->
     @opts = $.extend({}, @opts, opts)

-    instances = for cls in @_connectedClasses
+    instances = for cls in @constructor._connectedClasses
       name = cls.className.charAt(0).toLowerCase() + cls.className.slice(1)
       @[name] = new cls(@)


tasking_type -> target_type

tasking_type -> target_type

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -51,12 +51,12 @@
   setPeriods: (id, periods, opens_at) ->
     tasking_plans = _.map periods, (period) ->
       _.extend( _.pick(period, 'opens_at', 'due_at'),
-        target_id: period.id, tasking_type:'period'
+        target_id: period.id, target_type:'period'
       )
     @_change(id, {tasking_plans})

   _findTasking: (tasking_plans, periodId) ->
-    _.findWhere(tasking_plans, {target_id:periodId, tasking_type:'period'})
+    _.findWhere(tasking_plans, {target_id:periodId, target_type:'period'})

   _getPeriodDates: (id, period) ->
     throw new Error('BUG: Period is required arg') unless period
@@ -113,7 +113,7 @@
       if tasking
         tasking[attr] = date
       else
-        tasking = _.extend({tasking_type: 'period', target_id: periodId}, {"#{attr}":val})
+        tasking = _.extend({target_type: 'period', target_id: periodId}, {"#{attr}":val})
         tasking_plans.push(tasking)
     else
       for tasking in tasking_plans


Fix comment in server/init.coffee

Fix comment in server/init.coffee

--- server/init.coffee
+++ server/init.coffee
@@ -8,7 +8,7 @@
     CozyInstance = require './models/cozyinstance'
     AllBanksData = require "../tests/fixtures/banks-all.json"

-    # In env mode, intercept weboob requests
+    # In debug env mode, intercept weboob requests
     require '../tests/mock-weboob'

     # Bank initialization


Better wording on the "buy now" tooltip

Better wording on the "buy now" tooltip

--- js/inner.coffee
+++ js/inner.coffee
@@ -46,7 +46,7 @@
   init: (element, valueAccessor, allBindingsAccessor, viewModel) ->
     options = ko.utils.unwrapObservable(valueAccessor()) or {}
     content = ko.utils.unwrapObservable(options.content) or ""
-    $(element).popover({content: content})
+    $(element).popover({content: content, html: true, delayOut: 1000})

     ko.utils.domNodeDisposal.addDisposeCallback(element, () ->
       $(element).popover('hide')
@@ -256,9 +256,9 @@
     @popover_content = @komp =>
       switch @dont_build()
         when "no-user"
-          "pusher is not a Circle member"
+          "Invite #{@author()} to Circle to test their pushes. Send them this link: https://circleci.com/?join=test-your-code"
         when "user-not-paid"
-          "pusher doesn't have a paid plan"
+          "#{@author()}'s trial is over. Add them to your account"

   # TODO: CSRF protection
   retry_build: (data, event) =>


Subscribe to queued builds

Subscribe to queued builds

--- js/inner.coffee
+++ js/inner.coffee
@@ -375,8 +375,11 @@
   isRunning: () =>
     @start_time() and not @stop_time()

+  shouldSubscribe: () =>
+    @isRunning() or @status() == "queued"
+
   maybeSubscribe: () =>
-    if @isRunning()
+    if @shouldSubscribe()
       @build_channel = VM.pusher.subscribe(@pusherChannel())
       @build_channel.bind('pusher:subscription_error', (status) -> notifyError status)


allow rewrite to top-level /docs

allow rewrite to top-level /docs

--- js/outer.coffee
+++ js/outer.coffee
@@ -57,8 +57,8 @@
   class Docs extends Page
     rewrite_old_name: (name) =>
       switch name
-        when "/common-problems" then "/troubleshooting-common-problems"
-        when "/common-problems#intro" then "/troubleshooting-common-problems"
+        when "/common-problems" then ""
+        when "/common-problems#intro" then ""
         when "/common-problems#file-ordering" then "/file-ordering"
         when "/common-problems#missing-log-dir" then "/missing-log-dir"
         when "/common-problems#missing-file" then "/missing-file"
@@ -75,7 +75,7 @@
         when "/common-problems#capybara-timeout" then "/capybara-timeout"
         when "/common-problems#clojure-12" then "/clojure-12"

-        when "/faq" then "/troubleshooting-common-problems"
+        when "/faq" then ""
         when "/faq#permissions" then "/permissions"
         when "/faq#what-happens" then "/what-happens"
         when "/faq#look-at-code" then "/look-at_code"
@@ -145,7 +145,7 @@

     render: (cx) =>
       rewrite = @rewrite_old_name cx.params.splat[0]
-      if rewrite
+      if rewrite != false
         return cx.redirect "/docs" + rewrite
       name = @filename cx
       $("body").attr("id","docs-page").html(HAML['header'](renderContext))


client/remote-ext: add helpers to computestack

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/remote-extensions/computestack.coffee
+++ client/app/lib/remote-extensions/computestack.coffee
@@ -1,3 +1,10 @@
 remote         = require('../remote')

 module.exports = class JComputeStack extends remote.api.JComputeStack
+
+  getUnreadCount: ->
+
+    @_revisionStatus?.status?.code or 0
+
+
+  getOldOwner: -> @config?.oldOwner


Fixed AppDidQuit stuff.

Fixed AppDidQuit stuff.

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -177,7 +177,7 @@

   quit:(appInstance, callback = noop)->

-    @unregister appInstance
+    appInstance.getView().destroy()
     callback()

   quitAll:->
@@ -230,7 +230,6 @@
       @appControllers[name].instances.splice index, 1
       if @appControllers[name].instances.length is 0
         delete @appControllers[name]
-      appInstance.destroy()

   createPromptModal:(appOptions, callback)->
     # show modal and wait for response
@@ -280,6 +279,7 @@
     appView?.once "KDObjectWillBeDestroyed", =>
       @unregister appInstance
       appInstance.emit "AppDidQuit"
+      KD.getSingleton('appManager').emit  "AppDidQuit", appInstance

   setLastActiveIndex:(appInstance)->


Processing function called with port as context

--- spec/InPort.coffee
+++ spec/InPort.coffee
@@ -39,6 +39,16 @@
       chai.expect(p.isConnected()).to.equal false
     it 'should not contain a socket initially', ->
       chai.expect(p.sockets.length).to.equal 0
+
+  describe 'with processing function called with port as context', ->
+    it 'should set context to port itself', ->
+      s = new socket
+      p = new inport
+      p.on 'data', (packet, outPorts) ->
+        chai.expect(this).toBe p
+        chai.expect(packet).toEqual 'some-data'
+      p.attach s
+      s.send 'some-data'

   describe 'with default value', ->
     p = s = null


views -> allViews

views -> allViews

Change-Id: Ia7f9a8c821fc9d1337dbaaadd5a8b7d9a894b262

--- app/coffeescripts/home/views/quickStartBar/QuickStartBarView.coffee
+++ app/coffeescripts/home/views/quickStartBar/QuickStartBarView.coffee
@@ -3,7 +3,7 @@
   'i18n!dashboard'
   'compiled/home/views/quickStartBar/allViews'
   'formToJSON'
-], ({View, Model}, I18n, views) ->
+], ({View, Model}, I18n, allViews) ->

 	capitalize = (str) ->
     str.replace /\b[a-z]/g, (match) -> match.toUpperCase()
@@ -50,7 +50,7 @@
       viewName = capitalize(@modelName) + 'View'
       @currentFormView?.teardown?()
       @currentFormView = @views[viewName] or= do =>
-        view = new views[viewName]
+        view = new allViews[viewName]
         view.on 'save', @onSaveSuccess
         view.on 'saveFail', @onSaveFail
       @currentFormView.render()


fix new files in Safari

fix new files in Safari

fixes: CNVS-15200

freaking Safari doesn't implement the URLUtils
interface so we can't use `new URL`
see: https://developer.mozilla.org/en-US/docs/Web/API/URL

test plan:
* open new files in safari
  (and FF, IE, & chrome to be thorough)
* it should load.

Change-Id: Ieb219bd019e029020a572580987c98d2c93c1014
Reviewed-on: https://gerrit.instructure.com/40319
Reviewed-by: Jason Madsen <bcb5fff571a20f07c35d17ba9c364855c776dce7@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/react_files/utils/updateAPIQuerySortParams.coffee
+++ app/coffeescripts/react_files/utils/updateAPIQuerySortParams.coffee
@@ -14,9 +14,8 @@
       order: queryParams.order || ''

     oldUrl = collection.url
-    newUrl = new URL(oldUrl)
-    params = deparam(newUrl.search)
-    newUrl.search = param _.extend(params, newParams)
-    newUrl = newUrl.toString()
+    [baseUrl, search] = oldUrl.split('?')
+    params = _.extend(deparam(search), newParams)
+    newUrl = baseUrl + '?' + param(params)
     collection.url = newUrl
     collection.reset() if newUrl isnt oldUrl and !collection.loadedAll


fixing filter functionality

fixing filter functionality

--- @client/dashboard/customize.coffee
+++ @client/dashboard/customize.coffee
@@ -124,7 +124,7 @@
           INPUT
             style:
               width: '100%'
-            placeholder: 'Filter to subs with customization containing...'
+            placeholder: 'Filter to forums with customization containing...'
             ref: 'customization_filter'
             type: 'text'
             defaultValue: ''
@@ -143,12 +143,11 @@
               onChange: (ev) =>
                 @local.compare_to = ev.target.value
                 save @local
-                console.log @local
-
-              for [sub, id] in other_subs when @local.customization_filter.length == 0 || id.toLowerCase().indexOf(@local.customization_filter.toLowerCase()) > -1
+
+              for [forum, cust] in other_subs when @local.customization_filter.length == 0 || JSON.stringify(cust).toLowerCase().indexOf(@local.customization_filter.toLowerCase()) > -1
                 OPTION
-                  value: sub
-                  sub
+                  value: forum
+                  forum
             ".consider.it:"

           if !!compare_to


activity feed: set isLoading to false on clicking nav items

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -92,6 +92,7 @@
           controller.followedActivityArrived activities.first

     @getView().innerNav.on "NavItemReceivedClick", (data)=>
+      @isLoading = no
       @resetAll()
       @setFilter data.type
       @populateActivity()


Split options into options and dependencies

--- lib/Task/ActivityTask/Download.coffee
+++ lib/Task/ActivityTask/Download.coffee
@@ -11,8 +11,9 @@
     Match.check dependencies, Match.ObjectIncluding
       read: Read
       save: Save
+    dependencies.save.input = dependencies.read.output = new stream.PassThrough({objectMode: true})
     super
-    @save.input = @read.output = new stream.PassThrough({objectMode: true})
+
   execute: ->
     Promise.join(@read.execute(), @save.execute())


Fix typo

Fix typo

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -73,7 +73,7 @@
 class CampfireStreaming extends EventEmitter
   constructor: (options, @robot) ->
     unless options.token? and options.rooms? and options.account?
-      @robot.logger.error "Not enough parameters provided. I Need a token, rooms and account"
+      @robot.logger.error "Not enough parameters provided. I need a token, rooms and account"
       process.exit(1)

     @token         = options.token


Now if the user is null..

Now if the user is null..

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -31,7 +31,7 @@

     withAuthor = (callback) -> (id, created, room, user, body) ->
       if user is null
-        console.log "Null user!"
+        user = bot.Me

       bot.User user, (err, userData) ->
         if userData.user


extract getAttr & hasAttr

extract getAttr & hasAttr

--- src/render.coffee
+++ src/render.coffee
@@ -10,11 +10,17 @@
 get = (key, context) ->
   key.split(/\./).reduce(((c, key) -> c[key]), context)

+getAttr = (element, attr) ->
+  element.attributes.filter((i) -> i.name is attr)[0]
+
+hasAttr = (element, attr) ->
+  element.attributes.some((i) -> i.name is attr)
+
 renderElement = (element, context) ->
   return [element] if typeof element is 'string'
-  if element.attributes.some((i) -> i.name is 'b-repeat')
+  if hasAttr element, 'b-repeat'
     return renderElements element, context
-  if element.attributes.some((i) -> i.name is 'b-if')
+  if hasAttr element, 'b-if'
     attr = element.attributes.filter((i) -> i.name is 'b-if')[0]
     value = get attr.value, context
     return [] unless value
@@ -51,12 +57,11 @@
   children = element.children.slice()
   attributes = element.attributes.filter (i) ->
     i.name isnt 'b-repeat'
-  attr = element.attributes.filter((i) -> i.name is 'b-repeat')[0]
+  attr = getAttr element, 'b-repeat'
   match = attr.value.match /^\s*(\w+)\s*in\s*(\w+)\s*$/
   throw new Error('b-repeat:' + attr.value) unless match?
-  itemName = match[1]
-  list = context[match[2]]
-  list.reduce (c, item) ->
+  [_, itemName, listName] = match
+  context[listName].reduce (c, item) ->
     context[itemName] = item
     c.concat renderElement({ name, attributes, children }, context)
   , []


Implement object specificity for dependent attribute declarations. [Closes #53]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -80,8 +80,16 @@
       @sync() if Rivets.config.preloadData

     if @options.dependencies?.length
-      for keypath in @options.dependencies
-        Rivets.config.adapter.subscribe @model, keypath, @sync
+      for dependency in @options.dependencies
+        if /^\./.test dependency
+          model = @model
+          keypath = dependency.substr 1
+        else
+          dependency = dependency.split '.'
+          model = @view.models[dependency.shift()]
+          keypath = dependency.join '.'
+
+        Rivets.config.adapter.subscribe model, keypath, @sync

     if @isBidirectional()
       bindEvent @el, 'change', @publish


Use a parser to preprocess quotes.

Use a parser to preprocess quotes.

--- src/quote.coffee
+++ src/quote.coffee
@@ -20,6 +20,7 @@
 fs = require 'fs'
 _ = require 'underscore'
 moment = require 'moment'
+parsers = require './parser'

 CREATOR_ROLE = 'quote pontiff'

@@ -97,7 +98,7 @@
       else
         msg.send "#{quotes.length} quotes loaded successfully."

-  robot.respond /create quote: ([\s\S]*)/i, (msg) ->
+  robot.respond /(create|verbatim|slackapp|slackweb) quote:\s*([^]+)/i, (msg) ->
     unless robot.auth.hasRole(msg.message.user, CREATOR_ROLE)
       msg.reply [
         "You can't do that! You're not a *#{CREATOR_ROLE}*."
@@ -105,27 +106,25 @@
       ].join("\n")
       return

-    q = msg.match[1]
-
-    # Post-processing specific to Slack.
-    processed = ['']
-    [speaker, ts] = []
-    for line in q.split(/\n/)
-      m = line.match /(\S+) \[(\d?\d):(\d\d) ([AP]M)\]/
-      if m?
-        [x, speaker, hours, minutes, ampm] = m
-        [hours, minutes] = [parseInt(hours), parseInt(minutes)]
-        hours += 12 if ampm is 'PM'
-        timestamp = moment({hours: hours, minutes: minutes})
-        ts = timestamp.format('h:mm A D MMM YYYY')
-      else if speaker? and line.length > 0
-        processed.push "[#{ts}] #{speaker}: #{line}"
-      else if line.length > 0
-        processed.push line
-    processed.push ''
-
-    nquote = processed.join("\n")
-    fs.appendFile quotefilePath, nquote, ->
+    pname = msg.match[1]
+    pname = 'identity' if pname is 'create' or pname is 'verbatim'
+
+    parse = parsers[pname]
+    unless parse?
+      msg.reply [
+        "I don't recognize the `#{pname}` parser."
+        "Please file an issue at: https://github.com/smashwilson/hubot-quotefile/issues/new"
+      ].join("\n")
+      return
+
+    try
+      quote = parse msg.match[2]
+    catch e
+      msg.reply "http://www.sadtrombone.com/"
+      msg.reply e.message
+      msg.reply "```\n#{e.stack}\n```"
+
+    fs.appendFile quotefilePath, processed, ->
       msg.reply "Quote added."
       reloadThen (err) ->
         if err?


Added manual XPath evaluation fallback for bogus elements.

--- src/range.coffee
+++ src/range.coffee
@@ -39,7 +39,14 @@
 # Returns the Node if found otherwise null.
 Range.nodeFromXPath = (xpath, root=document) ->
   evaluateXPath = (xp, nsResolver=null) ->
-    document.evaluate('.' + xp, root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
+    try
+      document.evaluate('.' + xp, root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
+    catch exception
+      if exception?.code is 52
+        console.log "XPath evaluation failed with code 52. Trying manual..."
+        $.dummyXPathEvaluate xp, root
+      else
+        throw exception

   if not $.isXMLDoc document.documentElement
     evaluateXPath xpath


Small route changes

Small route changes

--- src/routes.coffee
+++ src/routes.coffee
@@ -3,25 +3,29 @@
   app.get '/', (req, res) ->
     res.end ""

-  # Login user or org
+  # Login user
   app.post '/login', (req, res) ->
     res.end ""

-  # Register new user or org
+  # Register new user
   app.post '/register', (req, res) ->
     res.end ""

-  # Get calendar
-  app.get '/calendar/:id', (req, res) ->
+  # Get user data
+  app.get '/user', (req, res) ->
     res.end ""

-  app.post '/calendar/add', (req,res) ->
+  # Update user settings
+  app.post '/user/settings', (req, res) ->
     res.end ""

-  app.post '/calendar/remove/:id', (req, res) ->
+  # Subscribe to user calendar
+  app.post '/user/subscribe', (req, res) ->
     res.end ""

-
+  # Unsubscribe from calendar
+  app.post '/user/unsubscribe', (req, res) ->
+    res.end ""

   # Get ICal


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -464,7 +464,7 @@
 @returns {Number} This number as an amount of rotations
 ###
 unless 5.rotations
-  Object.defineProperty Number:, 'rotations',
+  Object.defineProperty Number.prototype, 'rotations',
     get: ->
       this * Math.TAU


Restore Quick exit if candidate have no slash Accidentally removed with introduction of extension bonus.

--- src/scorer.coffee
+++ src/scorer.coffee
@@ -467,12 +467,15 @@
   end = subject.length - 1
   end-- while subject[end] is PathSeparator

-  # Get position of basePath of subject.
-  basePos = subject.lastIndexOf(PathSeparator, end)
-
   # Get a bonus for matching extension
   extAdjust = 1.0 + getExtensionScore(subject_lw, prepQuery.ext)
   fullPathScore *= extAdjust
+
+  # Get position of basePath of subject.
+  basePos = subject.lastIndexOf(PathSeparator, end)
+
+  # no basePath, nothing else to compute.
+  return fullPathScore if (basePos is -1)

   # Get the number of folder in query
   depth = prepQuery.depth


Add getHTML and setHTML

Add getHTML and setHTML

--- src/scribe.coffee
+++ src/scribe.coffee
@@ -80,6 +80,9 @@
     ops = @editor.getDelta().getOpsAt(index, length)
     return new Tandem.Delta(0, ops)

+  getHTML: ->
+    return @editor.root.innerHTML
+
   getLength: ->
     return @editor.getDelta().endLength

@@ -99,6 +102,9 @@
     delta.startLength = this.getLength()
     @editor.applyDelta(delta)

+  setHTML: (html) ->
+    @editor.root.innerHTML = html
+
   updateContents: (delta) ->
     @editor.applyDelta(delta)


Remove debugging print

Remove debugging print

--- src/search.coffee
+++ src/search.coffee
@@ -19,7 +19,6 @@
             replace: (url, query) =>
                 url += query
                 cities = p13n.getCities()
-                console.log cities, !!cities
                 if cities && cities.length
                     for city in cities
                         url += "&municipality=#{city}"


setup: Return graph in additon to bindings

In case someone would like to access it, without having to load
it themselves manually

--- src/setup.coffee
+++ src/setup.coffee
@@ -60,7 +60,7 @@
       return callback err if err

       addBindings broker, bindings, (err) ->
-        return callback err, bindings
+        return callback err, bindings, graph

 exports.parse = parse = (args) ->
   graph = null


cleanup

cleanup

--- src/coffee/data_model.coffee
+++ src/coffee/data_model.coffee
@@ -3,7 +3,6 @@
   type : 'DataTable'
   initialize : (attrs, options)->
     super(attrs, options)
-    #console.log('init in DataTable')
     @register_property('offset', ['data_slice'],
       (() -> return @get('data_slice')[0]), false
     )
@@ -18,7 +17,6 @@
     total_rows : 0
   default_view : DataTableView
   load : (offset) ->
-    console.log('DTV load: url='+@get('url'))
     $.get("/data" + @get('url'),
       data_slice : JSON.stringify(@get('data_slice')) ,
       (data) =>
@@ -83,8 +81,8 @@
   default_view : InteractiveContextView
   defaults :
     children : []
-    width : $(window).width();
-    height : $(window).height();
+    width : $(window).width()
+    height : $(window).height()
     render_loop : true
 class InteractiveContexts extends Backbone.Collection
   model : InteractiveContext


Use CoffeeScript operators

Use CoffeeScript operators

--- src/coffee/mandelbrot.coffee
+++ src/coffee/mandelbrot.coffee
@@ -66,7 +66,7 @@
     for i in [0...@numPixels]
       @renderPixel i

-      if i % 100 == 0
+      if i % 100 is 0
         currentProgress = Math.round 100 * i / @numPixels
         if currentProgress > lastProgress
           @emit 'progress', currentProgress


fixed the timestamps

fixed the timestamps

--- src/status.coffee
+++ src/status.coffee
@@ -43,7 +43,8 @@
            title="#{ iso_datestring @date }">
             #{ @date.toLocaleDateString() }
         """
-        # el.timeago()
+
+        if $.fn.timeago? then el.timeago() else el

     renderScreenName: -> """
         <a class="username"
@@ -51,7 +52,7 @@
             #{@username}
         """

-    renderBody: -> """
#{@text}
"""
+    renderBody: -> """
#{@text.replace '\n', '
'}
"""

     raw: null

@@ -62,7 +63,7 @@
 iso_datestring = (d) ->
     pad = (n) -> if n < 10 then '0' + n else n
     year    = pad d.getUTCFullYear()
-    month   = pad d.getUTCMonth()
+    month   = pad d.getUTCMonth() + 1
     day     = pad d.getUTCDate()
     hours   = pad d.getUTCHours()
     minutes = pad d.getUTCMinutes()


Re-add needed sync() calls

Re-add needed sync() calls

They're needed to update the preview

--- src/components/question.cjsx
+++ src/components/question.cjsx
@@ -23,24 +23,31 @@
   changeAnswer: (answerId) ->
     curAnswer = QuestionStore.getCorrectAnswer(@props.id)
     QuestionActions.setCorrectAnswer(@props.id, answerId, curAnswer?.id)
+    @props.sync()

   updateStimulus: (event) ->
     QuestionActions.updateStimulus(@props.id, event.target?.value)
+    @props.sync()

   updateStem: (event) ->
     QuestionActions.updateStem(@props.id, event.target?.value)
+    @props.sync()

   updateSolution: (event) ->
     QuestionActions.updateSolution(@props.id, event.target?.value)
+    @props.sync()

   addAnswer: ->
     QuestionActions.addNewAnswer(@props.id)
+    @props.sync()

   removeAnswer:(answerId) ->
     QuestionActions.removeAnswer(@props.id, answerId)
+    @props.sync()

   moveAnswer: (answerId, direction) ->
     QuestionActions.moveAnswer(@props.id, answerId, direction)
+    @props.sync()

   multipleChoiceClicked: (event) -> QuestionActions.toggleMultipleChoiceFormat(@props.id)
   freeResponseClicked: (event) -> QuestionActions.toggleFreeResponseFormat(@props.id)


Added comments, always comes handy :)

Added comments, always comes handy :)

--- src/utils.coffee
+++ src/utils.coffee
@@ -64,7 +64,6 @@
   return unless @DEBUG
   now = (Date.now() - appStartTime) / 1000.0
   process.stdout.write inColor @config.debug, 'DEBUG:', args...
-  # process.log inColor color, sprintf "[%06.3f] %s", now, text

 exports.pick = (from, what) ->
   obj = {}
@@ -92,9 +91,18 @@

   object

+###*
+# Finds value in an Array of Arrays
+# @param input: Array of Arrays like [[1,2,3],[4,5,6]]
+# @param index: Index (Integer) in the subarray to check value against
+# @param value: Value to look for
+# @param all: Boolean (default false), return first match or all matches
+# @returns {index|[indexes]} depending on the 'all' param (-1 means no match)
+###
 exports.findInSubArray = (input, index, value, all=false) ->
   {length} = input

+  # It is easier to think looping backwards (from last index down to 0)
   inner = (_index) ->
     loop
       _index--


Added ctx as the last parameter for helpers.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -279,8 +279,9 @@

         for name, helper of helpers
           do (name, helper) ->
-            ctx[name] = ->
-              helper.apply ctx, arguments
+            ctx[name] = (args...) ->
+              args.push ctx
+              helper.apply ctx, args

         # Names of non-input context vars.
         names = []


Proper reference for `databag` (#77)

Proper reference for `databag` (#77)

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -495,7 +495,7 @@
             opts = {}

           if app.settings['databag']
-            opts.params = data
+            opts.params = ctx.data

           if not opts.postrender?
             postrender = report


Initial code for `@param` (in root scope).

#40

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -311,6 +311,25 @@
         context.session_store.get data.id, cb
       else
         cb err
+
+  context.param = (obj) ->
+    build = (callback) ->
+      (req,res,next,p) ->
+        ctx =
+          request: req
+          req: req
+          response: res
+          res: res
+          next: next
+          param: p
+        apply_helpers ctx
+        callback.apply ctx
+
+    if typeof obj is 'function'
+      @app.param build obj
+    else
+      for k, v of obj
+        @app.param k, build v

   # Register a route with express.
   route = (r) ->


Fix email address reference in Slack adapter

The `user.profile.email` attribute seems to have won out over
`user.email_address`. This PR allows an undefined value in the former to
fall back to the latter.

Reference: https://github.com/slackapi/hubot-slack/issues/367
Fixes #102

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -194,7 +194,9 @@
         robot.emit 'error', err, msg
         return

-      email  = msg.message.user.pagerdutyEmail || msg.message.user.email_address
+      ## Determine the email based on the adapter type (v4.0.0+ of the Slack adapter stores it in `profile.email`)
+      email  = msg.message.user.pagerdutyEmail || msg.message.user.email_address || msg.message.user.profile.email
+
       filteredIncidents = if force
                             incidents # don't filter at all
                           else


cleanup error handling on page action

cleanup error handling on page action

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -103,7 +103,11 @@
       else
         msg.send "No open incidents"

+  robot.respond /(pager|major)( me)? (?:trigger|page) ([\w\-]+)$/i, (msg) ->
+    msg.reply "Please include a user or schedule to page, like 'hubot pager ops everything is on fire'."
+
   robot.respond /(pager|major)( me)? (?:trigger|page) ([\w\-]+) (.+)$/i, (msg) ->
+    msg.finish()
     fromUserName   = msg.message.user.name
     userOrSchedule = msg.match[3]
     reason         = msg.match[4]
@@ -121,7 +125,7 @@
       # Figure out what we're trying to page
       pagerDutyUserFromNameOrSchedule msg, msg.match[3], (userToPage) ->
         if !userToPage
-          msg.reply "Couldn't find a user or unique schedule matching #{userToPage} :/"
+          msg.reply "Couldn't find a user or unique schedule matching #{userOrSchedule} :/"
         else
           pagerDutyIntegrationAPI msg, "trigger", description, (json) ->
             query = {


Remove debug output.

Remove debug output.

--- src/spec/matcher.spec.coffee
+++ src/spec/matcher.spec.coffee
@@ -26,7 +26,6 @@
       apiClient =
         getByExternalIds: ->
           new Promise (resolve, reject) ->
-            console.log "HAJO"
             res =
               body:
                 results: [


fix for rotations

fix for rotations


resolve with nothing if artwork partner is institution

--- apps/artwork/artwork_rails.coffee
+++ apps/artwork/artwork_rails.coffee
@@ -42,6 +42,7 @@

         @fetchArtwork()
         .then ({ @artwork }) =>
+          return resolve() if @artwork.partner.type is 'Institution'
           @excludedIds.push @artwork._id
           @fetchAuctionArtworks()
         .then =>
@@ -154,6 +155,7 @@
               id
               default_profile_id
               is_linkable
+              type
               name
             }


inject iconic after block load

inject iconic after block load

--- apps/root/client/feed_view.coffee
+++ apps/root/client/feed_view.coffee
@@ -8,7 +8,7 @@
 BlockCollectionView = require '../../../components/block_collection/client/block_collection_view.coffee'
 BlockView = require '../../../components/block_collection/client/block_view.coffee'
 User = require '../../../models/user.coffee'
-
+IconicJS = require '../../../components/iconic/client/iconic.min.js'

 feedTemplate = -> require('../../../components/feed/templates/feed.jade') arguments...

@@ -51,6 +51,9 @@

     @$('.grid__block').each @initBlockView

+    _.defer ->
+      IconicJS().inject 'img.iconic'
+
   updateTitle: (diff) =>
     window.document.title = "Arena (#{diff})"


Fix isNumber()

Fix isNumber()

--- src/Strings.coffee
+++ src/Strings.coffee
@@ -27,7 +27,7 @@
   firstToUpperCase: (str) ->
     str.replace /(^\w)/, (firstChar) -> firstChar.toUpperCase()

-  isNumber: (str) -> !/[^\d\.E-]/g.test(str)
+  isNumber: (str) -> Types.isNumber(Numbers.parse(str))

 # TODO(aramk) Improve naive pluralize methods.


engine#add

engine#add

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -33,47 +33,47 @@
     I.currentState.trigger "overlay", canvas

-    # Just pass through to the current state
-    add: (className, entityData) ->
-      # Allow optional add "Class", data form
-      if entityData?
-        entityData.class = className
-      else
-        entityData = className
+  # Just pass through to the current state
+  add: (className, entityData) ->
+    # Allow optional add "Class", data form
+    if entityData?
+      entityData.class = className
+    else
+      entityData = className

-      self.trigger "beforeAdd", entityData
-      object = I.currentState.add(entityData)
-      self.trigger "afterAdd", object
+    self.trigger "beforeAdd", entityData
+    object = I.currentState.add(entityData)
+    self.trigger "afterAdd", object

-      return object
+    return object

-    camera: (n=0) ->
-      self.cameras()[n]
+  camera: (n=0) ->
+    self.cameras()[n]

-    cameras: (newCameras) ->
-      if newCameras?
-        I.currentState.cameras(newCameras)
+  cameras: (newCameras) ->
+    if newCameras?
+      I.currentState.cameras(newCameras)

-        return self
-      else
-        I.currentState.cameras()
+      return self
+    else
+      I.currentState.cameras()

-    fadeIn: (options={}) ->
-      self.cameras().invoke('fadeIn', options)
-    fadeOut: (options={}) ->
-      self.cameras().invoke('fadeOut', options)
-    flash: (options={}) ->
-      self.camera(options.camera).flash(options)
-    objects: ->
-      I.currentState.objects()
-    setState: (newState) ->
-      requestedState = newState
-    shake: (options={}) ->
-      self.camera(options.camera).shake(options)
-    saveState: ->
-      I.currentState.saveState()
-    loadState: (newState) ->
-      I.currentState.loadState(newState)
-    reload: ->
-      I.currentState.reload()
+  fadeIn: (options={}) ->
+    self.cameras().invoke('fadeIn', options)
+  fadeOut: (options={}) ->
+    self.cameras().invoke('fadeOut', options)
+  flash: (options={}) ->
+    self.camera(options.camera).flash(options)
+  objects: ->
+    I.currentState.objects()
+  setState: (newState) ->
+    requestedState = newState
+  shake: (options={}) ->
+    self.camera(options.camera).shake(options)
+  saveState: ->
+    I.currentState.saveState()
+  loadState: (newState) ->
+    I.currentState.loadState(newState)
+  reload: ->
+    I.currentState.reload()


removing todos because they are done

removing todos because they are done

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -1,4 +1,3 @@
-# TODO: This is a work in progress
 Engine.GameState = (I, self) ->
   Object.reverseMerge I,
     currentState: GameState()
@@ -25,14 +24,6 @@
       I.currentState.trigger "enter", previousState

       requestedState = null
-
-  # TODO: Drawing, cameras??
-  # Cameras should be components of GameStates
-  # Draw will pass through and trigger all the
-  # events in the game state
-  # Engine will still respond to beforeDraw
-  # and overlay to add overlays that apply to any
-  # game state

   self.bind "draw", (canvas) ->
     I.currentState.trigger "beforeDraw", canvas


Camera refactoring

Camera refactoring

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -41,8 +41,13 @@
       return object
     camera: (n=0) ->
       self.cameras()[n]
-    cameras: ->
-      I.currentState.cameras()
+    cameras: (newCameras) ->
+      if newCameras?
+        I.currentState.cameras(newCameras)
+
+        return self
+      else
+        I.currentState.cameras()
     flash: (options={}) ->
       self.camera(options.camera).flash(options)
     objects: ->


make command system and help command work again

--- src/factories/command.coffee
+++ src/factories/command.coffee
@@ -1,67 +1,67 @@
 module.exports.fragments_commandPrefix = ->
   'command_'

-module.exports.fragments_getCommandNamesFromLifetime = (
+module.exports.fragments_getCommandNames = (
   fragments_helfer
   fragments_commandPrefix
+  fragments_source
 ) ->
-  (lifetime) ->
-    Object.keys(lifetime.factories)
+  ->
+    keys = fragments_source.keys?() or []
+    keys
       .filter (x) -> x.indexOf(fragments_commandPrefix) is 0
       .map (x) -> x.slice(fragments_commandPrefix.length)
       .map fragments_helfer.camelSnakeToHyphenColon

-module.exports.fragments_commandNameToFactoryPropertyName = (
+module.exports.fragments_commandNameToKey = (
   fragments_helfer
   fragments_commandPrefix
 ) ->
   (name) ->
     fragments_commandPrefix + fragments_helfer.hyphenColonToCamelSnake(name)

-module.exports.fragments_getCommandInLifetime = (
+module.exports.fragments_getCommand = (
   fragments_Promise
   fragments_hinoki
-  fragments_commandNameToFactoryPropertyName
+  fragments_commandNameToKey
   fragments_source
+  fragments_applicationLifetime
 ) ->
-  (lifetime, commandName) ->
-    unless commandName?
-      commandName = 'help'
-
-    commandName = commandName.toLowerCase()
-    commandKey = fragments_commandNameToFactoryPropertyName(commandName)
+  (commandName) ->
+    commandKey = fragments_commandNameToKey(commandName.toLowerCase())
     commandFactory = fragments_source(commandKey)

     unless commandFactory?
       return

     (args...) ->
-      fragments_hinoki(fragments_source, lifetime, commandKey).then (commandInstance) ->
-        commandInstance args...
+      fragments_hinoki(fragments_source, fragments_applicationLifetime, commandKey)
+        .then (commandInstance) ->
+          commandInstance args...

 module.exports.fragments_runCommand = (
-  fragments_getCommandInLifetime
-  fragments_applicationLifetime
+  fragments_getCommand
+  fragments_source
 ) ->
   (commandName, args...) ->
-    command = fragments_getCommandInLifetime(
-      fragments_applicationLifetime
-      commandName
-    )
+    unless commandName?
+      commandName = 'help'
+    command = fragments_getCommand(commandName)
     if command?
       command args...
     else
       throw new Error "no such command: #{commandName}"

-module.exports.fragments_getCommandHelpLinesFromLifetime = (
-  fragments_getCommandNamesFromLifetime
-  fragments_commandNameToFactoryPropertyName
+module.exports.fragments_getCommandHelpLines = (
+  fragments_getCommandNames
+  fragments_commandNameToKey
   fragments_isjs
   fragments_lodash
+  fragments_source
 ) ->
   (lifetime, args...) ->
     _ = fragments_lodash
-    commandNames = fragments_getCommandNamesFromLifetime(lifetime)
+    commandNames = fragments_getCommandNames()
     # help for a specific command or namespace is requested
     if args.length isnt 0
       prefix = args.join(':')
@@ -71,19 +71,19 @@
     commandNames = _.sortBy(commandNames)

     commandNames.map (name) ->
-      key = fragments_commandNameToFactoryPropertyName name
+      key = fragments_commandNameToKey name
       line = name
-      docstring = lifetime.factories[key].$help
+      factory = fragments_source(key)
+      docstring = factory.$help
       if docstring?
         line += ' ' + docstring
       return line

 module.exports.command_help = (
-  fragments_getCommandHelpLinesFromLifetime
-  fragments_applicationLifetime
+  fragments_getCommandHelpLines
 ) ->
   (args...) ->
-    fragments_getCommandHelpLinesFromLifetime(fragments_applicationLifetime, args...).forEach (line) ->
+    fragments_getCommandHelpLines(args...).forEach (line) ->
       console.log line
 module.exports.command_help.$help = '[namespace-prefixes...] display available commands'

@@ -130,13 +130,3 @@
     fragments_APPLICATION factory

 module.exports.command_serve.$help = "[server-callback-name (default: 'server')] - start a server with server-callback-name as callback"
-
-module.exports.command_info = (
-  fragments_APPLICATION
-) ->
-  fragments_APPLICATION (
-    fragments_console
-  ) ->
-    (args...) ->
-      fragments_console.log 'TODO'
-      fragments_console.log 'OK'


fix(timestamps): Don't crash on null times #1929

--- src/flux/models/utils.coffee
+++ src/flux/models/utils.coffee
@@ -56,6 +56,7 @@
   timeZone: tz

   shortTimeString: (time) ->
+    return "" unless time
     diff = moment().diff(time, 'days', true)
     if diff <= 1
       format = "h:mm a"
@@ -66,6 +67,7 @@
     moment(time).format(format)

   fullTimeString: (time) ->
+    return "" unless time
     moment(time).tz(Utils.timeZone).format("dddd, MMMM Do YYYY, h:mm:ss a z")

   fastOmit: (props, without) ->


Specify the HTTP method

Specify the HTTP method

--- src/kissmetrics-batch.coffee
+++ src/kissmetrics-batch.coffee
@@ -13,6 +13,7 @@
     signature = @_generateSignature baseUrl, apiSecret

     request = http.request
+      method: @HTTP_METHOD
       host: @HOST
       path: "#{baseUrl}?_signature=#{signature}"
       headers:


Fixed crash in debug mode caused by incorrect debug directory path.

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -60,7 +60,7 @@
 writeResponse = (currentRequest, res) ->
   if config.get('DEBUG', currentRequest.session.deviceId)
     dump = "# RESPONSE #{new Date(Date.now())}\n" + JSON.stringify(res.headers) + "\n#{res.data}\n\n"
-    require('fs').appendFile("../debug/#{currentRequest.session.deviceId}.dump", dump, (err) ->
+    require('fs').appendFile("./debug/#{currentRequest.session.deviceId}.dump", dump, (err) ->
       throw err if err
     )

@@ -464,7 +464,7 @@

       if config.get('DEBUG', currentRequest.session.deviceId)
         dump = "# REQUEST #{new Date(Date.now())}\n" + JSON.stringify(httpRequest.headers) + "\n#{httpRequest.getBody()}\n\n"
-        require('fs').appendFile("../debug/#{currentRequest.session.deviceId}.dump", dump, (err) ->
+        require('fs').appendFile("./debug/#{currentRequest.session.deviceId}.dump", dump, (err) ->
           throw err if err
         )


Fix voting in beatmap modding

Fix voting in beatmap modding

--- resources/assets/coffee/react/beatmap-discussions/discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussion.coffee
@@ -95,7 +95,7 @@
   doVote: (score) ->
     LoadingOverlay.show()

-    $.ajax laroute.route('beatmapset-discussions.vote', discussions: @props.discussion.id),
+    $.ajax laroute.route('beatmap-discussions.vote', beatmap_discussions: @props.discussion.id),
       method: 'PUT',
       data:
         beatmap_discussion_vote:


client/main.coffee

client/main.coffee

--- client/main.coffee
+++ client/main.coffee
@@ -10,9 +10,9 @@
 if Meteor.isCordova
     Meteor.startup ->
         document.addEventListener("backbutton", ->
-            if document.location.pathname is "/ontap" or ""
+            if document.location.pathname is "/"
                 navigator.app.exitApp()
-            else if document.location.pathname is "/"
+            else if document.location.pathname is "/ontap"
                 navigator.app.exitApp()
             false)
     # window.onpopstate = ->


Improved cluster picker.

Improved cluster picker.


Updated to fix orientation of error bounds

--- attitude/display/assets/functions.coffee
+++ attitude/display/assets/functions.coffee
@@ -7,7 +7,7 @@
 createErrorSurface = (d)->
   # Function that turns orientation
   # objects into error surface
-  coords = [d.upper,d.lower]
+  coords = [d.lower,d.upper]
   data =
     type: 'Feature'
     geometry:


Test moving to end of last word in file with 'w'

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -100,6 +100,12 @@
         keydown('w')
         expect(editor.getCursorScreenPosition()).toEqual [3, 2]

+      it "moves the cursor to the end of the word if last word in file", ->
+        editor.setText("abc")
+        editor.setCursorScreenPosition([0, 0])
+        keydown('w')
+        expect(editor.getCursorScreenPosition()).toEqual([0, 3])
+
     describe "as a selection", ->
       describe "within a word", ->
         beforeEach ->


:fire: no more debug logs for async helper

--- spec/spec-helpers.coffee
+++ spec/spec-helpers.coffee
@@ -17,19 +17,15 @@
     nextArgs = null

     runs ->
-      console.debug "SH:callAsync:runs"
       async (args...) ->
-        console.debug "SH:callAsync:callback: ", args
         done = true
         nextArgs = args

     waitsFor ->
-      console.debug "SH:callAsync:waits: ", done
       done
     , null, timeout

     if next?
       runs ->
-        console.debug "SH:callAsync:runs2: ", nextArgs
         next.apply(this, nextArgs)