various XSSEncode fixes

various XSSEncode fixes

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -255,7 +255,7 @@
 class LocationView extends JView

   pistachio:-> "{{ @getFirstLocation #(locationTags)}}"
-  getFirstLocation:(locationTags)-> locationTags[0]
+  getFirstLocation:(locationTags)-> Encoder.XSSEncode locationTags[0]

 class PersonalFormSkillTagView extends AbstractPersonalFormView


test/teamshelpers: fix create stack functionality

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -289,6 +289,8 @@

     newCredentialPage = '.kdview.stacks.stacks-v2'
     saveButton = "#{newCredentialPage} button[type=submit]"
+    regionSelector = '.kdview.formline.region .kdselectbox select'
+    eu_west_1 = "#{regionSelector} option[value=eu-west-1]"

     { accessKeyId, secretAccessKey } = @getAwsKey()

@@ -301,6 +303,13 @@
       .pause 1000
       .setValue "#{newCredentialPage} .secret-key input", secretAccessKey
       .pause 1000
+      .click regionSelector
+      .pause 1000
+      .waitForElementVisible eu_west_1, 20000
+      .click eu_west_1
+      .pause 1000
+      .click "#{newCredentialPage} .title input"
+      .pause 1000
       .click saveButton, -> done()

@@ -319,15 +328,10 @@
       .click useThisAndContinueButton
       .waitForElementVisible editorPaneSelector, 20000
       .click saveButtonSelector
-    browser.element 'css selector', successModal, (result) ->
-      if result.status is 0
-        browser
-          .waitForElementVisible successModal, 40000
-          .click closeButton, ->
-            browser.end()
-            done()
-      else
-        browser.end()
+      .pause 10000
+      .waitForElementVisible successModal, 40000
+      .click closeButton
+      .pause 5000, ->
         done()


hot fix

hot fix

--- coffee/helpers/uploadVideoInVkByLink.coffee
+++ coffee/helpers/uploadVideoInVkByLink.coffee
@@ -27,7 +27,7 @@

 			json = JSON.parse body

-			if json.response.upload_url
+			if json.response && json.response.upload_url
 				request json.response.upload_url, (err, head, body) -> done json
 			else
 				done json


fixing the library

fixing the library

--- javascripts/jquery.gridly.coffee
+++ javascripts/jquery.gridly.coffee
@@ -73,13 +73,13 @@
     $elements = @$sorted()
     @ordinalize($elements)
     setTimeout @layout, 0
-    @settings?.callbacks?.dragging?.started?($elements)
+    @settings?.callbacks?.reordering?($elements)

   stop: (event, ui) =>
     $elements = @$sorted()
     @ordinalize($elements)
     setTimeout @layout, 0
-    @settings?.callbacks?.dragging?.stopped?($elements)
+    @settings?.callbacks?.reordered?($elements)

   $sorted: ($elements) =>
     ($elements || @$('> *')).sort (a,b) ->
@@ -112,16 +112,18 @@
     positions.sort @compare

     $elements = positions.map (position) -> position.$element
-    $reordered = @settings.callbacks?.reorder?($elements)
-    $elements = $reordered if $reordered
+    $elements = @optimize($elements)

     for i in [0...$elements.length]
       @reordinalize($($elements[i]), i)

     @layout()

+  size: ($element) =>
+    (($element.data('width') || $element.width()) + @settings.gutter) / (@settings.base + @settings.gutter)
+
   position: ($element, columns) =>
-    size = (($element.data('width') || $element.width()) + @settings.gutter) / (@settings.base + @settings.gutter)
+    size = @size($element)

     height = Infinity
     column = 0
@@ -156,9 +158,9 @@
     height: Math.max columns...
     positions: positions

-  layout: () =>
-    $elements = @$sorted()
-    $reordered = @settings.callbacks?.reorder?($elements)
+  layout: =>
+    $elements = @$sorted()
+    $reordered = @optimize($elements)
     $elements = $reordered if $reordered

     structure = @structure($elements)
@@ -175,6 +177,28 @@
     @$el.css
       height: structure.height

+  optimize: (originals) =>
+    results = []
+
+    columns = 0
+    while originals.length > 0
+      columns = 0 if columns is @settings.columns
+
+      index = 0
+      for index in [0...originals.length]
+        break if columns + @size($(originals[index])) <= @settings.columns
+
+      if index is originals.length
+        index = 0
+        columns = 0
+
+      columns += @size($(originals[index]))
+
+      # Move from originals into results
+      results.push(originals.splice(index,1)[0])
+
+    return results
+
 $.fn.extend
   gridly: (option = {}) ->
     @each ->


removed un-necessary test

removed un-necessary test

--- test/components/task-plan/footer.spec.coffee
+++ test/components/task-plan/footer.spec.coffee
@@ -26,6 +26,7 @@
 UNPUBLISHED_READING = extendBasePlan({type: 'reading'})
 PUBLISHED_READING = extendBasePlan({type: 'reading', published_at: yesterday})
 VISIBLE_READING = extendBasePlan({type: 'reading', published_at: yesterday, opens_at: yesterday})
+VISIBLE_HW = extendBasePlan({type: 'homework', published_at: yesterday, opens_at: yesterday})

 NEW_HW = extendBasePlan({type: 'homework', id: "_CREATING_1"})
 HW_WITH_EXERCISES = ({
@@ -79,10 +80,3 @@
     expect(node.querySelector('.delete-link')).to.be.null
     expect(node.querySelector('.-save')).to.be.null
     expect(node.querySelector('.-publish')).to.not.be.null
-
-  it 'should only show select problems when homework is new', ->
-    node = helper(NEW_HW)
-
-    expect(node.querySelector('.delete-link')).to.be.null
-    expect(node.querySelector('.-save')).to.be.null
-    expect(node.querySelector('.-publish')).to.be.null


added abs, columns to functional tests

added abs, columns to functional tests

--- test/functional/druidFunctional.mocha.coffee
+++ test/functional/druidFunctional.mocha.coffee
@@ -43,6 +43,9 @@
             { name: 'anonymous', type: 'BOOLEAN' }
             { name: 'added', type: 'NUMBER' }
             { name: 'count', type: 'NUMBER' }
+            { name: 'commentLength', type: 'NUMBER' }
+            { name: 'metroCode', type: 'STRING' }
+            { name: 'cityName', type: 'STRING' }
             { name: 'user_unique', special: 'unique' }
           ]
           filter: $('time').in(TimeRange.fromJS({
@@ -590,6 +593,33 @@
               {
                 "Count": 22140
                 "Channel": "French"
+              }
+            ]
+          }
+        ])
+        testComplete()
+      ).done()
+
+    it "works with absolute value split", (testComplete) ->
+      ex = ply()
+      .apply('AbsSplit',
+        $('wiki').split($('commentLength').absolute(), 'AbsCommentLength')
+        .sort('$AbsCommentLength', 'descending')
+        .limit(3)
+      )
+
+      basicExecutor(ex).then((result) ->
+        expect(result.toJS()).to.deep.equal([
+          {
+            "AbsSplit": [
+              {
+                "AbsCommentLength": 1
+              }
+              {
+                "AbsCommentLength": 10
+              }
+              {
+                "AbsCommentLength": 100
               }
             ]
           }


config: change newkontrol to test it out

--- config/main.staging.coffee
+++ config/main.staging.coffee
@@ -231,8 +231,8 @@
   haproxy:
     webPort     : 3020
   newkontrol      :
-    host          : "127.0.0.1"
-    port          : 80
+    host          : "stage-kontrol.sj.koding.com"
+    port          : 4000
     certFile      : "/opt/koding/go/src/koding/kontrol/kontrolproxy/files/10.0.5.231_cert.pem"
     keyFile       : "/opt/koding/go/src/koding/kontrol/kontrolproxy/files/10.0.5.231_key.pem"
   proxyKite       :


Set flow simultaneous uploads to 2

Set flow simultaneous uploads to 2

Fixes #4441

--- web/service/upload.coffee
+++ web/service/upload.coffee
@@ -9,7 +9,7 @@
       target: router.controllers.uploadChunk.route()
       method: 'octet'
       chunkSize: 4194304
-      simultaneousUploads: 1
+      simultaneousUploads: 2
       testChunks: false
       chunkRetryInterval: 5000
       permanentErrors: [400, 403, 404, 409, 415, 500, 501]


instead of checking for png as empty string, just using false eval (python changes cause a different value to be sent)

--- static/coffee/common/plot_context.coffee
+++ static/coffee/common/plot_context.coffee
@@ -100,7 +100,7 @@
   build_children: () ->
     view_classes = []
     for view_model in @mget_obj('children')
-      if view_model.get('png') == ""
+      if not view_model.get('png')
         console.log("no png for #{view_model.id} making one")
         pv = new PlotView({model:view_model})
         pv.save_png()


returning correct result. Called changed

--- scripts/neige.coffee
+++ scripts/neige.coffee
@@ -15,7 +15,7 @@

    annoyIntervalId = null

-   robot.respond /active les alertes neige/, (msg) ->
+   robot.respond /neige/, (msg) ->
      if annoyIntervalId
        msg.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
        return
@@ -26,11 +26,10 @@
         .header('Accept', 'application/json')
         .get() (err, res, body) ->
           data = JSON.parse(body)
-          console.log(data.weather.snow_report)
-          msg.send "#{data.weather.snow_report.conditions}"
+          msg.send "#{data.weather.snow_report[0].conditions}"
      , 1000

-   robot.respond /désactive les alertes neige/, (msg) ->
+   robot.respond /stop neige/, (msg) ->
      if annoyIntervalId
        msg.send "OK, ça va..."
        clearInterval(annoyIntervalId)


update ze french phrase :fr:

update ze french phrase :fr:

"au revoir" -- to our next meeting

"À voir" -- to (to see)?? or "avoir" -- to have?

what dis

--- scripts/night.coffee
+++ scripts/night.coffee
@@ -22,7 +22,7 @@
   "May you never urinate the sweet sweet sounds of 70's disco funk",
   "So long, and thanks for all the fish.",
   "Finally",
-  "À voir!",
+  "Au revoir!",
   "Don't let the back door hit ya where the good Lord split ya",
   "May your feet never fall off and grow back as cactuses",
   "TTYL",
@@ -38,4 +38,4 @@

 module.exports = (robot) ->
   robot.hear /(good night|bye|nighty night)/i, (msg) ->
-    msg.send msg.random ways_to_say_good_night+    msg.send msg.random ways_to_say_good_night


Remove unused dependencies

Remove unused dependencies

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -1,7 +1,4 @@
 define [
-  'jquery'
-  'underscore'
-  'backbone'
   'cs!helpers/backbone/views/base'
   'hbs!./body-template'
   'less!./body'


If meta is null, don't try to parse it

If meta is null, don't try to parse it

--- src/streammachine/outputs/shoutcast.coffee
+++ src/streammachine/outputs/shoutcast.coffee
@@ -111,7 +111,8 @@
                     @client.offset = @source.offset()

                     @source.onFirstMeta (err,meta) =>
-                        @ice.queue meta
+                        if meta
+                            @ice.queue meta

                     @metaFunc = (data) =>
                         unless @_lastMeta && _u(data).isEqual(@_lastMeta)


fix channel to response

fix channel to response

--- scripts/relay.coffee
+++ scripts/relay.coffee
@@ -22,9 +22,10 @@
 blog    = new RelayBlog(member, util)
 relayBlogBrainKey = "relay-blog-brain-key"

-post = (name, message, icon) ->
+post = (name, message, icon, channel = channelToPost) ->
+  channel = if( channel == "Shell" || channel == undefined) then channelToPost else channel
   client.api.chat.postMessage
-    channel: channelToPost
+    channel: channel
     text: message
     username: name
     icon_url: icon
@@ -33,7 +34,9 @@
     console.log err if err
     console.log response

-
+findChannelByMessage = (res) ->
+  slackMsg = res.message.rawMessage ? {}
+  slackMsg.channel

 module.exports = (robot) ->

@@ -41,19 +44,26 @@
     result = blog.apply()
     robot.brain.set(relayBlogBrainKey, result)
     message = blog.toMessageOkarin result.member
-    post "鳳凰院凶真", message, "https://pbs.twimg.com/profile_images/378800000078323076/5f6afc04e701807ae99024e84c83057d.jpeg"
+    post "鳳凰院凶真",
+      message,
+      "https://pbs.twimg.com/profile_images/378800000078323076/5f6afc04e701807ae99024e84c83057d.jpeg",
+      findChannelByMessage(res)

   robot.respond /relay last$/i, (res) ->
     result  = robot.brain.get(relayBlogBrainKey) ? {}
     message = blog.toMessageMayusy (result.member ? [])
-    post "まゆしぃ", message, "http://pic.prepics-cdn.com/1046oryou/5661857.jpeg"
+    post "まゆしぃ",
+      message,
+      "http://pic.prepics-cdn.com/1046oryou/5661857.jpeg",
+      findChannelByMessage(res)

   robot.respond /relay reset$/i, (res) ->
     if(res.message.user.name in admin)
       robot.brain.remove(relayBlogBrainKey)
       post "鳳凰院凶真",
         "そうか・・・それがシュタインズゲートか。\nしかたあるまい、今を持ってオペレーション・スクルドは中止とする。\nエル・プサイ・コングルゥ",
-        "https://pbs.twimg.com/profile_images/378800000078323076/5f6afc04e701807ae99024e84c83057d.jpeg"
+        "https://pbs.twimg.com/profile_images/378800000078323076/5f6afc04e701807ae99024e84c83057d.jpeg",
+        findChannelByMessage(res)
     else
       console.log (res.message.user.name + " called admin command")
       res.send "選ばれし者以外はこのコマンドは実行出来ぬ"
@@ -71,6 +81,7 @@
       post "タイムリープマシン",
         message,
         "http://livedoor.blogimg.jp/onecall_dazeee/imgs/1/8/18f2fe6b.png"
+        findChannelByMessage(res)
     else
       console.log (res.message.user.name + " called admin command")
       res.send "選ばれし者以外はこのコマンドは実行出来ぬ"


Fixed bug causing device preset to be processed twice in some cases.

--- db.coffee
+++ db.coffee
@@ -59,23 +59,25 @@
       callback(presets, objects)
       return

-    presetsCollection.find().toArray((err, p) ->
-      throw err if err
-      presets = p
+    if not presets
+      presetsCollection.find().toArray((err, p) ->
+        throw err if err
+        presets = p

-      redisClient.setex('presets', config.PRESETS_CACHE_DURATION, JSON.stringify(presets))
-      callback(presets, objects) if objects
-    )
+        redisClient.setex('presets', config.PRESETS_CACHE_DURATION, JSON.stringify(presets))
+        callback(presets, objects) if objects
+      )

-    objectsCollection.find().toArray((err, o) ->
-      throw err if err
-      objects = {}
-      for i in o
-        objects[i._id] = i
+    if not objects
+      objectsCollection.find().toArray((err, o) ->
+        throw err if err
+        objects = {}
+        for i in o
+          objects[i._id] = i

-      redisClient.setex('objects', config.PRESETS_CACHE_DURATION, JSON.stringify(objects))
-      callback(presets, objects) if presets
-    )
+        redisClient.setex('objects', config.PRESETS_CACHE_DURATION, JSON.stringify(objects))
+        callback(presets, objects) if presets
+      )
   )


优化文件路径

优化文件路径

--- lib/project/create-project.coffee
+++ lib/project/create-project.coffee
@@ -1,4 +1,5 @@
 {$, Emitter, Directory, File, GitRepository, BufferedProcess} = require 'atom'
+pathM = require 'path'
 desc = require '../utils/text-description'
 ChameleonBox = require '../utils/chameleon-box-view'
 CreateProjectView = require './create-project-view'
@@ -52,7 +53,10 @@

   newEmptyProject: (options) ->
     info = options.projectInfo
-    appConfig = new File(info.appPath+'/'+'appConfig.json')
+    appConfigFileName = 'appConfig.json'
+    appConfigFilePath = pathM.join info.appPath,appConfigFileName
+    console.log appConfigFilePath
+    appConfig = new File appConfigFilePath
     appConfig.create()
       .then (isSuccess) =>
         console.log isSuccess
@@ -61,6 +65,7 @@
           appConfig.write(JSON.stringify(info))
           alert '项目创建成功！'
           @closeView()
+          atom.project.setPaths([info.appPath])
         else
           alert '项目创建失败...'


applyMarkdown: remove moronic code

applyMarkdown: remove moronic code

--- core/utils.coffee
+++ core/utils.coffee
@@ -117,7 +117,7 @@
     # - links are broken due to textexpansions (images too i guess)
     return null unless text

-    marked.setOptions
+    marked text,
       gfm       : true
       pedantic  : false
       sanitize  : true
@@ -125,24 +125,6 @@
         if hljs.LANGUAGES[lang]?
         then hljs.highlight(lang,text).value
         else text
-
-    text = Encoder.htmlDecode marked text
-
-    sanitizeText = $(text)
-
-    # Proxify images
-    sanitizeText.find("img").each (i,element) =>
-      src = element.getAttribute 'src'
-      element.setAttribute "src", src?.replace /.*/, @proxifyUrl
-
-    # Give all outbound links a target blank
-    sanitizeText.find("a").each (i, element) =>
-      href = element.getAttribute "href"
-      element.setAttribute "href", href.replace /^\s*javascript/i, 'JS_REMOVED'
-      unless /^(#!)/.test $(element).attr("href")
-        $(element).attr("target", "_blank")
-
-    text = $("<div />").append(sanitizeText.clone()).remove().html() # workaround for .html()

   applyLineBreaks: (text)->
     return null unless text


spacing

spacing

--- test/external/druidExternal.mocha.coffee
+++ test/external/druidExternal.mocha.coffee
@@ -940,7 +940,6 @@
           testComplete()
         ).done()

-
     describe "should return null correctly on a timeseries query", ->
       ex = $('wiki').split("$time.timeBucket(P1D, 'Etc/UTC')", 'Time')
         .apply('Count', '$wiki.count()')
@@ -951,7 +950,6 @@
           expect(result.toJS()).to.deep.equal([])
           testComplete()
         ).done()
-

     describe "should return null correctly on a topN query", ->
       ex = $('wiki').split("$page", 'Page')
@@ -1011,5 +1009,3 @@
           expect(err.message).to.equal('unexpected result from Druid (timeseries)')
           testComplete()
         ).done()
-
-


Added support for array as $.ajax success setting

--- test/tb/lib/ti/ajax/settings_test.coffee
+++ test/tb/lib/ti/ajax/settings_test.coffee
@@ -551,6 +551,25 @@
       $.ajax '/error', settings

   describe 'success', ->
+
+    it 'should allow an array of functions and call each in turn', (done) ->
+
+      handled = 0
+
+      settings =
+        success: [
+          (data, textStatus, xhr) ->
+            handled++
+        ,
+          (data, textStatus, xhr) ->
+            handled++
+        ,
+          (data, textStatus, xhr) ->
+            done() if handled is 2
+        ]
+
+      $.ajax '/test', settings
+
   describe 'timeout', ->
   describe 'type', ->
   describe 'url', ->


fix(macTime): Select meridian after updating view

--- src/directives/time.coffee
+++ src/directives/time.coffee
@@ -199,8 +199,8 @@
                 when meridianSelected and key is keys.P
                   setMeridian("PM")

+              updateInput()
               selectMeridian()
-              updateInput()

       element.on 'keyup', (event) ->
         key = event.which


use rootPath of active server as projectRoot. refs #61

--- lib/atom-ternjs-reference.coffee
+++ lib/atom-ternjs-reference.coffee
@@ -41,7 +41,7 @@

   findReference: ->
     return unless @manager.client
-    dir = atom.project.getDirectories()[0]
+    dir = @manager.server.rootPath
     return unless dir
     editor = atom.workspace.getActiveTextEditor()
     return unless editor
@@ -62,7 +62,7 @@

   gatherMeta: (data) ->
     for item, i in data.refs
-      projectRoot = atom.project.getDirectories()[0]
+      projectRoot = @manager.server.rootPath
       file = path.resolve(__dirname, projectRoot.path + '/' + item.file)
       content = fs.readFileSync(file, 'utf8')
       buffer = new TextBuffer({ text: content })


hub: hopefully improve how project server restart works (unclear)

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -3442,9 +3442,24 @@

     restart: (cb) =>
         @dbg("restart")
+        @free_resources()
+        @project = bup_server.get_project(@project_id)
         @project.restart(cb:cb)
+
+    free_resources: () =>
+        winston.debug("free_resources: #{@project_id}")
         delete @_status
+        try
+            @_socket?.end()
+        catch e
+            winston.debug("free_resources: exception closing main _socket: #{e}")
         delete @_socket
+        for k, s in @_sockets
+            try
+                s.end()
+            catch e
+                winston.debug("free_resources: exception closing a socket: #{e}")
+        @_sockets = {}

     # Send a JSON message to a session.
     # NOTE -- This makes no sense for console sessions, since they use a binary protocol,


Fix app.coffee translation of app.js

Fix app.coffee translation of app.js

--- src/frontend/js/app.coffee
+++ src/frontend/js/app.coffee
@@ -9,17 +9,16 @@
     for id, message of parsedResponse
       # For each message, we construct a new element
       # containing that message ...
-      messageBox = $("
-

-

-            ID
-
#{id}

-

-

-            Message
-

-

-

+      messageBox = $("

+

+                          ID
+
#{id}

+

+

+                          Message
+

+

+

       ")
       messageBox.find(".message p").text message


Trim errDiv output

Trim errDiv output

--- lib/ide-haskell-repl-view.coffee
+++ lib/ide-haskell-repl-view.coffee
@@ -198,7 +198,7 @@

   setError: (err) ->
     if @errDiv?
-      @errDiv.innerText = err
+      @errDiv.innerText = err.trim()
     else
       @upi.setMessages @splitErrBuffer err


refactor: remove callbacks from synchronous steps definitions

--- features/step_definitions/run_steps.coffee
+++ features/step_definitions/run_steps.coffee
@@ -12,17 +12,14 @@
     execFile file, args, options, (@error, @stdout, @stderr) => done()

-  @Then /^I see the output$/, (output, done) ->
+  @Then /^I see the output$/, (output) ->
     expect(trimLines(@stdout)).to.contain trimLines(output)
-    done()

-  @Then /^I see the error$/, (error, done) ->
+  @Then /^I see the error$/, (error) ->
     expect(trimLines(@stderr)).to.contain trimLines(error)
-    done()

-  @Then /^it exits with a non\-zero status$/, (done) ->
+  @Then /^it exits with a non\-zero status$/, ->
     @errorExpected = true
     expect(@error).to.exist
-    done()


Router: Fixed export tag.

Router: Fixed export tag.

--- src/lib/core/Router.coffee
+++ src/lib/core/Router.coffee
@@ -189,7 +189,7 @@
     for some reason, you have to destroy previously created instances. Otherwise
     HistoryManager events will be triggered multiple times.

-    @expose
+    @export
   ###
   destroy: ->
     @historyManager.disposeInternal()


minor changes in the sec

minor changes in the sec

--- library/membrane/securing.coffee
+++ library/membrane/securing.coffee
@@ -64,8 +64,8 @@
         inbound = request.connection.address()
         assert server = @kernel.server.address()
         assert secure = @kernel.secure.address()
-        assert not _.isEmpty "#{str(inbound)}"
-        assert master = nconf.get "master:host"
+        assert not _.isEmpty try "#{str(inbound)}"
+        assert master = try nconf.get "master:host"
         return next() if str(inbound) is str(server)
         return next() if str(inbound) is str(secure)
         return next() if str(inbound) is master


Tweak

Tweak

--- config/test_tasks.coffee
+++ config/test_tasks.coffee
@@ -10,7 +10,7 @@
   # ensure that globals for the target backend are loaded
   require '../test/node_jquery_xhr'
   global.test_parameters = require '../test/parameters'
-  gulp.src('node_modules/backbone-orm/test/spec/sync/**/*.coffee')
+  gulp.src('node_modules/backbone-orm/test/spec/sync/**/*.tests.coffee')
     .pipe(mocha({}))
     .pipe es.writeArray (err, array) ->
       delete global.test_parameters


Log invalid IPN callbacks in the console

--- bot.iced
+++ bot.iced
@@ -380,7 +380,7 @@
 	res.end()

 	# Check if valid IPN callback
-	return unless ipnParams.ipn_secret is moolah.ipn_secret
+	unless ipnParams.ipn_secret is moolah.ipn_secret then return console.error "Incorrect IPN secret: #{ipnParams.ipn_secret}"
 	# Credit the user / or cancel the transaction
 	await Users_collection.findOne {lastAddFundTx: ipnParams.tx}, defer(err, user)
 	if err then return console.error err


sort by least voted on

sort by least voted on

Hours of sleep: 26
Cups of coffee: 5
Cans of soda: 1
Cans of redbull: 0

--- controllers/teams.coffee
+++ controllers/teams.coffee
@@ -37,7 +37,7 @@
 app.get /^\/(entries)?\/?$/, (req, res, next) ->
   page = (req.param('page') or 1) - 1
   query = { 'entry.votable': true, lastDeploy: {$ne: null} }
-  options = { sort: [['updatedAt', 1]], limit: 50, skip: 50 * page }
+  options = { sort: [['voteCounts.judge', 1]], limit: 50, skip: 50 * page }
   Team.find query, {}, options, (err, teams) ->
     return next err if err
     Team.count query, (err, count) ->


feat(app generator): add ability to include certain code into debug builds only

--- generators/app/templates/@Gruntfile.coffee
+++ generators/app/templates/@Gruntfile.coffee
@@ -162,6 +162,11 @@
                 app:
                     src:
                         browserify:     '<%= build.source %>app.coffee'
+
+                        ##  To have certain code included only in debug builds, prefix the filename with `debug.`
+                        ##
+                        debug:          '<%= build.source %>**/debug.*.coffee'
+
                         lint:           '<%= build.source %>**/*.coffee'

                     ##                  NOTE:   <%= npm.main %> should have <%= build.dist %> as its prefix:
@@ -283,7 +288,10 @@
                         noParse:        '<%= browserify.options.browserifyOptions.noParse %>'
                         debug:          true

-                files:                  '<%= browserify.app_dist.files %>'
+                files: [
+                    src:                [ '<%= build.part.app.src.browserify %>', '<%= build.part.app.src.debug %>' ]
+                    dest:               '<%= build.part.app.tgt %>'
+                ]

         ##


Fix build errors

Fix build errors

--- server/server.coffee
+++ server/server.coffee
@@ -21,7 +21,7 @@
 requireHTTPS = (req, res, next) ->
     logger.info "secure=", req.secure, req.protocol, req.headers.host, req.url, req.headers["X-Forwarded-Proto"]
     if !req.secure and !req.headers.host.startsWith("127.0.0.1")  # the latter is to avoid inner api requests
-        logger.info "Redirecting to https"  req.protocol, req.headers.host, req.url, req.headers["X-Forwarded-Proto"]
+        logger.info "Redirecting to https", req.protocol, req.headers.host, req.url, req.headers["X-Forwarded-Proto"]
         return res.redirect 'https://' + req.headers.host + req.url
     next()


don't need to parse bags with REST api, make redis connection member of Server class

--- server/server.coffee
+++ server/server.coffee
@@ -10,11 +10,12 @@
 CONTROLLER_ADDRESS = "tcp://0.0.0.0:5555"
 Q_PREFIX = '__q'

-redisC = null # the connection to redis
+class Server extends process.EventEmitter
+    # an object storing queue -> list of available workers
+    workers = {}

-class Server extends process.EventEmitter
     constructor: (config) ->
-        redisC = new redis.createClient
+        @redis = new redis.createClient

         pusher = zeromq.createSocket 'push'
         controller = zeromq.createSocket 'pub'
@@ -40,15 +41,15 @@
             response.end body

         addRoute "\/queue\/(.+?)\/?$", {
-            GET: (match, response) ->
+            GET: (match, response) =>
                 queue = Q_PREFIX + match[1]
-                redisC.lrange queue, 0, -1, (err, messages) ->
+                @redis.lrange queue, 0, -1, (err, messages) ->
                     respond response, {
                         success: true,
                         message: messages
                     }

-            POST: (match, response, bag) ->
+            POST: (match, response, bag) =>
                 queue = Q_PREFIX + match[1]
                 id = uuid.generate()
                 job = JSON.stringify {
@@ -56,43 +57,31 @@
                     message: bag
                 }

-                redisC.transaction ->
-                    redisC.set id, job
-                    redisC.lpush queue, id, (err, status) ->
+                @redis.transaction =>
+                    @redis.set id, job
+                    @redis.lpush queue, id, (err, status) ->
                         respond response, { success: status, uuid: id }

-            DELETE: (match, response) ->
+            DELETE: (match, response) =>
                 queue = Q_PREFIX + match[1]
-                redisC.del queue, (err, status) ->
+                @redis.del queue, (err, status) ->
                     respond response, {
                         success: status,
                     }
         }

         addRoute "\/job\/(.+?)\/?$", {
-            GET: (match, response) ->
-                redisC.get match[1], (err, job) ->
+            GET: (match, response) =>
+                @redis.get match[1], (err, job) ->
                     respond response, job
         }

-        listener = http.createServer (request, response) ->
+        listener = http.createServer (request, response) =>
             found = false
             message = ""
             request.on 'data', (data) ->
                 message += data
             request.on 'end', ->
-                if message
-                    try
-                        bag = JSON.parse message
-                    catch e
-                        respond response, {
-                            success: false,
-                            error: {
-                                message: e.message,
-                                type: e.type
-                            }
-                        }
-                        return
                 _.each routes, (methods, route) ->
                     match = request.url.match(new RegExp(route))
                     if match and methods[request.method]
@@ -111,7 +100,7 @@
                 throw err
             @emit('started', host, port)

-        redisC.on 'close', (in_error) ->
+        @redis.on 'close', (in_error) =>
             if in_error
                 util.puts "Connection to redis failed, please make sure the server is running."
                 process.exit -1


Update editor height change spec

Update editor height change spec

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -1271,11 +1271,16 @@

   describe "when the editor component is resized", ->
     it "updates the size in the editor model", ->
+      spyOn(editor, 'setHeight').andCallThrough()
       originalHeight = editor.getHeight()
       fourLinesHeight = 4 * editor.getLineHeightInPixels()
       node.style.height = "#{originalHeight - fourLinesHeight}px"
-      component.forceUpdate()
-      expect(editor.getHeight()).not.toBe originalHeight
+
+      waitsFor ->
+        editor.setHeight.callCount > 0
+
+      runs ->
+        expect(editor.getHeight()).not.toBe originalHeight

   buildMouseEvent = (type, properties...) ->
     properties = extend({bubbles: true, cancelable: true}, properties...)


clean up logic a bit

clean up logic a bit

--- src/components/task-teacher-review/exercise.cjsx
+++ src/components/task-teacher-review/exercise.cjsx
@@ -36,7 +36,6 @@
     formats.indexOf('free-response') > -1

   renderNoFreeResponse: ->
-    return null unless @expectsFreeResponse()
     freeResponsesClasses = 'teacher-review-answers has-no-answers'
     header = <i>No student text responses</i>

@@ -45,7 +44,6 @@
       className={freeResponsesClasses}/>

   renderFreeResponse: ->
-    return null unless @expectsFreeResponse()
     {answers} = @props
     {showAnswers} = @state
     question = @getQuestion()
@@ -72,7 +70,8 @@
     {answers, answered_count} = @props
     question = @getQuestion()

-    studentResponses = if answers.length then @renderFreeResponse() else @renderNoFreeResponse()
+    if @expectsFreeResponse()
+      studentResponses = if answers.length then @renderFreeResponse() else @renderNoFreeResponse()

     <CardBody className='task-step' pinned={false}>
       <Question


Update es-419.coffee - Spanish - Español (América Latina) (#5215)

* Update Spanish - Español (América Latina)

* Providing 50 translations

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -1,89 +1,89 @@
 module.exports = nativeDescription: "Español (América Latina)", englishDescription: "Spanish (Latin America)", translation:

   new_home:
-#    built_for_teachers_title: "A Coding Game Built with Teachers in Mind"
-#    built_for_teachers_blurb: "Teaching kids to code can often feel overwhelming. CodeCombat helps all educators teach students how to code in either JavaScript or Python, two of the most popular programming languages. With a comprehensive curriculum that includes six computer science units and reinforces learning through project-based game development and web development units, kids will progress on a journey from basic syntax to recursion!"
-#    built_for_teachers_subtitle1: "Computer Science"
-#    built_for_teachers_subblurb1: "Starting with our free Introduction to Computer Science course, students master core coding concepts such as while/for loops, functions, and algorithms."
-#    built_for_teachers_subtitle2: "Game Development"
-#    built_for_teachers_subblurb2: "Learners construct mazes and use basic input handling to code their own games that can be shared with friends and family."
-#    built_for_teachers_subtitle3: "Web Development"
+    built_for_teachers_title: "Un Juego de Codificación Construido con Maestros en Mente"
+    built_for_teachers_blurb: "Enseñar a los niños a codificar puede ser abrumador. CodeCombat ayuda a todos los educadores a enseñar a los estudiantes cómo codificar en JavaScript o Python, dos de los lenguajes de programación más populares. Con un plan de estudios comprensivo que incluye seis unidades de ciencias de computación, y refuerza el aprendizaje a través de unidades en el desarrollo de juegos y desarrollo web basados en proyectos, los niños progresarán en un viaje desde la sintaxis básica hasta la recursión!"
+    built_for_teachers_subtitle1: "Ciencias de Computación"
+    built_for_teachers_subblurb1: "Comenzando con nuestro curso gratuito de Introducción a las Ciencias de Computación, los estudiantes dominan los conceptos básicos de codificación, tales como bucles de while / for, funciones y algoritmos."
+    built_for_teachers_subtitle2: "Desarrollo de Juegos"
+    built_for_teachers_subblurb2: "Los alumnos construyen laberintos y utilizan el manejo de entrada básico para codificar sus propios juegos que pueden compartir con amigos y familiares."
+    built_for_teachers_subtitle3: "Desarrollo Web"
 #    built_for_teachers_subblurb3: "Using HTML, CSS, and jQuery, learners flex their creative muscles to program their own webpages with a custom URL to share with their classmates."
-#    century_skills_title: "21st Century Skills"
-#    century_skills_blurb1: "Students Don't Just Level Up Their Hero, They Level Up Themselves"
+    century_skills_title: "Habilidades del siglo 21"
+    century_skills_blurb1: "Los Estudiantes No Solo Suben de Nivel a su Héroe, Sino que se Suben de Nivel Ellos Mismos"
 #    century_skills_quote1: "You mess up…so then you think about all of the possible ways to fix it, and then try again. I wouldn't be able to get here without trying hard."
-#    century_skills_subtitle1: "Critical Thinking"
+    century_skills_subtitle1: "Pensamiento Crítico"
 #    century_skills_subblurb1: "With coding puzzles that are naturally scaffolded into increasingly challenging levels, CodeCombat's programming game ensures kids are always practicing critical thinking."
 #    century_skills_quote2: "Everyone else was making mazes, so I thought, ‘capture the flag’ and that’s what I did."
-#    century_skills_subtitle2: "Creativity"
+    century_skills_subtitle2: "Creatividad"
 #    century_skills_subblurb2: "CodeCombat encourages students to showcase their creativity by building and sharing their own games and webpages."
 #    century_skills_quote3: "If I got stuck on a level. I would work with people around me until we were all able to figure it out."
-#    century_skills_subtitle3: "Collaboration"
+    century_skills_subtitle3: "Colaboración"
 #    century_skills_subblurb3: "Throughout the game, there are opportunities for students to collaborate when they get stuck and to work together using our pair programming guide."
 #    century_skills_quote4: "I’ve always had aspirations of designing video games and learning how to code ... this is giving me a great starting point."
-#    century_skills_subtitle4: "Communication"
+    century_skills_subtitle4: "Comunicación"
 #    century_skills_subblurb4: "Coding requires kids to practice new forms of communication, including communicating with the computer itself and conveying their ideas using the most efficient code."
-#    classroom_in_box_title: "We Strive To:"
+    classroom_in_box_title: "Nos Esforzamos Por:"
 #    classroom_in_box_blurb1: "Engage every student so that they believe coding is for them."
 #    classroom_in_box_blurb2: "Empower any educator to feel confident when teaching coding."
 #    classroom_in_box_blurb3: "Inspire all school leaders to create a world-class computer science program."
-#    creativity_rigor_title: "Where Creativity Meets Rigor"
-#    creativity_rigor_subtitle1: "Make coding fun and teach real-world skills"
+    creativity_rigor_title: "Donde la Creatividad Alcanza el Rigor"
+    creativity_rigor_subtitle1: "Haga que la codificación sea divertida y enseñe habilidades del mundo real"
 #    creativity_rigor_blurb1: "Students type real Python and JavaScript while playing games that encourage trial-and-error, critical thinking, and creativity. Students then apply the coding skills they’ve learned by developing their own games and websites in project-based courses."
-#    creativity_rigor_subtitle2: "Reach students at their level"
+    creativity_rigor_subtitle2: "Alcanza a los alumnos a sus niveles"
 #    creativity_rigor_blurb2: "Every CodeCombat level is scaffolded based on millions of data points and optimized to adapt to each learner. Practice levels and hints help students when they get stuck, and challenge levels assess students' learning throughout the game."
-#    creativity_rigor_subtitle3: "Built for all teachers, regardless of experience"
+    creativity_rigor_subtitle3: "Construido para todos profesores, independientemente de la experiencia"
 #    creativity_rigor_blurb3: "CodeCombat’s self-paced, standards-aligned curriculum makes teaching computer science possible for everyone. CodeCombat equips teachers with the training, instructional resources, and dedicated support to feel confident and successful in the classroom."
-#    featured_partners_title1: "Featured In"
-#    featured_partners_title2: "Awards & Partners"
-#    featured_partners_blurb1: "CollegeBoard Endorsed Provider"
-#    featured_partners_blurb2: "Best Creativity Tool for Students"
-#    featured_partners_blurb3: "Top Pick for Learning"
-#    featured_partners_blurb4: "Code.org Official Partner"
-#    featured_partners_blurb5: "CSforAll Official Member"
-#    featured_partners_blurb6: "Hour of Code Activity Partner"
-#    for_leaders_title: "For School Leaders"
-#    for_leaders_blurb: "A Comprehensive, Standards-Aligned Computer Science Program"
-#    for_leaders_subtitle1: "Easy Implementation"
+    featured_partners_title1: "Presentado En"
+    featured_partners_title2: "Premios y Socios"
+    featured_partners_blurb1: "Proveedor Respaldado por CollegeBoard"
+    featured_partners_blurb2: "Mejor Herramienta de Creatividad para Estudiantes"
+    featured_partners_blurb3: "La Mejor Selección para el Aprendizaje"
+    featured_partners_blurb4: "Socio Oficial de Code.org"
+    featured_partners_blurb5: "Miembro Oficial de CSforAll"
+    featured_partners_blurb6: "Socio de Hour of Code Activity"
+    for_leaders_title: "Para los Líderes de la Escuela"
+    for_leaders_blurb: "Un Programa Comprensiva de Ciencias de Computación Alineado con los Estándares"
+    for_leaders_subtitle1: "Implementación Fácil"
 #    for_leaders_subblurb1: "A web-based program that requires no IT support. Get started in under 5 minutes using Google or Clever Single Sign-On (SSO)."
-#    for_leaders_subtitle2: "Full Coding Curriculum"
+    for_leaders_subtitle2: "Plan Completa de Estudios de Codificación"
 #    for_leaders_subblurb2: "A standards-aligned curriculum with instructional resources and professional development to enable any teacher to teach computer science."
-#    for_leaders_subtitle3: "Flexible Use Cases"
+    for_leaders_subtitle3: "Casos de Uso Flexible"
 #    for_leaders_subblurb3: "Whether you want to build a Middle School coding elective, a CTE pathway, or an AP Computer Science Principles class, CodeCombat is tailored to suit your needs."
-#    for_leaders_subtitle4: "Real-World Skills"
+    for_leaders_subtitle4: "Habilidades del Mundo Real"
 #    for_leaders_subblurb4: "Students build grit and develop a growth mindset through coding challenges that prepare them for the 500K+ open computing jobs."
-#    for_teachers_title: "For Teachers"
-#    for_teachers_blurb: "Tools to Unlock Student Potential"
-#    for_teachers_subtitle1: "Project-Based Learning"
+    for_teachers_title: "Para Maestros"
+    for_teachers_blurb: "Herramientas Para Abrir el Potencial del Estudiante"
+    for_teachers_subtitle1: "Aprendizaje Basado en Proyectos"
 #    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
-#    for_teachers_subtitle2: "Teacher Dashboard"
+    for_teachers_subtitle2: "Tablero de Maestros"
 #    for_teachers_subblurb2: "View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
-#    for_teachers_subtitle3: "Built-in Assessments"
+    for_teachers_subtitle3: "Evaluaciones Incorporadas"
 #    for_teachers_subblurb3: "Personalize instruction and ensure students understand core concepts with formative and summative assessments."
-#    for_teachers_subtitle4: "Automatic Differentiation"
+    for_teachers_subtitle4: "Diferenciacion Automatica"
 #    for_teachers_subblurb4: "Engage all learners in a diverse classroom with practice levels that adapt to each student's learning needs."
 #    game_based_blurb: "CodeCombat is a game-based computer science program where students type real code and see their characters react in real time."
-#    get_started: "Get started"
-#    global_title: "Join Our Global Community of Learners and Educators"
-#    global_subtitle1: "Learners"
-#    global_subtitle2: "Lines of Code"
-#    global_subtitle3: "Teachers"
-#    global_subtitle4: "Countries"
-#    go_to_my_classes: "Go to my classes"
-#    go_to_my_courses: "Go to my courses"
+    get_started: "Empieza ahora"
+    global_title: "Únete a nuestra Comunidad Global de Estudiantes y Educadores"
+    global_subtitle1: "Alumnos"
+    global_subtitle2: "Líneas de Código"
+    global_subtitle3: "Maestros"
+    global_subtitle4: "Países"
+    go_to_my_classes: "Ir a mis clases"
+    go_to_my_courses: "Ir a mis cursos"
 #    quotes_quote1: "Name any program online, I’ve tried it. None of them match up to CodeCombat. Any teacher who wants their students to learn how to code... start here!"
 #    quotes_quote2: " I was surprised about how easy and intuitive CodeCombat makes learning computer science. The scores on the AP exam were much higher than I expected and I believe CodeCombat is the reason why this was the case."
 #    quotes_quote3: "CodeCombat has been the most beneficial for teaching my students real-life coding capabilities. My husband is a software engineer and he has tested out all of my programs. He put this as his top choice."
 #    quotes_quote4: "The feedback … has been so positive that we are structuring a computer science class around CodeCombat. The program really engages the students with a gaming style platform that is entertaining and instructional at the same time. Keep up the good work, CodeCombat!"
-#    see_example: "See example"
+#    see_example: "Ver ejemplo"
     slogan: "El juego más interesante para aprender programación." # {change}
-#    teach_cs1_free: "Teach CS1 Free"
-#    teachers_love_codecombat_title: "Teachers Love CodeCombat"
+    teach_cs1_free: "Enseña CS1 Gratis"
+    teachers_love_codecombat_title: "Los Maestros les Encantan CodeCombat"
 #    teachers_love_codecombat_blurb1: "Report that their students enjoy using CodeCombat to learn how to code"
 #    teachers_love_codecombat_blurb2: "Would recommend CodeCombat to other computer science teachers"
 #    teachers_love_codecombat_blurb3: "Say that CodeCombat helps them support students’ problem solving abilities"
 #    teachers_love_codecombat_subblurb: "In partnership with McREL International, a leader in research-based guidance and evaluations of educational technology."
-#    try_the_game: "Try the game"
+    try_the_game: "Prueba el juego"
     classroom_edition: "Versión para el Aula:"
     learn_to_code: "Aprende a programar:"
     play_now: "Jugar Ahora"


Remove extra nextAnimationFrame call

Remove extra nextAnimationFrame call

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -661,7 +661,6 @@
           font-weight: bold;
         }
       """
-      nextAnimationFrame() # re-measure characters once for a synchronous set of stylesheet changes
       nextAnimationFrame() # update based on new measurements

       cursor = componentNode.querySelector('.cursor')


Remove backbone attributes from Player

Remove backbone attributes from Player

--- app/models/Player.coffee
+++ app/models/Player.coffee
@@ -31,7 +31,7 @@
     img = new Image()
     img.src = tileSet.image

-    @set 'atlas':img
+    @atlas = img

   onTouch: (otherBody, point, impulse) =>
     return false if not @physBody?
@@ -64,7 +64,6 @@
   render: (ctx, cx, cy) =>
     tileSet = @tileSet

-    img = @get 'atlas'
     spritePkt = @getSpritePacket()

     # position of first pixel at [sx, sy] in atlas
@@ -87,7 +86,7 @@
     dx = dx - @tileSet.offset.x
     dy = dy - @tileSet.offset.y

-    ctx.drawImage img, sx, sy, sw, sh, dx, dy, dw, dh
+    ctx.drawImage @atlas, sx, sy, sw, sh, dx, dy, dw, dh

   # override from Entity class


:lipstick:

:lipstick:

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -57,12 +57,12 @@
     false

   componentDidUpdate: (prevProps) ->
-    @updateRenderedLines()
+    @updateLines()
     @measureLineHeightAndCharWidth() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight')
     @clearScopedCharWidths() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily')
     @measureCharactersInNewLines() unless @props.scrollingVertically

-  updateRenderedLines: ->
+  updateLines: ->
     {editor, visibleRowRange, showIndentGuide, selectionChanged} = @props
     [startRow, endRow] = visibleRowRange
     visibleLines = editor.linesForScreenRows(startRow, endRow - 1)


specの構造を修正

specの構造を修正

--- spec/service/BBSIndex.spec.coffee
+++ spec/service/BBSIndex.spec.coffee
@@ -1,5 +1,19 @@
-describe "App.BBSIndex", ->
+describe "App.BBSIndex.BBSIndexService", ->
   "use strict"
+
+  beforeEach ->
+    module "BBSIndex"
+
+    inject ($httpBackend, bbsIndexService) =>
+      @$httpBackend = $httpBackend
+      @bbsIndexService = angular.copy(bbsIndexService)
+      return
+    return
+
+  afterEach ->
+    @$httpBackend.verifyNoOutstandingExpectation()
+    @$httpBackend.verifyNoOutstandingRequest()
+    return

   describe ".parse", ->
     it "与えられたHTML文字列をパースし、BBS一覧のデータを返す", ->
@@ -42,24 +56,6 @@

       expect(result).toBeNull()
       return
-    return
-  return
-
-describe "bbsIndexService", ->
-  "use strict"
-
-  beforeEach ->
-    module "BBSIndex"
-
-    inject ($httpBackend, bbsIndexService) =>
-      @$httpBackend = $httpBackend
-      @bbsIndexService = angular.copy(bbsIndexService)
-      return
-    return
-
-  afterEach ->
-    @$httpBackend.verifyNoOutstandingExpectation()
-    @$httpBackend.verifyNoOutstandingRequest()
     return

   describe ".get", ->


Firefox +IE demand the event param

Firefox +IE demand the event param

--- shared/social.coffee
+++ shared/social.coffee
@@ -7,7 +7,7 @@
       true

 Template.entrySocial.events
-  'click .btn': ->
+  'click .btn': (event)->
     serviceName = $(event.target).attr('id').split('-')[1]
     callback = (err) ->
       if (!err)


Add additional consistency check for selected cells.

--- h2o-web/src/main/steam/scripts/flow.coffee
+++ h2o-web/src/main/steam/scripts/flow.coffee
@@ -2152,9 +2152,14 @@
     Flow.Cell _, _renderers, type, input

   checkConsistency = ->
+    selectionCount = 0
     for cell, i in _cells()
       unless cell
         error "index #{i} is empty"
+      else
+        if cell.isSelected()
+          selectionCount++
+    error "selected cell count = #{selectionCount}" if selectionCount isnt 1
     return

   selectCell = (target) ->


Not going to use coffeescript

Not going to use coffeescript

--- app/scripts/hello.coffee
+++ app/scripts/hello.coffee
@@ -1 +0,0 @@
-console.log "'Allo from CoffeeScript!"


fetch representation if fair has full feature

--- apps/fairs/routes.coffee
+++ apps/fairs/routes.coffee
@@ -8,6 +8,7 @@
 representation = (fair) ->
   dfd = Q.defer()
   sets = new OrderedSets(owner_type: 'Fair', owner_id: fair.id, sort: 'key')
+
   sets.fetchAll(cache: true).then ->
     set = sets.findWhere(key: 'explore')?.get('items')
     fair.representation = set
@@ -18,7 +19,7 @@
 # see if they are published or not
 profiles = (fairs) ->
   _.compact fairs.map (fair) ->
-    if fair.has('organizer')
+    if fair.get('has_full_feature') is true
       fair.related().profile.fetch(cache: true)

 parseGroups = (fairs) ->
@@ -39,7 +40,6 @@
     data: sort: '-start_at', size: 50
     success: ->
       Q.allSettled(profiles(fairs)).then(->
-
         { currentFairs, pastFairs, upcomingFairs } = parseGroups(fairs)

         featuredFairs = _.flatten [currentFairs, pastFairs]
@@ -55,6 +55,7 @@
         ]

         Q.allSettled(promises).then(->
+
           res.locals.sd.FAIRS = currentFairs
           res.render 'index',
             featuredFairs: featuredFairs


Defer input event registration to avoid counting the initial event (legit bug?)

--- test/src/system/custom_element_test.coffee
+++ test/src/system/custom_element_test.coffee
@@ -37,7 +37,7 @@
 editorTest "element triggers input events when the document changes", (done) ->
   element = getEditorElement()
   eventCount = 0
-  element.addEventListener "input", (event) -> eventCount++
+  defer -> element.addEventListener "input", (event) -> eventCount++

   typeCharacters "a", ->
     equal eventCount, 1


Allow `f2` and `tab` keybindings

Allow `f2` and `tab` keybindings

This is possible now that we're on atom-shell@0.6.4

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -61,10 +61,6 @@
       for binding in bindings
         continue if binding.indexOf(' ') != -1

-        # To be fixed in atom-shell
-        continue if binding.indexOf('f2') != -1
-        continue if binding.indexOf('tab') != -1
-
         filtered[key] ?= []
         filtered[key].push(binding)
     filtered


show holiday requirements as well

show holiday requirements as well

--- shared/idle/controllers/maps/IdleMapViewerController.coffee
+++ shared/idle/controllers/maps/IdleMapViewerController.coffee
@@ -102,9 +102,10 @@
             requires = no
             requirementText = "\nRequirements\n-------------------"
             if child.requireAchievement then requirementText += "\nAchievement: #{child.requireAchievement}";requires=yes
-            if child.requireBoss then requirementText += "\nBoss Kill: #{child.requireBoss}";requires=yes
-            if child.requireClass then requirementText += "\nClass: #{child.requireClass}";requires=yes
+            if child.requireBoss        then requirementText += "\nBoss Kill: #{child.requireBoss}";requires=yes
+            if child.requireClass       then requirementText += "\nClass: #{child.requireClass}";requires=yes
             if child.requireCollectible then requirementText += "\nCollectible: #{child.requireCollectible}";requires=yes
+            if child.requireHoliday     then requirementText += "\nHoliday: #{child.requireHoliday}";requires=yes

             itemText = "#{itemText}\n#{requirementText}" if requires

@@ -187,6 +188,7 @@
               requireAchievement: object.properties.requireAchievement
               requireClass:       object.properties.requireClass
               flavorText:         object.properties.flavorText
+              requireHoliday:     object.properties.holiday

           $scope.drawMap()


Update script to how it was run

Update script to how it was run

--- scripts/updateOldClassrooms.coffee
+++ scripts/updateOldClassrooms.coffee
@@ -29,10 +29,14 @@

 # ObjectId for 8/1/17
 startDateObjectId = mongoose.Types.ObjectId("598026f00000000000000000")
+endDateObjectId = mongoose.Types.ObjectId('5aaf752d0000000000000000')

-batchSize = 10
+batchSize = 20

 co(->
+  todo = yield Classroom.count({_id: {$gte: startDateObjectId}, 'courses.levels.assessment': {$exists: false}})
+  total = todo
+
   while true
     classrooms = yield Classroom.find({_id: {$gte: startDateObjectId}, 'courses.levels.assessment': {$exists: false}}).limit(batchSize)
     break unless classrooms.length > 0
@@ -40,13 +44,14 @@
       Promise.resolve co ->
         yield classroom.setUpdatedCourses({isAdmin: false, addNewCoursesOnly: false, includeAssessments: true})
         database.validateDoc(classroom)
-        # classroom = yield classroom.save()
+        classroom = yield classroom.save()
         console.log("#{new Date().toISOString().substring(0, 10)} Updated classroom #{classroom.id} #{classroom.get('name')}")
     res = yield classroomUpdates
     console.log("#{new Date().toISOString().substring(0, 10)} #{res.length} classrooms updated.")
+    todo -= res.length
+    console.log('todo', todo, '/', total, new Date())

     # TEMP
-    break

 ).then(->
   process.exit()


Fix meeting date format

Fix meeting date format

--- angular/core/components/poll/meeting/time/poll_meeting_time.coffee
+++ angular/core/components/poll/meeting/time/poll_meeting_time.coffee
@@ -8,14 +8,13 @@
       m.format(formatFor(m))

     formatFor = (m) ->
-      switch m._f
-        when "YYYY-MM-DDTHH:mm:ss.SSSSZ"
-          if m.year() == moment().year()
-            "D MMMM - h:mma"
-          else
-            "D MMMM YYYY - h:mma"
-        when "YYYY-MM-DD"
-          if m.year() == moment().year()
-            "D MMMM"
-          else
-            "D MMMM YYYY"
+      if m._f == 'YYYY-MM-DD'
+        if m.year() == moment().year()
+          "D MMMM"
+        else
+          "D MMMM YYYY"
+      else
+        if m.year() == moment().year()
+          "D MMMM - h:mma"
+        else
+          "D MMMM YYYY - h:mma"


Fix in place edit script response handling

--- app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee
+++ app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee
@@ -38,7 +38,8 @@
       else
         response.responseText
     success: (response) ->
-      $.globalEval(response.responseText)
+      if response && $(this).data().options?.accept == 'script'
+        $.globalEval(response.responseText)
     datetimepicker:
       format: "dd.mm.yyyy hh:ii"
       autoclose: true


Add placeholder to in place editable

Add placeholder to in place editable

--- app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee
+++ app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee
@@ -27,29 +27,30 @@
 EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook

 window.initInPlaceEditable = ->
-  $('.editable').editable
-    onblur: 'submit'
-    placement: 'bottom'
-    emptytext: I18n.lookup('admin.js.empty') || 'Empty'
-    error: (response, newValue) ->
-      log response
-      if response.status == 422
-        flash JSON.parse(response.responseText).errors.join(', ')
-      else
-        response.responseText
-    success: (response) ->
-      if response && $(this).data().options?.accept == 'script'
-        $.globalEval(response.responseText)
-    datetimepicker:
-      format: "dd.mm.yyyy hh:ii"
-      autoclose: true
-      todayBtn: true
-      language: I18n.locale
-    ajaxOptions:
-      xhrFields:
-        withCredentials: true
-      headers:
-        Accept: 'application/json'
+  $('.editable').each ->
+    $el = $(this)
+    $el.editable
+      onblur: 'submit'
+      placement: 'bottom'
+      emptytext: $el.attr('placeholder') || I18n.lookup('admin.js.empty') || 'Empty'
+      error: (response) ->
+        if response.status == 422
+          Object.entries(response.responseJSON.errors).map((i) -> i.join(': ')).join(', ')
+        else
+          response.responseText
+      success: (response) ->
+        if response && $(this).data().options?.accept == 'script'
+          $.globalEval(response.responseText)
+      datetimepicker:
+        format: "dd.mm.yyyy hh:ii"
+        autoclose: true
+        todayBtn: true
+        language: I18n.locale
+      ajaxOptions:
+        xhrFields:
+          withCredentials: true
+        headers:
+          Accept: 'application/json'

 $(document).on 'admin:init', initInPlaceEditable


Fixed removing of double empty lines.

Fixed removing of double empty lines.

--- app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
@@ -72,6 +72,7 @@
         # build html body
         article['html'] = article.body.trim()
         article['html'].replace(/\n\n/m, "\n");
+        article['html'].replace(/\n\r\n\r/m, "\n");
         article['html'] = window.linkify( article['html'] )
         notify = "" + T('See more') + ""
         @article_changed = false


Implement an email submission handler.

Implement an email submission handler.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -79,7 +79,24 @@
 ## ANCHOR TAB LOADING AND DISPLAY
 ##
 submitEmail = (event) ->
-  # TODO
+  # In case of compatibility mode
+  $ = jQuery
+  tabId = $("#anchortab-loader").data('tab-id')
+
+  $target = $(event.target)
+  $input = $target.closest("#anchor-tab").find(".email-input")
+  email = $input.val()
+  submissionUri = "http://" + apiDomain + "/api/v1/embed/" + tabId + "/submit"
+
+  $.ajax
+    url: submissionUri
+    dataType: 'jsonp'
+    data:
+      email: email
+    success: (event) ->
+      alert("Email submitted!")
+    error: (xhr, status, error) ->
+      alert(error)

 displayTab = (tabJson) ->
   # In case of compatibility mode


object merge doesn't touch d3plus values

--- src/object/merge.coffee
+++ src/object/merge.coffee
@@ -12,15 +12,15 @@
 module.exports = (obj1, obj2) ->

   copyObject = (obj, ret) ->
-    for a of obj
-      unless typeof obj[a] is "undefined"
-        if validate(obj[a])
-          ret[a] = {}  if typeof ret[a] isnt "object"
-          copyObject obj[a], ret[a]
-        else if not d3selection(obj[a]) and obj[a] instanceof Array
-          ret[a] = obj[a].slice(0)
+    for k, v of obj
+      unless typeof v is "undefined" or k.indexOf("d3plus") is 0
+        if validate v
+          ret[k] = {} if typeof ret[k] isnt "object"
+          copyObject v, ret[k]
+        else if not d3selection(v) and v instanceof Array
+          ret[k] = v.slice 0
         else
-          ret[a] = obj[a]
+          ret[k] = v

   obj3 = {}


prevents invoices-list to load forever when widgets content cannot be retrieved

--- src/components/widgets/invoices-list/invoices-list.directive.coffee
+++ src/components/widgets/invoices-list/invoices-list.directive.coffee
@@ -18,7 +18,7 @@
   # Widget specific methods
   # --------------------------------------
   w.initContext = ->
-    $scope.isDataFound = !_.isEmpty(w.content.entities)
+    $scope.isDataFound = !_.isEmpty(w.content) && !_.isEmpty(w.content.entities)

     if $scope.isDataFound && $scope.orderBy=='due '
       if $scope.entityType=='suppliers'


fixed WebSocketBridge mapping of sockets to contexts

--- src/bridges/WebsocketBridge.coffee
+++ src/bridges/WebsocketBridge.coffee
@@ -18,8 +18,8 @@

   # Map current context so that native calls know where to send responses
   map_context: ()=>
-    if window.top.AG_SCREEN_ID? and window.top.AG_LAYER_ID? and window.top.AG_VIEW_ID?
-      @send "mapWebSocketConnectionToContext", { parameters: {screen: window.top.AG_SCREEN_ID, layer: window.top.AG_LAYER_ID, view: window.top.AG_VIEW_ID } }
+    @send method: "mapWebSocketConnectionToContext"
+
     return @

   sendMessageToNative:(message)->


#83: distance support has been capped by level 5.0

--- src/client/PlayerComparator.coffee
+++ src/client/PlayerComparator.coffee
@@ -47,10 +47,12 @@
   bIsGreater = -1
   gamesA = a.gamesCitizen + a.gamesSheriff + a.gamesMafia + a.gamesDon
   gamesB = b.gamesCitizen + b.gamesSheriff + b.gamesMafia + b.gamesDon
-  averageA = if gamesA > 0 then a.rating / gamesA else 0
-  averageB = if gamesB > 0 then b.rating / gamesB else 0
-  forceA = averageA + if gamesA then Math.log(gamesA) / Math.log(3) else 0
-  forceB = averageB + if gamesB then Math.log(gamesB) / Math.log(3) else 0
+  averageA = if gamesA > 0 then a.rating / gamesA else 0.0
+  averageB = if gamesB > 0 then b.rating / gamesB else 0.0
+  distanceSupportA = if gamesA then Math.log(gamesA) / Math.log(3) else 0.0
+  distanceSupportB = if gamesB then Math.log(gamesB) / Math.log(3) else 0.0
+  forceA = averageA + Math.min(distanceSupportA, 5)
+  forceB = averageB + Math.min(distanceSupportB, 5)
   if gamesA != gamesB
     if gamesA <= minimalDistance && gamesB <= minimalDistance || gamesA > minimalDistance && gamesB > minimalDistance
       if Math.abs(forceA - forceB) / Math.max(forceA, forceB) > 0.01


Fix possible crash in signature validation

--- file/acp/be.bastelstu.wcf.nodePush/app.coffee
+++ file/acp/be.bastelstu.wcf.nodePush/app.coffee
@@ -80,6 +80,10 @@
 checkSignature = (data, key) ->
 	[ signature, payload ] = String(data).split /-/

+	unless payload?
+		debug "Invalid signature #{data}"
+		return false
+
 	payload = new Buffer payload, 'base64'
 	if signature.length isnt 40
 		debug "Invalid signature #{data}"


Moved request state logic to parse()

Moved request state logic to parse()

--- SingularityUI/app/models/Request.coffee
+++ SingularityUI/app/models/Request.coffee
@@ -1,14 +1,18 @@
 Model = require './model'

 class Request extends Model
-
-    initialize: =>
-        @set "displayState", @getState()
-
+
     parse: (data) ->
         if data.request?
             data.request.daemon = if _.isNull(data.request.daemon) then true else data.request.daemon
             data.daemon = data.request.daemon
+
+            if data.cleanupType?
+                data.displayState = contants.requestStates.CLEANUP
+            else if data.requestDeployState? and data.requestDeployState.pendingDeploy?
+                data.displayState = constants.requestStates.PENDING
+            else
+                data.displayState = constants.requestStates[data.state]
         data

     url: => "#{ config.apiRoot }/requests/request/#{ @get('id') }"
@@ -40,15 +44,6 @@
             options.contentType = 'text/plain'

         $.ajax options
-
-    getState: =>
-        if @get("cleanupType")?
-            return contants.requestStates.CLEANUP
-        else if @get("requestDeployState").pendingDeploy != null
-            return constants.requestStates.PENDING
-        else
-            apiState = @get("state")
-            return constants.requestStates[apiState]

 module.exports = Request


improve create blueprint

improve create blueprint

--- template/base/api/blueprints/create.coffee
+++ template/base/api/blueprints/create.coffee
@@ -20,7 +20,7 @@
 @param {String} callback - default jsonp callback param (i.e. the name of the js function returned)
 @param {*} * - other params will be used as `values` in the create
 ###
-module.exports = createRecord = (req, res) ->
+module.exports = (req, res) ->
   Model = actionUtil.parseModel(req)

   # Create data object (monolithic combination of all parameters)
@@ -28,23 +28,27 @@
   data = actionUtil.parseValues(req)

   # Create new instance of model using data from params
-  Model.create(data).exec created = (err, newInstance) ->
+  Model.create(data).populateAll().exec (err, newInstance) ->

     # Differentiate between waterline-originated validation errors
     # and serious underlying issues. Respond with badRequest if a
     # validation error is encountered, w/ validation info.
     return res.negotiate(err)  if err

-    # If we have the pubsub hook, use the model class's publish method
-    # to notify all subscribers about the created item
-    if req._sails.hooks.pubsub
-      if req.isSocket
-        Model.subscribe req, newInstance
-        Model.introduce newInstance
-      Model.publishCreate newInstance, not req.options.mirror and req
+    # Use find method to return the model for the populate option
+    Model.findOne(newInstance.id).populateAll().exec (err, newInstance) ->

-    # Send JSONP-friendly response if it's supported
-    # (HTTP 201: Created)
-    res.status 201
-    res.ok newInstance.toJSON()
-    return
+      # If we have the pubsub hook, use the model class's publish method
+      # to notify all subscribers about the created item
+      if req._sails.hooks.pubsub
+        if req.isSocket
+          Model.subscribe req, newInstance
+          Model.introduce newInstance
+        Model.publishCreate newInstance, not req.options.mirror and req
+
+      # Send JSONP-friendly response if it's supported
+      # (HTTP 201: Created)
+
+      # Change the id for user.email
+      res.status 201
+      res.ok newInstance


reduce for's repeated code

reduce for's repeated code

--- test-integration/helpers/utils/wait.coffee
+++ test-integration/helpers/utils/wait.coffee
@@ -3,38 +3,61 @@
 class Wait
   constructor: (test) -> @test = test

-  # TODO reduce the copy pasta between for and forMultiple
-  forMultiple: (locator, ms = 60 * 1000) =>
-    locator = @test.utils.toLocator(locator)
-    locator.shouldBeVisible ?= true
-    waitUntil = if locator.shouldBeVisible then 'elementIsVisible' else 'elementIsEnabled'
+  _for: (single = true) =>
+    singleOptions =
+      find: @test.driver.findElement.bind(@test.driver)
+      untilLocated: selenium.until.elementLocated
+      buildWaitMessage: (locator) ->
+        "Looking for #{JSON.stringify(locator)}"
+      buildFoundMessage: (locator, result) ->
+        "Found #{JSON.stringify(locator)}"
+      buildDisplayMessage: (locator) ->
+        "Waiting for #{JSON.stringify(locator)} to show"
+      filter: (result) ->
+        result

-    @giveTime ms, =>
-      @test.utils.verboseWrap "Waiting for multiple #{JSON.stringify(locator)}", =>
-        @test.driver.wait(selenium.until.elementsLocated(locator))
-        # Because of animations an element might be in the DOM but not visible
-        el = @test.driver.findElements(locator)
+    multipleOptions =
+      find: @test.driver.findElements.bind(@test.driver)
+      untilLocated: selenium.until.elementsLocated
+      buildWaitMessage: (locator) ->
+        "Looking for multiple #{JSON.stringify(locator)}"
+      buildFoundMessage: (locator, result) ->
+        "Found #{result.count} matching #{JSON.stringify(locator)}"
+      buildDisplayMessage: (locator) ->
+        "Waiting for #{JSON.stringify(locator)} to show"
+      filter: (result) ->
+        result[0]

-        el.then (elements) =>
-          @test.utils.verbose("Found #{elements.count}")
-          @test.driver.wait(selenium.until[waitUntil](elements[0]))
+    options = if single then singleOptions else multipleOptions

-    el = @test.driver.findElements(locator)
-    el
+    {find, untilLocated, buildWaitMessage, buildFoundMessage, buildDisplayMessage, filter} = options
+
+    (locator, ms = 60 * 1000) =>
+      locator = @test.utils.toLocator(locator)
+
+      {shouldBeVisible} = locator
+      shouldBeVisible ?= true
+      untilReady = if shouldBeVisible then selenium.until.elementIsVisible else selenium.until.elementIsEnabled
+
+      @giveTime ms, =>
+        @test.utils.verboseWrap buildWaitMessage(locator), =>
+          @test.driver.wait(untilLocated(locator))
+
+          # Because of animations an element might be in the DOM but not visible
+          find(locator).then (result) =>
+            @test.utils.verbose(buildFoundMessage(locator, result))
+            @test.utils.verboseWrap buildDisplayMessage(locator), =>
+              @test.driver.wait(untilReady(filter(result)))
+
+      el = find(locator)
+      el
+
+  forMultiple: (locator, ms = 60 * 1000) ->
+    @_for(false)(locator, ms)

   # Waits for an element to be available and bumps up the timeout to be at least 60sec from now
-  for: (locator, ms = 60 * 1000) =>
-    locator = @test.utils.toLocator(locator)
-    locator.shouldBeVisible ?= true
-    waitUntil = if locator.shouldBeVisible then 'elementIsVisible' else 'elementIsEnabled'
-
-    @giveTime ms, =>
-      @test.driver.wait(selenium.until.elementLocated(locator))
-      el = @test.driver.findElement(locator)
-      # Because of animations an element might be in the DOM but not visible
-      @test.utils.verboseWrap "Waiting for #{JSON.stringify(locator)}", => @test.driver.wait(selenium.until[waitUntil](el))
-    el = @test.driver.findElement(locator)
-    el
+  for: (locator, ms = 60 * 1000) ->
+    @_for(true)(locator, ms)

   click: (locator, ms) =>
     @test.utils.verboseWrap "Wait and click #{JSON.stringify(locator)}", =>


fix self model reference test

fix self model reference test

--- test/generators/relational/has_many.coffee
+++ test/generators/relational/has_many.coffee
@@ -55,6 +55,7 @@
     schema: _.defaults({
       owner: -> ['belongsTo', SelfReference, foreign_key: 'owner_id', as: 'self_references']
       self_references: -> ['hasMany', SelfReference, as: 'owner']
+      is_base: 'Boolean'
     }, BASE_SCHEMA)
     sync: SYNC(SelfReference)

@@ -103,6 +104,7 @@
         create_queue.defer (callback) -> Fabricator.create(SelfReference, BASE_COUNT, {
           name: Fabricator.uniqueId('self_reference_')
           created_at: Fabricator.date
+          is_base: true
         }, (err, models) -> MODELS.self_references = models; callback(err))
         create_queue.defer (callback) -> Fabricator.create(SelfReference, BASE_COUNT, {
           name: Fabricator.uniqueId('self_reference_target_')
@@ -132,18 +134,18 @@
         for self_reference in MODELS.self_references
           do (self_reference) ->
             save_queue.defer (callback) ->
+              self_reference_inverse = MODELS.self_reference_inverses.pop()
+              self_reference_inverse.set({
+                owner: self_reference
+              })
+              self_reference_inverse.save callback
+            save_queue.defer (callback) ->
               self_references = self_reference.get('self_references') or []
               self_references.push MODELS.self_reference_targets.pop()
               self_reference.set({
                 self_references: self_references
               })
               self_reference.save callback
-            save_queue.defer (callback) ->
-              self_reference_inverse = MODELS.self_reference_inverses.pop()
-              self_reference_inverse.set({
-                owner: self_reference
-              })
-              self_reference_inverse.save callback

         save_queue.await callback

@@ -1273,7 +1275,7 @@
       related_key = 'self_references'
       related_id_accessor = 'self_reference_ids'

-      SelfReference.cursor({$one: true}).include(related_key).toModels (err, owner) ->
+      SelfReference.cursor({$one: true, is_base: true}).include(related_key).toModels (err, owner) ->
         assert.ok(!err, "No errors: #{err}")
         assert.ok(owner, 'found model')
         owner_id = owner.id


Update spec for new sorted order

Update spec for new sorted order

--- spec/apm-spec.coffee
+++ spec/apm-spec.coffee
@@ -32,9 +32,9 @@
         runs ->
           expect(available.length).toBe 3
           expect(available[0].name).toBe 'beverly-hills'
-          expect(available[1].name).toBe 'multi-version'
-          expect(available[1].version).toBe '2.0.0'
-          expect(available[2].name).toBe 'blossom'
+          expect(available[1].name).toBe 'blossom'
+          expect(available[2].name).toBe 'multi-version'
+          expect(available[2].version).toBe '2.0.0'

     xdescribe 'when a version is specified', ->
       it 'returns the packages that are applicable for that version', ->


Use deep equality check for ASTs.

Use deep equality check for ASTs.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -32,7 +32,7 @@
     ]
     it 'should be able to produce correct AST', ->
       results = parser.compile statement
-      expect(stringify(ast)).to.eql stringify results
+      expect(ast).to.deep.equal results
       #assert results[0]._uuid? is true, 'has uuid'
       #for key, val of ast
       # expect(results[0][key]).to.eql val
@@ -52,7 +52,7 @@
     ]
     it 'should be able to produce correct AST', ->
       results = parser.compile statement
-      expect(stringify(ast)).to.eql stringify results
+      expect(ast).to.deep.equal results
       #assert results[0]._uuid? is true, 'has uuid'
       #for key, val of ast
       # expect(results[0][key]).to.eql val
@@ -84,10 +84,10 @@
     ]
     it '/ long-form', ->
       results = parser.compile statement
-      expect(ast).to.eql results
+      expect(ast).to.deep.equal results
     it '/ short-form', ->
       results = parser.compile statement2
-      expect(ast2).to.eql results
+      expect(ast2).to.deep.equal results

   describe 'with a statement containing CCSS & VFL', ->
     statement = """
@@ -120,7 +120,7 @@
     ]
     it 'should be able to produce correct AST', ->
       results = parser.compile statement
-      expect(ast).to.eql results
+      expect(ast).to.deep.equal results
       #assert results[0]._uuid? is true, 'has uuid'
       #for key, val of ast
       #  expect(results[0][key]).to.eql val
@@ -276,7 +276,7 @@
     ]
     it 'should be able to produce correct AST', ->
       results = parser.compile statement
-      expect(ast).to.eql results
+      expect(ast).to.deep.equal results
       #assert results[0]._uuid? is true, 'has uuid'
       #for key, val of ast
       #  expect(results[0][key]).to.eql val


req.hostname becomes req.headHost, arguably less prone to name-collisions

--- web/app.coffee
+++ web/app.coffee
@@ -58,7 +58,7 @@
 # Observability status from http://vimeo.com/52714202
 router.get "/health", ->
   @res.send
-    of: @req.hostname
+    of: @req.headHost
     on: (new Date)
     pid: process.pid
     uptime: process.uptime() # in seconds
@@ -82,12 +82,12 @@
   app.use express.logger()

   # Enable host-specific variations of pages, app details, etc.
-  # So far 'astrolin.org' and 'astropi.org'. In a Jade template, hostname becomes
+  # So far 'astrolin.org' and 'astropi.org'. In a Jade template, headHost becomes
   # theme - available as a local var.
   app.use (req, res, next) ->
-    domain = req.headers.host.split(':')[0]
-    req.hostname = if dev and process.env.HOST_APP? then process.env.HOST_APP else domain
-    res.locals.theme = if req.hostname is 'astropi.org' then 'pi' else 'lin'
+    req.headHost = req.headers.host.split(':')[0]
+    req.headHost = process.env.HOST_APP if dev and process.env.HOST_APP?
+    res.locals.theme = if req.headHost is 'astropi.org' then 'pi' else 'lin'
     next()

   # Some heck configuration options for custom errors.


--- spec/mediador.coffee
+++ spec/mediador.coffee
@@ -261,7 +261,25 @@

-spec "#off: Removes any amount of subscriptions @newAPI"
+spec "#off: Removes any amount of subscriptions @newAPI", ->
+  # given
+  class AlwaysTrue
+    match: -> true
+  class AlwaysFalse
+    match: -> false
+  venue = new Mediador
+  venue.on 'first', 'subscription', '', AlwaysTrue
+  venue.on 'second', 'subscription', '', AlwaysFalse
+  venue.on 'third', 'subscription', '', AlwaysTrue
+
+  # when
+  venue.off 'irrelevant'
+
+  # then
+  assert.equal venue.subscriptions.length, 1
+  assert venue.subscriptions[0] instanceof AlwaysFalse
+
+

 spec "#on: Creates using properties as events, methods as callback and @set as context @newAPI"


Not all 3xx codes are redirects

Not all 3xx codes are redirects

--- src/package-fetcher.coffee
+++ src/package-fetcher.coffee
@@ -122,7 +122,7 @@
         redirectCount = 0
         getCallback = (res) =>
           unless res.statusCode is 200
-            if res.statusCode >= 300 and res.statusCode < 400
+            if res.statusCode in [ "300", "301", "302", "303", "307", "308" ]
               redirectCount += 1
               if redirectCount > 5
                 error "Unable to GET #{href}: Too many redirects"


Remove lint

Remove lint

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -424,7 +424,7 @@
     delete @activePackages[pack.name]
     @emitter.emit 'did-deactivate-package', pack

-  handleMetadataError: (error, packagePath)->
+  handleMetadataError: (error, packagePath) ->
     metadataPath = path.join(packagePath, 'package.json')
     detail = error.message + " in #{metadataPath}"
     stack = error.stack + "\n  at #{metadataPath}:1:1"


Fix responsive plots

Fix responsive plots

--- bokehjs/src/coffee/common/plot.coffee
+++ bokehjs/src/coffee/common/plot.coffee
@@ -123,7 +123,8 @@
     if @mget('responsive')
       throttled_resize = _.throttle(@resize, 100)
       $(window).on("resize", throttled_resize)
-      $(@resize)
+      # Just need to wait a small delay so container has a width
+      _.delay(@resize, 10)

     @unpause()


Use :? instead of ( is)? in the listeners

While I prefer the sentence structure using the word is, the
roles.coffee script heavily listens for and responds to commands with
is in them. This causes a very confusing message when updating cash:

    > hubot cash burn rate is $10,000
    Ok, our burn rate is $10,000 per month
    I don't know anything about cash burn rate

Since roles.coffee is shipped with hubot and used in a number of
other situations, I've concluded that the best course of action is to
change the listeners for this script.

I've explored other options for this, such as using "set burn rate at"
or "update burn rate to", but this seems like the simplest change
with the least amount of impact.

--- src/scripts/cash.coffee
+++ src/scripts/cash.coffee
@@ -82,11 +82,11 @@
   cash = new Cash robot
   optf = new OutputFormatter()

-  robot.respond /cash (left|on hand)( is)? (.+)$/i, (msg) ->
+  robot.respond /cash (left|on hand):? (.+)$/i, (msg) ->
     amount = cash.set_on_hand msg.match[3]
     msg.send "Ok, cash on hand is #{optf.toDollars(amount)}"

-  robot.respond /cash burn( rate)?( is)? (.+)$/i, (msg) ->
+  robot.respond /cash burn( rate)?:? (.+)$/i, (msg) ->
     amount = cash.set_burn_rate msg.match[3]
     msg.send "Ok, our burn rate is #{optf.toDollars(amount)} per month"


Make level in toppanel a link

Make level in toppanel a link

--- client/components/TopPanel.coffee
+++ client/components/TopPanel.coffee
@@ -171,7 +171,7 @@

                                 {getClassStartingFromJuly(@props.myUser.graduateYear)}

-                                {stripLabel(@props.myUser.level.current)}
+                                <Link to={"/material/" + @props.myUser.level.current}>{stripLabel(@props.myUser.level.current)}</Link>

                                 {@props.myUser.rating}
                                 {" / "}


IsUp now requires https

IsUp now requires https

--- src/scripts/isup.coffee
+++ src/scripts/isup.coffee
@@ -19,7 +19,7 @@
       msg.send domain

 isUp = (msg, domain, cb) ->
-  msg.http("http://isitup.org/#{domain}.json")
+  msg.http("https://isitup.org/#{domain}.json")
     .header('User-Agent', 'Hubot')
     .get() (err, res, body) ->
       response = JSON.parse(body)


add return when api url is not set in time script

--- src/scripts/time.coffee
+++ src/scripts/time.coffee
@@ -31,6 +31,7 @@
       return
     unless process.env.HUBOT_WWO_API_URL
       msg.send 'Please, set HUBOT_WWO_API_URL environment variable'
+      return
     msg.http(process.env.HUBOT_WWO_API_URL)
       .query({
         q: msg.match[1]


Fix failing tests

Fix failing tests

--- src/set/set_sort.coffee
+++ src/set/set_sort.coffee
@@ -73,14 +73,14 @@
     @set('length', @_storage.length)
     @fire('itemsWereRemoved', removedItems, removedIndexes)

-  toArray: -> @_storage.slice()
+  toArray: -> @get('_storage').slice()

   toArrayOfKeys: ->
-    for item in @_storage
+    for item in @get('_storage')
       item.get(@key)

   forEach: (iterator, ctx) ->
-    iterator.call(ctx, e, i, this) for e, i in @_storage.slice()
+    iterator.call(ctx, e, i, this) for e, i in @get('_storage').slice()
     return

   find: (block) ->


Render CourseRegistration, not NewCourse

--- coach/src/concept-coach/base.cjsx
+++ coach/src/concept-coach/base.cjsx
@@ -8,7 +8,7 @@

 {Task} = require '../task'
 navigation = {Navigation} = require '../navigation'
-NewCourseRegistration = require '../course/new-registration'
+CourseRegistration = require '../course/registration'
 ErrorNotification = require './error-notification'
 SafariWarning = require './safari-warning'
 AccountsIframe = require '../user/accounts-iframe'
@@ -168,9 +168,9 @@
       when 'login'
         <LoginGateway />
       when 'registration'
-        <NewCourseRegistration {...@props} />
+        <CourseRegistration {...@props} />
       when 'second-semester-registration'
-        <NewCourseRegistration {...@props} secondSemester=true />
+        <CourseRegistration {...@props} secondSemester=true />
       when 'task'
         <Task {...@props} key='task'/>
       when 'progress'


Sync timestamp with server

Sync timestamp with server

--- demo/server/main.coffee
+++ demo/server/main.coffee
@@ -45,11 +45,12 @@
       visGraph.edges = (value for key, value of edges)
       visGraph.nodes = (value for key, value of nodes)

-      return visGraph
+      return {updatedAt: +new Date, data: visGraph}

     createNode: (form) ->
       check form, Object
-      n = db.nodes({description: form.description, name: form.name, createdAt: +new Date, updatedAt: +new Date}).replaceLabels([form.label]).get()
+      updatedAt = +new Date
+      n = db.nodes({description: form.description, name: form.name, createdAt: updatedAt, updatedAt}).replaceLabels([form.label]).get()
       n.label = n.name
       n.group = n.labels[0]
       n
@@ -57,7 +58,8 @@
     updateNode: (form) ->
       check form, Object
       form.id = parseInt form.id
-      n = db.nodes(form.id).setProperties({description: form.description, name: form.name, updatedAt: +new Date}).replaceLabels([form.label]).get()
+      updatedAt = +new Date
+      n = db.nodes(form.id).setProperties({description: form.description, name: form.name, updatedAt}).replaceLabels([form.label]).get()
       n.label = n.name
       n.group = n.labels[0]
       n
@@ -70,11 +72,12 @@
       First we set node to removed state, so all other clients will remove that node on long-polling
       After 30 seconds we will get rid of the node from Neo4j, if it still exists
       ###
+      updatedAt = +new Date
       n = db.nodes(id)
       unless n.property 'removed'
         n.setProperties
           removed: true
-          updatedAt: +new Date
+          updatedAt: updatedAt

         Meteor.setTimeout ->
           n = db.nodes(id)
@@ -87,10 +90,12 @@
       form.to = parseInt form.to
       form.from = parseInt form.from

+      updatedAt = +new Date
+
       n1 = db.nodes(form.from)
       n2 = db.nodes(form.to)
-      n1.setProperty 'updatedAt', +new Date
-      n2.setProperty 'updatedAt', +new Date
+      n1.setProperty {updatedAt}
+      n2.setProperty {updatedAt}

       r = n1.to(n2, form.type, {description: form.description}).get()
       r.from    = r.start
@@ -111,10 +116,11 @@
       If this relationship already marked as removed, then it changed by someone else
       We will just wait for long-polling updates on client
       ###
+      updatedAt = +new Date
       unless oldRel.property 'removed'
         oldRel.setProperties
           removed: true
-          updatedAt: +new Date
+          updatedAt: updatedAt

         Meteor.setTimeout ->
           r = db.getRelation(form.id)
@@ -123,8 +129,8 @@

         n1 = db.nodes(form.from)
         n2 = db.nodes(form.to)
-        n1.setProperty 'updatedAt', +new Date
-        n2.setProperty 'updatedAt', +new Date
+        n1.setProperty {updatedAt}
+        n2.setProperty {updatedAt}

         r = n1.to(n2, form.type, {description: form.description}).get()
         r.from    = r.start
@@ -142,17 +148,24 @@

       ###
       First we set relationship to removed state, so all other clients will remove that relationship on long-polling
-      After 30 seconds we will get rid of the relationship from Neo4j, if it still exists
+      After 15 seconds we will get rid of the relationship from Neo4j, if it still exists
       ###
+      updatedAt = +new Date
       r = db.getRelation(id)
+      _r = r.get()
+      n1 = db.nodes(_r.start)
+      n2 = db.nodes(_r.end)
+      n1.setProperty {updatedAt}
+      n2.setProperty {updatedAt}
+
       unless r.property 'removed'
         r.setProperties
           removed: true
-          updatedAt: +new Date
+          updatedAt: updatedAt

         Meteor.setTimeout ->
           r = db.getRelation(id)
           r.delete() if r?.get?()
-        , 30000
+        , 15000
       true


Update nav

Update nav

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -94,11 +94,11 @@
     twitter_follow: "Seguir"
     teachers: "Maestros"
     careers: "Carreras"
-#    facebook: "Facebook"
-#    twitter: "Twitter"
-#    create_a_class: "Create a Class"
-#    other: "Other"
-#    learn_to_code: "Learn to Code!"
+    facebook: "Facebook"
+    twitter: "Twitter"
+    create_a_class: "Crear una Clase"
+    other: "Otro"
+    learn_to_code: "¡Aprender a programar!"

   modal:
     close: "Cerrar"


Prevent "Incorrect IPN secret" logs from showing up if the secret is undefined

--- bot.iced
+++ bot.iced
@@ -386,7 +386,9 @@
 	res.end()

 	# Check if valid IPN callback
-	unless ipnParams.ipn_secret is moolah.ipn_secret then return console.error "Incorrect IPN secret: #{ipnParams.ipn_secret}"
+	unless ipnParams.ipn_secret is moolah.ipn_secret
+		return if ipnParams.ipn_secret is undefined
+		return console.error "Incorrect IPN secret: #{ipnParams.ipn_secret}"
 	# Credit the user / or cancel the transaction
 	await Users_collection.findOne {lastAddFundTx: ipnParams.tx}, defer(err, user)
 	if err then return console.error err


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -65,6 +65,7 @@
         .attr("text-anchor", "middle")
         .attr("alignment-baseline", "central")
         .attr('font-size', radiusi + 'px')
+        .attr("text-shadow", "2px 2px #222222")
         .text(sum)
       arcs.append("svg:text").attr("transform", (d, i) ->
         #procent_val = Math.round(data[i].value/sum * 100)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -76,7 +76,7 @@
             labelGroup = svg.append('g').attr('class', 'labels')
         totalLabel = svg.select('g.totalLabel')
         if totalLabel.empty()
-            totalLabel= svg.append('g').attr('class', 'totalLabel')
+            totalLabel = svg.append('g').attr('class', 'totalLabel')

         path = pathGroup.selectAll('path.pie').data(piedata)
         path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
@@ -255,12 +255,12 @@
         for pd in dataSet
             totaltickets += pd.value
         #console.log totaltickets
-        totalLabel.enter()
+        totalLabel#.enter()
             .append('text')
             .text(totalTickets)
             .attr('text-anchor', 'middle')
             .attr("filter","url(#dropshadow)")
-        totalLabel.exit().remove()
+        #totalLabel.exit().remove()

         oldPieData = piedata
         return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -36,7 +36,7 @@
         .attr("width", width)
         .attr("height", height)
       .append("svg:g")
-        .attr("transform", "translate(" + radiuso + "," + radiuso + ")")
+        .attr("transform", "translate(" + width/2 + "," + height/2 + ")")

     arc = d3.svg.arc()
       .outerRadius(radiuso)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -54,7 +54,7 @@
     sum=0
     for val in data
       sum += val.value
-    textRadiusInc = Math.Round(sum / data.count)
+#    textRadiusInc = Math.Round(sum / data.count)
     if !sum
       arcs.append("svg:text")
         .attr('fill', "#fff")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -210,11 +210,6 @@

         $(@node).children("svg").remove();

-        #svg = d3.select(@node).append("svg:svg")
-        #    .attr("width", width)
-        #    .attr("height", height)
-        #    .append("svg:g")
-        #    .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
         svg = d3.select(@node).append("svg")
             .attr("width", width)
             .attr("height", height)
@@ -230,17 +225,9 @@
           .innerRadius(radiusi)

         piedata = pie(data[0].label)
-        console.log("update pie", piedata)
-
-        #arcs = svg.selectAll("g.slice")
-        #    .data(pie)
-        #    .enter().append("svg:g").attr("class", "slice")
-
-        #arcs.append("svg:path").attr("fill", (d, i) -> color i)
-        #    .attr("fill-opacity", 0.4).attr("d", arc)
+        #console.log("update pie", piedata)

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

         #create a marker element if it doesn't already exist
         defs = svg.select("defs")
@@ -276,27 +263,10 @@

         path = pathGroup.selectAll("path.pie")
             .data(piedata)
-        console.log("path",path.empty())
-
-        #path.enter().append("path")
-        #    .attr("class", "pie")
-        #    .attr("fill",(d, i) -> return color i)
-
-        path = pathGroup.selectAll("path.pie")
-            .data(piedata)
-            .enter().append("g").attr("class","slice")
-
-        path.append("path")
-            .attr("fill",(d, i) -> return color i).attr("d",arc)
-
-        #arcs = svg.selectAll("g.slice")
-        #    .data(pie)
-        #    .enter().append("svg:g").attr("class", "slice")
-
-        #arcs.append("svg:path").attr("fill", (d, i) -> color i)
-        #    .attr("fill-opacity", 0.4).attr("d", arc)
-
-
+
+        path.enter().append("path")
+            .attr("class", "pie")
+            .attr("fill",(d, i) -> return color i)

         #path.transition()
         #    .duration(1500)
@@ -308,29 +278,29 @@
         #    .attrTween("d", @removePieTween)
         #    .remove();

-        #path.transition()
-        #    .duration(1500)
-        #    #.attrTween("d", pieTween)
-        #    .attrTween("d", (d,i) ->
-        #        console.log("d",d)
-        #        console.log("i",i)
-        #        #i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-        #        i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: 90, endAngle: 135})
-        #        return (t) ->
-        #            b = i(t)
-        #            return arc(b)
-        #    )
-
-        #path.exit()
-        #    .transition()
-        #    .duration(300)
-        #    .attrTween("d", (d,i) ->
-        #        i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
-        #        return (t) ->
-        #            b = i(t)
-        #            return arc(b)
-        #    )
-        #    .remove()
+        path.transition()
+            .duration(1500)
+            #.attrTween("d", pieTween)
+            .attrTween("d", (d,i) ->
+                console.log("d",d)
+                console.log("i",i)
+                i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
+                return (t) ->
+                    b = i(t)
+                    return arc(b)
+            )
+
+        path.exit()
+            .transition()
+            .duration(300)
+            .attrTween("d", (d,i) ->
+                i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
+                return (t) ->
+                    b = i(t)
+                    return arc(b)
+            )
+            .remove()
+
         labels = labelGroup.selectAll("text")
             .data(piedata.sort((p1,p2) -> return p1.startAngle - p2.startAngle))
         labels.enter()


Agent pie: edit pointer transition

Agent pie: edit pointer transition

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -321,6 +321,7 @@
         #    )
         #    #.style('opacity', 1)
         pointers.transition().duration(2000).attr('d', (d) ->
+                console.log('d',d)
 		        if d.cx > d.l
 			        'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
 		        else


Implement members listing.

Implement members listing.

--- routes/api/v1/members.coffee
+++ routes/api/v1/members.coffee
@@ -13,11 +13,29 @@
 #
 #    You should have received a copy of the GNU General Public License
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ObjectId = require('mongodb').ObjectID;
+
 module.exports = (app, db) ->
   # GET /api/v1/members - Retrieve all members for a particular session.
   # (Defaults to current session.)
   app.get '/api/v1/members', (req, res) ->
-    res.send(501)
+    db.collection("sessions").findOne {current: true}, (err, currentSession) ->
+      selectedSessionIdStr = req.query.sessionId || currentSession._id.toString()
+      selectedSessionId = new ObjectId(selectedSessionIdStr)
+
+      db.collection("members").find({sessions: selectedSessionId}).toArray (err, results) ->
+        if err
+          errorId = Math.random().toString(36).substring(7)
+          console.error("Error " + errorId + ": " + err)
+
+          res.json
+            id: errorId,
+            error: err
+          , 500
+
+          return
+
+        res.json(results)

   # GET /api/v1/member/:id - Retrieve all information on a particular member.
   app.get '/api/v1/member/:id', (req, res) ->


[a11y] Fixes Usage Rights Warning for screenreader

fixes CNVS-18372

Test Plan:
   - Enable Usage Rights
   - Using a screenreader go to new files
   - Navigate to a file that has been uploaded without usage rights
     information.  It will have the warning icon.
   - The screenreader should read out the warning.

Change-Id: I5828afb5a62fd2fbbf87e6f0265fe3415c2f36a5
Reviewed-on: https://gerrit.instructure.com/48472
Tested-by: Jenkins
Reviewed-by: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>

--- app/coffeescripts/react_files/components/UsageRightsIndicator.coffee
+++ app/coffeescripts/react_files/components/UsageRightsIndicator.coffee
@@ -11,6 +11,8 @@

   UsageRightsIndicator = React.createClass
     displayName: 'UsageRightsIndicator'
+
+    warningMessage: I18n.t('Before publishing this file, you must specify usage rights.')

     propTypes:
       model: customPropTypes.filesystemObject.isRequired
@@ -31,9 +33,11 @@
           button {
               className: 'UsageRightsIndicator__openModal btn-link'
               onClick: @handleClick
-              title: I18n.t('Before publishing this file, you must specify usage rights.')
+              title: @warningMessage
               'data-tooltip': 'top'
             },
+              span {className: 'screenreader-only'},
+                @warningMessage
               i {className: 'UsageRightsIndicator__warning icon-warning'}
         else
           null


ElasticSearch host.

ElasticSearch host.

--- app/server/config.coffee
+++ app/server/config.coffee
@@ -12,5 +12,5 @@
     else
       "http://#{process.env.SUBDOMAIN}.jit.su/"
   elasticSearch:
-    host: ''
+    host: 'https://101e695cd8f4b826000.qbox.io'
   version: require('../../package.json')['version']


Add searchSongsByQuery to server.Api.

Add searchSongsByQuery to server.Api.

--- app/server/js/api.coffee
+++ app/server/js/api.coffee
@@ -35,6 +35,10 @@
     @route api.songs.byUrl
       .get (req) ->
         elastic.getSongsByUrl req.params.urlArtist, req.params.urlName
+
+    @route api.songs.search
+      .get (req) ->
+        elastic.searchSongsByQuery req.query.query

     @route api.clientErrors
       .post (req) ->


Skip creating group VM for now.

Skip creating group VM for now.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -282,24 +282,24 @@
         if err then handleError err
         else user.update { $set: { uid } }, handleError

-    JGroup.on 'GroupCreated', ({group, creator})->
-      group.fetchBundle (err, bundle)->
-        console.log err, bundle
-        if err then handleError err
-        else if bundle and bundle.sharedVM
-          creator.fetchUser (err, user)->
-            if err then handleError err
-            else
-              addVm {
-                user
-                type    : 'group'
-                target  : group
-                planCode  : 'free'
-                planOwner : "group_#{group._id}"
-                sudo    : yes
-                name    : group.slug
-                groups  : wrapGroup group
-              }
+    # JGroup.on 'GroupCreated', ({group, creator})->
+    #   group.fetchBundle (err, bundle)->
+    #     console.log err, bundle
+    #     if err then handleError err
+    #     else if bundle and bundle.sharedVM
+    #       creator.fetchUser (err, user)->
+    #         if err then handleError err
+    #         else
+    #           addVm {
+    #             user
+    #             type    : 'group'
+    #             target  : group
+    #             planCode  : 'free'
+    #             planOwner : "group_#{group._id}"
+    #             sudo    : yes
+    #             name    : group.slug
+    #             groups  : wrapGroup group
+    #           }

     JGroup.on 'GroupDestroyed', (group)->
       group.fetchVms (err, vms)->