DEFAULT_ENV = '_cam'

API_HOSTS =
  production: '' # Same domain!
  staging: 'https://panoptes-staging.zooniverse.org'
  development: 'http://localhost:3000'
  test: 'http://localhost:7357'
  _cam: 'http://172.17.2.87:3000'

API_APPLICATION_IDS =
  _cam: '05fd85e729327b2f71cda394d8e87e042e0b77b05e05280e8246e8bdb05d54ed'

module.exports =
  host: API_HOSTS[process.env.NODE_ENV ? DEFAULT_ENV]
  clientAppID: API_APPLICATION_IDS[process.env.NODE_ENV ? DEFAULT_ENV]

DEFAULT_ENV = 'staging'

API_HOSTS =
  production: '' # Same domain!
  staging: 'https://panoptes-staging.zooniverse.org'
  development: 'http://localhost:3000'
  test: 'http://localhost:7357'
  cam: 'http://172.17.2.87:3000'

API_APPLICATION_IDS =
  cam: '05fd85e729327b2f71cda394d8e87e042e0b77b05e05280e8246e8bdb05d54ed'

BROWSER_ENV_PARAM = location.search.match(/\W?env=(\w+)/)?[1]

env = BROWSER_ENV_PARAM ? process.env.NODE_ENV ? DEFAULT_ENV

module.exports =
  host: API_HOSTS[env]
  clientAppID: API_APPLICATION_IDS[env]

Allow changing environment from browser

Allow changing environment from browser


MaterialLink = React.createClass
  render: ->
    link = @props.link
    return `(

{link.text}

    )`

MaterialLinks = React.createClass
  render: ->
    links =  @props.links.map (link)->
      return if link? then `<MaterialLink link={link} />` else ''

    return `(

        {links}

    )`

window.MaterialLink = MaterialLink
window.MaterialLinks = MaterialLinks

Fix necessary key for looped items

Fix necessary key for looped items


fs = require 'fs'
path = require 'path'
_ = require 'underscore-plus'

fillTemplate = (filePath, data) ->
  template = _.template(String(fs.readFileSync(filePath + '.in')))
  filled = template(data)
  fs.writeFileSync(filePath, filled)

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

  grunt.registerTask 'mkdeb', 'Create debian package', ->
    done = @async()

    {name, version, description} = grunt.file.readJSON('package.json')
    section = 'devel'
    arch = 'amd64'
    maintainer = 'GitHub <support@github.com>'
    data = {name, version, description, section, arch, maintainer}

    control = path.join('resources', 'linux', 'debian', 'control')
    fillTemplate(control, data)
    desktop = path.join('resources', 'linux', 'Atom.desktop')
    fillTemplate(desktop, data)
    icon = path.join('resources', 'atom.png')
    buildDir = grunt.config.get('atom.buildDir')

    cmd = path.join('script', 'mkdeb')
    args = [version, control, desktop, icon, buildDir]
    spawn({cmd, args}, done)

fs = require 'fs'
path = require 'path'
_ = require 'underscore-plus'

fillTemplate = (filePath, data) ->
  template = _.template(String(fs.readFileSync(filePath + '.in')))
  filled = template(data)
  fs.writeFileSync(filePath, filled)

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

  grunt.registerTask 'mkdeb', 'Create debian package', ->
    done = @async()

    {name, version, description} = grunt.file.readJSON('package.json')
    section = 'devel'
    arch = 'amd64'
    maintainer = 'GitHub <atom@github.com>'
    data = {name, version, description, section, arch, maintainer}

    control = path.join('resources', 'linux', 'debian', 'control')
    fillTemplate(control, data)
    desktop = path.join('resources', 'linux', 'Atom.desktop')
    fillTemplate(desktop, data)
    icon = path.join('resources', 'atom.png')
    buildDir = grunt.config.get('atom.buildDir')

    cmd = path.join('script', 'mkdeb')
    args = [version, control, desktop, icon, buildDir]
    spawn({cmd, args}, done)

:penguin: Use correct email address for deb package.


adapter = if window.location.search.lastIndexOf('fixture') != -1
            DS.FixtureAdapter.extend
              latency: 250
          else
            DS.RESTAdapter.configure 'plurals',
              event_search: 'event_searches'

            DS.RESTAdapter.extend
              namespace: 'api/v1'

Pancakes.Adapter = adapter

# vim:ts=2:sw=2:et:tw=78

#= require models/data_collector

adapter = if window.location.search.lastIndexOf('fixture') != -1
            DS.FixtureAdapter.extend
              latency: 250
          else
            DS.RESTAdapter.configure 'plurals',
              event_search: 'event_searches'

            DS.RESTAdapter.extend
              namespace: 'api/v1'

Pancakes.Adapter = adapter

Pancakes.Adapter.map 'Pancakes.DataCollector',
  primaryKey: 'username'

# vim:ts=2:sw=2:et:tw=78

Set DataCollector's primary key as set by Pancakes' services.


$(document).ready ->
  $('#index-table').on 'change', '#select-all', ->
    if $(this).is(':checked')
      $('#index-table').find('.print-survey').prop 'checked', true
      $('#submit').prop 'disabled', false
    else
      $('#index-table').find('.print-survey').prop 'checked', false
      $('#submit').prop 'disabled', true
    return
  $('#index-table').on 'change', '.print-survey', ->
    if $('#index-table').find('.print-survey:checked').length == 0
      $('#submit').prop 'disabled', true
    else
      $('#submit').prop 'disabled', false
    return
  return

$(document).ready ->
  $('#index-table').on 'change', '#select-all', ->
    if $(this).is(':checked')
      $('#index-table').find('.print-survey').prop 'checked', true
      $('#submit').prop 'disabled', false
    else
      $('#index-table').find('.print-survey').prop 'checked', false
      $('#submit').prop 'disabled', true
    return
  $('#index-table').on 'change', '.print-survey', ->
    if $('#index-table').find('.print-survey:checked').length == 0
      $('#submit').prop 'disabled', true
    else
      $('#submit').prop 'disabled', false
    if $('.print-survey:checked').length == $('#index-table').find('tbody > tr').length
      $('#select-all').prop 'checked', true
    else
      $('#select-all').prop 'checked', false
    return
  return

Update to coffeescript for select all and individual select interactivity


About = require '../lib/about'

describe "About", ->
  workspaceElement = null

  beforeEach ->
    workspaceElement = atom.views.getView(atom.workspace)

    waitsForPromise ->
      atom.packages.activatePackage('about')

  describe "when the about:about-atom command is triggered", ->
    it "shows the About Atom view", ->
      # Attaching the workspaceElement to the DOM is required to allow the
      # `toBeVisible()` matchers to work. Anything testing visibility or focus
      # requires that the workspaceElement is on the DOM. Tests that attach the
      # workspaceElement to the DOM are generally slower than those off DOM.
      jasmine.attachToDOM(workspaceElement)

      expect(workspaceElement.querySelector('.about-atom')).not.toExist()
      atom.commands.dispatch workspaceElement, 'about:about-atom'

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about-atom')
        expect(aboutElement).toBeVisible()

About = require '../lib/about'
{$} = require 'atom-space-pen-views'

describe "About", ->
  workspaceElement = null

  beforeEach ->
    workspaceElement = atom.views.getView(atom.workspace)

    waitsForPromise ->
      atom.packages.activatePackage('about')

  describe "when the about:about-atom command is triggered", ->
    it "shows the About Atom view", ->
      # Attaching the workspaceElement to the DOM is required to allow the
      # `toBeVisible()` matchers to work. Anything testing visibility or focus
      # requires that the workspaceElement is on the DOM. Tests that attach the
      # workspaceElement to the DOM are generally slower than those off DOM.
      jasmine.attachToDOM(workspaceElement)

      expect(workspaceElement.querySelector('.about-atom')).not.toExist()
      atom.commands.dispatch workspaceElement, 'about:about-atom'

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about-atom')
        expect(aboutElement).toBeVisible()

  describe "when the version number is clicked", ->
    it "copies the version number to the clipboard", ->
      atom.commands.dispatch workspaceElement, 'about:about-atom'

      waitsFor ->
        atom.workspace.getActivePaneItem()

      runs ->
        aboutElement = workspaceElement.querySelector('.about-atom')
        versionContainer = aboutElement.querySelector('.about-version-container')
        $(versionContainer).click()
        expect(atom.clipboard.read()).toBe atom.getVersion()

Add spec for copying version

Add spec for copying version


React = require 'react'

{ Classes } = require 'shiny'

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

    propTypes:
        title   : React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array])
        level   : React.PropTypes.oneOf([1,2,3,4,5,6])
        link    : React.PropTypes.string

    getDefaultProps: -> {
        level: 3
    }
    render: ->
        tag = "h#{ @props.level }"
        cx = new Classes('_Title', @props.className)
        if @props.link
            contents = {@props.title}
            cx.add('-link')
        else
            contents = @props.title
        return React.createElement(tag, className: cx, contents)

React = require 'react'

{ Classes } = require 'shiny'

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

    propTypes:
        title   : React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array])
        level   : React.PropTypes.oneOf([1,2,3,4,5,6])
        link    : React.PropTypes.string

    getDefaultProps: -> {
        level: 3
    }
    render: ->
        tag = "h#{ @props.level }"
        cx = new Classes('_Title', @props.className)
        if @props.link
            contents = {@props.title}
            cx.add('link')
        else
            contents = @props.title
        return React.createElement(tag, className: cx, contents)

Remove extraneous - from variant.

Remove extraneous - from variant.


_ = require 'underscore'
sd = require('sharify').data
ExploreBlocks = require '../../collections/explore_blocks'

@index = (req, res, next) ->
  return next() unless req.user

  res.locals.sd.CURRENT_PATH = "/"
  res.locals.sd.FEED_TYPE = 'primary'
  res.render 'feed', path: 'Feed'

@notifications = (req, res, next) ->
  res.locals.sd.FEED_TYPE = 'notifications'
  res.render 'feed', path: 'Notifications'

@explore = (req, res, next) ->
  blocks = new ExploreBlocks null

  if req.query.subject
    _.extend blocks.options, filter: req.query.subject
    res.locals.sd.SUBJECT = req.query.subject

  if req.query.sort
    res.locals.sd.SORT = sort_by = req.query.sort
    res.locals.sd.SEED = seed = Math.floor(Math.random() * 100000000) + 1
    _.extend blocks.options,
      sort: sort_by
      seed: seed

  blocks.fetch
    error: (m, err) -> next err
    success: ->
      res.locals.sd.BLOCKS = blocks.toJSON()
      res.render "explore", blocks: blocks.models

_ = require 'underscore'
sd = require('sharify').data
ExploreBlocks = require '../../collections/explore_blocks'

@index = (req, res, next) ->
  return next() unless req.user?.id

  res.locals.sd.CURRENT_PATH = "/"
  res.locals.sd.FEED_TYPE = 'primary'
  res.render 'feed', path: 'Feed'

@notifications = (req, res, next) ->
  res.locals.sd.FEED_TYPE = 'notifications'
  res.render 'feed', path: 'Notifications'

@explore = (req, res, next) ->
  blocks = new ExploreBlocks null

  if req.query.subject
    _.extend blocks.options, filter: req.query.subject
    res.locals.sd.SUBJECT = req.query.subject

  if req.query.sort
    res.locals.sd.SORT = sort_by = req.query.sort
    res.locals.sd.SEED = seed = Math.floor(Math.random() * 100000000) + 1
    _.extend blocks.options,
      sort: sort_by
      seed: seed

  blocks.fetch
    error: (m, err) -> next err
    success: ->
      res.locals.sd.BLOCKS = blocks.toJSON()
      res.render "explore", blocks: blocks.models

Check for user id on feed redirect

Check for user id on feed redirect


window.addEventListener 'game.response',
  ({detail: {path, body}}) ->
    if path == '/kcsapi/api_get_member/mapinfo'
      basic = getStore 'info.basic'
      if config.get 'poi.mapStartCheck.ship.enable', false
        minShipSlots = config.get 'poi.mapStartCheck.ship.minFreeSlots', 4
        shipSlots = basic.api_max_chara - Object.keys(getStore('info.ships')).length
        if shipSlots < minShipSlots
          setTimeout =>
            error __ "Attention! Ship Slot has only %s left.", "#{@state.maxChara - @state.shipCount}"
          , 1000
      if config.get 'poi.mapStartCheck.item.enable', false
        minEquipSlots = config.get 'poi.mapStartCheck.item.minFreeSlots', 10
        equipSlots = basic.api_max_slotitem - Object.keys(getStore('info.equips')).length
        if slotsLeft < minFreeItemSlots
          errMsg = __ "Attention! Item Slot is full."
          if slotsLeft > 0
            errMsg = __ "Attention! Only %d free item slot(s) left!", slotsLeft
          setTimeout =>
            error errMsg
          , 1000

__ = i18n.main.__.bind(i18n.main)

window.addEventListener 'game.response',
  ({detail: {path, body}}) ->
    if path == '/kcsapi/api_get_member/mapinfo'
      basic = getStore 'info.basic'
      if config.get 'poi.mapStartCheck.ship.enable', false
        minShipSlots = config.get 'poi.mapStartCheck.ship.minFreeSlots', 4
        shipSlots = basic.api_max_chara - Object.keys(getStore('info.ships')).length
        if shipSlots < minShipSlots
          setTimeout =>
            error __ "Attention! Ship Slot has only %s left.", shipSlots
          , 1000
      if config.get 'poi.mapStartCheck.item.enable', false
        minEquipSlots = config.get 'poi.mapStartCheck.item.minFreeSlots', 10
        equipSlots = basic.api_max_slotitem - Object.keys(getStore('info.equips')).length
        if equipSlots < minEquipSlots
          errMsg = __ "Attention! Item Slot is full."
          if equipSlots > 0
            errMsg = __ "Attention! Only %d free item slot(s) left!", equipSlots
          setTimeout =>
            error errMsg
          , 1000

Fix sortie free slot check

Fix sortie free slot check


_ = require 'underscore'
React = require 'react'
EventEmitter2 = require 'eventemitter2'
Course = require '../course/model'
api = require '../api'

BLANK_USER =
  is_admin: false
  is_content_analyst: false
  is_customer_service: false
  name: null
  profile_url: null

User =

  channel: new EventEmitter2 wildcard: true

  update: (data) ->
    _.extend(this, data.user)
    @courses = _.map data.courses, (course) -> new Course(course)
    @channel.emit('change')

  logout: ->
    _.extend(this, BLANK_USER)
    @channel.emit('change')

  isMemberOfCourse: (collectionUUID) ->
    course = _.findWhere @courses, ecosystem_book_uuid: collectionUUID
    return !!course

  ensureStatusLoaded: ->
    api.channel.emit("user.send.statusUpdate") unless @isLoggedIn()

  isLoggedIn: -> !!@profile_url

api.channel.on 'user.receive.*', ({data}) ->
  User.update(loaded: true)
  if data.access_token
    api.channel.emit('set.access_token', data.access_token)
  User.endpoints = data.endpoints
  if data.user then User.update(data.user) else User.logout()

# start out as a blank user
_.extend(User, BLANK_USER)

module.exports = User

_ = require 'underscore'
React = require 'react'
EventEmitter2 = require 'eventemitter2'
Course = require '../course/model'
api = require '../api'

BLANK_USER =
  is_admin: false
  is_content_analyst: false
  is_customer_service: false
  name: null
  profile_url: null

User =

  channel: new EventEmitter2 wildcard: true

  update: (data) ->
    _.extend(this, data.user)
    @courses = _.map data.courses, (course) -> new Course(course)
    @channel.emit('change')

  logout: ->
    _.extend(this, BLANK_USER)
    @channel.emit('change')

  getCourse: (collectionUUID) ->
    _.findWhere @courses, ecosystem_book_uuid: collectionUUID

  ensureStatusLoaded: ->
    api.channel.emit("user.send.statusUpdate") unless @isLoggedIn()

  isLoggedIn: -> !!@profile_url

api.channel.on 'user.receive.*', ({data}) ->
  User.update(loaded: true)
  if data.access_token
    api.channel.emit('set.access_token', data.access_token)
  User.endpoints = data.endpoints
  if data.user then User.update(data.user) else User.logout()

# start out as a blank user
_.extend(User, BLANK_USER)

module.exports = User

Rename isMemberOfCourse to just getCourse


Sprangular.directive 'addressSelection', ->
  restrict: 'E'
  templateUrl: 'addresses/selection.html'
  scope:
    address: '='
    addresses: '='
    countries: '='
    disabled: '='
  controller: ($scope) ->
    $scope.existingAddress = false

    $scope.$watch 'addresses', (addresses) ->
      return unless addresses

      if addresses.length > 0
        found = _.find addresses, (existing) ->
          existing.same($scope.address)

        $scope.toggleExistingAddress() if found

    $scope.toggleExistingAddress = ->
      $scope.existingAddress = !$scope.existingAddress

      if $scope.existingAddress
        $scope.address = $scope.addresses[0]
      else
        $scope.address = new Sprangular.Address()

  link: (element, attrs) ->
    attrs.disabled = false unless attrs.disabled?

Sprangular.directive 'addressSelection', ->
  restrict: 'E'
  templateUrl: 'addresses/selection.html'
  scope:
    address: '='
    addresses: '='
    countries: '='
    disabled: '='
  controller: ($scope) ->
    $scope.existingAddress = false

    $scope.$watch 'addresses', (addresses) ->
      return unless addresses && addresses.length > 0

      found = _.find addresses, (existing) ->
        existing.same($scope.address)

      $scope.toggleExistingAddress() if found

    $scope.toggleExistingAddress = ->
      $scope.existingAddress = !$scope.existingAddress

      if $scope.existingAddress
        $scope.address = $scope.addresses[0]
      else
        $scope.address = new Sprangular.Address()

  link: (element, attrs) ->
    attrs.disabled = false unless attrs.disabled?

Simplify guarding in address-selection watch


".text.html":
  "polymer element":
    "prefix": "pe"
    "body": """
    <link rel="import" href="${1:../bower_components}/polymer/polymer.html">

    <dom-module id="$2">
      <style>
        :host {
          $5
        }
      </style>
      <template>
        $4
      </template>
      <script>
        Polymer({
          is: '$2',
          $3
        });
      </script>
    </dom-module>
    """
  "polymer element no template":
    "prefix": "pen"
    "body": """
    <link rel="import" href="${1:../bower_components}/polymer/polymer.html">

    <script>
      Polymer({
        is: '$2',
        $3
      });
    </script>
    """
  "polymer element external script":
    "prefix": "pes"
    "body": """
    <link rel="import" href="${1:../bower_components}/polymer/polymer.html">

    <dom-module id="$2">
      <style>
        :host {
          $5
        }
      </style>
      <template>
        $4
      </template>
      <script src="$3">
      </script>
    </dom-module>
    """

Add a snippet that references an external script

Sometimes you just don't want to inline...


TextBuffer = require 'text-buffer'
{Point, Range} = TextBuffer
{File, Directory} = require 'pathwatcher'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
{includeDeprecatedAPIs, deprecate} = require 'grim'

module.exports =
  BufferedNodeProcess: require '../src/buffered-node-process'
  BufferedProcess: require '../src/buffered-process'
  GitRepository: require '../src/git-repository'
  Notification: require '../src/notification'
  TextBuffer: TextBuffer
  Point: Point
  Range: Range
  File: File
  Directory: Directory
  Emitter: Emitter
  Disposable: Disposable
  CompositeDisposable: CompositeDisposable

# The following classes can't be used from a Task handler and should therefore
# only be exported when not running as a child node process
unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
  module.exports.Task = require '../src/task'
  module.exports.TextEditor = require '../src/text-editor'

if includeDeprecatedAPIs
  Object.defineProperty module.exports, 'Git', get: ->
    deprecate "Please require `GitRepository` instead of `Git`: `{GitRepository} = require 'atom'`"
    module.exports.GitRepository

TextBuffer = require 'text-buffer'
{Point, Range} = TextBuffer
{File, Directory} = require 'pathwatcher'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'

module.exports =
  BufferedNodeProcess: require '../src/buffered-node-process'
  BufferedProcess: require '../src/buffered-process'
  GitRepository: require '../src/git-repository'
  Notification: require '../src/notification'
  TextBuffer: TextBuffer
  Point: Point
  Range: Range
  File: File
  Directory: Directory
  Emitter: Emitter
  Disposable: Disposable
  CompositeDisposable: CompositeDisposable

# The following classes can't be used from a Task handler and should therefore
# only be exported when not running as a child node process
unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
  module.exports.Task = require '../src/task'
  module.exports.TextEditor = require '../src/text-editor'

Remove deprecations from Atom exports

:fire: Remove deprecations from Atom exports


noflo = require 'noflo'
Canvas = require('noflo-canvas').canvas

# @runtime noflo-nodejs
# @name BufferToCanvas

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

  c.icon = 'expand'
  c.description = 'Convert a buffer to a canvas'

  c.inPorts.add 'buffer',
    datatype: 'object'
    description: 'An image buffer'
  c.outPorts.add 'canvas',
    datatype: 'object'

  noflo.helpers.WirePattern c,
    in: ['buffer']
    out: ['canvas']
    async: true
    forwardGroups: true
  , (payload, groups, out, callback) ->
    image = new Canvas.Image
    image.src = payload
    # Create a host canvas and draw on it
    canvas = new Canvas(image.width, image.height)
    ctx = canvas.getContext '2d'
    ctx.drawImage image, 0, 0, image.width, image.height
    out.send canvas
    do callback

  c

noflo = require 'noflo'
Canvas = require('noflo-canvas').canvas

# @runtime noflo-nodejs
# @name BufferToCanvas

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

  c.icon = 'expand'
  c.description = 'Convert a buffer to a canvas'

  c.inPorts.add 'buffer',
    datatype: 'object'
    description: 'An image buffer'
  c.outPorts.add 'canvas',
    datatype: 'object'
  c.outPorts.add 'error',
    datatype: 'object'

  noflo.helpers.WirePattern c,
    in: ['buffer']
    out: ['canvas']
    async: true
    forwardGroups: true
  , (payload, groups, out, callback) ->
    image = new Canvas.Image
    image.src = payload
    unless image.width > 0 and image.height > 0
      err = new Error "Failed to convert a buffer to a canvas"
      return callback err
    # Create a host canvas and draw on it
    canvas = new Canvas(image.width, image.height)
    ctx = canvas.getContext '2d'
    ctx.drawImage image, 0, 0, image.width, image.height
    out.send canvas
    do callback

  c

Add error handling in BufferToCanvas

Add error handling in BufferToCanvas


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

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

getCurrentKeyboardLayout = ->
  observer.getCurrentKeyboardLayout()

getCurrentKeyboardLanguage = ->
  observer.getCurrentKeyboardLanguage()

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

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

  ret

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

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

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

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

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

getCurrentKeyboardLayout = ->
  observer.getCurrentKeyboardLayout()

getCurrentKeyboardLanguage = ->
  observer.getCurrentKeyboardLanguage()

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

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

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

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

Use object to dedupe languages

Use object to dedupe languages


path = require 'path'
os = require 'os'
LessCache = require 'less-cache'
{Subscriber} = require 'emissary'

tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'

# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
module.exports =
class LessCompileCache
  Subscriber.includeInto(this)

  @cacheDir: path.join(tmpDir, 'atom-compile-cache', 'less')

  constructor: ({resourcePath, importPaths}) ->
    @lessSearchPaths = [
      path.join(resourcePath, 'static', 'variables')
      path.join(resourcePath, 'static')
    ]

    if importPaths?
      importPaths = importPaths.concat(@lessSearchPaths)
    else
      importPaths = @lessSearchPaths

    @cache = new LessCache
      cacheDir: @constructor.cacheDir
      importPaths: importPaths
      resourcePath: resourcePath
      fallbackDir: path.join(resourcePath, 'less-compile-cache')

  setImportPaths: (importPaths=[]) ->
    @cache.setImportPaths(importPaths.concat(@lessSearchPaths))

  read: (stylesheetPath) -> @cache.readFileSync(stylesheetPath)

  destroy: -> @unsubscribe()

path = require 'path'
fs = require 'fs-plus'
LessCache = require 'less-cache'
{Subscriber} = require 'emissary'

# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
module.exports =
class LessCompileCache
  Subscriber.includeInto(this)

  @cacheDir: path.join(fs.getHomeDirectory(), 'compile-cache', 'less')

  constructor: ({resourcePath, importPaths}) ->
    @lessSearchPaths = [
      path.join(resourcePath, 'static', 'variables')
      path.join(resourcePath, 'static')
    ]

    if importPaths?
      importPaths = importPaths.concat(@lessSearchPaths)
    else
      importPaths = @lessSearchPaths

    @cache = new LessCache
      cacheDir: @constructor.cacheDir
      importPaths: importPaths
      resourcePath: resourcePath
      fallbackDir: path.join(resourcePath, 'less-compile-cache')

  setImportPaths: (importPaths=[]) ->
    @cache.setImportPaths(importPaths.concat(@lessSearchPaths))

  read: (stylesheetPath) -> @cache.readFileSync(stylesheetPath)

  destroy: -> @unsubscribe()

Store less cache in ~/.atom/compile-cache


_          = require 'underscore'
Backbone   = require 'backbone'
Backbone.$ = require 'jquery'
plugin     = require 'plugin'

module.exports = Backbone.View.extend

  template: require './template'

  initialize: ->
    underscoreTest = _.last([0,1,2, 'hi mom!'])
    @render()

  render: ->
    @$el.html @template
      description: 'Starter Gulp + Browserify project to demonstrate some common tasks:'
      tools: [
        'CommonJS bundling and watching'
        'Working with multiple bundles'
        'Factoring out shared dependencies'
        'Live reloading across devices'
        'JS transforms and compiling'
        'CSS preprocessing: node-sass (Lightning fast libsass!)'
        'Image optimization'
        'Non common-js plugins with common-js dependencies'
        'Using modules already bundled with other modules'
      ]

    plugin()

_          = require 'underscore'
Backbone   = require 'backbone'
Backbone.$ = require 'jquery'
plugin     = require 'plugin'

module.exports = Backbone.View.extend

  template: require './template'

  initialize: ->
    underscoreTest = _.last([0,1,2, 'hi mom!'])
    @render()

  render: ->
    @$el.html @template
      description: 'Starter Gulp + Browserify project to demonstrate some common tasks:'
      tools: [
        'CommonJS bundling and watching'
        'Working with multiple bundles'
        'Factoring out shared dependencies'
        'Live reloading across devices'
        'JS transforms and compiling'
        'CSS preprocessing: node-sass (Lightning fast libsass!)'
        'Iconfont generation'
        'Image optimization'
        'Non common-js plugins with common-js dependencies'
        'Using modules already bundled with other modules'
      ]

    plugin()

Add iconfont to list of tools

Add iconfont to list of tools


_ = require 'underscore-plus'
{EventEmitter} = require 'events'

module.exports =
class AutoUpdater
  _.extend @prototype, EventEmitter.prototype

  setFeedUrl: ->
    console.log 'setFeedUrl'

  quitAndInstall: ->
    console.log 'quitAndInstall'

  checkForUpdates: ->
    console.log 'checkForUpdates'

_ = require 'underscore-plus'
path = require 'path'
fs = require 'fs'

{EventEmitter} = require 'events'
{BufferedProcess} = require 'atom'

module.exports =
class AutoUpdater
  _.extend @prototype, EventEmitter.prototype

  setFeedUrl: (url) ->
    @updateUrl = url

  quitAndInstall: ->
    console.log 'quitAndInstall'

  checkForUpdates: ->
    throw new Error("Update URL is not set") unless @updateUrl

    emit 'checking-for-update'

    updateDotExe = path.join(path.dirName(process.execPath), '..', 'update.exe')
    unless fs.existsSync(updateDotExe)
      console.log 'Running developer or Chocolatey version of Atom, skipping'
      emit 'update-not-available'
      return

    args = ['--update', @updateUrl]
    updateJson = ""

    ps = new BufferedProcess
      command: updateDotExe,
      args,
      stdout: (output) -> updateJson += output
      exit: (exitCode) ->
        unless exitCode is 0
          console.log 'Failed to update: ' + exitCode + ' - ' + updateJson
          emit 'update-not-available'
          return

        updateInfo = null
        try
          updateInfo = JSON.parse updateJson
        catch ex
          console.log "Update JSON isn't valid: " + updateJson
          emit 'update-not-available'
          return

        unless updateInfo and updateInfo.releasesToApply.length > 0
          console.log "You're on the latest version!"
          emit 'update-not-available'
          return

        # We don't have separate "check for update" and "download" in Squirrel,
        # we always just download
        emit 'update-available'
        emit 'update-downloaded', {}

Save point before Boss Fight

Save point before Boss Fight


Aether = require '../aether'

describe "Global Scope Exploit Suite", ->
  it 'should intercept "this"', ->
    code = "G=100;var globals=(function(){return this;})();return globals.G;"
    aether = new Aether()
    aether.transpile(code)
    expect(aether.run()).toEqual 100

  it 'should disallow using Function', ->
    code = "Function('')"
    aether = new Aether()
    aether.transpile(code)
    expect(->aether.run()).toThrow

  it 'should disallow Function.__proto__.constructor', ->
    code = "(function(){}).__proto__.constructor('')"
    aether = new Aether()
    aether.transpile(code)
    expect(->aether.run()).toThrow

  it 'should protect builtins', ->
    code = "(function(){}).__proto__.constructor = 100;"
    aether = new Aether()
    aether.transpile(code)
    aether.run()
    expect((->).__proto__.constructor).not.toEqual 100

Aether = require '../aether'

describe "Global Scope Exploit Suite", ->
  it 'should intercept "this"', ->
    code = "G=100;var globals=(function(){return this;})();return globals.G;"
    aether = new Aether()
    aether.transpile(code)
    expect(aether.run()).toEqual 100

  it 'should disallow using Function', ->
    code = "Function('')"
    aether = new Aether()
    aether.transpile(code)
    func = aether.createFunction()
    expect(func).toThrow()

  it 'should disallow Function.__proto__.constructor', ->
    code = "(function(){}).__proto__.constructor('')"
    aether = new Aether()
    aether.transpile(code)
    func = aether.createFunction()
    expect(func).toThrow()

  it 'should protect builtins', ->
    code = "(function(){}).__proto__.constructor = 100;"
    aether = new Aether()
    aether.transpile(code)
    aether.run()
    expect((->).__proto__.constructor).not.toEqual 100

Fix up the sandbox testing functions.

Fix up the sandbox testing functions.


path = require 'path'

chai = require 'chai'
global.should = chai.should()

global.bin = path.join __dirname, '..', '..', 'bin', 'brave-mouse'

Set ‘bin’ to main executable in every test file


allTestFiles = []
TEST_REGEXP = /(spec|test)\.js$/i

Object.keys(window.__karma__.files).forEach (file) ->
  allTestFiles.push(file) if TEST_REGEXP.test(file)

require.config
  #Karma serves files under /base, which is the basePath from your config file
  baseUrl: '/base/src'
  paths: {
    'easyxdm': '../compiled/vendor/easyXDM'
  }
  #dynamically load all test files
  deps: allTestFiles

  # we have to kickoff jasmine, as it is asynchronous
  callback: window.__karma__.start

allTestFiles = []
TEST_REGEXP = /(spec|test)\.js$/i

Object.keys(window.__karma__.files).forEach (file) ->
  allTestFiles.push(file) if TEST_REGEXP.test(file)

window.__requirejs__ =
  loaded_amds: {}
  clearRequireState: ->
    for key, value of window.__requirejs__.loaded_amds
      requirejs.undef key
      delete window.__requirejs__.loaded_amds[key]

require.config
  #Karma serves files under /base, which is the basePath from your config file
  baseUrl: '/base/src'
  paths: {
    'easyxdm': '../compiled/vendor/easyXDM'
  }
  #dynamically load all test files
  deps: allTestFiles

  # we have to kickoff jasmine, as it is asynchronous
  callback: window.__karma__.start

requirejs.onResourceLoad = (context, map, deps) -> window.__requirejs__.loaded_amds[map.id] = true

Create helper to auto-undef requirejs modules

Useful if some module needs to be mocked.


lineman = require(process.env["LINEMAN_MAIN"])

# completely ignore Lineman's built-in application config
config =
  pkg: lineman.grunt.file.readJSON("package.json")

  appTasks:
    common: [ "copy" ]
    dev: []
    dist: []

  loadNpmTasks: [
    "grunt-contrib-copy"
  ]

  clean:
    dev:
      src: "generated"
    dist:
      src: "dist"

  copy:
    manifest:
      src: "<%= files.manifest.source %>"
      dest: "generated/<%= files.manifest.generated %>"
      options:
        processContent: (content, srcpath) ->
          lineman.grunt.template.process content,
            data: config

module.exports = config

lineman = require(process.env["LINEMAN_MAIN"])

# completely ignore Lineman's built-in application config
config =
  pkg: lineman.grunt.file.readJSON("package.json")

  appTasks:
    common: [ "copy" ]
    dev: [ "watch" ]
    dist: []

  loadNpmTasks: [
    "grunt-contrib-copy"
  ]

  clean:
    dev:
      src: "generated"
    dist:
      src: "dist"

  copy:
    manifest:
      src: "<%= files.manifest.source %>"
      dest: "generated/<%= files.manifest.generated %>"
      options:
        processContent: (content, srcpath) ->
          lineman.grunt.template.process content,
            data: config

  watch:
    manifest:
      files: "<%= copy.manifest.src %>"
      tasks: [ "copy:manifest" ]

module.exports = config

Add watcher and watch manifest.json

Add watcher and watch manifest.json


{full: version} = require './lib/version'
TARGET = "eyetribe-#{version}"
isDevelopment = version.indexOf 'SNAPSHOT' > 0

gulp = require 'gulp'
browserify = require 'browserify'
uglify = require 'gulp-uglify'
source = require 'vinyl-source-stream'
concat = require 'gulp-concat'

gulp.task 'default', ['compress']

gulp.task 'browserify', ->
  browserify
    entries: ['./template/entry.coffee']
    extensions: ['.coffee']
    debug: true
  .transform 'coffeeify'
  .bundle()
  .pipe source TARGET + '.js'
  .pipe gulp.dest '.'

gulp.task 'compress', ['browserify'], ->
  gulp.src TARGET + '.js'
  .pipe uglify
    outSourceMap: true
  .pipe concat TARGET + '.min.js'
  .pipe gulp.dest '.'

gulp.task 'watch', ['compress'], ->
  gulp.watch 'lib/*.coffee', ['compress']

gulp.task 'server', ['watch'], ->
  require './server'

{full: version} = require './lib/version'
TARGET = "eyetribe-#{version}"
isDevelopment = version.indexOf 'SNAPSHOT' > 0

gulp = require 'gulp'
browserify = require 'browserify'
uglify = require 'gulp-uglify'
source = require 'vinyl-source-stream'
concat = require 'gulp-concat'

gulp.task 'default', ['compress']

gulp.task 'browserify', ->
  browserify
    entries: ['./template/entry.coffee']
    extensions: ['.coffee']
    debug: true
  .transform 'coffeeify'
  .bundle (error)->
    console.error error if error
  .pipe source TARGET + '.js'
  .pipe gulp.dest '.'
  @

gulp.task 'compress', ['browserify'], ->
  gulp.src TARGET + '.js'
  .pipe uglify
    outSourceMap: true
  .pipe concat TARGET + '.min.js'
  .pipe gulp.dest '.'

gulp.task 'watch', ['compress'], ->
  gulp.watch 'lib/*.coffee', ['compress']

gulp.task 'server', ['watch'], ->
  require './server'

Handle error on browserify to keep watching


CurrentUser = require '../../models/current_user'

module.exports.refresh = (req, res) ->
  return res.redirect("/") unless req.user
  req.user.fetch
    error: res.backboneError
    success: ->
      req.login req.user, (error) ->
        if (error)
          next error
        else
          # Avoid race condition where this returns _before_
          # session is actually saved, by setting something explicitly.
          req.session.userRefresh = new Date()
          res.json req.user.attributes

module.exports.settings = (req, res) ->
  return res.redirect("/log_in?redirect_uri=#{req.url}") unless req.user

  user = new CurrentUser req.user.attributes

  # Fetching here gets all current user properties, as is, req.user
  # only has :public fields. Also note that UserEdit inherits
  # CurrentUser's override of sync to add the access_token data param
  user.fetch
    error: res.backboneError
    success: ->
      res.locals.sd.USER_EDIT = user
      res.render './templates/settings',
        user: user

CurrentUser = require '../../models/current_user'

module.exports.refresh = (req, res, next) ->
  return res.redirect("/") unless req.user
  req.user.fetch
    error: res.backboneError
    success: ->
      req.login req.user, (error) ->
        if (error)
          next error
        else
          # Avoid race condition where this returns _before_
          # session is actually saved, by setting something explicitly.
          req.session.save()
            .then ->
              res.json req.user.attributes
            .catch next

module.exports.settings = (req, res) ->
  return res.redirect("/log_in?redirect_uri=#{req.url}") unless req.user

  user = new CurrentUser req.user.attributes

  # Fetching here gets all current user properties, as is, req.user
  # only has :public fields. Also note that UserEdit inherits
  # CurrentUser's override of sync to add the access_token data param
  user.fetch
    error: res.backboneError
    success: ->
      res.locals.sd.USER_EDIT = user
      res.render './templates/settings',
        user: user

Save session explicitly before returning from user refresh


$          = require 'jquery'
_          = require 'underscore'
Marionette = require 'backbone.marionette'
require './helpers/mixinTemplateHelpers'
require './helpers/handlebarsHelpers'
require '../../jquery/toggleWrapper'

# backup the original method
_remove = Marionette.View::remove

module.exports =

  ###
  Adds a 'blocker' to the view displayed while its entities are synced
  ###
  addOpacityWrapper: (init = true, options = {}) ->
    _.defaults options,
      className: 'opacity'

    @$el.toggleWrapper options, @cid, init

  ###
  View remove

  Overrides the original method, adding a fadeout to the view
  (colored if the view entity has been destroyed)
  ###
  remove: (args...) ->
    # console.log "removing", @
    fadeOutTime = 300

    if @model?.isDestroyed?()

      wrapper = @addOpacityWrapper true,
        backgroundColor: 'red'

      wrapper.fadeOut fadeOutTime, ->
        $(@).remove()

      @$el.fadeOut fadeOutTime, =>
        _remove.apply @, args
    else
      _remove.apply @, args

  ###
  Global template helpers
  ###
  templateHelpers: require './helpers/templateHelpers'

$          = require 'jquery'
_          = require 'underscore'
Marionette = require 'backbone.marionette'
channel    = require '../../utilities/appChannel'
require './helpers/mixinTemplateHelpers'
require './helpers/handlebarsHelpers'
require '../../jquery/toggleWrapper'

# backup the original method
_remove = Marionette.View::remove

module.exports =

  # application global Radio channel
  appChannel: channel

  ###
  Adds a 'blocker' to the view displayed while its entities are synced
  ###
  addOpacityWrapper: (init = true, options = {}) ->
    _.defaults options,
      className: 'opacity'

    @$el.toggleWrapper options, @cid, init

  ###
  View remove

  Overrides the original method, adding a fadeout to the view
  (colored if the view entity has been destroyed)
  ###
  remove: (args...) ->
    # console.log "removing", @
    fadeOutTime = 300

    if @model?.isDestroyed?()

      wrapper = @addOpacityWrapper true,
        backgroundColor: 'red'

      wrapper.fadeOut fadeOutTime, ->
        $(@).remove()

      @$el.fadeOut fadeOutTime, =>
        _remove.apply @, args
    else
      _remove.apply @, args

  ###
  Global template helpers
  ###
  templateHelpers: require './helpers/templateHelpers'

Make the global radio channel available to the views


{$$, SelectListView} = require 'atom'

git = require '../git'
StatusView = require './status-view'

module.exports =
class ListView extends SelectListView
  initialize: (@data) ->
    super
    @addClass 'overlay from-top'
    @parseData()

  parseData: ->
    items = @data.split("\n")
    branches = []
    for item in items
      item = item.replace(/\s/g, '')
      unless item is ''
        branches.push {name: item}
    @setItems branches
    atom.workspaceView.append this
    @focusFilterEditor()

  getFilterKey: -> 'name'

  viewForItem: ({name}) ->
    current = false
    if name.startsWith "*"
      name = name.slice(1)
      current = true
    $$ ->
      @li name, =>
        @div class: 'pull-right', =>
          @span('Current') if current

  confirmed: ({name}) ->
    @checkout name.match(/\*?(.*)/)[1]
    @cancel()

  checkout: (branch) ->
    git.cmd
      args: ['checkout', branch],
      stdout: (data) ->
        new StatusView(type: 'success', message: data.toString())
        atom.project.getRepo()?.refreshStatus()

fs = require 'fs-plus'
{$$, SelectListView} = require 'atom'

git = require '../git'
StatusView = require './status-view'

module.exports =
class ListView extends SelectListView
  initialize: (@data) ->
    super
    @addClass 'overlay from-top'
    @parseData()

  parseData: ->
    items = @data.split("\n")
    branches = []
    for item in items
      item = item.replace(/\s/g, '')
      unless item is ''
        branches.push {name: item}
    @setItems branches
    atom.workspaceView.append this
    @focusFilterEditor()

  getFilterKey: -> 'name'

  viewForItem: ({name}) ->
    current = false
    if name.startsWith "*"
      name = name.slice(1)
      current = true
    $$ ->
      @li name, =>
        @div class: 'pull-right', =>
          @span('Current') if current

  confirmed: ({name}) ->
    @checkout name.match(/\*?(.*)/)[1]
    @cancel()

  checkout: (branch) ->
    git.cmd
      args: ['checkout', branch],
      stdout: (data) ->
        new StatusView(type: 'success', message: data.toString())
        atom.workspace.eachEditor (editor) ->
          fs.exists editor.getPath(), (exist) -> editor.destroy() if not exist
        atom.project.getRepo()?.refreshStatus()

Destroy editors without files after checkout


el = @elvis

class Binding
  constructor: (@model, @attr, @transform) ->

  bindTo: (obj, attr) ->
    @model.on "change:#{@attr}", =>
      el.setAttr(obj, attr, @getValue())

  getValue: ->
    value = @model.get(@attr)
    if @transform then @transform(value) else value

el.bind = (model, attr, transform) ->
  new Binding(model, attr, transform)

el.registerPlugin (element) ->
  if element instanceof Binding
    binding = element
    node = el.text(binding.getValue())
    binding.bindTo(node, 'text')
    return node
  element

el = @elvis

class Binding
  constructor: (@model, @attr, @transform) ->

  bindTo: (obj, attr) ->
    @toObj = obj
    @toAttr = attr
    @model.on("change:#{@attr}", @update, this)
    this

  getValue: ->
    value = @model.get(@attr)
    if @transform then @transform(value) else value

  update: ->
    el.setAttr(@toObj, @toAttr, @getValue())

el.bind = (model, attr, transform) ->
  new Binding(model, attr, transform)

el.registerPlugin (element) ->
  if element instanceof Binding
    node = el.text()
    element.bindTo(node, 'text').update()
    return node
  element

Clean up backbone plugin code

Clean up backbone plugin code


App.UsersNewController = Em.Controller.extend
  needs: ["application"]
  validRoles: ["admin", "member"]

App.UsersNewController = Em.Controller.extend
  needs: ["application"]
  validRoles: ["member", "admin"]

  actions:
    save: ->
      userAttributes = @getProperties(["firstName", "lastName", "role", "password", "email"])

      user = @store.createRecord("user", userAttributes)
      console.log userAttributes
      successCallback = =>
        @transitionToRoute("users.index")
      errorCallback = => console.log("error saving user")
      user.save().then(successCallback, errorCallback)

Save action for new user

Save action for new user


servers = {
  official: 'sync.nitrotasks.com:443'
  local:    'localhost:8080'
  heroku:   'nitro-server.herokuapp.com'
  custom:   '192.168.0.100:8080'
}

# Set active server
active = servers.local

module.exports =
  server: active
  sync:   active + '/socket'
  email:  active + '/email'

servers = {
  official: 'sync.nitrotasks.com:443'
  local:    'localhost:8080'
  heroku:   'nitro-server.herokuapp.com'
  custom:   '192.168.0.100:8080'
}

# Set active server
active = servers.heroku

module.exports =
  server: active
  sync:   active + '/socket'
  email:  active + '/email'

Switch servers to use the heroku beta

Switch servers to use the heroku beta


{View} = require 'atom-space-pen-views'
{CompositeDisposable} = require 'event-kit'

module.exports =
class MinimapSelectionView extends View
  decorations: []
  @content: ->
    @div class: 'minimap-selection'

  initialize: (@minimapView) ->
    @subscriptions = new CompositeDisposable
    @subscriptions.add @minimapView.editor.onDidChangeSelectionRange @handleSelection
    @subscriptions.add @minimapView.editor.onDidAddCursor @handleSelection
    @subscriptions.add @minimapView.editor.onDidChangeCursorPosition @handleSelection
    @subscriptions.add @minimapView.editor.onDidRemoveCursor @handleSelection

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

  destroy: ->
    @detach()
    @subscriptions.dispose()
    @minimapView = null

  handleSelection: =>
    @removeDecorations()

    {editor} = @minimapView

    return if editor.getSelections().length is 1 and editor.getLastSelection().isEmpty()

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

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

{View} = require 'atom-space-pen-views'
{CompositeDisposable} = require 'event-kit'

module.exports =
class MinimapSelectionView extends View
  decorations: []
  @content: ->
    @div class: 'minimap-selection'

  initialize: (@minimapView) ->
    @subscriptions = new CompositeDisposable
    @subscriptions.add @minimapView.editor.onDidChangeSelectionRange @handleSelection
    @subscriptions.add @minimapView.editor.onDidAddCursor @handleSelection
    @subscriptions.add @minimapView.editor.onDidChangeCursorPosition @handleSelection
    @subscriptions.add @minimapView.editor.onDidRemoveCursor @handleSelection

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

  destroy: ->
    @detach()
    @subscriptions.dispose()
    @minimapView = null

  handleSelection: =>
    @removeDecorations()

    {editor} = @minimapView

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

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

:bug: Fix error raised when expanding the number of cursors


class PrivateMessageListItemView extends ActivityListItemView

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

    options.cssClass           = KD.utils.curry 'privatemessage ', options.cssClass
    options.commentViewClass or= ReplyView
    options.commentSettings    = {}
    {commentSettings}          = options
    commentSettings.channelId  = options.channelId
    options.activitySettings   = {}
    {activitySettings}         = options
    activitySettings.disableFollow ?= yes

    super options, data

    @likeView = new ReplyLikeView {}, data

    @decorate()

    @commentBox.listPreviousLink.on 'ReachedToTheBeginning', @bound 'showParentPost'

  decorate: ->

    {repliesCount} = @getData()

    @showParentPost()  if repliesCount < 3

  showParentPost: ->

    @setClass 'with-parent'
    firstReply = @commentBox.controller.getListItems().first

    return  unless firstReply

    if @getData().account._id is firstReply.getData().account._id
      firstReply.setClass 'consequent'

  hideParentPost: -> @unsetClass 'with-parent'

  pistachio: ->
    """

      {{> @avatar}}

        {{> @author}}{{> @timeAgoView}}{{> @likeView}}

      {{> @editWidgetWrapper}}
      {article{@formatContent #(body)}}
      {{> @embedBox}}

    """

class PrivateMessageListItemView extends ActivityListItemView

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

    options.cssClass           = KD.utils.curry 'privatemessage ', options.cssClass
    options.commentViewClass or= ReplyView
    options.commentSettings    = {}
    {commentSettings}          = options
    commentSettings.channelId  = options.channelId
    options.activitySettings   = {}
    {activitySettings}         = options
    activitySettings.disableFollow ?= yes

    super options, data

    @likeView = new ReplyLikeView {}, data

    @decorate()

    @commentBox.listPreviousLink.on 'ReachedToTheBeginning', @bound 'showParentPost'

  decorate: ->

    {repliesCount} = @getData()

    @showParentPost()  if repliesCount < 3

  showParentPost: ->

    @setClass 'with-parent'
    firstReply = @commentBox.controller.getListItems().first

    return  unless firstReply

    if @getData().account._id is firstReply.getData().account._id
      firstReply.setClass 'consequent'

  hideParentPost: -> @unsetClass 'with-parent'

  pistachio: ->
    """

      {{> @settingsButton}}
      {{> @avatar}}

        {{> @author}}{{> @timeAgoView}}{{> @likeView}}

      {{> @editWidgetWrapper}}
      {article{@formatContent #(body)}}
      {{> @embedBox}}

    """

PrivateMessage: Add settings button to Private message list item


# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

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

	$(document).on 'keyup', '#item-text', checkTextChange(this)

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

# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

ready = ->
	parseText = (text) ->
		# parsing markdown to html
		$('#item-preview-content').html(marked(text))
		# highlighting
		$('#item-preview-content').find('pre code').each (i, block) ->
			hljs.highlightBlock(block);

	checkTextChange = (e) ->
		old = v = $(e).find('#item-text').val()
		return ->
			v = $(e).find('#item-text').val()
			if old isnt v
				old = v
				parseText(v)

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

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

Fix parsing markdown at first

Fix parsing markdown at first


# Description:
#   Liona is just so awesome
#
# Commands:
#   hubot <3

whitelist = require '../support/whitelist'

module.exports = (robot) ->
  robot.respond /say to ([a-zA-Z-_\#]+) (.*)/, (msg) ->
    if whitelist.canSay(robot, msg.message.user)
      [room, message] = msg.match[1..2]
      robot.messageRoom room, message
    else
      msg.reply "Denied because you #{msg.message.user.name} are not in the access list."

  robot.respond /emote to ([a-zA-Z-_\#]+) (.*)/, (msg) ->
    if whitelist.canSay(robot, msg.message.user)
      [room, message] = msg.match[1..2]
      # change the msg.message.user.room and msg.message.room to the room we want
      # the emote to go to, as the emote command is made to emote to the place the
      # message was received from
      msg.message.user.room = msg.message.room = room
      msg.emote message
    else
      msg.reply "Denied because you #{msg.message.user.name} are not in the access list."

Add ability to make Liobot emote

Add ability to make Liobot emote


`import Ember from 'ember'`
`import DS from 'ember-data'`
`import { findAll, findById, findByQuery } from '../extensions/finders'`
`import strategyFor from '../extensions/strategy-for'`
`import push from '../extensions/push'`

initialize = (ctn, app) ->
  unless DS.Store::_emberDataComplexPatched is true
    DS.Store.reopen
      _emberDataComplexPatched: true
      push: push
      findAll: findAll
      findById: findById
      findByQuery: findByQuery
      strategyFor: strategyFor

DataComplexInitializer =
  name: 'data-complex'
  before: 'store'
  initialize: initialize

`export {initialize}`
`export default DataComplexInitializer`

`import Ember from 'ember'`
`import DS from 'ember-data'`
`import { findAll, findById, findByQuery } from '../extensions/finders'`
`import strategyFor from '../extensions/strategy-for'`
`import push from '../extensions/push'`

initialize = (ctn, app) ->
  unless DS.Store::_emberDataComplexPatched is true
    DS.Store.reopen
      _emberDataComplexPatched: true
      push: push
      findAll: findAll
      findById: findById
      findByQuery: findByQuery
      strategyFor: strategyFor

DataComplexInitializer =
  name: 'data-complex'
  before: 'ember-data'
  initialize: initialize

`export {initialize}`
`export default DataComplexInitializer`

Use the `ember-data` initializer as a target instead of the soon to be deprecated `store` initializer


class AppyApp.Screens.ReadySetGo extends AppyApp.Screens.Base

    render: ->

Hello bright shiny world

Served by the AppyApp extension’s ReadySetGo screen

class AppyApp.Screens.ReadySetGo extends AppyApp.Screens.Base

    render: ->
        <LC.ScreenWrapper identifier="appy-app">

Hello bright shiny world

Served by the AppyApp extension’s ReadySetGo screen

        </LC.ScreenWrapper>

Adjust fixture to match current generated content


Subject = require 'zooniverse/models/subject'

module.exports = ->
  new Subject
    location: standard: './subjects/standard/514092d13ae74064ba00000e.jpg'

    coords: [0, 0]

    metadata:
      tutorial: true
      file_name: 'TUTORIAL_SUBJECT'

    workflow_ids: ['514092d13ae74064ba000002']

Add workflow ID to tutorial subject

Add workflow ID to tutorial subject


fs = require 'fs'
path = require 'path'
helpers = require './helpers'
compiler = require './compiler'

exports.compileModule = compile = (source, options) ->
  result = compiler.compileModule source
  if result.request
    console.error options
    console.error result
  else
    result.js

# Compile and execute a string of CoffeeScript (on the server), correctly
# setting `__filename`, `__dirname`, and relative `require()`.
exports.run = (code, options = {}) ->
  mainModule = require.main

  # Set the filename.
  mainModule.filename = process.argv[1] =
    if options.filename then fs.realpathSync(options.filename) else '.'

  # Clear the module cache.
  mainModule.moduleCache and= {}

  # Assign paths for node_modules loading
  dir = if options.filename
    path.dirname fs.realpathSync options.filename
  else
    fs.realpathSync '.'
  mainModule.paths = require('module')._nodeModulePaths dir

  # Compile.
  if not helpers.isCoffee(mainModule.filename) or require.extensions
    answer = compile code, options
    code = compiler.library + compiler.immutable + (answer.js ? answer)

  mainModule._compile code, mainModule.filename

fs = require 'fs'
path = require 'path'
helpers = require './helpers'
compiler = require './compiler'

exports.compileModule = compile = (source, options) ->
  result = compiler.compileModule source
  if result.request
    console.error options
    console.error result
  else if result.errors
    throw new Error result.errors[0].message
  else
    result.js

# Compile and execute a string of CoffeeScript (on the server), correctly
# setting `__filename`, `__dirname`, and relative `require()`.
exports.run = (code, options = {}) ->
  mainModule = require.main

  # Set the filename.
  mainModule.filename = process.argv[1] =
    if options.filename then fs.realpathSync(options.filename) else '.'

  # Clear the module cache.
  mainModule.moduleCache and= {}

  # Assign paths for node_modules loading
  dir = if options.filename
    path.dirname fs.realpathSync options.filename
  else
    fs.realpathSync '.'
  mainModule.paths = require('module')._nodeModulePaths dir

  # Compile.
  if not helpers.isCoffee(mainModule.filename) or require.extensions
    answer = compile code, options
    code = compiler.library + compiler.immutable + (answer.js ? answer)

  mainModule._compile code, mainModule.filename

Throw on type error during CLI run

Throw on type error during CLI run


window.Tahi = {}
Tahi.papers =
  init: ->
    $('#add_author').on 'click', (e) ->
      e.preventDefault()
      $('<li class="author">').appendTo $('ul.authors')

$(document).ready ->
  if $('[contenteditable!=false]').length > 0
    for elementId in ['body_editable', 'abstract_editable']
      CKEDITOR.inline elementId,
        extraPlugins: 'sharedspace'
        removePlugins: 'floatingspace,resize'
        sharedSpaces:
          top: 'ckeditor-toolbar'
        toolbarGroups: [
          { name: 'styles' }
          { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }
          { name: 'clipboard',   groups: [ 'clipboard', 'undo' ] }
          { name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] }
          { name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] }
          { name: 'links' }
          { name: 'tools' }
        ]

  $('#save_button').on 'click', (e) ->
    e.preventDefault()
    $.ajax
      url: $(this).data('url') + '.json'
      method: "PUT"
      data:
        paper:
          title: $.trim($('#title_editable').text())
          body: CKEDITOR.instances.body_editable.getData()
          abstract: CKEDITOR.instances.abstract_editable.getData()
          short_title: $.trim($('#short_title_editable').text())
      success:
        window.location = "/"
  Tahi.papers.init()

window.Tahi = {}
Tahi.papers =
  init: ->
    $('#add_author').on 'click', (e) ->
      e.preventDefault()
      $('<li class="author">').appendTo $('ul.authors')

$(document).ready ->
  if $('[contenteditable!=false]').length > 0
    for elementId in ['body_editable', 'abstract_editable']
      CKEDITOR.inline elementId,
        extraPlugins: 'sharedspace'
        removePlugins: 'floatingspace,resize'
        sharedSpaces:
          top: 'ckeditor-toolbar'
        toolbar: [
          [ 'Styles', 'Format', 'FontSize' ]
          [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ]
          [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', 'Blockquote', 'CreateDiv']
          [ 'PasteFromWord' ],
          [ 'Link', 'Unlink', 'Anchor']
          [ 'Find', 'Replace', '-', 'Scayt', '-', 'ShowBlocks' ]
        ]

  $('#save_button').on 'click', (e) ->
    e.preventDefault()
    $.ajax
      url: $(this).data('url') + '.json'
      method: "PUT"
      data:
        paper:
          title: $.trim($('#title_editable').text())
          body: CKEDITOR.instances.body_editable.getData()
          abstract: CKEDITOR.instances.abstract_editable.getData()
          short_title: $.trim($('#short_title_editable').text())
      success:
        window.location = "/"
  Tahi.papers.init()

Remove unneeded tools from the toolbar

Remove unneeded tools from the toolbar


_ = require 'underscore-plus'
{EditorView, View} = require 'atom'
MarkerView = require './marker-view'

module.exports =
class FindResultsView extends View

  @content: ->
    @div class: 'search-results'

  initialize: (@findModel) ->
    @markerViews = {}
    @subscribe @findModel, 'updated', (args...) => @markersUpdated(args...)

  attach: ->
    @getEditor()?.underlayer.append(this)

  detach: ->
    super

  beforeRemove: ->
    @destroyAllViews()

  getEditor: ->
    activeView = atom.workspaceView.getActiveView()
    if activeView instanceof EditorView then activeView else null

  markersUpdated: (markers) ->
    editor = @getEditor()

    if not editor?
      @destroyAllViews()
    else
      markerViewsToRemoveById = _.clone(@markerViews)
      for marker in markers
        if @markerViews[marker.id]
          delete markerViewsToRemoveById[marker.id]
        else
          markerView = new MarkerView({editor, marker})
          @append(markerView.element)
          @markerViews[marker.id] = markerView

      for id, markerView of markerViewsToRemoveById
        delete @markerViews[id]
        markerView.remove()

      editor.requestDisplayUpdate()

  destroyAllViews: ->
    @empty()
    @markerViews = {}

_ = require 'underscore-plus'
{EditorView, View} = require 'atom'
MarkerView = require './marker-view'

module.exports =
class FindResultsView extends View

  @content: ->
    @div class: 'search-results'

  initialize: (@findModel) ->
    @markerViews = {}
    @subscribe @findModel, 'updated', (args...) => @markersUpdated(args...)

  attach: ->
    @getEditor()?.underlayer.append(this)

  detach: ->
    super

  beforeRemove: ->
    @destroyAllViews()

  getEditor: ->
    activeView = atom.workspaceView.getActiveView()
    if activeView?.hasClass('editor') then activeView else null

  markersUpdated: (markers) ->
    editor = @getEditor()

    if not editor?
      @destroyAllViews()
    else
      markerViewsToRemoveById = _.clone(@markerViews)
      for marker in markers
        if @markerViews[marker.id]
          delete markerViewsToRemoveById[marker.id]
        else
          markerView = new MarkerView({editor, marker})
          @append(markerView.element)
          @markerViews[marker.id] = markerView

      for id, markerView of markerViewsToRemoveById
        delete @markerViews[id]
        markerView.remove()

      editor.requestDisplayUpdate()

  destroyAllViews: ->
    @empty()
    @markerViews = {}

Use class instead of instanceof to check if the active view is an editor

This supports the ReactEditorView in addition to the legacy EditorView.


###
Nesh Configuration. Implements a basic configuration system
which can load and save state via JSON. By default this data
is stored in ~/.nesh_config.json, but a custom path can be
passed to the load and save functions.
###
fs = require 'fs'
log = require './log'
path = require 'path'

_config = {}

config = exports

# A platform-agnostic way of getting the user's home directory
config.home = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE

# The default path to the configuration file
config.path = path.join config.home, '.nesh_config.json'

# Reset to a blank configuration
config.reset = ->
    _config = {}

# Load a configuration file. This should be called once before
# loading plugins so that plugins have access to the config.
# Calling it again will reload the configuration.
config.load = (path = config.path) ->
    if fs.existsSync path
        # Try to load the config file
        log.debug "Loading config from #{path}"
        try
            _config = require path
        catch e
            throw "Error loading Nesh config from #{path}: #{e}"
    else
        log.debug "No config found at #{path}"

# Save the configuration to a file.
config.save = (path = config.path) ->
    fs.writeFileSync path, JSON.stringify(_config)

# Get the configuration object
config.get = ->
    _config

###
Nesh Configuration. Implements a basic configuration system
which can load and save state via JSON. By default this data
is stored in ~/.nesh_config.json, but a custom path can be
passed to the load and save functions.
###
fs = require 'fs'
log = require './log'
path = require 'path'

_config = {}

config = exports

# A platform-agnostic way of getting the user's home directory (Unix-like platforms: $HOME; Winwdows: %USERPROFILE%).
config.home = process.env.HOME or process.env.USERPROFILE

# The default path to the configuration file
config.path = path.join config.home, '.nesh_config.json'

# Reset to a blank configuration
config.reset = ->
    _config = {}

# Load a configuration file. This should be called once before
# loading plugins so that plugins have access to the config.
# Calling it again will reload the configuration.
config.load = (path = config.path) ->
    if fs.existsSync path
        # Try to load the config file
        log.debug "Loading config from #{path}"
        try
            _config = require path
        catch e
            throw "Error loading Nesh config from #{path}: #{e}"
    else
        log.debug "No config found at #{path}"

# Save the configuration to a file.
config.save = (path = config.path) ->
    fs.writeFileSync path, JSON.stringify(_config)

# Get the configuration object
config.get = ->
    _config

Use just `process.env.USERPROFILE` to determine a user's home folder on Windows

Removed reference to %HOMEPATH%, because it doesn't contain a drive
spec. (e.g., it contains only \Users\Jdoe rather than C:\Users\Jdoe),
whereas %USERPROFILE% does include the drive spec., which makes it the
better choice.


exports.versions =
  '7_1': 0x71000001
  '7_2': 0x72090002
  '7_3_A': 0x730A0003
  '7_3_B': 0x730B0003
  '7_4': 0x74000004

exports.versionsByValue = {}
for name, value of exports.versions
  exports.versionsByValue[value] = name

exports.versions =
  '7_2': 0x72090002
  '7_3_A': 0x730A0003
  '7_3_B': 0x730B0003
  '7_4': 0x74000004

exports.versionsByValue = {}
for name, value of exports.versions
  exports.versionsByValue[value] = name

Remove TDS 7.1 from supported versions

Remove TDS 7.1 from supported versions


angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, OrderCycle, ocInstance) ->
  $controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})

  $scope.view = 'incoming'

  $scope.exchangeTotalVariants = (exchange) ->
    return unless this.enterprises? && this.enterprises[exchange.enterprise_id]?

    enterprise = this.enterprises[exchange.enterprise_id]
    return enterprise.numVariants if enterprise.numVariants?

    $scope.loadExchangeProducts(this, exchange)
    return unless enterprise.supplied_products?

    enterprise.numVariants = $scope.countVariants(enterprise.supplied_products)

  $scope.countVariants = (products) ->
    return 0 unless products

    numVariants = 0
    for product in products
      numVariants += product.variants.length
    numVariants

  $scope.addSupplier = ($event) ->
    $event.preventDefault()
    OrderCycle.addSupplier $scope.new_supplier_id, $scope.exchangeListChanged

angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, OrderCycle, ocInstance) ->
  $controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})

  $scope.view = 'incoming'

  $scope.exchangeTotalVariants = (exchange) ->
    return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?

    enterprise = $scope.enterprises[exchange.enterprise_id]
    return enterprise.numVariants if enterprise.numVariants?

    $scope.loadExchangeProducts($scope, exchange)
    return unless enterprise.supplied_products?

    enterprise.numVariants = $scope.countVariants(enterprise.supplied_products)

  $scope.countVariants = (products) ->
    return 0 unless products

    numVariants = 0
    for product in products
      numVariants += product.variants.length
    numVariants

  $scope.addSupplier = ($event) ->
    $event.preventDefault()
    OrderCycle.addSupplier $scope.new_supplier_id, $scope.exchangeListChanged

Replace the use of this with

Replace the use of this with


_ = require 'underscore-plus'

module.exports =
class CSVEditor
  constructor: (@config={}) ->

  get: (path, config) ->
    if config? then @config[path]?[config] else @config[path]

  set: (path, config, value) ->
    @config[path] ?= {}
    @config[path][config] = value

  move: (oldPath, newPath) ->
    @config[newPath] = @config[oldPath]
    delete @config[oldPath]

  serialize: ->
    _.clone(@config)

_ = require 'underscore-plus'

module.exports =
class CSVConfig
  constructor: (@config={}) ->

  get: (path, config) ->
    if config? then @config[path]?[config] else @config[path]

  set: (path, config, value) ->
    @config[path] ?= {}
    @config[path][config] = value

  move: (oldPath, newPath) ->
    @config[newPath] = @config[oldPath]
    delete @config[oldPath]

  serialize: ->
    _.clone(@config)

Fix typo in csv config class name

:art: Fix typo in csv config class name


assert  = require 'assert'
leveldb = require '../lib'

describe 'db', ->
  db = new leveldb.DB
  path = "#{__dirname}/../tmp/db-test-file"
  key = "Hello"
  value = "World"

  it 'should open database', (done) ->
    db.open path, { create_if_missing: true, paranoid_checks: true }, done

  it 'should put key/value pair', (done) ->
    db.put key, value, done

  it 'should get key/value pair', (done) ->
    db.get key, (err, result) ->
      assert.ifError err
      assert.equal result, value
      done()

  it 'should delete key', (done) ->
    db.del key, done

  it 'should not get key/value pair', (done) ->
    db.get key, (err, result) ->
      assert.ifError err
      assert.equal result, undefined

  it 'should close database', (done) ->
    db.close done

assert  = require 'assert'
leveldb = require '../lib'

describe 'db', ->
  db = new leveldb.DB
  path = "#{__dirname}/../tmp/db-test-file"
  key = "Hello"
  value = "World"

  it 'should open database', (done) ->
    db.open path, { create_if_missing: true, paranoid_checks: true }, done

  it 'should put key/value pair', (done) ->
    db.put key, value, done

  it 'should get key/value pair', (done) ->
    db.get key, (err, result) ->
      assert.ifError err
      assert.equal result, value
      done()

  it 'should delete key', (done) ->
    db.del key, done

  it 'should not get key/value pair', (done) ->
    db.get key, (err, result) ->
      assert.ifError err
      assert.equal result, undefined
      done()

  it 'should close database', (done) ->
    db.close done

Call done() in leveled async test

Call done() in leveled async test


React = require 'react'
BS = require 'react-bootstrap'
Router = require 'react-router'
{CurrentUserStore} = require '../../flux/current-user'
{TransitionAssistant} = require '../unsaved-state'

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

  contextTypes:
    router: React.PropTypes.func

  redirectToAccount: ->
    window.open(CurrentUserStore.getProfileUrl(), 'account-profile')

  render: ->
    return null unless CurrentUserStore.getProfileUrl()
    <BS.MenuItem onSelect={@redirectToAccount} >
      My Account
    </BS.MenuItem>

React = require 'react'
BS = require 'react-bootstrap'

{CurrentUserStore} = require '../../flux/current-user'

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

  render: ->
    return null unless CurrentUserStore.getProfileUrl()

      My Account

Use a link with target=_blank for account profile


# Description:
#   None
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   hubot achievement get <achievement> [achiever's gravatar email] - life goals are in reach
#
# Author:
#   Chris

module.exports = (robot) ->
  robot.hear /achievement (get|unlock(ed)?) (.+?)(\s*[^@\s]+@[^@\s]+)?\s*$/i, (msg) ->
    caption = msg.match[3]
    email = msg.match[4] || msg.message.user.email_address
    url = "http://achievement-unlocked.heroku.com/xbox/#{escape(caption)}.png"
    if email
      url += "?email=#{escape(email.trim())}.png"
    msg.send(url)

  robot.hear /achievement (get|unlock(ed)?)/i, (msg) ->
    url = "http://achievement-unlocked.heroku.com/xbox/#{escape("Bane of Daniel Webster")}.png"
    msg.send(url)

Remove automatic Daniel Webster achievement

Looks like test code accidentally left in


class TentStatus.FetchPool extends TentStatus.Paginator
  constructor: (@collection, @options = {}) ->
    super
    @sinceId = @options.sinceId

  paramsForOffsetAndLimit: (sinceId, limit) =>
    params = { limit: limit }
    params.since_id = sinceId if sinceId
    params

  fetch: (options = {}) =>
    _options = {
      success: (items) =>
        @sinceId = items.last()?.get('id') || @sinceId
        @options.success?()
        @trigger 'fetch:success'
    }
    options = _.extend _options, options
    super(options)

class TentStatus.FetchPool extends TentStatus.Paginator
  constructor: (@collection, @options = {}) ->
    super
    @sinceId = @options.sinceId

  paramsForOffsetAndLimit: (sinceId, limit) =>
    params = { limit: limit }
    params.since_id = sinceId if sinceId
    params

  fetch: (options = {}) =>
    _options = {
      success: (items) =>
        newItems = []
        existingItems = TentStatus.Collections.posts.toArray()
        for i in items
          newItems.push i unless existingItems.indexOf(i)

        if newItems.length
          @sinceId = items.last()?.get('id') || @sinceId
          @options.success?()
          @trigger 'fetch:success'
    }
    options = _.extend _options, options
    super(options)

Fix deleted post triggering new post notification


should = require 'should'
restler = require 'restler'

setup = require './setup'

describe 'with no services or routes wired up', () ->
  beforeEach (done) ->
    {@app, @server, @url} = setup()
    done()

  afterEach (done) ->
    @server.close()
    done()

  it 'should return with a 501 status', (done) ->
    req = restler.get @url
    should.exist req

    req.on 'complete', (result, response) ->
      should.exist result, 'expected result to exist'
      should.exist response, 'expected response to exist'
      should.exist response.statusCode, 'expected statusCode to exist'

      response.statusCode.should.equal 501

      done()

should = require 'should'
restler = require 'restler'

setup = require './setup'

describe 'with no services or routes wired up', () ->
  beforeEach (done) ->
    {@app, @server, @url} = setup()
    done()

  afterEach (done) ->
    @server.close()
    done()

  it 'should return with a 501 status', (done) ->
    req = restler.get @url
    should.exist req

    req.once 'complete', (result, response) ->
      should.exist result, 'expected result to exist'
      should.exist response, 'expected response to exist'
      should.exist response.statusCode, 'expected statusCode to exist'

      response.statusCode.should.equal 501

      done()

Fix adapter code to handle unresolved services, errors


angular.module('loomioApp').directive 'lmoHref', ($window, $router, LmoUrlService) ->
  restrict: 'A'
  scope:
    path: '@lmoHref'
    model: '=lmoHrefModel'
  link: (scope, elem, attrs) ->
    route = LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
    elem.attr 'href', ''
    elem.bind 'click', ($event) ->
      if $event.metaKey
        $window.open route, '_blank'
      else
        $router.navigate route

angular.module('loomioApp').directive 'lmoHref', ($window, $router, LmoUrlService) ->
  restrict: 'A'
  scope:
    path: '@lmoHref'
    model: '=lmoHrefModel'
  link: (scope, elem, attrs) ->
    route = LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
    elem.attr 'href', ''
    elem.bind 'click', ($event) ->
      if $event.metaKey or $event.ctrlKey
        $window.open route, '_blank'
      else
        $router.navigate route

Add support for ctrl key

Add support for ctrl key


"*":
  autosave:
    enabled: true
  core:
    audioBeep: false
    closeDeletedFileTabs: true
    excludeVcsIgnoredPaths: false
    packagesWithKeymapsDisabled: []
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "one-light-syntax"
    ]
  "custom-title":
    template: "<%= fileName %><% if (projectPath) { %> - <%= projectPath %><% if (gitHead) { %> [<%= gitHead %>]<% } %><% } %>"
  editor:
    backUpBeforeSaving: true
    fontSize: 26
    invisibles: {}
    preferredLineLength: 120
    scrollPastEnd: true
    showIndentGuide: true
    softWrap: true
    softWrapAtPreferredLineLength: true
    zoomFontWhenCtrlScrolling: true
  "exception-reporting":
    userId: "23726a94-4d58-6922-8de3-2676f1bd97ca"
  pigments:
    extendAutocompleteToColorValue: true
    extendAutocompleteToVariables: true
  "release-notes":
    viewedVersion: "0.94.0"
  "tree-view":
    hideVcsIgnoredFiles: true
    squashDirectoryNames: true
  "tree-view-open-files": {}
  welcome:
    showOnStartup: false
  wordcount:
    alwaysOn: true
    noextension: true
".css.source.stylus":
  editor:
    preferredLineLength: 120

"*":
  autosave:
    enabled: true
  core:
    audioBeep: false
    closeDeletedFileTabs: true
    excludeVcsIgnoredPaths: false
    packagesWithKeymapsDisabled: []
    telemetryConsent: "limited"
    themes: [
      "one-light-ui"
      "one-light-syntax"
    ]
  "custom-title":
    template: "<%= fileName %><% if (projectPath) { %> - <%= projectPath %><% if (gitHead) { %> [<%= gitHead %>]<% } %><% } %>"
  editor:
    backUpBeforeSaving: true
    fontSize: 19
    invisibles: {}
    preferredLineLength: 120
    scrollPastEnd: true
    showIndentGuide: true
    softWrap: true
    softWrapAtPreferredLineLength: true
    zoomFontWhenCtrlScrolling: true
  "exception-reporting":
    userId: "23726a94-4d58-6922-8de3-2676f1bd97ca"
  pigments:
    extendAutocompleteToColorValue: true
    extendAutocompleteToVariables: true
  "release-notes":
    viewedVersion: "0.94.0"
  "tree-view":
    hideVcsIgnoredFiles: true
    squashDirectoryNames: true
  "tree-view-open-files": {}
  welcome:
    showOnStartup: false
  wordcount:
    alwaysOn: true
    noextension: true
".css.source.stylus":
  editor:
    preferredLineLength: 120

Adjust font size of editor

Adjust font size of editor


class IDE.PreviewPane extends IDE.Pane

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

    options.cssClass = KD.utils.curry 'preview-pane', options.cssClass
    options.paneType = 'preview'

    super options, data

    viewerOptions =
      delegate    : this
      params      :
        path      : @getOptions().url

    delete viewerOptions.params.path  unless viewerOptions.params.path

    @addSubView @previewer = new PreviewerView viewerOptions

    @previewer.on 'ViewerLocationChanged', (newLocation) =>
      @setOption 'url', newLocation
      @emit 'LocationChanged', newLocation

Update preview pane url and emit location changed event.


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

  $("a.popover").click (event) ->
    event.preventDefault()
    popover_src = $(this).children("img").attr("data-src")
    popover_caption = $(this).children("img").attr("alt")
    popover_width = $(this).children("img").attr("data-width")
    popover_height = $(this).children("img").attr("data-height")
    $("div#popover img").attr "src", popover_src
    $("div#popover img").attr "alt", popover_caption
    $("div#popover img").attr "width", popover_width
    $("div#popover img").attr "height", popover_height
    $("div#popover").fadeIn "fast"

  $("div#popover button").click ->
    event.preventDefault()
    $("div#popover").fadeOut "fast"

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

  $("a.popover").click (event) ->
    event.preventDefault()
    popover_src = $(this).children("img").attr("data-src")
    popover_caption = $(this).children("img").attr("alt")
    popover_width = $(this).children("img").attr("data-width")
    popover_height = $(this).children("img").attr("data-height")
    $("div#popover img").attr "src", popover_src
    $("div#popover img").attr "alt", popover_caption
    $("div#popover img").attr "width", popover_width
    $("div#popover img").attr "height", popover_height
    $("div#popover").fadeIn "fast"

  $("div#popover button").click (event) ->
    event.preventDefault()
    $("div#popover").fadeOut "fast"

Add event parameter to popover close button click


$(".js-bucket-list").sortable({
  connectWith: ".js-bucket-list"
  update: (event, ui) ->
    if this == ui.item.parent()[0]
      window.target    = $(event.target)
      bucket_id = target.data("bucket-id")
      position  = ui.item.index()

      console.log bucket_id, position

      $.ajax
        type: "POST"
        url: ui.item.data("prioritized-issue-path")
        dataType: "json"
        data: { prioritized_issue: { bucket_id: bucket_id, row_order_position: position } }
}).disableSelection();

$ ->
  $(".js-bucket-list").sortable({
    connectWith: ".js-bucket-list"
    update: (event, ui) ->
      if this == ui.item.parent()[0]
        window.target    = $(event.target)
        bucket_id = target.data("bucket-id")
        position  = ui.item.index()

        $.ajax
          type: "POST"
          url: ui.item.data("prioritized-issue-path")
          dataType: "json"
          data: { prioritized_issue: { bucket_id: bucket_id, row_order_position: position } }
  }).disableSelection();

Remove console log and wrap in document start


CurrentUser = require '../../../../models/current_user.coffee'
{ Following, FollowButton } = require '../../../../components/follow_button/index.coffee'

module.exports = (artists) ->
  user = CurrentUser.orNull()
  following = new Following if user

  ids = artists.map (artist) ->
    $el = $(".artist-follow[data-id='#{artist.id}']")

    new FollowButton
      el: $el
      following: following
      modelName: 'artist'
      model: artist
      href: "#{artist.href}/follow"
      context_page: "Show page"

    artist.id

  following.syncFollows ids if user

Backbone = require 'backbone'
CurrentUser = require '../../../../models/current_user.coffee'
{ Following, FollowButton } = require '../../../../components/follow_button/index.coffee'

module.exports = (artists) ->
  user = CurrentUser.orNull()
  following = new Following if user

  ids = artists.map (artist) ->
    $el = $(".artist-follow[data-id='#{artist.id}']")

    new FollowButton
      el: $el
      following: following
      modelName: 'artist'
      model: new Backbone.Model artist
      href: "#{artist.href}/follow"
      context_page: "Show page"

    artist.id

  following.syncFollows ids if user

Fix follow on show page

Fix follow on show page


describe 'acceptance', ->
  Given -> @subject = require '../lib/kindly'

  context 'sync', ->
    When -> @files = @subject.get('test/fixtures')
    Then -> expect(@files).to.deep.equal
      files: [ 'test/fixtures/baz.quux' , 'test/fixtures/foo.bar']
      directories: []
      other: []

  context 'async', ->
    When (done) -> @subject.get 'test/fixtures', (err, @files) => done()
    Then -> expect(@files).to.deep.equal
      files: [ 'test/fixtures/baz.quux' , 'test/fixtures/foo.bar']
      directories: []
      other: []

describe 'acceptance', ->
  Given -> @subject = require '../lib/kindly'

  context 'sync', ->
    When -> @descriptors = @subject.get('test/fixtures')
    Then -> expect(@descriptors.files).to.contain 'test/fixtures/baz.quux'
    And -> expect(@descriptors.files).to.contain 'test/fixtures/foo.bar'

  context 'async', ->
    When (done) -> @subject.get 'test/fixtures', (err, @descriptors) => done()
    Then -> expect(@descriptors.files).to.contain 'test/fixtures/baz.quux'
    And -> expect(@descriptors.files).to.contain 'test/fixtures/foo.bar'

Order matters on arrays, unfortunately

Order matters on arrays, unfortunately


runEvaluation = ->
  $('#result').text("Loading...")
  $.ajax {
    "url": "/jsontest",
    "type": "POST",
    "data": JSON.stringify({
      "language": $('#eval-language').val(),
      "code": $('textarea').val()
    }),
    "dataType": "json",
    "contentType": "application/json; charset=utf-8",
    "success": (data) =>
      $('#result').text(JSON.stringify(data, null, 4))
    "error": (jqXHR, textStatus, errorThrown) =>
      $('#result').text("[" + textStatus + "] " + errorThrown)
  }

$ ->
  $('textarea').keydown (event) =>
    if (event.keyCode == 10 or event.keyCode == 13) and event.ctrlKey
      runEvaluation()

  $('#eval-submit').click (event) =>
    runEvaluation()

runEvaluation = ->
  $('#result').text("Loading...")
  $.ajax {
    "url": "/api/evaluate",
    "type": "POST",
    "data": JSON.stringify({
      "language": $('#eval-language').val(),
      "code": $('textarea').val()
    }),
    "dataType": "json",
    "contentType": "application/json; charset=utf-8",
    "success": (data) =>
      $('#result').text(JSON.stringify(data, null, 4))
    "error": (jqXHR, textStatus, errorThrown) =>
      $('#result').text("[" + textStatus + "] " + errorThrown)
  }

$ ->
  $('textarea').keydown (event) =>
    if (event.keyCode == 10 or event.keyCode == 13) and event.ctrlKey
      runEvaluation()

  $('#eval-submit').click (event) =>
    runEvaluation()

Make /try hit the new endpoint.

Make /try hit the new endpoint.


#= require almond

window.ttm ||= {}

ttm.ClassMixer = (klass)->
  klass.build = ->
    it = new klass
    it.initialize && it.initialize.apply(it, arguments)
    it
  klass.prototype.klass = klass
  klass

define "lib/class_mixer", ->
  return ttm.ClassMixer

#= require almond

window.ttm ||= {}
window.decorators ||= {}

ttm.ClassMixer = (klass)->
  klass.build = ->
    it = new klass
    it.initialize && it.initialize.apply(it, arguments)
    it
  klass.prototype.klass = klass
  klass

define "lib/class_mixer", ->
  return ttm.ClassMixer

WIP: Implement decorator pattern for student dashboard.


# A collection of common, simple text response
# commands that don’t require data from elsewhere
#
# Command(s):
#   hubot schedule    — Return the current stream schedule
#   hubot social      — Links to Twitter and Facebook
#   hubot commands    - Shows a current list of commands
#   hubot bot         - A quick bot about/introduction

module.exports = (robot) ->
    robot.respond /schedule$/i, (msg) ->
        msg.send "Our current schedule is Tuesday, Thursday, Saturday 10pm-1am GMT and Sunday 10pm-12am GMT."

    robot.respond /social$/i, (msg) ->
        msg.send "You can find me on Twitter at http://twitter.com/masonest_ and on Facebook at http://facebook.com/masonest"

    robot.respond /commands$/i, (msg) ->
        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
            msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet, !bot and !shoutout."
            return

        msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet and !bot."

    robot.respond /bot$/i, (msg) ->
        msg.send "I’m Awebot. I am Masonest’s custom bot and was built by him. I’m still young and learning new things all the time."

# A collection of common, simple text response
# commands that don’t require data from elsewhere
#
# Command(s):
#   hubot schedule    — Return the current stream schedule
#   hubot social      — Links to Twitter and Facebook
#   hubot commands    - Shows a current list of commands
#   hubot bot         - A quick bot about/introduction

module.exports = (robot) ->
    robot.respond /schedule$/i, (msg) ->
        msg.send "Our current schedule is Tuesday, Thursday, Saturday 10pm-1am GMT and Sunday 10pm-12am GMT."

    robot.respond /social$/i, (msg) ->
        msg.send "You can find me on Twitter at http://twitter.com/masonest_ and on Facebook at http://facebook.com/masonest"

    robot.respond /commands$/i, (msg) ->
        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
            msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet, !bot, !points and !shoutout."
            return

        msg.send "Hey, #{msg.envelope.user.name}. Here's the commands that are available to you: !commands, !schedule, !social, !ctt/!tweet, !points and !bot."

    robot.respond /bot$/i, (msg) ->
        msg.send "I’m Awebot. I am Masonest’s custom bot and was built by him. I’m still young and learning new things all the time."

Add points command to command list.

Add points command to command list.


Flow.Browser = (_) ->
  _docs = signals []

  _sortedDocs = lift _docs, (docs) ->
    sortBy docs, (doc) -> -doc.date().getTime()

  _hasDocs = lift _docs, (docs) -> docs.length > 0

  createNotebookView = (notebook) ->
    _name = notebook.name
    _date = signal new Date notebook.timestamp_millis
    _fromNow = lift _date, Flow.Util.fromNow

    load = ->
      _.confirm 'Are you sure you want to load this notebook?', { acceptCaption: 'Load Notebook', declineCaption: 'Cancel' }, (accept) ->
        if accept
          _.requestObject 'notebook', _name, (error, doc) ->
            if error
              _.alert error.message
            else
              _.load _name, doc

    purge = ->
      _.requestDeleteObject 'notebook', _name, (error) ->
        if error
          debug error
        else
          _docs.remove self

    self =
      name: _name
      date: _date
      fromNow: _fromNow
      load: load
      purge: purge

  loadNotebooks = ->
    _.requestObjects 'notebook', (error, notebooks) ->
      if error
        debug error
      else
        #XXX sort
        _docs map notebooks, (notebook) -> createNotebookView notebook

  link _.ready, ->
    loadNotebooks()

    link _.saved, ->
      loadNotebooks()

  docs: _sortedDocs
  hasDocs: _hasDocs
  loadNotebooks: loadNotebooks

Flow.Browser = (_) ->
  _docs = signals []

  _sortedDocs = lift _docs, (docs) ->
    sortBy docs, (doc) -> -doc.date().getTime()

  _hasDocs = lift _docs, (docs) -> docs.length > 0

  createNotebookView = (notebook) ->
    _name = notebook.name
    _date = signal new Date notebook.timestamp_millis
    _fromNow = lift _date, Flow.Util.fromNow

    load = ->
      _.confirm 'This action will replace your active notebook.\nAre you sure you want to continue?', { acceptCaption: 'Load Notebook', declineCaption: 'Cancel' }, (accept) ->
        if accept
          _.requestObject 'notebook', _name, (error, doc) ->
            if error
              _.alert error.message
            else
              _.load _name, doc

    purge = ->
      _.requestDeleteObject 'notebook', _name, (error) ->
        if error
          debug error
        else
          _docs.remove self

    self =
      name: _name
      date: _date
      fromNow: _fromNow
      load: load
      purge: purge

  loadNotebooks = ->
    _.requestObjects 'notebook', (error, notebooks) ->
      if error
        debug error
      else
        #XXX sort
        _docs map notebooks, (notebook) -> createNotebookView notebook

  link _.ready, ->
    loadNotebooks()

    link _.saved, ->
      loadNotebooks()

  docs: _sortedDocs
  hasDocs: _hasDocs
  loadNotebooks: loadNotebooks

Improve warning message when loading notebooks


selenium = require 'selenium-webdriver'
{TestHelper} = require './test-element'

COMMON_ELEMENTS =
  courseLink: (appearance, isCoach = false) ->
    dataAttr = 'data-appearance'

    if appearance?
      dataAttr += "='#{appearance}'"

    if isCoach
      teacherLink = "[#{dataAttr}] > [href*='cc-dashboard']"
      studentLink = "[#{dataAttr}] > [href*='content']"
    else
      teacherLink = "[#{dataAttr}] > [href*='calendar']"
      studentLink = "[#{dataAttr}] > [href*='list']"

    css: "#{teacherLink}, #{studentLink}"

class CourseSelect extends TestHelper

  constructor: (test, testElementLocator) ->

    testElementLocator ?= '.course-listing'
    super(test, '.course-listing', COMMON_ELEMENTS)

  goTo: (category) ->
    @waitUntilLoaded()
    # Go to the bio dashboard
    switch category
      when 'BIOLOGY' then @el.courseLink.get('biology').click()
      when 'PHYSICS' then @el.courseLink.get('physics').click()
      when 'CONCEPT_COACH' then @el.courseLink.get(null, true).click()
      else @el.courseLink.get().click()

module.exports = CourseSelect

selenium = require 'selenium-webdriver'
{TestHelper} = require './test-element'

COMMON_ELEMENTS =
  courseLink: (appearance, isCoach = false) ->
    dataAttr = 'data-appearance'

    if appearance?
      dataAttr += "='#{appearance}'"

    if isCoach
      teacherLink = "[#{dataAttr}] > [href*='cc-dashboard']"
      studentLink = "[#{dataAttr}] > [href*='content']"
    else
      teacherLink = "[#{dataAttr}] > [href*='calendar']"
      studentLink = "[#{dataAttr}] > [href*='list']"

    css: "#{teacherLink}, #{studentLink}"

class CourseSelect extends TestHelper

  constructor: (test, testElementLocator) ->

    testElementLocator ?= '.course-listing'
    super(test, '.course-listing', COMMON_ELEMENTS)

  goTo: (category) ->
    @waitUntilLoaded()
    # Go to the bio dashboard
    switch category
      when 'BIOLOGY' then @el.courseLink.get('biology').click()
      when 'PHYSICS' then @el.courseLink.get('physics').click()
      when 'CONCEPT_COACH' then @el.courseLink.get(null, true).click()
      else @el.courseLink.get().click()

  goToCourseByName: (name) ->
    test.waitClick(css: "[data-title='#{name}'] > a")

module.exports = CourseSelect

Add helper method to goto a course by name


path = require 'path'
url = require 'url'
request = require 'request'
fs = require './fs'
TextMateTheme = require './text-mate-theme'

# Convert a TextMate theme to an Atom theme
module.exports =
class ThemeConverter
  constructor: (@sourcePath, destinationPath) ->
    @destinationPath = path.resolve(destinationPath)

  readTheme: (callback) ->
    {protocol} = url.parse(@sourcePath)
    if protocol is 'http:' or protocol is 'https:'
      request @sourcePath, (error, response, body) =>
        if error?
          if error.code is 'ENOTFOUND'
            error = "Could not resolve URL: #{@sourcePath}"
          callback(error)
        else  if response.statusCode isnt 200
          callback("Request to #{@sourcePath} failed (#{response.headers.status})")
        else
          callback(null, body)
    else
      sourcePath = path.resolve(@sourcePath)
      if fs.isFileSync(sourcePath)
        callback(null, fs.readFileSync(sourcePath, 'utf8'))
      else
        callback("TextMate theme file not found: #{sourcePath}")

  convert: (callback) ->
    @readTheme (error, themeContents) =>
      return callback(error) if error?

      try
        theme = new TextMateTheme(themeContents)
      catch error
        return callback(error)

      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'base.less'), theme.getStylesheet())
      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'syntax-variables.less'), theme.getSyntaxVariables())
      callback()

path = require 'path'
url = require 'url'
fs = require './fs'
request = require './request'
TextMateTheme = require './text-mate-theme'

# Convert a TextMate theme to an Atom theme
module.exports =
class ThemeConverter
  constructor: (@sourcePath, destinationPath) ->
    @destinationPath = path.resolve(destinationPath)

  readTheme: (callback) ->
    {protocol} = url.parse(@sourcePath)
    if protocol is 'http:' or protocol is 'https:'
      requestOptions = url: @sourcePath
      request.get requestOptions, (error, response, body) =>
        if error?
          if error.code is 'ENOTFOUND'
            error = "Could not resolve URL: #{@sourcePath}"
          callback(error)
        else  if response.statusCode isnt 200
          callback("Request to #{@sourcePath} failed (#{response.headers.status})")
        else
          callback(null, body)
    else
      sourcePath = path.resolve(@sourcePath)
      if fs.isFileSync(sourcePath)
        callback(null, fs.readFileSync(sourcePath, 'utf8'))
      else
        callback("TextMate theme file not found: #{sourcePath}")

  convert: (callback) ->
    @readTheme (error, themeContents) =>
      return callback(error) if error?

      try
        theme = new TextMateTheme(themeContents)
      catch error
        return callback(error)

      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'base.less'), theme.getStylesheet())
      fs.writeFileSync(path.join(@destinationPath, 'stylesheets', 'syntax-variables.less'), theme.getSyntaxVariables())
      callback()

Use request helper in ThemeConverter class