_ = require('underscore')
config = require('./config')
async = require('async')
PulsarApiClient = require('./pulsar-api-client')

class PulsarApi

  _clientMap = {}

  constructor: ()->
    pulsarApiConfig = config.pulsarApi

    clientDefaultConfig = _.pick(pulsarApiConfig, 'url', 'token')
    @_clientDefault = new PulsarApiClient(clientDefaultConfig.url, clientDefaultConfig.authToken)

    _.each pulsarApiConfig.auxiliary, (clientConfig, key) ->
      clientConfig = _.defaults({}, clientConfig, clientDefaultConfig)
      _clientMap[key] = new PulsarApiClient(clientConfig.url, clientConfig.authToken)

  getClientDefault: () ->
    @_clientDefault

  getClient: (app, env) ->
    name = @_getClientName(app, env)
    if (_clientMap[name])
      _clientMap[name]
    else
      @getClientDefault()

  runJob: (job) ->
    client = @getClient(job.app, job.env)
    client.runJob(job)

  jobs: (callback) ->
    clientList = _.toArray(_clientMap)
    clientList.unshift @_clientDefault

    getClientJobs = (client, callback) ->
      client.jobs(callback)

    async.map clientList, getClientJobs, (results) ->
      concatenator = (all, items) ->
        all.concat items
      jobs = _.reduce(results, concatenator, [])
      callback(jobs)

  _getClientName: (app, env) ->
    name = ''
    name += app if app
    name += '/' + env if env
    return name

module.exports = new PulsarApi()

_ = require('underscore')
config = require('./config')
async = require('async')
PulsarApiClient = require('./pulsar-api-client')

class PulsarApi

  _clientMap = {}

  constructor: ()->
    pulsarApiConfig = config.pulsarApi
    @_clientDefault = new PulsarApiClient(pulsarApiConfig.url, pulsarApiConfig.authToken)
    _.each pulsarApiConfig.auxiliary, (clientConfig, key) ->
      _clientMap[key] = new PulsarApiClient(clientConfig.url, clientConfig.authToken)

  getClientDefault: () ->
    @_clientDefault

  getClient: (app, env) ->
    name = @_getClientName(app, env)
    if (_clientMap[name])
      _clientMap[name]
    else
      @getClientDefault()

  runJob: (job) ->
    client = @getClient(job.app, job.env)
    client.runJob(job)

  jobs: (callback) ->
    clientList = _.toArray(_clientMap)
    clientList.unshift @_clientDefault

    getClientJobs = (client, callback) ->
      client.jobs(callback)

    async.map clientList, getClientJobs, (results) ->
      concatenator = (all, items) ->
        all.concat items
      jobs = _.reduce(results, concatenator, [])
      callback(jobs)

  _getClientName: (app, env) ->
    name = ''
    name += app if app
    name += '/' + env if env
    return name

module.exports = new PulsarApi()

Clarify PulsarApi construction. Remove the default config extending redundancy.


angular.module("grumbles")
  .controller "FormCtrl", ($http, Output) ->
    @submit = ->
      return unless @command

      words = @command.split ' '
      words.length = 3
      path = words.join '/'

      $http.post("/cli/#{path}")
        .success (data, status, headers, config) =>
          Output.add data
          @command = ""

        .error (data, status, headers, config) ->
          Output.add data if angular.isObject(data)

    this

angular.module("grumbles")
  .controller "FormCtrl", ($http, Output) ->
    @commandQueue = []

    @submit = ->
      return unless @command

      @commandQueue = @command.split("&&").map (command, index) ->
        query: command.trim()
        nextIndex:  index + 1

      queryCommand @commandQueue[0]

    queryCommand = (command) =>
      return unless command

      words = command.query.split ' '
      words.length = 3
      path = words.join '/'

      $http.post("/cli/#{path}")
        .success (data, status, headers, config) =>
          Output.add data
          @command = ""

          queryCommand @commandQueue[command.nextIndex]

        .error (data, status, headers, config) =>
          Output.add data if angular.isObject(data)

          @commandQueue = []

    this

Add command queue functionality using `&&`


<%= view_namespace %> ||= {}

class <%= view_namespace %>.IndexView extends Backbone.View
  template: JST["<%= jst 'index' %>"]

  initialize: () ->
    _.bindAll(this, 'addOne', 'addAll', 'render');

    @options.<%= plural_model_name %>.bind('reset', this.addAll);

  addAll: () ->
    @options.<%= plural_model_name %>.each(this.addOne)

  addOne: (<%= singular_model_name %>) ->
    view = new <%= view_namespace %>.<%= singular_name.camelize %>View({model : <%= singular_model_name %>})
    this.$("tbody").append(view.render().el)

  render: ->
    $(this.el).html(this.template(<%= plural_model_name %>: this.options.<%= plural_model_name %>.toJSON() ))
    @addAll()

    return this

<%= view_namespace %> ||= {}

class <%= view_namespace %>.IndexView extends Backbone.View
  template: JST["<%= jst 'index' %>"]

  initialize: () ->
    _.bindAll(this, 'addOne', 'addAll', 'render')

    @options.<%= plural_model_name %>.bind('reset', @addAll)

  addAll: () ->
    @options.<%= plural_model_name %>.each(@addOne)

  addOne: (<%= singular_model_name %>) ->
    view = new <%= view_namespace %>.<%= singular_name.camelize %>View({model : <%= singular_model_name %>})
    @$("tbody").append(view.render().el)

  render: ->
    $(@el).html(@template(<%= plural_model_name %>: @options.<%= plural_model_name %>.toJSON() ))
    @addAll()

    return this

Use proper CoffeeScript in scaffold

Use proper CoffeeScript in scaffold


$ ->
  if $('h1').html() == 'Changes'
    setTimeout(->
      location.reload()
    , 10 * 1000)

$ ->
  if $('h1').html() == 'Changes'
    setTimeout(->
      location.reload()
    , 120 * 1000)

Refresh changes page every 2 minutes.

Refresh changes page every 2 minutes.


_ = require 'underscore'
Range = require 'range'

module.exports =
class Snippet
  name: null
  prefix: null
  body: null
  lineCount: null
  tabStops: null

  constructor: ({@name, @prefix, bodyTree}) ->
    @body = @extractTabStops(bodyTree)

  extractTabStops: (bodyTree) ->
    tabStopsByIndex = {}
    bodyText = []
    [row, column] = [0, 0]

    # recursive helper function; mutates vars above
    extractTabStops = (bodyTree) ->
      for segment in bodyTree
        if segment.index?
          { index, content } = segment
          index = Infinity if index == 0
          start = [row, column]
          extractTabStops(content)
          tabStopsByIndex[index] = new Range(start, [row, column])
        else if _.isString(segment)
          bodyText.push(segment)
          segmentLines = segment.split('\n')
          column += segmentLines.shift().length
          while (nextLine = segmentLines.shift())?
            row += 1
            column = nextLine.length

    extractTabStops(bodyTree)
    @lineCount = row + 1
    @tabStops = []
    for index in _.keys(tabStopsByIndex).sort()
      @tabStops.push tabStopsByIndex[index]

    bodyText.join('')

_ = require 'underscore'
{Range} = require 'telepath'

module.exports =
class Snippet
  name: null
  prefix: null
  body: null
  lineCount: null
  tabStops: null

  constructor: ({@name, @prefix, bodyTree}) ->
    @body = @extractTabStops(bodyTree)

  extractTabStops: (bodyTree) ->
    tabStopsByIndex = {}
    bodyText = []
    [row, column] = [0, 0]

    # recursive helper function; mutates vars above
    extractTabStops = (bodyTree) ->
      for segment in bodyTree
        if segment.index?
          { index, content } = segment
          index = Infinity if index == 0
          start = [row, column]
          extractTabStops(content)
          tabStopsByIndex[index] = new Range(start, [row, column])
        else if _.isString(segment)
          bodyText.push(segment)
          segmentLines = segment.split('\n')
          column += segmentLines.shift().length
          while (nextLine = segmentLines.shift())?
            row += 1
            column = nextLine.length

    extractTabStops(bodyTree)
    @lineCount = row + 1
    @tabStops = []
    for index in _.keys(tabStopsByIndex).sort()
      @tabStops.push tabStopsByIndex[index]

    bodyText.join('')

Remove Point and Range classes now provided by telepath


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

createHashID = ->
  crypto.createHash('sha1').update("#{Math.random()}").digest('hex');

module.exports = (file, callback) ->
  return callback("No `image` file. Make sure the `image` field is being set.") unless file

  p = path.normalize("#{global.image_path}#{createHashID()}_#{file.name}")
  fs.rename file.path, p, (error) ->
    return callback(error) if error
    callback null, path

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

createHashID = ->
  crypto.createHash('sha1').update("#{Math.random()}").digest('hex');

module.exports = (file, callback) ->
  return callback("No `image` file. Make sure the `image` field is being set.") unless file

  _path = path.normalize("#{global.image_path}#{createHashID()}_#{file.name}")
  is = fs.createReadStream file.path
  os = fs.createWriteStream _path

  is.pipe os
  is.on 'end', ->
    fs.unlinkSync file.path
    callback null, _path

Change image copy method to work accross different partitions


class Toolbox extends Backbone.View
  _.extend @prototype, Backbone.Events

  tagName: 'div'
  className: 'toolbox'
  template: require './templates/toolbox'

  events:
    'click .tool-icon button' : 'createTool'
    'click button[name="remove-tools"]' : 'removeTools'

  render: =>
    tools = _.keys Ubret
    tools = _.map tools, (tool) ->
      tool = {name: tool}
    @$el.html @template {tools: tools}
    @

  createTool: (e) =>
    toolType = $(e.currentTarget).attr('name')
    @trigger 'create', toolType

  removeTools: (e) =>
    @trigger "remove-tools"

module.exports = Toolbox

class Toolbox extends Backbone.View
  _.extend @prototype, Backbone.Events

  tagName: 'div'
  className: 'toolbox'
  template: require './templates/toolbox'

  events:
    'click .tool-icon button' : 'createTool'
    'click button[name="remove-tools"]' : 'removeTools'

  render: =>
    tools = _.keys Ubret
    toolNames = new Array
    toolNames.push { name: tool } for tool in tools when tool isnt 'BaseTool'
    @$el.html @template {tools: toolNames}
    @

  createTool: (e) =>
    toolType = $(e.currentTarget).attr('name')
    @trigger 'create', toolType

  removeTools: (e) =>
    @trigger "remove-tools"

module.exports = Toolbox

Fix to remove BaseTool from Toolbox

Fix to remove BaseTool from Toolbox


# Keybindings require three things to be fully defined: A selector that is
# matched against the focused element, the keystroke and the command to
# execute.
#
# Below is a basic keybinding which registers on all platforms by applying to
# the root workspace element.

# For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps

# TODO
# '.workspace':
#   'ctrl-alt-o': 'tree-ignore:toggle'

# Keybindings require three things to be fully defined: A selector that is
# matched against the focused element, the keystroke and the command to
# execute.
#
# Below is a basic keybinding which registers on all platforms by applying to
# the root workspace element.

# For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps

'.workspace':
  'shift-cmd-h': 'tree-ignore:toggle'

Add keybinding for toggle command

Add keybinding for toggle command


Trix.registerElement "trix-toolbar",
  defaultCSS: """
    %t {
      white-space: nowrap;
    }

    %t [data-trix-dialog] {
      display: none;
    }

    %t [data-trix-dialog][data-trix-active] {
      display: block;
    }

    %t [data-trix-dialog] [data-trix-validate]:invalid {
      background-color: #ffdddd;
    }
  """

  connect: ->
    if @innerHTML is ""
      @innerHTML = Trix.config.toolbar.getDefaultHTML()

Trix.registerElement "trix-toolbar",
  defaultCSS: """
    %t {
      white-space: nowrap;
    }

    %t [data-trix-dialog] {
      display: none;
    }

    %t [data-trix-dialog][data-trix-active] {
      display: block;
    }

    %t [data-trix-dialog] [data-trix-validate]:invalid {
      background-color: #ffdddd;
    }
  """

  # Element lifecycle

  # `attachedCallback` (defined as `connect` here) doesn't run in Firefox when
  # an element is dynamically inserted while the document is still loading. Most
  # likely due to a bug in the v0 polyfill. For this element, the result is a
  # blank toolbar. Workaround: Render when created too in `createdCallback`.
  createdCallback: ->
    @render()

  connect: ->
    @render()

  # Private

  render: ->
    @innerHTML ||= Trix.config.toolbar.getDefaultHTML()

Fix blank <trix-toolbar> in Firefox

Fix blank <trix-toolbar> in Firefox


Review.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')
  fix: DS.hasMany('commit', {inverse: 'fix'})
  fixed: DS.hasMany('commit', {inverse: 'fixed'})

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

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

  isPassed: (()->
    @get('state') == 'passed'
  ).property('state')

  isFixed: (()->
    @get('state') == 'fixed'
  ).property('state')

  isAccepted: (()->
    @get('state') == 'accepted'
  ).property('state')

  belongsToMe: (()->
    Review.User.find('me').get('person') is @get('author')
  ).property()

Review.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')
  fix: DS.hasMany('commit', {inverse: 'fix'})
  fixed: DS.hasMany('commit', {inverse: 'fixed'})

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

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

  isPassed: (()->
    @get('state') == 'passed'
  ).property('state')

  isFixed: (()->
    @get('state') == 'fixed'
  ).property('state')

  isAccepted: (()->
    @get('state') == 'accepted'
  ).property('state')

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

Use new syntax for finding records

Use new syntax for finding records


EditableForm = $.fn.editableform.Constructor
EditableForm.prototype.saveWithUrlHook = (value) ->
  url = @options.url
  @options.url = (params) =>
    params[@options.model] ||= {}
    params[@options.model][params.name] = value
    ajax_opts =
      url: url
      data: params
      type: 'PATCH'
      dataType: 'json'
    delete params.name
    delete params.value
    delete params.pk
    $.ajax $.extend(ajax_opts, @options.ajaxOptions)
  @saveWithoutUrlHook(value)
EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook

$(document).on 'admin:init', (e) ->
  $('.editable').editable
    onblur: 'submit'
    placement: 'bottom'
    emptytext: I18n.lookup('admin.js.empty') || 'Empty'
    datetimepicker:
      format: "dd.mm.yyyy hh:ii"
      autoclose: true
      todayBtn: true
      language: I18n.locale

EditableForm = $.fn.editableform.Constructor
EditableForm.prototype.saveWithUrlHook = (value) ->
  url = @options.url
  @options.url = (params) =>
    params[@options.model] ||= {}
    params[@options.model][params.name] = value
    params._method = 'PATCH'
    ajax_opts =
      url: url
      data: params
      type: 'POST'
      dataType: 'json'
    delete params.name
    delete params.value
    delete params.pk
    $.ajax $.extend(ajax_opts, @options.ajaxOptions)
  @saveWithoutUrlHook(value)
EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook

$(document).on 'admin:init', (e) ->
  $('.editable').editable
    onblur: 'submit'
    placement: 'bottom'
    emptytext: I18n.lookup('admin.js.empty') || 'Empty'
    datetimepicker:
      format: "dd.mm.yyyy hh:ii"
      autoclose: true
      todayBtn: true
      language: I18n.locale
    ajaxOptions:
      xhrFields:
        withCredentials: true
      headers:
        Accept: 'application/json'

Add CORS support for in place edit

Add CORS support for in place edit


tv4 = require 'tv4'
chai = require 'chai' if not chai
path = require 'path'
fs = require 'fs'

schemaPath = '../schema'
getSchema = (name) ->
  filepath = path.join __dirname, schemaPath, name
  loadSchema filepath

loadSchema = (filepath) ->
  content = fs.readFileSync filepath, { encoding: 'utf-8' }
  return JSON.parse content

describe 'Schema meta validation', ->
  schemas = fs.readdirSync path.join __dirname, schemaPath
  schemas = schemas.filter (s) -> (s.indexOf('.json') == 0)
  before: ->
    metaSchema = loadSchema path.join __dirname, 'json-schema.json'
    tv4.addSchema 'http://json-schema.org/draft-04/schema', metaSchema
  after: ->
    tv4.reset()
  schemas.forEach (schemaFile) ->
    schema = getSchema schemaFile
    tv4.addSchema schema.id, schema
    describe "#{schemaFile} (#{schema.title or schema.description})", ->
      it 'should validate against JSON meta schema', ->
        result = tv4.validateResult schema, 'http://json-schema.org/draft-04/schema'
        chai.expect(result.valid).to.equal true

tv4 = require 'tv4'
chai = require 'chai' if not chai
path = require 'path'
fs = require 'fs'

schemaPath = '../schema'
getSchema = (name) ->
  filepath = path.join __dirname, schemaPath, name
  loadSchema filepath

loadSchema = (filepath) ->
  content = fs.readFileSync filepath, { encoding: 'utf-8' }
  return JSON.parse content

describe 'Schema meta validation', ->
  schemas = fs.readdirSync path.join __dirname, schemaPath
  schemas = schemas.filter (s) -> (s.indexOf('.json') != -1)

  before: ->
    metaSchema = loadSchema path.join __dirname, 'json-schema.json'
    tv4.addSchema 'http://json-schema.org/draft-04/schema', metaSchema
  after: ->
    tv4.reset()
  schemas.forEach (schemaFile) ->
    schema = getSchema schemaFile
    tv4.addSchema schema.id, schema
    describe "#{schemaFile} (#{schema.title or schema.description})", ->
      it 'should validate against JSON meta schema', ->
        result = tv4.validateResult schema, 'http://json-schema.org/draft-04/schema'
        chai.expect(result.valid).to.equal true

Bring back schema meta validation

Test: Bring back schema meta validation


"*":
  core:
    disabledPackages: [
      "spell-check"
    ]
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "atom-light-syntax"
    ]
  editor:
    fontFamily: "Menlo"
    invisibles: {}
    softWrap: true
  "exception-reporting":
    userId: "0f9c4bca-c1e4-c885-1374-c2dd26290ade"
  "spell-check": {}
  tabs:
    addNewTabsAtEnd: true
  welcome:
    showOnStartup: false

"*":
  core:
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "atom-light-syntax"
    ]
  editor:
    fontFamily: "Menlo"
    invisibles: {}
    softWrap: true
  "exception-reporting":
    userId: "0f9c4bca-c1e4-c885-1374-c2dd26290ade"
  "spell-check": {}
  tabs:
    addNewTabsAtEnd: true
  welcome:
    showOnStartup: false

Enable the spell checker for Atom

Enable the spell checker for Atom


angular.module("doubtfire.sessions.states.sign-out", [])

#
# State for sign out
#
.config(($stateProvider) ->
  signOutStateData =
    url: "/sign_out"
    views:
      main:
        controller: "SignOutCtrl"
        templateUrl: "sessions/states/sign-out/sign-out.tpl.html"
    data:
      pageTitle: "_Sign Out_"
  $stateProvider.state "sign_out", signOutStateData
)
.controller("SignOutCtrl", ($state, $timeout, auth, api, currentUser) ->
  if auth.signOut api + "/auth/" + currentUser.authenticationToken + ".json"
    $timeout (-> $state.go "sign_in"), 750
  return this
)

angular.module("doubtfire.sessions.states.sign-out", [])

#
# State for sign out
#
.config(($stateProvider) ->
  signOutStateData =
    url: "/sign_out"
    views:
      main:
        controller: "SignOutCtrl"
        templateUrl: "sessions/states/sign-out/sign-out.tpl.html"
    data:
      pageTitle: "_Sign Out_"
  $stateProvider.state "sign_out", signOutStateData
)
.controller("SignOutCtrl", ($state, $timeout, $http, auth, api, currentUser) ->
  if auth.signOut api + "/auth/" + currentUser.authenticationToken + ".json"
    $http.get("#{api}/auth/signout_url").then ((response) ->
      signoutURL = response.data.auth_signout_url || false
      if signoutURL
        window.location.assign(signoutURL)
      else
        $timeout (-> $state.go "sign_in"), 750
    )
  return this
)

Add AAF signout redirect URL to config

ENHANCE: Add AAF signout redirect URL to config


#
# Main API Server
#

restify = require 'restify'
chalk = require 'chalk'

newrelic = require './helpers/newrelic'
exceptionHandler = require './helpers/exceptionhandler'
logger = require './helpers/logger'

app = module.exports = restify.createServer()
listenHost = process.env.HOST or process.env.VCAP_APP_HOST or "0.0.0.0"
listenPort = process.env.PORT or process.env.VCAP_APP_PORT or 8080

#
# Applying Restify built-in plugins and other helpers
#

# Restify workaround for cURL
app.pre restify.pre.userAgentConnection()
# Restify workaround for handling trailing slashes
app.pre restify.pre.sanitizePath()
app.use restify.queryParser()

# Prevents leaking internal errors through the API
app.on 'uncaughtException', exceptionHandler

#
# Launching server
#

server = app.listen listenPort, listenHost, ->
  address = server.address().address
  port = server.address().port
  logger.info \
    chalk.green "Server listening on http://#{address}:#{port} " +
    chalk.grey "(PID: #{process.pid})"

module.exports.app = app
routes = require './routes'

#
# Main API Server
#
# Newrelic should be on the top
newrelic = require './helpers/newrelic'

restify = require 'restify'
chalk = require 'chalk'

exceptionHandler = require './helpers/exceptionhandler'
logger = require './helpers/logger'

app = module.exports = restify.createServer()
listenHost = process.env.HOST or process.env.VCAP_APP_HOST or "0.0.0.0"
listenPort = process.env.PORT or process.env.VCAP_APP_PORT or 8080

#
# Applying Restify built-in plugins and other helpers
#

# Restify workaround for cURL
app.pre restify.pre.userAgentConnection()
# Restify workaround for handling trailing slashes
app.pre restify.pre.sanitizePath()
app.use restify.queryParser()

# Prevents leaking internal errors through the API
app.on 'uncaughtException', exceptionHandler

#
# Launching server
#

server = app.listen listenPort, listenHost, ->
  address = server.address().address
  port = server.address().port
  logger.info \
    chalk.green "Server listening on http://#{address}:#{port} " +
    chalk.grey "(PID: #{process.pid})"

module.exports.app = app
routes = require './routes'

Move Newrelic to the top

Move Newrelic to the top


Reflux = require 'reflux'
Actions = require '../actions/database'
Common = require '../common/application'
Got = require 'got'

cache =
  loading: false
  connections: []

Store = Reflux.createStore(
  init: ->
    @listenToMany(Actions)

  onCreateConnection: (data) ->
    cache.loading = true
    @trigger()

    Got "http://localhost:#{process.env.ZENIT_SERVICE}/connect/#{data.host}/#{data.user}/#{data.password}/#{data.database}", (err, body) =>
      cache.loading = false

      if err or body is 'fail'
        Common.beep()
      else
        cache.connections.push
          name: 'Example'
          status: true
          data: data

      @trigger()

  ###
  query: (sql) -> new Promise((resolve, reject) ->
    got "http://localhost:#{process.env.ZENIT_SERVICE}/query/#{encodeURIComponent(sql)}", (err, body) ->
      return reject(err) if err

      try
        resolve(JSON.parse(body))
      catch err
        reject(err)
  )
  ###

  getStore: ->
    cache
)

module.exports = Store

Reflux = require 'reflux'
Actions = require '../actions/database'
Common = require '../common/application'
Got = require 'got'

cache =
  loading: false
  connections: []

Store = Reflux.createStore(
  init: ->
    @listenToMany(Actions)

  onCreateConnection: (data) ->
    cache.loading = true
    @trigger()

    Got "http://localhost:#{process.env.ZENIT_SERVICE}/connect/#{data.host}/#{data.user}/#{data.password}/#{data.database}", (err, body) =>
      cache.loading = false

      if err or body is 'fail'
        Common.beep()
      else
        cache.connections.unshift
          name: 'Example'
          status: true
          data: data

      @trigger()

  ###
  query: (sql) -> new Promise((resolve, reject) ->
    got "http://localhost:#{process.env.ZENIT_SERVICE}/query/#{encodeURIComponent(sql)}", (err, body) ->
      return reject(err) if err

      try
        resolve(JSON.parse(body))
      catch err
        reject(err)
  )
  ###

  getStore: ->
    cache
)

module.exports = Store

Use unshift instead of push for creating connections


module.exports =
class InputComponent
  constructor: (@presenter) ->
    @domNode = document.createElement('input')
    @domNode.classList.add('hidden-input')
    @domNode.setAttribute('data-react-skip-selection-restoration', true)
    @domNode.style['-webkit-transform'] = 'translateZ(0)'
    @domNode.addEventListener 'paste', (event) => event.preventDefault()
    @updateSync()

  updateSync: ->
    @oldState ?= {}
    newState = @presenter.state.hiddenInput

    if newState.top isnt @oldState.top
      @domNode.style.top = newState.top + 'px'
      @oldState.top = newState.top

    if newState.left isnt @oldState.left
      @domNode.style.left = newState.left + 'px'
      @oldState.left = newState.left

    if newState.width isnt @oldState.width
      @domNode.style.width = newState.width + 'px'
      @oldState.width = newState.width

    if newState.height isnt @oldState.height
      @domNode.style.height = newState.height + 'px'
      @oldState.height = newState.height

module.exports =
class InputComponent
  constructor: (@presenter) ->
    @domNode = document.createElement('input')
    @domNode.classList.add('hidden-input')
    @domNode.setAttribute('data-react-skip-selection-restoration', true)
    @domNode.style['-webkit-transform'] = 'translateZ(0)'
    @domNode.addEventListener 'paste', (event) -> event.preventDefault()
    @updateSync()

  updateSync: ->
    @oldState ?= {}
    newState = @presenter.state.hiddenInput

    if newState.top isnt @oldState.top
      @domNode.style.top = newState.top + 'px'
      @oldState.top = newState.top

    if newState.left isnt @oldState.left
      @domNode.style.left = newState.left + 'px'
      @oldState.left = newState.left

    if newState.width isnt @oldState.width
      @domNode.style.width = newState.width + 'px'
      @oldState.width = newState.width

    if newState.height isnt @oldState.height
      @domNode.style.height = newState.height + 'px'
      @oldState.height = newState.height

Fix a lint warning reported by `./script/grunt lint`.

Now it runs without reporting any issues.


setTimeout ->
  ok =
    loaded: true
    interactive: !(document.documentMode < 11)
    complete: true

  if ok[document.readyState]
    FactlinkJailRoot.host_ready_promise.resolve()
  else
    document.addEventListener('DOMContentLoaded', -> FactlinkJailRoot.host_ready_promise.resolve())

  if 'complete' == document.readyState
    FactlinkJailRoot.host_loaded_promise.resolve()
  else
    window.addEventListener('load', ->
      FactlinkJailRoot.host_ready_promise.resolve()
      #IE pre 11's readyStates are weird; to ensure we can't miss anything,
      #trigger ready at the latest on load

      FactlinkJailRoot.host_loaded_promise.resolve()
    )
, 0

setTimeout ->
  isDOMContentLoaded =
    loaded: true
    interactive: !(document.documentMode < 11)
    complete: true

  if isDOMContentLoaded[document.readyState]
    FactlinkJailRoot.host_ready_promise.resolve()
  else
    document.addEventListener('DOMContentLoaded', -> FactlinkJailRoot.host_ready_promise.resolve())

  if 'complete' == document.readyState
    FactlinkJailRoot.host_loaded_promise.resolve()
  else
    window.addEventListener('load', ->
      FactlinkJailRoot.host_ready_promise.resolve()
      #IE pre 11's readyStates are weird; to ensure we can't miss anything,
      #trigger ready at the latest on load

      FactlinkJailRoot.host_loaded_promise.resolve()
    )
, 0

Use more descriptive variable name

Use more descriptive variable name


RouteHandler = require '../../core/RouteHandler'

###
Index/frontpage/home

@author Torstein Thune
@copyright 2015 Microbrew.it
###
class Handler extends RouteHandler

	getRoute: () ->
		super { path: '/', method: 'GET' }

	render: (err, res, body, user) ->
		return @renderer.page
			title: "Home of homebrewers"
			navigationState: 'home'
			user: user
			html: @renderer.render
				template: "public/templates/home.jade"
				data:
					user: user
					results: body.beers

	onRequest: (request, reply) ->
		@api.beers.get { limit: 10 }, (err, res, body) =>
			reply @render(err, res, body, request.user)
		, request.token

module.exports = Handler

RouteHandler = require '../../core/RouteHandler'

###
Index/frontpage/home

@author Torstein Thune
@copyright 2015 Microbrew.it
###
class Handler extends RouteHandler

	getRoute: () ->
		super { path: '/', method: 'GET' }

	render: (err, res, body, user) ->
		return @renderer.page
			title: "Home of homebrewers"
			navigationState: 'home'
			user: user
			html: @renderer.render
				template: "public/templates/home.jade"
				data:
					user: user
					results: body.beers

	onRequest: (request, reply) ->
		@api.beers.get { partial: 'last', limit: 10 }, (err, res, body) =>
			reply @render(err, res, body, request.user)
		, request.token

module.exports = Handler

Change index to display latest recipes

Change index to display latest recipes


TentStatus.Routers.profile = new class ProfileRouter extends Marbles.Router
  routes: {
    "profile" : "currentProfile"
    ":entity/profile" : "profile"
  }

  actions_titles: {
    "currentProfile" : "Profile"
    "profile" : "Profile"
  }

  currentProfile: (params) =>
    if TentStatus.Helpers.isAppSubdomain()
      return @navigate('/global', {trigger:true, replace: true})

    new Marbles.Views.Profile entity: TentStatus.config.domain_entity.toString()
    TentStatus.setPageTitle page: @actions_titles.currentProfile

  profile: (params) =>
    if TentStatus.Helpers.isAppSubdomain()
      return @navigate('/global', {trigger:true, replace: true})

    new Marbles.Views.Profile entity: params.entity

    title = @actions_titles.profile
    title = "#{TentStatus.Helpers.formatUrlWithPath(params.entity)} - #{title}" if params.entity
    TentStatus.setPageTitle page: title

TentStatus.Routers.profile = new class ProfileRouter extends Marbles.Router
  routes: {
    "profile" : "currentProfile"
    ":entity/profile" : "profile"
  }

  actions_titles: {
    "currentProfile" : "Profile"
    "profile" : "Profile"
  }

  _initAuthorInfoView: (options = {}) =>
    new Marbles.Views.AuthorInfo _.extend options,
      el: document.getElementById('author-info')

  currentProfile: (params) =>
    if TentStatus.Helpers.isAppSubdomain()
      return @navigate('/global', {trigger:true, replace: true})

    new Marbles.Views.Profile entity: TentStatus.config.domain_entity.toString()
    @_initAuthorInfoView()
    TentStatus.setPageTitle page: @actions_titles.currentProfile

  profile: (params) =>
    if TentStatus.Helpers.isAppSubdomain()
      return @navigate('/global', {trigger:true, replace: true})

    new Marbles.Views.Profile entity: params.entity
    @_initAuthorInfoView()

    title = @actions_titles.profile
    title = "#{TentStatus.Helpers.formatUrlWithPath(params.entity)} - #{title}" if params.entity
    TentStatus.setPageTitle page: title

Add author info to profile feeds

Add author info to profile feeds


{ Util: { parseValue, bytesToNum }, convert: { bytesToHex, hexToBytes } } = require 'bitcoinjs-lib'

# Send transaction to Bitcoin network using blockchain.info's pushtx
tx_broadcast = (tx, cb) ->
  tx = bytesToHex tx.serialize()
  ($.post 'https://blockchain.info/pushtx?cors=true', { tx })
    .fail((xhr, status, err) -> cb "Error from blockchain.info pushtx: #{ xhr.responseText or err }")
    .done((data) -> cb null, data)

# Load unspent inputs (from blockchain.info)
load_unspent = (address, cb) ->
  xhr = $.get "http://blockchain.info/unspent?active=#{address}&cors=true"
  xhr.done (res) ->
    if res.unspent_outputs
      unspent = for { tx_hash, tx_output_n, value, script, confirmations } in res.unspent_outputs when confirmations > 0
        hash: tx_hash
        index: tx_output_n
        value: value
        script: script
      cb null, unspent
    else cb new Error 'Missing unspent outputs in blockchain.info response'
  xhr.fail (xhr, status, err) ->
    # This isn't actually an error - just send it as an empty array of inputs
    if xhr.status is 500 and xhr.responseText is 'No free outputs to spend'
      cb null, []
    else cb new Error "Cannot load data from blockchain.info: #{ xhr.responseText or err }"

module.exports = { tx_broadcast, load_unspent }

{ Util: { parseValue, bytesToNum }, convert: { bytesToHex, hexToBytes } } = require 'bitcoinjs-lib'

# Send transaction to Bitcoin network using blockchain.info's pushtx
tx_broadcast = (tx, cb) ->
  tx = bytesToHex tx.serialize()
  ($.post 'https://blockchain.info/pushtx?cors=true', { tx })
    .fail((xhr, status, err) -> cb "Error from blockchain.info pushtx: #{ xhr.responseText or err }")
    .done((data) -> cb null, data)

# Load unspent inputs (from blockchain.info)
load_unspent = (address, cb) ->
  xhr = $.get "https://blockchain.info/unspent?active=#{address}&cors=true"
  xhr.done (res) ->
    if res.unspent_outputs
      unspent = for { tx_hash, tx_output_n, value, script, confirmations } in res.unspent_outputs when confirmations > 0
        hash: tx_hash
        index: tx_output_n
        value: value
        script: script
      cb null, unspent
    else cb new Error 'Missing unspent outputs in blockchain.info response'
  xhr.fail (xhr, status, err) ->
    # This isn't actually an error - just send it as an empty array of inputs
    if xhr.status is 500 and xhr.responseText is 'No free outputs to spend'
      cb null, []
    else cb new Error "Cannot load data from blockchain.info: #{ xhr.responseText or err }"

module.exports = { tx_broadcast, load_unspent }

Switch to blockchain.info over SSL

Switch to blockchain.info over SSL


import isFunction          from 'es-is/function'
import isGeneratorFunction from 'es-is/generator-function'

import log   from '../log'
import tasks from '../tasks'

import invokeAsync     from './async'
import invokeGenerator from './generator'
import invokeSync      from './sync'
import serial          from './serial'

invoked = {}

# Invoke delegates to one of the above
invoke = (name, opts, cb) ->
  log.debug 'invoke', name, opts

  # Prevent recursive calls
  return if invoked[name]
  invoked[name] = true

  # Grab task action, any deps and parsed options
  {action, deps} = tasks[name]

  done = (err) ->
    invoked = {}
    (cb err) if isFunction cb

  invokeAction = (err) ->
    return done err if err?

    # Is a generator task
    if isGeneratorFunction action
      return invokeGenerator name, action, opts, done

    # Two arguments, action expects callback
    if action.length == 2
      return invokeAsync name, action, opts, done

    # Single argument, detected callback
    if /^function \((callback|cb|done|next)\)/.test action
      return invokeAsync name, action, null, done

    # 0 or 1 argument action, no callback detected
    invokeSync name, action, opts, done

  # Process deps first if any
  return serial deps, opts, invokeAction

  invokeAction()

export default invoke

import isFunction          from 'es-is/function'
import isGeneratorFunction from 'es-is/generator-function'

import log   from '../log'
import tasks from '../tasks'

import invokeAsync     from './async'
import invokeGenerator from './generator'
import invokeSync      from './sync'
import serial          from './serial'

invoked = {}

# Invoke delegates to one of the above
invoke = (name, opts, cb) ->
  log.debug 'invoke', name, opts

  # Prevent recursive calls
  return if invoked[name]
  invoked[name] = true

  unless (task = tasks[name])?
    throw new Error 'No such task: #{task}'

  # Grab task action, any deps and parsed options
  {action, deps} = tasks[name]

  done = (err) ->
    invoked = {}
    (cb err) if isFunction cb

  invokeAction = (err) ->
    return done err if err?

    # Is a generator task
    if isGeneratorFunction action
      return invokeGenerator name, action, opts, done

    # Two arguments, action expects callback
    if action.length == 2
      return invokeAsync name, action, opts, done

    # Single argument, detected callback
    if /^function \((callback|cb|done|next)\)/.test action
      return invokeAsync name, action, null, done

    # 0 or 1 argument action, no callback detected
    invokeSync name, action, opts, done

  # Process deps first if any
  return serial deps, opts, invokeAction

  invokeAction()

export default invoke

Throw warning on missing task

Throw warning on missing task


{View} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'

module.exports =
class WrapGuide extends View
  @activate: (rootView, state) ->
    rootView.eachEditor (editor) =>
      @appendToEditorPane(rootView, editor) if editor.attached

  @appendToEditorPane: (rootView, editor) ->
    if underlayer = editor.pane()?.find('.underlayer')
      underlayer.append(new WrapGuide(rootView, editor))

  @content: ->
    @div class: 'wrap-guide'

  defaultColumn: 80

  initialize: (@rootView, @editor) =>
    @observeConfig 'editor.fontSize', => @updateGuide()
    @subscribe @editor, 'editor:path-changed', => @updateGuide()
    @subscribe @editor, 'editor:min-width-changed', => @updateGuide()
    @subscribe $(window), 'resize', => @updateGuide()

  getGuideColumn: (path) ->
    customColumns = config.get('wrapGuide.columns')
    return @defaultColumn unless _.isArray(customColumns)
    for customColumn in customColumns
      continue unless _.isObject(customColumn)
      regex = customColumn['pattern']
      continue unless regex
      return parseInt(customColumn['column']) if new RegExp(regex).test(path)
    @defaultColumn

  updateGuide: ->
    column = @getGuideColumn(@editor.getPath(), @defaultColumn)
    if column > 0
      columnWidth = @editor.charWidth * column
      if columnWidth < @editor.layerMinWidth or columnWidth < @editor.width()
        @css('left', "#{columnWidth}px").show()
      else
        @hide()
    else
      @hide()

{View} = require 'space-pen'
$ = require 'jquery'
_ = require 'underscore'

module.exports =
class WrapGuide extends View
  @activate: (rootView, state) ->
    rootView.eachEditor (editor) =>
      @appendToEditorPane(rootView, editor) if editor.attached

  @appendToEditorPane: (rootView, editor) ->
    if underlayer = editor.pane()?.find('.underlayer')
      underlayer.append(new WrapGuide(rootView, editor))

  @content: ->
    @div class: 'wrap-guide'

  defaultColumn: 80

  initialize: (@rootView, @editor) =>
    @observeConfig 'editor.fontSize', => @updateGuide()
    @subscribe @editor, 'editor:path-changed', => @updateGuide()
    @subscribe @editor, 'editor:min-width-changed', => @updateGuide()
    @subscribe $(window), 'resize', => @updateGuide()

  getGuideColumn: (path) ->
    customColumns = config.get('wrapGuide.columns')
    return @defaultColumn unless _.isArray(customColumns)
    for customColumn in customColumns
      continue unless _.isObject(customColumn)
      pattern = customColumn['pattern']
      continue unless pattern
      return parseInt(customColumn['column']) if new RegExp(pattern).test(path)
    @defaultColumn

  updateGuide: ->
    column = @getGuideColumn(@editor.getPath(), @defaultColumn)
    if column > 0
      columnWidth = @editor.charWidth * column
      if columnWidth < @editor.layerMinWidth or columnWidth < @editor.width()
        @css('left', "#{columnWidth}px").show()
      else
        @hide()
    else
      @hide()

Rename variable to pattern to shadow config key path


Repository  = require('../models/repository').Repository
Build       = require('../models/build').Build
Resque      = require('../config/resque').Connection

exports.findOrCreateRepository = (desired_repository, callback) ->
  query =
    name:      desired_repository.name
    ownerName: desired_repository.ownername

  Repository.findOne query, (err, repository) ->
    if err
      throw err

    if !repository
      repository = new Repository desired_repository
    else
      repository.url = desired_repository.url

    repository.save (err) ->
      if err
        throw err

      console.log 'Found/created repository for ' + repository.url

      callback err, repository

exports.triggerRepositoryBuild = (repository, branch, callback) ->
  build = new Build
    branch: branch

  repository.builds.push build

  repository.save (err) ->
    if err
      throw err

    console.log 'Scheduling build for ' + repository.url + '/' + branch

    Resque.enqueue 'builder', 'build', [
      buildId: build.id,
      repositoryId: repository.id
    ]

    callback()

Repository  = require('../models/repository').Repository
Build       = require('../models/build').Build
Resque      = require('../config/resque').Connection

exports.findOrCreateRepository = (desired_repository, callback) ->
  query =
    name:      desired_repository.name
    ownerName: desired_repository.ownerName

  Repository.findOne query, (err, repository) ->
    if err
      throw err

    if !repository
      repository = new Repository desired_repository
    else
      repository.url = desired_repository.url

    repository.save (err) ->
      if err
        throw err

      console.log 'Found/created repository for ' + repository.url

      callback err, repository

exports.triggerRepositoryBuild = (repository, branch, callback) ->
  build = new Build
    branch: branch

  repository.builds.push build

  repository.save (err) ->
    if err
      throw err

    console.log 'Scheduling build for ' + repository.url + '/' + branch

    Resque.enqueue 'builder', 'build', [
      buildId: build.id,
      repositoryId: repository.id
    ]

    callback()

Fix typo in the query string.

Fix typo in the query string.


gpio = require('gpio')
Events = require('events').EventEmitter
Cylon = require('cylon-raspi')

module.exports = class Buzzer extends Events
  interval: 200
  buzzes: 0
  lastOpened: new Date()
  isOpen: false
  defaultTime: 5
  maxTime: 15000
  constructor: ( pin )->
    @pinNum = pin
    @servo = null
    Cylon.robot
      connection:
        name: 'raspi', adaptor: 'raspi'
      device:
        device: name: 'servo', driver: 'servo', pin: 3,
      work: ( servo ) =>
        @servo = servo

  open: ( length )->
    if !@servo
      @notInitialized()
      return

    console.log('Trying to open')
    time = Number(length)
    if isNaN(time)
      time = @defaultTime
    if !time
      time = @defaultTime

    time *= 1000

    if time > @maxTime
      time = @maxTime

    if !@isOpen
      console.log('Opening for '+time)
      @isOpen = true
      @lastOpened = new Date()
      @buzzes++
      @set( 1 )
      setTimeout( =>
        @close()
      , time )

  close: ->
    if !@servo
      @notInitialized()
      return
    console.log('Closing')
    @set( 0, =>
      @isOpen = false
    )

  notInitialized: ->
    console.log('Servo not initialized')

  getLastOpened: ->
    @lastOpened.getTime()

  getBuzzCount: ->
    @buzzes

gpio = require('gpio')
Events = require('events').EventEmitter
Cylon = require('cylon')

module.exports = class Buzzer extends Events
  interval: 200
  buzzes: 0
  lastOpened: new Date()
  isOpen: false
  defaultTime: 5
  maxTime: 15000
  constructor: ( pin )->
    @pinNum = pin
    @servo = null
    Cylon.robot
      connection:
        name: 'raspi', adaptor: 'raspi'
      device:
        device: name: 'servo', driver: 'servo', pin: 3,
      work: ( servo ) =>
        @servo = servo

  open: ( length )->
    if !@servo
      @notInitialized()
      return

    console.log('Trying to open')
    time = Number(length)
    if isNaN(time)
      time = @defaultTime
    if !time
      time = @defaultTime

    time *= 1000

    if time > @maxTime
      time = @maxTime

    if !@isOpen
      console.log('Opening for '+time)
      @isOpen = true
      @lastOpened = new Date()
      @buzzes++
      @set( 1 )
      setTimeout( =>
        @close()
      , time )

  close: ->
    if !@servo
      @notInitialized()
      return
    console.log('Closing')
    @set( 0, =>
      @isOpen = false
    )

  notInitialized: ->
    console.log('Servo not initialized')

  getLastOpened: ->
    @lastOpened.getTime()

  getBuzzCount: ->
    @buzzes

Switch back to regular cylon

Switch back to regular cylon


assert = require 'assert'

Helper = require('hubot-test-helper')
helper = new Helper('../scripts/poo-tracker.coffee')

describe 'poo-tracker', ->
  room = null

  beforeEach ->
    room = helper.createRoom()
    room.user.say 'shithead', '@hubot hello, this is dog'

  it 'compiled if it got this fucking far', ->
    assert room.messages.length > 0

assert = require 'assert'

Helper = require('hubot-test-helper')
process.env.POO_REDIS_URL = "redis://h:pass@localhost:6379/0"
helper = new Helper('../scripts/poo-tracker.coffee')

describe 'poo-tracker', ->
  room = null

  beforeEach ->
    room = helper.createRoom()
    room.user.say 'shithead', '@hubot hello, this is dog'

  it 'compiled if it got this fucking far', ->
    assert room.messages.length > 0

Set env var before loading script

Set env var before loading script


ModalView = require 'views/core/ModalView'
template = require 'templates/editor/campaign/save-campaign-modal'
DeltaView = require 'views/editor/DeltaView'

module.exports = class SaveCampaignModal extends ModalView
  id: 'save-campaign-modal'
  template: template
  plain: true

  events:
    'click #save-button': 'onClickSaveButton'

  constructor: (options, @modelsToSave) ->
    super(options)

  afterRender: ->
    @$el.find('.delta-view').each((i, el) =>
      $el = $(el)
      model = @modelsToSave.find( id: $el.data('model-id'))
      deltaView = new DeltaView({model: model})
      @insertSubView(deltaView, $el)
    )
    super()

  onClickSaveButton: ->
    @showLoading()
    @reverseLevelsBeforeSave()
    modelsBeingSaved = (model.patch() for model in @modelsToSave.models)
    $.when(_.compact(modelsBeingSaved)...).done(-> document.location.reload())

  reverseLevelsBeforeSave: ->
    # For some unfathomable reason, not in our code anywhere, the levels get reversed during the save somehow.
    # Since we want to maintain their orders, we reverse them first, so that when they're reversed again, it's right.
    # Yaaaay!
    return unless campaign = _.find @modelsToSave.models, (m) -> m.constructor.className is 'Campaign'
    levelsReversed = {}
    levels = campaign.get 'levels'
    levelIDs = _.keys(levels).reverse()
    for levelID in levelIDs
      levelsReversed[levelID] = levels[levelID]
      campaign.set 'levels', levelsReversed

ModalView = require 'views/core/ModalView'
template = require 'templates/editor/campaign/save-campaign-modal'
DeltaView = require 'views/editor/DeltaView'

module.exports = class SaveCampaignModal extends ModalView
  id: 'save-campaign-modal'
  template: template
  plain: true

  events:
    'click #save-button': 'onClickSaveButton'

  constructor: (options, @modelsToSave) ->
    super(options)

  afterRender: ->
    @$el.find('.delta-view').each((i, el) =>
      $el = $(el)
      model = @modelsToSave.find( id: $el.data('model-id'))
      deltaView = new DeltaView({model: model})
      @insertSubView(deltaView, $el)
    )
    super()

  onClickSaveButton: ->
    @showLoading()
    modelsBeingSaved = (model.patch() for model in @modelsToSave.models)
    $.when(_.compact(modelsBeingSaved)...).done(-> document.location.reload())

Stop reversing levels in CampaignEditor

Stop reversing levels in CampaignEditor

Campaign saves started being reversed, and removing this
code brings it back to "normal".


ThangType = require 'models/ThangType'
CocoCollection = require 'collections/CocoCollection'

module.exports = class ThangNamesCollection extends CocoCollection
  url: '/db/thang.type/names'
  model: ThangType
  isCachable: false

  constructor: (@ids) ->
    super()
    @ids.sort()
    if @ids.length > 55
      console.error 'Too many ids, we\'ll likely go over the GET url kind-of-limit of 2000 characters.'

  fetch: (options) ->
    options ?= {}
    _.extend options, {data: {ids: @ids}}
    super(options)

ThangType = require 'models/ThangType'
CocoCollection = require 'collections/CocoCollection'

module.exports = class ThangNamesCollection extends CocoCollection
  url: '/db/thang.type/names'
  model: ThangType
  isCachable: false

  constructor: (@ids) ->
    super()
    @ids.sort()

  fetch: (options) ->
    options ?= {}
    _.extend options, {data: {ids: @ids}}
    super(options)

Remove a log no longer relevant to modern browsers


leveldb = exports = module.exports = require './leveldb/handle'
binding = require '../build/Release/leveldb.node'

leveldb.version = '0.7.0'
leveldb.bindingVersion = "#{binding.majorVersion}.#{binding.minorVersion}"

leveldb.Batch = require('./leveldb/batch').Batch

###

    Create a partitioned bitwise comparator for use with opening a database.

    @param {Array} partitions Partition configuration data.
      @param {Array} partitions[] Slice configuration.
        @param {Integer} partitions[][0] Number of bytes in this slice. Use
          zero to set the sorting direction for all bytes from the current
          offset until the next slice or the end of the key.
        @param {Boolean} partitions[][1] If true, use reverse bitwise
          sorting until the next slice or the end of the key.

###

leveldb.partitionedBitwiseComparator = ->

  # variable args
  args = Array.prototype.slice.call arguments

  # ([7, true], [8, false]) or ([[7, true], [8, false]])
  args = if Array.isArray args[0][0] then args[0] else args

  # flatten bounds
  bounds = []
  for [ limit, reverse ] in args
    bounds.push parseInt limit
    bounds.push !!reverse

  binding.createPartitionedBitwiseComparator bounds

leveldb = exports = module.exports = require './leveldb/handle'
binding = require '../build/Release/leveldb.node'

leveldb.version = '0.7.1'
leveldb.bindingVersion = "#{binding.majorVersion}.#{binding.minorVersion}"

leveldb.Batch = require('./leveldb/batch').Batch

###

    Create a partitioned bitwise comparator for use with opening a database.

    @param {Array} partitions Partition configuration data.
      @param {Array} partitions[] Slice configuration.
        @param {Integer} partitions[][0] Number of bytes in this slice. Use
          zero to set the sorting direction for all bytes from the current
          offset until the next slice or the end of the key.
        @param {Boolean} partitions[][1] If true, use reverse bitwise
          sorting until the next slice or the end of the key.

###

leveldb.partitionedBitwiseComparator = ->

  # variable args
  args = Array.prototype.slice.call arguments

  # ([7, true], [8, false]) or ([[7, true], [8, false]])
  args = if Array.isArray args[0][0] then args[0] else args

  # flatten bounds
  bounds = []
  for [ limit, reverse ] in args
    bounds.push parseInt limit
    bounds.push !!reverse

  binding.createPartitionedBitwiseComparator bounds

Fix incorrect version in CoffeeScript portion.


# TODO: Normalize to 100 or 1.00
SCALE = 30

me = {
  name: 'me'
  shape: 'turtle'
  position: [0,0]
  color: 'blue'
  programs: {first: [], repeat: ['forward']}
  editable: true
}

exit = {
  name: 'exit'
  shape: 'diamond'
  position: [0,4]
  color: 'red'
  programs: {turtle: ['victory']}
}

me2 = me#$.extend me, {position: [2,2]}

exports.game = {
  name: "Example Game"
  based_on: 'baseline'
  comment: "For testing purposes only"

  language: {
    # Array of Operation, key, argument
    victory: ['.send', 'done',  1]
    failure: ['.send', 'done', -1]
  }

  shapes: {
    diamond: [
      "M0,#{SCALE} l#{SCALE},#{SCALE} l#{SCALE},-#{SCALE} l-#{SCALE},-#{SCALE} Z"
    ]
  }

  levels: [
    {
      name: 'Move the Turtle to the Exit'
      sprites: [me, exit]
      goal: {
        clicks: 1
        ticks: 4
        bricks: 1
      }
    }
    {
      name: 'Double Time'
      sprites: [me, me2, exit]
      goal: {
        clicks: 10
        ticks: 8
        bricks: 6
      }
    }
  ]
}

{my} = require '../my'
{vector} = require '../god/vector'

# TODO: Normalize to 100 or 1.00
SCALE = 30

me = {
  name: 'me'
  shape: 'turtle'
  position: [0,0]
  color: 'blue'
  programs: {first: [], repeat: ['forward']}
  editable: true
}

exit = {
  name: 'exit'
  shape: 'diamond'
  position: [0,4]
  color: 'red'
  programs: {turtle: ['victory']}
}

me2 = my.extend me, {position: [2,2]}

exports.game = {
  name: "Example Game"
  based_on: 'baseline'
  comment: "For testing purposes only"

  language: {
    # Array of Operation, key, argument
    victory: ['.send', 'done',  1]
    failure: ['.send', 'done', -1]
  }

  shapes: {
    diamond: [
      "M0,#{SCALE} l#{SCALE},#{SCALE} l#{SCALE},-#{SCALE} l-#{SCALE},-#{SCALE} Z"
    ]
  }

  levels: [
    {
      name: 'Move the Turtle to the Exit'
      sprites: [me, exit]
      goal: {
        clicks: 1
        ticks: 4
        bricks: 1
      }
    }
    {
      name: 'Double Time'
      sprites: [me, me2, exit]
      goal: {
        clicks: 10
        ticks: 8
        bricks: 6
      }
    }
  ]
}

Update expample to use my

Update expample to use my


path = require 'path'
express = require 'express'
http = require 'http'
apm = require '../lib/apm-cli'

describe 'apm search', ->
  server = null

  beforeEach ->
    silenceOutput()
    spyOnToken()

    app = express()
    app.get '/search', (request, response) ->
      response.sendfile path.join(__dirname, 'fixtures', 'search.json')
    server =  http.createServer(app)
    server.listen(3000)

    process.env.ATOM_PACKAGES_URL = "http://localhost:3000"

  afterEach ->
    server.close()

  it 'lists the matching packages', ->
    callback = jasmine.createSpy('callback')
    apm.run(['search', 'duck'], callback)

    waitsFor 'waiting for command to complete', ->
      callback.callCount > 0

    runs ->
      expect(console.log).toHaveBeenCalled()
      expect(console.log.argsForCall[1][0]).toContain 'duckberg'
      expect(console.log.argsForCall[2][0]).toContain 'duckblur'
      expect(console.log.argsForCall[3][0]).toContain 'ducktales'

  it "logs an error if the query is missing or empty", ->
    callback = jasmine.createSpy('callback')
    apm.run(['search'], callback)

    waitsFor 'waiting for command to complete', ->
      callback.callCount > 0

    runs ->
      expect(console.error).toHaveBeenCalled()
      expect(console.error.argsForCall[0][0].length).toBeGreaterThan 0

Add spec for missing search query

Add spec for missing search query


# run with:
# $ rake spec
# OR
# $ npm install jasmine-node
# $ jasmine-node --coffee spec

exports.wesabe =
  log: [],

  provide: (module, value) ->
    @walk module, (part, mod, level, levels) ->
      mod[part] ||= if value? and level is level.length - 1
        value
      else
        {}

  require: (module) ->
    require "../application/chrome/content/wesabe/#{module.replace('.', '/')}"
    @walk module

  taint: (object) ->
    object

  untaint: (object) ->
    object

  radioactive: (args...) ->
    @log.push 'radioactive', args

  debug: (args...) ->
    @log.push 'debug', args

  info: (args...) ->
    @log.push 'info', args

  warn: (args...) ->
    @log.push 'warn', args

  error: (args...) ->
    @log.push 'error', args

  fatal: (args...) ->
    @log.push 'fatal', args

  walk: (module, callback) ->
    base = wesabe
    parts = module.split('.')

    for part, i in parts
      callback part, base, i, parts if callback?
      base = base[part]

    return base

# run with:
# $ rake spec
# OR
# $ npm install jasmine-node
# $ jasmine-node --coffee spec

exports.wesabe =
  log: [],

  provide: (module, value) ->
    @walk module, (part, mod, level, levels) ->
      mod[part] ||= if value? and (level is levels.length - 1)
        value
      else
        {}

  require: (module) ->
    require "../application/chrome/content/wesabe/#{module.replace('.', '/')}"
    @walk module

  taint: (object) ->
    object

  untaint: (object) ->
    object

  radioactive: (args...) ->
    @log.push 'radioactive', args

  debug: (args...) ->
    @log.push 'debug', args

  info: (args...) ->
    @log.push 'info', args

  warn: (args...) ->
    @log.push 'warn', args

  error: (args...) ->
    @log.push 'error', args

  fatal: (args...) ->
    @log.push 'fatal', args

  walk: (module, callback) ->
    base = wesabe
    parts = module.split('.')

    for part, i in parts
      callback part, base, i, parts if callback?
      base = base[part]

    return base

Make providing a value with wesabe.provide actually work.


module.exports =
  selector: ['.source.css.scss', '.source.sass']
  id: 'aligner-scss' # package name
  config:
    ':-alignment':
      title: 'Padding for :'
      description: 'Pad left or right of the character'
      type: 'string'
      default: 'right'
    ':-leftSpace':
      title: 'Left space for :'
      description: 'Add 1 whitespace to the left'
      type: 'boolean'
      default: false
    ':-rightSpace':
      title: 'Right space for :'
      description: 'Add 1 whitespace to the right'
      type: 'boolean'
      default: true
    ':-scope':
      title: 'Character scope'
      description: 'Scope string to match'
      type: 'string'
      default: 'key-value|property-name'

module.exports =
  selector: ['.source.css.scss', '.source.sass']
  id: 'aligner-scss' # package name
  config:
    ':-alignment':
      title: 'Padding for :'
      description: 'Pad left or right of the character'
      type: 'string'
      default: 'right'
    ':-leftSpace':
      title: 'Left space for :'
      description: 'Add 1 whitespace to the left'
      type: 'boolean'
      default: false
    ':-rightSpace':
      title: 'Right space for :'
      description: 'Add 1 whitespace to the right'
      type: 'boolean'
      default: true
    ':-scope':
      title: 'Character scope'
      description: 'Scope string to match'
      type: 'string'
      default: 'key-value|property-name|operator'

Add operator scope to `:`

Add operator scope to `:`


# External Dependencies
debug = require('debug')('minimum-wage-service')
express = require 'express'

# Express Components
router = express.Router()

#Internal Dependencies
employer_size_router = require './employer_size'
survey_router = require './survey'

router.all '*', (req,res,next) ->
  res.header("Access-Control-Allow-Origin", "*")
  res.header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With")
  next()

router.get '/wage_theft/report_info', (req, res) ->
  res.json({
    post_url: "https://docs.google.com/forms/d/1ArpKfNDrrsdNl05VLkT3tECjA0vctk8wnVrMGQSIqIE/formResponse"
    mapping:
      "employer-name": "entry.1223479717"
      "employee-name": "entry.1255459603"
      "employee-email": "entry.570498028"
      "employee-phone": "entry.229881285"
  }).end()

router.use '/employer_size', employer_size_router
router.use '/survey', survey_router

module.exports = router

# External Dependencies
debug = require('debug')('minimum-wage-service')
express = require 'express'

# Express Components
router = express.Router()

#Internal Dependencies
employer_size_router = require './employer_size'
survey_router = require './survey'

router.all '*', (req,res,next) ->
  res.header("Access-Control-Allow-Origin", "*")
  res.header("Access-Control-Allow-Headers", "Content-Type, X-Requested-With")
  next()

router.get '/wage_theft/report_info', (req, res) ->
  res.json({
    post_url: "https://docs.google.com/forms/d/1ArpKfNDrrsdNl05VLkT3tECjA0vctk8wnVrMGQSIqIE/formResponse"
    mapping:
      "employer[name]": "entry.1223479717"
      "employee[name]": "entry.1255459603"
      "employee[email]": "entry.570498028"
      "employee[phone]": "entry.229881285"
  }).end()

router.use '/employer_size', employer_size_router
router.use '/survey', survey_router

module.exports = router

Modify wage theft report info endpoint

Modify wage theft report info endpoint


# Your snippets
#
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
# expand the prefix into a larger code block with templated values.
#
# You can create a new snippet in this file by typing "snip" and then hitting
# tab.
#
# An example CoffeeScript snippet to expand log to console.log:
#
# '.source.coffee':
#   'Console log':
#     'prefix': 'log'
#     'body': 'console.log $1'
#
'.source.ruby':
  'FactoryGirl Build':
    'prefix': 'fgb'
    'body': 'FactoryGirl.build($1)$0'
  'FactoryGirl Build Stubbed':
    'prefix': 'fgbs'
    'body': 'FactoryGirl.build_stubbed($1)$0'
  'FactoryGirl Create':
    'prefix': 'fgc'
    'body': 'FactoryGirl.create($1)$0'
  'It do':
    'prefix': 'itd'
    'body': "it '$1' do\n$0\nend"
  'Describe':
    'prefix': 'desc'
    'body': "describe $1 do\n$0\nend"
  'Let':
    'prefix': 'let'
    'body': 'let($1) { $0 }'
  'It':
    'prefix': 'it'
    'body': 'it { $0 }'
  'Subject':
    'prefix': 'subj'
    'body': 'subject($1) { $0 }'
  'Before':
    'prefix': 'bef'
    'body': 'before { $0 }'

# Your snippets
#
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
# expand the prefix into a larger code block with templated values.
#
# You can create a new snippet in this file by typing "snip" and then hitting
# tab.
#
# An example CoffeeScript snippet to expand log to console.log:
#
# '.source.coffee':
#   'Console log':
#     prefix: 'log'
#     body: 'console.log $1'
#
.source.coffee:
  Describe:
    prefix: 'des'
    body: "describe '$1', ->$0"
.source.ruby:
  Before:
    prefix: 'bef'
    body: 'before { $0 }'
  Describe:
    prefix: 'desc'
    body: "describe $1 do\n$0\nend"
  'FactoryGirl Build':
    prefix: 'fgb'
    body: 'FactoryGirl.build($1)$0'
  'FactoryGirl Build Stubbed':
    prefix: 'fgbs'
    body: 'FactoryGirl.build_stubbed($1)$0'
  'FactoryGirl Create':
    prefix: 'fgc'
    body: 'FactoryGirl.create($1)$0'
  It:
    prefix: 'it'
    body: 'it { $0 }'
  'It do':
    prefix: 'itd'
    body: "it '$1' do\n$0\nend"
  Let:
    prefix: 'let'
    body: 'let($1) { $0 }'
  Subject:
    prefix: 'subj'
    body: 'subject($1) { $0 }'

Add CoffeeScript describe and reorganize


window.languageAutocomplete = ->
  return false if !$("[data-provide=typeahead]").length

  projectLanguages = $("[data-provide=typeahead]").data "source"

  $("[data-provide=typeahead]").typeahead
    name: $(this).attr "name"
    local: projectLanguages

jQuery ->
  $("a[rel=popover]").popover()
  $(".tooltip").tooltip()
  $("[rel=tooltip]").tooltip()

  $('#presents a:first').tab('show')

  $("#presents a").click (e) ->
    e.preventDefault()
    $(this).tab "show"

    languageAutocomplete()

$(document).on 'ready page:load', ->
  Emojify.replace '.js-emoji'

window.languageAutocomplete = ->
  return false if !$("[data-provide=typeahead]").length

  projectLanguages = $("[data-provide=typeahead]").data "source"

  $("[data-provide=typeahead]").typeahead
    name: $(this).attr "name"
    local: projectLanguages

jQuery.timeago.settings.allowFuture = true;

jQuery ->
  $("a[rel=popover]").popover()
  $(".tooltip").tooltip()
  $("[rel=tooltip]").tooltip()

  $('#presents a:first').tab('show')

  $("#presents a").click (e) ->
    e.preventDefault()
    $(this).tab "show"

    languageAutocomplete()

$(document).on 'ready page:load', ->
  Emojify.replace '.js-emoji'

Allow future dates in timeago

Allow future dates in timeago

Although not something that can happen in normal application usage
(without something going a bit wrong) this is a bit of an issue if you
have seeded the database with future PRs. This change of setting
resolves this issue.


'use strict'
Hapi = require 'hapi'
Boom = require 'boom'
BadRequest = Boom.badRequest
join = require('path').join

class Server

  constructor: ->
    @server = new Hapi.Server()
    @server.connection(port: 4555)
    @_names()

  _names: ->
    @server.route
      method: 'GET'
      path: '/names'
      handler: (req, reply)=>
        return reply(BadRequest('Server Has not been started!')) unless @snippetNames
        reply(names: @snippetNames)

  ###
  The required path is relative to this module...
  ###
  _configure: (context, path)->
    @requiredFile = context.require(path)
    console.log 'file', path
    console.log 'file', @requiredFile
    @snippetNames = Object.keys(@requiredFile)
    @_setupRoutes()

  _setupRoutes: ->
    @server.route
      method: ['PUT', 'POST', 'GET']
      path: '/snippet/{name}'
      handler: (req, reply)=>
        snippet = @requiredFile[req.params.name]
        return reply(BadRequest('Snippet Not Found!')) unless snippet
        snippet(req, reply)

  start: (context, path, cb)->
    throw Error('No Path Given!') unless path
    @_configure(context, path)
    @server.start (err)->
      cb(err) if cb

module.exports = new Server()

'use strict'
Hapi = require 'hapi'
Boom = require 'boom'
BadRequest = Boom.badRequest
join = require('path').join

class Server

  constructor: ->
    @server = new Hapi.Server()
    @server.connection(process.env.PORT or 4545)
    @_names()

  _names: ->
    @server.route
      method: 'GET'
      path: '/names'
      handler: (req, reply)=>
        return reply(BadRequest('Server Has not been started!')) unless @snippetNames
        reply(names: @snippetNames)

  ###
  The required path is relative to this module...
  ###
  _configure: (context, path)->
    @requiredFile = context.require(path)
    console.log 'file', path
    console.log 'file', @requiredFile
    @snippetNames = Object.keys(@requiredFile)
    @_setupRoutes()

  _setupRoutes: ->
    @server.route
      method: ['PUT', 'POST', 'GET']
      path: '/snippet/{name}'
      handler: (req, reply)=>
        snippet = @requiredFile[req.params.name]
        return reply(BadRequest('Snippet Not Found!')) unless snippet
        snippet(req, reply)

  start: (context, path, cb)->
    throw Error('No Path Given!') unless path
    @_configure(context, path)
    @server.start (err)->
      cb(err) if cb

module.exports = new Server()

Use the deis specified port if it exists.


#= require ./validators

class Batman.ExclusionValidator extends Batman.Validator
  @triggers 'exlusionIn'

  constructor: (options) ->
    @acceptableValues = options.exlusionIn
    super

  validateEach: (errors, record, key, callback) ->
    value = record.get(key)

    for acceptableValue in @acceptableValues
      if acceptableValue == value
        errors.add key, @format(key, 'included_in_list')
        return callback()

    callback()

Batman.Validators.push Batman.ExclusionValidator

#= require ./validators

class Batman.ExclusionValidator extends Batman.Validator
  @triggers 'exlusionIn'

  constructor: (options) ->
    @unacceptableValues = options.exlusionIn
    super

  validateEach: (errors, record, key, callback) ->
    value = record.get(key)

    for unacceptableValue in @unacceptableValues
      if unacceptableValue == value
        errors.add key, @format(key, 'included_in_list')
        return callback()

    callback()

Batman.Validators.push Batman.ExclusionValidator

Rename variables for readability and to avoid confusion


'.platform-linux atom-text-editor.ide-haskell-repl, .platform-win32 atom-text-editor.ide-haskell-repl':
  'shift-enter': 'ide-haskell-repl:exec-command'
  'shift-up': 'ide-haskell-repl:history-back'
  'shift-down': 'ide-haskell-repl:history-forward'
  'ctrl-shift-r': 'ide-haskell-repl:ghci-reload'

'.platform-darwin atom-text-editor.ide-haskell-repl':
  'cmd-enter': 'ide-haskell-repl:exec-command'
  'shift-up': 'ide-haskell-repl:history-back'
  'shift-down': 'ide-haskell-repl:history-forward'
  'ctrl-shift-r': 'ide-haskell-repl:ghci-reload'

'.platform-linux atom-text-editor.ide-haskell-repl':
  'shift-enter': 'ide-haskell-repl:exec-command'
  'shift-up': 'ide-haskell-repl:history-back'
  'shift-down': 'ide-haskell-repl:history-forward'
  'ctrl-shift-r': 'ide-haskell-repl:ghci-reload'

'.platform-win32 atom-text-editor.ide-haskell-repl':
  'shift-enter': 'ide-haskell-repl:exec-command'
  'shift-up': 'ide-haskell-repl:history-back'
  'shift-down': 'ide-haskell-repl:history-forward'
  'ctrl-shift-r': 'ide-haskell-repl:ghci-reload'

'.platform-darwin atom-text-editor.ide-haskell-repl':
  'cmd-enter': 'ide-haskell-repl:exec-command'
  'shift-up': 'ide-haskell-repl:history-back'
  'shift-down': 'ide-haskell-repl:history-forward'
  'ctrl-shift-r': 'ide-haskell-repl:ghci-reload'

Work around Atom not displaying keybindings


do ->
  global.Libs or= {}

  global.Libs.JsZip = require("jszip")

  global.Libs.CoffeeScript = require("coffee-script")

  # global.Libs.Canarium = require("canarium")

  global.Libs.GitHubFactory = {
    GitHub: require("github-api")
    apiBase: "http://#{window.location.host}/api"
    create: (args...) ->
      instance = new @GitHub(args...)
      instance.__apiBase = @apiBase if @apiBase?
      return instance
  }

Remove GitHubFactory (replaced with GitHubFetcher)


path = require 'path'

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

  grunt.registerTask 'partial-clean', 'Delete some of the build files', ->
    rm grunt.config.get('atom.buildDir')
    rm '/tmp/atom-coffee-cache'
    rm '/tmp/atom-cached-atom-shells'
    rm 'node'

  grunt.registerTask 'clean', 'Delete all the build files', ->
    rm 'node_modules'
    rm 'atom-shell'
    rm path.join(process.env.HOME, '.atom', '.node-gyp')
    grunt.task.run('partial-clean')

path = require 'path'

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

  grunt.registerTask 'partial-clean', 'Delete some of the build files', ->
    rm grunt.config.get('atom.buildDir')
    rm '/tmp/atom-coffee-cache'
    rm '/tmp/atom-cached-atom-shells'
    rm 'node'
    rm 'atom-shell'

  grunt.registerTask 'clean', 'Delete all the build files', ->
    rm 'node_modules'
    rm path.join(process.env.HOME, '.atom', '.node-gyp')
    grunt.task.run('partial-clean')

Remove atom-shell during partial-clean task instead of clean task


mod = angular.module 'mothership', [
  'ui.router'
  'ui.bootstrap'
]

mod.config ($stateProvider, $urlRouterProvider) ->

  $urlRouterProvider.otherwise('/playing')

  $stateProvider.state
    name: 'layout'
    abstract: true
    views:
      'layout':
        template: '<m-header></m-header><main ui-view></main>'

  $stateProvider.state
    name: 'playing'
    url: '/playing'
    parent: 'layout'
    template: '<m-playing></m-playing>'

  $stateProvider.state
    name: 'playlist'
    url: '/playlist'
    parent: 'layout'
    template: '<m-playlist></m-playlist>'

  $stateProvider.state
    name: 'browse',
    url: '/browse?page&sort&direction'
    parent: 'layout'
    template: '<m-browse></m-browse>'

  # This needs to be the last state, because of the wildcard url match.
  $stateProvider.state
    name: 'browse.uri'
    url: '/{uri:.*}?page&sort&direction'
    parent: 'layout'
    template: '<m-browse></m-browse>'

mod.run ($rootScope, $state) ->
  $rootScope.$state = $state

mod = angular.module 'mothership', [
  'ui.router'
  'ui.bootstrap'
]

mod.config (
  $stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider
) ->

  $urlRouterProvider.otherwise('/playing')

  valToString = (val) -> if val? then val.toString() else val
  $urlMatcherFactoryProvider.type 'nonURIEncoded',
    encode: valToString
    decode: valToString
    is: -> true

  $stateProvider.state
    name: 'layout'
    abstract: true
    views:
      'layout':
        template: '<m-header></m-header><main ui-view></main>'

  $stateProvider.state
    name: 'playing'
    url: '/playing'
    parent: 'layout'
    template: '<m-playing></m-playing>'

  $stateProvider.state
    name: 'playlist'
    url: '/playlist'
    parent: 'layout'
    template: '<m-playlist></m-playlist>'

  $stateProvider.state
    name: 'browse',
    url: '/browse?page&sort&direction'
    parent: 'layout'
    template: '<m-browse></m-browse>'

  # This needs to be the last state, because of the wildcard url match.
  $stateProvider.state
    name: 'browse.uri'
    url: '/{uri:nonURIEncoded}?page&sort&direction'
    parent: 'layout'
    template: '<m-browse></m-browse>'

mod.run ($rootScope, $state) ->
  $rootScope.$state = $state

:bug: Fix prevent ui router from encoding path names in the URL


React = require 'react'
objectAssign = require 'object-assign'

module.exports = (icons) ->
  React.createClass
    propTypes:
      icon: React.PropTypes.string.isRequired
      size: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number
      ])
      style: React.PropTypes.object

    getDefaultProps: ->
      return {
        size: 24
        style: {}
      }

    render: ->
      styles = {
        fill: "currentcolor"
        verticalAlign: "middle"
         # Use CSS instead of the width attr to support non-pixel units
        width: @props.size
      }

      return (
        <svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" fit
          style={objectAssign(
            styles,
            @props.style
          )}>
            {icons[@props.icon]}
        </svg>
      )

React = require 'react'
objectAssign = require 'object-assign'

module.exports = (icons) ->
  React.createClass
    propTypes:
      icon: React.PropTypes.string.isRequired
      size: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number
      ])
      style: React.PropTypes.object

    getDefaultProps: ->
      return {
        size: 24
        style: {}
      }

    render: ->
      styles = {
        fill: "currentcolor"
        verticalAlign: "middle"
        # Use CSS instead of the width attr to support non-pixel units
        width: @props.size
        # Prevents scaling issue in IE
        height: @props.size
      }

      return (
        <svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" fit
          style={objectAssign(
            styles,
            @props.style
          )}>
            {icons[@props.icon]}
        </svg>
      )

Add `height` to prevent scaling issue in IE


React = require 'react'
CropInitializer = require './initializer'
GenericTask = require '../generic'

module.exports = React.createClass
  displayName: 'CropTask'

  statics:
    getSVGProps: ({workflow, classification, annotation}) ->
      tasks = require '../index'
      [previousCropAnnotation] = classification.annotations.filter (anAnnotation) =>
        taskDescription = workflow.tasks[anAnnotation.task]
        TaskComponent = tasks[taskDescription.type]
        TaskComponent is this and anAnnotation.value? and anAnnotation isnt annotation
      if previousCropAnnotation?
        {x, y, width, height} = previousCropAnnotation.value
        viewBox: "#{x} #{y} #{width} #{height}"

    InsideSubject: CropInitializer

    getDefaultTask: ->
      type: 'crop'
      instruction: 'Drag to select the relevant area.'
      help: ''

    getTaskText: (task) ->
      task.instruction

    getDefaultAnnotation: ->
      value: null

    isAnnotationComplete: (task, annotation) ->
      annotation.value? or not task.required

  getDefaultProps: ->
    task: null
    annotation: null
    onChange: Function.prototype

  render: ->
    <GenericTask question={@props.task.instruction} help={@props.task.help}>
      <p>
        <button type="button" onClick={@handleClear}>Clear current crop</button>
      </p>
    </GenericTask>

  handleClear: ->
    @props.annotation.value = null
    @props.onChange()

React = require 'react'
CropInitializer = require './initializer'
GenericTask = require '../generic'

module.exports = React.createClass
  displayName: 'CropTask'

  statics:
    getSVGProps: ({workflow, classification, annotation}) ->
      tasks = require '../index'
      [previousCropAnnotation] = classification.annotations.filter (anAnnotation) =>
        taskDescription = workflow.tasks[anAnnotation.task]
        TaskComponent = tasks[taskDescription.type]
        TaskComponent is this and anAnnotation.value? and anAnnotation isnt annotation
      if previousCropAnnotation?
        {x, y, width, height} = previousCropAnnotation.value
        viewBox: "#{x} #{y} #{width} #{height}"

    InsideSubject: CropInitializer

    getDefaultTask: ->
      type: 'crop'
      instruction: 'Drag to select the relevant area.'
      help: ''

    getTaskText: (task) ->
      task.instruction

    getDefaultAnnotation: ->
      value: null

    isAnnotationComplete: (task, annotation) ->
      annotation.value? or not task.required

  getDefaultProps: ->
    task: null
    annotation: null
    onChange: Function.prototype

  render: ->
    <GenericTask question={@props.task.instruction} help={@props.task.help}>
      <p>
        <button type="button" disabled={not @props.annotation.value?} onClick={@handleClear}>Clear current crop</button>
      </p>
    </GenericTask>

  handleClear: ->
    @props.annotation.value = null
    @props.onChange()

Disable Clear button when there's no crop


dependencies = [
    'ngRoute',
    'ui.bootstrap',
    'myApp.filters',
    'myApp.services',
    'myApp.controllers',
    'myApp.directives',
    'myApp.common',
    'myApp.routeConfig'
]

app = angular.module('myApp', dependencies)

angular.module('myApp.routeConfig', ['ngRoute']).config ($locationProvider) ->
    $locationProvider.html5Mode({
        enabled: true,
        requireBase: false
    })

@commonModule = angular.module('myApp.common', [])
@controllersModule = angular.module('myApp.controllers', [])
@servicesModule = angular.module('myApp.services', [])
@modelsModule = angular.module('myApp.models', [])
@directivesModule = angular.module('myApp.directives', [])
@filtersModule = angular.module('myApp.filters', [])

Change indentation to follow coffeelint

Change indentation to follow coffeelint


_ = require 'underscore'
NamespaceStore = require '../../src/flux/stores/namespace-store'

describe "NamespaceStore", ->
  beforeEach ->
    @constructor = NamespaceStore.constructor

  it "should initialize current() using data saved in config", ->
    state =
      "id": "123",
      "email_address":"bengotow@gmail.com",
      "object":"namespace"
      "organization_unit": "label"

    spyOn(atom.config, 'get').andCallFake -> state
    instance = new @constructor
    expect(instance.current().id).toEqual(state['id'])
    expect(instance.current().emailAddress).toEqual(state['email_address'])

  it "should initialize current() to null if data is not present", ->
    spyOn(atom.config, 'get').andCallFake -> null
    instance = new @constructor
    expect(instance.current()).toEqual(null)

  it "should initialize current() to null if data is invalid", ->
    spyOn(atom.config, 'get').andCallFake -> "this isn't an object"
    instance = new @constructor
    expect(instance.current()).toEqual(null)

_ = require 'underscore'
NamespaceStore = require '../../src/flux/stores/namespace-store'

describe "NamespaceStore", ->
  beforeEach ->
    @instance = null
    @constructor = NamespaceStore.constructor

  afterEach ->
    @instance.stopListeningToAll()

  it "should initialize current() using data saved in config", ->
    state =
      "id": "123",
      "email_address":"bengotow@gmail.com",
      "object":"namespace"
      "organization_unit": "label"

    spyOn(atom.config, 'get').andCallFake -> state
    @instance = new @constructor
    expect(@instance.current().id).toEqual(state['id'])
    expect(@instance.current().emailAddress).toEqual(state['email_address'])

  it "should initialize current() to null if data is not present", ->
    spyOn(atom.config, 'get').andCallFake -> null
    @instance = new @constructor
    expect(@instance.current()).toEqual(null)

  it "should initialize current() to null if data is invalid", ->
    spyOn(atom.config, 'get').andCallFake -> "this isn't an object"
    @instance = new @constructor
    expect(@instance.current()).toEqual(null)

Fix memory leak in NamespaceStoreSpec

fix(spec): Fix memory leak in NamespaceStoreSpec


class Lanes.Components.RadioField extends Lanes.React.Component

    mixins: [
        Lanes.Components.Form.FieldMixin
    ]

    renderEdit: ->
        <BS.Col {...@props}>
            <BS.Input
                type="radio"
                checked={@props.checked? || @props.value == @model[@props.name]}
                onChange={@handleChange}
                {...@props} />
        </BS.Col>

class FakeInputEvent
    constructor: (value) ->
        @target = {value}
    isDefaultPrevented: -> false

class Lanes.Components.RadioField extends Lanes.React.Component

    mixins: [
        Lanes.Components.Form.FieldMixin
    ]

    handleRadioChange: (ev) ->
        if ev.target.checked
            @fieldMixinSetValue( new FakeInputEvent(@props.value) )

    renderEdit: (props, handlers) ->
        <BS.FormControl
            {...props}
            {...handlers}
            type="radio"
            checked={@props.checked? || @props.value == @model[@props.name]}
            onChange={@handleRadioChange}
        />

Fix radio input to work with new BS forms


Include RNH and RUN file extensions

Include RNH and RUN file extensions

RUN appears to be one of the file extensions used by the original RUNOFF
program. RNH appears to signify a help file (supposedly "RUNOFF HELP"?).

Sources:

* http://web.mit.edu/Saltzer/www/publications/starring/starring.run
* http://odl.sysworks.biz/disk$vaxdocdec953/decw$book/dy4yaaa7.decw$book
* https://github.com/endlesssoftware/mmk/blob/master/mmk_help.rnh
* https://github.com/LuaDist/unzip/blob/master/vms/unzip_def.rnh


showFrame = document.getElementById("frame")
last_created_text = null

window.remote = Factlink.createFrameProxyObject window.parent,
  ['hide', 'show', 'highlightNewFactlink', 'stopHighlightingFactlink',
    'createdNewFactlink', 'trigger', 'setFeatureToggles'
  ]

local =
    showFactlink: (id, successFn) ->
      url = "/client/facts/#{id}"
      showUrl url, successFn

    prepareNewFactlink: (text, siteUrl, siteTitle, guided, successFn) ->
      url = "/facts/new" +
              "?fact=" + encodeURIComponent(text) +
              "&url=" + encodeURIComponent(siteUrl) +
              "&title=" + encodeURIComponent(siteTitle) +
              "&guided=" + encodeURIComponent(guided)
      showUrl url, successFn
      last_created_text = text

Factlink.listenToWindowMessages null, local

window.highlightLastCreatedFactlink = (id, text) ->
  if last_created_text == text
    remote.highlightNewFactlink(text, id)

showUrl = (url, successFn) ->
  window.onModalReady = ->
    window.onModalReady = -> # nothing
    successFn()

  backbone = showFrame.contentWindow.Backbone
  history = backbone?.history
  if history && backbone.History.started
    # Force (re)loading the url, even if already showing that url
    # If history.fragment is equal to the current url, it doesn't reload,
    # so we reset it to null
    history.fragment = null

    history.navigate url, trigger: true
  else
    showFrame.onload = -> window.onModalReady()
    showFrame.src = url

showFrame.src = '/client/blank'

showFrame = document.getElementById("frame")
last_created_text = null

window.remote = Factlink.createFrameProxyObject window.parent,
  ['hide', 'onModalReady', 'highlightNewFactlink', 'stopHighlightingFactlink',
    'createdNewFactlink', 'trigger', 'setFeatureToggles'
  ]

local =
    showFactlink: (id) ->
      url = "/client/facts/#{id}"
      showUrl url

    prepareNewFactlink: (text, siteUrl, siteTitle, guided) ->
      url = "/facts/new" +
              "?fact=" + encodeURIComponent(text) +
              "&url=" + encodeURIComponent(siteUrl) +
              "&title=" + encodeURIComponent(siteTitle) +
              "&guided=" + encodeURIComponent(guided)
      showUrl url
      last_created_text = text

Factlink.listenToWindowMessages null, local

window.highlightLastCreatedFactlink = (id, text) ->
  if last_created_text == text
    remote.highlightNewFactlink(text, id)

showUrl = (url) ->
  backbone = showFrame.contentWindow.Backbone
  history = backbone?.history
  if history && backbone.History.started
    # Force (re)loading the url, even if already showing that url
    # If history.fragment is equal to the current url, it doesn't reload,
    # so we reset it to null
    history.fragment = null

    history.navigate url, trigger: true
  else
    showFrame.onload = -> window.remote.onModalReady()
    showFrame.src = url

showFrame.src = '/client/blank'

Replace callbacks with an API

Replace callbacks with an API


App.IndexRoute = App.AuthenticatedRoute.extend
  setupController: (controller, model)->
    states   = []
    stateIds = []
    activeState = null

    model.forEach (item)->
      stateId = item.get("id")
      if !activeState? && item.get("joined") == true
        activeState = item
      states.push(stateId)
      states[stateId] = {}

    for stateId in stateIds
      console.log "k:", stateId, states[stateId]

    controller.set("stateIds", stateIds)
    controller.set("states", states)
    controller.set("activeState", activeState)
    @_super(controller, model)

  model: ->
    @store.find("room_user_state")

App.IndexRoute = App.AuthenticatedRoute.extend
  setupController: (controller, model)->
    stateData   = []
    stateIds = []
    activeState = null

    # Loop thru all room states
    #   find the first joined room to load
    #   start pollers for all joined rooms
    model.forEach (item)=>
      stateId = item.get("id")
      stateIds.push(stateId)
      console.log stateId

      # This is to make sure the first active room is loaded
      if item.get("joined") == true
        activeState = item if !activeState?
        poller = new App.Poller()
        poller.store  = @store
        poller.roomId = item.get("room").get("id")
        poller.start(item.get("room").get("id"))
        stateData[stateId] = {"poller": poller}
      else
        stateData[stateId] = {}

    for stateId in stateIds
      console.log "stateId: #{stateId}"

    controller.set("stateIds", stateIds)
    controller.set("stateData", stateData)
    controller.set("activeState", activeState)
    @_super(controller, model)

  model: ->
    @store.find("room_user_state")

  deactivate: ->
    states = controller.get("states")
    console.log states

App.Poller = Em.Object.extend
  start: ()->
    @started = true
    @messages = []
    @beforeMessageId = null
    @timer = setInterval @onPoll.bind(@), 3000

  stop: ->
    clearInterval(@timer)
    @started = false

  onPoll: ->
    console.log("polling now #{@roomId}")
    if @afterMessageId
      console.log "after #{@afterMessageId}"
      url = "/api/messages/#{@roomId}?after=#{@afterMessageId}"
    else
      url = "/api/messages/#{@roomId}"

    $.getJSON url, (response)=>
      Em.$.each response.messages, (index, message)=>
        if (! @store.recordIsLoaded(App.Message, message.id))
          console.log message
          @afterMessageId = message.id

Add Poller class to poll rooms

Add Poller class to poll rooms


class FinderPane extends Pane

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

    super options, data

    vmController = KD.getSingleton 'vmController'
    vmController.fetchDefaultVmName (vmName) =>
      @finder = new NFinderController
        nodeIdPath       : 'path'
        nodeParentIdPath : 'parentPath'
        contextMenu      : yes
        useStorage       : no

      @addSubView @finder.getView()
      @finder.updateVMRoot vmName, "/home/#{KD.nick()}"

      @finder.on 'FileNeedsToBeOpened', (file) =>
        file.fetchContents (err, contents) ->
          appManager = KD.getSingleton 'appManager'
          appManager.tell 'IDE', 'openFile', file, contents

class FinderPane extends Pane

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

    super options, data

    KD.getSingleton('appManager').open 'Finder', (finderApp) =>
      @createFinderController finderApp
      @bindListeners()

  createFinderController: (finderApp) ->
    @finderController = finderApp.create()
    @addSubView @finderController.getView()
    @finderController.reset()

    # this is legacy, should be deleted when we open source koding finder
    @finderController.getView().subViews.first.destroy()

  bindListeners: ->
    @finderController.on 'FileNeedsToBeOpened', (file) =>
      file.fetchContents (err, contents) ->
        appManager = KD.getSingleton 'appManager'
        appManager.tell 'IDE', 'openFile', file, contents

Refactor finder pane to work with multiple vms.


class KDBlockingModalView extends KDModalView
  constructor:->
    super
    $(window).off "keydown.modal"

  putOverlay:()->
    @$overlay = $ "
",
      class : "kdoverlay"
    @$overlay.hide()
    @$overlay.appendTo "body"
    @$overlay.fadeIn 200

  setDomElement:(cssClass)->
    @domElement = $ "

"

  click:(e)->

class KDBlockingModalView extends KDModalView
  constructor:->
    super
    $(window).off "keydown.modal"

  putOverlay:()->
    @$overlay = $ "
",
      class : "kdoverlay"
    @$overlay.hide()
    @$overlay.appendTo "body"
    @$overlay.fadeIn 200

  setDomElement:(cssClass)->
    @domElement = $ """

      """

  click:(e)->

Fix kdblockingmodal, take care hidden objects


React = require 'react'
CollectionsManager = require './manager'
alert = require '../lib/alert'

# Shows an icon to logged-in users that pops up a collections manager
module?.exports = React.createClass
  displayName: 'CollectionsManagerIcon'

  propTypes:
    subject: React.PropTypes.object
    user: React.PropTypes.object

  toggleCollectionsManagerPopup: ->
    alert (resolve) =>
      <div className="content-container">
        <CollectionsManager user={@props.user} project={@props.project} subject={@props.subject} onSuccess={resolve} />
      </div>

  render: ->
    <button
      className="collections-manager-icon"
      title="Collect"
      onClick={@toggleCollectionsManagerPopup}>
      <i className="fa fa-list" />
    </button>

React = require 'react'
CollectionsManager = require './manager'
Dialog = require 'modal-form/dialog'

# Shows an icon to logged-in users that pops up a collections manager
module?.exports = React.createClass
  displayName: 'CollectionsManagerIcon'

  propTypes:
    subject: React.PropTypes.object
    user: React.PropTypes.object

  getInitialState: ->
    open: false

  render: ->
    <button
      className="collections-manager-icon"
      title="Collect"
      onClick={@setState.bind this, {open: true}, null}>
      <i className="fa fa-list" />

      {if @state.open
        <Dialog tag="div">
          <CollectionsManager user={@props.user} project={@props.project} subject={@props.subject} onSuccess={@setState.bind this, {open: false}, null} />
        </Dialog>}
    </button>

Use modal-form dialog for collections manager icon


{ Disposable } = require 'atom'
path = require 'path'
fs = require 'fs'
glob = require 'glob'

module.exports =
class Cleaner extends Disposable
  constructor: (latex) ->
    @latex = latex

  clean: ->
    if !@latex.manager.findMain()
      return false
    rootDir = path.dirname(@latex.mainFile)
    removeGlobs = atom.config.get('atom-latex.file_ext_to_clean')\
      .replace(/\s/,'').split(',')
    for removeGlob in removeGlobs
      glob(removeGlob, cwd: rootDir, (err, files) ->
        if err
          return
        for file in files
          fullPath = path.resolve(rootDir, file)
          fs.unlink(fullPath)
      )
    return true

{ Disposable } = require 'atom'
path = require 'path'
fs = require 'fs'
glob = require 'glob'

module.exports =
class Cleaner extends Disposable
  constructor: (latex) ->
    @latex = latex

  clean: ->
    if !@latex.manager.findMain()
      return false
    rootDir = path.dirname(@latex.mainFile)
    FileExts = atom.config.get('atom-latex.file_ext_to_clean')\
      .replace(/\*\./g,'').replace(/\,\s/g,'|')
    glob("**/*.*(#{FileExts})", cwd: rootDir, (err, files) ->
      if err
        console.log err
        return
      for file in files
        fullPath = path.resolve(rootDir, file)
        fs.unlink(fullPath,(e) ->
          console.log e if e?)
    )
    return true

Clean files in project sub directories as well


class AceApplicationTabView extends ApplicationTabView

  removePane_: KDTabView::removePane

  removePane: (pane, shouldDetach) ->
    {aceView} = pane.getOptions()

    return  @removePane_ pane, shouldDetach  unless aceView

    {ace} = aceView
    file  = ace.getData()

    return @removePane_ pane, shouldDetach  unless ace.isContentChanged()

    modal = new KDModalView
      width         : 620
      cssClass      : "modal-with-text"
      title         : "Do you want to save your changes?"
      content       : "
Your changes will be lost if you don't save them.
"
      overlay       : yes
      buttons       :
        "SaveClose" :
          cssClass  : "modal-clean-green"
          title     : "Save and Close"
          callback  : =>
            if file.path.indexOf("localfile:") is 0
              file.once "fs.saveAs.finished", => @removePane_ pane
              @willClose = yes
              ace.requestSaveAs()
              modal.destroy()
            else
              ace.requestSave()
              @closePaneAndModal pane, modal
        "DontSave"  :
          cssClass  : "modal-clean-red"
          title     : "Don't Save"
          callback  : =>
            @closePaneAndModal pane, modal
        "Cancel"    :
          cssClass  : "modal-cancel"
          title     : "Cancel"
          callback  : =>
            modal.destroy()

  closePaneAndModal: (pane, modal) ->
    @removePane_ pane
    modal.destroy()

class AceApplicationTabView extends ApplicationTabView

  removePane_: KDTabView::removePane

  removePane: (pane, shouldDetach, quiet = no) ->
    {aceView} = pane.getOptions()

    return  @removePane_ pane, shouldDetach  if quiet or not aceView

    {ace} = aceView
    file  = ace.getData()

    return @removePane_ pane, shouldDetach  unless ace.isContentChanged()

    modal = new KDModalView
      width         : 620
      cssClass      : "modal-with-text"
      title         : "Do you want to save your changes?"
      content       : "
Your changes will be lost if you don't save them.
"
      overlay       : yes
      buttons       :
        "SaveClose" :
          cssClass  : "modal-clean-green"
          title     : "Save and Close"
          callback  : =>
            if file.path.indexOf("localfile:") is 0
              file.once "fs.saveAs.finished", => @removePane_ pane
              @willClose = yes
              ace.requestSaveAs()
              modal.destroy()
            else
              ace.requestSave()
              @closePaneAndModal pane, modal
        "DontSave"  :
          cssClass  : "modal-clean-red"
          title     : "Don't Save"
          callback  : =>
            @closePaneAndModal pane, modal
        "Cancel"    :
          cssClass  : "modal-cancel"
          title     : "Cancel"
          callback  : =>
            modal.destroy()

  closePaneAndModal: (pane, modal) ->
    @removePane_ pane
    modal.destroy()

Ace: Add `quiet` parameter to `removePane` method


#    BeamWars - A multiplayer in-browser remake of the Mac game of the same name
#    Copyright (C) 2013  Aaron Hill
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see http://www.gnu.org/licenses/.

define([], ->

  class Player

    constructor: (@name, pos) ->
      @positions = []
      @positions.push(pos)
      @current_line = []

    lastPos: ->
      @positions[@positions.length - 1]

    currentLine: ->
      @current_line.slice() # Copy array

    currentLinePos: ->
      @current_line[@current_line.length - 1]

    addToLine: (pos) ->
      @current_line.push(pos)

  Player
)

#    BeamWars - A multiplayer in-browser remake of the Mac game of the same name
#    Copyright (C) 2013  Aaron Hill
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see http://www.gnu.org/licenses/.

define(['fabric'], ->

  class Player

    constructor: (@name, pos) ->
      @positions = []
      @positions.push(pos)
      @current_line = new fabric.Polyline([], {
        stroke: 'black',
        stokeWidth: 3,
        fille: 'white'
      })

    lastPos: ->
      @positions[@positions.length - 1]

    currentLine: ->
      @current_line.points.slice() # Copy array

    currentLinePos: ->
      @current_line.points[@current_line.points.length - 1]

    addToLine: (pos) ->
      @current_line.points.push(pos)

  Player
)

Use fabric.js in Player class

Use fabric.js in Player class