define (require) ->
  _ = require('underscore')
  Backbone = require('backbone')
  router = require('cs!router')
  BaseView = require('cs!helpers/backbone/views/base')
  #MailPopoverView = require('cs!./popovers/mail/mail')
  template = require('hbs!./title-template')
  require('less!./title')

  return class MediaTitleView extends BaseView
    template: template
    templateHelpers: () ->
      title = @model.get('title')

      # Set information used for social media links
      share =
        url: Backbone.history.fragment
        source: 'Connexions'
        summary: @model.get('abstract') or 'An OpenStax College book.'
        title: title or 'Untitled'
        image: @model.get('image') or "#{Backbone.history.location.host}/images/logo.png"

      # Encode all of the shared values for a URI
      _.each share, (value, key, list) ->
        list[key] = encodeURI(value)

      return {share: share, encodedTitle: encodeURI(title)}

    initialize: () ->
      super()
      @listenTo(@model, 'change:title', @render)
      @listenTo(router, 'navigate', @render)

    #onRender: () ->
    #  $share = @$el.find('.share')
    #  @attachPopover new MailPopoverView({owner: $share.find('.mail'), model: @model})

define (require) ->
  _ = require('underscore')
  Backbone = require('backbone')
  settings = require('cs!settings')
  router = require('cs!router')
  BaseView = require('cs!helpers/backbone/views/base')
  #MailPopoverView = require('cs!./popovers/mail/mail')
  template = require('hbs!./title-template')
  require('less!./title')

  return class MediaTitleView extends BaseView
    template: template
    templateHelpers: () ->
      title = @model.get('title')

      # Set information used for social media links
      share =
        url: location.origin + settings.root + Backbone.history.fragment
        source: 'Connexions'
        summary: @model.get('abstract') or 'An OpenStax College book.'
        title: title or 'Untitled'
        image: @model.get('image') or "#{Backbone.history.location.origin}/images/logo.png"

      # Encode all of the shared values for a URI
      _.each share, (value, key, list) ->
        list[key] = encodeURI(value)

      return {share: share, encodedTitle: encodeURI(title)}

    initialize: () ->
      super()
      @listenTo(@model, 'change:title', @render)
      @listenTo(router, 'navigate', @render)

    #onRender: () ->
    #  $share = @$el.find('.share')
    #  @attachPopover new MailPopoverView({owner: $share.find('.mail'), model: @model})

Include location.origin in URL for share links


ETahi.HeartbeatService = Em.Object.extend
  interval: 10 * 1000
  intervalId: null
  resource: null
  url: null

  init: ->
    resource = @get('resource')
    throw new Error("need to specify resource") unless resource
    @set('url', "#{resource.path()}/heartbeat")

  start: ->
    @heartbeat() # immediate heartbeat
    heartbeatWrapper = => @heartbeat()
    @set('intervalId', setInterval(heartbeatWrapper, @get('interval')))

  stop: ->
    iid = @get('intervalId')
    if iid
      clearInterval(iid)
      @set('iid', null)

  heartbeat: ->
    $.ajax
      url: @get('url')
      type: "PUT"
      headers:
        'Tahi-Heartbeat': true

ETahi.HeartbeatService = Em.Object.extend
  interval: 90 * 1000
  intervalId: null
  resource: null
  url: null

  init: ->
    resource = @get('resource')
    throw new Error("need to specify resource") unless resource
    @set('url', "#{resource.path()}/heartbeat")

  start: ->
    @heartbeat() # immediate heartbeat
    heartbeatWrapper = => @heartbeat()
    @set('intervalId', setInterval(heartbeatWrapper, @get('interval')))

  stop: ->
    iid = @get('intervalId')
    if iid
      clearInterval(iid)
      @set('iid', null)

  heartbeat: ->
    $.ajax
      url: @get('url')
      type: "PUT"
      headers:
        'Tahi-Heartbeat': true

Set heartbeat pulse to 90 seconds

Set heartbeat pulse to 90 seconds


class @Calendar
  options =
    month: "short"
    day: "numeric"
    year: "numeric"

  constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
    cal = new CalHeatMap()
    cal.init
      itemName: ["contribution"]
      data: timestamps
      start: new Date(starting_year, starting_month)
      domainLabelFormat: "%b"
      id: "cal-heatmap"
      domain: "month"
      subDomain: "day"
      range: 12
      tooltip: true
      label:
        position: "top"
      legend: [
        0
        10
        20
        30
      ]
      legendCellPadding: 3
      cellSize: $('.user-calendar').width() / 73
      onClick: (date, count) ->
        formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
        $.ajax
          url: calendar_activities_path
          data:
            date: formated_date
          cache: false
          dataType: "html"
          success: (data) ->
            $(".user-calendar-activities").html data

class @Calendar
  constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
    cal = new CalHeatMap()
    cal.init
      itemName: ["contribution"]
      data: timestamps
      start: new Date(starting_year, starting_month)
      domainLabelFormat: "%b"
      id: "cal-heatmap"
      domain: "month"
      subDomain: "day"
      range: 12
      tooltip: true
      label:
        position: "top"
      legend: [
        0
        10
        20
        30
      ]
      legendCellPadding: 3
      cellSize: $('.user-calendar').width() / 73
      onClick: (date, count) ->
        formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
        $.ajax
          url: calendar_activities_path
          data:
            date: formated_date
          cache: false
          dataType: "html"
          success: (data) ->
            $(".user-calendar-activities").html data

Remove unused "options" object from Calendar JS


class Skr.Components.CustomerFinder extends Lanes.React.Component

    propTypes:
        model:      Lanes.PropTypes.Model.isRequired
        commands:   React.PropTypes.object
        autoFocus:  React.PropTypes.bool
        name:       React.PropTypes.string
        selectField:   React.PropTypes.bool

    getDefaultProps: ->
        autoFocus: false, label: 'Customer Code', name: 'customer'

    dataObjects:
        query: ->
            new Lanes.Models.Query({
                syncOptions: @props.syncOptions
                src: Skr.Models.Customer, fields: [
                    {id:'id', visible: false}
                    { id: 'code', fixedWidth: 130 }, 'name', 'notes',
                    { id: 'open_balance', fixedWidth: 100, textAlign: 'right', format: (v) ->
                        if v then _.bigDecimal(v).toFixed(2) else '0.00'
                    }
                ]
            })

    render: ->
        props = _.clone(@props)

        if props.selectField
            <LC.SelectField sm=2 labelField="code" {...props} />
        else
            <LC.RecordFinder ref="finder" sm=3 autoFocus
                commands={@props.commands}
                query={@query}
                {...props} />

class Skr.Components.CustomerFinder extends Lanes.React.Component

    propTypes:
        model:      Lanes.PropTypes.Model.isRequired
        commands:   React.PropTypes.object
        autoFocus:  React.PropTypes.bool
        name:       React.PropTypes.string
        selectField:   React.PropTypes.bool

    getDefaultProps: ->
        autoFocus: false, label: 'Customer Code', name: 'customer'

    dataObjects:
        query: ->
            new Lanes.Models.Query({
                syncOptions: @props.syncOptions
                src: Skr.Models.Customer, fields: [
                    {id:'id', visible: false}
                    { id: 'code', fixedWidth: 130 }, 'name', 'notes',
                    { id: 'open_balance', fixedWidth: 100, textAlign: 'right', format: (v) ->
                        if v then _.bigDecimal(v).toFixed(2) else '0.00'
                    }
                ]
            })

    render: ->
        props = _.clone(@props)
        if @model.hasAttribute('customer_code')
            props.defaultLabel = @model.customer_code

        if props.selectField
            <LC.SelectField sm=2 labelField="code" {...props} />
        else
            <LC.RecordFinder ref="finder" sm=3 autoFocus
                commands={@props.commands}
                query={@query}
                {...props} />

Set a defaultLabel if it exists

Set a defaultLabel if it exists


class window.BoardView extends Backbone.View

    el: $('.board__tiles')

    initialize: ->
      @listenTo @collection, 'reset', @render
      @collection.view = @
      @collection.shuffle()

    render: =>
      els = []
      @$el.html('')
      @collection.each (tile) ->
        view = new TileView( model: tile )
        els.push view.render().el
      @$el.append els
      return @

class window.BoardView extends Backbone.View

    el: $('.board__tiles')

    initialize: ->
      @listenTo @collection, 'reset', @render
      @collection.shuffle()
      @collection.view = @

    render: =>
      $.fx.off = yes
      els = []
      @$el.html('')
      @collection.each (tile) ->
        view = new TileView( model: tile )
        els.push view.render().el
      @$el.append els
      _.delay ->
        $.fx.off = no
      , 100
      @

Disable animations on new game

Disable animations on new game


global = global  # the "global.global" might be deleted later

module.exports =
class CallbacksRegistry
  constructor: ->
    @emptyFunc = -> throw new Error "Browser trying to call a non-exist callback
      in renderer, this usually happens when renderer code forgot to release
      a callback installed on objects in browser when renderer was going to be
      unloaded or released."
    @callbacks = {}

  add: (callback) ->
    id = Math.random().toString()
    @callbacks[id] = callback
    id

  get: (id) ->
    @callbacks[id] ? ->

  call: (id, args...) ->
    @get(id).call global, args...

  apply: (id, args...) ->
    @get(id).apply global, args...

  remove: (id) ->
    delete @callbacks[id]

Fix running callback when global.global is deleted

Close #2366.


window.zooniverse ?= {}

EventEmitter = window.zooniverse?.EventEmitter or require 'zooniverse/lib/event-emitter'
$ = window.jQuery

class Api extends EventEmitter
  @current: null

  project: '.'

  headers: {}

  constructor: ({@project, @host, loadTimeout} = {}) ->
    super
    @select()

  request: (type, url, data, done, fail) ->
    dataType = 'json'
    headers = { }
    url = "#{@host}#{url}"
    if typeof data is 'function'
      [fail, done, data] = [done, data, null]

    if typeof data is 'object'
      data = JSON.parse JSON.stringify data

    request = $.ajax {type, url, data, dataType}
    request.done done
    request.fail fail

  get: ->
    @request 'get', arguments...

  getJSON: ->
    @request 'getJSON', arguments...

  post: ->
    @request 'post', arguments...

  put: ->
    @request 'put', arguments...

  delete: ->
    @request 'delete', arguments...

  select: ->
    @trigger 'select'
    @constructor.current = @

window.zooniverse.Api = Api
module?.exports = Api

window.zooniverse ?= {}

EventEmitter = window.zooniverse?.EventEmitter or require 'zooniverse/lib/event-emitter'
$ = window.jQuery

class Api extends EventEmitter
  @current: null

  project: '.'

  headers: {}

  constructor: ({@project, @host, loadTimeout} = {}) ->
    super
    @select()

  request: (type, url, data, done, fail) ->
    dataType = 'json'
    headers = { }
    url = "#{@host}#{url}"

    if typeof data is 'function'
      [fail, done, data] = [done, data, null]

    if typeof data is 'object' and data isnt null
      if data.classification
        data.classification.annotations.push 'facebook': 'true'
      data = JSON.parse JSON.stringify data

    request = $.ajax {type, url, data, dataType}
    request.done done
    request.fail fail

  get: ->
    @request 'get', arguments...

  getJSON: ->
    @request 'getJSON', arguments...

  post: ->
    @request 'post', arguments...

  put: ->
    @request 'put', arguments...

  delete: ->
    @request 'delete', arguments...

  select: ->
    @trigger 'select'
    @constructor.current = @

window.zooniverse.Api = Api
module?.exports = Api

Add facebook annotation to submitted classifications


kd            = require 'kd'

AdminIntegrationsListView           = require './adminintegrationslistview'
AdminConfiguredIntegrationsListView = require './adminconfiguredintegrationslistview'

module.exports = class AdminMembersView extends kd.View

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

    options.cssClass = 'integrations'

    super options, data

    @createTabView()

  createTabView: ->

    @addSubView tabView = @tabView = new kd.TabView hideHandleCloseIcons: yes

    tabView.addPane all        = new kd.TabPaneView name: 'All Services'
    tabView.addPane configured = new kd.TabPaneView name: 'Configured Integrations'

    @allListView        = new AdminIntegrationsListView           integrationType: 'new'
    @configuredListView = new AdminConfiguredIntegrationsListView integrationType: 'configured'

    all.addSubView @allListView
    configured.addSubView @configuredListView

    tabView.showPaneByIndex 0

    @allListView.on 'ShowConfiguredTab', =>
      tabView.showPaneByIndex 1
      @configuredListView.refresh()

  handleAction: (action) ->

    index = if action is 'Configure' then 1 else 0
    @tabView.showPaneByIndex index

kd            = require 'kd'

AdminIntegrationsListView           = require './adminintegrationslistview'
AdminConfiguredIntegrationsListView = require './adminconfiguredintegrationslistview'

module.exports = class AdminMembersView extends kd.View

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

    options.cssClass = 'integrations'

    super options, data

    @createTabView()

  createTabView: ->

    @addSubView tabView = @tabView = new kd.TabView hideHandleCloseIcons: yes

    tabView.addPane all        = new kd.TabPaneView name: 'All Services'
    tabView.addPane configured = new kd.TabPaneView name: 'Configured Integrations'

    @allListView        = new AdminIntegrationsListView           integrationType: 'new'
    @configuredListView = new AdminConfiguredIntegrationsListView integrationType: 'configured'

    all.addSubView @allListView
    configured.addSubView @configuredListView

    tabView.showPaneByIndex 0

    @allListView.on 'ShowConfiguredTab', =>
      tabView.showPaneByIndex 1
      @configuredListView.refresh()

  handleAction: (action) ->

    index = if action is 'Configured' then 1 else 0
    @tabView.showPaneByIndex index

Integrations: Fix typo to show configured tabs by router.


_ = require('underscore')
fs = require('fs')

class Config
  constructor: () ->
    filePath = @findConfig()
    data = @parse(filePath)
    @validate(data)
    return Object.freeze(data)

  findConfig: () ->
    if(process.env.HUBOT_PULSAR_CONFIG)
      return process.env.HUBOT_PULSAR_CONFIG
    hubotConfPath = './pulsar.config.json'
    if(fs.existsSync(hubotConfPath) && fs.statSync(hubotConfPath).isFile())
      return hubotConfPath
    return __dirname + '/../config.json'

  parse: (filePath) ->
    content = fs.readFileSync(filePath, {encoding: 'utf8'})
    return JSON.parse(content)

  validate: (data) ->
    pulsarApi = data.pulsarApi
    if(!pulsarApi)
      throw new Error('Define `pulsarApi` config options')

config = new Config()
module.exports = config

_ = require('underscore')
fs = require('fs')

class Config
  constructor: () ->
    filePath = Config.findConfig()
    data = @parse(filePath)
    @validate(data)
    return Object.freeze(data)

  parse: (filePath) ->
    content = fs.readFileSync(filePath, {encoding: 'utf8'})
    return JSON.parse(content)

  validate: (data) ->
    pulsarApi = data.pulsarApi
    if(!pulsarApi)
      throw new Error('Define `pulsarApi` config options')

  @findConfig: () ->
    if(process.env.HUBOT_PULSAR_CONFIG)
      return process.env.HUBOT_PULSAR_CONFIG
    hubotConfPath = './pulsar.config.json'
    if(fs.existsSync(hubotConfPath) && fs.statSync(hubotConfPath).isFile())
      return hubotConfPath
    return __dirname + '/../config.json'

config = new Config()
module.exports = config

Make `Config.findConfig` to be a static.


[
	{
		title: "@Atom Theme"
		paths: [
			"/Users/johngardner/Labs/Atom-PhoenixTheme"
		]
		devMode: true
	}
	{
		title: ".files"
		paths: [
			"/Users/johngardner/.files"
		]
		devMode: true
	}
	{
		title: "language-roff"
		paths: [
			"/Users/johngardner/Labs/language-roff"
		]
		devMode: true
	}
	{
		title: "file-icons"
		paths: [
			"/Users/johngardner/Labs/file-icons"
		]
		devMode: true
	}
	{
		title: "Atom-FS"
		paths: [
			"/Users/johngardner/Labs/Atom-FS"
		]
		devMode: true
	}
	{
		title: "Utils"
		paths: [
			"/Users/johngardner/Labs/Utils"
		]
		devMode: true
	}
]

[
	{
		title: "@Atom Theme"
		paths: [
			"/Users/johngardner/Labs/Atom-PhoenixTheme"
		]
		devMode: true
	}
	{
		title: ".files"
		paths: [
			"/Users/johngardner/.files"
		]
		devMode: true
	}
	{
		title: "language-roff"
		paths: [
			"/Users/johngardner/Labs/language-roff"
		]
		devMode: true
	}
	{
		title: "file-icons"
		paths: [
			"/Users/johngardner/Labs/file-icons"
		]
		devMode: true
	}
	{
		title: "Atom-FS"
		paths: [
			"/Users/johngardner/Labs/Atom-FS"
		]
		devMode: true
	}
	{
		title: "Utils"
		paths: [
			"/Users/johngardner/Labs/Utils"
		]
		devMode: true
	}
	{
		title: "GNU Groff"
		paths: [
			"/Users/johngardner/Mirrors/GNU-Groff"
		]
		devMode: true
	}
]

Add link to GNU Groff mirror in project listing


angular.module 'qbn.edsl', ['qbn.quality', 'qbn.storylet', 'qbn.choice']
  .factory 'qbnEdsl', (qualities, storylets, frontalChoices, choiceFactory) ->
    api =
      quality: (id, name, description, args = {}) ->
        {defaultValue, defaultProgress, maxProgress, hasProgress, progressEscalation,
         visible} = args
        defaultValue ?= 0
        defaultProgress ?= 0
        maxProgress ?= if hasProgress then 100 else 0
        progressEscalation ?= 0.10
        visible ?= true
        qualities.register id, name, description, defaultValue,
                                defaultProgress, maxProgress, progressEscalation,
                                visible
        return
      storylet: (id, title, text, choices = [], args = {}) ->
        {frontFacingChoice} = args
        storylets.register id, title, text, choices, frontFacingChoice
        return
      choice: (id, title, text, next, args = {}) ->
        {visibleReqs, activeReqs} = args
        visibleReqs ?= {}
        activeReqs ?= {}
        choiceFactory id, title, text, visibleReqs, activeReqs, args
      front: (choice) ->
        frontalChoices.register choice
        return
    return Object.freeze api

angular.module 'qbn.edsl', ['qbn.quality', 'qbn.storylet', 'qbn.choice']
  .factory 'qbnEdsl', (qualities, storylets, frontalChoices, choiceFactory) ->
    api =
      quality: (id, name, description, args = {}) ->
        {defaultValue, defaultProgress, maxProgress, hasProgress, progressEscalation,
         visible} = args
        defaultValue ?= 0
        defaultProgress ?= 0
        maxProgress ?= if hasProgress then 100 else 0
        progressEscalation ?= 0.10
        visible ?= true
        qualities.register id, name, description, defaultValue,
                                defaultProgress, maxProgress, progressEscalation,
                                visible
        return
      storylet: (id, title, text, choices = [], args = {}) ->
        {frontFacingChoice} = args
        storylets.register id, title, text, choices, frontFacingChoice
        return
      choice: (id, title, text, next, args = {}) ->
        {visibleReqs, activeReqs} = args
        visibleReqs ?= {}
        activeReqs ?= {}
        choiceFactory id, title, text, visibleReqs, activeReqs, next
      front: (choice) ->
        frontalChoices.register choice
        return
    return Object.freeze api

Fix Typo in Choice EDSL

Fix Typo in Choice EDSL


# Toggle the administration system.
($ document).keypress (e) ->
  ($ '.administration').toggleClass 'visible' if (e.keyCode || e.which) == 96

# "Hero" image fade-in.
window.onload = ->
  ($ '.hero-image img').animate({opacity: 1}, 300)

# Search field highlighting.
($ document).ready ->
  ($ '#id_q').focus ->
    ($ this).closest('.searchbar').addClass 'focus'
  ($ '#id_q').blur ->
    ($ this).closest('.searchbar').removeClass 'focus'

# NProgress-related calls.
($ document).on 'page:fetch', ->
  NProgress.start()

($ document).on 'page:change', ->
  NProgress.done()

($ document).on 'page:restore', ->
  NProgress.remove()

# Happenings-related calls.
($ document).ready ->
  # ($ '.happening-toggle').click ->
  ($ document).on 'click', '.happening-toggle', ->
    console.log 'what'
    ($ '.happening-decade-list').toggleClass 'visible'

# Toggle the administration system.
($ document).keypress (e) ->
  ($ '.administration').toggleClass 'visible' if (e.keyCode || e.which) == 96

# "Hero" image fade-in.
window.onload = ->
  ($ '.hero-image img').animate({opacity: 1}, 300)

# Search field highlighting.
($ document).ready ->
  ($ '#id_q').focus ->
    ($ this).closest('.searchbar').addClass 'focus'
  ($ '#id_q').blur ->
    ($ this).closest('.searchbar').removeClass 'focus'

# NProgress-related calls.
($ document).on 'page:fetch', ->
  NProgress.start()

($ document).on 'page:change', ->
  NProgress.done()

($ document).on 'page:restore', ->
  NProgress.remove()

# Happenings-related calls.
($ document).ready ->
  ($ document).on 'click', '.happening-toggle', ->
    ($ '.happening-decade-list').toggleClass 'visible'
    ($ '.happening-toggle .ss-icon:last-child').toggleClass 'ss-dropdown ss-directup'

Switch that dropdown icon when we toggle things.


#
# Inject common project-wide [view locals](http://expressjs.com/api.html#app.locals).
#

# uuid = require 'node-uuid'
{ parse, format } = require 'url'
_ = require 'underscore'
{ NODE_ENV } = require '../../config'

module.exports = (req, res, next) ->
  res.locals.sd.CURRENT_PATH = parse(req.url).pathname
  res.locals._ = _
  res.locals.homeHref = req.user?.homePath() or '/'

  # TODO: remove after campaign
  # only show if the user is logged in and confirmed
  # and hasn't closed the CTA
  res.locals.showInvestCTA = (!req.cookies['invest_cta']? and req.user and req.user.get('is_confirmed'))

  next()

#
# Inject common project-wide [view locals](http://expressjs.com/api.html#app.locals).
#

# uuid = require 'node-uuid'
{ parse, format } = require 'url'
_ = require 'underscore'
{ NODE_ENV } = require '../../config'

module.exports = (req, res, next) ->
  res.locals.sd.CURRENT_PATH = parse(req.url).pathname
  res.locals._ = _
  res.locals.homeHref = req.user?.homePath() or '/'

  # respect do not track headers
  res.locals.doNotTrack = res.locals.sd.DO_NOT_TRACK = req.headers.dnt

  # TODO: remove after campaign
  # only show if the user is logged in and confirmed
  # and hasn't closed the CTA
  res.locals.showInvestCTA = (!req.cookies['invest_cta']? and req.user and req.user.get('is_confirmed'))

  next()

Set DNT setting on middleware

Set DNT setting on middleware


fs = require 'fs'
path = require 'path'
chalk = require 'chalk'
request = require 'request'
restapi = require './restapi'
validator = require './validator'
bundle = require './bundle'

module.exports = (dir, options, callback) ->
  console.log chalk.yellow 'Validating gadget'
  apiUrl = options.apiUrl + '/gadgets'

  validator.checkProject dir, options, (err) ->
    if err then return callback err
    # If we could fix receiving endpoint, we could do
    # reader.pipe(tar.Pack()).pipe(request.post(...))
    bundle.createBundle dir, (err, bundleStream) ->
      if err then return callback err
      uploadBundleToRestAPI apiUrl, bundleStream, options, callback

uploadBundleToRestAPI = (apiUrl, bundleStream, options, callback) ->
  opts =
    url: apiUrl
    headers:
      SID: options.sessionId

  req = request.post opts, restapi.jsonResponseHandler(callback)
  req.form().append('content', bundleStream)

fs = require 'fs'
path = require 'path'
chalk = require 'chalk'
request = require 'request'
restapi = require './restapi'
validator = require './validator'
bundle = require './bundle'

module.exports = (dir, options, callback) ->
  console.log chalk.yellow 'Validating gadget'
  apiUrl = options.apiUrl + '/gadgets'

  validator.checkProject dir, options, (err) ->
    if err then return callback err
    bundle.createBundle dir, (err, bundleStream) ->
      if err then return callback err
      uploadBundleToRestAPI apiUrl, bundleStream, options, callback

uploadBundleToRestAPI = (apiUrl, bundleStream, options, callback) ->
  opts =
    url: apiUrl
    headers:
      SID: options.sessionId

  req = request.post opts, restapi.jsonResponseHandler(callback)
  req.form().append('content', bundleStream)

Comment makes less sense now

Comment makes less sense now


define (require) ->
  Backbone = require('backbone')
  settings = require('settings')

  # Class to handle loading analytics scripts and wrapping
  # handlers around them so that modules don't have to
  # interact with global variables directly
  return new class AnalyticsHandler
    constructor: () ->
      # Setup temporary analytics.js objects
      window.GoogleAnalyticsObject = 'ga'
      window.ga = () -> (window.ga.q ?= []).push(arguments)
      window.ga.l = 1 * new Date()

      # Initialize analytics.js account
      window.ga('create', settings.analyticsID, 'auto')

      # ## Setup ga.js
      #window._gaq ?= []

      # Asynchronously load analytics.js.
      require(['https://www.google-analytics.com/analytics.js'])

      # Asynchronously load ga.js
      #require(['https://www.google-analytics.com/ga.js'])

    # Wrapper functions to add analytics events
    ga: () -> window.ga?.apply(@, arguments) # analytics.js
    #gaq: () -> window._gaq?.push(arguments[0], arguments[1]) # ga.js

    # Send the current page to every analytics service
    send: (account, fragment = Backbone.history.fragment) ->
      if not /^\//.test(fragment) then fragment = '/' + fragment

      # Use the default analytics ID in settings if no account is specified
      account ?= settings.analyticsID

      @ga('send', 'pageview')
      #@gaq(['_setAccount', account], ['_trackPageview', fragment])

define (require) ->
  Backbone = require('backbone')
  settings = require('settings')

  # Class to handle loading analytics scripts and wrapping
  # handlers around them so that modules don't have to
  # interact with global variables directly
  return new class AnalyticsHandler
    constructor: () ->
      # Setup temporary analytics.js objects
      window.GoogleAnalyticsObject = 'ga'
      window.ga = () -> (window.ga.q ?= []).push(arguments)
      window.ga.l = 1 * new Date()

      # Initialize analytics.js account
      window.ga('create', settings.analyticsID, 'auto')

      # ## Setup ga.js
      #window._gaq ?= []

      # Asynchronously load analytics.js.
      require(['https://www.google-analytics.com/analytics.js'])

      # Asynchronously load ga.js
      #require(['https://www.google-analytics.com/ga.js'])

    # Wrapper functions to add analytics events
    ga: () -> window.ga?.apply(@, arguments) # analytics.js
    #gaq: () -> window._gaq?.push(arguments[0], arguments[1]) # ga.js

    # Send the current page to every analytics service
    send: (account, fragment = Backbone.history.fragment) ->
      if not /^\//.test(fragment) then fragment = '/' + fragment

      # Use the default analytics ID in settings if no account is specified
      account ?= settings.analyticsID
      ga('create', account, 'auto', account)

      @ga("#{account}.send", 'pageview')
      #@gaq(['_setAccount', account], ['_trackPageview', fragment])

Fix handling of multiple GA tracking numbers


# Paper Input components snippets

".text.html":
  "paper-input-container":
    "prefix": "paper-input"
    "body": """
    <paper-input-container id="$1">
      <label>${2:Input label}</label>
      <input is="iron-input"$3>
    </paper--container>
    """
  "paper input error":
    "prefix": "paper-input-error"
    "body": """
    <paper-input-error>${1:Only numbers are allowed!}</paper-input-error>
    """
  "paper-textarea":
    "prefix": "paper-textarea"
    "body": """
    <paper-textarea id="$1" label="${2:Textarea label}"$3></paper-textarea>
    """

# Paper Input components snippets

".text.html":
  "paper-input-container":
    "prefix": "paper-input-container"
    "body": """
    <paper-input-container id="$1">
      <label>${2:Input label}</label>
      <input is="iron-input"$3>
    </paper--container>
    """
  "paper input error":
    "prefix": "paper-input-error"
    "body": """
    <paper-input-error>${1:Only numbers are allowed!}</paper-input-error>
    """
  "paper-textarea":
    "prefix": "paper-textarea"
    "body": """
    <paper-textarea id="$1" label="${2:Textarea label}"$3></paper-textarea>
    """

Fix improperly named snippet prefix

Fix improperly named snippet prefix


# Allow `.coffee` files in `require()`.
require 'coffee-script'
fs = require 'fs'
_ = require 'underscore'

HOME = process.env.HOME

class Impromptu
  paths: ["#{HOME}/.impromptu.coffee", "#{HOME}/.impromptu.js", "#{HOME}/.impromptu"]

  constructor: ->
    @prompt = new Impromptu.Prompt

    configPath = _.find @paths, (path) ->
      fs.existsSync path

    return unless configPath

    # Load a new Impromptu module from a file.
    configFile = require configPath
    return unless typeof configFile == 'function'

    # Go!
    configFile Impromptu, @prompt.section

# Expose `Impromptu`.
exports = module.exports = Impromptu

# Expose APIs.
exports.Prompt = require './prompt'
exports.color = require './color'
exports.db = require './db'
exports.module = require './module'

# Allow `.coffee` files in `require()`.
require 'coffee-script'
fs = require 'fs'
_ = require 'underscore'

CONFIG_DIR = "#{process.env.HOME}/.impromptu"

class Impromptu
  paths: "#{CONFIG_DIR}/prompt.#{ext}" for ext in ['coffee', 'js']

  constructor: ->
    @prompt = new Impromptu.Prompt

    configPath = _.find @paths, (path) ->
      fs.existsSync path

    return unless configPath

    # Load a new Impromptu module from a file.
    configFile = require configPath
    return unless typeof configFile == 'function'

    # Go!
    configFile Impromptu, @prompt.section

# Expose `Impromptu`.
exports = module.exports = Impromptu

# Expose APIs.
exports.Prompt = require './prompt'
exports.color = require './color'
exports.db = require './db'
exports.module = require './module'

Move prompt config file to subdirectory

Move prompt config file to subdirectory

We want to allow prompts to use modules, but we shouldn't make your whole home directory the parent for our node_modules directory.

Uses ~/.impromptu and renames the file to prompt.(js|coffee), which is now more descriptive.


Router = require 'routes'
_ = require 'lodash'

noService = require './noService'
resolve = require './resolve'
makeRouter = require './makeRouter'

# Given a collection of route defs with resolved law services
# and a request object, return an object containing the service,
# and the HTTP request-extracted arguments we wish to pass to it.
#   services :: already-initialized law services
#   routeDefs :: config object for RESTful routing
makeAdapter = (services, routeDefs) ->

  resolved = resolve services, routeDefs

  match = (req) ->
    method = req.method.toLowerCase()
    pathname = req._parsedUrl.pathname

    router = makeRouter resolved
    found = router.match pathname

    return {
      service: found?.fn[method] || noService
      pathArgs: found?.params
    }

  # Return a piece of connect middleware
  (req, res) ->

    {service, pathArgs} = match req

    # This assumes that connect.bodyParser has been applied
    # earlier in the middleware chain.
    {body, query, cookies} = req
    args = _.merge {}, pathArgs, query, cookies, body

    service args, (err, result) ->
      switch err?.message
        when '501 Not Implemented'
          statusCode = 501
        else
          if err?
            console.log {err}
            statusCode = 500
          else
            statusCode = 200

      contentType = 'application/json'

      res.writeHead statusCode, contentType
      toSend = JSON.stringify result
      res.end (JSON.stringify result)

module.exports = makeAdapter

Router = require 'routes'
_ = require 'lodash'

noService = require './noService'
resolve = require './resolve'
makeRouter = require './makeRouter'
expandResources = require './expandResources'

# Given a collection of route defs with resolved law services
# and a request object, return an object containing the service,
# and the HTTP request-extracted arguments we wish to pass to it.
#   services :: already-initialized law services
#   routeDefs :: config object for RESTful routing
makeAdapter = (services, routeDefs) ->

  expandedDefs = expandResources routeDefs
  resolved = resolve services, expandedDefs

  match = (req) ->
    method = req.method.toLowerCase()
    pathname = req._parsedUrl.pathname

    router = makeRouter resolved
    found = router.match pathname

    return {
      service: found?.fn[method] || noService
      pathArgs: found?.params
    }

  # Return a piece of connect middleware
  (req, res) ->

    {service, pathArgs} = match req

    # This assumes that connect.bodyParser has been applied
    # earlier in the middleware chain.
    {body, query, cookies} = req
    args = _.merge {}, pathArgs, query, cookies, body

    service args, (err, result) ->
      switch err?.message
        when '501 Not Implemented'
          statusCode = 501
        else
          if err?
            console.log {err}
            statusCode = 500
          else
            statusCode = 200

      contentType = 'application/json'

      res.writeHead statusCode, contentType
      toSend = JSON.stringify result
      res.end (JSON.stringify result)

module.exports = makeAdapter

Include resource expansion in middleware route def processing


module.exports =
  processResponse: (res) ->
    # Check to see if the contentype is "something/json" or
    # "something/somethingelse+json"
    if res.contentType and (/^.*\/(?:.*\+)?json(;|\z)/i).test res.contentType
      # If the body hasn't been parsed yet, parse it.
      raw = if typeof res.body is 'string' then res.body else res.text
      res.body = JSON.parse raw if raw

module.exports =
  processResponse: (res) ->
    # Check to see if the contentype is "something/json" or
    # "something/somethingelse+json"
    if res.contentType and (/^.*\/(?:.*\+)?json(;|$)/i).test res.contentType
      # If the body hasn't been parsed yet, parse it.
      raw = if typeof res.body is 'string' then res.body else res.text
      res.body = JSON.parse raw if raw

Use $ to match end of string

Use $ to match end of string

It seems like some JS RE engines don't support \z


# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
if window.location.pathname.match(/\/apply$/)
	$ ->

		changeSection = (e, noscroll)->
			hash = window.location.hash
			sectionId = hash.substring 1
			$('.section').each ()->
				section = $ @
				if section.attr('id') is sectionId
					section.toggleClass 'hide', false
				else
					section.toggleClass 'hide', true
			$('.wizard-nav a').each ()->
				link = $ @
				parent = link.parent()
				if link.attr('href') is hash
					parent.toggleClass 'active', true
				else
					parent.toggleClass 'active', false
			window.scrollTo 0, 0 unless noscroll

		$(window).on 'hashchange', changeSection

		showDownloadWarning = ->
			$('#download-warning').modal()

		submitUpdateForm = ->
			$('#update-button').submit()

		initialize = (e)->
			$('.download-forms').click(
				showDownloadWarning
				submitUpdateForm
			)
			firstSectionHash = $('.wizard-nav a').eq(0).attr 'href'
			if window.location.hash.length <= 1
				window.location.hash = firstSectionHash
			else
				changeSection null, !!e

		$(document).ajaxComplete initialize
		initialize()

Use spaces instead of tabs for coffeescript indent

As dictated by coffeelint, and the coffeescript style guide at
https://github.com/polarmobile/coffeescript-style-guide


define ['jquery', 'semantic', 'utils', 'charts'], ($, semantic, utils, charts) ->

  currentCity = utils.getCookie('city')
  availableCity = ['Guangzhou', 'Beijing', 'Shanghai', 'Chengdu', 'Shenyang']
  currentCity = 'Guangzhou' if currentCity not in availableCity
  charts.renderChart currentCity

  # listen city click
  for city in availableCity
    $("##{city}").click () ->
      utils.setCookie 'city', city, 7
      return false
  $(".ui.dropdown").dropdown
    on: 'hover'
  return

define ['jquery', 'semantic', 'utils', 'charts'], ($, semantic, utils, charts) ->

  currentCity = utils.getCookie('city')
  availableCity = ['Guangzhou', 'Beijing', 'Shanghai', 'Chengdu', 'Shenyang']
  currentCity = 'Guangzhou' if currentCity not in availableCity
  charts.renderChart currentCity

  # listen city click
  # FIXME: clean this quick and dirty code
  $('#Guangzhou').click () ->
    utils.setCookie 'city', 'Guangzhou', 7
    charts.renderChart 'Guangzhou'
    return false
  $('#Beijing').click () ->
    utils.setCookie 'city', 'Beijing', 7
    charts.renderChart 'Beijing'
    return false
  $('#Shanghai').click () ->
    utils.setCookie 'city', 'Shanghai', 7
    charts.renderChart 'Shanghai'
    return false
  $('#Shenyang').click () ->
    utils.setCookie 'city', 'Shenyang', 7
    charts.renderChart 'Shenyang'
    return false
  $('#Chengdu').click () ->
    utils.setCookie 'city', 'Chengdu', 7
    charts.renderChart 'Chengdu'
    return false
  $(".ui.dropdown").dropdown
    on: 'hover'
  return

Revert "Clean dropdown binding code"

Revert "Clean dropdown binding code"

This reverts commit fda52379e8674e44445ad7f0c246f88ca036dd6d.
Code is broken


# DocPad Configuration File
# http://docpad.org/docs/config

path = require 'path'

#@poweredByDocPad = false

@templateData =
  site:
    title: "My Website"

  getPreparedTitle: ->
    if @document.title
      "#{@document.title} | #{@site.title}"
    else
      @site.title

@collections =
  pages: ->
    @getCollection "html"
    .findAllLive isPage:true
    .on "add", (model) ->
      model.setMetaDefaults layout:"default"

@plugins =
  less:
    parseOptions:
      paths: ["#{__dirname}/node_modules"]
  xcopy:
    fonts:
      src: path.join require.resolve('font-awesome/package'), '..', 'fonts'
      dst: 'fonts'
  webpack:
    optimize:
      minimize: true

@environments =
  development:
    plugins:
      webpack:
        optimize:
          minimize: false

# DocPad Configuration File
# http://docpad.org/docs/config

path = require 'path'

#@poweredByDocPad = false

@templateData =
  site:
    title: "My Website"

  getPreparedTitle: ->
    if @document.title
      "#{@document.title} | #{@site.title}"
    else
      @site.title

@collections =
  pages: ->
    @getCollection "html"
    .findAllLive isPage:true
    .on "add", (model) ->
      model.setMetaDefaults layout:"default"

@plugins =
  less:
    parseOptions:
      paths: path.join __dirname, "node_modules"
  xcopy:
    fonts:
      src: path.join require.resolve('font-awesome/package'), '..', 'fonts'
      dst: 'fonts'
  webpack:
    optimize:
      minimize: true

@environments =
  development:
    plugins:
      webpack:
        optimize:
          minimize: false

Use path, since required it

Use path, since required it


BaseStackEditorView = require './basestackeditorview'

module.exports = class StackTemplateEditorView extends BaseStackEditorView

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

    unless options.content
      options.content = require '../defaulttemplate'

    super options, data

    ace = @getAce()

    @once 'EditorReady', =>

      unless options.showHelpContent
        return ace.contentChanged = no

      position = row: 0, column: 0
      content  = """
        # Here is your stack preview
        # You can make advanced changes like modifying your VM,
        # installing packages, and running shell commands.

      """

      ace.editor.session.insert position, content
      ace.contentChanged = no

BaseStackEditorView = require './basestackeditorview'

module.exports = class StackTemplateEditorView extends BaseStackEditorView

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

    unless options.content
      options.content = require '../defaulttemplate'

    super options, data

    if options.showHelpContent
      @once 'EditorReady', @bound 'insertHelpText'

  insertHelpText: ->

    position = row: 0, column: 0
    content  = """
      # Here is your stack preview
      # You can make advanced changes like modifying your VM,
      # installing packages, and running shell commands.

    """

    ace = @getAce()
    ace.editor.session.insert position, content
    ace.contentChanged = no

Refactor inserting help text into stack template editor


ipc = require 'ipc'
webContents = require 'web-contents'
webViewManager = null  # Doesn't exist in early initialization.

nextInstanceId = 0
guestInstances = {}

# Generate guestInstanceId.
getNextInstanceId = (webContents) ->
  ++nextInstanceId

# Create a new guest instance.
createGuest = (embedder, params) ->
  webViewManager ?= process.atomBinding 'web_view_manager'

  id = getNextInstanceId embedder
  guestInstances[id] = webContents.create
    isGuest: true
    guestInstanceId: id
    storagePartitionId: params.storagePartitionId
  webViewManager.addGuest id, embedder, guestInstances[id]
  id

# Destroy an existing guest instance.
destroyGuest = (id) ->
  webViewManager.removeGuest id
  guestInstances[id].destroy()
  delete guestInstances[id]

ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, type, params, requestId) ->
  event.sender.send "ATOM_SHELL_RESPONSE_#{requestId}", createGuest(event.sender, params)

ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, guestInstanceId) ->
  destroyGuest guestInstanceId

ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_AUTO_SIZE', (event, guestInstanceId, params) ->
  guestInstances[id]?.setAutoSize params.enableAutoSize, params.min, params.max

ipc = require 'ipc'
webContents = require 'web-contents'
webViewManager = null  # Doesn't exist in early initialization.

nextInstanceId = 0
guestInstances = {}

# Generate guestInstanceId.
getNextInstanceId = (webContents) ->
  ++nextInstanceId

# Create a new guest instance.
createGuest = (embedder, params) ->
  webViewManager ?= process.atomBinding 'web_view_manager'

  id = getNextInstanceId embedder
  guest = webContents.create
    isGuest: true
    guestInstanceId: id
    storagePartitionId: params.storagePartitionId
  guestInstances[id] = guest
  webViewManager.addGuest id, embedder, guest

  # Destroy guest when the embedder is gone.
  embedder.once 'render-view-deleted', -> destroyGuest id

  id

# Destroy an existing guest instance.
destroyGuest = (id) ->
  webViewManager.removeGuest id
  guestInstances[id].destroy()
  delete guestInstances[id]

ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, type, params, requestId) ->
  event.sender.send "ATOM_SHELL_RESPONSE_#{requestId}", createGuest(event.sender, params)

ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, guestInstanceId) ->
  destroyGuest guestInstanceId

ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_AUTO_SIZE', (event, guestInstanceId, params) ->
  guestInstances[id]?.setAutoSize params.enableAutoSize, params.min, params.max

Destroy guest when the embedder is gone

Destroy guest when the embedder is gone


window.Backbone ||= {}
window.Backbone.Views ||= {}

class Backbone.Views.VisualisationView extends Backbone.Diorama.NestingView
  template: Handlebars.templates['visualisation.hbs']

  events:
    "click .download-visualisation": "download"

  initialize: (options) ->
    @visualisation = options.visualisation
    # If we haven't fixed the JSON saving bug - will be removed
    if typeof @visualisation.get('data') is 'string'
      @visualisation.set('data', Backbone.Faker.Reports.createFakeData())

  download: ->
    window.location = @visualisation.buildIndicatorDownloadUrl('csv')

  render: =>
    @closeSubViews()
    @$el.html(@template(
      thisView: @
      visualisation: @visualisation
      visualisationViewName: @visualisation.get('type') + "View"
    ))
    @renderSubViews()

    return @

  onClose: ->
    @closeSubViews()

window.Backbone ||= {}
window.Backbone.Views ||= {}

class Backbone.Views.VisualisationView extends Backbone.Diorama.NestingView
  template: Handlebars.templates['visualisation.hbs']

  events:
    "click .download-visualisation": "download"

  initialize: (options) ->
    @visualisation = options.visualisation
    # If we haven't fixed the JSON saving bug - will be removed
    if typeof @visualisation.get('data') is 'string'
      @visualisation.set('data', Backbone.Faker.Reports.createFakeData())

  download: ->
    window.location = @visualisation.buildIndicatorDataUrl('csv')

  render: =>
    @closeSubViews()
    @$el.html(@template(
      thisView: @
      visualisation: @visualisation
      visualisationViewName: @visualisation.get('type') + "View"
    ))
    @renderSubViews()

    return @

  onClose: ->
    @closeSubViews()

Fix typo in visualisation download url

Fix typo in visualisation download url


noflo = require 'noflo'
animated = require 'animated-gif-detector'
fs = require 'fs'
# @runtime noflo-nodejs
# @name DetectAnimatedGif

exports.getComponent = ->
  c = new noflo.Component

  c.icon = 'expand'
  c.description = 'Detect if a given GIF is animated'

  c.inPorts.add 'buffer',
    datatype: 'all'
    description: 'An image buffer'
  c.outPorts.add 'animated',
    datatype: 'boolean'

  noflo.helpers.WirePattern c,
    in: ['buffer']
    out: ['animated']
    async: true
    forwardGroups: true
  , (buffer, groups, out, callback) ->
    if Buffer.isBuffer buffer
      out.send animated buffer
      do callback
      return
    else if typeof buffer is 'string'
      isAnimated = false
      fs.createReadStream buffer
        .pipe animated()
        .once 'animated', ->
          isAnimated = true
          out.send true
          do callback
          return
        .on 'finish', ->
          unless isAnimated
            out.send false
          do callback
          return
    else
      out.send false
      do callback
      return

  c

noflo = require 'noflo'
animated = require 'animated-gif-detector'
fs = require 'fs'
# @runtime noflo-nodejs
# @name DetectAnimatedGif

exports.getComponent = ->
  c = new noflo.Component

  c.icon = 'expand'
  c.description = 'Detect if a given GIF is animated'

  c.inPorts.add 'buffer',
    datatype: 'all'
    description: 'An image buffer'
  c.outPorts.add 'animated',
    datatype: 'boolean'

  noflo.helpers.WirePattern c,
    in: ['buffer']
    out: ['animated']
    async: true
    forwardGroups: true
  , (buffer, groups, out, callback) ->
    if Buffer.isBuffer buffer
      out.send animated buffer
      do callback
      return
    else if typeof buffer is 'string'
      isAnimated = false
      fs.createReadStream buffer
        .pipe animated()
        .once 'animated', ->
          isAnimated = true
          return
        .on 'finish', ->
          out.send isAnimated
          do callback
          return
    else
      out.send false
      do callback
      return

  c

Send only on finish event

Send only on finish event


Feeds = require '/imports/collections/feeds'
{ regexEscape } = require '/imports/utils'

Meteor.methods
  addFeed: (feed) ->
    url = feed.url
    user = Meteor.user()
    urlWithoutProtocol = url.replace(/^https?\:\/\//i, '');
    regex = new RegExp "^https?:\/\/#{regexEscape(urlWithoutProtocol)}"
    if Feeds.findOne(url: {$regex: regex})
      if Meteor.isServer
        throw new Meteor.Error('duplicate_entry', "#{url} has already been added.")
      return

    if not Roles.userIsInRole(user._id, ['admin'])
      if Meteor.isServer
        throw new Meteor.Error('auth', 'User does not have permission to create incident reports')
      return

    feed.addedByUserId = user._id
    feed.addedByUserName = user.profile.name
    feed.addedDate = new Date()
    Feeds.insert feed

  removeFeed: (feedId) ->
    if not Roles.userIsInRole(user._id, ['admin'])
      if Meteor.isServer
        throw new Meteor.Error('auth', 'User does not have permission to delete incident reports')
      return
    Feeds.remove feedId

Feeds = require '/imports/collections/feeds'
{ regexEscape } = require '/imports/utils'

Meteor.methods
  addFeed: (feed) ->
    url = feed.url
    user = Meteor.user()
    urlWithoutProtocol = url.replace(/^https?\:\/\//i, '');
    regex = new RegExp "^https?:\/\/#{regexEscape(urlWithoutProtocol)}"
    if Feeds.findOne(url: {$regex: regex})
      if Meteor.isServer
        throw new Meteor.Error('duplicate_entry', "#{url} has already been added.")
      return

    if not Roles.userIsInRole(user._id, ['admin'])
      if Meteor.isServer
        throw new Meteor.Error('auth', 'User does not have permission to create incident reports')
      return

    feed.addedByUserId = user._id
    feed.addedByUserName = user.profile.name
    feed.addedDate = new Date()
    Feeds.insert feed

  removeFeed: (feedId) ->
    if not Roles.userIsInRole(Meteor.user()._id, ['admin'])
      if Meteor.isServer
        throw new Meteor.Error('auth', 'User does not have permission to delete incident reports')
      return
    Feeds.remove feedId

Fix feed removal - get user id by calling meteors user method


###
#
###
SpViewBuilder = (ContentBlockService, builder) ->
	builder.build = ($routeParams) ->
		blocks = []
		compile = () ->
			blocks.reverse()
			template = blocks[0].body
			blocks = blocks[1..-1]
			angular.forEach blocks, (block) ->
				template = template.replace("<sp-yield></sp-yield>", block.body)
			template
		ContentBlockService.find_by_slug($routeParams.slug)
			.then (block) ->
				blocks.push(block)
				if block.layout_block_id?
					ContentBlockService.find(block.layout_block_id)
					.then (layout) ->
						block = layout
						blocks.push(layout)
						if block.layout_block_id?
							ContentBlockService.find(block.layout_block_id)
							.then (layout) ->
								block = layout
								blocks.push(layout)
								compile()
						else
							compile()

	service =
		builder: builder
	service

angular.module('sp.client').provider 'SpViewBuilder', ->
	builder =
		build: ($routeParams) ->
			""
	@template = ($routeParams) ->
		builder.build($routeParams)
	@$get = ['ContentBlockService', (ContentBlockService) ->
		# Returns the view builder service
		return new SpViewBuilder(ContentBlockService, builder)
	]
	@

###
#
###
SpViewBuilder = (ContentBlockService, builder) ->
	builder.build = ($routeParams) ->
		blocks = []
		compile = () ->
			blocks.reverse()
			template = blocks[0].body
			blocks = blocks[1..-1]
			angular.forEach blocks, (block) ->
				template = template.replace("<sp-yield></sp-yield>", block.body)
			template
		getLayout = (layout_block_id) ->
			ContentBlockService.find(layout_block_id)
				.then (layout) ->
					block = layout
					blocks.push(layout)
					if block.layout_block_id?
						getLayout(block.layout_block_id)

		ContentBlockService.find_by_slug($routeParams.slug)
			.then (block) ->
				blocks.push(block)
				if block.layout_block_id?
					getLayout(block.layout_block_id)
						.then ->
							compile()
				else
					compile()

	service =
		builder: builder
	service

angular.module('sp.client').provider 'SpViewBuilder', ->
	builder =
		build: ($routeParams) ->
			""
	@template = ($routeParams) ->
		builder.build($routeParams)
	@$get = ['ContentBlockService', (ContentBlockService) ->
		# Returns the view builder service
		return new SpViewBuilder(ContentBlockService, builder)
	]
	@

Allow for limitless nesting of layouts

Allow for limitless nesting of layouts


#==============================================================================
# lib/runner.js module
#==============================================================================
describe 'runner', ->
  loadFile = require('mocks').loadFile
  constant = require '../../lib/constants'
  m = null

  beforeEach ->
    m = loadFile __dirname + '/../../lib/runner.js'

  #============================================================================
  # runner.parseExitCode
  #============================================================================
  describe 'parseExitCode', ->
    EXIT0 = constant.EXIT_CODE_0

    it 'should return 0 exit code if present in the buffer', ->
      expect(m.parseExitCode new Buffer 'something\nfake' + EXIT0).toBe 0

    it 'should null the exit code part of the buffer', ->
      buffer = new Buffer 'some' + EXIT0
      m.parseExitCode buffer

      expect(buffer.toString()).toBe 'some\000\000\000\000\000\000'

    it 'should not touch buffer without exit code and return default', ->
      msg = 'some nice \n messgae {}'
      buffer = new Buffer msg
      code = m.parseExitCode buffer, 10

      expect(buffer.toString()).toBe msg
      expect(code).toBe 10

    it 'should not slice buffer if smaller than exit code msg', ->
      # regression
      fakeBuffer = {length: 1, slice: -> null}
      spyOn fakeBuffer, 'slice'

      code = m.parseExitCode fakeBuffer, 10
      expect(fakeBuffer.slice).not.toHaveBeenCalled()

#==============================================================================
# lib/runner.js module
#==============================================================================
describe 'runner', ->
  loadFile = require('mocks').loadFile
  constant = require '../../lib/constants'
  m = null

  beforeEach ->
    m = loadFile __dirname + '/../../lib/runner.js'

  #============================================================================
  # runner.parseExitCode
  #============================================================================
  describe 'parseExitCode', ->
    EXIT0 = constant.EXIT_CODE_0

    it 'should return 0 exit code if present in the buffer', ->
      expect(m.parseExitCode new Buffer 'something\nfake' + EXIT0).toBe 0

    it 'should null the exit code part of the buffer', ->
      buffer = new Buffer 'some' + EXIT0
      m.parseExitCode buffer

      expect(buffer.toString()).toBe 'some\0\0\0\0\0\0'

    it 'should not touch buffer without exit code and return default', ->
      msg = 'some nice \n messgae {}'
      buffer = new Buffer msg
      code = m.parseExitCode buffer, 10

      expect(buffer.toString()).toBe msg
      expect(code).toBe 10

    it 'should not slice buffer if smaller than exit code msg', ->
      # regression
      fakeBuffer = {length: 1, slice: -> null}
      spyOn fakeBuffer, 'slice'

      code = m.parseExitCode fakeBuffer, 10
      expect(fakeBuffer.slice).not.toHaveBeenCalled()

Fix unit tests to work with latest coffeescript

Just change octal `\000` to decimal `\0`.


class Workbench.Views.DatastreamShowView extends Backbone.View
  template: JST["workbench/workbench/templates/datastream"]

  tagName: "li"
  className: "datastream"

  initialize: ->
    @chartView = new Workbench.Views.DatastreamChartView
      model: @model

    @latestView = new Workbench.Views.DatastreamLatestView
      model: @model

    @listenTo @model, "change:seriesData", =>
      _.delay(=>
        @chartView.setElement(@$(".chart")).render()
      , 0)

      @latestView.setElement(@$("#latest_#{@model.id}")).render()

  render: ->
    @$el.animate("min-height": 200).promise().done(=>
      @$el.html(@template(@model.toJSON()))
    )
    this

class Workbench.Views.DatastreamShowView extends Backbone.View
  template: JST["workbench/workbench/templates/datastream"]

  tagName: "li"
  className: "datastream"

  initialize: ->
    @renderDeferred = $.Deferred()

    @chartView = new Workbench.Views.DatastreamChartView
      model: @model

    @latestView = new Workbench.Views.DatastreamLatestView
      model: @model

    @listenTo @model, "change:seriesData", =>
      @renderDeferred.done(=>
        _.delay(=>
          @chartView.setElement(@$(".chart")).render()
        , 0)
      )

      @latestView.setElement(@$("#latest_#{@model.id}")).render()

  render: ->
    @$el.animate("min-height": 200).promise().done(=>
      @$el.html(@template(@model.toJSON()))
      @renderDeferred.resolve()
    )
    this

Use deferred to ensure render order for chart


{assert}   = require 'chai'
{Uploader} = require '../../src/uploader'

describe 'Small file upload @integration test', ->
  source = undefined
  uploader  = undefined

  before (done) ->
    source = new Buffer "first\nsecond\nthird\n"

    uploader = new Uploader
      accessKey: process.env.AWS_S3_ACCESS_KEY
      secretKey: process.env.AWS_S3_SECRET_KEY
      bucket:    process.env.AWS_S3_TEST_BUCKET
      objectName: "testfile" + new Date().getTime()
      stream: source
      debug: true
    done()

  describe ' and When I write a file and finish', ->
    data = null

    before (done) ->
      @timeout parseInt process.env.TEST_TIMEOUT, 10 or 300000

      uploader.send (err, returnedData) ->
        data = returnedData
        done err

    it 'I have received ETag', ->
      assert.equal data.ETag.length, 34

{assert}   = require 'chai'
{Uploader} = require '../../src/uploader'

describe 'Small file upload @integration test', ->
  source = undefined
  uploader  = undefined

  before (done) ->
    source = new Buffer "key;value\ntest;1\nexample;2\n"

    uploader = new Uploader
      accessKey: process.env.AWS_S3_ACCESS_KEY
      secretKey: process.env.AWS_S3_SECRET_KEY
      bucket:    process.env.AWS_S3_TEST_BUCKET
      objectName: "testfile" + new Date().getTime()
      stream: source
      objectParams:
        ContentType: 'text/csv'
      debug: true
    done()

  describe ' and When I write a file and finish', ->
    data = null

    before (done) ->
      @timeout parseInt process.env.TEST_TIMEOUT, 10 or 300000

      uploader.send (err, returnedData) ->
        data = returnedData
        done err

    it 'I have received ETag', ->
      assert.equal data.ETag.length, 34

Update test using set content-type

Update test using set content-type


window.Backbone ||= {}
window.Backbone.Views ||= {}

class Backbone.Views.SectionView extends Backbone.Diorama.NestingView
  template: Handlebars.templates['section.hbs']
  tagName: 'section'
  className: 'section-view'

  events:
    "click .add-narrative": "addNarrative"
    "click .add-visualisation": "addVisualisation"

  initialize: (options) ->
    @section = options.section

  render: =>
    @closeSubViews()
    noContent = !@section.get('narrative')? and !@section.get('visualisation')
    @$el.html(@template(
      thisView: @
      section: @section.toJSON()
      noContent: noContent
      narrative: @section.get('narrative')
      visualisation: @section.get('visualisation')
    ))
    @renderSubViews()

    return @

  addNarrative: =>
    @section.set('narrative', new Backbone.Models.Narrative())
    @render()

  addVisualisation: =>
    @section.set('visualisation', new Backbone.Models.Visualisation())
    @render()

  onClose: ->
    @closeSubViews()

window.Backbone ||= {}
window.Backbone.Views ||= {}

class Backbone.Views.SectionView extends Backbone.Diorama.NestingView
  template: Handlebars.templates['section.hbs']
  tagName: 'section'
  className: 'section-view'

  events:
    "click .add-narrative": "addNarrative"
    "click .add-visualisation": "addVisualisation"

  initialize: (options) ->
    @section = options.section

  render: =>
    @closeSubViews()
    noContent = !@section.get('narrative')? and !@section.get('visualisation')?
    @$el.html(@template(
      thisView: @
      section: @section.toJSON()
      noContent: noContent
      narrative: @section.get('narrative')
      visualisation: @section.get('visualisation')
    ))
    @renderSubViews()

    return @

  addNarrative: =>
    @section.set('narrative', new Backbone.Models.Narrative())
    @render()

  addVisualisation: =>
    @section.set('visualisation', new Backbone.Models.Visualisation())
    @render()

  onClose: ->
    @closeSubViews()

Add missing ? to conditional

Add missing ? to conditional


_ = require 'underscore'

module.exports =
  render: ->
    # Prepare any submit buttons for Ladda
    @$btn = @$('.ladda-button').ladda()

    # Automatically focus the first visible input
    _.defer =>
      @$('.form-control:visible').eq(0).focus()

  formParams: ->
    # Uses jQuery formParams, but don't try to convert number values to numbers, etc.
    @$el.formParams no

  submit: (promise) ->
    @$btn.ladda 'start'

    promise.always(
      @$btn.ladda 'stop'
    ).fail(
      @renderServerErrors
    )

  renderServerErrors: (res) =>

    # First let's get rid of the old ones
    @$('.help-block').remove()
    @$('.has-error').removeClass('has-error')

    if errors = res?.responseJSON?.errors
      _.each errors, (error) =>
        if error.type is 'required'
          message = 'Required'
        else
          message = error.message

        @$ """[name="#{error.path}"]"""
          .closest '.form-group'
          .find('.help-block').remove().end()
          .addClass 'has-error'
          .append """
            #{message}
          """

      @$('.has-error').eq(0).find('[name]').focus()

_ = require 'underscore'

module.exports =
  render: ->
    # Prepare any submit buttons for Ladda
    @$btn = @$('.ladda-button').ladda()

    # Automatically focus the first visible input
    _.defer =>
      @$('.form-control:visible').eq(0).focus()

  formParams: ->
    # Uses jQuery formParams, but don't try to convert number values to numbers, etc.
    @$el.formParams no

  submit: (promise) ->
    @$btn.ladda 'start'

    promise.always(
      @$btn.ladda 'stop'
    ).fail(
      _.bind(@renderServerErrors, @)
    )

  renderServerErrors: (res) ->

    # First let's get rid of the old ones
    @$('.help-block').remove()
    @$('.has-error').removeClass('has-error')

    if errors = res?.responseJSON?.errors
      _.each errors, (error) =>
        if error.type is 'required'
          message = 'Required'
        else
          message = error.message

        @$ """[name="#{error.path}"]"""
          .closest '.form-group'
          .find('.help-block').remove().end()
          .addClass 'has-error'
          .append """
            #{message}
          """

      @$('.has-error').eq(0).find('[name]').focus()

Fix for rendering server errors in new Form mixin


class Skr.Components.PrintFormChooser extends Lanes.React.Component

    propTypes:
        label: React.PropTypes.string
        model: Lanes.PropTypes.Model.isRequired

    mixins: [
        Lanes.React.Mixins.ReadEditingState
    ]

    onChange: ->
        (f) => @invoice.form = f

    renderEdit: (value) ->
        choices = @props.model.constructor.Templates
        <Lanes.Vendor.ReactWidgets.DropdownList
            data={choices}
            value={value}
            onChange={@onChange}
        />

    renderValue: (value) ->
        value

    render: ->
        value = @props.model[@props.name or 'form'] or 'default'
        props = _.omit(@props, 'choices', 'name')
        <LC.FormGroup editing={@isEditingRecord()}
            className="field" {...props}
        >
            {if @isEditingRecord() then @renderEdit(value) else @renderValue(value)}
        </LC.FormGroup>

class Skr.Components.PrintFormChooser extends Lanes.React.Component

    propTypes:
        label: React.PropTypes.string.isRequired
        model: Lanes.PropTypes.Model.isRequired
        choices: React.PropTypes.array

    mixins: [
        Lanes.React.Mixins.ReadEditingState
    ]

    onChange: (val) ->
        if @props.onChange
            @props.onChange?(val, @props)
        else
            @props.model[@props.name] = val

    renderEdit: (value) ->
        choices = @props.choices || @props.model.constructor.Templates
        <Lanes.Vendor.ReactWidgets.DropdownList
            data={choices}
            value={value}
            onChange={@onChange}
        />

    renderValue: (value) ->
        value

    render: ->
        value = @props.value or @props.model[@props.name or 'form'] or 'default'
        props = _.omit(@props, 'choices', 'name')
        <LC.FormGroup editing={@isEditingRecord()}
            className="field" {...props}
        >
            {if @isEditingRecord() then @renderEdit(value) else @renderValue(value)}
        </LC.FormGroup>

Allow overriding get/set from props

Allow overriding get/set from props


app = require('../config/app').app

io                = require('socket.io').listen(app)
RepositorySchema  = require('../models/repository').RepositorySchema

io.sockets.on 'connection', (socket) ->
  RepositorySchema.pre 'save', (next) ->
    console.log 'socket.io sending repository event'
    socket.emit 'repository',
      'repository': this
    next()

Make sure to execute the next filter.

Make sure to execute the next filter.


mkdirp = require 'mkdirp'
fs = require 'fs'

#
# Convenient method for writing a file on
# a path that might not exist. This function
# will create all folders provided in the
# path to the file.
#
#
#    writeToFile('/tmp/hi/folder/file.js', "console.log('hi')")
#
# will create a file at /tmp/hi/folder/file.js with provided content
#
writeToFile = (file, content) ->
  try
    fs.writeFileSync(file, content)
  catch e
    if e.code == 'ENOENT' or e.code == 'EBADF'
      splitted = file.split('/')
      mkdirp.sync(splitted.splice(0, splitted.length-1).join('/'), 0o0777)

      # Retry!
      writeToFile(file, content)
    else
      console.log e

normalizeUrl = (url) ->
  protocol = url.match(/^(http|https):\/\//)?[0]
  protocol = '' unless protocol?
  url = url.replace(protocol, '')
  url = url.replace('//', '/')
  return protocol + url

exports.writeToFile = writeToFile
exports.normalizeUrl = normalizeUrl

mkdirp = require 'mkdirp'
fs = require 'fs'

#
# Convenient method for writing a file on
# a path that might not exist. This function
# will create all folders provided in the
# path to the file.
#
#
#    writeToFile('/tmp/hi/folder/file.js', "console.log('hi')")
#
# will create a file at /tmp/hi/folder/file.js with provided content
#
writeToFile = (file, content) ->
  try
    fs.writeFileSync(file, content)
  catch e
    if e.code == 'ENOENT' or e.code == 'EBADF'
      splitted = file.split('/')
      mkdirp.sync(splitted.splice(0, splitted.length-1).join('/'), 0o0777)

      # Retry!
      writeToFile(file, content)
    else
      console.log e

normalizeUrl = (url) ->
  protocol = url.match(/^(http|https):\/\//)?[0]
  protocol = '' unless protocol?
  url = url.replace(protocol, '')
  url = url.replace('\\', '/')
  url = url.replace('//', '/')
  return protocol + url

exports.writeToFile = writeToFile
exports.normalizeUrl = normalizeUrl

Fix url normalization for Windows

Fix url normalization for Windows

When bundle-up is called from Windows, it uses the file path to generate
the compiled assets url, resulting in things like
"/\generated\bundle\asdfg.css".

This little patch fixes that.


$ ->
	data = [
			name: 'Original'
			participants: 100
			conversions: 45
			color: '#00aa00'
		,
			name: 'Variation'
			participants: 100
			conversions: 50
			color: 'blue'
	]

	variations = []
	variations.push new App.Variation item.name, item.color, item.participants, item.conversions for item in data

	calculator = new App.Calculator variations
	calculator.renderGraph()

$ ->
	data =
		title: 'Original vs Variation'
		variations: [
				name: 'Original'
				participants: 100
				conversions: 45
				color: '#00aa00'
			,
				name: 'Variation'
				participants: 100
				conversions: 50
				color: '#0000ff'
		]

	variations = []
	variations.push new App.Variation item.name, item.color, item.participants, item.conversions for item in data.variations

	calculator = new App.Calculator data.title, variations
	calculator.render()

Add title to the chart

Add title to the chart


class IDE.Panel extends KDView

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

    options.cssClass = KD.utils.curry 'panel', options.cssClass

    super options, data

    @panesContainer = []
    @panes          = []
    @panesByName    = {}

    @createLayout()

  createLayout: ->
    {layoutOptions}  = @getOptions()
    unless layoutOptions
      throw new Error 'You should pass layoutOptions to create a panel'

    @layout = new IDE.WorkspaceLayoutBuilder { delegate: this, layoutOptions }
    @addSubView @layout

  createPane: (paneOptions) ->
    PaneClass = @getPaneClass paneOptions
    pane      = new PaneClass paneOptions

    @panesByName[paneOptions.name] = pane  if paneOptions.name

    @panes.push pane
    @emit 'NewPaneCreated', pane
    return pane

  getPaneClass: (paneOptions) ->
    paneType  = paneOptions.type
    PaneClass = if paneType is 'custom' then paneOptions.paneClass else @findPaneClass paneType

    unless PaneClass
      throw new Error "PaneClass is not defined for \"#{paneOptions.type}\" pane type"

    return PaneClass

  findPaneClass: (paneType) ->
    paneClasses =
      terminal  : IDE.TerminalPane
      editor    : IDE.EditorPane
      preview   : IDE.PreviewPane
      finder    : IDE.FinderPane
      drawing   : IDE.DrawingPane

    return paneClasses[paneType]

  getPaneByName: (name) ->
    return @panesByName[name] or null

class IDE.Panel extends KDView

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

    options.cssClass = KD.utils.curry 'panel', options.cssClass

    super options, data

    @panesContainer = []
    @panes          = []
    @panesByName    = {}

    @createLayout()

  createLayout: ->
    {layoutOptions}  = @getOptions()

    unless layoutOptions
      throw new Error 'You should pass layoutOptions to create a panel'

    layoutOptions.delegate = this

    @layout = new IDE.WorkspaceLayoutBuilder layoutOptions
    @addSubView @layout

  createPane: (paneOptions) ->
    PaneClass = @getPaneClass paneOptions
    pane      = new PaneClass paneOptions

    @panesByName[paneOptions.name] = pane  if paneOptions.name

    @panes.push pane
    @emit 'NewPaneCreated', pane
    return pane

  getPaneClass: (paneOptions) ->
    paneType  = paneOptions.type
    PaneClass = if paneType is 'custom' then paneOptions.paneClass else @findPaneClass paneType

    unless PaneClass
      throw new Error "PaneClass is not defined for \"#{paneOptions.type}\" pane type"

    return PaneClass

  findPaneClass: (paneType) ->
    paneClasses =
      terminal  : IDE.TerminalPane
      editor    : IDE.EditorPane
      preview   : IDE.PreviewPane
      finder    : IDE.FinderPane
      drawing   : IDE.DrawingPane

    return paneClasses[paneType]

  getPaneByName: (name) ->
    return @panesByName[name] or null

Fix for new layout builder in Panel.

Fix for new layout builder in Panel.


SmartEvents = require '/imports/collections/smartEvents'
Incidents = require '/imports/collections/incidentReports'
#Allow multiple modals or the suggested locations list won't show after the
#loading modal is hidden
Modal.allowMultiple = true

Template.smartEvent.onCreated ->
  @editState = new ReactiveVar false
  @eventId = new ReactiveVar()
  @autorun =>
    eventId = Router.current().getParams()._id
    @eventId.set eventId
    @subscribe 'smartEvents', eventId,
      onReady: =>
        event = SmartEvents.findOne(eventId)
        eventDateRange = event.dateRange
        query = disease: event.disease
        if eventDateRange
          query['dateRange.start'] = $lte: eventDateRange.end
          query['dateRange.end'] = $gte: eventDateRange.start
        @subscribe 'smartEventIncidents', query

Template.smartEvent.onRendered ->
  new Clipboard '.copy-link'

Template.smartEvent.helpers
  smartEvent: ->
    SmartEvents.findOne(Template.instance().eventId.get())

  isEditing: ->
    Template.instance().editState.get()

  deleted: ->
    SmartEvents.findOne(Template.instance().eventId.get())?.deleted

  hasAssociatedIncidents: ->
    Incidents.find().count()

  incidentReportsTemplateData: ->
    incidents: Incidents.find({}, {sort: {'dateRange.end': 1}})
    eventType: 'smart'

Template.smartEvent.events
  'click .edit-link, click #cancel-edit': (event, instance) ->
    instance.editState.set(not instance.editState.get())

SmartEvents = require '/imports/collections/smartEvents'
Incidents = require '/imports/collections/incidentReports'
#Allow multiple modals or the suggested locations list won't show after the
#loading modal is hidden
Modal.allowMultiple = true

Template.smartEvent.onCreated ->
  @editState = new ReactiveVar false
  @eventId = new ReactiveVar()

Template.smartEvent.onRendered ->
  eventId = Router.current().getParams()._id
  @eventId.set(eventId)
  @subscribe 'smartEvents', eventId
  @autorun =>
    event = SmartEvents.findOne(eventId)
    if event
      eventDateRange = event.dateRange
      query = disease: event.disease
      if eventDateRange
        query['dateRange.start'] = $lte: eventDateRange.end
        query['dateRange.end'] = $gte: eventDateRange.start
      @subscribe 'smartEventIncidents', query

Template.smartEvent.onRendered ->
  new Clipboard '.copy-link'

Template.smartEvent.helpers
  smartEvent: ->
    SmartEvents.findOne(Template.instance().eventId.get())

  isEditing: ->
    Template.instance().editState.get()

  deleted: ->
    SmartEvents.findOne(Template.instance().eventId.get())?.deleted

  hasAssociatedIncidents: ->
    Incidents.find().count()

  incidentReportsTemplateData: ->
    incidents: Incidents.find({}, sort: 'dateRange.end': 1)
    eventType: 'smart'

Template.smartEvent.events
  'click .edit-link, click #cancel-edit': (event, instance) ->
    instance.editState.set(not instance.editState.get())

Make IR query reactive when smart event is edited


angular.module('doubtfire.units.partials.unit-student-plagiarism-list',[])

#
# List of all possible plagiarism detected in student's work
#
.directive('unitStudentPlagiarismList', ->
  replace: true
  restrict: 'E'
  templateUrl: 'units/unit-student-plagiarism-list/unit-student-plagiarism-list.tpl.html'
  controller: ($scope, $filter, currentUser, gradeService, projectService) ->
    $scope.grades = gradeService.grades

    $scope.view = "all-students"
    studentFilter = "allStudents"

    $scope.tutorName = currentUser.profile.name
    $scope.search = ""
    $scope.reverse = false
    $scope.currentPage = 1
    $scope.maxSize = 5
    $scope.pageSize = 15

    $scope.assessingUnitRole = $scope.unitRole

    $scope.setActiveView = (kind) ->
      $scope.view = kind
      if kind == 'my-students'
        studentFilter = "myStudents"
      else
        studentFilter = "allStudents"

    $scope.activeStudent = null
    $scope.activeTask = null

    $scope.selectStudent = (student) ->
      $scope.activeStudent = student
      if student
        projectService.fetchDetailsForProject student, $scope.unit, (project) ->
          $scope.activeTask = _.find(student.tasks, (task) -> task.similar_to_count > 0)

    $scope.selectTask = (task) ->
      $scope.activeTask = task
)

angular.module('doubtfire.units.unit-student-plagiarism-list',[])

#
# List of all possible plagiarism detected in student's work
#
.directive('unitStudentPlagiarismList', ->
  replace: true
  restrict: 'E'
  templateUrl: 'units/unit-student-plagiarism-list/unit-student-plagiarism-list.tpl.html'
  controller: ($scope, $filter, currentUser, gradeService, projectService) ->
    $scope.grades = gradeService.grades

    $scope.view = "all-students"
    studentFilter = "allStudents"

    $scope.tutorName = currentUser.profile.name
    $scope.search = ""
    $scope.reverse = false
    $scope.currentPage = 1
    $scope.maxSize = 5
    $scope.pageSize = 15

    $scope.assessingUnitRole = $scope.unitRole

    $scope.setActiveView = (kind) ->
      $scope.view = kind
      if kind == 'my-students'
        studentFilter = "myStudents"
      else
        studentFilter = "allStudents"

    $scope.activeStudent = null
    $scope.activeTask = null

    $scope.selectStudent = (student) ->
      $scope.activeStudent = student
      if student
        projectService.fetchDetailsForProject student, $scope.unit, (project) ->
          $scope.activeTask = _.find(student.tasks, (task) -> task.similar_to_count > 0)

    $scope.selectTask = (task) ->
      $scope.activeTask = task
)

FIX: Remove 'partials' from module name of student plagiarism list


{Emitter} = require 'event-kit'
{KeyboardLayoutObserver} = require '../build/Release/keyboard-layout-observer.node'

emitter = new Emitter
observer = new KeyboardLayoutObserver -> emitter.emit 'did-change-current-keyboard-layout', getCurrentKeyboardLayout()

getCurrentKeyboardLayout = ->
  observer.getCurrentKeyboardLayout()

getCurrentKeyboardLanguage = ->
  observer.getCurrentKeyboardLanguage()

getInstalledKeyboardLanguages = ->
  rawList = observer.getInstalledKeyboardLanguages()

  ret = []
  for item in rawList
    continue if ret.indexOf(item) >= 0
    ret.push(item)

  ret

onDidChangeCurrentKeyboardLayout = (callback) ->
  emitter.on 'did-change-current-keyboard-layout', callback

observeCurrentKeyboardLayout = (callback) ->
  callback(getCurrentKeyboardLayout())
  onDidChangeCurrentKeyboardLayout(callback)

module.exports = {getCurrentKeyboardLayout, getCurrentKeyboardLanguage, getInstalledKeyboardLanguages, onDidChangeCurrentKeyboardLayout, observeCurrentKeyboardLayout}

{Emitter} = require 'event-kit'
{KeyboardLayoutObserver} = require '../build/Release/keyboard-layout-observer.node'

emitter = new Emitter
observer = new KeyboardLayoutObserver -> emitter.emit 'did-change-current-keyboard-layout', getCurrentKeyboardLayout()

getCurrentKeyboardLayout = ->
  observer.getCurrentKeyboardLayout()

getCurrentKeyboardLanguage = ->
  observer.getCurrentKeyboardLanguage()

getInstalledKeyboardLanguages = ->
  # NB: This method returns one language per input method, and users can have
  # >1 layout that matches a given language (i.e. Japanese probably has Hiragana
  # and Katakana, both would correspond to the language "ja"), so we need to
  # dedupe this list.
  rawList = observer.getInstalledKeyboardLanguages()

  ret = []
  for item in rawList
    continue if ret.indexOf(item) >= 0
    ret.push(item)

  ret

onDidChangeCurrentKeyboardLayout = (callback) ->
  emitter.on 'did-change-current-keyboard-layout', callback

observeCurrentKeyboardLayout = (callback) ->
  callback(getCurrentKeyboardLayout())
  onDidChangeCurrentKeyboardLayout(callback)

module.exports = {getCurrentKeyboardLayout, getCurrentKeyboardLanguage, getInstalledKeyboardLanguages, onDidChangeCurrentKeyboardLayout, observeCurrentKeyboardLayout}

Comment why we have this method

Comment why we have this method


class ChatConversationListController extends CommonChatController

  constructor:->
    super
    @getListView().on 'moveToIndexRequested', @bound 'moveItemToIndex'

  # loadItems:(callback)->
  #   super

  #   @me.fetchFollowersWithRelationship {}, {}, (err, accounts)=>
  #     @instantiateListItems accounts unless err

class ChatConversationListController extends CommonChatController

  constructor:->
    super
    @getListView().on 'moveToIndexRequested', @bound 'moveItemToIndex'

  addItem:(data)->
    # Make sure there is one conversation with same channel name
    {chatChannel} = data
    for chat in @itemsOrdered
      return  if chat.conversation?.channel?.name is chatChannel?.name
    super data

Make sure there is one conversation with same channel name in the conversation list


# Description:
#   Corgime
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   hubot corgi me - Receive a corgi
#   hubot corgi bomb N - get N corgis
#
# Author:
#   alexgodin

module.exports = (robot) ->

  robot.respond /corgi me/i, (msg) ->
    msg.http("http://corginator.herokuapp.com/random")
      .get() (err, res, body) ->
        msg.send JSON.parse(body).corgi

  robot.respond /corgi bomb( (\d+))?/i, (msg) ->
    count = msg.match[2] || 5
    msg.http("http://corginator.heroku.com/bomb?count=" + count)
      .get() (err, res, body) ->
        msg.send corgi for corgi in JSON.parse(body).corgis

# Description:
#   Corgime
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   hubot corgi me - Receive a corgi
#   hubot corgi bomb N - get N corgis
#
# Author:
#   alexgodin

module.exports = (robot) ->

  robot.respond /corgi me/i, (msg) ->
    msg.http("http://corginator.herokuapp.com/random")
      .get() (err, res, body) ->
        msg.send JSON.parse(body).corgi

  robot.respond /corgi bomb( (\d+))?/i, (msg) ->
    count = msg.match[2] || 5
    msg.http("http://corginator.herokuapp.com/bomb?count=" + count)
      .get() (err, res, body) ->
        msg.send corgi for corgi in JSON.parse(body).corgis

Fix corgi bomb backend url

Fix corgi bomb backend url

Let corgis fly in.


Backbone.Factlink ||= {}

do ->
  #options:
  #  parentView: marionette view
  #  tooltipViewFactory: -> view
  #  selector: selector identifying what can be hovered over.
  #  $offsetParent: a dom node within which to position the
  #    tooltip with respect to the target (hovered) node. By
  #    default, uses parentView.$el.

  Backbone.Factlink.Tooltip = (options) ->
    positionedRegion = null

    maker = ($el, $target) ->
      throw "Cannot open duplicate tooltip" if positionedRegion?
      regionOptions = _.extend {}, options,
        contentView: options.tooltipViewFactory()

      positionedRegion =
        new Backbone.Factlink.PositionedRegion regionOptions
      positionedRegion.bindToElement $target,
        options.$offsetParent || $el
      positionedRegion.show new PopoverView regionOptions
      positionedRegion.updatePosition()
      positionedRegion.$el

    remover = ->
      positionedRegion.resetFade()
      positionedRegion = null

    closeHandler = Backbone.Factlink.TooltipJQ
      $el: options.parentView.$el
      selector: options.selector
      makeTooltip: maker
      removeTooltip: remover

    options.parentView.on 'close', closeHandler.close

Backbone.Factlink ||= {}

#options:
#  parentView: marionette view
#  tooltipViewFactory: -> view
#  selector: selector identifying what can be hovered over.
#  $offsetParent: a dom node within which to position the
#    tooltip with respect to the target (hovered) node. By
#    default, uses parentView.$el.

Backbone.Factlink.Tooltip = (options) ->
  positionedRegion =
    new Backbone.Factlink.PositionedRegion options

  maker = ($el, $target) ->
    popoverOptions = _.extend {}, options,
      contentView: options.tooltipViewFactory()

    positionedRegion.bindToElement $target,
      options.$offsetParent || $el
    positionedRegion.show new PopoverView popoverOptions
    positionedRegion.updatePosition()
    positionedRegion.$el

  remover = ->
    positionedRegion.resetFade()

  closeHandler = Backbone.Factlink.TooltipJQ
    $el: options.parentView.$el
    selector: options.selector
    makeTooltip: maker
    removeTooltip: remover

  options.parentView.on 'close', closeHandler.close

Remove unnecessary do nesting; there are no private variables in this scope.


describe 'Body', ->
  Body = require '../lib/javascripts/body'
  view = null

  initView = ({ collection } = {}) ->
    collection ?= new Backbone.Collection [
      { foo: 'foo', bar: 'bar' }
    ]

    view = new Body { collection }

  showView = ->
    initView(arguments...).render()

  afterEach ->
    view?.destroy()
    view = null

  it 'has a className', ->
    expect(showView().$el).toHaveClass 'body'

  it 'has many body rows', ->
    expect(showView().$el.children).not.toBeEmpty()

describe 'Body', ->
  Body = require '../lib/javascripts/body'
  view = null

  initView = ({ collection } = {}) ->
    collection ?= new Backbone.Collection [
      { foo: 'foo', bar: 'bar' }
    ]

    view = new Body { collection }

  showView = ->
    initView(arguments...).render()

  afterEach ->
    view?.destroy()
    view = null

  it 'has a className', ->
    expect(showView().$el).toHaveClass 'body'

  it 'has many body rows', ->
    expect(showView().$el).not.toBeEmpty()

Fix bad spec: children is a function and it should actually assert the collection view not to be empty


# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require jquery
#= require jquery_ujs
#= require foundation/foundation
#= require foundation/foundation.alerts
#= require foundation/foundation.clearing
#= require foundation/foundation.cookie
#= require foundation/foundation.dropdown
#= require foundation/foundation.forms
#= require foundation/foundation.joyride
#= require foundation/foundation.magellan
#= require foundation/foundation.orbit
#= require foundation/foundation.reveal
#= require foundation/foundation.section
#= require foundation/foundation.tooltips
#= require foundation/foundation.topbar
#= require foundation/foundation.interchange
#= require foundation/foundation.placeholder
#= require foundation/foundation.abide
#= require_tree .

$ ->
  $(document).foundation()
  $(".notice").not(".alert").delay(3000).slideUp "slow"

Remove slide animation for alerts

Remove slide animation for alerts


Client = require 'librato'

module.exports = new Client \
  process.env.LIBRATO_USER, \
  process.env.LIBRATO_TOKEN, \
  prefix: process.env.LIBRATO_PREFIX, source: process.env.DOTCLOUD_SERVICE_ID or 'test'

Add default Librato source as "test"

Add default Librato source as "test"


# vim: ts=2:sw=2:sta
io = require('socket.io-client')

url = process.env.TEST_TARGET || 'https://snap-backend-dev.herokuapp.com' #'http://localhost:8080'

users = process.env.USERS || 50

randWord = (len) ->
  Math.random().toString(36).substr(2, len)

startPlayer = (name) ->
  socket = io(url, {multiplex: false})

  socket.on 'connect', ()->
    setTimeout () ->
      socket.emit 'new player', name

      console.log "connected a test player #{name}"

      setInterval () ->
        w = randWord(2)
        socket.emit 'new word', w
        console.log "#{name} wrote: #{w}"
      , 1000
    , Math.random()*2000

for a in [0..users]
  startPlayer(process.env.TEST_NAME || "player #{a}")
  # console.log randWord(2)

# vim: ts=2:sw=2:sta
io = require('socket.io-client')

url = process.env.TEST_TARGET || 'https://snap-backend-dev.herokuapp.com' #'http://localhost:8080'

users = process.env.USERS || 50

WORD_CHARS = "abcdefghijklmnopqrstuvwxyz"

randWord = (len) ->
  word = ""
  for c in [1..len]
    index = Math.floor(Math.random() * WORD_CHARS.length)
    word += WORD_CHARS.charAt(index)
  return word

startPlayer = (name) ->
  socket = io(url, {multiplex: false})

  socket.on 'connect', ()->
    setTimeout () ->
      socket.emit 'new player', name

      console.log "connected a test player #{name}"

      setInterval () ->
        w = randWord(2)
        socket.emit 'new word', w
        console.log "#{name} wrote: #{w}"
      , 1000
    , Math.random()*users*100

for a in [1..users]
  startPlayer(process.env.TEST_NAME || "player #{a}")
  # console.log randWord(2)

Fix up the stress tester

Fix up the stress tester

- bring players out at 10 players/s
- bring back 2-letter words (no numbers)


module.exports =
class Message
  constructor: (@type, @message, @options={}) ->

  getType: -> @type

  getMessage: -> @message

  isClosable: ->
    !!@options.closable

  getIcon: ->
    return @options.icon if @options.icon?
    switch @type
      when 'fatal' then 'flame'
      when 'error' then 'bug'
      when 'warning' then 'alert'
      when 'info' then 'info'
      when 'success' then 'check'

  getIssueUrl: ->
    "https://github.com/atom/atom/issues/new?title=#{encodeURI(@getIssueTitle())}&body=#{encodeURI(@getIssueBody())}"

  getIssueTitle: ->
    @message

  getIssueBody: ->
    # TODO: crib version information from bug-report: https://github.com/lee-dohm/bug-report/blob/master/lib/bug-report.coffee#L69
    """
    There was an unhandled error

    Stack Trace
    ```
    At #{@options.errorDetail}

    #{@options.stack}
    ```
    """

# Experimental: This will likely change, do not use.
module.exports =
class Message
  constructor: (@type, @message, @options={}) ->

  getOptions: -> @options

  getType: -> @type

  getMessage: -> @message

  getDetail: -> @optons.detail

  isClosable: ->
    !!@options.closable

  getIcon: ->
    return @options.icon if @options.icon?
    switch @type
      when 'fatal' then 'flame'
      when 'error' then 'bug'
      when 'warning' then 'alert'
      when 'info' then 'info'
      when 'success' then 'check'

  getIssueUrl: ->
    "https://github.com/atom/atom/issues/new?title=#{encodeURI(@getIssueTitle())}&body=#{encodeURI(@getIssueBody())}"

  getIssueTitle: ->
    @message

  getIssueBody: ->
    # TODO: crib version information from bug-report: https://github.com/lee-dohm/bug-report/blob/master/lib/bug-report.coffee#L69
    """
    There was an unhandled error

    Stack Trace
    ```
    At #{@options.detail}

    #{@options.stack}
    ```
    """

Add some things to Message

Add some things to Message


# *************************************
#
#   Shortcut
#   -> Trigger clicking an element after keyup
#
# *************************************
#
#   Dependencies
#   - Orientation.keyCodes
#
# *************************************
#
# @param $element      { jQuery object }
# @param dataAttribute { string }
# @param keyCodes      { object }
#
# *************************************

@Orientation.shortcut = ( options ) ->
  settings = $.extend
    $element      : $( '[data-shortcut]' )
    dataAttribute : 'shortcut'
    keyCodes      : Orientation.keyCodes
  , options

  settings.$element.each ->
    key = settings.keyCodes[ $(@).data( settings.dataAttribute ) ]

    $( document ).on 'keyup', ( event ) =>
      $element = $(@)
      tag      = event.target.tagName.toLowerCase()

      unless tag is 'input' or tag is 'textarea'
        if event.which is key
          $element.trigger( 'focus' ).trigger( 'click' )

          if $element.prop( 'tagName' ).toLowerCase() is 'a'
            window.location = $element.attr( 'href' )

# -------------------------------------
#   Usage
# -------------------------------------
#
# Orientation.shortcut()
#

# *************************************
#
#   Shortcut
#   -> Trigger clicking an element after keyup
#
# *************************************
#
#   Dependencies
#   - Orientation.keyCodes
#
# *************************************
#
# @param $element      { jQuery object }
# @param dataAttribute { string }
# @param keyCodes      { object }
#
# *************************************

@Orientation.shortcut = ( options ) ->
  settings = $.extend
    $element      : $( '[data-shortcut]' )
    dataAttribute : 'shortcut'
    keyCodes      : Orientation.keyCodes
  , options

  settings.$element.each ->
    element     = $(@)
    key         = String( element.data( settings.dataAttribute ) )
    keyCode     = settings.keyCodes[ key.toLowerCase() ]
    isInteger   = not isNaN( parseInt( key, 10 ) )
    isUpperCase = key is key.toUpperCase()

    $( document ).on 'keyup', ( event ) =>
      element = $(@)
      tag     = event.target.tagName.toLowerCase()

      unless tag is 'input' or tag is 'textarea'
        if event.which is keyCode

          if ( isUpperCase and event.shiftKey )\
          or ( not isUpperCase and not event.shiftKey )\
          or isInteger
            element.trigger( 'focus' ).trigger( 'click' )

            if element.prop( 'tagName' ).toLowerCase() is 'a'
              window.location = element.attr( 'href' )

# -------------------------------------
#   Usage
# -------------------------------------
#
# Orientation.shortcut()
#

Add shift-modified keys to Shortcut script


define([
    'observable'
    'knockout'
    'd3'
    'dimple'
], (Observable, ko, d3, dimple) ->
  (dataO, container, options) ->
    ulId = @genId
    $('#ul'+ulId+' a').click( (e) ->
      e.preventDefault()
      e.stopImmediatePropagation()

      $('#tab'+ulId+' div.active').removeClass('active')
      $('#ul'+ulId+' li.active').removeClass('active')
      id = $(@).attr('href')
      $(id).addClass('active')
      $(@).parent().addClass('active')
    )

    dataO.subscribe( (newData) =>
      #notify tabs
    )
)

define([
    'jquery'
    'observable'
    'knockout'
    'd3'
    'dimple'
], ($, Observable, ko, d3, dimple) ->
  (dataO, container, options) ->
    ulId = @genId
    $('#ul'+ulId+' a').click( (e) ->
      e.preventDefault()
      e.stopImmediatePropagation()

      $('#tab'+ulId+' div.active').removeClass('active')
      $('#ul'+ulId+' li.active').removeClass('active')
      id = $(@).attr('href')
      $(id).addClass('active')
      $(@).parent().addClass('active')
    )

    # select the first tab
    $('#ul'+ulId+' li:first a').click()

    dataO.subscribe( (newData) =>
      #notify tabs
    )
)

Select the first tab automatically

Select the first tab automatically


###
Welcome to AssetGallery -- http://github.com/ewr/AssetGallery
###

# stub console.log() for IE
if !window.console
    class window.console
        @log: ->

AssetGallery ?= {}

###
Welcome to AssetGallery -- http://github.com/ewr/AssetGallery
###

# stub console.log() for IE
if !window.console
    class window.console
        @log: ->

window.AssetGallery ?= {}

Update AssetGallery definition to be compatible with CS 1.3.3


packages: [
  "autocomplete-python"
  "fold-functions"
  "language-ini"
  "linter"
  "linter-pep8"
  "linter-python-pep8"
  "markdown-preview-plus"
  "package-sync"
  "python-indent"
]

packages: [
  "auto-detect-indentation"
  "autocomplete-python"
  "fold-functions"
  "language-ini"
  "linter"
  "linter-pep8"
  "linter-python-pep8"
  "markdown-preview-plus"
  "package-sync"
  "python-indent"
]

Add indentation auto detection to Atom

Add indentation auto detection to Atom


"*":
  editor:
    tabLength: 4
    fontFamily: "Menlo"
    softTabs: false
    autoIndentOnPaste: false
    invisibles: {}
    lineHeight: 1.3
    fontSize: 11
  core:
    themes: [
      "atom-light-ui"
      "phoenix-syntax"
    ]
    disabledPackages: [
      "snippets"
      "SFTP-deployment"
      "spell-check"
      "autocomplete-css"
      "autocomplete-plus"
      "compare-files"
      "merge-conflicts"
      "atom-compare-files"
      "background-tips"
      "welcome"
    ]
  "exception-reporting":
    userId: "4b2dfe3a-7eef-a871-4d35-230cb11366f8"
  welcome:
    showOnStartup: false
  "tree-view":
    hideIgnoredNames: true
  whitespace:
    removeTrailingWhitespace: false
  "asciidoc-preview": {}
  "autoclose-html": {}
  "SFTP-deployment": {}
  "remote-sync": {}
  "remote-edit": {}
  "file-icons":
    coloured: false
  "recent-files":
    updated: false
  "spell-check": {}
  "autocomplete-plus": {}
  "atom-beautify":
    _analyticsUserId: "84a7da30-e058-42d8-9fd2-cc0519eb2781"
  "status-bar": {}
  hex: {}
  docblockr: {}
  "toggle-quotes": {}
  "find-and-replace":
    showSearchWrapIcon: false

Add setting for proposed "Show Wrap Icon" option

Fingers crossed this gets merged into the package's upstream.

See also: atom/find-and-replace#618


class Workbench.Views.SensorMapView extends Backbone.Marionette.ItemView
  template: false

  initialize: ->
    @zoom = 13

  onDestroy: ->
    @map.remove() if @map

  onShow: ->
    if @el.id is ""
      console.warn "No Map Element"
    else
      @location = [@model.get("latitude"), @model.get("longitude")]
      @map = L.map(@el.id).setView(@location, @zoom)
      L.tileLayer(Workbench.tile_url, {
        attribution: Workbench.tile_attribution
      }).addTo(@map)

      L.marker(@location).addTo(@map)

class Workbench.Views.SensorMapView extends Backbone.Marionette.ItemView
  template: false

  initialize: ->
    @zoom = 13

  onDestroy: ->
    @map.remove() if @map

  onShow: ->
    if @el.id is ""
      console.warn "No Map Element"
    else
      @location = [@model.get("latitude"), @model.get("longitude")]
      @map = L.map(@el.id, {
        continuousWorld: true
        center: Workbench.provider.center
        minZoom: Workbench.provider.zoomRange.min
        maxZoom: Workbench.provider.zoomRange.max
        crs: Workbench.provider.CRS
      })

      L.tileLayer(Workbench.provider.url, {
          attribution: Workbench.provider.attribution
      }).addTo(@map)

      @map.setView(@location, @zoom)

      L.marker(@location).addTo(@map)

Use AWM tiles for small sensor map

Use AWM tiles for small sensor map


window.Tahi ||= {}
Tahi.utils =
  toCamel: (string) ->
    string.replace /(\-[a-z])/g, ($1) ->
      $1.toUpperCase().replace "-", ""

  windowHistory: ->
    window.history

  bindColumnResize: ->
    $(window).off('resize.columns').on 'resize.columns', =>
      @resizeColumnHeaders()

  resizeColumnHeaders: ->
    $headers = $('.columns .column-header')
    return unless $headers.length

    $headers.css('height', '')
    heights = $headers.find('h2').map ->
      $(this).outerHeight()

    max = null
    try
      max = Math.max.apply(Math, heights)
    catch error
      console.log "Math error, setting height to 20"
      console.log error
      max = 20

    $headers.css('height', max)
    $('.column-content').css('top', max)

  togglePropertyAfterDelay: (obj, prop, startVal, endVal, ms) ->
    obj.set(prop, startVal)
    setTimeout( ->
      Ember.run.schedule("actions", obj, 'set', prop, endVal)
    ms)

  debug: (description, obj) ->
    if ETahi.environment == 'development'
      console.groupCollapsed(description)
      console.log(obj)
      console.groupEnd()

window.Tahi ||= {}
Tahi.utils =
  toCamel: (string) ->
    string.replace /(\-[a-z])/g, ($1) ->
      $1.toUpperCase().replace "-", ""

  windowHistory: ->
    window.history

  bindColumnResize: ->
    $(window).off('resize.columns').on 'resize.columns', =>
      @resizeColumnHeaders()

  resizeColumnHeaders: ->
    $headers = $('.columns .column-header')
    return unless $headers.length

    $headers.css('height', '')
    heights = $headers.find('h2').map ->
      $(this).outerHeight()

    max = null
    try
      max = Math.max.apply(Math, heights)
    catch error
      console.log "Math error, setting height to 20"
      console.log error
      max = 20

    $headers.css('height', max)
    $('.column-content').css('top', max)

  togglePropertyAfterDelay: (obj, prop, startVal, endVal, ms) ->
    obj.set(prop, startVal)
    setTimeout( ->
      Ember.run.schedule("actions", obj, 'set', prop, endVal)
    ms)

  debug: (description, obj) ->
    if ETahi.environment == 'development'
      console.groupCollapsed(description)
      console.log(Em.copy(obj, true))
      console.groupEnd()

Support logging correctly when msg is mutated