# Centralizes configuration for currently running split tests
#
# eg.
# header_design:
#   key: 'header_design'
#   outcomes:
#     old: 8
#     new: 2
#
# Or, `outcomes` can be an array, when you specify `weighting: 'equal'.
#   weighting: 'equal'
#   outcomes: [
#     'old'
#     'new'
#   ]
#   edge: 'new'
#   dimension: 'dimension1' # Optional GA dimension
#   scope: 'local' # Optionally disable global initialization
#   control_group: 'old'  #Defaults to `control`, Reflection sees this.
#
# For equal weighting, add `weighting: 'equal'` and `outcomes` as an array.
#
# Note: if there are no running tests
# this should export empty Object
# module.exports = {}

module.exports = {
  artist_page_signup_modal:
    key: "artist_page_signup_modal"
    outcomes:
      control: 50
      experiment: 50
    control_group: "control"
    edge: "experiment"
}

# Centralizes configuration for currently running split tests
#
# eg.
# header_design:
#   key: 'header_design'
#   outcomes:
#     old: 8
#     new: 2
#
# Or, `outcomes` can be an array, when you specify `weighting: 'equal'.
#   weighting: 'equal'
#   outcomes: [
#     'old'
#     'new'
#   ]
#   edge: 'new'
#   dimension: 'dimension1' # Optional GA dimension
#   scope: 'local' # Optionally disable global initialization
#   control_group: 'old'  #Defaults to `control`, Reflection sees this.
#
# For equal weighting, add `weighting: 'equal'` and `outcomes` as an array.
#
# Note: if there are no running tests
# this should export empty Object
# module.exports = {}

module.exports = {
  artist_page_signup_modal:
    key: "artist_page_signup_modal"
    outcomes:
      control: 50
      experiment: 50
    control_group: "control"
    edge: "experiment"
  collection_hubs:
    key: "collection_hubs"
    outcomes:
      control: 100
      experiment: 0
    edge: 'experiment'
}

Set up A/B test for Collection Hubs (for now admin-only)

Signed-off-by: Myk Bilokonsky <75f49cdf1693dcb6c4be1fd9678ee266e958b7d5@gmail.com>


### define
../view/skeletontracing/abstract_tree_view : AbstractTreeView
###

class AbstractTreeController

  model : null
  view : null

  constructor : (@model) ->

    container = $("#abstractTreeViewer")
    @view = new AbstractTreeView(container.width(), container.height())
    container.append(@view.canvas)

    @bind()
    @drawTree(model.skeletonTracing.getTree())

  bind : ->

    @model.skeletonTracing.on({
      newActiveNode        : => @drawTree(),
      newActiveTree        : => @drawTree(),
      newTree              : => @drawTree(),
      mergeTree            : => @drawTree(),
      reloadTrees          : => @drawTree(),
      deleteTree           : => @drawTree(),
      deleteActiveNode     : => @drawTree(),
      newNode              : => @drawTree()
      })

  drawTree : ->

    @view.drawTree(@model.skeletonTracing.getTree(), @model.skeletonTracing.getActiveNodeId())

  setActiveNode : (nodeId, centered, mergeTree) ->

    { model } = @

    model.skeletonTracing.setActiveNode(nodeId, mergeTree)

    @centerActiveNode() if centered

  centerActiveNode : ->

    { model } = @

    position = model.skeletonTracing.getActiveNodePos()
    if position
      model.flycam.setPosition(position)

Remove trailing spaces, trigger jenkins

Remove trailing spaces, trigger jenkins


# Description:
#   Enable Hubot to post messages to Kato rooms using the HTTP API, allowing for messages with a coloured background and varying poster name.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   Emit event "kato-http-message" from your script, after setting room name-id matches in Hubot's brain with
#
#
# Notes:
#
#
# Author:
#   ocean

module.exports = (robot) ->
  robot.on 'kato-http-post', (kato-http-post) ->
    sendMessage kato-http-post

  robot.brain.data.kato-http-api.rooms or= {}

  sendMessage = (message) ->

    data = JSON.stringify({
      room: message.room || 'all',
      text: message.text,
      from: message.from || robot.name,
      color: message.color || 'grey',
      renderer: 'markdown'
    })

    roomURL = robot.brain.data.kato-http-api.rooms[data.room]

    robot.http(roomURL)
      .header('Content-Type', 'application/json')
      .post(data) (err, res, body) ->
        if err
          robot.emit 'error', err, res, body
          return
        robot.emit res, body

# Description:
#   Enable Hubot to post messages to Kato rooms using the HTTP API, allowing for messages with a coloured background and varying poster name.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   Emit event "kato-http-message" from your script, after setting Kato "All" room URL in environment variables.
#
#
# Notes:
#
#
# Author:
#   ocean

module.exports = (robot) ->

  config = secrets:
    katoRoomURL: process.env.HUBOT_KATO_ALL_ROOM_HTTP_POST_URL

  robot.on 'kato-http-post', (kato-http-post) ->
    sendMessage kato-http-post

  sendMessage = (message) ->

    data = JSON.stringify({
      text: message.text,
      from: message.from || robot.name,
      color: message.color || 'grey',
      renderer: 'markdown'
    })

    robot.http(config.secrets.katoRoomURL)
      .header('Content-Type', 'application/json')
      .post(data) (err, res, body) ->
        if err
          robot.emit 'error', err, res, body
          return
        robot.emit res, body

Set up environment variable to hold Kato "All" room HTTP POST URL.


angular.module("hyperadmin")
  .directive "formActions", ->
    template: """

    <md-button type="submit" class="md-primary md-raised"
      ng-disabled="form.$invalid">Submit</md-button>
    <md-button class="md-warn" ui-sref="^">Cancel

    """
    restrict: "E"
    scope:
      cancelState: "=cancelState"
      form: "=form"

Fix form button alignment issue

Fix form button alignment issue


# 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://coffeescript.org/

$ ->
  animateMetric = $(".metric-box strong").each (index) ->
    $this = $(this)
    starting_point = 0
    $target_count = parseInt($this.text().replace(/\,/g, ''))

    # set the test to 0
    $this.counter = starting_point

    tween = createjs.Tween.get($this).to( {counter: $target_count}, (1000 + index * 1000) , createjs.Ease.quintOut)

    tween.addEventListener 'change', (event) ->
      $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
      return

# 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://coffeescript.org/

roundUpToThousand = (value) ->
  return 1000 * Math.ceil(value / 1000)

$ ->
  animateMetric = $(".metric-box strong").each (index) ->
    $this = $(this)
    starting_point = 0
    $target_count = parseInt($this.text().replace(/\,/g, ''))

    # set the test to 0
    $this.counter = starting_point

    tween = createjs.Tween.get($this).to( {counter: $target_count}, (1000 + index * 1000) , createjs.Ease.quintOut)

    tween.addEventListener 'change', (event) ->
      # if the number is under 90 of the target, increment number by thousands
      $this.counter = roundUpToThousand($this.counter) if $this.counter < $target_count * 0.9
      $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
      return

Increment number by 1000s until almost at target


@ledger.wallet ?= {}

_.extend ledger.wallet,

  sweepPrivateKey: ({privateKey, account, txFee: 10000}, callback) ->
    completion = new CompletionClosure(callback)
    recipientAddress = account.getHDAccount().getCurrentPublicAddress()
    ecKey = new window.bitcoin.ECKey.fromWIF(privateKey)
    publicKey = ecKey.pub.getAddress().toString()
    addresses = [publicKey]

    ledger.api.UnspentOutputsRestClient.instance.getUnspentOutputsFromAddresses addresses , (outputs, error) ->
      return completion.failure(error) if error?

      txBuilder = new window.bitcoin.TransactionBuilder()
      amountToSend = new ledger.wallet.Value()

      _.async.each outputs, (output, done, hasNext) ->
        amountToSend = amountToSend.add output.value
        txBuilder.addInput(output.transaction_hash, output.output_index)

        if hasNext is false and amountToSend.lte(txFee)
          # Not enough available funds
          completion.failure(new ledger.StandardError(ledger.errors.NotEnoughFunds))
        else if hasNext is false
          amountToSend = amountToSend.subtract(10000)
          txBuilder.addOutput(recipientAddress, amountToSend.toNumber())

          txBuilder.sign(index, ecKey) for input, index in txBuilder.tx.ins
          txHex = txBuilder.build().toHex()
          ledger.api.TransactionsRestClient.instance.postTransactionHex txHex, (txHash, error) =>
            completion.complete(txHash, error)
        else
          # Continue to collect funds
          do done
    completion.readonly()

@ledger.wallet ?= {}

_.extend ledger.wallet,

  sweepPrivateKey: ({privateKey, account, txFee}, callback) ->
    txFee ?= 10000
    completion = new CompletionClosure(callback)
    recipientAddress = account.getHDAccount().getCurrentPublicAddress()
    ecKey = new window.bitcoin.ECKey.fromWIF(privateKey)
    publicKey = ecKey.pub.getAddress().toString()
    addresses = [publicKey]

    ledger.api.UnspentOutputsRestClient.instance.getUnspentOutputsFromAddresses addresses , (outputs, error) ->
      return completion.failure(error) if error?

      txBuilder = new window.bitcoin.TransactionBuilder()
      amountToSend = new ledger.wallet.Value()

      _.async.each outputs, (output, done, hasNext) ->
        amountToSend = amountToSend.add output.value
        txBuilder.addInput(output.transaction_hash, output.output_index)

        if hasNext is false and amountToSend.lte(txFee)
          # Not enough available funds
          completion.failure(new ledger.StandardError(ledger.errors.NotEnoughFunds))
        else if hasNext is false
          amountToSend = amountToSend.subtract(10000)
          txBuilder.addOutput(recipientAddress, amountToSend.toNumber())

          txBuilder.sign(index, ecKey) for input, index in txBuilder.tx.ins
          txHex = txBuilder.build().toHex()
          ledger.api.TransactionsRestClient.instance.postTransactionHex txHex, (txHash, error) =>
            completion.complete(txHash, error)
        else
          # Continue to collect funds
          do done
    completion.readonly()

Fix bad parameter in sweepPrivateKey

Fix bad parameter in sweepPrivateKey


$(".verified-status-tag").html "<%= escape_javascript(render :partial => 'shared/verified_status_tag', :formats => [ :html ]) %>"
$(".club-admin-status-text").html "<%= escape_javascript(render :partial => 'admin/club_live_status', :formats => [ :html ]) %>"
$(".club-promote-text").html "<%= escape_javascript(render :partial => 'admin/club_promote', :locals => { :club => @club }, :formats => [ :html ]) %>"
$(".modal").removeClass "paypal-confirm-modal" if $(".modal").hasClass("paypal-confirm-modal")
$(".modal").modal "hide"

Fix Admin PayPal Verified Tag Update

Fix Admin PayPal Verified Tag Update

Fix the Admin page to ensure that when a User verifies their account,
the corresponding PayPal verified tag in the header is updated.


define [
  'jquery'
  'underscore'
  'backbone'
], ($, _, Backbone) ->

  class Region
    constructor: (el, parent) ->
      @parent = parent
      @el = el

    show: (view) ->
      @$el = @$el or @parent.$el.find(@el)
      view.parent = @parent
      @view = view
      @view.setElement(@$el).render()

    close: () ->
      @view?.close()
      @$el.empty()

  class Regions
    constructor: (regions, $context) ->
      _.each _.keys(regions), (region) =>
        @[region] = new Region(regions[region], $context)

  return class BaseView extends Backbone.View
    initialize: () ->
      @regions = new Regions(@regions, @)

    render: () ->
      @$el.html(@template)

      return @

    close: () ->
      _.each @regions, (region) ->
        region.view.close()

      @stopListening()
      @remove()
      @unbind()

define [
  'jquery'
  'underscore'
  'backbone'
], ($, _, Backbone) ->

  class Region
    constructor: (el, parent) ->
      @parent = parent
      @el = el

    show: (view) ->
      @close()
      @append(view)

    append: (view) ->
      @$el = @$el or @parent.$el.find(@el).eq(0)
      view.parent = @parent
      @views = @views or []
      @views.push(view)
      view.setElement($('<div>').appendTo(@$el)).render()

    close: () ->
      _.each @views, (view) ->
        view.close()

      @$el?.empty()

  class Regions
    constructor: (regions, $context) ->
      _.each _.keys(regions), (region) =>
        @[region] = new Region(regions[region], $context)

  return class BaseView extends Backbone.View
    initialize: () ->
      @regions = new Regions(@regions, @)

    render: () ->
      @$el.html(@template)

      return @

    close: () ->
      _.each @regions, (region) ->
        region.close()

      @stopListening()
      @remove()
      @unbind()

Allow views to be appended

Allow views to be appended


run = () ->
	ReactDOM = require 'react-dom'
	cookie = require 'cookie'
	{ XmlEntities } = require 'html-entities'

	init = require './init.coffee'
	{ store } = require './store.coffee'
	userActions = require './actions/user-actions.coffee'
	routes = require './views/routes.coffee'
	createFactory = require './create-factory.coffee'
	Root = createFactory require './views/root.coffee'
	appActions = require './actions/app-actions.coffee'

	cookies = cookie.parse document.cookie
	if cookies.session
		session = JSON.parse atob cookies.session
		if session.user && session.timestamp
			store.dispatch userActions.setUser session.user, session.timestamp

	entities = new XmlEntities()
	serverState = document.getElementById('state').textContent
	data = JSON.parse entities.decode serverState
	store.dispatch appActions.init data
	state = store.getState()
	params = state.routing.state || {}
	init params, document.documentElement.lang
		.then ->
			ReactDOM.render Root(), document.getElementById 'app'
		.catch (err) ->
			console.error err.stack

if window.Promise && Object.assign
	run()
else
	require [ 'es6-promise', 'object-assign' ], (promiseLib, assign) ->
		window.Promise || (window.Promise = promiseLib.Promise)
		Object.assign || (Object.assign = assign)
		run()

require('es6-promise').polyfill()
assign = require 'object-assign'
ReactDOM = require 'react-dom'
cookie = require 'cookie'
{ XmlEntities } = require 'html-entities'

init = require './init.coffee'
{ store } = require './store.coffee'
userActions = require './actions/user-actions.coffee'
routes = require './views/routes.coffee'
createFactory = require './create-factory.coffee'
Root = createFactory require './views/root.coffee'
appActions = require './actions/app-actions.coffee'

Object.assign || (Object.assign = assign)

cookies = cookie.parse document.cookie
if cookies.session
	session = JSON.parse atob cookies.session
	if session.user && session.timestamp
		store.dispatch userActions.setUser session.user, session.timestamp

entities = new XmlEntities()
serverState = document.getElementById('state').textContent
data = JSON.parse entities.decode serverState
store.dispatch appActions.init data
state = store.getState()
params = state.routing.state || {}
init params, document.documentElement.lang
	.then ->
		ReactDOM.render Root(), document.getElementById 'app'
	.catch (err) ->
		console.error err.stack

Remove conditional polyfilling again. es6-promise should always be included since native Promise in Chrome is buggy. object-assign is so small it makes no sense to conditionally load on its own. fetch is a bit unfinished, and doesn't support cookies, so giving up on that for now.


Promise = require('bluebird')
canvas = require('./_canvas')

exports.getAll = ->
	return canvas.get
		resource: 'application'
		options:
			orderby: 'app_name asc'
			expand: 'device'

exports.get = (id) ->
	return canvas.get
		resource: 'application'
		id: id

	.then (application) ->
		if not application?
			Promise.reject(new Error('Not found'))

		return application

exports.create = (name, deviceType) ->
	return canvas.post
		resource: 'application'
		data:
			app_name: name
			device_type: deviceType

	.then (res) ->
		id = res?.id

		if not id?
			return Promise.reject(new Error('Could not find created application id.'))

		return id

exports.remove = (id) ->
	return canvas.delete
		resource: 'application'
		id: id

Promise = require('bluebird')
canvas = require('./_canvas')

exports.getAll = ->
	return canvas.get
		resource: 'application'
		options:
			orderby: 'app_name asc'
			expand: 'device'

exports.get = (id) ->
	return canvas.get
		resource: 'application'
		id: id

	.then (application) ->
		if not application?
			return Promise.reject(new Error('Not found'))

		return application

exports.create = (name, deviceType) ->
	return canvas.post
		resource: 'application'
		data:
			app_name: name
			device_type: deviceType

	.then (res) ->
		id = res?.id

		if not id?
			return Promise.reject(new Error('Could not find created application id.'))

		return id

exports.remove = (id) ->
	return canvas.delete
		resource: 'application'
		id: id

Return promise rejection to prevent Bluebird from complaining


define ['jquery', 'underscore', 'backbone', 'bootstrap', 'plugins/select_range'], ($, _, Backbone) ->
  class SearchInputView extends Backbone.View

    initialize: () ->
      @$el.typeahead
        source: @getTypeaheadAddresses
        updater: (item) =>
          _.defer @afterTypeahead
          item
        minLength: 3

    getTypeaheadAddresses: (query, callback) =>
      params = $.param { query: query }
      $.getJSON "/autocomplete?#{params}", (addresses) ->
        callback(addresses)

    afterTypeahead: () =>
      idx = @$el.val().lastIndexOf(',')
      @$el.selectRange(idx) if idx? and idx > 0

    indicateError: () ->
      @$el.closest('.control-group').addClass('error')

    clearError: () ->
      @$el.closest('.control-group').removeClass('error')

    validate: () ->
      if $.trim(@val()) is ''
        @indicateError()
        false
      else
        true

    focus: () =>
      @$el.focus()

    val: (v) =>
      if arguments.length > 0
        @$el.val.apply(@$el, arguments)
      else
        @$el.val()

    placeholder: (v) =>
      @originalPlaceholder ?= @$el.prop('placeholder')
      @$el.prop('placeholder', v)

    resetPlaceholder: () =>
      @$el.prop('placeholder', @originalPlaceholder)

define ['jquery', 'underscore', 'backbone', 'bootstrap', 'plugins/select_range'], ($, _, Backbone) ->
  class SearchInputView extends Backbone.View

    initialize: () ->
      @$el.typeahead
        source: @getTypeaheadAddresses
        updater: (item) =>
          _.defer @afterTypeahead
          item
        minLength: 3

    getTypeaheadAddresses: (query, callback) =>
      params = $.param { query: query }
      $.getJSON "/autocomplete?#{params}", (addresses) ->
        callback(addresses)

    afterTypeahead: () =>
      idx = @$el.val().lastIndexOf(',')
      @$el.selectRange(idx) if idx? and idx > 0

    indicateError: () ->
      @$el.closest('.control-group').addClass('error')
      this

    clearError: () ->
      @$el.closest('.control-group').removeClass('error')
      this

    validate: () ->
      if $.trim(@val()) is ''
        @indicateError()
        false
      else
        true

    focus: () =>
      @$el.focus()

    blur: ->
      @$el.blur()

    val: (v) =>
      if arguments.length > 0
        @$el.val.apply(@$el, arguments)
      else
        @$el.val()

    placeholder: (v) =>
      @originalPlaceholder ?= @$el.prop('placeholder')
      @$el.prop('placeholder', v)

    resetPlaceholder: () =>
      @$el.prop('placeholder', @originalPlaceholder)

Add blur method to SearchInputView.

Add blur method to SearchInputView.


gulp = require "gulp"

defaultTasks = [
  "scripts:minify:watch",
  "styles:minify:watch",
  "develop:watch",
]

gulp.task "default", defaultTasks, ->

gulp = require "gulp"

defaultTasks = [
  "scripts:watch",
  "scripts:minify:watch",
  "styles:minify:watch",
  "develop:watch",
]

gulp.task "default", defaultTasks, ->

Make sure that regular scripts are build too


`import DS from 'ember-data'`

Commit = DS.Model.extend
  remoteId: DS.attr('string')
  message: DS.attr('string')
  state: DS.attr('string')
  remoteUrl: DS.attr('string')
  project: DS.belongsTo('project')
  expiresAt: DS.attr('date')
  createdAt: DS.attr('date')
  authoredAt: DS.attr('date')
  author: DS.belongsTo('author')
  tickets: DS.hasMany('ticket')
  reviewer: DS.belongsTo('user', {async: true})
  fix: DS.hasMany('commit', {inverse: 'fix'})
  fixed: DS.hasMany('commit', {inverse: 'fixed'})

  shortHash: Ember.computed('remoteId', ->
    @get('remoteId').substr(0,6)
  )

  isRejected: Ember.computed('state', ->
    ['rejected', 'auto_rejected'].contains(@get('state'))
  )

  isPassed: Ember.computed('state', ->
    @get('state') == 'passed'
  )

  isFixed: Ember.computed('state', ->
    @get('state') == 'fixed'
  )

  isAccepted: Ember.computed('state', ->
    @get('state') == 'accepted'
  )

  belongsToMe: Ember.computed ->
    @store.find('user', 'me').get('person') is @get('author')

  hasReviewer: Ember.computed.notEmpty('reviewer.id')

`export default Commit`

`import DS from 'ember-data'`

Commit = DS.Model.extend
  remoteId: DS.attr('string')
  message: DS.attr('string')
  state: DS.attr('string')
  remoteUrl: DS.attr('string')
  project: DS.belongsTo('project')
  expiresAt: DS.attr('date')
  createdAt: DS.attr('date')
  authoredAt: DS.attr('date')
  author: DS.belongsTo('author')
  tickets: DS.hasMany('ticket')
  reviewer: DS.belongsTo('user', {async: true})
  fix: DS.hasMany('commit', {inverse: 'fix'})
  fixed: DS.hasMany('commit', {inverse: 'fixed'})

  shortHash: Ember.computed('remoteId', ->
    @get('remoteId').substr(0,6)
  )

  isRejected: Ember.computed('state', ->
    ['rejected', 'auto_rejected'].contains(@get('state'))
  )

  isPassed: Ember.computed('state', ->
    @get('state') == 'passed'
  )

  isFixed: Ember.computed('state', ->
    @get('state') == 'fixed'
  )

  isAccepted: Ember.computed('state', ->
    @get('state') == 'accepted'
  )

  hasExpiration: Ember.computed('state', ->
    @get('state') != 'fixed' and @get('state') != 'accepted'
  )

  belongsToMe: Ember.computed ->
    @store.find('user', 'me').get('person') is @get('author')

  hasReviewer: Ember.computed.notEmpty('reviewer.id')

`export default Commit`

Add computed property for expiration display

[#98105252]


ColorScanner = require '../lib/color-scanner'
{TextEditor} = require 'atom'

describe 'ColorScanner', ->
  [scanner, editor, bufferColor] = []

  withTextEditor = (fixture, block) ->
    describe "with #{fixture} buffer", ->
      beforeEach ->
        waitsForPromise -> atom.workspace.open(fixture)
        runs -> editor = atom.workspace.getActiveTextEditor()

      afterEach -> editor = null

      do block

  withScannerForTextEditor = (fixture, block) ->
    withTextEditor fixture, ->
      beforeEach -> scanner = new ColorScanner(buffer: editor.getBuffer())

      afterEach -> scanner = null

      do block

  describe '::search', ->
    withScannerForTextEditor 'four-variables.styl', ->
      beforeEach ->
        bufferColor = scanner.search()

      it 'returns the first buffer color match', ->
        expect(bufferColor).toBeDefined()

      describe 'successive searches', ->
        it 'returns a buffer color for each match and then undefined', ->
          expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeDefined()
          # expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeUndefined()

ColorScanner = require '../lib/color-scanner'
{TextEditor} = require 'atom'

describe 'ColorScanner', ->
  [scanner, editor, bufferColor] = []

  withTextEditor = (fixture, block) ->
    describe "with #{fixture} buffer", ->
      beforeEach ->
        waitsForPromise -> atom.workspace.open(fixture)
        runs -> editor = atom.workspace.getActiveTextEditor()

      afterEach -> editor = null

      do block

  withScannerForTextEditor = (fixture, block) ->
    withTextEditor fixture, ->
      beforeEach -> scanner = new ColorScanner(buffer: editor.getBuffer())

      afterEach -> scanner = null

      do block

  describe '::search', ->
    withScannerForTextEditor 'four-variables.styl', ->
      beforeEach ->
        bufferColor = scanner.search()

      it 'returns the first buffer color match', ->
        expect(bufferColor).toBeDefined()

      describe 'the resulting buffer color', ->
        it 'has a text range', ->
          expect(bufferColor.textRange).toEqual([8,12])

        it 'has a buffer range', ->
          expect(bufferColor.bufferRange).toEqual([[0, 8],[0, 12]])

        it 'has a color', ->
          expect(bufferColor.color).toBeColor('#ffffff')

        it 'stores the matched text', ->
          expect(bufferColor.match).toEqual('#fff')

      describe 'successive searches', ->
        it 'returns a buffer color for each match and then undefined', ->
          expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeDefined()
          # expect(scanner.search()).toBeDefined()
          expect(scanner.search()).toBeUndefined()

Add tests for buffer color properties

:white_check_mark: Add tests for buffer color properties


class @WalletMessageIndexDialogViewController extends ledger.common.DialogViewController

  view:
    derivationPath: '#derivation_path'
    message: '#message'
    confirmButton: '#confirm_button'
    error: "#error_container"

  onAfterRender: ->
    super
    @_isEditable = @params.editable || no
    chrome.app.window.current().show()
    @view.derivationPath.val("m/" + @params.path)
    @view.message.val(@message)

    unless @_isEditable
      @view.message.attr("read-only", on)
      @view.derivationPath.attr("read-only", on)

  cancel: ->
    unless @_isEditable
      Api.callback_cancel 'sign_message', t('wallet.message.errors.cancelled')
    @dismiss()

  confirm: ->
    path = Api.cleanPath(@view.derivationPath.val())
    message = @view.message.val()
    if _.isEmpty(path) || path.match(/[^0-9\/']/ig)?
      @view.error.text(t("wallet.message.index.invalid_path"))
      return
    if _.isEmpty(message)
      @view.error.text(t("wallet.message.index.invalid_message"))
      return
    dialog = new WalletMessageProcessingDialogViewController(path: path, message: message, editable: @_isEditable)
    @getDialog().push dialog

class @WalletMessageIndexDialogViewController extends ledger.common.DialogViewController

  view:
    derivationPath: '#derivation_path'
    message: '#message'
    confirmButton: '#confirm_button'
    error: "#error_container"

  onAfterRender: ->
    super
    @_isEditable = @params.editable || no
    chrome.app.window.current().show()
    @view.derivationPath.val("m/" + Api.cleanPath(@params.path))
    @view.message.val(@params.message)
    unless @_isEditable
      @view.message.attr("readonly", on)
      @view.derivationPath.attr("readonly", on)

  cancel: ->
    unless @_isEditable
      Api.callback_cancel 'sign_message', t('wallet.message.errors.cancelled')
    @dismiss()

  confirm: ->
    path = Api.cleanPath(@view.derivationPath.val())
    message = @view.message.val()
    if _.isEmpty(path) || path.match(/[^0-9\/']/ig)?
      @view.error.text(t("wallet.message.index.invalid_path"))
      return
    if _.isEmpty(message)
      @view.error.text(t("wallet.message.index.invalid_message"))
      return
    dialog = new WalletMessageProcessingDialogViewController(path: path, message: message, editable: @_isEditable)
    @getDialog().push dialog

Fix Message signature via API

Fix Message signature via API


describe "Skr.Models.SalesOrder", ->

    xit "must have a customer to save", (done)->
        model = new Skr.Models.SalesOrder()
        Lanes.Test.ModelSaver.perform(model, done).then (save)->
            expect(save.error).toHaveBeenCalled()
            expect(model.errors.customer).toContain("is not set")

describe "Skr.Models.SalesOrder", ->

    it "it calculates total", ->
        so = new Skr.Models.SalesOrder()
        so.lines.add(sku_code: 'TEST', price: 2.12, qty:1)
        expect(so.total).toEqual(jasmine.any(_.bigDecimal))
        expect(so.total.toString()).toEqual('2.12')
        so.order_total = 1.42
        expect(so.total.toString()).toEqual('1.42')
        expect(so.total).toEqual(jasmine.any(_.bigDecimal))

    it 'updates total when lines change', ->
        so = new Skr.Models.SalesOrder()
        line = so.lines.add(sku_code: 'TEST', price: 2.12, qty:1)
        line.price = 3.42
        expect(so.total.toString()).toEqual('3.42')
        line.price = 233.111
        expect(so.total.toString()).toEqual('233.111')

Test that total is updated when lines change


angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
  return (products, hubPermissions, hub_id) ->
    return [] if !hub_id
    return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)

angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
  return (products, hubPermissions, hub_id) ->
    return [] if !hub_id
    return [] if !hubPermissions[hub_id]
    return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)

Add guard clause on variant overrides filter in case hub id is not present in the hubPermissions


Message = require './message'

class PanelView extends HTMLElement
  initialize:(@linter) ->
    @id = 'linter-panel'
    @_decorations = []

  empty: ->
    return unless @_decorations.length
    @_decorations.forEach (decoration) ->
      try decoration.destroy()
    @_decorations = []
    @innerHTML = ''

  update: ->
    if @linter.views.messages.length
      @render @linter.views.messages
      @linter.views.setPanelVisibility(true)
    else
      @hide()

  hide: ->
    @empty()
    @linter.views.setPanelVisibility(false)

  render: (messages) ->
    @empty()
    messages.forEach (message) =>
      if @linter.views.scope is 'file'
        return unless message.currentFile
      if message.currentFile and message.position
        p = message.position
        range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
        marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'line-number', class: "line-number-#{message.type.toLowerCase()}"
        )
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'highlight', class: "highlight-#{message.type.toLowerCase()}"
        )
      MessageLine = new Message()
      MessageLine.initialize(message, @linter.views.scope is 'project')
      @appendChild MessageLine

module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})

Message = require './message'

class PanelView extends HTMLElement
  initialize:(@linter) ->
    @id = 'linter-panel'

  render: (messages) ->
    messages.forEach (message) =>
      if @linter.views.scope is 'file'
        return unless message.currentFile
      if message.currentFile and message.position
        p = message.position
        range = [[p[0][0] - 1, p[0][1] - 1], [p[1][0] - 1, p[1][1]]]
        marker = @linter.activeEditor.markBufferRange range, {invalidate: 'never'}
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'line-number', class: "line-number-#{message.type.toLowerCase()}"
        )
        @_decorations.push @linter.activeEditor.decorateMarker(
          marker, type: 'highlight', class: "highlight-#{message.type.toLowerCase()}"
        )
      MessageLine = new Message()
      MessageLine.initialize(message, @linter.views.scope is 'project')
      @appendChild MessageLine

module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})

Remove redundant part of PanelView

:fire: Remove redundant part of PanelView


enemies = null
enemyList = []

class EnemyManager

  maxEnemies: 15
  spawnFrequency: 3 #seconds

  constructor: ->
    @enemiesOnScreen = 0
    enemies = game.add.group()
    enemies.enableBody = true

    game.time.events.loop(Phaser.Timer.SECOND * @spawnFrequency, @spawnWrapper, this)

  spawn: (x, y) ->
    @enemiesOnScreen++
    enemy = new Enemy(x, y)
    enemies.add(enemy.ref)
    enemyList.push(enemy)

  spawnWrapper: ->
    if @enemiesOnScreen < @maxEnemies
      x = Math.floor(Math.random()*(player.ref.x + 300) + player.ref.x - 300)
      while Math.abs(player.ref.x - x) <= 175
        x = Math.floor(Math.random()*(player.ref.x + 300) + player.ref.x - 300)
      y = Math.floor(Math.random()*(GameWorld.groundHeight-1000) + GameWorld.groundHeight-100)
      new Explosion(x-32, y-50, 0xffffff, player.scale, 5)
      @spawn(x, y)

enemies = null
enemyList = []

class EnemyManager

  maxEnemies: 15

  constructor: ->
    @enemiesOnScreen = 0
    enemies = game.add.group()
    enemies.enableBody = true

    switch currentLevel
      when 'one'   then @spawnFrequency = 4 #seconds
      when 'two'   then @spawnFrequency = 3 #seconds
      when 'three' then @spawnFrequency = 2 #seconds
      when 'four'  then @spawnFrequency = 1 #second

    game.time.events.loop(Phaser.Timer.SECOND * @spawnFrequency, @spawnWrapper, this)

  spawn: (x, y) ->
    @enemiesOnScreen++
    enemy = new Enemy(x, y)
    enemies.add(enemy.ref)
    enemyList.push(enemy)

  spawnWrapper: ->
    if @enemiesOnScreen < @maxEnemies
      x = Math.floor(Math.random()*(player.ref.x + 300) + player.ref.x - 300)
      while Math.abs(player.ref.x - x) <= 175
        x = Math.floor(Math.random()*(player.ref.x + 300) + player.ref.x - 300)
      y = Math.floor(Math.random()*(GameWorld.groundHeight-1000) + GameWorld.groundHeight-100)
      new Explosion(x-32, y-50, 0xffffff, player.scale, 5)
      @spawn(x, y)

Add varying enemy spawn frequency depending on selected level


crypto = nodeRequire 'crypto'

module.exports =
class Pasteboard
  signatureForMetadata: null

  md5: (text) ->
    crypto.createHash('md5').update(text).digest('hex')

  write: (text, metadata) ->
    @signatureForMetadata = @md5(text)
    @metadata = metadata
    $native.writeToPasteboard(text)

  read: ->
    text = $native.readFromPasteboard()
    value = [text]
    value.push(@metadata) if @signatureForMetadata == @md5(text)
    value

crypto = nodeRequire 'crypto'

module.exports =
class Pasteboard
  signatureForMetadata: null

  md5: (text) ->
    crypto.createHash('md5').update(text, 'utf8').digest('hex')

  write: (text, metadata) ->
    @signatureForMetadata = @md5(text)
    @metadata = metadata
    $native.writeToPasteboard(text)

  read: ->
    text = $native.readFromPasteboard()
    value = [text]
    value.push(@metadata) if @signatureForMetadata == @md5(text)
    value

Set input encoding to utf8

Set input encoding to utf8


#!highlights/node_modules/coffee-script/bin/coffee

Highlights = require 'highlights'
fs = require 'fs'
path = require 'path'
split = require 'split'
highlighter = new Highlights()
highlighter.requireGrammarsSync
  modulePath: require.resolve('./atom-language-perl6/package.json')

stdin = process.openStdin()
stdin.setEncoding 'utf8'

stdin.on 'data', (input) ->
    name = input.trim()
    process.exit() if name == 'exit'
    file_to_hl = path.resolve(name)
    console.error "Highlights is reading #{file_to_hl}"
    foo = ->
      fs.readFileSync file_to_hl, 'utf8'

    html = highlighter.highlightSync
      fileContents: foo()
      scopeName: 'source.perl6fe'

    console.log html

#!highlights/node_modules/coffee-script/bin/coffee

Highlights = require 'highlights'
fs = require 'fs'
path = require 'path'
highlighter = new Highlights()
highlighter.requireGrammarsSync
  modulePath: require.resolve('./atom-language-perl6/package.json')

stdin = process.openStdin()
stdin.setEncoding 'utf8'

stdin.on 'data', (input) ->
    name = input.trim()
    process.exit() if name == 'exit'
    file_to_hl = path.resolve(name)
    console.error "Highlights is reading #{file_to_hl}"
    foo = ->
      fs.readFileSync file_to_hl, 'utf8'

    html = highlighter.highlightSync
      fileContents: foo()
      scopeName: 'source.perl6fe'

    console.log html

Remove split which isn't in the node package dependencies


Darkswarm.controller "AccordionCtrl", ($scope, localStorageService, $timeout, $document, CurrentHub) ->
  key = "accordion_#{$scope.order.id}#{CurrentHub.hub.id}#{$scope.order.user_id}"
  value = if localStorageService.get(key) then {} else { details: true, billing: false, shipping: false, payment: false }
  localStorageService.bind $scope, "accordion", value, key
  $scope.accordionSections = ["details", "billing", "shipping", "payment"]

  $scope.show = (section)->
    $scope.accordion[section] = true

  $scope.$on 'purchaseFormInvalid', (event, form) ->
    # Scroll to first invalid section
    for section in $scope.accordionSections
      if not form[section].$valid
        $scope.show section

        # If we call scrollTo() directly after show(), when one of the accordions above the
        # scroll location is closed by show(), scrollTo() will scroll to the old location of
        # the element. Putting this in a zero-length timeout is enough delay for the DOM to
        # have updated.
        $timeout ->
          # Scrolling is confused by our position:fixed top bar - add an offset to scroll
          # to the correct location, plus 5px buffer
          offset_height = $("nav.top-bar").height() + 5
          $document.scrollTo $("##{section}"), offset_height, 500
        break

Darkswarm.controller "AccordionCtrl", ($scope, localStorageService, $timeout, $document, CurrentHub) ->
  key = "accordion_#{$scope.order.id}#{CurrentHub.hub.id}#{$scope.order.user_id}"
  value = if localStorageService.get(key) then {} else { details: true, billing: false, shipping: false, payment: false }
  localStorageService.bind $scope, "accordion", value, key
  $scope.accordionSections = ["details", "billing", "shipping", "payment"]
  # Scrolling is confused by our position:fixed top bar - add an offset to scroll
  # to the correct location, plus 5px buffer
  offset_height = $("nav.top-bar").height() + 5

  $scope.show = (section)->
    $scope.accordion[section] = true
    # If we call scrollTo() directly after show(), when one of the accordions above the
    # scroll location is closed by show(), scrollTo() will scroll to the old location of
    # the element. Putting this in a 50 ms timeout is enough delay for the DOM to
    # have updated.
    $timeout (->
      $document.scrollTo $("##{section}"), offset_height, 500), 50

  $scope.$on 'purchaseFormInvalid', (event, form) ->
    # Scroll to first invalid section
    for section in $scope.accordionSections
      if not form[section].$valid
        $scope.show section
        break

Call toScroll after any new panel is . Also added an actual delay as it still scrolled to the 'old' location.. 10 ms was too short to allow the location to update after the old panel closes (on Chrome), 50 ms seems ok.


require './spec-helper'
ColorSearch = require '../lib/color-search'

describe 'ColorSearch', ->
  [search, pigments, project] = []

  beforeEach ->
    waitsForPromise -> atom.packages.activatePackage('pigments').then (pkg) ->
      pigments = pkg.mainModule
      project = pigments.getProject()

    waitsForPromise -> project.initialize()

  describe 'when created with basic options', ->
    beforeEach ->
      search = new ColorSearch
        sourceNames: [
          '**/*.styl'
          '**/*.less'
        ]
        ignoredNames: [
          'project/vendor/**'
        ]
        context: project.getContext()

    it 'dispatches a did-complete-search when finalizing its search', ->
      spy = jasmine.createSpy('did-complete-search')
      search.onDidCompleteSearch(spy)
      waitsFor -> spy.callCount > 0
      runs -> expect(spy.argsForCall[0][0].length).toEqual(23)

require './spec-helper'
ColorSearch = require '../lib/color-search'

describe 'ColorSearch', ->
  [search, pigments, project] = []

  beforeEach ->
    atom.config.set 'pigments.sourceNames', [
      '**/*.styl'
      '**/*.less'
    ]

    waitsForPromise -> atom.packages.activatePackage('pigments').then (pkg) ->
      pigments = pkg.mainModule
      project = pigments.getProject()

    waitsForPromise -> project.initialize()

  describe 'when created with basic options', ->
    beforeEach ->
      search = new ColorSearch
        sourceNames: atom.config.get 'pigments.sourceNames'
        ignoredNames: [
          'project/vendor/**'
        ]
        context: project.getContext()

    it 'dispatches a did-complete-search when finalizing its search', ->
      spy = jasmine.createSpy('did-complete-search')
      search.onDidCompleteSearch(spy)
      waitsFor -> spy.callCount > 0
      runs -> expect(spy.argsForCall[0][0].length).toEqual(23)

Fix missing config in search specs

:green_heart: Fix missing config in search specs


Q = require('q')

class GeoCoder
  constructor: () ->
    @geocoder = new google.maps.Geocoder()
  code: (request) ->
    deferred = Q.defer()
    @geocoder.geocode request, (results, status) ->
      if status is google.maps.GeocoderStatus.OK
        deferred.resolve results.map (result) ->
          address: result.formatted_address
          location:
            longitude: result.geometry.loaction.lng()
            latitude:  result.geometry.loaction.lat()
      else
        deferred.reject status
    deferred.promise
  codeAddress: (address) ->
    @code address: address
  codeLocation: (location) ->
    @code location: google.maps.LatLng(location.latitude, location.longitude)

exports = {GeoCoder}

$ = require('jquery')
Bacon = require('bacon')

class GeoCoder
  constructor: () ->
    @geocoder = new google.maps.Geocoder()
  code: (request) ->
    deferred = $.Deferred()
    @geocoder.geocode request, (results, status) ->
      if status is google.maps.GeocoderStatus.OK
        deferred.resolve results.map (result) ->
          address: result.formatted_address
          location:
            longitude: result.geometry.loaction.lng()
            latitude:  result.geometry.loaction.lat()
      else
        deferred.reject status
    Bacon.fromPromise deferred
  codeAddress: (address) ->
    @code address: address
  codeLocation: (location) ->
    @code location: google.maps.LatLng(location.latitude, location.longitude)

class CurrentLocation
  constructor: ->
    stream = Bacon.fromBinder (sink) ->
      navigator.geolocation.watchPosition(
        (coords:{longitude, latitude}) -> sink {longitude, lataiude}
        (e) -> sink new Bacon.Error(e)
    @location = stream.toProperty()

module.exports = {GeoCoder, CurrentLocation}

Add a class for tracking current location


ETahi.PaperEditRoute = ETahi.AuthorizedRoute.extend
  beforeModel: ->
    visualEditorScript = '/visual-editor.js'
    unless ETahi.LazyLoaderMixin.loaded[visualEditorScript]
      $.getScript(visualEditorScript).then ->
        ETahi.LazyLoaderMixin.loaded[visualEditorScript] = true

  model: (params) ->
    paper = @modelFor('paper')
    new Ember.RSVP.Promise((resolve, reject) ->
      paper.get('tasks').then((tasks) -> resolve(paper)))

  afterModel: (model) ->
    @replaceWith('paper.index', model) if model.get('submitted')

  setupController: (controller, model) ->
    controller.set('model', model)
    controller.set 'authors', @store.all('author').filter (author) =>
      author.get('authorGroup.paper') == model

  actions:
    viewCard: (task) ->
      paper = @modelFor('paper')
      redirectParams = ['paper.edit', @modelFor('paper')]
      @controllerFor('application').get('overlayRedirect').pushObject(redirectParams)
      @controllerFor('application').set('overlayBackground', 'paper/edit')
      @transitionTo('task', paper.id, task.id)

ETahi.PaperEditRoute = ETahi.AuthorizedRoute.extend
  beforeModel: ->
    visualEditorScript = '/visual-editor.js'
    unless ETahi.LazyLoaderMixin.loaded[visualEditorScript]
      $.getScript(visualEditorScript).then ->
        ETahi.LazyLoaderMixin.loaded[visualEditorScript] = true

  model: ->
    paper = @modelFor('paper')
    new Ember.RSVP.Promise((resolve, reject) ->
      paper.get('tasks').then((tasks) -> resolve(paper)))

  afterModel: (model) ->
    @replaceWith('paper.index', model) if model.get('submitted')

  setupController: (controller, model) ->
    controller.set('model', model)
    controller.set 'authors', @store.all('author').filter (author) =>
      model.get('authorGroups').indexOf(author.get('authorGroup')) > -1

  actions:
    viewCard: (task) ->
      paper = @modelFor('paper')
      redirectParams = ['paper.edit', @modelFor('paper')]
      @controllerFor('application').get('overlayRedirect').pushObject(redirectParams)
      @controllerFor('application').set('overlayBackground', 'paper/edit')
      @transitionTo('task', paper.id, task.id)

Change Author filter criteria in PaperEditRoute.

Don't filter using AuthorGroup.paper, the yet-to-be-hydrated paper model
will clobber the reference and screw things up.


Shothere.Views.Movies ||= {}

class Shothere.Views.Movies.IndexView extends Shothere.Views.MapView
  template: JST["templates/movies/index"]

  initialize: () ->
    @options.movies.bind('reset', @addAll)

  addAll: () =>
    @options.movies.each(@addOne)

  addOne: (movie) =>
    view = new Shothere.Views.Movies.MovieView({model : movie})
    @$("tbody").append(view.render().el)
    @addMarker movie.get("latitude"), movie.get("longitude")

  render: =>
    $(@el).html(@template(movies: @options.movies.toJSON() ))
    @renderMap @options.center[0], @options.center[1], 2
    @addAll()
    return this

Shothere.Views.Movies ||= {}

class Shothere.Views.Movies.IndexView extends Shothere.Views.MapView
  template: JST["templates/movies/index"]

  templatePopup: JST["templates/movies/popup"]

  initialize: () ->
    @options.movies.bind('reset', @addAll)

  addAll: () =>
    @options.movies.each(@addOne)

  addOne: (movie) =>
    view = new Shothere.Views.Movies.MovieView({model : movie})
    @$("tbody").append(view.render().el)
    @addMarkerWithPopup movie.get("latitude"), movie.get("longitude"), @templatePopup, movie.toJSON()

  render: =>
    $(@el).html(@template(movies: @options.movies.toJSON() ))
    @renderMap @options.center[0], @options.center[1], 2
    @addAll()
    return this

Add popups to the index map

Add popups to the index map


if @Curri && @Curri.user
  userData =
    email: Curri.user.email
    classRole: Curri.user.classrole_type
    created: Curri.user.created_at
    firstName: Curri.user.first_name || ''
    lastName: Curri.user.last_name || ''

  analytics.identify(Curri.user.id, userData)

if @Curri && @Curri.user
  userData =
    email: Curri.user.email
    classRole: Curri.user.classrole_type
    created: Curri.user.created_at
    firstName: Curri.user.first_name || 'No'
    lastName: Curri.user.last_name || 'Name'

  analytics.identify(Curri.user.id, userData)

Fix for issue with blank names

Fix for issue with blank names


ModalView = require 'views/core/ModalView'
template = require 'templates/courses/student-log-in-modal'
auth = require 'core/auth'
forms = require 'core/forms'
User = require 'models/User'

module.exports = class StudentSignInModal extends ModalView
  id: 'student-log-in-modal'
  template: template

  events:
    'click #log-in-btn': 'onClickLogInButton'
    'submit form': 'onSubmitForm'

  onSubmitForm: (e) ->
    e.preventDefault()
    @login()

  onClickLogInButton: ->
    @login()

  login: ->
    userObject = forms.formToObject @$el
    res = tv4.validateMultiple userObject, User.schema
    return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
    @enableModalInProgress(@$el)
    auth.loginUser userObject, (jqxhr) =>
      error = jqxhr.responseJSON[0]
      message = error.property + ' ' + error.message
      @disableModalInProgress(@$el)
      @$('#errors-alert').text(message).removeClass('hide')

ModalView = require 'views/core/ModalView'
template = require 'templates/courses/student-log-in-modal'
auth = require 'core/auth'
forms = require 'core/forms'
User = require 'models/User'

module.exports = class StudentSignInModal extends ModalView
  id: 'student-log-in-modal'
  template: template

  events:
    'click #log-in-btn': 'onClickLogInButton'
    'submit form': 'onSubmitForm'

  onSubmitForm: (e) ->
    e.preventDefault()
    @login()

  onClickLogInButton: ->
    @login()

  login: ->
    data = forms.formToObject @$el
    @enableModalInProgress(@$el)
    auth.loginUser data, (jqxhr) =>
      error = jqxhr.responseJSON[0]
      message = _.filter([error.property, error.message]).join(' ')
      @disableModalInProgress(@$el)
      @$('#errors-alert').text(message).removeClass('hide')

Fix StudentLoginModal when email or password is not included


# 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://coffeescript.org/

ready = ->
	parseText = (text) ->
		# parsing markdown to html
		$('#item-preview-content').html(marked(text))
		# highlighting
		$('#item-preview-content').find('pre code').each (i, block) ->
			hljs.highlightBlock(block);
	parseBody = ->
		$('#item-body').html(marked(text))
		$('#item-body').find('pre code').each (i, block) ->
			hljs.highlightBlock(block);
	checkTextChange = (e) ->
		old = v = $(e).find('#item-text').val()
		return ->
			v = $(e).find('#item-text').val()
			if old isnt v
				old = v
				parseText(v)

	parseBody()
	parseText $('#item-text').val() if $('#item-text').val()
	$(document).on 'keyup', '#item-text', checkTextChange(this)

$(document).ready(ready)
$(document).on('page:load', ready)

# 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://coffeescript.org/

ready = ->
	parseText = (text) ->
		# parsing markdown to html
		$('#item-preview-content').html(marked(text))
		# highlighting
		$('#item-preview-content').find('pre code').each (i, block) ->
			hljs.highlightBlock(block);
	parseBody = ->
		$('#item-body').html(marked(text))
		$('#item-body').find('pre code').each (i, block) ->
			hljs.highlightBlock(block);
	checkTextChange = (e) ->
		old = v = $(e).find('#item-text').val()
		return ->
			v = $(e).find('#item-text').val()
			if old isnt v
				old = v
				parseText(v)

	parseBody() if $('#item-body')[0]
	parseText $('#item-text').val() if $('#item-text').val()
	$(document).on 'keyup', '#item-text', checkTextChange(this)

$(document).ready(ready)
$(document).on('page:load', ready)

Fix error for parsing undefined

Fix error for parsing undefined


## Key-value table panel without a table header

## Example
#  React.createElement TablePanel,
#    type:  'success'
#    title: "Volume for each room"
#    obj:   { ocean: 23, air: 12, ... }

@MovingVolumePanel = React.createClass
  getInitialState: ->
    data: @props.data

  render: ->
    $ = React.DOM

    $.div
      className: "panel panel-#{ @props.type }"
      $.div
        className: 'panel-heading'
        @props.title
      $.div
        className: 'panel-body'
        $.table
          className: "table"
          $.tbody null,
            for key, i in Object.keys(@props.data)
              $.tr
                key: i # Requried by React.js
                $.td
                  className: 'lead'
                  key
                $.td
                  className: 'lead'
                  @props.data[key]

## Key-value table panel without a table header

## Example
#  React.createElement TablePanel,
#    type:  'success'
#    title: "Volume for each room"
#    obj:   { ocean: 23, air: 12, ... }

@MovingVolumePanel = React.createClass
  getInitialState: ->
    data: @props.data

  render: ->
    $ = React.DOM

    $.div
      className: "panel panel-#{ @props.type }"
      $.div
        className: 'panel-heading'
        @props.title
      $.div
        className: 'panel-body'
        $.table
          className: "table table-striped"
          $.tbody null,
            for key, i in Object.keys(@props.data)
              $.tr
                key: i # Requried by React.js
                $.td
                  className: 'lead text-center col-sm-5'
                  key
                $.td
                  className: 'lead text-center col-sm-5'
                  @props.data[key]

Adjust table widths for table panels

Adjust table widths for table panels


mongo = require '../models/connection'

exports.collect = (req, res) ->
    if req.get('content-type').indexOf('application/json') == -1
        throw new Error("Body request is not JSON.")

    jsonData = req.body

    mongo.db.collection('sensor').count(
        {sensor_id : jsonData.sensor_id}, (err, count) ->
            if err
                console.log(err)
                res.send 500

            else if  count < 1
                console.log "Adding new sensor " + jsonData.sensor_id + " to DB"
                mongo.db.collection('sensor').insert(
                    {sensor_id : jsonData.sensor_id, model : jsonData.model }, {w:1}, (err, collection) ->
                        console.log(err)
                )
        )

    mongo.db.collection('data').insert(
        jsonData, {w:1}, (err, collection) ->
            if err
                console.log(err)
                res.send 500
            else
                res.send 200
    )

mongo = require '../models/connection'

exports.collect = (req, res) ->
    if req.get('content-type').indexOf('application/json') == -1
        throw new Error("Body request is not JSON.")

    jsonData = req.body

    mongo.db.collection('sensors').count(
        {sensor_id : jsonData.sensor_id}, (err, count) ->
            if err
                console.log(err)
                res.send 500

            else if  count < 1
                mongo.db.collection('sensors').insert(
                    {sensor_id : jsonData.sensor_id, model : jsonData.model, hub_id : jsonData.hub_id, created_on : Date.now()}, {w:1}, (err, collection) ->
                        console.log(err)
                )
        )
    data =
        sensor_id : jsonData.sensor_id
        value : jsonData.value
        model : jsonData.model
        unit : jsonData.unit
        created_on : jsonData.created_on

    mongo.db.collection('data').insert(
        jsonData, {w:1}, (err, collection) ->
            if err
                console.log(err)
                res.send 500
            else
                res.send 200
    )

Save the hub_id from the HomePi

Save the hub_id from the HomePi


Settings = require 'settings-sharelatex'

module.exports = Features =
	externalAuthenticationSystemUsed: ->
		Settings.ldap? or Settings.saml? or Settings.overleaf?.oauth?

	hasFeature: (feature) ->
		switch feature
			when 'homepage'
				return Settings.enableHomepage
			when 'registration'
				return not Features.externalAuthenticationSystemUsed()
			when 'github-sync'
				return Settings.enableGithubSync
			when 'v1-return-message'
				return Settings.accountMerge? and Settings.overleaf?
			when 'publish-modal'
				return Settings.showPublishModal
			when 'custom-togglers'
				return Settings.overleaf?
			else
				throw new Error("unknown feature: #{feature}")

Settings = require 'settings-sharelatex'

module.exports = Features =
	externalAuthenticationSystemUsed: ->
		Settings.ldap? or Settings.saml? or Settings.overleaf?.oauth?

	hasFeature: (feature) ->
		switch feature
			when 'homepage'
				return Settings.enableHomepage
			when 'registration'
				return not Features.externalAuthenticationSystemUsed()
			when 'github-sync'
				return Settings.enableGithubSync
			when 'v1-return-message'
				return Settings.accountMerge? and Settings.overleaf?
			when 'publish-modal'
				return Settings.showPublishModal
			when 'custom-togglers'
				return Settings.overleaf?
			when 'templates'
				return !Settings.overleaf?
			else
				throw new Error("unknown feature: #{feature}")

Add `templates` to the `hasFeature` function


# 3rd Party Libraries
immutable = require('immutable')

# Internal Libraries
misc = require('smc-util/misc')
{types} = misc

exports.get_store_def = (name) ->
    name: name

    stateTypes:
        height             : types.number         # 0 means not rendered; otherwise is the height of the chat editor
        input              : types.string         # content of the input box
        is_preview         : types.bool           # currently displaying preview of the main input chat
        last_sent          : types.string         # last sent message
        messages           : types.immutable.Map  # Map of all messages
        offset             : types.number         # information about where on screen the chat editor is located
        position           : types.number         # more info about where chat editor is located
        saved_mesg         : types.string         # The message state before saving and edited message. Potentially broken with mutiple edits
        use_saved_position : types.bool           # whether or not to maintain last saved scroll position (used when unmounting then remounting, e.g., due to tab change)

    getInitialState: =>
        height             : 0
        input              : ''
        is_preview         : undefined
        last_sent          : undefined
        messages           : undefined
        offset             : undefined
        position           : undefined
        saved_mesg         : undefined
        use_saved_position : undefined

# 3rd Party Libraries
immutable = require('immutable')

# Internal Libraries
misc = require('smc-util/misc')
{types} = misc

exports.get_store_def = (name) ->
    name: name

    stateTypes:
        height             : types.number         # 0 means not rendered; otherwise is the height of the chat editor
        input              : types.string         # content of the input box
        is_preview         : types.bool           # currently displaying preview of the main input chat
        last_sent          : types.string         # last sent message
        messages           : types.immutable.Map  # Map of all messages
        offset             : types.number         # information about where on screen the chat editor is located
        position           : types.number         # more info about where chat editor is located
        saved_mesg         : types.string         # The message state before saving and edited message. Potentially broken with mutiple edits
        use_saved_position : types.bool           # whether or not to maintain last saved scroll position (used when unmounting then remounting, e.g., due to tab change)
        saved_position     : types.number

    getInitialState: =>
        height             : 0
        input              : ''
        is_preview         : undefined
        last_sent          : undefined
        messages           : undefined
        offset             : undefined
        position           : undefined
        saved_mesg         : undefined
        use_saved_position : undefined
        saved_position     : undefined

Fix scroll save for chat

Fix scroll save for chat


Walrus = require '../../bin/walrus'

TestHelpers =

  pass : ( specs, suffix='' ) ->

    fs    = require 'fs'
    path  = require 'path'
    exec  = require( 'child_process' ).exec

    for file in fs.readdirSync specs when path.extname( file ) is '.wal'

      do ( file ) ->

        base = path.basename file, '.wal'

        text = fs.readFileSync "#{specs}/#{base}.wal",           'utf8'
        json = fs.readFileSync "#{specs}/#{base}.js",            'utf8'
        html = fs.readFileSync "#{specs}/#{base}#{suffix}.html", 'utf8'

        tmpl = Walrus.Parser.parse text

        it "should pass the #{base}#{suffix} example", ( done ) ->

          comp = tmpl.compile( eval( "(#{json})" ) )

          if comp is html
            done( )
          else

            cmd = """
            printf "#{comp}" | diff --unified #{specs}/#{base}#{suffix}.html -
            """

            exec cmd, ( error, stdout, stderr ) ->

              done new Error "Expected did not match actual:\n" + stdout

module.exports = TestHelpers

Walrus = require '../../bin/walrus'

fs   = require 'fs'
path = require 'path'

TestHelpers =

  read : ( filename ) -> fs.readFileSync filename, 'utf8' if path.existsSync filename

  pass : ( specs, suffix='' ) ->

    fs    = require 'fs'
    path  = require 'path'
    exec  = require( 'child_process' ).exec

    for file in fs.readdirSync specs when path.extname( file ) is '.wal'

      do ( file ) =>

        base = path.basename file, '.wal'

        text = @read "#{specs}/#{base}.wal"
        json = @read "#{specs}/#{base}.js"
        html = @read "#{specs}/#{base}#{suffix}.html"

        # if we can't find the suffixed version, try and find
        # one without the suffix and use that instead.
        html = @read "#{specs}/#{base}.html" if not html

        # if _that_ one doesn't exist, throw a helpful error.
        throw "Can't find example html at #{specs}/#{base}#{suffix}.html or #{specs}/#{base}.html" if not html

        tmpl = Walrus.Parser.parse text

        it "should pass the #{base}#{suffix} example", ( done ) ->

          comp = tmpl.compile( eval( "(#{json})" ) )

          if comp is html
            done( )
          else

            cmd = """
            printf "#{comp}" | diff --unified #{specs}/#{base}#{suffix}.html -
            """

            exec cmd, ( error, stdout, stderr ) ->

              done new Error "Expected did not match actual:\n" + stdout

module.exports = TestHelpers

Make test helpers smart about checking for suffixed tests


Sprangular.controller 'CheckoutDeliveryAndPaymentCtrl', ($scope, Account, Cart, Checkout) ->
  $scope.order = Cart.current
  $scope.processing = false
  $scope.user = Account.user

  $scope.$watch 'order.state', (state) ->
    $scope.done = state == 'confirm'
    $scope.active = _.contains(['delivery', 'payment'], state)

  $scope.edit = ->
    $scope.order.state = 'delivery'

  $scope.advance = ->
    order = $scope.order
    return if order.shippingAddress.isInvalid() || (!order.billToShipAddress && order.billingAddress.isInvalid())

    $scope.processing = true

    Checkout.setPayment()
      .then ->
          $scope.processing = false
        , ->
          $scope.processing = false

Sprangular.controller 'CheckoutDeliveryAndPaymentCtrl', ($scope, Account, Cart, Checkout) ->
  $scope.order = Cart.current
  $scope.processing = false
  $scope.user = Account.user

  $scope.$watch 'order.state', (state) ->
    $scope.done = state == 'confirm'
    $scope.active = _.contains(['delivery', 'payment'], state)

  $scope.edit = ->
    $scope.order.state = 'delivery'

  $scope.advance = ->
    order = $scope.order
    return if order.creditCard.isInvalid()

    $scope.processing = true

    Checkout.setPayment()
      .then ->
          $scope.processing = false
        , ->
          $scope.processing = false

Check if CC is valid when setting payment


uuid = require 'node-uuid'
ssdp = require './ssdp'
extend = require('./helpers').extend

configDefaults =
    device:
        schema:
            prefix: 'urn:schemas-upnp-org:device'
        uuid: 'uuid:' + uuid()
    network:
        ssdp:
            timeout: 1800
            address: '239.255.255.250'
            port: 1900

upnp = {}
upnp.start = (config, callback) ->
    # add config values recieved from caller
    extend config, configDefaults
    ssdp.start config, (err, msg) ->
        callback err, msg

module.exports = upnp

uuid = require 'node-uuid'
ssdp = require './ssdp'
xmlServer = require './xml-server'

upnp = {}
upnp.createDevice = (type, version, callback) =>
    for arg in arguments
        if typeof arg is 'Function'
            callback = arg

    @config =
        device:
            schema:
                prefix: 'urn:schemas-upnp-org:device'
            uuid: 'uuid:' + uuid()
            type: type ? 'Basic'
            version: version ? '1.0'
        network:
            ssdp:
                timeout: 1800
                address: '239.255.255.250'
                port: 1900
            http:
                address: '192.168.9.3'
                port: 3000

    xmlServer.start @config, (err, msg) ->
        callback err, msg

upnp.announce = (name, version, callback) =>
    for arg in arguments
        if typeof arg is 'Function'
            callback = arg

    @config['app'] =
        name: name ? 'Generic UPnP Device'
        version: version ? '1.0'

    ssdp.start @config, (err, msg) ->
        callback err, msg

module.exports = upnp

Initialize UPnP in a different way

Initialize UPnP in a different way


# Your keymap
#
# Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors
# to apply styles to elements, Atom keymaps use selectors to associate
# keystrokes with events in specific contexts.
#
# You can create a new keybinding in this file by typing "key" and then hitting
# tab.
#
# Here's an example taken from Atom's built-in keymap:
#
# 'atom-text-editor':
#   'enter': 'editor:newline'
#
# 'atom-workspace':
#   'ctrl-shift-p': 'core:move-up'
#   'ctrl-p': 'core:move-down'
#
# You can find more information about keymaps in these guides:
# * https://atom.io/docs/latest/customizing-atom#customizing-key-bindings
# * https://atom.io/docs/latest/advanced/keymaps
#
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it here:
# https://github.com/bevry/cson#what-is-cson

'atom-text-editor':
	'ctrl-cmd-t': 'whitespace:convert-spaces-to-tabs'
	'cmd-l': 'go-to-line:toggle'

Add additional shortcut for go-to-line in Atom


module.exports = (obj, methodName, key, logger) ->
	metrics = require('./metrics')

	if typeof obj[methodName] != 'function'
		throw new Error("[Metrics] expected object property '#{methodName}' to be a function")

	realMethod = obj[methodName]
	key = "methods.#{key}"

	obj[methodName] = (originalArgs...) ->

		[firstArgs..., callback] = originalArgs
		if !callback? || typeof callback != 'function'
			throw new Error(
				"[Metrics] expected wrapped method '#{methodName}' to be invoked with a callback"
			)

		timer = new metrics.Timer(key)
		start = new Date()

		realMethod.call this, firstArgs..., (callbackArgs...) ->
			timer.done()
			elapsedTime = new Date() - start
			if logger?
				logger.log {key, elapsedTime}, "[Metrics] timed async method call"
			callback.apply this, callbackArgs

module.exports = (obj, methodName, key, logger) ->
	metrics = require('./metrics')

	if typeof obj[methodName] != 'function'
		throw new Error("[Metrics] expected object property '#{methodName}' to be a function")

	realMethod = obj[methodName]
	key = "methods.#{key}"

	obj[methodName] = (originalArgs...) ->

		[firstArgs..., callback] = originalArgs
		if !callback? || typeof callback != 'function'
			throw new Error(
				"[Metrics] expected wrapped method '#{methodName}' to be invoked with a callback"
			)

		timer = new metrics.Timer(key)

		realMethod.call this, firstArgs..., (callbackArgs...) ->
			elapsedTime = timer.done()
			if logger?
				logger.log {key, elapsedTime}, "[Metrics] timed async method call"
			callback.apply this, callbackArgs

Clean up, don't allocate an extra date

Clean up, don't allocate an extra date


$              = require 'jquery'
SplitView      = require '../utils/split-view'
StatusListView = require './status-list-view'
DiffView       = require './diff-view'

class ChangesView extends SplitView
  initialize: ({@uri, width}) ->
    @statusListView = new StatusListView
    @diffView       = new DiffView

    @width(width) if width > 0

    @setSubViews
      summaryView: @statusListView
      detailsView: @diffView

    @update()

  update: ->
    @statusListView.update()

  getTitle: ->
    'Commit changes'

  getURI: -> @uri

  serialize: ->
    deserializer: 'GitChangesView'
    uri: @getURI()
    width: @width()

module.exports = document.registerElement 'git-experiment-changes-view',
  prototype: ChangesView.prototype

$              = require 'jquery'
SplitView      = require '../utils/split-view'
StatusListView = require './status-list-view'
DiffView       = require './diff-view'

class ChangesView extends SplitView
  initialize: ({@uri, width}) ->
    @statusListView = new StatusListView
    @diffView       = new DiffView

    @width(width) if width > 0

    @setSubViews
      summaryView: @statusListView
      detailsView: @diffView

    @update()

  update: ->
    @el.trigger('index-updated')

  getTitle: ->
    'Commit changes'

  getURI: -> @uri

  serialize: ->
    deserializer: 'GitChangesView'
    uri: @getURI()
    width: @width()

module.exports = document.registerElement 'git-experiment-changes-view',
  prototype: ChangesView.prototype

Clear cached views on update

Clear cached views on update


Phone = require('../Phone')
PhoneNumber = require('../PhoneNumber')

class Iraq
	constructor: ->
		@countryName = "Iraq"
		@countryNameAbbr = "IRQ"
		@countryCode = '964'
		@regex = /^(?:\+|00)964(7?\d{1,2})\d{7}$/
		@optionalTrunkPrefix = '0'
		@nationalNumberSeparator = ' '
		@nationalDestinationCode =
			['1', '21', '23', '24', '25', '30', '32', '33', '36', '37', '40', '42', '43', '50', '53', '60', '62', '66']

# register
iraq = new Iraq()
Phone.countries['964'] = iraq

# exports
module.exports = iraq

Phone = require('../Phone')
PhoneNumber = require('../PhoneNumber')

class Iraq
	constructor: ->
		@countryName = "Iraq"
		@countryNameAbbr = "IRQ"
		@countryCode = '964'
		@regex = /^(?:\+|00)964(7?\d{1,2})\d{7}$/
		@optionalTrunkPrefix = '0'
		@nationalNumberSeparator = ' '
		@nationalDestinationCode =
			['7(\\d{2})', '1', '21', '23', '24', '25', '30', '32', '33', '36', '37', '40', '42', '43', '50', '53', '60', '62', '66']

	specialRules: (withoutCountryCode, withoutNDC, ndc) =>
		phone = new PhoneNumber(@countryNameAbbr, @countryCode, ndc, withoutNDC)

		if withoutNDC.length is 7
			phone.isMobile = ndc[0] is '7'
			return phone
		else if withoutNDC.length is 7 or withoutNDC.length is 6 then return phone

# register
iraq = new Iraq()
Phone.countries['964'] = iraq

# exports
module.exports = iraq

Add mobile operator codes for Iraq

Add mobile operator codes for Iraq


Bacon = require 'baconjs'
atomStreams = require './streams.coffee'

filteredPathsStream = (pairwisePathsStream, filterFn) ->
  pairwisePathsStream.flatMap (pair) ->
    Bacon.fromArray filterFn(pair)

module.exports = ->
  pathsStream = atomStreams.fromDisposable atom.project, 'onDidChangePaths'
    .merge Bacon.fromArray [ [], atom.project.getPaths() ]
  lastProjectPathsProp = pathsStream.slidingWindow(2, 2)

  addStream = filteredPathsStream lastProjectPathsProp, ([currentPaths, newPaths]) ->
    newPaths.filter (path) -> currentPaths.indexOf(path) < 0

  removeStream = filteredPathsStream lastProjectPathsProp, ([currentPaths, newPaths]) ->
    currentPaths.filter (path) -> newPaths.indexOf(path) < 0

  return {
    addStream: addStream
    removeStream: removeStream
  }

Bacon = require 'baconjs'
atomStreams = require './streams.coffee'

filteredPathsStream = (pairwisePathsStream, filterFn) ->
  pairwisePathsStream.flatMap (pair) ->
    Bacon.sequentially 0, filterFn(pair)

module.exports = ->
  pathsStream = atomStreams.fromDisposable atom.project, 'onDidChangePaths'
    .merge Bacon.sequentially 0, [ [], atom.project.getPaths() ]
  lastProjectPathsProp = pathsStream.slidingWindow(2, 2)

  addStream = filteredPathsStream lastProjectPathsProp, ([currentPaths, newPaths]) ->
    newPaths.filter (path) -> currentPaths.indexOf(path) < 0

  removeStream = filteredPathsStream lastProjectPathsProp, ([currentPaths, newPaths]) ->
    currentPaths.filter (path) -> newPaths.indexOf(path) < 0

  return {
    addStream: addStream
    removeStream: removeStream
  }

Use sequentially instead of fromArray

Use sequentially instead of fromArray

per FAQ recommendation,
https://github.com/baconjs/bacon.js/wiki/FAQ#why-isnt-my-subscriber-call
ed


# auto-indent when changing line or inserting at end of line
atom.commands.onDidDispatch (evt) ->
  validCommands = [
    'vim-mode-plus:change',
    'vim-mode-plus:insert-after-end-of-line'
  ]
  return unless evt.type in validCommands
  editor = atom.workspace.getActivePaneItem()
  element = editor.getElement()
  return unless 'insert-mode' in element.classList
  for cursor in editor.getCursors()
    return unless cursor.getBufferColumn() is 0
  editor.autoIndentSelectedRows()

# auto-indent when changing line or inserting at end of line
handleAutoIndentAfterChangeLine = (evt) ->
  validCommands = [
    'vim-mode-plus:change',
    'vim-mode-plus:insert-after-end-of-line'
  ]
  return unless evt.type in validCommands
  editor = atom.workspace.getActivePaneItem()
  element = editor.getElement()
  return unless 'insert-mode' in element.classList
  for cursor in editor.getCursors()
    return unless cursor.getBufferColumn() is 0
  editor.autoIndentSelectedRows()

handleTrimWhitespaceAfterEscape = (evt) ->
  validCommands = [
    'vim-mode-plus:reset-normal-mode'
    'vim-mode-plus:activate-normal-mode'
  ]
  return unless evt.type in validCommands
  editor = atom.workspace.getActivePaneItem()
  for cursor in editor.getCursors()
    row = cursor.getBufferRow()
    text = editor.buffer.getLines()[row]
    if text.match /^\s+$/
      editor.deleteToBeginningOfLine()

atom.commands.onDidDispatch (evt) ->
  handleAutoIndentAfterChangeLine(evt)

atom.commands.onWillDispatch (evt) ->
  handleTrimWhitespaceAfterEscape(evt)

Trim trailing whitespace when exiting insert mode


module.exports = (grunt) ->
	grunt.initConfig
		clean:
			package: ['tasks/gyp.js']
			test: ['test/support/build']
		coffee:
			task:
				options:
					bare: true
				files:
					'tasks/gyp.js': 'tasks/gyp.coffee'
		mochacli:
			options:
				bail: true
				compilers: ['coffee:coffee-script/register']
			files: ['test/']

	grunt.loadNpmTasks 'grunt-contrib-clean'
	grunt.loadNpmTasks 'grunt-contrib-coffee'
	grunt.loadNpmTasks 'grunt-mocha-cli'

	grunt.registerTask 'package', ['coffee:task']
	grunt.registerTask 'test', ['clean:test', 'mochacli']

	grunt.registerTask 'default', []

module.exports = (grunt) ->
	grunt.initConfig
		clean:
			package: ['tasks/gyp.js']
			test: ['test/support/build']
		coffee:
			task:
				options:
					bare: true
				files:
					'tasks/gyp.js': 'tasks/gyp.coffee'
		mochacli:
			options:
				bail: true
				compilers: ['coffee:coffee-script/register']
			files: ['test/']

	grunt.loadNpmTasks 'grunt-contrib-clean'
	grunt.loadNpmTasks 'grunt-contrib-coffee'
	grunt.loadNpmTasks 'grunt-mocha-cli'

	grunt.registerTask 'package', ['coffee:task']
	grunt.registerTask 'test', ['clean', 'mochacli']

	grunt.registerTask 'default', []

Remove tasks/gyp.js before running tests

Otherwise Grunt would prefer tasks/gyp.js over tasks/gyp.coffee and changes in the CoffeeScript source would have no effect.


window.FactlinkAppMode ?= {}
window.FactlinkAppMode.coreInClient = (app) ->
  app.onClientApp = true
  app.startClientRegions()

  annotatedSiteEnvoy = window.initClientCommunicator()

  new ClientRouter controller: new ClientController annotatedSiteEnvoy

  FactlinkApp.clientCloseDiscussionModalInitializer annotatedSiteEnvoy
  app.vent.on 'close_discussion_modal', ->
    mp_track "Discussion Modal: Close (Button)"
    annotatedSiteEnvoy 'closeModal_noAction'

  annotatedSiteEnvoy 'modalFrameReady', Factlink.Global.can_haz

window.FactlinkAppMode ?= {}
window.FactlinkAppMode.coreInClient = (app) ->
  app.onClientApp = true
  app.startClientRegions()

  annotatedSiteEnvoy = window.initClientCommunicator()

  new ClientRouter controller: new ClientController(annotatedSiteEnvoy)

  FactlinkApp.clientCloseDiscussionModalInitializer annotatedSiteEnvoy
  app.vent.on 'close_discussion_modal', ->
    mp_track "Discussion Modal: Close (Button)"
    annotatedSiteEnvoy 'closeModal_noAction'

  annotatedSiteEnvoy 'modalFrameReady', Factlink.Global.can_haz

Add parens to call with return value

Add parens to call with return value


# If this is your first time writing a language grammar, check out:
#   - http://manual.macromates.com/en/language_grammars

'scopeName': 'source.ait'
'name': 'Ait'
'fileTypes': [
  'ait'
]
'patterns': [
  {
    'match': '^#.*$'
    'name': 'comment.ait'
  }
  {
    'captures':
      '1':
        'name': 'keyword.load.ait'
      '2':
        'name': 'constant.path.ait'
      '3':
        'name': 'keyword.semicolon.ait'
    'match': '^(@.+)\\s(.+)(;)$'
  }
  {
    'captures':
      '1':
        'name': 'constant.definition.ait'
      '2':
        'name': 'keyword.colon.ait'
    'match': '^(.*)(:)\\s'
    'name': 'definition.word.ait'
  }
  {
    'match': '[;\\[\\]()]'
    'name': 'keyword.ait'
  }
  {
    'captures':
      '1':
        'name': 'entity.number.ait'
    'match': '(-?\\d+(\\.\\d+)?)(?![\\w<>+-=*%])'
  }
  {
    'match': '[a-zA-Z0-9<>+-=\\\\*%]+'
    'name': 'entity.word.ait'
  }
  {
    'match': '(".*")'
    'name': 'constant.ait'
  }
]

# If this is your first time writing a language grammar, check out:
#   - http://manual.macromates.com/en/language_grammars

'scopeName': 'source.ait'
'name': 'Ait'
'fileTypes': [
  'ait'
]
'patterns': [
  {
    'match': '#.*$'
    'name': 'comment.ait'
  }
  {
    'captures':
      '1':
        'name': 'keyword.load.ait'
      '2':
        'name': 'constant.path.ait'
      '3':
        'name': 'keyword.semicolon.ait'
    'match': '^(@.+)\\s(.+)(;)$'
  }
  {
    'captures':
      '1':
        'name': 'constant.definition.ait'
      '2':
        'name': 'keyword.colon.ait'
    'match': '^(.*)(:)\\s'
    'name': 'definition.word.ait'
  }
  {
    'match': '[;\\[\\]()]'
    'name': 'keyword.ait'
  }
  {
    'captures':
      '1':
        'name': 'entity.number.ait'
    'match': '(-?\\d+(\\.\\d+)?)(?![\\w<>+-=*%])'
  }
  {
    'match': '[a-zA-Z0-9<>+-=\\\\*%]+'
    'name': 'entity.word.ait'
  }
  {
    'match': '(".*")'
    'name': 'constant.ait'
  }
]

Remove start of line from comment regex in syntax highlighting


path = require 'path'

module.exports = (grunt) ->
  {spawn} = require('./task-helpers')(grunt)

  grunt.registerTask 'codesign', 'Codesign the app', ->
    done = @async()

    if process.platform is 'darwin' and process.env.XCODE_KEYCHAIN
      unlockKeychain (error) ->
        if error?
          done(error)
        else
          signApp(done)
    else
      signApp(done)

  unlockKeychain = (callback) ->
    cmd = 'security'
    {XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env
    args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN]
    spawn {cmd, args}, (error) -> callback(error)

  signApp = (callback) ->
    switch process.platform
      when 'darwin'
        cmd = 'codesign'
        args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
        spawn {cmd, args}, (error) -> callback(error)
      when 'win32'
        signtool = process.env.JANKY_SIGNTOOL ? 'signtool'
        cmd = 'taskkill /FIM atom.exe & ' + signtool
        args = [path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')]
        spawn {cmd, args}, (error) -> callback(error)
      else
        callback()

path = require 'path'

module.exports = (grunt) ->
  {spawn} = require('./task-helpers')(grunt)

  grunt.registerTask 'codesign', 'Codesign the app', ->
    done = @async()

    if process.platform is 'darwin' and process.env.XCODE_KEYCHAIN
      unlockKeychain (error) ->
        if error?
          done(error)
        else
          signApp(done)
    else
      signApp(done)

  unlockKeychain = (callback) ->
    cmd = 'security'
    {XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN} = process.env
    args = ['unlock-keychain', '-p', XCODE_KEYCHAIN_PASSWORD, XCODE_KEYCHAIN]
    spawn {cmd, args}, (error) -> callback(error)

  signApp = (callback) ->
    switch process.platform
      when 'darwin'
        cmd = 'codesign'
        args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
        spawn {cmd, args}, (error) -> callback(error)
      when 'win32'
        spawn {cmd: 'taskkill', args: ['/FIM', 'atom.exe']}, ->
          cmd = process.env.JANKY_SIGNTOOL ? 'signtool'
          args = [path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')]
          spawn {cmd, args}, (error) -> callback(error)
      else
        callback()

Break up commands into two spawns

Break up commands into two spawns


{_, Document} = require 'atom'

SettingsView = null
settingsView = null

configUri = 'atom://config'

createSettingsView = (state) ->
  SettingsView ?= require './settings-view'
  unless state instanceof Document
    state = _.extend({deserializer: deserializer.name, version: deserializer.version}, state)
    state = atom.site.createDocument(state)
  settingsView = new SettingsView(state)

openPanel = (panelName) ->
  atom.workspaceView.open(configUri)
  settingsView.showPanel(panelName)

deserializer =
  acceptsDocuments: true
  name: 'SettingsView'
  version: 1
  deserialize: (state) -> createSettingsView(state)
atom.deserializers.add(deserializer)

module.exports =
  activate: ->
    atom.project.registerOpener (filePath) ->
      createSettingsView({uri: configUri}) if filePath is configUri

    atom.workspaceView.command 'settings-view:toggle', ->
      openPanel('General')

    atom.workspaceView.command 'settings-view:show-keybindings', ->
      openPanel('Keybindings')

    atom.workspaceView.command 'settings-view:change-themes', ->
      openPane('Themes')

    atom.workspaceView.command 'settings-view:install-packages', ->
      openPanel('Packages')

{_, Document} = require 'atom'

SettingsView = null
settingsView = null

configUri = 'atom://config'

createSettingsView = (state) ->
  SettingsView ?= require './settings-view'
  unless state instanceof Document
    state = _.extend({deserializer: deserializer.name, version: deserializer.version}, state)
    state = atom.site.createDocument(state)
  settingsView = new SettingsView(state)

openPanel = (panelName) ->
  atom.workspaceView.open(configUri)
  settingsView.showPanel(panelName)

deserializer =
  acceptsDocuments: true
  name: 'SettingsView'
  version: 1
  deserialize: (state) -> createSettingsView(state)
atom.deserializers.add(deserializer)

module.exports =
  activate: ->
    atom.project.registerOpener (filePath) ->
      createSettingsView({uri: configUri}) if filePath is configUri

    atom.workspaceView.command 'settings-view:toggle', ->
      openPanel('General')

    atom.workspaceView.command 'settings-view:show-keybindings', ->
      openPanel('Keybindings')

    atom.workspaceView.command 'settings-view:change-themes', ->
      openPanel('Themes')

    atom.workspaceView.command 'settings-view:install-packages', ->
      openPanel('Packages')

Call openPanel instead of openPane

Call openPanel instead of openPane


NotificationPlugin = require "../../notification-plugin"

class Campfire extends NotificationPlugin
  @receiveEvent: (config, event) ->
    # Build the message
    if event.error
      message = "#{event.error.exceptionClass}" + (if event.error.message then ": #{event.error.message}" else "") + " (#{event.error.url})"
    else
      message =  "#{event.trigger.message} in #{event.project.name} (#{event.project.url})" + ( if event.error then " in #{event.error.context} (#{event.error.url})" else "")

    # Build the request
    payload =
      message:
        body: message
        type: "TextMessage"

    url = "https://#{config.authToken}:X@#{config.domain}.campfirenow.com/room/#{config.roomId}/speak.xml"

    # Send the request to campfire
    @request
      .post(url)
      .send(payload)
      .end();

module.exports = Campfire

NotificationPlugin = require "../../notification-plugin"

class Campfire extends NotificationPlugin
  @receiveEvent: (config, event) ->
    # Build the message
    if event.error
      message = "#{event.trigger.message} in #{event.project.name}! #{event.error.exceptionClass}" + (if event.error.message then ": #{event.error.message}" else "") + " (#{event.error.url})"
    else
      message =  "#{event.trigger.message} in #{event.project.name} (#{event.project.url})" + ( if event.error then " in #{event.error.context} (#{event.error.url})" else "")

    # Build the request
    payload =
      message:
        body: message
        type: "TextMessage"

    url = "https://#{config.authToken}:X@#{config.domain}.campfirenow.com/room/#{config.roomId}/speak.xml"

    # Send the request to campfire
    @request
      .post(url)
      .send(payload)
      .end();

module.exports = Campfire

Update format for campfire plugin

Update format for campfire plugin


Augury.Views.Parameters.Edit = Backbone.View.extend(
  initialize: (attrs) ->
    @options = attrs

  events:
    'click button#save': 'save'
    'click button#cancel': 'cancel'
    'change :input': 'changed'
    'focusout :input': 'validate'

  render: ->
    @$el.html JST["admin/templates/parameters/edit"](parameter: @model)
    Backbone.Validation.bind @

    @$('#parameter-data-type').val @model.get('data_type')

    $('#content-header').find('.page-title').text(if @model.isNew() then 'New Parameter' else 'Edit Parameter')

    $('#content-header').find('.page-actions').remove()
    $('#content-header').find('.table-cell').after JST["admin/templates/parameters/back_button"]

    @

  save: (e) ->
    e.preventDefault()
    @model.validate()
    if @model.isValid()
      @model.save {}, success: @saved, error: @displayErrors

  cancel: (e) ->
    e.preventDefault()
    if @model.isNew()
      Augury.parameters.remove @model
    Backbone.history.navigate '/parameters', trigger: true

  saved: ->
    Augury.Flash.success "The parameter has been successfully saved."
)

Augury.Views.Parameters.Edit = Backbone.View.extend(
  initialize: (attrs) ->
    @options = attrs

  events:
    'click button#save': 'save'
    'click button#cancel': 'cancel'
    'change :input': 'changed'
    'focusout :input': 'validate'

  render: ->
    @$el.html JST["admin/templates/parameters/edit"](parameter: @model)
    Backbone.Validation.bind @

    @$('#parameter-data-type').val @model.get('data_type')
    @$('#parameter-data-type').select2()

    $('#content-header').find('.page-title').text(if @model.isNew() then 'New Parameter' else 'Edit Parameter')

    $('#content-header').find('.page-actions').remove()
    $('#content-header').find('.table-cell').after JST["admin/templates/parameters/back_button"]

    @

  save: (e) ->
    e.preventDefault()
    @model.validate()
    if @model.isValid()
      @model.save {}, success: @saved, error: @displayErrors

  cancel: (e) ->
    e.preventDefault()
    if @model.isNew()
      Augury.parameters.remove @model
    Backbone.history.navigate '/parameters', trigger: true

  saved: ->
    Augury.Flash.success "The parameter has been successfully saved."
)

Use select2 for data_type select

Use select2 for data_type select


class window.UserPopoverContentView extends StatisticsPopoverContentView

  templateHelpers: ->
    heading: @model.get('name')

  onRender: ->
    @statisticsRegion.show new UserStatisticsView model: @model
    @ui.buttonRegion.addClass 'statistics-popover-content-button-hidden'

    @_showFollowButton()

  _showFollowButton: ->
    return if @model.is_current_user()

    @buttonRegion.show new FollowUserButtonView user: @model, mini: true
    @ui.buttonRegion.removeClass 'statistics-popover-content-button-hidden'

UserPopoverContentView.makeTooltip = (view, user, options={}) ->
  Backbone.Factlink.makeTooltipForView view,
      positioning: {align: 'left', side: 'bottom'}
      selector: options.selector || '.js-user-link'
      $offsetParent: options.$offsetParent
      tooltipViewFactory: -> new UserPopoverContentView model: user.clone()

Use cloned model for UserPopoverContentView to not run into trouble with urls for following/unfollowing


{WorkspaceView} = require 'atom'
CodeContext = require '../lib/code-context'

describe 'CodeContext', ->
  beforeEach ->
    atom.workspaceView = new WorkspaceView
    textSource = atom.workspace.getActiveEditor()
    @codeContext = new CodeContext('test.txt', '/tmp/test.txt', null)

  describe 'fileColonLine when lineNumber is not set', ->
    it 'returns the full filepath when fullPath is truthy', ->
      expect(@codeContext.fileColonLine()).toMatch("/tmp/test.txt");
      expect(@codeContext.fileColonLine(true)).toMatch("/tmp/test.txt");

    it 'returns only the filename and line number when fullPath is falsy', ->
      expect(@codeContext.fileColonLine(false)).toMatch("test.txt");

  describe 'fileColonLine when lineNumber is set', ->
    it 'returns the full filepath when fullPath is truthy', ->
      @codeContext.lineNumber = 42
      expect(@codeContext.fileColonLine()).toMatch("/tmp/test.txt");
      expect(@codeContext.fileColonLine(true)).toMatch("/tmp/test.txt");

    it 'returns only the filename and line number when fullPath is falsy', ->
      @codeContext.lineNumber = 42
      expect(@codeContext.fileColonLine(false)).toMatch("test.txt");

  describe 'getCode', ->
    it 'returns undefined if no textSource is available', ->
      expect(@codeContext.getCode()).toBe(undefined)

CodeContext = require '../lib/code-context'

describe 'CodeContext', ->
  beforeEach ->
    @codeContext = new CodeContext('test.txt', '/tmp/test.txt', null)

  describe 'fileColonLine when lineNumber is not set', ->
    it 'returns the full filepath when fullPath is truthy', ->
      expect(@codeContext.fileColonLine()).toMatch("/tmp/test.txt");
      expect(@codeContext.fileColonLine(true)).toMatch("/tmp/test.txt");

    it 'returns only the filename and line number when fullPath is falsy', ->
      expect(@codeContext.fileColonLine(false)).toMatch("test.txt");

  describe 'fileColonLine when lineNumber is set', ->
    it 'returns the full filepath when fullPath is truthy', ->
      @codeContext.lineNumber = 42
      expect(@codeContext.fileColonLine()).toMatch("/tmp/test.txt");
      expect(@codeContext.fileColonLine(true)).toMatch("/tmp/test.txt");

    it 'returns only the filename and line number when fullPath is falsy', ->
      @codeContext.lineNumber = 42
      expect(@codeContext.fileColonLine(false)).toMatch("test.txt");

  describe 'getCode', ->
    it 'returns undefined if no textSource is available', ->
      expect(@codeContext.getCode()).toBe(undefined)

    it 'returns the text from the textSource when available', ->
      # TODO: Test using an actual editor or a selection?
      dummyTextSource = {}
      dummyTextSource.getText = ->
        "print 'hello world!'"
      @codeContext.textSource = dummyTextSource
      code = @codeContext.getCode()

      expect(typeof code).toEqual('string')
      expect(code).toMatch("print 'hello world!'")

Add a new spec for testing getCode()

Add a new spec for testing getCode()


{View} = require 'atom'
MarkerView = require './marker-view'
module.exports =
class MinimapSelectionView extends View
  markers: []
  @content: ->
    @div class: 'minimap-selection'

  initialize: (@minimapView) ->
    @subscribe @minimapView.editorView, "selection:changed", @handleSelection

  attach: ->
    @minimapView.miniUnderlayer.append(this)
    @handleSelection()

  destroy: ->
    @detach()
    @unsubscribe()
    @minimapView = null

  handleSelection: =>
    @removeMarkers()

    {editor} = @minimapView

    return if editor.getSelection().isEmpty()

    for selection in editor.getSelections()
      marker = new MarkerView(editorView: @minimapView, marker: selection.marker)
      @markers.push marker
      @append marker.element

  removeMarkers: ->
    return unless @markers?
    return if @markers.length is 0
    marker.element.remove() for marker in @markers
    marker = null
    @markers = []

{View} = require 'atom'
MarkerView = require './marker-view'
module.exports =
class MinimapSelectionView extends View
  decorations: []
  @content: ->
    @div class: 'minimap-selection'

  initialize: (@minimapView) ->
    @subscribe @minimapView.editorView, "selection:changed", @handleSelection
    @subscribe @minimapView.editorView, "cursor-added", @handleSelection
    @subscribe @minimapView.editorView, "cursor-moved", @handleSelection
    @subscribe @minimapView.editorView, "cursor-removed", @handleSelection

  attach: ->
    @minimapView.miniUnderlayer.append(this)
    @handleSelection()

  destroy: ->
    @detach()
    @unsubscribe()
    @minimapView = null

  handleSelection: =>
    @removeDecorations()

    {editor} = @minimapView

    return if editor.getSelection().isEmpty()

    for selection in editor.getSelections()
      @decorations.push @minimapView.decorateMarker(selection.marker, type: 'highlight-under', scope: '.editor .selection .region')

  removeDecorations: ->
    return if @decorations.length is 0
    decoration.destroy() for decoration in @decorations
    @decorations = []

Implement support for the new minimap decoration API


angular.module('doubtfire.units.states.task-inbox', [
  # 'doubtfire.units.states.task-inbox.task-commenter'
  # 'doubtfire.units.states.task-inbox.task-submission'
])

#
# Teacher child state for units
#
.config(($stateProvider) ->
  $stateProvider.state 'units#tasks', {
    parent: 'units#index'
    url: '/tasks/:taskId'
    views:
      unitIndex:
        templateUrl: "units/states/task-inbox/task-inbox.tpl.html"
        controller: "UnitTaskInboxFeedback"
    data:
      task: "Task Inbox"
      pageTitle: "_Home_"
      roleWhitelist: ['Tutor', 'Convenor', 'Admin']
   }
)

.controller('UnitTaskInboxFeedback', ($scope) ->

)

angular.module('doubtfire.units.states.task-inbox', [
  # 'doubtfire.units.states.task-inbox.task-commenter'
])

#
# Teacher child state for units
#
.config(($stateProvider) ->
  $stateProvider.state 'units#tasks', {
    parent: 'units#index'
    url: '/tasks/:taskId'
    views:
      unitIndex:
        templateUrl: "units/states/task-inbox/task-inbox.tpl.html"
        controller: "UnitTaskInboxFeedback"
    data:
      task: "Task Inbox"
      pageTitle: "_Home_"
      roleWhitelist: ['Tutor', 'Convenor', 'Admin']
   }
)

.controller('UnitTaskInboxFeedback', ($scope, $state, $stateParams) ->
  setTaskIdUrlParm = (taskId) ->
    # Change URL of new task without notify
    $state.go('.', {taskId: taskId}, {notify: false})

  if _.isNumber($stateParams.taskId)
    $scope.selectedTask = _.find($scope.unit.tasks, {id: $stateParams.taskId})

  $scope.$watch 'selectedTask', (newTask) ->
    if newTask?
      setTaskIdUrlParm(newTask.id)
    else
      setTaskIdUrlParm(null)
)

ENHANCE: Allow usage of task inbox task ID URL parameter


angular.module('browser').
factory('Browser', ->
  return class Browser
    @prefixs:  ['webkit', 'Moz', 'ms', 'o', 'khtml']

    @window:   window
    @element:  window.document.documentElement
    @body:     window.document.getElementsByTagName('body')[0]

    @height: ->
      @window.innerHeight   or
      @element.clientHeight or
      @body.clientHeight

    @width: ->
      @window.innerWidth   or
      @element.clientWidth or
      @body.clientWidth

    @haveTransform: ->
      return @testCSSProp('transform')

    @title: (string)->
      return string[0].toUpperCase() + string[1..].toLowerCase()

    @testCSSProp: (name)->
      if @body.style[name]?
        return true

      name = @title(name)

      if @body.style[name]?
        return true

      for prefix in @prefixs
        if @body.style["#{prefix}#{name}"]?
          return true
      return false
)

angular.module('browser').
factory('Browser', ->
  return class Browser
    @prefixs:  ['webkit', 'Moz', 'ms', 'o', 'khtml']

    @window:   window
    @element:  window.document.documentElement
    @body:     window.document.getElementsByTagName('body')[0]

    @_height: ->
      @window.innerHeight   or
      @element.clientHeight or
      @body.clientHeight

    @height: ->
      if window.screen? and window.screen.availHeight?
        return window.screen.availHeight
      else
        return @_height()

    @_width: ->
      @window.innerWidth   or
      @element.clientWidth or
      @body.clientWidth

    @width: ->
      if window.screen? and window.screen.availWidth?
        return window.screen.availWidth
      else
        return @_width()

    @haveTransform: ->
      return @testCSSProp('transform')

    @title: (string)->
      return string[0].toUpperCase() + string[1..].toLowerCase()

    @testCSSProp: (name)->
      if @body.style[name]?
        return true

      name = @title(name)

      if @body.style[name]?
        return true

      for prefix in @prefixs
        if @body.style["#{prefix}#{name}"]?
          return true
      return false
)

Improve width and height detection.

Improve width and height detection.