Possibility to set language from url parameter

--- server-src/dev.coffee
+++ server-src/dev.coffee
@@ -38,7 +38,16 @@
 staticFileHelper = (fpath) ->
     config.get('static_path') + fpath

-get_language = (host) ->
+get_language = (req) ->
+    get_language_from_parameter(req.query) || get_language_from_host(req.hostname)
+
+get_language_from_parameter = (query) ->
+    if query.lang?.match /^(fi|en|sv)$/
+        query.lang
+    else
+        null
+
+get_language_from_host = (host) ->
     if host.match /^servicemap\./
         'en'
     else if host.match /^servicekarta\./
@@ -62,8 +71,7 @@
         if req.query.treenode?
             fullUrl = req.originalUrl.replace(/treenode/g, 'service_node');
             res.redirect 301, fullUrl
-        host = req.hostname
-        config.default_language = get_language host
+        config.default_language = get_language req
         config.is_embedded = options.embedded

         client_config = config.util.toObject()


payment: buy modal should just accept a workflow

This way, we can just pass the options directly to the Payment Workflow, rather than needing to pipeline the options around

--- client/app/MainApp/payment/buymodal.coffee
+++ client/app/MainApp/payment/buymodal.coffee
@@ -9,7 +9,6 @@
     super options, data

   viewAppended: ->
-    { productForm, confirmForm } = @getOptions()
-    workflow = new PaymentWorkflow { productForm, confirmForm }
+    { workflow } = @getOptions()
     @addSubView workflow
     @forwardEvent workflow, 'PaymentConfirmed'


appscontroller: make sure callback is called in any case

--- client/app/lib/kodingappscontroller.coffee
+++ client/app/lib/kodingappscontroller.coffee
@@ -61,7 +61,7 @@
       @appendHeadElement 'script', \
         { app: app, url:app.script, identifier:app.identifier, force: yes }, callback

-    return
+    return callback null, { app }

   @unloadAppScript = (app, callback = kd.noop) ->


WebtermView: cosmetic updates

WebtermView: cosmetic updates

--- client/app/lib/terminal/webtermview.coffee
+++ client/app/lib/terminal/webtermview.coffee
@@ -1,14 +1,15 @@
-kd = require 'kd'
+kd                   = require 'kd'
+globals              = require 'globals'
+KDCustomScrollView   = kd.CustomScrollView
+KDNotificationView   = kd.NotificationView
 KDButtonViewWithMenu = kd.ButtonViewWithMenu
-KDCustomScrollView = kd.CustomScrollView
-KDNotificationView = kd.NotificationView
-Machine = require '../providers/machine'
-Terminal = require './terminal'
-TerminalWrapper = require './terminalwrapper'
-WebTermMessagePane = require './webtermmessagepane'
-WebtermSettingsView = require './webtermsettingsview'
-settings = require './settings'
-globals = require 'globals'
+
+Machine              = require '../providers/machine'
+settings             = require './settings'
+Terminal             = require './terminal'
+TerminalWrapper      = require './terminalwrapper'
+WebTermMessagePane   = require './webtermmessagepane'
+WebtermSettingsView  = require './webtermsettingsview'

 module.exports = class WebTermView extends KDCustomScrollView
@@ -47,8 +48,8 @@
         style     : 'editor-advanced-settings-menu'
         icon      : yes
         iconOnly  : yes
-        iconClass : "cog"
-        type      : "contextmenu"
+        iconClass : 'cog'
+        type      : 'contextmenu'
         delegate  : this
         itemClass : WebtermSettingsView
         click     : (pubInst, event)-> @contextMenu event
@@ -66,12 +67,12 @@

     @listenWindowResize()

-    @on "ReceivedClickElsewhere", =>
+    @on 'ReceivedClickElsewhere', =>
       @setFocus no
       kd.getSingleton('windowController').removeLayer this

-    @getElement().addEventListener "mousedown", (event) =>
+    @getElement().addEventListener 'mousedown', (event) =>
       @terminal.mousedownHappened = yes
     , yes

@@ -91,13 +92,13 @@
         # If machine is stopped we need to invalidate current sessions
         # user can decide to create a new one or destroy this one.
         if event.status is Stopped
-          @messagePane.handleError message: "ErrNoSession"
+          @messagePane.handleError message: 'ErrNoSession'

     # Listen for resize events to show an error after a resize has
     # finished. This provides same UX as a stopped machine (above).
     computeController.on "resize-#{machineId}", (event) =>
       if event.percentage is 100
-        @messagePane.handleError message: "ErrNoSession"
+        @messagePane.handleError message: 'ErrNoSession'

     @setKeyView()

@@ -109,7 +110,7 @@
     @messagePane.on 'DiscardSession', @lazyBound 'emit', 'WebTerm.terminated'

-  generateOptions:->
+  generateOptions: ->

     delegateOptions = @getDelegate().getOptions()
     myOptions       = @getOptions()
@@ -131,7 +132,7 @@
   getKite: -> @getMachine().getBaseKite()

-  webtermConnect: (mode)->
+  webtermConnect: (mode) ->

     mode ?= if @_lastRemote? then 'resume' else 'create'

@@ -146,21 +147,21 @@

     kite.init().then =>

-      kite.webtermConnect(options).then (remote)=>
+      kite.webtermConnect(options).then (remote) =>

         return  unless remote?

         @_lastRemote = remote

-        @setOption "session", remote.session
-
-        @terminal.eventHandler = (data)=>
-          @emit "WebTermEvent", data
+        @setOption 'session', remote.session
+
+        @terminal.eventHandler = (data) =>
+          @emit 'WebTermEvent', data

         @terminal.server = remote
         @sessionId = remote.session

-        @emit "WebTermConnected", remote
+        @emit 'WebTermConnected', remote

         @_triedToReconnect = no

@@ -211,8 +212,8 @@

     kd.utils.killRepeat @checker

-    unless @status is "fail"
-      @emit "TerminalClosed",
+    unless @status is 'fail'
+      @emit 'TerminalClosed',
         machineId : @getMachine().uid
         sessionId : @getOptions().session

@@ -252,7 +253,7 @@
     kd.getSingleton('windowController').addLayer this
     @setFocus()
     @emit 'KeyViewIsSet'
-    @once "ReceivedClickElsewhere", => @setFocus no
+    @once 'ReceivedClickElsewhere', => @setFocus no

   setFocus: (state = yes) ->
     @focused = state
@@ -293,7 +294,7 @@
   listenFullscreen: (event)->
     requestFullscreen = (event.metaKey or event.ctrlKey) and event.keyCode is 13
     if requestFullscreen
-      mainView = kd.getSingleton "mainView"
+      mainView = kd.getSingleton 'mainView'
       mainView.toggleFullscreen()
       event.preventDefault()


removed artifacts from failed attempts at reactivity

--- client/coffeescript/templates/group.coffee
+++ client/coffeescript/templates/group.coffee
@@ -1,5 +1,4 @@
 _.extend( Template.group,
-  #id: -> {id: Meteor.uuid()}
   name: -> @name
   show_replies: ->
     @show ?= true


Tests: Teams: Added searchChannels test.

--- client/test/lib/teams/teamschannels.coffee
+++ client/test/lib/teams/teamschannels.coffee
@@ -2,6 +2,7 @@
 utils    = require '../utils/utils.js'
 teamsHelpers = require '../helpers/teamshelpers.js'

+sidebarSectionsSelector = '.activity-sidebar .SidebarSections'

 module.exports =

@@ -27,3 +28,19 @@
     teamsHelpers.createChannelsAndCheckList(browser, user)
     browser.end()

+
+  searchChannels: (browser) ->
+
+    channelListModal    = '.ChannelList-Modal'
+    searchInputSelector = "#{channelListModal} .ChannelList-searchInput"
+    threadsContainer    = "#{channelListModal} .SidebarModalThreads"
+
+    user         = teamsHelpers.loginTeam(browser)
+    channelNames = teamsHelpers.createChannelsAndCheckList(browser, user)
+
+    browser
+      .waitForElementVisible  searchInputSelector, 20000
+      .setValue               searchInputSelector, channelNames[0].slice 0, 8
+      .assert.containsText    threadsContainer, channelNames[0]
+      .end()
+


Minor fix to previous refactoring

Minor fix to previous refactoring

--- h2o-web/src/main/steam/scripts/create-model-dialog.coffee
+++ h2o-web/src/main/steam/scripts/create-model-dialog.coffee
@@ -112,6 +112,8 @@
             when 'list'
               if value.length
                 parameters[control.name] = "[#{value.join ','}]"
+            else
+              parameters[control.name] = value

     _.requestModelBuild _algorithm.data.key, parameters, (error, result) ->
       if error


Make sure iterator binding doesn't build arrays implicity

--- src/view/bindings/iterator_binding.coffee
+++ src/view/bindings/iterator_binding.coffee
@@ -56,6 +56,7 @@
           subview.unset(@attributeName)
           subview.die()
           break
+    return

   die: ->
     @prototypeNode = null


restore to original key scope if one exists after answer table's key scope is enabled and disabled

--- src/components/question/answers-table.cjsx
+++ src/components/question/answers-table.cjsx
@@ -1,6 +1,7 @@
 React = require 'react'
 _ = require 'underscore'

+keymaster = require 'keymaster'
 keysHelper = require '../../helpers/keys'

 KEYS =
@@ -45,7 +46,31 @@
     keySet: 'multiple-choice'

   getInitialState: ->
+    originalKeyScope = @getOriginalKeyScope()
+
     answer_id: null
+    originalKeyScope: originalKeyScope
+
+  componentWillReceiveProps: (nextProps) ->
+    originalKeyScope = @getOriginalKeyScope(nextProps)
+    @setState({originalKeyScope}) if originalKeyScope?
+
+    @resetToOriginalKeyScope() if not _.isNull(@props.keySet) and _.isNull(nextProps.keySet)
+
+  componentWillUnmount: ->
+    @resetToOriginalKeyScope()
+
+  getOriginalKeyScope: (props) ->
+    props ?= @props
+
+    originalKeyScope = keymaster.getScope()
+    originalKeyScope if props.keySet isnt originalKeyScope and originalKeyScope isnt @state?.originalKeyScope
+
+  resetToOriginalKeyScope: ->
+    {originalKeyScope} = @state
+    keymaster.setScope(originalKeyScope) if originalKeyScope?
+
+    @setState(originalKeyScope: undefined)

   onChangeAnswer: (answer, changeEvent) ->
     if @props.onChange?


name input behavior

name input behavior

--- lib/assets/javascripts/code_sync/editor/name_input.coffee
+++ lib/assets/javascripts/code_sync/editor/name_input.coffee
@@ -39,5 +39,6 @@
   toggle: ()->
     if @getValue()?.length is 0
       @editor.setDefaultExtension()
+      @editor.currentName = @editor.currentPath = undefined

     @$el.toggle()


adding syntax highlighting for F7 and F# files

--- grammars/fstar.cson
+++ grammars/fstar.cson
@@ -2,7 +2,7 @@

 'name': 'F*'

-'fileTypes': ['fst', 'fsi']
+'fileTypes': ['fst', 'fsi', 'fs', 'fs7', 'fsti']

 'editor':
   'commentStart' : '\\(\\*'


Updated grammar.json

Updated grammar.json

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -383,6 +383,21 @@
       }
       {
         include: "#expression-operator-special"
+      }
+      {
+        match: "[.]([\\p{L}\\p{Nl}$_][\\p{L}\\p{Nl}$\\p{Mn}\\p{Mc}\\p{Nd}\\p{Pc}\\x{200C}\\x{200D}]*)\\b"
+      }
+      {
+        captures:
+          "1":
+            name: "entity.name.type"
+          "2":
+            name: "entity.name.type"
+        match: "\\b(out)[.](global)\\b"
+      }
+      {
+        match: "\\b(input|state|JSON|out)\\b"
+        name: "entity.name.type"
       }
       {
         include: "#expression-special-class"
@@ -484,8 +499,8 @@
         name: "keyword.operator.js"
     match: "(\\s+|\\b)(>=|>>|>)(\\s+|\\b)"
   "expression-operator-special":
-    match: "(?<!\\.|\\$)\\b(delete|in|instanceof|new|typeof\\s*|void|with)\\b(?!\\$)"
-    name: "keyword.operator.js"
+    match: "(?<!\\.|\\$)\\b(delete|in|instanceof|new|typeof\\s*|void|with|from|to|step)\\b(?!\\$)"
+    name: "keyword"
   "expression-operator-unary":
     match: "\\b(!|\\+\\+)\\b|\\b\\-\\-(?<!\\s)"
     name: "keyword.operator.js"
@@ -627,6 +642,25 @@
         ]
       }
       {
+        applyEndPatternLast: 1
+        begin: "\\["
+        beginCaptures:
+          "0":
+            name: "punctuation.section.scope.begin.js"
+        end: "\\]"
+        endCaptures:
+          "0":
+            name: "punctuation.section.scope.end.js"
+        patterns: [
+          {
+            include: "#js"
+          }
+        ]
+      }
+      {
+        match: "[.]([\\p{L}\\p{Nl}$_][\\p{L}\\p{Nl}$\\p{Mn}\\p{Mc}\\p{Nd}\\p{Pc}\\x{200C}\\x{200D}]*)\\b"
+      }
+      {
         include: "#expression"
       }
       {
@@ -658,7 +692,15 @@
         name: "entity.other.inherited-class.js"
       }
       {
-        match: "\\b(console|JSON)\\b"
+        captures:
+          "1":
+            name: "entity.name.type"
+          "2":
+            name: "entity.name.type"
+        match: "\\b(require)[.](resolve)\\b"
+      }
+      {
+        match: "\\b(console|JSON|event|window|setTimeout|setInterval|require)\\b"
         name: "entity.name.type"
       }
       {


Removed hasOne test

Removed hasOne test

--- test/generators/relational/has_one.coffee
+++ test/generators/relational/has_one.coffee
@@ -104,30 +104,31 @@
             assert.equal(flat_id, flat.id, 'Loaded model is correct')
             done()

-    it 'Can create a related model by id (hasOne)', (done) ->
-      Reverse.findOne (err, test_model) ->
-        assert.ok(!err, "No errors: #{err}")
-        assert.ok(test_model, 'found model')
-
-        reverse_id = test_model.id
-        new_model = new Owner({reverse_id: reverse_id})
-
-        queue = new Queue(1)
-        if not new_model.get('reverse').isLoaded()
-          queue.defer (callback) -> new_model.get('reverse').fetch bbCallback callback
-
-        queue.defer (callback) -> new_model.save {}, bbCallback callback
-
-        queue.defer (callback) ->
-          new_model.get 'reverse', (err, reverse) ->
-            assert.ok(!err, "No errors: #{err}")
-            assert.ok(reverse, 'found related model')
-            assert.equal(reverse_id, reverse.id, 'Loaded model is correct')
-            callback()
-
-        queue.await (err) ->
-          assert.ok(!err, "No errors: #{err}")
-          done()
+    # TODO: does this case make sense given the location of the foreign keys?
+    # it 'Can create a related model by id (hasOne)', (done) ->
+    #   Reverse.findOne (err, test_model) ->
+    #     assert.ok(!err, "No errors: #{err}")
+    #     assert.ok(test_model, 'found model')
+
+    #     reverse_id = test_model.id
+    #     new_model = new Owner({reverse_id: reverse_id})
+
+    #     queue = new Queue(1)
+    #     if not new_model.get('reverse').isLoaded()
+    #       queue.defer (callback) -> new_model.get('reverse').fetch bbCallback callback
+
+    #     queue.defer (callback) -> new_model.save {}, bbCallback callback
+
+    #     queue.defer (callback) ->
+    #       new_model.get 'reverse', (err, reverse) ->
+    #         assert.ok(!err, "No errors: #{err}")
+    #         assert.ok(reverse, 'found related model')
+    #         assert.equal(reverse_id, reverse.id, 'Loaded model is correct')
+    #         callback()
+
+    #     queue.await (err) ->
+    #       assert.ok(!err, "No errors: #{err}")
+    #       done()

     it 'Handles a get query for a belongsTo relation', (done) ->
       Owner.findOne (err, test_model) ->


more cheering

more cheering

--- scripts/football.coffee
+++ scripts/football.coffee
@@ -33,3 +33,6 @@

   robot.respond /cheer for the packers/i, (msg) ->
     msg.send "Go Pack go! Go Pack go!"
+
+  robot.respond /cheer for the patriots/i, (msg) ->
+    msg.send "Don't throw an interception!"


Replaced encodeURI with encodeURIComponent; added class methods; and added support for callbacks

--- src/paste.coffee
+++ src/paste.coffee
@@ -19,46 +19,70 @@
 DEF_SERVER_ERROR = "We are unable to process your request at this time due to a server error. Please try again later."
 DEF_CREDIT = "Generated by ClaudeBot's hubot-paste"

-class Paste
-    constructor: (@msg) ->
+class IPaste
+    constructor: (@robot, @msg = false) ->

-    create: (err, link) ->
+    create: (err, link, callback = false) ->
         if err
-            @msg.reply DEF_SERVER_ERROR
-            return @msg.robot.logger.error err
-        @msg.reply link
+            @msg.reply DEF_SERVER_ERROR if @msg
+            return @robot.logger.error err
+        if @msg
+            @msg.reply link
+        if callback
+            callback link

-class Dpaste extends Paste
-    create: (text, expire = 1) ->
-        data = "content=#{encodeURI(text)}&expiry_days=#{expire}"
-        data += "&poster=#{@msg.robot.name}&title=#{encodeURI(DEF_CREDIT)}"
+class Dpaste extends IPaste
+    create: (text, expire = 1, callback = false) ->
+        data = "content=#{encodeURIComponent(text)}&expiry_days=#{expire}"
+        data += "&poster=#{@robot.name}&title=#{encodeURIComponent(DEF_CREDIT)}"

-        @msg.http(DPASTE_API_URL)
+        @robot.http(DPASTE_API_URL)
             .header("content-type", "application/x-www-form-urlencoded")
             .post(data) (err, res, body) =>
-                @msg.robot.logger.debug res.headers
-                super err, res.headers.location
+                @robot.logger.debug res.headers
+                super err, res.headers.location, callback

-class Pastebin extends Paste
-    create: (text, expire = "1D") ->
-        data = "api_dev_key=#{encodeURI(PASTEBIN_API_KEY)}&api_option=paste"
-        data += "&api_paste_code=#{encodeURI(text)}&api_paste_expire_date=#{expire}"
-        data += "&api_paste_name=#{encodeURI(DEF_CREDIT)}"
+class Pastebin extends IPaste
+    create: (text, expire = "1D", callback = false) ->
+        data = "api_dev_key=#{encodeURIComponent(PASTEBIN_API_KEY)}&api_option=paste"
+        data += "&api_paste_code=#{encodeURIComponent(text)}&api_paste_expire_date=#{expire}"
+        data += "&api_paste_name=#{encodeURIComponent(DEF_CREDIT)}"

-        @msg.http(PASTEBIN_API_URL)
+        @robot.http(PASTEBIN_API_URL)
             .header("content-type", "application/x-www-form-urlencoded")
             .post(data) (err, res, body) =>
-                @msg.robot.logger.debug res.headers
-                super err, body
+                @robot.logger.debug res.headers
+                super err, body, callback
+
+class PasteBot
+    constructor: (@robot) ->
+        @registerListeners()
+
+    registerListeners: ->
+        @robot.respond /dpaste (.+)/i, (msg) =>
+            service = new Dpaste @robot, msg
+            service.create msg.match[1]
+
+        if PASTEBIN_API_KEY?
+            @robot.respond /pastebin (.+)/i, (msg) =>
+                service = new Pastebin @robot, msg
+                service.create msg.match[1]
+        else
+            @robot.logger.warning "Missing PASTEBIN_API_KEY in environment. Pastebin services will not be available."
+
+    dpaste: (text, expire = "1", callback) ->
+        service = new Dpaste @robot
+        try
+            service.create text, expire, callback
+        catch error
+            return @robot.logger.error error
+
+    pastebin: (text, expire = "1D", callback) ->
+        service = new Pastebin @robot
+        try
+            service.create text, expire, callback
+        catch error
+            return @robot.logger.error error

 module.exports = (robot) ->
-    robot.respond /dpaste (.+)/i, (msg) ->
-        service = new Dpaste msg
-        service.create msg.match[1]
-
-    if PASTEBIN_API_KEY?
-        robot.respond /pastebin (.+)/i, (msg) ->
-            service = new Pastebin msg
-            service.create msg.match[1]
-    else
-        robot.logger.warning "Missing PASTEBIN_API_KEY in environment. Pastebin services will not be available."
+    new PasteBot robot


No tile label

No tile label

--- public/app/block.coffee
+++ public/app/block.coffee
@@ -33,11 +33,8 @@
                 bmp = new $e.Bitmap frame.image
                 bmp.sourceRect = frame.rect

-                label = new $e.Text("[#{@data.location.x},#{@data.location.y}](#{x},#{y})")
-
                 tile = new $e.Container()
                 tile.addChild bmp
-                tile.addChild label
                 tile.x = (x * @data.tileset.frames.width)
                 tile.y = (y * @data.tileset.frames.width)


use average when checking for crashed pipeline

--- scripts/pipeline.coffee
+++ scripts/pipeline.coffee
@@ -76,19 +76,21 @@
     # could run scripts with something like this probably:
     # build = spawn '/bin/bash', ['test.sh']

-  # this runs every 10 seconds and demonstrates how to send messages to channels
+  # this starts a new load monitor every 5 minutes
   setInterval () ->
-    killIfContinouslyAbove 300
-  , 3000
+    killIfOnAverageAbove 300
+  , 300000

-  killIfContinouslyAbove = (timeRemaining, timeRunning = 0) ->
+  killIfOnAverageAbove = (timeRemaining, timeRunning = 0, average = 0, iterations = 0) ->
     getLoad "", (load) ->
-      if load.engine.cpu > 90
-        if timeRemaining <= 0
-          robot.adapter.send {room:'test'}, "CPU load has been over 90% for "+timeRunning+" seconds..."
+      average = ( average * iterations + load.engine.cpu ) / ( iterations + 1 )
+      if timeRemaining <= 0
+        robot.adapter.send {room:'test'}, "Average CPU load for the last 5 minutes is: "+average+" %"
+        if average > 70
+          robot.adapter.send {room:'test'}, "CPU load has been on average above 70% for the last 5 minutes..."
           robot.adapter.send {room:'test'}, "TODO: restart Pipeline 2 here if this trigger seems to correlate with Pipeline 2 having crashed"
-        else
-          setTimeout () ->
-            killIfContinouslyAbove timeRemaining - 1, timeRunning + 1
-          , 1000
+      else
+        setTimeout () ->
+          killIfOnAverageAbove timeRemaining - 1, timeRunning + 1, average, iterations + 1
+        , 1000


Remove obsolete deserialization code

Remove obsolete deserialization code

--- lib/archive-editor.coffee
+++ lib/archive-editor.coffee
@@ -59,6 +59,3 @@

   isEqual: (other) ->
     other instanceof ArchiveEditor and @getURI() is other.getURI()
-
-if parseFloat(atom.getVersion()) < 1.7
-  atom.deserializers.add(ArchiveEditor)


CLI: changing the 'publish' command to send the whole package manifest to the server instead of just some fields

--- cli/commands/publish.coffee
+++ cli/commands/publish.coffee
@@ -29,13 +29,9 @@

     sout "» Publishing #{lovelyrc.host}/packages/#{package.name} ".ljust(61)
     hosting.send_package
-      name:         package.name
-      version:      package.version
-      description:  package.description
-      dependencies: package.dependencies
-      license:      package.license
-      build:        read("build/#{package.name}.js")
-      readme:       read('README.md')
+      manifest: read('package.json')
+      build:    read("build/#{package.name}.js")
+      readme:   read('README.md')

     sout "Done\n".green


add decodeURI for url

add decodeURI for url

--- vendor/assets/javascripts/jquery.socialShareButtons.js.coffee
+++ vendor/assets/javascripts/jquery.socialShareButtons.js.coffee
@@ -25,7 +25,7 @@
         callbackCounter: (type, count) ->
         callbackClick: (type) ->
         containerSelector: '.socials-share-buttons-container'
-        url: location.href
+        url: decodeURI(location.href)
         title: $("meta[property='og:title']").attr("content") or document.title
         image: $("meta[property='og:image']").attr("content")
         description: $("meta[name='description']").attr("content")


App index: override entryPoint.slug before remote is ready

This was causing to loading the group page with koding initially until remote
is connected. And some of the calls was going through with client.context.group
as koding

--- client/app/lib/index.coffee
+++ client/app/lib/index.coffee
@@ -40,10 +40,11 @@
     global._kd      = kd
     global._remote  = remote

+  globals.config.entryPoint.slug = globals.currentGroup.slug
+
   remote.once 'ready', ->
     globals.currentGroup           = remote.revive globals.currentGroup
     globals.userAccount            = remote.revive globals.userAccount
-    globals.config.entryPoint.slug = globals.currentGroup.slug

     setupAnalytics()


Support HTML values

Support HTML values

--- src/quill.coffee
+++ src/quill.coffee
@@ -14,6 +14,8 @@
   $quill = @$('.quill')
   $quill.data('quill', @quill)
   value = @data.value
+  if typeof value == 'string'
+    value = @quill.clipboard.convert(value)
   @quill.setContents(value) if value?
   {onRender} = @data.atts
   onRender?.apply(@)


Give some indication that a script listener threw an exception.

--- src/robot.coffee
+++ src/robot.coffee
@@ -59,7 +59,11 @@
   #
   # Returns nothing.
   receive: (message) ->
-    @listeners.forEach (lst) -> lst.call message
+    @listeners.forEach (lst) ->
+      try
+        lst.call message
+      catch ex
+        console.log "error while calling listener: #{ex}"

   # Public: Loads every script in the given path.
   #


Don't collide with self

Don't collide with self

--- src/shape.coffee
+++ src/shape.coffee
@@ -262,6 +262,7 @@
    * @param {Shape} other Shape object to test collision with
   ###
   collidesWith: (other) ->
+    return false if @ == other
     Math.abs(@position.x - other.position.x) < @size.width / 2 + other.size.width / 2 && Math.abs(@position.y - other.position.y) < @size.height / 2 + other.size.height / 2

   tween: (property, target, duration = 500, easing = 'linearNone') ->


No longer exits on client close. Fixes #127

--- src/slack.coffee
+++ src/slack.coffee
@@ -88,13 +88,8 @@
     @emit "connected"

   clientClose: =>
-    @robot.logger.info 'Slack client closed'
-    @client.removeListener 'error', @.error
-    @client.removeListener 'loggedIn', @.loggedIn
-    @client.removeListener 'open', @.open
-    @client.removeListener 'close', @.clientClose
-    @client.removeListener 'message', @.message
-    process.exit 0
+    # Don't actually do anything since we may reconnect in the future
+    @robot.logger.info 'Slack client closed, waiting for reconnect'

   message: (msg) =>
     # Ignore our own messages


Some more revisions to go off of

Some more revisions to go off of

--- src/sound.coffee
+++ src/sound.coffee
@@ -1,3 +1,25 @@
+###
+
+TODO:
+  Replace references to buzz with Kona.Sound
+    Or even find out if you can use @ in the top-
+    level namespace instead of Kona.Sound (would be clean)
+    @get can probably be refactored as well
+
+###
+
+
+
+
+
+
+
+
+
+
+
+
+
 # OGG - Firefox, Chrome, Opera
 # MP3 - IE

@@ -176,79 +198,81 @@

       @getErrorCode = ->
-        if supported and @sound.error
-          return @sound.error.code
-        return 0
+        return (if supported && @sound.error then @sound.error.code else 0)

       @getErrorMessage = ->
         return null if !supported
-          switch @getErrorCode
-            case 1:
-              return 'MEDIA_ERR_ABORTED'
-            case 2:
-              return 'MEDIA_ERR_NETWORK'
-            case 3:
-              return 'MEDIA_ERR_DECODE'
-            case 4:
-              return 'MEDIA_ERR_SRC_NOT_SUPPORTED'
-            default:
-              return null
+        switch @getErrorCode
+          when 1 then 'MEDIA_ERR_ABORTED'
+          when 2 then 'MEDIA_ERR_NETWORK'
+          when 3 then 'MEDIA_ERR_DECODE'
+          when 4 then 'MEDIA_ERR_SRC_NOT_SUPPORTED'
+          else null

       @getStateCode = ->
-        return null if !supported
-        return @sound.readyState
+        return (if supported then @sound.readyState else null)

       @getStateMessage = ->
         return null if !supported
-          switch @getStateCode
-            case 0:
-              return 'HAVE_NOTHING'
-            case 1:
-              return 'HAVE_METADATA'
-            case 2:
-              return 'HAVE_CURRENT_DATA'
-            case 3:
-              return 'HAVE_FUTURE_DATA'
-            case 4:
-              return 'HAVE_ENOUGH_DATA'
-            default:
-              return null
+        switch @getStateCode
+          when 0 then 'HAVE_NOTHING'
+          when 1 then 'HAVE_METADATA'
+          when 2 then 'HAVE_CURRENT_DATA'
+          when 3 then 'HAVE_FUTURE_DATA'
+          when 4 then 'HAVE_ENOUGH_DATA'
+          else null

       @getNetworkStateCode = ->
-        return null if !supported
-        return @sound.networkState
+        return (if supported then @sound.networkState else null)

       @getNetworkStateMessage = ->
         return null if !supported
-          switch @getNetworkStateCode
-            case 0:
-              return 'NETWORK_EMPTY'
-            case 1:
-              return 'NETWORK_IDLE'
-            case 2:
-              return 'NETWORK_LOADING'
-            case 3:
-              return 'NETWORK_NO_SOURCE'
-            default:
-              return null
+        switch @getNetworkStateCode
+          when 0 'NETWORK_EMPTY'
+          when 1 'NETWORK_IDLE'
+          when 2 'NETWORK_LOADING'
+          when 3 'NETWORK_NO_SOURCE'
+          else null

       @set = (key, value) ->
-        return @ if !supported
-
-
-      @sound[key] = value
-        return @
+        return (if supported then @sound[key] = value else @)

       @get = (key) ->
+        # TODO: this can be done better
         return null if !supported
-        return if key then @sound[key] else @sound
+        return if key? then @sound[key] else @sound
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

 # I left this kind of untouched because I wasn't sure of how to deal with line 259
@@ -359,6 +383,21 @@
         }

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
       @fadeIn = (duration, callback) ->
         return @ if !supported
         return @setVolume(0).fadeTo(100, duration, callback)
@@ -369,7 +408,6 @@
         return @fadeTo(0, duration, callback)

-# Check here too
       @fadeWith = (sound, duration) ->
         return @ if !supported
         @fadeOut (duration), ->
@@ -378,7 +416,16 @@
         return @

+
+
+
+
+
+
+
+
 # Check here too
+# TODO: fat arrow
       @whenReady = (func) ->
         return null if !supported
         that = @
@@ -387,6 +434,13 @@
             func.call(that)
         else
           func.call(that)
+
+
+
+
+
+
+

         # privates
 # Another check
@@ -407,51 +461,55 @@

       addSource = (sound, src) ->
-        source = document.createElement( 'source' )
+        source = document.createElement('source')
         source.src = src
-        if buzz.types[getExt(src)]
-          source.type = buzz.types[getExt(src)]
+        if Kona.Sound.types[getExt(src)]?
+          source.type = Kona.Sound.types[getExt(src)]
         sound.appendChild(source)

+
+
+
+
+
+
         # init
-        # So is this whole block of code belong to addSource?
-        # There was already a closed bracket above this to close out the function
-        if supported and src {
-            for var i in buzz.defaults
-              if buzz.defaults.hasOwnProperty(i)
-                options[i] = options[i] || buzz.defaults[i]
-
-            @sound = document.createElement('audio')
-
-            if src instanceof Array
-                for var j in src
-                  if src.hasOwnProperty(j)
-                    addSource(@sound, src[j])
-            else if options.formats.length
-                for var k in options.formats
-                  if(options.formats.hasOwnProperty(k))
-                    addSource(@sound, src + '.' + options.formats[k])
-            else
-              addSource(@sound, src)
-
-            if options.loop
-              @loop()
-
-            if options.autoplay
-              @sound.autoplay = 'autoplay'
-
-            if options.preload === true
-              @sound.preload = 'auto'
-            else if options.preload === false
-              @sound.preload = 'none'
-            else
-              @sound.preload = options.preload
-
-            @setVolume(options.volume)
-
-            buzz.sounds.push(@)
-        }
-    },
+        if supported and src
+          for var i in buzz.defaults
+            if buzz.defaults.hasOwnProperty(i)
+              options[i] = options[i] || buzz.defaults[i]
+
+          @sound = document.createElement('audio')
+
+          if src instanceof Array
+              for var j in src
+                if src.hasOwnProperty(j)
+                  addSource(@sound, src[j])
+          else if options.formats.length
+              for var k in options.formats
+                if(options.formats.hasOwnProperty(k))
+                  addSource(@sound, src + '.' + options.formats[k])
+          else
+            addSource(@sound, src)
+
+          if options.loop
+            @loop()
+
+          if options.autoplay
+            @sound.autoplay = 'autoplay'
+
+          if options.preload === true
+            @sound.preload = 'auto'
+          else if options.preload === false
+            @sound.preload = 'none'
+          else
+            @sound.preload = options.preload
+
+          @setVolume(options.volume)
+
+          buzz.sounds.push(@)
+
+

@@ -481,12 +539,12 @@

       @load = ->
-        fn( 'load' )
+        fn('load')
         return @

       @play = ->
-        fn( 'play' )
+        fn('play')
         return @

@@ -594,19 +652,23 @@
         return @

-      // privates
+      # privates
       fn = ->
         args = argsToArray(null, arguments)
         func = args.shift()
-        for i in sounds
-        #for( var i = 0 i < sounds.length i++ ) {
-          sounds[i][func].apply(sounds[i], args)
-        argsToArray = (array, args) ->
+        for sound in sounds
+          sound[func].apply(sound, args)
+
+
+      argsToArray = (array, args) ->
         return (array instanceof Array) ? array : Array.prototype.slice.call(args)
-    #},
-
-
-    #all: function()
+
+
+
+
+
+
+
     all: ->
       return new buzz.group(buzz.sounds)


Working through some of the gnarlier functions

--- src/sound.coffee
+++ src/sound.coffee
@@ -245,173 +245,117 @@

       @get = (key) ->
-        # TODO: this can be done better
-        return null if !supported
-        return if key? then @sound[key] else @sound
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-# I left this kind of untouched because I wasn't sure of how to deal with line 259
+        if supported
+          return (if key? then @sound[key] else @sound)
+        else
+          return null
+
+
       @bind = (types, func) ->
         return @ if !supported
-        types = types.split( ' ' )
-        that = @,
-        efunc = function( e ) { func.call(that, e) }
-          for( var t = 0 t < types.length t++ ) {
-              type = types[t],
-              idx = type
-              type = idx.split('.')[0]
-              events.push({idx: idx, func: efunc})
-              @sound.addEventListener(type, efunc, true)
+        types = types.split(' ')
+        self = @
+        efunc = (e) -> func.call(self, e)
+
+        for type in types
+          idx = type
+          type = idx.split('.')[0]
+          events.push {
+            idx: idx
+            func: efunc
           }
-          return @
-      }
-
-
-# I need to go over this one too
+          @sound.addEventListener(type, efunc, true)
+
+        return @
+
+
       @unbind = (types) ->
         return @ if !supported
         types = types.split(' ')
-          #for( var t = 0 t < types.length t++ ) {
-           #   var idx = types[ t ],
-            #      type = idx.split( '.' )[ 0 ]
-          for t in types
-            idx = t
-            type = idx.split('.')[0]
-              for( var i = 0 i < events.length i++ ) {
-                #for i in events
-                temp = i
-                  #var namespace = events[ i ].idx.split( '.' )
-                  namespace = temp.idx.split('.')
-                  if (events[i].idx == idx || (namespace[1] && namespace[1] == idx.replace( '.', '' ) ) ) {
-                      @sound.removeEventListener( type, events[ i ].func, true )
-                      // remove event
-                      events.splice(i, 1)
-                  }
-              }
-          }
-          return @
-      }
-
-
-# Do you remove the ( and { from lines 302 and 307?
+
+        for type in types
+          idx = type
+          type = idx.split('.')[0]
+
+          for event, i in events
+            namespace = event.idx.split('.')
+            if event.idx == idx || (namespace[1] && namespace[1] == idx.replace('.', '') )
+              @sound.removeEventListener( type, event.func, true );
+              events.splice(i, 1) # remove event
+
+        return @
+
+
       @bindOnce = (type, func) ->
         return @ if !supported
-        that = @
+        self = @
         eventsOnce[pid++] = false
-        @bind(pid + type, function() {
+        @bind pid + type, ->
           if !eventsOnce[pid]
             eventsOnce[pid] = true
-            func.call(that)
+            func.call(self)
              @unbind(pid + type)
-          })

       @trigger = (types) ->
         return @ if !supported
-        types = types.split( ' ' )
-        for( var t = 0 t < types.length t++ ) {
-            var idx = types[t]
-            for( var i = 0 i < events.length i++ ) {
-                var eventType = events[ i ].idx.split( '.' )
-                if ( events[ i ].idx == idx || ( eventType[ 0 ] && eventType[ 0 ] == idx.replace( '.', '' ) ) ) {
-                  var evt = document.createEvent('HTMLEvents')
-                  evt.initEvent( eventType[ 0 ], false, true )
+        types = types.split(' ')
+
+        for type in types
+          idx = type
+
+          for event in events
+            eventType = event.idx.split('.')
+            if event.idx == idx || ( eventType[0] && eventType[0] == idx.replace('.', '') )
+                  evt = document.createEvent('HTMLEvents')
+                  evt.initEvent(eventType[0], false, true)
                   @sound.dispatchEvent(evt)
-                  }
-              }
-          }
-        return @
-
-
-# Something with fadeTo and doFade
+
+        return @
+
+
       @fadeTo = (to, duration, callback) ->
         return @ if !supported
-        if duration instanceof Function
+
+        if (duration instanceof Function)
           callback = duration
-          duration = buzz.defaults.duration
+          duration = Kona.Sound.defaults.duration
         else
-          duration = duration || buzz.defaults.duration
-        from = @volume,
-        delay = duration / Math.abs(from - to),
-        that = @
+          duration = duration || Kona.Sound.defaults.duration
+
+        from = @volume
+        delay = duration / Math.abs( from - to )
+        self = @
         @play()

-
-        function doFade() {
-          setTimeout( function() {
-          if ( from < to and that.volume < to ) {
-            that.setVolume( that.volume += 1 )
-            doFade()
-          } else if ( from > to and that.volume > to ) {
-              that.setVolume( that.volume -= 1 )
+        doFade = ->
+          setTimeout ->
+              if from < to && self.volume < to
+                self.setVolume(self.volume += 1)
                 doFade()
-          } else if ( callback instanceof Function ) {
-                callback.apply( that )
-                }
-          }, delay )
-          }
-            this.whenReady( function() {
+              else if from > to && self.volume > to
+                self.setVolume(self.volume -= 1)
                 doFade()
-            })
-
-            return this
-        }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+              else if callback instanceof Function
+                callback.apply(self)
+          , delay
+
+        @whenReady -> doFade()
+
+        return @

       @fadeIn = (duration, callback) ->
-        return @ if !supported
-        return @setVolume(0).fadeTo(100, duration, callback)
+        return (if supported then @setVolume(0).fadeTo(100, duration, callback) else @)

       @fadeOut = (duration, callback) ->
-        return @ if !supported
-        return @fadeTo(0, duration, callback)
+        return (if supported then @fadeTo(0, duration, callback) else @)

       @fadeWith = (sound, duration) ->
         return @ if !supported
-        @fadeOut (duration), ->
-          @stop()
+        @fadeOut (duration), -> @stop()
         sound.play().fadeIn(duration)
         return @

@@ -423,37 +367,40 @@

-
-# Check here too
-# TODO: fat arrow
+      # TODO: KONA EVENT
       @whenReady = (func) ->
         return null if !supported
-        that = @
-        if @sound.readyState === 0
+        self = @
+        if @sound.readyState == 0
           @bind ('canplay.buzzwhenready'), ->
-            func.call(that)
+            func.call(self)
         else
-          func.call(that)
-
-
-
-
-
-
-
-
-        # privates
-# Another check
-# It was an object with a collection of key value pairs
+          func.call(self)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+      # privates
       timeRangeToArray = (timeRange) ->
-        array = []
+        result = []
         length = timeRange.length - 1
-        for(var i = 0 i <= length i++)
-          array.push(
-          start: timeRange.start(length)
-          end: timeRange.end(length)
+        for num in [0..length]
+          result.push(
+            start: timeRange.start(length)
+            end: timeRange.end(length)
           )
-        return array
+        return result

       getExt = (filename) ->
@@ -466,10 +413,6 @@
         if Kona.Sound.types[getExt(src)]?
           source.type = Kona.Sound.types[getExt(src)]
         sound.appendChild(source)
-
-
-
-


Fix loading ace-builds as assets in meteor 0.9.2

With the new package asset filename changes introduced in 0.9.2,
`/packages/developer:package/assets` need to be `/packages/developer_package/assets`

--- sharejs-client.coffee
+++ sharejs-client.coffee
@@ -1,5 +1,5 @@
 # Set asset path in Ace config
-require('ace/config').set('basePath', '/packages/mizzao:sharejs/ace-builds/src')
+require('ace/config').set('basePath', '/packages/mizzao_sharejs/ace-builds/src')

 class ShareJSConnector


Remove mdMedia line

Remove mdMedia line

--- angular/core/components/sidebar/sidebar.coffee
+++ angular/core/components/sidebar/sidebar.coffee
@@ -8,7 +8,6 @@
     $scope.currentState = ""

     $scope.$on 'toggleSidebar', (event, show) ->
-      return if !$mdMedia("gt-md")
       if !_.isUndefined(show)
         $scope.showSidebar = show
       else


Add anonymous class constructor method

Add anonymous class constructor method

--- app/assets/javascripts/monster/scaffold.coffee
+++ app/assets/javascripts/monster/scaffold.coffee
@@ -11,20 +11,11 @@
         target[arg_name[attr]] = arguments_list[i]
     else
       target[arg_name] = arguments_list[i]
-window.$controller = (name, args..., definition) ->
-  args.unshift '$scope': '$'
-  args.push 'Event'
-  args.push 'Api'
-  if $('body').hasClass('home_pages') || $('body').hasClass('app_pages')
-    args.push '$state'
-    args.push '$stateParams'
-  names = name.split '/'
-  injection_args = args_to_injection_parameters args
-  super_def = class extends definition
+window.$mkcontroller = (args..., definition)->
+  args.unshift '$scope': '$' unless typeof(args[0]) == 'object' && args[0]['$scope']
+  class extends definition
     constructor:->
       set_args args, @, arguments
-      @_controller = names[0]
-      @_action     = names[1]
       @$export 'stop'
       @$root = @$.$root
       super
@@ -37,6 +28,20 @@
     stop:(event)=>
       event.stop()
       @$
+window.$controller = (name, args..., definition) ->
+  args.unshift '$scope': '$'
+  args.push 'Event'
+  args.push 'Api'
+  if $('body').hasClass('home_pages') || $('body').hasClass('app_pages')
+    args.push '$state'
+    args.push '$stateParams'
+  names = name.split '/'
+  injection_args = args_to_injection_parameters args
+  super_def = class extends $mkcontroller args..., definition
+    constructor:->
+      @_controller = names[0]
+      @_action     = names[1]
+      super
   app.controller name, [injection_args..., super_def]
 window.$service = (name, args..., definition) ->
   args.unshift '$http'


Add dropup class in responsive toolbar.

Add dropup class in responsive toolbar.

--- app/assets/javascripts/views/toolbar.js.coffee
+++ app/assets/javascripts/views/toolbar.js.coffee
@@ -1,6 +1,20 @@
 Wheelmap.ToolbarView = Ember.View.extend
   elementId: 'toolbar'
   templateName: 'toolbar'
+
+  init: ()->
+    @_super()
+
+  didInsertElement: ()->
+    $(window).on 'resize.toolbar', @adjustCategoryFilter
+    @adjustCategoryFilter()
+
+  didRemoveElement: ()->
+    $(window).off 'resize.toolbar'
+
+  adjustCategoryFilter: ()->
+    # @TODO We need a better place for this!
+    @$('.category-filter').toggleClass('dropup', $(window).width() < 767)

   addToggleSearchbar: (()->
     $searchbar = @$('.searchbar-form')


Added license

Added license

--- app/assets/javascripts/worker_status.js.coffee
+++ app/assets/javascripts/worker_status.js.coffee
@@ -1,3 +1,17 @@
+# Copyright 2013 Square Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+

 $(document).ready((->
   checkWorkerStatus = ->


Initial area tips

Initial area tips

--- muleview/coffee/app/view/MuleChart.coffee
+++ muleview/coffee/app/view/MuleChart.coffee
@@ -9,8 +9,6 @@
   animate: true

   timeLabel:
-    renderer: (timestamp) ->
-      Ext.Date.format(new Date(timestamp * 1000), Muleview.Settings.labelFormat)
     rotate:
       degrees: 315

@@ -23,6 +21,10 @@
     topKey

   initComponent: ->
+    timeFormatter = (timestamp) ->
+      Ext.Date.format(new Date(timestamp * 1000), Muleview.Settings.labelFormat)
+    @timeLabel.renderer = timeFormatter
+
     # @data should be a hash of key => keydata,
     # keydata should be: [[count, batch, timestamp], [count, batch, timestamp]...]

@@ -69,6 +71,15 @@
         yField: areaKeys
         title: @keyLegendName(key) for key in areaKeys
         highlight: @highlight
+        tips:
+          trackMouse: true
+          width: 105
+          tpl: "{key} = {value}"
+          renderer: (storeItem, item) ->
+            console.log('MuleChart.coffee\\ 79: item:', item);
+            console.log('MuleChart.coffee\\ 80: storeItem:', storeItem);
+            @setTitle timeFormatter(storeItem.get('timestamp'))
+            @update {key: item.storeField, value: storeItem.get(item.storeField)}

     # Alerts:
     if @alerts


Add additional assessment filter to teacher dashboard assessments tab (#142)

--- app/views/courses/TeacherClassView.ozar.coffee
+++ app/views/courses/TeacherClassView.ozar.coffee
@@ -331,6 +331,7 @@
     @courseAssessmentPairs = []
     for course in @courses.models
       assessmentLevels = @classroom.getLevels({courseID: course.id, assessmentLevels: true}).models
+      assessmentLevels = _.filter(assessmentLevels, (l) => l.get('assessment') == true)
       fullLevels = _.filter(@levels.models, (l) => l.get('original') in _.map(assessmentLevels, (l2)=>l2.get('original')))
       @courseAssessmentPairs.push([course, fullLevels])
     return @courseAssessmentPairs


remote TODOs

remote TODOs

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1751,7 +1751,7 @@
           trigger.flatMap ->
             value.take(1)
         [1,2])
-    describe "delayed bounce (TODO: how to name better)", ->
+    describe "delayed bounce", ->
       expectStreamEvents(
         ->
           src = series(1, [1,2])
@@ -1775,7 +1775,7 @@
             value.onValue(->)
             value.take(1)
         [1,2])
-    describe "delayed bounce (TODO: how to name better)", ->
+    describe "delayed bounce", ->
       expectStreamEvents(
         ->
           src = series(1, [1,2])


Fix CoffeeScript 1.12.6+ incompatibility

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -43,28 +43,28 @@
       it 'compile if current file is a .tex file', ->
         helper.setConfig 'atom-latex.build_after_save', true
         project = """#{path.dirname(__filename)}#{path.sep}latex_project"""
-        waitsForPromise -> atom.workspace.open(
+        waitsForPromise -> (atom.workspace.open(
           """#{project}#{path.sep}input.tex""").then (editor) ->
             Promise.resolve editor.save()
-          .then () ->
+          ).then () ->
             expect(builder.build).toHaveBeenCalled()

       it 'does nothing if config disabled', ->
         helper.setConfig 'atom-latex.build_after_save', false
         project = """#{path.dirname(__filename)}#{path.sep}latex_project"""
-        waitsForPromise -> atom.workspace.open(
+        waitsForPromise -> (atom.workspace.open(
           """#{project}#{path.sep}input.tex""").then (editor) ->
             Promise.resolve editor.save()
-          .then () ->
+          ).then () ->
             expect(builder.build).not.toHaveBeenCalled()

       it 'does nothing if current file is not a .tex file', ->
         helper.setConfig 'atom-latex.build_after_save', true
         project = """#{path.dirname(__filename)}#{path.sep}latex_project"""
-        waitsForPromise -> atom.workspace.open(
+        waitsForPromise -> (atom.workspace.open(
           """#{project}#{path.sep}dummy.file""").then (editor) ->
             Promise.resolve editor.save()
-          .then () ->
+          ).then () ->
             expect(builder.build).not.toHaveBeenCalled()

     describe 'toolchain feature', ->


check if onAddRecord is present when rendering

--- client/lanes/components/grid/Toolbar.cjsx
+++ client/lanes/components/grid/Toolbar.cjsx
@@ -1,7 +1,7 @@
 class Lanes.Components.Grid.Toolbar extends Lanes.React.BaseComponent

     propTypes:
-        addRecord: React.PropTypes.func
+        onAddRecord: React.PropTypes.func
         startEdit: React.PropTypes.func
         toolbarChildren: React.PropTypes.oneOfType([
             React.PropTypes.element,
@@ -13,16 +13,19 @@
         @props.startEdit(0)

     AddButton: ->
-        return null unless @props.allowCreate
+        return null unless @props.onAddRecord and @props.allowCreate
         <BS.Button className="navbar-btn add-row pull-right"
             onClick={@onAddRecord} bsSize='small'
         >
             <LC.Icon type="plus" />Add Row
         </BS.Button>

+
+    shouldDisplay: ->
+        not ( ( false == @props.commands?.isEditing() or not @props.allowCreate ) and not @props.toolbarChildren )
+
     render: ->
-        if ( ( false == @props.commands?.isEditing() or not @props.allowCreate ) and not @props.toolbarChildren )
-            return null
+        return null unless @shouldDisplay()

         props = _.extend {}, @props,
         <Lanes.Components.Grid.Toolbar key="toolbar" {...props} />


Prevented throwing errors for missing email addresses until an email is sent.

--- src/AccountsUi.coffee
+++ src/AccountsUi.coffee
@@ -71,10 +71,6 @@
     Setter.merge(@_config, config)
     clonedConfig = Setter.clone(@_config)
     unless config then return clonedConfig
-    unless @_config.email.adminAddress
-      throw new Error('AccountsUi: Admin email address not provided')
-    unless @_config.email.fromAddress
-      throw new Error('AccountsUi: From email address not provided')
     setUpRoutes(@_config.setUpRoutes) if Meteor.isClient
     @setUpTemplates() if Meteor.isServer
     clonedConfig
@@ -105,6 +101,14 @@
     currentRoute = Router.getCurrentName()
     _.indexOf(ROUTE_NAMES, currentRoute) >= 0

+  getAdminController: _.once ->
+    Routes.getBaseController().extend
+      onBeforeAction: ->
+        return unless @ready()
+        AccountsUi.signInRequired(@, {callNext: false})
+        user = Meteor.user()
+        if AccountsUtil.isAdmin(user) then @next() else AccountsUi.goToLogin()
+
 ROUTE_NAMES = ['login', 'forgotPassword', 'resetPassword', 'signUp', 'verifyEmail']
 setUpRoutes = _.once (callback) -> callback.call(AccountsUi)
 createRoute = (name, args) -> Router.route name,
@@ -124,8 +128,11 @@
   _.extend AccountsUi,
     createEmail: (email) ->
       config = AccountsUi.config()
+      fromAddress = config.email.fromAddress
+      unless fromAddress
+        throw new Error('AccountsUi: "from" email address not provided')
       email = Setter.defaults email,
-        from: config.email.fromAddress
+        from: fromAddress
       Logger.info('Created email', email)
       email

@@ -152,8 +159,11 @@

     sendEmailToAdmin: (email) ->
       config = AccountsUi.config()
+      adminAddress = config.email.adminAddress
+      unless adminAddress
+        throw new Error('AccountsUi: Admin email address not provided')
       email = Setter.defaults email,
-        to: config.email.adminAddress
+        to: adminAddress
       @sendEmail(email)

     sendEmailToUser: (args) ->


CollectionView: show child controls.

CollectionView: show child controls.

--- coffee/src/shells/collection.shell.coffee
+++ coffee/src/shells/collection.shell.coffee
@@ -223,6 +223,12 @@
       view.$el.addClass 'hidden'
       view.pause()

+      # remove view.controlView
+      controlsIndex = _.indexOf @controlsView.buttons, view.controlsView
+      if controlsIndex >= 0
+        @controlsView.buttons.splice controlsIndex, 1
+        @controlsView.softRender()
+
     view

@@ -238,7 +244,10 @@
     view.$el.removeClass 'hidden'

     @$el.append view.render().el
-    # TODO update controls
+
+    # add view.controlsView
+    @controlsView.buttons.push view.controlsView
+    @controlsView.render()

     view


Start highlighting from seekOffset

Start highlighting from seekOffset

Closes #130

--- coffee/src/shells/highlights_shell.coffee
+++ coffee/src/shells/highlights_shell.coffee
@@ -517,8 +517,8 @@
       @onClipHighlightDone()

     highlight =
-      timeStart: 0
-      timeEnd: @duration()
+      timeStart: @mediaView.seekOffset()
+      timeEnd: Math.min(@mediaView.seekOffset() + 10, @duration())
       title: ''

     @model.highlights().push highlight


groups: landing top fly navigation

groups: landing top fly navigation

--- components/tree/treeviewcontroller.coffee
+++ components/tree/treeviewcontroller.coffee
@@ -571,7 +571,7 @@
     @dragIsActive = yes
     e = event.originalEvent
     e.dataTransfer.effectAllowed = 'copyMove' # only dropEffect='copy' will be dropable
-    transferredData = (@getNodeId node.getData() for node in @selectedNodes)
+    transferredData = (JSON.stringify node.getData() for node in @selectedNodes)
     e.dataTransfer.setData('Text', transferredData.join()) # required otherwise doesn't work

     if @selectedNodes.length > 1


Use legacy compatible Buffer API.

Use legacy compatible Buffer API.

--- src/RedisState.coffee
+++ src/RedisState.coffee
@@ -386,7 +386,7 @@
       return Promise.resolve data
     for msg, idx in msgs
       obj = JSON.parse msg, (key, val) ->
-        if val?.type == 'Buffer' then Buffer.from(val.data) else val
+        if val?.type == 'Buffer' then new Buffer(val.data) else val
       obj.timestamp = parseInt tss[idx]
       obj.id = parseInt ids[idx]
       data[idx] = obj


Remove TODO (decided not to).

Remove TODO (decided not to).

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -135,7 +135,6 @@

   _expectations = []

-  # TODO: rename to _gather_all_errors?
   _find_all_errors = ->
       ( expectation.find_errors() for expectation in _expectations ).join("")


Allow specifying an alias when using --require

--- src/cli/common.coffee
+++ src/cli/common.coffee
@@ -67,6 +67,16 @@
         if key in ['ignore', 'require', 'plugins']
           # split comma separated values to arrays
           value = value.split ','
+          if key is 'require'
+            # handle special alias:module mapping
+            reqs = {}
+            for v in value
+              [alias, module] = v.split ':'
+              if not module?
+                module = alias
+                alias = module.replace(/\/$/, '').split('/')[-1..]
+              reqs[alias] = module
+            value = reqs
         config[key] = value
       callback null, config


Fix var name.

Fix var name.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -29,7 +29,7 @@
   if err?
     console.error "Problems on reading file '#{fileName}': #{err}"
     process.exit 2
-  stockxmlimport.run(content, mode)
+  stockimport.run(content, mode)
   .then (result) ->
     console.info result
     process.exit 0


Use paneForItem to activate the editor

Use paneForItem to activate the editor

--- lib/bookmarks-view.coffee
+++ lib/bookmarks-view.coffee
@@ -82,6 +82,6 @@
       super

   confirmed: ({editor, marker}) ->
-    atom.workspace.open(editor.getPath(), searchAllPanes: true).then (editor) ->
-      editor.setSelectedBufferRange?(marker.getBufferRange(), autoscroll: true)
+    editor.setSelectedBufferRange(marker.getBufferRange(), autoscroll: true)
+    atom.workspace.paneForItem(editor).activate()
     @cancel()


nginx: redirect /d/* to koding-dl s3 bucket

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -303,8 +303,15 @@
         proxy_connect_timeout 1;
       }

-      location = /kd {
-        rewrite / "https://koding-kd.s3.amazonaws.com/install-kd.sh" break;
+      # redirect /d/* to koding-dl S3 bucket; used to distributed
+      # // kd/klient installers
+      location ~^/d/(.*)$ {
+        proxy_pass            "https://s3.amazonaws.com/koding-dl/$1";
+        proxy_set_header      X-Real-IP       $remote_addr;
+        proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_connect_timeout 1;
+
+        resolver 8.8.8.8;
       }

       #{createRootLocation(KONFIG)}


Add HubSpot origin to access control allow list

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -247,6 +247,8 @@

     #{createHealthcheck(KONFIG)}

+    add_header Access-Control-Allow-Origin #{KONFIG.hubspotPageURL} always;
+
     # start server
     server {
       # we should not timeout on proxy connections


IDE: Draw ide with fake views if machine is not approved.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -401,6 +401,13 @@
       unless machineItem instanceof Machine
         machineItem = new Machine machine: machineItem

+      if not machineItem.isMine()
+        if machineItem.isPermanent() and not machineItem.isApproved()
+          { activitySidebar } = kd.singletons.mainView
+          box = activitySidebar.getMachineBoxByMachineUId machineItem.uid
+          box.machineItem.showSidebarSharePopup sticky: yes
+          withFakeViews = yes
+
       @setMountedMachine machineItem

       @prepareIDE withFakeViews


Register modal template varible name fix

--- public/scripts/backbone-schedule/views/modals/register.coffee
+++ public/scripts/backbone-schedule/views/modals/register.coffee
@@ -2,7 +2,7 @@
         'Underscore'
         'Backbone'
         'views/shark-view'
-        'text!tmpl/modals/register.ejs'], ($,_, Backbone, SharkView, registerTemplate) ->
+        'text!tmpl/modals/register.ejs'], ($,_, Backbone, SharkView, templateText) ->

   class RegisterView extends SharkView

@@ -10,7 +10,7 @@

     initialize: ->
       _.bindAll @
-      @registerTemplate = _.template registerTemplate
+      @template = _.template templateText

     show: ->
       @$el.html(@template()).appendTo $ 'body'


read all pods from wolfram

read all pods from wolfram

--- commands/wolfram.coffee
+++ commands/wolfram.coffee
@@ -42,11 +42,13 @@
                 message = null

             try
-                [pods] = root.children.filter (node) -> node.name is 'pod'
-                subpods = pods.children.filter (node) -> node.name is 'subpod'
-                for subpod in subpods
-                    [text] = subpod.children.filter (node) -> node.name is 'plaintext'
-                    message += "\n#{[text.firstchild().text]}"
+                pods = root.children.filter (node) -> node.name is 'pod'
+                for pod in pods
+                    subpods = pod.children.filter (node) -> node.name is 'subpod'
+                    for subpod in subpods
+                        [text] = subpod.children.filter (node) -> node.name is 'plaintext'
+                        message += "\n#{[text.firstchild().text]}"
             catch e
+                message += ''

         respond message


Add in tests for public read-only projects

--- services/web/test/acceptance/coffee/AuthorizationTests.coffee
+++ services/web/test/acceptance/coffee/AuthorizationTests.coffee
@@ -287,7 +287,7 @@
 			expect_read_access @other1, @project_id, done

 		it "should allow a user write access to its content", (done) ->
-			expect_content_write_access @owner, @project_id, done
+			expect_content_write_access @other1, @project_id, done

 		it "should not allow a user write access to its settings"#, (done) ->
 		# 	expect_no_settings_write_access @other1, @project_id, redirect_to: "/restricted", done
@@ -299,11 +299,41 @@
 			expect_read_access @anon, @project_id, done

 		it "should allow an anonymous user write access to its content", (done) ->
-			expect_content_write_access @owner, @project_id, done
+			expect_content_write_access @anon, @project_id, done

 		it "should not allow an anonymous user write access to its settings", (done) ->
 			expect_no_settings_write_access @anon, @project_id, redirect_to: "/restricted", done

 		it "should not allow an anonymous user admin access to it", (done) ->
 			expect_no_admin_access @anon, @project_id, redirect_to: "/restricted", done
-		+
+	describe "public read-only project", ->
+		before (done) ->
+			@owner.createProject "public-ro-project", (error, project_id) =>
+				return done(error) if error?
+				@project_id = project_id
+				@owner.makePublic @project_id, "readOnly", done
+
+		it "should allow a user read access to it", (done) ->
+			expect_read_access @other1, @project_id, done
+
+		it "should not allow a user write access to its content", (done) ->
+			expect_no_content_write_access @other1, @project_id, done
+
+		it "should not allow a user write access to its settings"#, (done) ->
+		# 	expect_no_settings_write_access @other1, @project_id, redirect_to: "/restricted", done
+
+		it "should not allow a user admin access to it", (done) ->
+			expect_no_admin_access @other1, @project_id, redirect_to: "/restricted", done
+
+		it "should allow an anonymous user read access to it", (done) ->
+			expect_read_access @anon, @project_id, done
+
+		it "should not allow an anonymous user write access to its content", (done) ->
+			expect_no_content_write_access @anon, @project_id, done
+
+		it "should not allow an anonymous user write access to its settings", (done) ->
+			expect_no_settings_write_access @anon, @project_id, redirect_to: "/restricted", done
+
+		it "should not allow an anonymous user admin access to it", (done) ->
+			expect_no_admin_access @anon, @project_id, redirect_to: "/restricted", done


improve gulp build task

improve gulp build task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -14,13 +14,13 @@

 gulp.task 'build', ->
   gulp.src './src/*'
-    .pipe babel modules: 'umd'
+    .pipe babel modules: 'umd', comments: false
     .pipe gulp.dest './dist/'

 gulp.task 'build-dev', ->
   gulp.src './src/*'
     .pipe sourcemaps.init()
-    .pipe ignoreError babel(modules: 'umd')
+    .pipe ignoreError babel(modules: 'umd', comments: false)
     .pipe sourcemaps.write()
     .pipe gulp.dest './dist/'


refine

refine

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

-gulp.task 'bundle.shared', utils.list('scripts'), ->
+gulp.task 'bundle.shared', ['scripts'], ->

     $.rollup {
         entry: utils.path.dst 'app/@shared/**/index.js'
@@ -447,7 +447,7 @@

-gulp.task 'test', utils.list('bundle.shared'), ->
+gulp.task 'test', ['bundle.shared'], ->

     gulp.src 'test/spec/**/*.spec.{coffee,coffee.md,litcoffee,js}'
         .pipe $.jasmine()


wait phantomjs 2

wait phantomjs 2

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -48,7 +48,10 @@
     _.delay cb, 10000

 gulp.task "test-own", ["test-own-crossbar"], (cb)->
-    cb()
+    stream = mocha_phantomjs()
+    stream.write path : "http://127.0.0.1:9000/test/"
+    stream.end()
+    stream

 # ***********


reduce compression

reduce compression

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -104,7 +104,7 @@
 gulp.task 'image_min', ->
   options = {
     resize: [1440,1440],
-    quality: 60,
+    quality: 80,
     progressive: true,
     compressionLevel: 6,
     sequentialRead: true,


Remove semicolons from split sql statements

They aren't necessary and in fact will break hive.

--- src/split.coffee
+++ src/split.coffee
@@ -1,6 +1,7 @@
 # Functions for splitting SQL with APIs and what not.
 request = require 'request'
 sync = require 'sync'
+_ = require 'lodash'

 module.exports =
 class Splitter
@@ -38,4 +39,4 @@
         name: "SplitError"
         status: response.statusCode
         message: body
-    JSON.parse(body).result.filter (s) -> s
+    JSON.parse(body).result.filter(_.identity).map (s) -> _.trimRight s, ';'


Removing weird methods scraped from mozilla docs

--- src/stubs.coffee
+++ src/stubs.coffee
@@ -986,14 +986,6 @@
 @methodOf Date#
 ###
 ###*
-Deprecated
-
-
-
-@name toGMTString
-@methodOf Date#
-###
-###*
 Converts a date to a string, returning the "date" portion using the operating
 system's locale's conventions.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -15,7 +15,7 @@
   self.bind "update", ->
     for property, data of I.activeTweens
       f = Easing[data.easing]()
-      I[property] = f
+      I[property] = f()

   tween: (duration, properties) ->
     for property, target of properties


Null guard for merge

Null guard for merge

--- src/utils.coffee
+++ src/utils.coffee
@@ -27,7 +27,7 @@
   #     Kona.Utils.merge(obj1, obj2)
   #       => { 'a' : 100, 'b' : 254, 'c' : 300 }
   #
-  merge: (obj1, obj2) ->
+  merge: (obj1, obj2={}) ->
     obj1[attr] = obj2[attr] for attr of obj2
     obj1


maximumReached also checking ip the operation is 'update'

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -188,6 +188,8 @@
     maximumReached: ->
       @options.maximum && @files.length >= @options.maximum

+    maximumReached: ->
+      @options.maximum && @files.length >= @options.maximum && not @isUpdateOperation()

     # update operation is characterized by
     # 1. config.method == 'update'


bugfix: favorites not added from summary screen

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -607,7 +607,6 @@
     radio.checked = false for radio in @classifierTypeRadios
     @onClickCycleChannels() if @cycling
     @showSummary()
-    @sendClassification()
     # hide all marks
     mark.setAttribute 'visibility', 'hidden' for mark in [@el.find(".mark")...]

@@ -766,6 +765,7 @@
     @guideShowing = !@guideShowing

   onClickNextSubject: ->
+    @sendClassification()
     @removeElementsOfClass(".known-asteroid")
     element.hide() for element in [@summaryContainer, @nextSubjectButton, @rightPanelSummary]
     @summaryImageContainer.empty()


sync activateFrame with classifier menu, hide next frame in 4up

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -166,7 +166,7 @@
         if @state is 'asteroidTool'
           # enforce one mark per frame
           if @asteroidMarkedInFrame[@currFrameIdx]
-            # console.log 'frame already marked!'
+            console.log 'frame already marked!'
             @destroyMarksInFrame @currFrameIdx, @currAsteroid.id
           else
             # console.log 'frame was empty'
@@ -290,6 +290,7 @@

   onClickFourUp: ->
     @el.find("#frame-id-#{i}").closest("div").show() for i in [0...4]
+    @nextFrame.hide()

     markingSurfaces = document.getElementsByClassName("marking-surface")
     @resizeElements(markingSurfaces, 254) # image sizing for 4up view
@@ -303,6 +304,7 @@
   onClickFlicker: ->
     markingSurfaces = document.getElementsByClassName("marking-surface")
     @resizeElements(markingSurfaces, 512) # image sizing for 4up view
+    @nextFrame.show()

     @enableSliderControls false
     @flickerButton.attr 'disabled', true
@@ -435,6 +437,7 @@

   activateFrame: (@active) ->
     @setAsteroidFrame(@active)
+    classifier.el.find(".asteroid-frame-#{@active}").addClass 'current-asteroid-frame'
     return if @el.attr('flicker') is "false"
     @showFrame(@active)


Fix problem in rendering routes without intermediate stops

--- src/views.coffee
+++ src/views.coffee
@@ -387,7 +387,7 @@
                     if random == 0
                         warning = 'Cobblestone-paved sidewalk.'
                     steps.push(text: text, warning: warning)
-            else if leg.mode in modes_with_stops
+            else if leg.mode in modes_with_stops and leg.intermediateStops
                 for stop in leg.intermediateStops
                     steps.push(
                         text: stop.name


Switch to using event.currentTarget.

Switch to using event.currentTarget.

Less coupling with dom elements.

--- src/views.coffee
+++ src/views.coffee
@@ -142,7 +142,7 @@
             if @prevent_switch
                 @prevent_switch = false
                 return
-            $element = $(event.target).closest('a')
+            $element = $(event.currentTarget)
             type = $element.data('type')
             @switch_content type
             # This removes clouds from the screen with css animation.
@@ -259,7 +259,7 @@
             '/#/service/' + id

         toggle_leaf: (event) ->
-            @toggle_element($(event.target).closest('li.service').find('.show-button'))
+            @toggle_element($(event.currentTarget).find('.show-button'))

         toggle_button: (event) ->
             event.preventDefault()
@@ -284,7 +284,7 @@
                 @app_view.remove_service_points(service_id)

         open: (event) ->
-            service_id = $(event.target).closest('li').data('service-id')
+            service_id = $(event.currentTarget).data('service-id')
             if not service_id
                 return null
             if service_id == 'root'


Add "no-transform" to the no_cache headers

I ran into an issue running meteor which user sockjs. My site didn't work when running on Chrome iOS with Google's [Data Saver](https://developer.chrome.com/multidevice/data-compression) feature turned on.

I [filed a bug](https://code.google.com/p/chromium/issues/detail?id=553828#) but the change above *seems* to work around the issue for now until they can fix it on their side.

--- src/webjs.coffee
+++ src/webjs.coffee
@@ -175,7 +175,7 @@
         return content

     h_no_cache: (req, res, content) ->
-        res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+        res.setHeader('Cache-Control', 'no-store, no-cache, no-transform, must-revalidate, max-age=0')
         return content

     expect_form: (req, res, _data, next_filter) ->


handle when node not in graph in Graph.{parents,children}

--- src/zuvor.coffee
+++ src/zuvor.coffee
@@ -253,16 +253,22 @@

   # O(n) where n is the number of parents of x
   Graph.prototype.parents = (x) ->
+    node = this.nodes[x]
+    unless node?
+      return []
     elements = []
-    for key,node of this.nodes[x].parents
-      elements.push node.value
+    for key,parent of this.nodes[x].parents
+      elements.push parent.value
     return elements

   # O(n) where n is the number of children of x
   Graph.prototype.children = (x) ->
+    node = this.nodes[x]
+    unless node?
+      return []
     elements = []
-    for key,node of this.nodes[x].children
-      elements.push node.value
+    for key,child of this.nodes[x].children
+      elements.push child.value
     return elements

   # elements without parents


Fixed rotation not being updated after a new SpriteSheet is built.

--- app/lib/surface/WebGLLayer.coffee
+++ app/lib/surface/WebGLLayer.coffee
@@ -147,6 +147,7 @@
       cocoSprite.options.resolutionFactor = @resolutionFactor
       cocoSprite.updateBaseScale()
       cocoSprite.updateScale()
+      cocoSprite.updateRotation()
     @trigger 'new-spritesheet'

   renderContainers: (thangType, colorConfig, actionNames, spriteSheetBuilder) ->


Better plugin loading errors

Better plugin loading errors

--- src/core/environment.coffee
+++ src/core/environment.coffee
@@ -156,16 +156,23 @@

   loadPluginModule: (module, callback) ->
     ### Load a plugin *module*. Calls *callback* when plugin is done loading, or an error ocurred. ###
+    id = 'unknown'
+    done = (error) ->
+      error.message = "Error loading plugin '#{ id }': #{ error.message }" if error?
+      callback error
+
     if typeof module is 'string'
+      id = module
       try
         module = @loadModule module
       catch error
-        callback error
+        done error
         return

-    module.call null, this, (error) ->
-      error.message = "Error loading plugin '#{ module }': #{ error.message }" if error?
-      callback error
+    try
+      module.call null, this, done
+    catch error
+      done error

   loadViewModule: (id, callback) ->
     ### Load a view *module* and add it to the environment. ###


Typofix, closes #195

Typofix, closes #195

--- src/core/environment.coffee
+++ src/core/environment.coffee
@@ -297,7 +297,7 @@
     config = Config.fromFileSync config
   else
     workDir ?= process.cwd()
-    if not config instanceof Config
+    if config not instanceof Config
       config = new Config config

   return new Environment config, workDir, log


removed jquery/coffee client timezone form adjuster

--- app/coffee/newentry.coffee
+++ app/coffee/newentry.coffee
@@ -16,45 +16,6 @@

 $(document).ready ->
   tagsController = new TagsController('.entryTags', 'edit')
-
-  # CLIENT TIMEZONE ADJUSTMENTS FOR NEW ENTRY
-  if $('#new_entry_mode').attr('name')?
-
-    # detect users timezone + offset in minutes using detect_timezone.js
-    timezone = determine_timezone().timezone
-    timezone_offset = timezone.utc_offset
-    timezone_text = timezone.olson_tz
-
-    # parse the timezone results into usable variables
-    $plus_neg = timezone_offset.substr(0,1)
-    $timezone_offset_hour = timezone_offset.split(':')[0] # first convert -08:00 format to -08
-    $timezone_offset_hour = $timezone_offset_hour.substring(1, $timezone_offset_hour.length) # remove +/-
-    if $timezone_offset_hour < 10
-      $timezone_offset_hour = $timezone_offset_hour.substring(1,$timezone_offset_hour.length) # remove 0 padding (08 - 8)
-
-    # adjust date hour pulldown for new entries
-    $utc_hour = $('select#dreamed_at_hour').val()
-    $utc_ampm = $('select#dreamed_at_ampm').val()
-
-    # convert to military time
-    $utc_hour = $utc_hour + 12 if $utc_ampm == 'pm'
-    $utc_hour = 0 if $utc_ampm == 'am' && $utc_hour == 12
-    $utc_hour = 12 if $utc_hour == 24
-
-    # synch utc hour with client's offset hour
-    $utc_hour = parseInt($utc_hour) - parseInt($timezone_offset_hour) if $plus_neg == '-'
-    $utc_hour = parseInt($utc_hour) + parseInt($timezone_offset_hour) if $plus_neg == '+'
-
-    # convert back to 12 hr time and update the pulldown values (hour/ampm)
-    if $utc_hour > 12
-      $utc_hour = $utc_hour - 12
-      $('select#dreamed_at_ampm').val('am')
-    if $utc_hour < 1
-      $utc_hour = $utc_hour + 12
-      $('select#dreamed_at_ampm').val('pm')
-
-    $('select#dreamed_at_hour').val($utc_hour)
-

   # Setup icon type changing
   $('#entryType_list').unbind();


fix broken league page

fix broken league page


fix #97 Now accurate top offset for wrapped-line.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -16,6 +16,7 @@
 SearchHistoryManager = require './search-history-manager'

 packageScope = 'vim-mode-plus'
+RowHeightInEm = 1.5

 module.exports =
 class VimState
@@ -150,6 +151,19 @@
     cursorsComponent = @editorElement.component.linesComponent.cursorsComponent
     cursorsComponent.cursorNodesById[cursor.id]

+  # Return cursor style top, left offset for **sowft-wrapped** line
+  # -------------------------
+  getOffsetForSelection: (selection) ->
+    bufferPoint = swrap(selection).getCharacterwiseHeadPosition()
+    screenPoint = @editor.screenPositionForBufferPosition(bufferPoint)
+    bufferRange = @editor.bufferRangeForBufferRow(bufferPoint.row)
+    screenRows = @editor.screenRangeForBufferRange(bufferRange).getRows()
+    rows = if selection.isReversed()
+      screenRows.indexOf(screenPoint.row)
+    else
+      -(screenRows.reverse().indexOf(screenPoint.row) + 1)
+    {top: rows * RowHeightInEm, left: screenPoint.column}
+
   modifyStyleForCursor: (cursor) ->
     domNode = @getDomNodeForCursor(cursor)
     return (new Disposable) unless domNode
@@ -158,13 +172,18 @@
     {style} = domNode
     switch @submode
       when 'linewise'
-        point = swrap(selection).getCharacterwiseHeadPosition()
-        style.setProperty('left', "#{point.column}ch") if point?
-        style.setProperty('top', '-1.5em') unless selection.isReversed()
+        if @editor.isSoftWrapped()
+          {left, top} = @getOffsetForSelection(selection)
+          style.setProperty('left', "#{left}ch") if left
+          style.setProperty('top', "#{top}em") if top
+        else
+          point = swrap(selection).getCharacterwiseHeadPosition()
+          style.setProperty('left', "#{point.column}ch") if point?
+          style.setProperty('top', "-#{RowHeightInEm}em") unless selection.isReversed()
       when 'characterwise', 'blockwise'
         unless selection.isReversed()
           if cursor.isAtBeginningOfLine()
-            style.setProperty('top', '-1.5em')
+            style.setProperty('top', "-#{RowHeightInEm}em")
           else
             style.setProperty('left', '-1ch')


Add basic stop route

Add basic stop route

--- lib/webserver.coffee
+++ lib/webserver.coffee
@@ -6,6 +6,13 @@
 server = http.createServer()

 SECRET = process.env.SECRET or "testingpass"
+
+getJSON = (req, cb) ->
+  optStr = ""
+  req.on "data", (buf) ->
+    optStr += buf.toString()
+  req.on "end", ->
+    cb JSON.parse optStr

 server.on 'request', (req, res) ->
   res.setHeader "Access-Control-Allow-Origin", "*"
@@ -33,6 +40,11 @@
           processes: slave.processes
           load: slave.load
       res.end JSON.stringify slaves
+    when "/stop"
+      #Should there should be logic here (or elsewhere) to send the right PIDs to the right slaves?
+      getJSON req, (opts) ->
+        cavalry.stop opts.slave, opts.ids, (err, body) ->
+          res.end()
     else
       gitter.handle req, res
 module.exports = server


KernelManager: Fix connection to existing kernels

* Created a mock kernelspec for existing kernels.

Fixes #374

--- lib/kernel-manager.coffee
+++ lib/kernel-manager.coffee
@@ -25,9 +25,23 @@

     startKernelFor: (grammar, onStarted) ->
+        try
+            rootDirectory = atom.project.rootDirectories[0].path or
+                path.dirname atom.workspace.getActiveTextEditor().getPath()
+            connectionFile = path.join(
+                rootDirectory, 'hydrogen', 'connection.json'
+            )
+            connectionString = fs.readFileSync connectionFile, 'utf8'
+            connection = JSON.parse connectionString
+            @startExistingKernel grammar, connection, connectionFile, onStarted
+            return
+
+        catch e
+            unless e.code is 'ENOENT'
+                throw e
+
         language = @getLanguageFor grammar
         @getKernelSpecFor language, (kernelSpec) =>
-
             unless kernelSpec?
                 message = "No kernel for language `#{language}` found"
                 detail = 'Check that the language for this file is set in Atom
@@ -35,45 +49,49 @@
                 atom.notifications.addError message, detail: detail
                 return

-            console.log 'startKernelFor:', language
             @startKernel kernelSpec, grammar, onStarted

+    startExistingKernel: (grammar, connection, connectionFile, onStarted) ->
+        language = @getLanguageFor grammar
+
+        console.log 'KernelManager: startExistingKernel: Assuming', language
+
+        kernelSpec =
+            display_name: 'Existing Kernel'
+            language: language
+            argv: []
+            env: {}
+
+        kernel = new ZMQKernel kernelSpec, grammar, connection, connectionFile
+        @_runningKernels[language] = kernel
+        @_executeStartupCode kernel
+        onStarted?(kernel)
+
+
     startKernel: (kernelSpec, grammar, onStarted) ->
         language = @getLanguageFor grammar

-        kernelSpec.language = language
-
-        finishKernelStartup = (kernel) =>
+        console.log 'KernelManager: startKernelFor:', language
+
+        launchSpec(kernelSpec).then ({config, connectionFile, spawn}) =>
+            kernel = new ZMQKernel(
+                kernelSpec, grammar,
+                config, connectionFile,
+                spawn
+            )
             @_runningKernels[language] = kernel
-
-            startupCode = Config.getJson('startupCode')[kernelSpec.display_name]
-            if startupCode?
-                console.log 'executing startup code'
-                startupCode = startupCode + ' \n'
-                kernel.execute startupCode
-
+            @_executeStartupCode kernel
             onStarted?(kernel)

-        try
-            rootDirectory = atom.project.rootDirectories[0].path
-            connectionFile = path.join rootDirectory, 'hydrogen',
-                'connection.json'
-            data = fs.readFileSync connectionFile, 'utf8'
-            config = JSON.parse data
-            console.log 'KernelManager: Using connection file: ', connectionFile
-
-            kernel = new ZMQKernel(kernelSpec, grammar, config, connectionFile)
-            finishKernelStartup kernel
-
-        catch e
-            unless e.code is 'ENOENT'
-                throw e
-            launchSpec(kernelSpec).then ({config, connectionFile, spawn}) ->
-                kernel = new ZMQKernel(
-                    kernelSpec, grammar, config, connectionFile, spawn
-                )
-                finishKernelStartup kernel
+
+    _executeStartupCode: (kernel) ->
+        displayName = kernel.kernelSpec.display_name
+        startupCode = Config.getJson('startupCode')[displayName]
+        if startupCode?
+            console.log 'KernelManager: Executing startup code:', startupCode
+            startupCode = startupCode + ' \n'
+            kernel.execute startupCode

     attachKernel: (grammar, kernel) ->


Added documentation for message routes.

Added documentation for message routes.

--- lib/routes/message.coffee
+++ lib/routes/message.coffee
@@ -9,6 +9,7 @@
 ObjectId = require('mongoose').Types.ObjectId
 multiparty = require('multiparty')
 Boom = require 'boom'
+Joi = require 'joi'
 NotFound = Boom.notFound

 getMessages = (req, reply)->
@@ -60,7 +61,74 @@
   {
     method: 'GET'
     path: '/group/{id}/message'
-    handler: getMessages
+    config:
+      handler: getMessages
+      description: 'Gets the messages for the given group.'
+      notes: "Gets the messages for the given group. You can use the parameter values to
+      go back further in time. The messages are returned in the order of: Most recent
+      message is at the lower index. This means that if you get the 30 most recent messages
+      then the message at index 0 is the *most recent* message sent. Clients may have to
+      reverse the order of the messages before inserting it into their list.
+
+      This route requires the user's access token, either as a query param,
+      or a header, but not both.
+      The header format must be: authorization: Bearer {token}
+      The query format must be: access_token={token}"
+      tags: ['api']
+      plugins:
+        'hapi-swagger':
+          responseMessages: [
+            {
+              code: 400
+              message: 'Bad Request. Occurs when you fail to give the required data.'
+            }
+            {
+              code: 401
+              message: 'Unauthorized'
+            }
+            {
+              code: 404
+              message: "Not Found. Occurs if you try to access a group you don't have access to."
+            }
+          ]
+      validate:
+        params:
+          id:
+            Joi.string().regex(/^[0-9a-f]{24}$/)
+        query:
+          access_token: Joi.string().min(1).lowercase().trim().when(
+            '$headers.authorization', {
+              is: Joi.exist(),
+              otherwise: Joi.forbidden()
+            })
+          page: Joi.number().integer().min(0).optional().default(0)
+        headers:
+          authorization: Joi.string().trim().regex(/^Bearer\s[a-zA-Z0-9]+$/).when(
+            'query.access_token', {
+              is: Joi.exist(),
+              otherwise: Joi.forbidden()
+            })
+      response:
+        schema:
+          Joi.array().items(
+            Joi.object({
+              _id: Joi.string().required().description("The id for the message")
+              from: Joi.string().required().description("The user id this message is from.
+              Clients should use this to map the message to the user account stored locally.")
+              group: Joi.string().required().description("The group id this message is from.")
+              text: Joi.string().optional().description("The message text may be null if they
+              didn't type anything, and just sent a picture.")
+              sent: Joi.date().required()
+              type: Joi.string().required().description("This will almost always be 'message',
+              but sometimes may be 'system' or the name of an integration.")
+              hasMedia: Joi.boolean().required()
+              media: Joi.array().items(Joi.string())
+              mediaHeader: Joi.array().items(Joi.string())
+              media_size: Joi.array().items(Joi.number())
+            }).meta({
+              className: 'Message'
+            })
+          )
   }
   {
     method: 'POST'
@@ -69,10 +137,132 @@
       handler: postMessageData
       payload:
         output: 'file'
+      description: 'Uploads media for a message.'
+      notes: "Media is not sent through socket.io, but rather uploaded as a
+      regular HTTP request. To ensure your message and image are delivered at the same
+      time, if you are sending a media message, you don't send it via socket.io at all,
+      but rather upload it here. Once the upload has completed, the server will internally
+      broadcast the message (with the appropriate media settings on it) to the clients
+      in the room. The clients can then view the message and download the media content.
+
+      This route requires the user's access token, either as a query param,
+      or a header, but not both.
+      The header format must be: authorization: Bearer {token}
+      The query format must be: access_token={token}"
+      tags: ['api']
+      plugins:
+        'hapi-swagger':
+          responseMessages: [
+            {
+              code: 400
+              message: 'Bad Request. Occurs when you fail to give the required data.'
+            }
+            {
+              code: 401
+              message: 'Unauthorized'
+            }
+            {
+              code: 501
+              message: "Not Implemented. This indicates that the server does not have
+              acess to the AWS_KEY and AWS_SECRET, or was configured incorrectly."
+            }
+          ]
+      validate:
+        params:
+          id:
+            Joi.string().regex(/^[0-9a-f]{24}$/)
+        query:
+          access_token: Joi.string().min(1).lowercase().trim().when(
+            '$headers.authorization', {
+              is: Joi.exist(),
+              otherwise: Joi.forbidden()
+            })
+        headers:
+          authorization: Joi.string().trim().regex(/^Bearer\s[a-zA-Z0-9]+$/).when(
+            'query.access_token', {
+              is: Joi.exist(),
+              otherwise: Joi.forbidden()
+            })
+        payload:
+          text: Joi.string()
+          media: Joi.binary().required()
+      response:
+        schema:
+          Joi.object({
+            _id: Joi.string().required().description("The id for the message")
+            from: Joi.string().required().description("The user id this message is from.
+            Clients should use this to map the message to the user account stored locally.")
+            group: Joi.string().required().description("The group id this message is from.")
+            text: Joi.string().optional().description("The message text may be null if they
+            didn't type anything, and just sent a picture.")
+            sent: Joi.date().required()
+            type: Joi.string().required().description("This will almost always be 'message',
+            but sometimes may be 'system' or the name of an integration.")
+            hasMedia: Joi.boolean().required()
+            media: Joi.array().items(Joi.string())
+            mediaHeader: Joi.array().items(Joi.string())
+            media_size: Joi.array().items(Joi.number())
+          }).meta({
+            className: 'Message'
+          })
   }
   {
     method: 'GET'
     path: '/group/{id}/message/{mesId}/media'
-    handler: getMessageData
+    config:
+      handler: getMessageData
+      description: 'Gets the media from a given message'
+      notes: "Media (pictures, videos, audio), are not sent through socket.io in
+      real time, but must be fetched once the message has been transmitted through
+      socket.io. This will return the content-type as the header.
+
+      This route requires the user's access token, either as a query param,
+      or a header, but not both.
+      The header format must be: authorization: Bearer {token}
+      The query format must be: access_token={token}"
+      tags: ['api']
+      plugins:
+        'hapi-swagger':
+          responseMessages: [
+            {
+              code: 400
+              message: 'Bad Request. Occurs when you fail to give the required data.'
+            }
+            {
+              code: 401
+              message: 'Unauthorized'
+            }
+            {
+              code: 404
+              message: "Not Found. Occurs when you attempt to access an image you don't have
+              access to."
+            }
+            {
+              code: 501
+              message: "Not Implemented. This indicates that the server does not have
+              acess to the AWS_KEY and AWS_SECRET, or was configured incorrectly."
+            }
+          ]
+      validate:
+        params:
+          id:
+            Joi.string().regex(/^[0-9a-f]{24}$/)
+          mesId:
+            Joi.string().regex(/^[0-9a-f]{24}$/)
+        query:
+          access_token: Joi.string().min(1).lowercase().trim().when(
+            '$headers.authorization', {
+              is: Joi.exist(),
+              otherwise: Joi.forbidden()
+            })
+        headers:
+          authorization: Joi.string().trim().regex(/^Bearer\s[a-zA-Z0-9]+$/).when(
+            'query.access_token', {
+              is: Joi.exist(),
+              otherwise: Joi.forbidden()
+            })
+      response:
+        schema:
+          Joi.binary().required()
   }
 ]


browser.fire should accept selector/element followed by event name.

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -320,12 +320,18 @@
   # name - Even name (e.g `click`)
   # target - Target element (e.g a link)
   # callback - Wait for events to be processed, then call me (optional)
-  fire: (name, target, callback)->
+  fire: (selector, eventName, callback)->
     unless @window
       throw new Error("No window open")
-    type = if name in MOUSE_EVENT_NAMES then "MouseEvents" else "HTMLEvents"
-    event = @document.createEvent(type)
-    event.initEvent(name, true, true)
+    target = @query(selector)
+    unless target && target.dispatchEvent
+      throw new Error("No target element (note: call with selector/element, event name and callback)")
+    if ~MOUSE_EVENT_NAMES.indexOf(eventName)
+      eventType = "MouseEvents"
+    else
+      eventType = "HTMLEvents"
+    event = @document.createEvent(eventType)
+    event.initEvent(eventName, true, true)
     @window._dispatchEvent(target, event, false)
     return @wait(callback)

@@ -567,7 +573,7 @@
   clickLink: (selector, callback)->
     unless link = @link(selector)
       throw new Error("No link matching '#{selector}'")
-    return @fire("click", link, callback)
+    return @fire(link, "click", callback)

   # Return the history object.
   @prototype.__defineGetter__ "history", ->
@@ -672,7 +678,7 @@
       throw new Error("This INPUT field is readonly")
     field.focus()
     field.value = value
-    @fire "change", field, callback
+    @fire field, "change", callback
     return this

   _setCheckbox: (selector, value, callback)->
@@ -767,7 +773,7 @@
       select = @xpath("./ancestor::select", option).value[0]
       option.setAttribute("selected", "selected")
       select.focus()
-      @fire "change", select, callback
+      @fire select, "change", callback
     else if callback
       process.nextTick ->
         callback null, false
@@ -799,7 +805,7 @@
         throw new Error("Cannot unselect in single select")
       option.removeAttribute("selected")
       select.focus()
-      @fire "change", select, callback
+      @fire select, "change", callback
     else if callback
       process.nextTick ->
         callback null, false
@@ -824,7 +830,7 @@
       field.files.push file
       field.value = filename
     field.focus()
-    @fire "change", field, callback
+    @fire field, "change", callback
     return this

   # ### browser.button(selector) : Element
@@ -860,7 +866,7 @@
     if button.getAttribute("disabled")
       throw new Error("This button is disabled")
     button.focus()
-    return @fire("click", button, callback)
+    return @fire(button, "click", callback)

   # Cookies and storage


Update _export.coffee

Update _export.coffee

--- packages/meteor-template2/src/_export.coffee
+++ packages/meteor-template2/src/_export.coffee
@@ -19,5 +19,5 @@
   template.onRendered ->
     TemplateTwoWayBinding.rendered @
     return
-  template.onRendered config.onCreated if config.onRendered
+  template.onRendered config.onRendered if config.onRendered
   template.onDestroyed config.onDestroyed if config.onDestroyed


encapsulated choosing between ADAR and ADAR_SHENI in HebrewDate class

--- src/hebrewDate.coffee
+++ src/hebrewDate.coffee
@@ -19,7 +19,8 @@
   getDayOfMonth: -> @dayOfMonth
   monthAndRangeAre: (month, range) -> @staticHebrewMonth is HebrewMonth[month] && @dayOfMonth in range
   isShabbat: -> 6 == @gregorianDate.getDay()
-  isPurim: -> @monthAndRangeAre((if @hebrewYear.isLeapYear() then 'ADAR_SHENI' else 'ADAR'), [14])
+  adarOrAdar2: -> if @hebrewYear.isLeapYear() then 'ADAR_SHENI' else 'ADAR'
+  isPurim: -> @monthAndRangeAre(@adarOrAdar2(), [14])
   isMoed: -> @monthAndRangeAre('NISAN', [17..20]) || @monthAndRangeAre('TISHRI', [17..21])
   isPesach: -> @monthAndRangeAre('NISAN', [15..22])
   isShabuot: -> @monthAndRangeAre('SIVAN', [6..7])


Remove semicolons

Remove semicolons

--- src/sentry/static/sentry/coffee/app.coffee
+++ src/sentry/static/sentry/coffee/app.coffee
@@ -20,9 +20,9 @@

             @cursor = null
             @queue = new app.ScoredList
-            @poll();
+            @poll()

-            window.setInterval(@tick, 300);
+            window.setInterval(@tick, 300)

         tick: ->
             if !@queue.length
@@ -34,7 +34,7 @@
             @group_list.addMember(@queue.pop())

             # # shiny fx
-            # $row.css('background-color', '#ddd').animate({backgroundColor: '#fff'}, 1200);
+            # $row.css('background-color', '#ddd').animate({backgroundColor: '#fff'}, 1200)

         getPollUrl: ->
             app.config.urlPrefix + '/api/' + app.config.projectId + '/poll/'
@@ -42,11 +42,11 @@
         poll: ->
             poll_url = @getPollUrl
             if !@config.realtime
-                window.setTimeout(@poll, 1000);
+                window.setTimeout(@poll, 1000)
                 return

             data = app.utils.getQueryParams()
-            data.cursor = @cursor || undefined;
+            data.cursor = @cursor || undefined

             $.ajax
                 url: @getPollUrl()
@@ -94,7 +94,7 @@
             @views = {}

             # TODO:
-            Sentry.charts.render('#chart');
+            Sentry.charts.render('#chart')

             # load content for first tab and initialize
             $('a[data-toggle=ajtab]').click (e) =>
@@ -120,9 +120,9 @@
                         for item in data
                             view.addMember(data)
                         $parent.css('opacity', 1)
-                        $tab.tab('show');
+                        $tab.tab('show')
                     # error: ->
-                    #     $cont.html('
{% trans "There was an error fetching data from the server." %}
');
+                    #     $cont.html('
{% trans "There was an error fetching data from the server." %}
')

             # initialize active tabs
             $('li.active a[data-toggle=ajtab]').click()


Load previews by https

Load previews by https

--- app/scripts/lib/formatters.coffee
+++ app/scripts/lib/formatters.coffee
@@ -90,7 +90,7 @@
   YT_PREVIEW_FORMATTER:
     [/([^<]+)<\/a>/g
     , ->
-      ""
+      ""
     ]

   markdown: (raw) ->


Cache user in activity

Cache user in activity

--- core/app/backbone/models/activity.coffee
+++ core/app/backbone/models/activity.coffee
@@ -3,4 +3,4 @@
     new Activity(this)

   user: ->
-    new User(this.get('user'))
+    @_user ?= new User @get('user')


Remove unused require

Remove unused require

--- src/injections.coffee
+++ src/injections.coffee
@@ -1,7 +1,6 @@
 _ = require 'underscore-plus'
 {OnigScanner} = require 'oniguruma'

-Pattern = require './pattern'
 ScopeSelector = require './scope-selector'

 module.exports =


Adding additional gateway endpoints

Adding additional gateway endpoints

--- src/features/gateway.coffee
+++ src/features/gateway.coffee
@@ -9,3 +9,26 @@
 		List: (terms, callback, error) ->

 			return @m.Request 'gateways', 'GET', terms, callback, error
+
+		`// @if TARGET=='nodejs'
+		`
+		Fields: (slug = 0, callback, error) ->
+
+			uri  = 'gateways/'+ if slug != 0 then slug+'/fields' else 'fields'
+
+			return @m.Request uri, 'GET', null, callback, error
+
+		Update: (slug, data, callback, error) ->
+
+			return @m.Request 'gateways/'+slug, 'PUT', data, callback, error
+
+		Enable: (slug, callback, error) ->
+
+			return @m.Request 'gateways/'+slug+'/enable', 'GET', null, callback, error
+
+		Disable: (slug, callback, error) ->
+
+			return @m.Request 'gateways/'+slug+'/disable', 'GET', null, callback, error
+
+		`// @endif
+		`


refactor: add support for Expose

refactor: add support for Expose

--- src/lib/boogie.coffee
+++ src/lib/boogie.coffee
@@ -113,5 +113,8 @@

 # Expose object to the global namespace.
-root = if typeof exports is 'object' then exports else this
-root.Boogie = Boogie
+if expose?
+  expose Boogie, 'Boogie'
+else
+  root = if typeof exports is 'object' then exports else this
+  root.Boogie = Boogie