:white_check_mark: add spec for ordered config.cson

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -679,6 +679,26 @@
           writtenConfig = CSON.writeFileSync.argsForCall[0][1]
           expect(writtenConfig).toEqual '*': atom.config.settings

+        it 'writes properties in alphabetical order', ->
+          atom.config.set('foo', 1)
+          atom.config.set('bar', 2)
+          atom.config.set('baz.foo', 3)
+          atom.config.set('baz.bar', 4)
+
+          CSON.writeFileSync.reset()
+          atom.config.save()
+
+          expect(CSON.writeFileSync.argsForCall[0][0]).toBe atom.config.configFilePath
+          writtenConfig = CSON.writeFileSync.argsForCall[0][1]
+          expect(writtenConfig).toEqual '*': atom.config.settings
+
+          expectedKeys = ['bar', 'baz', 'foo']
+          foundKeys = (key for key of writtenConfig['*'] when key in expectedKeys)
+          expect(foundKeys).toEqual expectedKeys
+          expectedKeys = ['bar', 'foo']
+          foundKeys = (key for key of writtenConfig['*']['baz'] when key in expectedKeys)
+          expect(foundKeys).toEqual expectedKeys
+
       describe "when ~/.atom/config.json doesn't exist", ->
         it "writes any non-default properties to ~/.atom/config.cson", ->
           atom.config.set("a.b.c", 1)


10 -> back to 2

10 -> back to 2

--- spec/editor-spec.coffee
+++ spec/editor-spec.coffee
@@ -1330,12 +1330,12 @@
           expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(0)
           expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(6)

-      it "increases the width of the rendered lines element to be either the width of the longest line or the width of the scrollView (whichever is longer)", ->
+      ffit "increases the width of the rendered lines element to be either the width of the longest line or the width of the scrollView (whichever is longer)", ->
         maxLineLength = editor.getMaxScreenLineLength()
         setEditorWidthInChars(editor, maxLineLength)
         widthBefore = editor.renderedLines.width()
         expect(widthBefore).toBe editor.scrollView.width() + 20
-        buffer.change([[12,0], [12,0]], [1..maxLineLength*10].join(''))
+        buffer.change([[12,0], [12,0]], [1..maxLineLength*2].join(''))
         expect(editor.renderedLines.width()).toBeGreaterThan widthBefore

     describe "when lines are removed", ->


Fine-tuning

Fine-tuning

--- _includes/coffeescripts/contact.coffee
+++ _includes/coffeescripts/contact.coffee
@@ -10,20 +10,18 @@
     event.preventDefault()
     $trigger = $(@)

-    # Clone form template and replace the link with it
+    # Clone form template
     $form = $("#contact-us-form-template").clone()
     $form.removeAttr("id")

-    # Inject the form after the trigger
-    $trigger.hide().after($form.show())
-
+    # Post the form w/ Ajax on submit
     $form.submit (event) ->
       event.preventDefault()
-      $form.find("input[type=submit]").val("Skickar meddelande...").attr("disabled", "disabled")
+      $form.find("input[type=submit]").val("Skickar meddelande ...").attr("disabled", "disabled")
       $.ajax
         type: "POST"
-        url: $trigger.attr('data-action') + "&contactid=#{$trigger.attr('data-contact-id')}"
-        data: $form.serialize()
+        url: $trigger.attr('data-action')
+        data: $form.serialize() + "&contactid=#{$trigger.attr('data-contact-id')}"
         success: (data) ->
           # Replace the form with the repsonce html
           # This is either
@@ -31,7 +29,11 @@
           #   * the form w/ validation messages
           $form.replaceWith(data)
         error: (x, y, z) ->
-          $form.after('
Ett fel inträffade, vänligen försök senare.
')
+          # Server error or timeout, nothing to do
+          $form.after('
Ett fel inträffade, vänligen försök senare eller skicka ditt meddelande till nedanstående e-postadress.
')
+
+    # Replace the trigger w/ the form
+    $trigger.replaceWith($form.show())

     # Scroll to top of form
     $('html, body').animate


Add helper method to clear preview

Add helper method to clear preview

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -8,9 +8,7 @@
     @isPreviewTab = false
     if atom.config.get('tabs.usePreviewTabs') and @item instanceof TextEditor
       @isPreviewTab = true
-      @addEventListener 'dblclick', =>
-        @isPreviewTab = false
-        @itemTitle.classList.remove('temp')
+      @addEventListener 'dblclick', => @clearPreview()

     @classList.add('tab', 'sortable')

@@ -157,6 +155,10 @@
   getTabs: ->
     @parentElement?.querySelectorAll('.tab') ? []

+  clearPreview: ->
+    @isPreviewTab = false
+    @itemTitle.classList.remove('temp')
+
   updateIconVisibility: ->
     if atom.config.get 'tabs.showIcons'
       @itemTitle.classList.remove('hide-icon')
@@ -165,9 +167,7 @@

   updateModifiedStatus: ->
     if @item.isModified?()
-      if atom.config.get('tabs.usePreviewTabs')
-        @isPreviewTab = false
-        @itemTitle.classList.remove('temp')
+      @clearPreview() if atom.config.get('tabs.usePreviewTabs')
       @classList.add('modified') unless @isModified
       @isModified = true
     else


Include reference to tab element when consuming service

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -192,7 +192,7 @@

     if @iconName = @item.getIconName?()
       @itemTitle.classList.add('icon', "icon-#{@iconName}")
-    else if @path? and @iconName = FileIcons.getService().iconClassForPath(@path)
+    else if @path? and @iconName = FileIcons.getService().iconClassForPath(@path, @)
       unless Array.isArray names = @iconName
         names = names.toString().split /\s+/g


Delete script-spec.coffee

Delete script-spec.coffee

--- spec/script-spec.coffee
+++ spec/script-spec.coffee
@@ -1,30 +0,0 @@
-{WorkspaceView} = require 'atom'
-script = require '../lib/script'
-
-# Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
-#
-# To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
-# or `fdescribe`). Remove the `f` to unfocus the block.
-
-describe 'script', ->
-  activationPromise = null
-
-  beforeEach ->
-    atom.workspaceView = new WorkspaceView
-    activationPromise = atom.packages.activatePackage('script')
-
-  describe 'when the script:toggle event is triggered', ->
-    it 'attaches and then detaches the view', ->
-      expect(atom.workspaceView.find('.script')).not.toExist()
-
-      # This is an activation event, triggering it will cause the package to be
-      # activated.
-      atom.workspaceView.trigger 'script:toggle'
-
-      waitsForPromise ->
-        activationPromise
-
-      runs ->
-        expect(atom.workspaceView.find('.script')).toExist()
-        atom.workspaceView.trigger 'script:toggle'
-        expect(atom.workspaceView.find('.script')).not.toExist()


Add spec for missing search query

Add spec for missing search query

--- spec/search-spec.coffee
+++ spec/search-spec.coffee
@@ -33,3 +33,14 @@
       expect(console.log.argsForCall[1][0]).toContain 'duckberg'
       expect(console.log.argsForCall[2][0]).toContain 'duckblur'
       expect(console.log.argsForCall[3][0]).toContain 'ducktales'
+
+  it "logs an error if the query is missing or empty", ->
+    callback = jasmine.createSpy('callback')
+    apm.run(['search'], callback)
+
+    waitsFor 'waiting for command to complete', ->
+      callback.callCount > 0
+
+    runs ->
+      expect(console.error).toHaveBeenCalled()
+      expect(console.error.argsForCall[0][0].length).toBeGreaterThan 0


Make providing a value with wesabe.provide actually work.

--- spec/spec_helper.coffee
+++ spec/spec_helper.coffee
@@ -9,7 +9,7 @@

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


return raw stdout update

return raw stdout update

--- lib/marie.ui.coffee
+++ lib/marie.ui.coffee
@@ -7,39 +7,12 @@
 ###

 class UI
-	@styles
-	clc: require 'cli-color'
-	BULLET: '\u262F '
-	DASH: '\u0335'
-	CARET: '\u2192 '
-	CHECK: '\u221A '
-	XPIXELS: 104
-
-	###
-	Construct app
-	###
-	constructor: ->
-		@styles =
-			WARN: @clc.yellowBright
-			INFO: @clc.white
-			OK: @clc.greenBright
-			ERROR: @clc.redBright
-
-	###
-	Configure message output syle method
-	@param [String] msg string message to output
-	@maram [String] theme style to apply to string
-	###
-	msg: (msg, theme) ->
-		console.log @clc.white "#{@CARET}" + clc[theme] msg

 	###
 	Configure write to console without new line method
 	@param [String] msg string message to output
 	###
 	write: (msg) ->
-		# @clear()
-		# process.stdout.write @clc.blackBright "#{@BULLET}" + @clc.blackBright msg
 		console.log msg

 	###
@@ -50,21 +23,10 @@
 		process.stdout.cursorTo 0

 	###
-	Configure message output method
-	@param [String] msg string message to output
-	@maram [String] symbol symbol to preprend to message
-	###
-	# log: (msg, symbol) ->
-	# 	@clear()
-	# 	console.log msg
-		# console.log @clc.white  (symbol or @CARET) + msg
-
-	###
 	Configure warning message output method
 	@param [String] msg string message to output
 	###
 	warn: (msg) ->
-		# @log @styles.WARN msg
 		console.log msg

 	###
@@ -72,7 +34,6 @@
 	@param [String] msg string message to output
 	###
 	notice: (msg) ->
-		# @log @styles.INFO msg
 		console.log msg

 	###
@@ -80,7 +41,6 @@
 	@param [String] msg string message to output
 	###
 	ok: (msg) ->
-		# @log @styles.OK(msg), @CHECK
 		console.log msg

 	###
@@ -88,17 +48,13 @@
 	@param [String] msg string message to output
 	###
 	error: (msg) ->
-		# @log @styles.INFO msg
 		console.log msg

 	###
 	Configure write line to output method
 	###
 	line: ->
-		# stdout = ''
-		# for i in [0..@XPIXELS] then stdout = stdout + @DASH
-		# console.log @clc.blackBright stdout
-		console.log ''
+		@clear()

 	###
 	Configure console header style method
@@ -107,9 +63,6 @@
 	@example ui.header 'a', 'b'
 	###
 	header: (a,b) ->
-		# @line()
-		# console.log @clc.yellowBright "#{a}" + @clc.whiteBright " #{b}"
-		# @line()
 		console.log "#{a} #{b}"

 # export ui module


Fix persistent store (local testing) with sharejs

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -440,6 +440,14 @@
   editorTimers = {}

   `import {ShareJS} from 'meteor/mizzao:sharejs'`
+
+  ## If we're using a persistent store for sharejs, we need to cleanup
+  ## leftover documents from last time.  This should only be for local
+  ## testing, but deleting them at startup prevents more catastrophic
+  ## failure when creating a duplicate ID.
+  docs = new Mongo.Collection 'docs'
+  docs.find().forEach (doc) ->
+    ShareJS.model.delete doc._id

   editor2messageUpdate = (id) ->
     Meteor.clearTimeout editorTimers[id]


fix typo #349

fix typo #349

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -1303,7 +1303,7 @@
         selection.cursor.moveLeft() unless range.isEmpty()
     super

-class ChangeOcurrence extends Change
+class ChangeOccurrence extends Change
   @extend()
   wordPattern: null
   scanRanges: null


improve messaging during plugin activation and fix debug setting

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -51,40 +51,45 @@

   activate: (state) ->
     packageVersion = atom.packages.getLoadedPackage('wakatime').metadata.version
-
-    if not isCLIInstalled()
-      installCLI(->
-        console.log 'Finished installing wakatime cli.'
-      )
-    else
-      isCLILatest((latest) ->
-        if not latest
+    cleanupOnUninstall()
+    setupConfigs()
+    @settingChangedObserver = atom.config.observe 'wakatime', settingChangedHandler
+
+    isPythonInstalled((installed) ->
+      if not installed
+        if os.type() is 'Windows_NT'
+          atom.confirm
+            message: 'WakaTime requires Python'
+            detailedMessage: 'Let\'s download and install Python now?'
+            buttons:
+              OK: -> installPython(finishActivation)
+              Cancel: -> window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+        else
+          window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+      else
+        if not isCLIInstalled()
           installCLI(->
             console.log 'Finished installing wakatime cli.'
+            finishActivation()
           )
-      )
-    isPythonInstalled((installed) ->
-      if not installed
-        atom.confirm
-          message: 'WakaTime requires Python'
-          detailedMessage: 'Let\'s download and install Python now?'
-          buttons:
-            OK: -> installPython()
-            Cancel: -> window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+        else
+          isCLILatest((latest) ->
+            if not latest
+              installCLI(->
+                console.log 'Finished installing wakatime cli.'
+                finishActivation()
+              )
+            else
+              finishActivation()
+          )
     )
-    cleanupOnUninstall()
-    setupEventHandlers()
-    setupConfigs()
-    statusBarTileView?.setTitle('WakaTime initialized')
-    statusBarTileView?.setStatus()
-    @settingChangedObserver = atom.config.observe 'wakatime', settingChangedHandler

   consumeStatusBar: (statusBar) ->
     statusBarTileView = new StatusBarTileView()
     statusBarTileView.init()
     @statusBarTile = statusBar?.addRightTile(item: statusBarTileView, priority: 300)
     if pluginReady
-      statusBarTileView.setTitle('WakaTime initialized')
+      statusBarTileView.setTitle('WakaTime ready')
       statusBarTileView.setStatus()
       if not atom.config.get 'wakatime.showStatusBarIcon'
         statusBarTileView?.hide()
@@ -93,18 +98,23 @@
     @statusBarTile?.destroy()
     statusBarTileView?.destroy()
     @settingChangedObserver?.dispose()
+
+finishActivation = () ->
+  pluginReady = true
+  setupEventHandlers()
+  statusBarTileView?.setTitle('WakaTime ready')
+  statusBarTileView?.setStatus()

 settingChangedHandler = (settings) ->
   if settings.showStatusBarIcon
     statusBarTileView?.show()
   else
     statusBarTileView?.hide()
-  if pluginReady
-    apiKey = settings.apikey
-    if isValidApiKey(apiKey)
-      atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
-      atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'
-      saveApiKey apiKey
+  apiKey = settings.apikey
+  if isValidApiKey(apiKey)
+    atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
+    atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'
+    saveApiKey apiKey

 saveApiKey = (apiKey) ->
   configFile = path.join getUserHome(), '.wakatime.cfg'
@@ -154,7 +164,6 @@
 setupConfigs = ->
   configFile = path.join getUserHome(), '.wakatime.cfg'
   fs.readFile configFile, 'utf-8', (err, configContent) ->
-    pluginReady = true
     if err?
       console.log 'Error: could not read wakatime config file'
       settingChangedHandler atom.config.get('wakatime')
@@ -187,7 +196,7 @@
         unloadHandler = null
   )

-setupEventHandlers = () ->
+setupEventHandlers = (callback) ->
   atom.workspace.observeTextEditors (editor) =>
     try
       buffer = editor.getBuffer()
@@ -212,6 +221,8 @@
           if editor.cursors.length > 0
             lineno = editor.cursors[0].getCurrentLineBufferRange().end.row + 1
           sendHeartbeat(file, lineno)
+    if callback?
+      callback()

 isPythonInstalled = (callback) ->
   pythonLocation((result) ->
@@ -249,26 +260,29 @@
         pythonLocation(callback, locations)
     )

-installPython = () ->
-  if os.type() is 'Windows_NT'
-    pyVer = '3.5.1'
-    arch = 'win32'
-    if os.arch().indexOf('x64') > -1
-      arch = 'amd64'
-    url = 'https://www.python.org/ftp/python/' + pyVer + '/python-' + pyVer + '-embed-' + arch + '.zip'
-
-    console.log 'Downloading python...'
-    zipFile = __dirname + path.sep + 'python.zip'
-    downloadFile(url, zipFile, ->
-
-      console.log 'Extracting python...'
-      unzip(zipFile, __dirname + path.sep + 'python', ->
-          fs.unlink(zipFile)
-          console.log 'Finished installing python.'
-      )
+installPython = (callback) ->
+  pyVer = '3.5.1'
+  arch = 'win32'
+  if os.arch().indexOf('x64') > -1
+    arch = 'amd64'
+  url = 'https://www.python.org/ftp/python/' + pyVer + '/python-' + pyVer + '-embed-' + arch + '.zip'
+
+  console.log 'downloading python...'
+  statusBarTileView?.setStatus('downloading python...')
+
+  zipFile = __dirname + path.sep + 'python.zip'
+  downloadFile(url, zipFile, ->
+
+    console.log 'extracting python...'
+    statusBarTileView?.setStatus('extracting python...')
+
+    unzip(zipFile, __dirname + path.sep + 'python', ->
+        fs.unlink(zipFile)
+        console.log 'Finished installing python.'
+        if callback?
+          callback()
     )
-  else
-    window.alert('WakaTime depends on Python. Install it from https://python.org/downloads then restart Atom.')
+  )

 isCLIInstalled = () ->
   return fs.existsSync(cliLocation())
@@ -318,6 +332,7 @@

 installCLI = (callback) ->
   console.log 'Downloading wakatime cli...'
+  statusBarTileView?.setStatus('downloading wakatime-cli...')
   url = 'https://github.com/wakatime/wakatime/archive/master.zip'
   zipFile = __dirname + path.sep + 'wakatime-master.zip'
   downloadFile(url, zipFile, ->
@@ -326,6 +341,7 @@

 extractCLI = (zipFile, callback) ->
   console.log 'Extracting wakatime-master.zip file...'
+  statusBarTileView?.setStatus('extracting wakatime-cli...')
   removeCLI(->
     unzip(zipFile, __dirname, callback)
   )
@@ -383,6 +399,8 @@
       if lineno?
         args.push('--lineno')
         args.push(lineno)
+      if atom.config.get 'wakatime.debug'
+        args.push('--verbose')

       if atom.project.contains(file.path)
         currentFile = file.path
@@ -416,7 +434,8 @@
             status = 'Error'
             title = 'Unknown Error (' + proc.exitCode + '); Check your Dev Console and ~/.wakatime.log for more info.'

-          console.warn msg
+          if msg?
+            console.warn msg
           statusBarTileView?.setStatus(status)
           statusBarTileView?.setTitle(title)


remove commented line

remove commented line

--- src/javascripts/progress_bar.coffee
+++ src/javascripts/progress_bar.coffee
@@ -48,7 +48,6 @@
   updateLoaded: (buffered) =>
     return unless @player.seekable.length

-    #newStart = @player.buffered.start(0) * @timeRailFactor()
     newWidth = @player.seekable.end(@player.seekable.length - 1) * @timeRailFactor()
     @loadedElement.css('margin-left', 0).width(newWidth)


Redesign visibility UI

Redesign visibility UI

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -14,70 +14,83 @@
       launch: false

   render: ->
+    looksDisabled =
+      opacity: 0.6
+      pointerEvents: 'none'
+

Project state and visibility

       {if @state.error

{@state.error.toString()}
}

-

-

-

-
-
-                <input type="checkbox" name="live" value={true} data-json-value={true} checked={@props.project.live} disabled={@state.setting.live} onChange={(e) => @set 'live', e.target.checked} />
-                Live
-
-
-            No workflow changes can be made a "live" project. Check this when you’ve finished development or want to apply for a beta test.
-

+

+
+          <input type="radio" name="private" value={true} data-json-value={true} checked={@props.project.private} disabled={@state.setting.private} onChange={@set.bind this, 'private', true} />
+          Private
+
+
+
+          <input type="radio" name="private" value={false} data-json-value={true} checked={not @props.project.private} disabled={@state.setting.private} onChange={@set.bind this, 'private', false} />
+          Public
+
+

-

-
-
-                <input type="radio" name="private" value={true} data-json-value={true} checked={@props.project.private} disabled={@state.setting.private} onChange={@set.bind this, 'private', true} />
-                Private
-
-
-
-                <input type="radio" name="private" value={false} data-json-value={true} checked={not @props.project.private} disabled={@state.setting.private} onChange={@set.bind this, 'private', false} />
-                Public
-
-
-            Only users with assigned roles can view a private project. Anyone with the URL can access a public project.
-

+
Only the assigned collaborators can view a private project. Anyone with the URL can access a public project.

-

-
-              <button type="button" className="standard-button full" disabled={not @props.project.live or @state.setting.beta or @props.project.beta} onClick={@set.bind this, 'beta', true}>Apply for beta</button>{' '}
-
-
-
Pending approval, expose this project to users who have opted in to be beta testers.

-              {unless @props.project.live
-
Only live projects can apply for a beta.
}
-              {if @props.project.beta_approved
-
Beta status for this project has been approved.

-              else if @props.project.beta
-
Beta status has been applied for. <button type="button" disabled={@state.setting.beta} onClick={@set.bind this, 'beta', false}>Cancel application</button>
}
-
-

+

-

-
-              <button type="button" className="standard-button full" disabled={not @props.project.beta_approved or @state.setting.launch or @props.project.launch} onClick={@set.bind this, 'launch', true}>Apply for full launch</button>
-
-
-
Pending approval, expose this project to the entire Zooniverse through the main projects listing.

-              {unless @props.project.beta_approved
-
Only project in beta can apply for a full launch.
}
-              {if @props.project.launch_approved
-
Beta status for this project has been approved.

-              else if @props.project.launch
-
Launch is awaiting Zooniverse approval. <button type="button" disabled={@state.setting.launch} onClick={@set.bind this, 'launch', false}>Cancel application</button>
}
-
-

-

-

+
+        <input type="radio" name="live" value={true} data-json-value={true} checked={not @props.project.live} disabled={@state.setting.live} onChange={@set.bind this, 'live', false} />
+        Development
+
+
+
+        <input type="radio" name="live" value={false} data-json-value={true} checked={@props.project.live} disabled={@state.setting.live} onChange={@set.bind this, 'live', true} />
+        Live
+
+
+
In a live project, workflows are locked and can no longer be edited. Classifications count toward subject retirement.

+
+

+

+
+

+          <button type="button" className="standard-button" disabled={@props.project.private or not @props.project.live or @state.setting.beta or @props.project.beta} onClick={@set.bind this, 'beta', true}>Apply for beta</button>{' '}
+          {if @props.project.private
+            Only public projects can apply for a beta.
+          else if not @props.project.live
+            Only live projects can apply for a beta.
+          else if @props.project.beta
+            Beta status has been applied for. <button type="button" disabled={@state.setting.beta} onClick={@set.bind this, 'beta', false}>Cancel application</button>}
+

+
+
Pending approval, expose this project to users who have opted in to help test new projects.

+
+        {if @props.project.beta_approved
+

+            Beta status for this project has been approved. To end the beta test and make changes, switch back to development mode.
+            {unless @props.project.launch or @props.project.launch_approved
+              If you’re ready to launch this project, see the next section.}
+
}
+
+

+

+
+

+            <button type="button" className="standard-button" disabled={not @props.project.beta_approved or @state.setting.launch or @props.project.launch} onClick={@set.bind this, 'launch', true}>Apply for full launch</button>{' '}
+
+            {unless @props.project.beta_approved
+              Only projects in beta can apply for a full launch.}
+            {if @props.project.launch_approved
+              This project is available to the whole Zooniverse!
+            else if @props.project.launch
+              Launch is awaiting Zooniverse approval. <button type="button" disabled={@state.setting.launch} onClick={@set.bind this, 'launch', false}>Cancel application</button>}
+

+
+
Pending approval, expose this project to the entire Zooniverse through the main projects listing.

+

+

   set: (property, value) ->


Add operator scope to `:`

Add operator scope to `:`

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -21,4 +21,4 @@
       title: 'Character scope'
       description: 'Scope string to match'
       type: 'string'
-      default: 'key-value|property-name'
+      default: 'key-value|property-name|operator'


Added carrying capacity and some randomness.

The carrying capacity is just pulled out of thin air, and the randomness is _not_ handled well, but it's a start.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -3,6 +3,7 @@
   values: null
   year: 2015
   initialNumCranes: null
+  carryingCapacity: 1000
   numCranes: null
   percentageEggsToTake: null
   captiveEggSurvival: null
@@ -65,16 +66,24 @@

   drawChart: ->
     @chart.render()
+
+  jiggle: (v) ->
+    newV = v + 0.1 * v * (Math.random() * 2 - 1)
+    newV = 0 if newV < 0
+    newV = 1 if newV > 1
+    return(newV)

   updatePopulation: (year) ->
     numPairs = @numCranes / 2
     numEggs = numPairs * @clutchSize
-    captive_babies = numEggs * @percentageEggsToTake * @captiveEggSurvival
+    captive_babies = numEggs * @percentageEggsToTake * @jiggle(@captiveEggSurvival)
     #console.log(captive_babies)
-    wild_babies = numEggs * (1-@percentageEggsToTake) * @wildEggSurvival
-    @numCranes = @numCranes * (1-@overallMortalityRate) + captive_babies + wild_babies
+    wild_babies = numEggs * (1-@percentageEggsToTake) * @jiggle(@wildEggSurvival)
+    @numCranes = @numCranes * (1-@jiggle(@overallMortalityRate)) + captive_babies + wild_babies
     if @numCranes <= 0
       @numCranes = 0
+    if @numCranes > @carryingCapacity
+      @numCranes = @carryingCapacity
     # console.log(@numCranes)
     return(@numCranes)

@@ -95,7 +104,7 @@
     @extendData()
     @drawChart()
     @runNumber = @runNumber + 1
-    @notDone = @runNumber < 5
+    @notDone = @runNumber < 100
     console.log("Run number #{@runNumber}")
     setTimeout(@tick, @tickLength) if @isRunning and @notDone


Added parentheses around generated conditional expression

--- src/main/compilers/compileIf.coffee
+++ src/main/compilers/compileIf.coffee
@@ -1,3 +1,3 @@
 module.exports = (args, c) ->
   throw new Error "'if' needs at least three parameters" if args.length < 3
-  "(#{c.compileNode(args[0])})?(#{c.compileNode(args[1])}):(#{c.compileNode(args[2])})"
+  "((#{c.compileNode(args[0])})?(#{c.compileNode(args[1])}):(#{c.compileNode(args[2])}))"


Clean up keys

Clean up keys

--- app/tasks/survey/chooser.cjsx
+++ app/tasks/survey/chooser.cjsx
@@ -110,7 +110,7 @@

{choice.label}

               </button>
               {if chosen
-                <button key={choiceID + i + "delete"} type="button" className="survey-task-chooser-choice-remover-button" onClick={@props.onRemoveChoice.bind null, choiceID}>x</button>}
+                <button type="button" className="survey-task-chooser-choice-remover-button" onClick={@props.onRemoveChoice.bind null, choiceID}>x</button>}

       }


Reset some files

Reset some files

--- lms/static/coffee/src/discussion/utils.coffee
+++ lms/static/coffee/src/discussion/utils.coffee
@@ -249,10 +249,7 @@
           $3
       else if RE_DISPLAYMATH.test(text)
         text = text.replace RE_DISPLAYMATH, ($0, $1, $2, $3) ->
-          #processedText += $1 + processor("$$" + $2 + "$$", 'display')
-          #bug fix, ordering is off
-          processedText =  processor("$$" + $2 + "$$", 'display') + processedText
-          processedText = $1 + processedText
+          processedText += $1 + processor("$$" + $2 + "$$", 'display')
           $3
       else
         processedText += text


Template: gulpfile: add clean and production tasks

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -5,6 +5,7 @@
 streamify       = require 'gulp-streamify'
 CSSmin          = require 'gulp-minify-css'

+rimraf          = require 'rimraf'
 source          = require 'vinyl-source-stream'
 watchify        = require 'watchify'
 coffeeify       = require 'coffeeify'
@@ -119,6 +120,20 @@
     .pipe gulp.dest './dist/css/'

+gulp.task 'clean', (cb) ->
+
+  rimraf './dist', cb
+
+
+gulp.task 'production', [ 'clean' ], ->
+
+  production = yes
+
+  gulp.start 'build', ->
+    gutil.log 'Building for production is completed,
+               you can now deploy ./dist folder'
+
+
 gulp.task 'build',   [
   'compile-scripts', 'styles', 'entry-point', 'export-kd', 'images'
 ]


Minor text change

Minor text change

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -657,7 +657,7 @@
       """

     uwi:
-      nav: "Urban Wildlife Inst."
+      nav: "Urban Wildlife Institute"
       header: "Urban Wildlife Institute"
       content: """

Utilizing Lincoln Park Zoo’s diverse scientific specialties, the Urban Wildlife Institute studies the interaction between urban development and the natural ecosystem to develop scientific standards for minimizing conflict between these overlapping areas. Landscape ecology, population biology, epidemiology, endocrinology, veterinary medicine and other core disciplines contribute to an increased understanding of ecosystem health in an urban setting. The Urban Wildlife Institute aims to use Chicago as a model for urban areas struggling to deal with wildlife relocation, rehabilitation, disease and conflicts.


Single box study

Single box study

--- app/assets/javascripts/cardsets.coffee
+++ app/assets/javascripts/cardsets.coffee
@@ -236,7 +236,48 @@

 namespace 'kksystem.cardsets.study.single_box', (ns) ->
   ns.init = (params) ->
-    console.log('single_box')
+    kksystem.models.Cardset.load [['id', 'eq', params.cardset]], (cardsets) =>
+      if cardsets or cardsets.length > 0
+        @cardset = cardsets[0]
+      else
+        kksystem.cardsets.study.error()
+        return
+      $('#statistics-current-set').text(@cardset.name)
+      kksystem.models.Cardbox.load [['id', 'eq', params.cardbox]], \
+          (cardboxes) =>
+        unless cardboxes or cardboxes.length > 0
+          kksystem.cardsets.study.error()
+          return
+        @cardbox = cardboxes[0]
+        kksystem.models.Card.load [['current_cardbox_id', 'eq', @cardbox.id], \
+            [null, 'order', 'rand']], (cards) =>
+          unless cards
+            kksystem.cardsets.study.error()
+            return
+          @cardbox.cards = cards
+          ns.displayNextCard()
+
+  ns.displayNextCard = ->
+    kksystem.cardsets.study.startLoading()
+    incomplete_cards = @cardbox.cards.filter (card) => not card.done
+    #If incomplete cards, continue studying, else end study.
+    if incomplete_cards.length > 0
+      @use_card = incomplete_cards[0]
+      kksystem.cardsets.study.setCardText
+        front: @use_card.front
+        back: @use_card.back
+      kksystem.cardsets.study.hookupCorrectnessButtons(ns.correct, ns.incorrect)
+      kksystem.cardsets.study.stopLoading()
+    else
+      kksystem.cardsets.study.finishStudy()
+
+  ns.correct = ->
+    ns.use_card.done = true
+    ns.displayNextCard()
+
+  ns.incorrect = ->
+    ns.use_card.done = true
+    ns.displayNextCard()

 namespace 'kksystem.cardsets.study.no_box', (ns) ->
   ns.init = (params) ->


The motivaton for identifying inanimate objects

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -214,13 +214,13 @@
     bike:
       label: 'Bike'
       description: '''
-        N/A
+        N/A. Identifying the frequency of bikes helps us understand  the the impact of human activity on the animal population in this urban habitat.
       '''

     car:
       label: 'Car'
       description: '''
-        N/A
+        N/A. Identifying the frequency of cars helps us understand the impact of human activity on the animal population in this urban habitat.
       '''

     chipmunk:
@@ -292,7 +292,7 @@
     livestock:
       label: 'Livestock'
       description: '''
-        N/A
+         N/A. Identifying the frequency of livestock helps us understand the impact of human activity on the animal population in this urban habitat.
       '''

     mink:
@@ -310,7 +310,7 @@
     mower:
       label: 'Mower'
       description: '''
-        N/A
+        N/A. Identifying the frequency of mowers helps us understand the impact of human activity on the animal population in this urban habitat.
     '''

     muskrat:
@@ -328,7 +328,7 @@
     otherVehicle:
       label: 'Vehicle, Other'
       description: '''
-        N/A
+        N/A. Identifying the frequency of vehicles helps us understand the impact of human activity on the animal population in this urban habitat.
       '''

     rabbit:


Remove dead code

Remove dead code

--- src/scripts/modules/media/title/title.coffee
+++ src/scripts/modules/media/title/title.coffee
@@ -26,12 +26,6 @@

         view.popovers.push(popover)

-    events:
-      'click .share a': 'clickShareButton'
-
-    clickShareButton: (e) ->
-      e.preventDefault()
-
     close: () ->
       _.each popovers, (popover) ->
         popover.destroy()


d3 mekko: minor interface tweaks

d3 mekko: minor interface tweaks

--- app/assets/javascripts/d3/mekko.coffee
+++ app/assets/javascripts/d3/mekko.coffee
@@ -11,7 +11,7 @@
     label: => @get('label') || @get('gquery')
     key:   => "#{@get 'carrier'}_#{@get 'sector'}"
     tooltip_text: =>
-      "#{@get 'carrier'} #{@get 'sector'}
#{parseInt @val()}PJ"
+      "#{@get 'carrier'} #{@get 'sector'}
#{parseInt @val()} PJ"

   NodeGroup: class extends Backbone.Model
       initialize: ->
@@ -64,10 +64,10 @@
     draw: =>
       @prepare_data()
       margins =
-        top: 40
+        top: 10
         bottom: 100
         left: 30
-        right: 30
+        right: 10

       @width = (@container_node().width()   || 490) - (margins.left + margins.right)
       @height = (@container_node().height() || 402) - (margins.top + margins.bottom)
@@ -80,6 +80,7 @@

       y_scale = d3.scale.linear().domain([100,0]).range([0, @height])
       @y_axis = d3.svg.axis().scale(y_scale).ticks(4).orient("left")
+        .tickFormat((x) -> "#{x}%")
       # axis
       @svg.append("svg:g").
         attr("class", "y_axis").
@@ -114,7 +115,7 @@
         .attr("text-anchor", "end")

       $('rect.carrier').qtip
-        content: -> $(this).attr('title')
+        content: -> $(this).attr('data-tooltip')
         show:
           event: 'mouseover' # silly IE
         hide:
@@ -144,7 +145,7 @@
         data(@sector_list.models, (d) -> d.get 'key' ).
         transition().duration(500).
         attr("dy", (d) => @x(d.total_value() / 2) + 5 ).
-        text((d) -> "#{d.get 'key'} #{parseInt d.total_value()}PJ")
+        text((d) -> "#{d.get 'key'} #{parseInt d.total_value()} PJ")

       # we need to track the offset for every sector
       offsets = {}
@@ -161,7 +162,7 @@
           offsets[d.get 'sector'] += d.val() / d.sector.total_value() * @height
           old
         ).
-        attr("title", (d) -> d.tooltip_text())
+        attr("data-tooltip", (d) -> d.tooltip_text())

 class D3.mekko.GroupCollection extends Backbone.Collection
   model: D3.mekko.NodeGroup


util-node/test: added list of WA zip codes from another page, test broken though


fix ctrl+enter note send

fix ctrl+enter note send

use argument e instead of event

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -55,7 +55,7 @@

     @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
     $(document).on('keypress', @notes_forms, (e)->
-      if event.keyCode == 10 || (event.ctrlKey && event.keyCode == 13)
+      if e.keyCode == 10 || (e.ctrlKey && e.keyCode == 13)
         $(@).parents('form').submit()
     )


Save cid after successful printer action

--- app/assets/javascripts/print.js.coffee
+++ app/assets/javascripts/print.js.coffee
@@ -2,9 +2,15 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
 printer_local_storage_key = "last-printer-used"
+cid_local_storage_key = "cid"

 $ ->
   printer_name = localStorage.getItem(printer_local_storage_key)
+  cid = localStorage.getItem(cid_local_storage_key)
+
+
+  if (cid)
+    $('#print_username').val(cid)

   if $('#print_printer').length

@@ -56,6 +62,7 @@
           $('#pq .name').text(data.username)
           $('#pq .pq').text(data.value)
           $('#pq .success').show()
+          localStorage.setItem(cid_local_storage_key, data.username)

     $('.printer-list').on 'click', '.set-printer', ->
@@ -74,6 +81,7 @@
     $('#new_print').on 'submit', (e) ->
       e.preventDefault()
       form = new FormData(this)
+      newCid = $('#print_username').val()

       $.ajax
         url: this.action,
@@ -84,6 +92,7 @@
       .success (data) ->
         $('.printer-feedback-alert').hide()
         $('.printer-feedback-success').show()
+        localStorage.setItem(cid_local_storage_key, newCid)
       .error (err) ->
         errors = err.responseJSON.errors
         $('.printer-feedback-alert .msg').html($('<ul/>').html(errors.map (err) -> $('<li/>').text(err)))


Allow for manual editing of dates

Allow for manual editing of dates

--- app/assets/javascripts/refugees.coffee
+++ app/assets/javascripts/refugees.coffee
@@ -5,6 +5,7 @@
     autoclose: true
     todayHighlight: true
     orientation: 'left bottom'
+    keyboardNavigation: false

   $('.dob.date').datepicker
     weekStart: 1
@@ -12,6 +13,7 @@
     autoclose: true
     todayHighlight: true
     orientation: 'left bottom'
+    keyboardNavigation: false

   # Add term
   $("form.refugee").on "click", ".add-term", (event) ->


Start synchronously (on mouseup too)

Start synchronously (on mouseup too)

--- src/trix/observers/selection_observer.coffee
+++ src/trix/observers/selection_observer.coffee
@@ -1,7 +1,7 @@
 {handleEvent} = Trix.DOM

 class Trix.SelectionObserver
-  events = ["DOMFocusIn", "DOMFocusOut", "mousedown", "keydown"]
+  events = ["DOMFocusIn", "DOMFocusOut", "mousedown", "mouseup", "keydown"]

   constructor: (@element) ->
     @range = getRange()
@@ -14,7 +14,7 @@
       @update()
     else
       @running = true
-      requestAnimationFrame(@tick)
+      @tick()

   stop: ->
     delete @running


:fire: leftover

:fire: leftover

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -45,7 +45,6 @@
   flashOnOperate:
     order: 8
     type: 'boolean'
-    default: 'console'
     default: true
   flashOnOperateDurationMilliSeconds:
     order: 9


Fixed accidentally added }

Fixed accidentally added }

--- app/assets/javascripts/stats.js.coffee
+++ app/assets/javascripts/stats.js.coffee
@@ -1,4 +1,4 @@
-}# Helper for generate C3.js graphs
+# Helper for generate C3.js graphs
 #----------------------------------------------------------------------

 buildGraph = (el) ->


Updated api.

Updated api.

--- lib/api.coffee
+++ lib/api.coffee
@@ -1,69 +1,64 @@
+require 'coffee-script'
+
+# ---
+
 async = require 'async'
 child_process = require 'child_process'
 stream_buffers = require 'stream-buffers'

 # ---

-logger = require './logger.js'
+utils = require './utils.coffee'
+logger = require './logger.coffee'

-# ---
-
-vboxmanage_path = switch
-	when process.platform.match /^win/ then path.join process.env.VBOX_INSTALL_PATH or '', 'VBoxManage.exe'
-	when process.platform.match /^dar/ then '/Applications/VirtualBox.app/Contents/MacOS/VBoxManage'
-	else 'VboxManage'
-
-# ---
-
-logger.debug "vboxmanage_path is set to #{vboxmanage_path}"
-
-# ---
-
-command_queue = async.queue (task, callback) ->
-	task.run callback
-
 # ---

 ###
 	* @param {string} command
 	* @param {array} args
-	* @param {function} callback
+	* @param {function(?err, code, output)} callback
 ###
-exports.command = (command, args..., callback) ->
-	args = args.filter (arg) -> arg
-
-	task =
-		stream: new stream_buffers.WritableStreamBuffer
+exports.command = do () ->
+	vboxmanage_path = switch
+		when process.platform.match /^win/ then path.join process.env.VBOX_INSTALL_PATH or '', 'VBoxManage.exe'
+		when process.platform.match /^dar/ then '/Applications/VirtualBox.app/Contents/MacOS/VBoxManage'
+		else 'VboxManage'

-		run: (callback) ->
-			logger.debug "exec #{command} #{args}"
+	command_queue = async.queue (task, callback) ->
+		task.run callback
+
+	(command, args..., callback) ->
+		args = args.filter (arg) -> arg
+
+		task =
+			stream: new stream_buffers.WritableStreamBuffer

-			child = child_process.spawn vboxmanage_path, [command].concat(args)
+			run: (callback) ->
+				logger.verbose "exec #{vboxmanage_path} #{command} #{args.join(' ')}"
+
+				child = child_process.spawn vboxmanage_path, [command].concat(args)
+
+				child.stdout.pipe @stream
+				child.stderr.pipe @stream
+				child.on 'error', (error) => callback error
+				child.on 'close', (code) => callback null, code, @stream.getContentsAsString('utf8')
+
+		command_queue.push task, (err, code, output) ->
+			return callback err if err
+			return callback null, code, output

-			child.stdout.pipe @stream
-			child.stderr.pipe @stream
-			child.on 'error', (error) => callback error
-			child.on 'close', (code) => callback null, code, @stream.getContentsAsString 'utf8'
-
-	command_queue.push task, (err, code, output) ->
-		return callback err if err
-		return callback null, code, output
-
-# ---
-
-split_machine_output = (machine_output) ->
-	machine_output.split('\n').map (line) ->
-
 # ---

 ###
+	* @param {string} name
+	* @param {function(?err, result)} callback
 ###
 exports.showvminfo = (name, callback) ->
 	exports.command 'showvminfo', name, '--machinereadable', (err, code, output) ->
 		return callback err if err
 		return callback new Error "cannot show info for #{name}" if code > 0
-		return callback null, output.split('\n').map () -> if callback
-exports.showvminfo 'baseimage', (err, o) -> console.log o
+		return callback null, utils.parse_info(output)
+
 # ---

 ###


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -263,12 +263,15 @@
     @lines.css('padding-bottom', linesHeight)

   renderLines: ->
+    @clearLines()
+    @updateLines()
+
+  clearLines: ->
     @lineCache = []
     @lines.find('.line').remove()

     @firstRenderedScreenRow = -1
     @lastRenderedScreenRow = -1
-    @updateLines()

   updateLines: ->
     firstVisibleScreenRow = @getFirstVisibleScreenRow()


small refactor

small refactor

--- src/base_model.coffee
+++ src/base_model.coffee
@@ -78,8 +78,6 @@

   ###
   constructor: (obj = {}) ->
-    self = @
-
     @_isValid = false

     # object, which contain all errors, which registers after validation
@@ -87,94 +85,90 @@
     @attributes = @normalize_attrs()

     for attr in @attrs()
-      do(attr) ->
-        if typeof(attr) is "object"
-          [key, ...] = Object.keys(attr)
-          throw new Error("Attributes should have a key and value") if !key
-          self["_#{key}"] = attr[key]
-        else
-          self["_#{attr}"] = null
+      if typeof(attr) is "object"
+        [key, ...] = Object.keys(attr)
+        throw new Error("Attributes should have a key and value") if !key
+        @["_#{key}"] = attr[key]
+      else
+        @["_#{attr}"] = null

     for klass in @has_many()
-      do(klass) ->
-        #FIXME : maybe normalize class name
-        self["_#{klass}"] = []
-        self.attributes.push("#{klass}")
-        self["add_#{klass}"] = (z) =>
-          name = z.constructor.name
-          me   = self.normal_name()
-          m_name = self.constructor.name
-
-          expected = klass.charAt(0).toUpperCase() + klass.slice(1)
-          throw new Error("Expected #{expected}, but given: #{name}") if name isnt expected
-          self.get("#{klass}").push(z)
-
-          #feedback
-          b_model = (for i in z.belongs_to()
-            do(i) ->
-              i if i['model'] == me
-          )[0]
-
-          if !b_model
-            throw new Error("Model #{name} must contain '@belongs_to: [{model: #{me}, back: #{me}_id]'")
-
-          if !(back = b_model['back'])
-            throw new Error("Define 'back' property for @belongs_to")
-
-          if self.attributes.indexOf(back) == -1
-            throw new Error("Foreign key: '#{back}' not contain in a '#{m_name}' model")
-
-          key = "#{me}_#{back}"
-          if z.attributes.indexOf(key) == -1
-            throw new Error("Define #{key} in @attrs for '#{expected}' model")
-
-          z.set("#{key}", self.get(back))
+      #FIXME : maybe normalize class name
+      @["_#{klass}"] = []
+      @attributes.push("#{klass}")
+      @["add_#{klass}"] = (z) =>
+        name = z.constructor.name
+        me   = @normal_name()
+        m_name = @constructor.name
+
+        expected = SiriusApplication.camelize(klass)
+        throw new Error("Expected #{expected}, but given: #{name}") if name isnt expected
+        @get("#{klass}").push(z)
+
+        #feedback
+        b_model = (for i in z.belongs_to()
+          do(i) ->
+            i if i['model'] == me
+        )[0]
+
+        if !b_model
+          throw new Error("Model #{name} must contain '@belongs_to: [{model: #{me}, back: #{me}_id]'")
+
+        if !(back = b_model['back'])
+          throw new Error("Define 'back' property for @belongs_to")
+
+        if @attributes.indexOf(back) == -1
+          throw new Error("Foreign key: '#{back}' not contain in a '#{m_name}' model")
+
+        key = "#{me}_#{back}"
+        if z.attributes.indexOf(key) == -1
+          throw new Error("Define #{key} in @attrs for '#{expected}' model")
+
+        z.set("#{key}", @get(back))

     #TODO: refactor this
     for klass in @has_one()
-      do(klass) ->
-        self["_#{klass}"] = null
-        self.attributes.push("#{klass}")
-        self["add_#{klass}"] = (z) =>
-          name = z.constructor.name
-          me   = self.normal_name()
-          m_name = self.constructor.name
-
-          expected = klass.charAt(0).toUpperCase() + klass.slice(1)
-          throw new Error("Expected #{expected}, but given: #{name}") if name isnt expected
-
-          if self.get("#{klass}")
-            throw new Error("Model #{expected} already exist for #{m_name}")
-
-          self.set("#{klass}", z)
-
-          #feedback
-          b_model = (for i in z.belongs_to()
-            do(i) ->
-              i if i['model'] == me
-          )[0]
-
-          if !b_model
-            throw new Error("Model #{name} must contain '@belongs_to: [{model: #{me}, back: #{me}_id]'")
-
-          if !(back = b_model['back'])
-            throw new Error("Define 'back' property for @belongs_to")
-
-          if self.attributes.indexOf(back) == -1
-            throw new Error("Foreign key: '#{back}' not contain in a '#{m_name}' model")
-
-          key = "#{me}_#{back}"
-          if z.attributes.indexOf(key) == -1
-            throw new Error("Define #{key} in @attrs for '#{expected}' model")
-
-          z.set("#{key}", self.get(back))
+      @["_#{klass}"] = null
+      @attributes.push("#{klass}")
+      @["add_#{klass}"] = (z) =>
+        name = z.constructor.name
+        me   = @normal_name()
+        m_name = @constructor.name
+
+        expected = SiriusApplication.camelize(klass)
+        throw new Error("Expected #{expected}, but given: #{name}") if name isnt expected
+
+        if @get("#{klass}")
+          throw new Error("Model #{expected} already exist for #{m_name}")
+
+        @set("#{klass}", z)
+
+        #feedback
+        b_model = (for i in z.belongs_to()
+          do(i) ->
+            i if i['model'] == me
+        )[0]
+
+        if !b_model
+          throw new Error("Model #{name} must contain '@belongs_to: [{model: #{me}, back: #{me}_id]'")
+
+        if !(back = b_model['back'])
+          throw new Error("Define 'back' property for @belongs_to")
+
+        if @attributes.indexOf(back) == -1
+          throw new Error("Foreign key: '#{back}' not contain in a '#{m_name}' model")
+
+        key = "#{me}_#{back}"
+        if z.attributes.indexOf(key) == -1
+          throw new Error("Define #{key} in @attrs for '#{expected}' model")
+
+        z.set("#{key}", @get(back))

     if Object.keys(obj).length != 0
       for attr in Object.keys(obj)
-        do(attr) ->
-          self.set(attr, obj[attr])
+        @set(attr, obj[attr])

   # base setter
   # @throw Error, when attributes not defined for current model
@@ -241,18 +235,16 @@
   # otherwise as { attrs }
   #
   to_json: (root = false) ->
-    self = @
     z = {}

     for attr in @attributes
-      do(attr) ->
-        value = self.get("#{attr}")
-        z["#{attr}"] = if self.has_many().indexOf(attr) > -1
-          for v in value then JSON.parse(v.to_json())
-        else if self.has_one().indexOf(attr) > -1
-          JSON.parse(value.to_json())
-        else
-          value
+      value = @get("#{attr}")
+      z["#{attr}"] = if @has_many().indexOf(attr) > -1
+        for v in value then JSON.parse(v.to_json())
+      else if @has_one().indexOf(attr) > -1
+        JSON.parse(value.to_json())
+      else
+        value

     if root
       o = {}


Don't emit loaded event on atom.packages

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -128,7 +128,6 @@
       callback(_.extend({}, results...))

   doneLoading: ->
-    atom.packages.emit 'snippets:loaded'
     @loaded = true
     @getEmitter().emit 'did-load-snippets'


Warn when `onDidChange` functions are not valid `Disposable`s

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -1,6 +1,6 @@
 path = require 'path'
 {$} = require 'atom-space-pen-views'
-{CompositeDisposable} = require 'atom'
+{Disposable, CompositeDisposable} = require 'atom'

 module.exports =
 class TabView extends HTMLElement
@@ -40,7 +40,11 @@
       @updateTooltip()

     if typeof @item.onDidChangeTitle is 'function'
-      @subscriptions.add @item.onDidChangeTitle(titleChangedHandler)
+      onDidChangeTitleDisposable = @item.onDidChangeTitle(titleChangedHandler)
+      if Disposable.isDisposable(onDidChangeTitleDisposable)
+        @subscriptions.add(onDidChangeTitleDisposable)
+      else
+        console.warn "::onDidChangeTitle does not return a valid disposable!"
     else if typeof @item.on is 'function'
       #TODO Remove once old events are no longer supported
       @item.on('title-changed', titleChangedHandler)
@@ -63,7 +67,11 @@
       @updateModifiedStatus()

     if typeof @item.onDidChangeModified is 'function'
-      @subscriptions.add @item.onDidChangeModified(modifiedHandler)
+      onDidChangeModifiedDisposable = @item.onDidChangeModified(modifiedHandler)
+      if Disposable.isDisposable(onDidChangeModifiedDisposable)
+        @subscriptions.add(onDidChangeModifiedDisposable)
+      else
+        console.warn "::onDidChangeModified does not return a valid disposable!"
     else if typeof @item.on is 'function'
       #TODO Remove once old events are no longer supported
       @item.on('modified-status-changed', modifiedHandler)


transated pub/sub convo creation logic to work in chat room add users

--- src/chat_room_view.coffee
+++ src/chat_room_view.coffee
@@ -25,6 +25,11 @@
     $('body').append(@$element)
     @loadPersistentMessages()

+    ## pub/sub for convo creation and convo switch
+    app.pub_sub.on 'user_logged_on', @sync_user_logged_on.bind(this)
+    app.pub_sub.on 'user_logged_off', @sync_user_logged_off.bind(this)
+    app.pub_sub.on 'new_convo', @sync_new_convo.bind(this)
+

     ## for add users view
     @add_user_bar = '
CancelAdd People
'
@@ -63,7 +68,7 @@
   switch_to_persistent_convo: (e) ->
     e.preventDefault()
     e.stopPropagation()
-    if not @switchmode
+    if @menu isnt "convo_switch"
       @$discussion.children().remove()
       @$element.find('textarea.send').remove()
       @$element.find('.mirrordiv').remove()
@@ -74,15 +79,15 @@
           view = new PersistentConversationView(convo, this)
           view.render()
           @$discussion.append(view.$element)
-      @switchmode = true
+      @menu = "convo_switch"
     else
       @render()
       @loadPersistentMessages()
-      @switchmode = false

   add_users_to_convo: (e) ->
     e.preventDefault()
+    @menu = "add_users"
     @new_convo_params = []
     @$discussion.children().remove()
     @$discussion.append('<input class="search" placeholder="Add People">')
@@ -95,9 +100,9 @@

   cancel_add_users: (e) ->
     e.preventDefault()
-    @$discussion.children().remove()
-    @$element.find('.add-user-bar').remove()
+    @render()
     @loadPersistentMessages()
+    @new_convo_params = []

   confirm_new_convo_params: (e) ->
     e.preventDefault()
@@ -133,6 +138,7 @@
     @new_convo_params = []

   render: ->
+    @menu = "chat"
     @$element.children().remove()
     @$element.html(chat_room_template(@convo))
     @$discussion = @$element.find('.discussion')
@@ -277,6 +283,27 @@
         @$element.find('section.conversation').append(@$file_upload)
         @$file_upload.on 'change', @file_upload.bind(this)

+  sync_user_logged_on: (e, user, index, location) ->
+    console.log('i hear logged in')
+    console.log(arguments)
+    if @menu is "add_users"
+      view = new UserView(user, this)
+      view.render()
+      if location is "first" or location is "last"
+        console.log('first or last')
+        @$discussion.children().eq(-1).before(view.$element)
+      else
+        console.log("middle!!!!!")
+        @$discussion.find('li.user').eq(index).before(view.$element)
+
+  sync_user_logged_off: (e, user, index) ->
+    if @menu is "add_users"
+      @$discussion.find('li.user').eq(index).remove()
+      return
+
+
+  sync_new_convo: (e, convo) ->
+


fixed problem with edit buttons

fixed problem with edit buttons

--- app/coffee/modules/base/navurls.coffee
+++ app/coffee/modules/base/navurls.coffee
@@ -62,7 +62,7 @@
         values = _.map(params, (x) -> trim(x.split("=")[1]))
         promises = _.map(values, (x) -> bindOnceP($scope, x))

-        return $q.all.apply($q, promises).then ->
+        return $q.all(promises).then ->
             options = {}
             for item in params
                 [key, value] = _.map(item.split("="), trim)


make sure lazy tree children stack vertically

--- lib/ui/views.coffee
+++ lib/ui/views.coffee
@@ -36,7 +36,7 @@
         return unless client.conn == conn
         getlazy(id).then (children) =>
           body = view.querySelector ':scope > .body'
-          children.map((x) => @render(x, opts)).forEach (x) ->
+          children.map((x) => @render(@tags.div([x]), opts)).forEach (x) ->
             body.appendChild x

   subtree: ({label, child}, opts) ->


Fix #83, category mapping.

Fix #83, category mapping.

--- src/coffee/mapping.coffee
+++ src/coffee/mapping.coffee
@@ -85,9 +85,9 @@
       cat =
         typeId: 'category'
       if _.has(@categories.externalId2id, rawCategory)
-        categories.push @categories.externalId2id[rawCategory]
+        cat.id = @categories.externalId2id[rawCategory]
       else if _.has(@categories.fqName2id, rawCategory)
-        categories.push @categories.fqName2id[rawCategory]
+        cat.id = @categories.fqName2id[rawCategory]
       else if _.has(@categories.name2id, rawCategory)
         if _.contains(@categories.duplicateNames, rawCategory)
           msg =  "[row #{rowIndex}:#{CONS.HEADER_CATEGORIES}] The category '#{rawCategory}' is not unqiue!"
@@ -96,7 +96,11 @@
           else
             @errors.push msg
         else
-          categories.push @categories.name2id[rawCategory]
+          cat.id = @categories.name2id[rawCategory]
+
+      if cat.id
+        categories.push cat
+
       else
         msg = "[row #{rowIndex}:#{CONS.HEADER_CATEGORIES}] Can not find category for '#{rawCategory}'!"
         if @continueOnProblems


SPEED. fixes lessthan3/packages#3700

SPEED. fixes lessthan3/packages#3700

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -247,6 +247,7 @@
     debug_mode = true if args[1] is 'debug'
     log 'DEBUG MODE' if debug_mode
     exit "must specify domain" unless DOMAIN
+    PROCESSED_PACKAGE_SCRIPTS = []

     # connect to DB
     connect (user, db) ->
@@ -336,26 +337,28 @@
           slug = slug_test.exec(site)?[1]
           slug if slug

-        getDomains = (done) ->
+        getDomains = (next) ->
           DOMAINS = []
           db_sites = db.collection 'sites'

-          async.eachSeries SLUGS, ((slug, next) ->
-            db_sites.findOne {'slug': slug}, (err, result) ->
-              return next() if result is null
+          db_sites.find {'slug': {$in: SLUGS}}, {
+            limit: 100000
+          }, (err, results) ->
+            return next err if err
+            for result in results
+              continue if result is null
               url = result.val().settings?.domain?.url
-              return next() unless url
+              continue unless url
               url = "http://#{url}" unless url.match HTTPtest
               if url not in sites and not url.match LT3test
                 DOMAINS.push url
                 log "ADDING: #{url}"
-              next()
-          ), (err) ->
-            done null, DOMAINS
+            next null, DOMAINS

         loadDomainSitemap = (domains, done) ->
           SITES = []
-          async.eachSeries domains, ((domain, next) ->
+
+          domain_iterator = (domain, next) ->
             async.waterfall [
               (_next) -> loadSitemap domain, _next
               (body, _next) -> parseXML body, _next
@@ -363,8 +366,11 @@
               return next() unless results
               SITES.push result for result in results
               next()
-          ), (err) ->
-            done null, SITES
+
+          if debug_mode
+            async.eachSeries domains, domain_iterator, -> done null, SITES
+          else
+            async.each domains, domain_iterator, -> done null, SITES

         async.waterfall [
           (next) -> getDomains next
@@ -378,7 +384,7 @@
         SCRIPTS = []
         SCRIPT_URLS = []

-        async.eachSeries sites, ((site, next) ->
+        site_iterator = (site, next) =>
           get site, (err, resp, body) ->
             if resp.statusCode < 200 or resp.statusCode > 302 or err
               switch body
@@ -400,38 +406,53 @@
                     if url and url not in SCRIPT_URLS
                       script = {type: 'CSS', url, site}
                       SCRIPT_URLS.push url
-                      SCRIPTS.push script
+                      if debug_mode
+                        SCRIPTS.push script
+                      else
+                        getPackageScripts [script], (err, scripts) ->
+                          loadScripts scripts
                   when 'text/javascript'
                     url = attribs.src
                     if url and url not in SCRIPT_URLS
                       script = {type: 'JS', url, site}
                       SCRIPT_URLS.push url
-                      SCRIPTS.push script
+                      if debug_mode
+                        SCRIPTS.push script
+                      else
+                        getPackageScripts [script], (err, scripts) ->
+                          loadScripts scripts
               onend: ->
                 next()
             }
             parser.write body
             parser.end()
-        ), ->
-          done null, SCRIPTS
+
+        if debug_mode
+          async.eachSeries sites, site_iterator, -> done null, SCRIPTS
+        else
+          async.each sites, site_iterator, -> done null, []
+

       # create URLs for package/main.js
       getPackageScripts = (scripts, next) ->
-        pkg_scripts = []
-        for {url, type, site} in scripts
+        return next() unless scripts
+        for script in scripts
+          {url, type, site} = script
           continue unless type is 'CSS'
           pkg = PKGtest.exec(url)?[1]
           continue unless pkg
           pkg_JS = "#{pkg}/main.js"
-          if pkg_JS not in pkg_scripts
+          if pkg_JS not in PROCESSED_PACKAGE_SCRIPTS
             scripts.push {site, type: 'JS', url: pkg_JS}
-            pkg_scripts.push pkg_JS
+            PROCESSED_PACKAGE_SCRIPTS.push pkg_JS

         next null, scripts

       # request CSS and JS scripts
-      loadScripts = (scripts, done) ->
-        async.eachSeries scripts, ((script, next) ->
+      loadScripts = (scripts = [], done) ->
+        return done() unless scripts
+        script_iterator = (script, next) ->
+          return next() unless script
           {url, type, site} = script
           if url.match DOMAINtest
             url = "#{DOMAIN}#{url}"
@@ -448,12 +469,16 @@
               }
               return next()

-            log "#{type} RETRIEVED: #{site}"
+            log "#{type} RETRIEVED: #{site}".green
             cacheCount resp.headers, type

             RAW_DATA.SCRIPTS_LOADED++
             next()
-        ), done
+
+        if debug_mode
+          async.eachSeries scripts, script_iterator, done
+        else
+          async.each scripts, script_iterator, done

       # format errors to github markup, copy to clipboard
       compileErrors = (next) ->


:bug: Fix a typo

:bug: Fix a typo

--- lib/validate.coffee
+++ lib/validate.coffee
@@ -34,7 +34,7 @@
       else
         throw new Error 'Missing html/text field on Linter Response'
       if result.trace
-        throw new Error 'Invalid trace field on Lintere Response' unless result.trace instanceof Array
+        throw new Error 'Invalid trace field on Linter Response' unless result.trace instanceof Array
       else result.trace = null
       result.range = Range.fromObject result.range if result.range?
       result.key = JSON.stringify(result)


Remove empty value/placeholder from single select list

--- src/coffee/cilantro/ui/controls/select.coffee
+++ src/coffee/cilantro/ui/controls/select.coffee
@@ -24,9 +24,6 @@
     # Renders a dropdown of items allowing for selection of a single item
     # from the list.
     class SingleSelectionList extends base.ControlCompositeView
-        options:
-            emptyValueLabel: '---'
-
         className: 'selection-list'

         itemView: SelectionListItem
@@ -68,19 +65,19 @@
                     @collection.reset(resp.values)
                     @ready()

+            @on 'ready', ->
+                # Since the first item is selected immediately by the very
+                # nature of a drow down list without a placeholder, we need to
+                # call the change method when the control originally renders
+                # so the value is set to the default selected option in the
+                # dropdown and the apply(or update) filter button becomes
+                # activated.
+                @change()
+
         onCollectionSync: ->
-            @collection.add
-                label: @options.emptyValueLabel
-                value: ''
-            , at: 0
-
             @render()

-        onSelectionChange: (event) ->
-            @ui.items.children().each (i, el) =>
-                if i > 0
-                    @collection.models[i].set('selected', el.selected)
-
+        onSelectionChange: ->
             @change()

         getField: ->
@@ -90,16 +87,10 @@
             return "exact"

         getValue: ->
-            selection = @collection.findWhere(selected: true)
-
-            if selection?
-                return selection.get('value')
-
-            return null
+            return @ui.items.val()

         setValue: (value) ->
-            @collection.each (model) ->
-                model.set('selected', model.get('value') == value)
+            @ui.items.val(value)

     class MultiSelectionList extends SingleSelectionList


Redirect to /log_in

Redirect to /log_in

--- apps/auth/client/index.coffee
+++ apps/auth/client/index.coffee
@@ -30,7 +30,7 @@
     return if @formIsSubmitting()
     @model.save @serializeForm(),
       success: ->
-        window.location = '/'
+        window.location = '/log_in'
       error: (model, response, options) =>
         @reenableForm()
         @$errors.html @errorMessage(response)


Make sure we scroll the the top when changing page.

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -71,8 +71,10 @@
   # Define routes
   router Steps

-  Steps.run Array '$rootScope', '$http', (scope, http) ->
+  Steps.run Array '$rootScope', '$http', '$window', (scope, http, $window) ->
     scope.startingPoints = []
+
+    scope.$on '$routeChangeSuccess', -> $window.scrollTo 0, 0

     http.get("/tools", {params: {capabilities: "initial"}}).then ({data}) ->
       scope.startingPoints = data.map (tool) -> tool.active = true; tool


matchBy options

matchBy options

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -96,7 +96,7 @@
       .option '--publish', 'When given, all changes will be published immediately'
       .option '--updatesOnly', "Won't create any new products, only updates existing"
       .option '--dryRun', 'Will list all action that would be triggered, but will not POST them to SPHERE.IO'
-      .option '-m, --matchBy [value]', 'Product attribute name which will be used to match products', 'id'
+      .option '-m, --matchBy [value]', 'Product attribute name which will be used to match products. Possible values: id, slug, sku, <custom_attribute_name>. Default: id. Localized attribute types are not supported for <custom_attribute_name> option', 'id'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --csv <file>'
       .action (opts) ->
         GLOBALS.DEFAULT_LANGUAGE = opts.language


Fix dongle undefined

Fix dongle undefined

--- app/src/common/base_application.coffee
+++ app/src/common/base_application.coffee
@@ -199,8 +199,8 @@
       (Try => @onDongleIsInBootloaderMode(dongle)).printError()

   notifyDongleIsUnlocked: () ->
-    DongleLogger().info('Dongle unlocked', dongle.id)
-    (Try => @onDongleIsUnlocked(dongle)).printError()
+    DongleLogger().info('Dongle unlocked', @dongle.id)
+    (Try => @onDongleIsUnlocked(@dongle)).printError()

   onConnectingDongle: (device) ->


:bug: fix double esc

:bug: fix double esc

Change in later version of atom

--- src/atom/streams.coffee
+++ src/atom/streams.coffee
@@ -33,8 +33,9 @@
     }

   cancelCommand: ->
-    resetVimCommandStream = @fromCommand 'atom-text-editor.vim-mode', 'vim-mode:reset-command-mode'
-    coreCancelStream      = @fromCommand 'atom-text-editor', 'core:cancel'
-    resetVimCommandStream.merge(coreCancelStream)
+    # double esc to trigger
+    resetVimStream   = @fromCommand 'atom-text-editor.vim-mode', 'vim-mode:reset-normal-mode'
+    coreCancelStream = @fromCommand 'atom-text-editor', 'core:cancel'
+    resetVimStream.merge(coreCancelStream)
       .bufferWithTimeOrCount(300, 2)
       .filter (x) -> x.length is 2


List View: Small fix to the previous improvement.

--- src/elements/ListView/ListViewTreeNode.coffee
+++ src/elements/ListView/ListViewTreeNode.coffee
@@ -882,7 +882,7 @@
 		tree = @getTree()

 		# Just for usability/accessibility. Use 'up' and 'down' to navigate through rows.
-		element = DOMNodes[0]
+		element = DOMNodes?[0]
 		if element
 			CUI.Events.listen
 				type: "keydown"


Revert _prettify method to split by new lines

--- lib/git.coffee
+++ lib/git.coffee
@@ -9,12 +9,12 @@

 _prettify = (data) ->
   return [] if data is ''
-  data = data.split(/\0/)[...-1]
-  [] = for mode, i in data by 2
-    {mode, path: data[i+1] }
-  # data = data.split(/\n/)
-  # data.map (file) ->
-  #   {mode: file[0], path: file.substring(1).trim()}
+  # data = data.split(/\0/)[...-1]
+  # [] = for mode, i in data by 2
+  #   {mode, path: data[i+1] }
+  data = data.split(/\n/)
+  data.filter((file) -> file isnt '').map (file) ->
+    {mode: file[0], path: file.substring(1).trim()}

 _prettifyUntracked = (data) ->
   return [] if data is ''
@@ -63,7 +63,8 @@
           stderr: (data) -> reject data.toString()
           exit: (code) ->
             if code is 0
-              resolve(if output is '' then true else output)
+              resolve output
+              # resolve(if output is '' then true else output)
             else resolve false
       catch
         notifier.addError 'Git Plus is unable to locate the git command. Please ensure process.env.PATH can access git.'


Remove duplicate devMode

Remove duplicate devMode

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -68,7 +68,7 @@

   resourcePath = path.resolve(resourcePath)

-  {resourcePath, version, devMode, test, devMode, specDirectory, logFile}
+  {resourcePath, version, devMode, test, specDirectory, logFile}

 setupCoffeeScript = ->
   CoffeeScript = null


Fix line number animations

Fix line number animations

--- src/controller.coffee
+++ src/controller.coffee
@@ -2490,7 +2490,7 @@
             div: div
             position:
               x: 0
-              y: lineHeight * line
+              y: lineHeight * line - aceScrollTop + @scrollOffsets.main.y
             vector: new draw.Point(
               0,
               lineHeight * line - (treeView.bounds[line].y + treeView.distanceToBase[line].above - @fontSize) - aceScrollTop + @scrollOffsets.main.y


wrapperElement.clientWidth is the inside dimension.

--- src/controller.coffee
+++ src/controller.coffee
@@ -333,13 +333,13 @@
     resizeBlockMode: ->
       @resizeTextMode()

-      @dropletElement.style.height = "#{@wrapperElement.offsetHeight}px"
+      @dropletElement.style.height = "#{@wrapperElement.clientHeight}px"
       if @paletteEnabled
         @dropletElement.style.left = "#{@paletteElement.offsetWidth}px"
-        @dropletElement.style.width = "#{@wrapperElement.offsetWidth - @paletteWrapper.offsetWidth}px"
+        @dropletElement.style.width = "#{@wrapperElement.clientWidth - @paletteWrapper.offsetWidth}px"
       else
         @dropletElement.style.left = "0px"
-        @dropletElement.style.width = "#{@wrapperElement.offsetWidth}px"
+        @dropletElement.style.width = "#{@wrapperElement.clientWidth}px"

       @resizeGutter()

@@ -1762,12 +1762,12 @@
           @redrawTextInput()

   Editor::resizeAceElement = ->
-    width = @wrapperElement.offsetWidth
+    width = @wrapperElement.clientWidth
     if @showPaletteInTextMode and @paletteEnabled
       width -= @paletteElement.offsetWidth

     @aceElement.style.width = "#{width}px"
-    @aceElement.style.height = "#{@wrapperElement.offsetHeight}px"
+    @aceElement.style.height = "#{@wrapperElement.clientHeight}px"

   last_ = (array) -> array[array.length - 1]

@@ -3100,7 +3100,7 @@
       else
         @mainScroller.style.overflowX = 'hidden'
       @mainScroller.style.overflowY = 'hidden'
-      @dropletElement.style.width = @wrapperElement.offsetWidth + 'px'
+      @dropletElement.style.width = @wrapperElement.clientWidth + 'px'

       @currentlyUsingBlocks = false; @currentlyAnimating = @currentlyAnimating_suppressRedraw = true

@@ -3263,7 +3263,7 @@
       setTimeout (=>
         # Hide scrollbars and increase width
         @mainScroller.style.overflow = 'hidden'
-        @dropletElement.style.width = @wrapperElement.offsetWidth + 'px'
+        @dropletElement.style.width = @wrapperElement.clientWidth + 'px'

         @redrawMain noText: true


Export Registry as GrammarRegistry

Export Registry as GrammarRegistry

--- src/first-mate.coffee
+++ src/first-mate.coffee
@@ -1,2 +1,3 @@
 module.exports =
   ScopeSelector: require './scope-selector'
+  GrammarRegistry: require './registry'


only one response

only one response

--- src/github-tfs.coffee
+++ src/github-tfs.coffee
@@ -153,7 +153,7 @@
   asciiTable = new AsciiTable()

   tableWrapper = ""
-  if robot.adapterName ? "slack"
+  if robot.adapterName ? "slack"
     tableWrapper = "```"

   robot.logger.debug robot.adapterName
@@ -224,12 +224,13 @@
         else
           robot.logger.debug body
           result = JSON.parse body
+          resultHeader = ""
           if tfsCollection? and tfsCollection isnt ""
-            res.reply "I found #{result.count} results for #{tfsProject} in #{tfsCollection}"
+            resultHeader = "I found #{result.count} results for #{tfsProject} in #{tfsCollection}\n"
           else
-            res.reply "I found #{result.count} results for #{tfsProject}"
+            resultHeader = "I found #{result.count} results for #{tfsProject}\n"
           tableResult = "#{tableWrapper}#{asciiTable.buildTable(tableDefinition, result.value)}#{tableWrapper}"
-          res.reply tableResult
+          res.reply "#{resultHeader}#{tableResult}"

   ########################################
   # POST the response and display a table


Add search action to app.songs.Store.

Add search action to app.songs.Store.

--- app/client/js/songs/store.coffee
+++ app/client/js/songs/store.coffee
@@ -16,17 +16,27 @@
   constructor: (@dispatcher, @routes, @xhr, @userStore) ->
     super 'songs'

-    ###* @type {Array.<app.songs.Song>} ###
+    ###*
+      @type {Array.<app.songs.Song>}
+    ###
     @recentlyUpdatedSongs = []

-    ###* @type {Array.<app.songs.Song>} ###
+    ###*
+      @type {Array.<app.songs.Song>}
+    ###
     @songsByUrl = []
+
+    ###*
+      @type {Array.<app.songs.Song>}
+    ###
+    @foundSongs = []

     @dispatcherIndex = @dispatcher.register @handleAction_.bind @

   @Actions:
-    PUBLISH_SONG: 'publish-song'
-    UNPUBLISH_SONG: 'unpublish-song'
+    PUBLISH_SONG: 'publishsong'
+    SEARCH: 'searchsongs'
+    UNPUBLISH_SONG: 'unpublishsong'

   handleAction_: (action, payload) ->
     switch action
@@ -34,6 +44,12 @@
         @xhr
           .put @routes.api.songs.id.url(id: payload.song.id), payload.json
           .then => @userStore.setSongPublisher payload.song
+      when Store.Actions.SEARCH
+        @xhr
+          .get @routes.api.songs.search.url null, query: payload.query
+          .then (songs) =>
+            @fromJson foundSongs: songs
+            @notify()
       when Store.Actions.UNPUBLISH_SONG
         @xhr
           .delete @routes.api.songs.id.url(id: payload.song.id)
@@ -43,6 +59,8 @@
     @override
   ###
   fromJson: (json) ->
+    if json.foundSongs
+      @foundSongs = json.foundSongs.map @instanceFromJson app.songs.Song
     if json.recentlyUpdatedSongs
       @recentlyUpdatedSongs = json.recentlyUpdatedSongs.map @instanceFromJson app.songs.Song
     if json.songsByUrl
@@ -50,6 +68,7 @@

   ###*
     @param {app.songs.Song} song
+    @return {goog.Promise}
   ###
   publish: (song) ->
     publishedSong = @createPublishedSong_ song
@@ -61,6 +80,11 @@
       song: song
       json: publishedSong.toJson()

+  ###*
+    @param {app.songs.Song} song
+    @return {app.songs.Song}
+    @private
+  ###
   createPublishedSong_: (song) ->
     json = song.toJson()
     json.publisher = @userStore.user.uid
@@ -69,7 +93,16 @@

   ###*
     @param {app.songs.Song} song
+    @return {goog.Promise}
   ###
   unpublish: (song) ->
     @dispatcher.dispatch Store.Actions.UNPUBLISH_SONG,
       song: song
+
+  ###*
+    @param {string} query
+    @return {goog.Promise}
+  ###
+  search: (query) ->
+    @dispatcher.dispatch Store.Actions.SEARCH,
+      query: query


Update Algolia API key

Update Algolia API key

--- app/core/services/algolia.coffee
+++ app/core/services/algolia.coffee
@@ -1,4 +1,4 @@
-client = algoliasearch("JJM5H2CHJR", "50382fc2f7fa69c67e8233ace7cd7c4c")
+client = algoliasearch("JJM5H2CHJR", "83e56b128e93aff5f0e69d6730270ce4")

 module.exports =
   client: client


Changed the way numbering works on collect goals.

--- app/lib/world/GoalManager.coffee
+++ app/lib/world/GoalManager.coffee
@@ -139,7 +139,7 @@
         @initGoalState(state, [[], keepFrom.keepFromLocation?.who], 'arrived')
       @initGoalState(state, [goal.getToLocations?.who, goal.keepFromLocations?.who], 'arrived')
       @initGoalState(state, [goal.leaveOffSides?.who, goal.keepFromLeavingOffSides?.who], 'left')
-      @initGoalState(state, [goal.collectThangs?.who, goal.keepFromCollectingThangs?.who], 'collected')
+      @initGoalState(state, [goal.collectThangs?.targets, goal.keepFromCollectingThangs?.targets], 'collected')
       @goalStates[goal.id] = state

   onThangDied: (e, frameNumber) ->
@@ -185,7 +185,7 @@
   checkCollected: (goalID, who, targets, thang, itemID, frameNumber) ->
     return unless itemID in targets
     return unless thang.id in who or thang.team in who
-    @updateGoalState(goalID, thang.id, 'collected', frameNumber)
+    @updateGoalState(goalID, itemID, 'collected', frameNumber)

   wrapUpGoalStates: (finalFrame) ->
     for goalID, state of @goalStates


Remove my console.log

Remove my console.log

--- src/configfinder.coffee
+++ src/configfinder.coffee
@@ -85,7 +85,6 @@

     config = getConfig(dir)

-    console.log('config', config)
     if config
         config = expandModuleNames(dir, config)


Organize isXX methods

Organize isXX methods

--- src/course/model.coffee
+++ src/course/model.coffee
@@ -20,9 +20,10 @@
     @set(attributes)
     _.bindAll(@, '_onRegistered', '_onConfirmed')

-
-  isPending: ->
-    @status is "pending"
+  isRegistered: -> not (@isIncomplete() or @isPending())
+  isIncomplete: -> not (@name or @to)
+  isPending: ->    @status is "pending"
+  cancelJoin: ->   @user.removeCourse(@)

   description: ->
     if @isIncomplete() # still fetching
@@ -32,7 +33,6 @@
     else
       "#{@name} #{_.first(@periods).name} period"

-  isIncomplete: -> not (@name or @to)

   set: (attributes) ->
     _.extend(@, attributes)
@@ -50,9 +50,7 @@

   confirm: ->
     api.channel.once "course.#{@id}.receive.confirmation.*", @_onConfirmed
-    api.channel.emit("course.#{@id}.send.confirmation", data: {
-      id: @id
-    })
+    api.channel.emit("course.#{@id}.send.confirmation", data: { id: @id })

   _onConfirmed:  ({data}) ->
     _.extend(@, data.to.course)


Support tool definitions that are zero padded eg T01 = T1

--- src/drill-parser.coffee
+++ src/drill-parser.coffee
@@ -59,11 +59,13 @@
     # tool definition
     else if ( code = block.match(/T\d+/)?[0] )
       # tool definition
+      toolCode = "T#{Number code[1..]}"
       if ( dia = block.match(/C[\d\.]+(?=.*$)/)?[0] )
         dia = Number dia[1..]
         command.tool = {}
-        command.tool[code] = { dia: dia }
-      else command.set = { currentTool: code }
+        command.tool[toolCode] = { dia: dia }
+      else
+        command.set = { currentTool: toolCode }

     # allow this to be tacked on the end of a command to be lenient
     # we're assuming trailing zero suppression, so we only care if the opposite


Using elapsed time in delayed events

Using elapsed time in delayed events

--- src/engine_delay.coffee
+++ src/engine_delay.coffee
@@ -28,14 +28,14 @@

   @name delay
   @methodOf Engine#
-  @param {Number} steps The number of steps to wait before executing the callback
+  @param {Number} seconds The number of steps to wait before executing the callback
   @param {Function} callback The callback to be executed.

   @returns {Engine} self
   ###
-  delay: (steps, callback) ->
+  delay: (seconds, callback) ->
     delayedEvents.push
-      delay: steps
+      delay: seconds
       callback: callback

     return self


Fix names for level 2 turtles

Fix names for level 2 turtles

--- src/game/example.coffee
+++ src/game/example.coffee
@@ -14,6 +14,10 @@
   editable: true
 }

+me2 = my.dup me
+my.name = 'yu'
+me2.position = [2,2]
+
 exit = {
   name: 'exit'
   shape: 'diamond'
@@ -22,9 +26,6 @@
   fill: 'red'
   behavior: {turtle: ['victory']}
 }
-
-me2 = my.dup me
-me2.position = [2,2]

 exports.game = {
   name: "Example Game"


Talker adapter, fix user initialization

Talker adapter, fix user initialization

- userForId should get the full user object as second argument
- the "users" event should populate the user data

It fix scripts that use robot#userForName, like the role script

--- src/hubot/talker.coffee
+++ src/hubot/talker.coffee
@@ -32,19 +32,23 @@
         bot.write {type: "ping"}
       , 25000

+    bot.on "Users", (message)->
+      for user in message.users
+        self.userForId(user.id, user)
+
     bot.on "TextMessage", (message)->
       console.log message
-      author = self.userForId(message.user.id)
+      author = self.userForId(message.user.id, message.user)
       self.receive new Robot.TextMessage(author, message.content.replace(/^\s*@hubot\s+/, "Hubot: "))

     bot.on "EnterMessage", (message) ->
       console.log message
-      author = self.userForId(message.user.id)
+      author = self.userForId(message.user.id, message.user)
       self.receive new Robot.EnterMessage(author)

     bot.on "LeaveMessage", (message) ->
       console.log message
-      author = self.userForId(message.user.id)
+      author = self.userForId(message.user.id, message.user)
       self.receive new Robot.LeaveMessage(author)

     @bot = bot
@@ -71,14 +75,12 @@
     #callback
     @socket.addListener 'data', (data) ->
       console.log data
-
-      if data.indexOf('"type":"users"') > 0
-        message = {"type": "none"}
-      else
-        message = JSON.parse(data)
-
+      message = JSON.parse(data)
+
       if message.type == "connected"
         console.log "Succesfully connected, listing users:"
+      if message.type == "users"
+        self.emit "Users", message
       if message.type == "message"
         self.emit "TextMessage", message
       if message.type == "join"


allowing option for differing search display and token display

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -114,7 +114,7 @@

         # Populate results list.
         for result in results
-          @resultList.append("
#{result.name}
")
+          @resultList.append("
#{result.name}
")

         # Re-select previously selected suggestion.
         $("li[data-id=" + selected.attr("data-id") + "]").addClass("selected") unless selected is null
@@ -189,8 +189,10 @@
     id = result.data('id')
     return unless id

+    tokenDisplayName = result.data('tokenDisplayName')
+
     # Create new token.
-    name = result.html()
+    name = tokenDisplayName || result.html()
     token = '' + name + ''

     # Re-place the caret in the editor (necessary if the user clicked on the autocomplete menu).


No need to add "app" to search paths

No need to add "app" to search paths

--- atom/renderer/lib/init.coffee
+++ atom/renderer/lib/init.coffee
@@ -11,9 +11,6 @@
 # of Atom's built-in libraries.
 globalPaths = Module.globalPaths
 globalPaths.push path.resolve(__dirname, '..', 'api', 'lib')
-# And also app.
-globalPaths.push path.join(process.resourcesPath, 'app')
-globalPaths.push path.join(process.resourcesPath, 'app.asar')

 # Import common settings.
 require path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')


Use fat arrow

Use fat arrow

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -117,7 +117,7 @@
             #     cb null, "no manifest"

         manifests = @constructor.manifests
-        async.parallel stack, (err, results)->
+        async.parallel stack, (err, results)=>
           warn err if err
           results.forEach (rawManifest)->
             # if rawManifest.substr(0,1) is '{'


test: Test how Swagger transaction names are compiled currently

--- test/integration/compile-swagger-test.coffee
+++ test/integration/compile-swagger-test.coffee
@@ -96,20 +96,23 @@
     )
   )

-  describe('with multiple responses', ->
-    # Multiple responses is the closest we can get with Swagger to something
-    # like transaction examples ¯\_(ツ)_/¯
+  describe.only('with multiple responses', ->
+    transactions = undefined
+    filename = 'apiDescription.json'
+    detectTransactionExamples = sinon.spy(require('../../src/detect-transaction-examples'))

-    detectTransactionExamples = sinon.spy(require('../../src/detect-transaction-examples'))
-    compilationResult = undefined
-    transaction = undefined
+    expected = [
+      {status: 200}
+      {status: 400}
+      {status: 500}
+    ]

     beforeEach((done) ->
       stubs = {'./detect-transaction-examples': detectTransactionExamples}

-      compileFixture(fixtures.multipleResponses.swagger, {stubs}, (args...) ->
+      compileFixture(fixtures.multipleResponses.swagger, {filename, stubs}, (args...) ->
         [err, compilationResult] = args
-        transaction = compilationResult.transactions[0]
+        transactions = compilationResult.transactions
         done(err)
       )
     )
@@ -117,5 +120,28 @@
     it('detection of transaction examples was not called', ->
       assert.isFalse(detectTransactionExamples.called)
     )
+    it('expected number of transactions was returned', ->
+      assert.equal(transactions.length, expected.length)
+    )
+
+    for expectations, i in expected
+      do (expectations, i) ->
+        context("origin of transaction ##{i + 1}", ->
+          it('uses URI as resource name', ->
+            assert.equal(transactions[i].origin.resourceName, '/honey')
+          )
+          it('uses method as action name', ->
+            assert.equal(transactions[i].origin.actionName, 'GET')
+          )
+        )
+
+        context("pathOrigin of transaction ##{i + 1}", ->
+          it('uses URI as resource name', ->
+            assert.equal(transactions[i].pathOrigin.resourceName, '/honey')
+          )
+          it('uses method as action name', ->
+            assert.equal(transactions[i].pathOrigin.actionName, 'GET')
+          )
+        )
   )
 )


Improvements with sessionID authentication

--- client/landing/app/MainApp/kite/newkite.coffee
+++ client/landing/app/MainApp/kite/newkite.coffee
@@ -9,11 +9,12 @@
     { @kiteName, @correlationName, @kiteKey } = options
     @localStore   = new Store
     @remoteStore  = new Store
+    @tokenStore = {}
     @autoReconnect = true
     @readyState = NOTREADY
     @kontrolEndpoint = "http://127.0.0.1:4000/request" #kontrol addr
+    @addr = ""
     @token = ""
-    @addr = ""
     @initBackoff options  if @autoReconnect
     @connect()

@@ -25,6 +26,7 @@
   bound: Bongo.bound

   connectDirectly:->
+    console.log "trying to connect to #{@addr}"
     @ws = new WebSocket "ws://#{@addr}/sock"
     @ws.onopen    = @bound 'onOpen'
     @ws.onclose   = @bound 'onClose'
@@ -35,9 +37,9 @@
     requestData =
       username   : "#{KD.nick()}"
       remoteKite : @kiteName
-      token      : KD.remote.getSessionToken()
+      sessionID  : KD.remote.getSessionToken()

-    # $.ajax was used here...
+    # $.ajax was used here... added if we want to replace the following in the future
     xhr = new XMLHttpRequest
     xhr.open "POST", @kontrolEndpoint, yes
     xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
@@ -46,6 +48,7 @@
        if xhr.status is 200
          data = JSON.parse xhr.responseText
          @token = data[0].token
+         console.log {@token}
          @addr = data[0].addr
          @connectDirectly()

@@ -112,7 +115,6 @@
       @remoteStore.get callbackId

   handleRequest: (method, args) ->
-    console.log {method}, {args}
     @scrub method, args, (scrubbed) =>
       messageString = JSON.stringify(scrubbed)
       @ready => @send scrubbed
@@ -125,13 +127,16 @@
       callback scrubbed

   tell:(options, callback) ->
-    @handleRequest options.method, [options, callback]
+    @ready =>
+      # token is needed to initiate a valid session
+      options.token = @token
+      options.username  = "#{KD.nick()}"
+      @handleRequest options.method, [options, callback]

   send: (data) ->
     try
       if @readyState is READY
         @ws.send JSON.stringify data
-        # console.log "sending", {data}
       else
         # console.log "slow down ... I'm still trying to reconnect!"
     catch e


KodingKey: remove register host name and key function from kodingrouter

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -260,28 +260,6 @@
     createContentHandler       = @bound 'createContentDisplayHandler'
     createStaticContentHandler = @bound 'createStaticContentDisplayHandler'

-    registerHostNameAndKey = ->
-      $.cookie "register-to-koding-client", yes
-      unless KD.isLoggedIn()
-        message = "Please login to proceed to the next step"
-        modal = new KDBlockingModalView
-          title        : "Koding Client Registration"
-          content      : "
#{message}
"
-          height       : "auto"
-          overlay      : yes
-          buttons      :
-            "Go to Login" :
-              style       : "modal-clean-gray"
-              callback    : ->
-                modal.destroy()
-                KD.utils.wait 5000, window.location.pathname = "/Login"
-            "Cancel" :
-              style       : "modal-cancel"
-              callback    : ->
-                modal.destroy()
-      else
-        location.href = "/"
-
     routes =

       '/'      : handleRoot
@@ -376,7 +354,7 @@
         else
           @handleRoute '/Login'

-      '/:name?/RegisterHostKey': registerHostNameAndKey
+      '/:name?/RegisterHostKey': KiteHelper.initiateRegistiration

       '/member/:username': ({params:{username}})->
         @handleRoute "/#{username}", replaceState: yes


About: added team

About: added team

--- client/landing/app/coffee/about/AppView.coffee
+++ client/landing/app/coffee/about/AppView.coffee
@@ -2,6 +2,7 @@
 AboutListItem = require './aboutlistitem'
 JobsView      = require './jobsview'
 FooterView    = require './../home/footerview'
+TEAM          = require './team'
 module.exports = class AboutView extends JView

   constructor:->
@@ -15,7 +16,7 @@
       scrollView  : no
       wrapper     : no
     ,
-      items       : KD.team.active
+      items       : TEAM.active

     @memberList = @activeController.getView()

@@ -67,7 +68,7 @@

Talent

-            #{KD.team.active.length} Koders
+            #{TEAM.active.length} Koders


must return a value or input is uncontrolled

--- client/lanes/components/grid/EditingMixin.cjsx
+++ client/lanes/components/grid/EditingMixin.cjsx
@@ -55,7 +55,7 @@
         </div>

     getFieldValue: (field) ->
-        @props.model[field.id]
+        @props.model[field.id] || ''

     onDateFieldChange: (date, field) ->
         @props.model[field.id] = date


Modify wage theft report info endpoint

Modify wage theft report info endpoint

--- src/routes/index.coffee
+++ src/routes/index.coffee
@@ -19,10 +19,10 @@
   res.json({
     post_url: "https://docs.google.com/forms/d/1ArpKfNDrrsdNl05VLkT3tECjA0vctk8wnVrMGQSIqIE/formResponse"
     mapping:
-      "employer-name": "entry.1223479717"
-      "employee-name": "entry.1255459603"
-      "employee-email": "entry.570498028"
-      "employee-phone": "entry.229881285"
+      "employer[name]": "entry.1223479717"
+      "employee[name]": "entry.1255459603"
+      "employee[email]": "entry.570498028"
+      "employee[phone]": "entry.229881285"
   }).end()

 router.use '/employer_size', employer_size_router


Fix spec (debug) and make async specs sane

--- apps/artist_2/test/routes.coffee
+++ apps/artist_2/test/routes.coffee
@@ -1,4 +1,3 @@
-_ = require 'underscore'
 sinon = require 'sinon'
 rewire = require 'rewire'
 Backbone = require 'backbone'
@@ -14,6 +13,7 @@
 describe 'Artist routes', ->
   beforeEach ->
     routes.__set__ 'metaphysics', @metaphysics = sinon.stub()
+    @metaphysics.debug = sinon.stub()
     @metaphysics.returns Q.resolve artist: artistJSON

     @req = params: { id: 'foo' }, get: (->), query: {}
@@ -25,26 +25,23 @@
       locals: sd: APP_URL: 'http://localhost:5000', CURRENT_PATH: '/artist_2/jeff-koons-1'

   describe '#index', ->
-    it 'renders the artist template', (done) ->
+    it 'renders the artist template', ->
       routes.index @req, @res
-      _.defer => _.defer =>
-        @res.render.args[0][0].should.equal 'index'
-        @res.render.args[0][1].artist.id.should.equal 'jeff-koons-1'
-        @res.render.args[0][1].artist.name.should.equal 'Jeff Koons'
-        done()
+        .then =>
+          @res.render.args[0][0].should.equal 'index'
+          @res.render.args[0][1].artist.id.should.equal 'jeff-koons-1'
+          @res.render.args[0][1].artist.name.should.equal 'Jeff Koons'

-    it 'bootstraps the artist', (done) ->
+    it 'bootstraps the artist', ->
       routes.index @req, @res
-      _.defer => _.defer =>
-        @res.locals.sd.ARTIST.should.equal artistJSON
-        done()
+        .then =>
+          @res.locals.sd.ARTIST.should.equal artistJSON

-    it 'redirects to canonical url', (done) ->
+    it 'redirects to canonical url', ->
       @res.locals.sd.CURRENT_PATH = '/artist_2/bar'
       routes.index @req, @res
-      _.defer => _.defer =>
-        @res.redirect.args[0][0].should.equal '/artist_2/jeff-koons-1'
-        done()
+        .then =>
+          @res.redirect.args[0][0].should.equal '/artist_2/jeff-koons-1'

   describe '#follow', ->
     beforeEach ->


Add MUST: note

Add MUST: note

--- src/schema/index.coffee
+++ src/schema/index.coffee
@@ -32,6 +32,7 @@
 # name. This makes it easy to develop and use Ezekiel, because you can always trust that
 # alias will be there. If you're not using aliases and/or virtual objects, then no harm done, all
 # aliases will equal names, and you're good to go.
+# MUST: remove the ability to change alias / name once schema object is instantiated
 class AliasedObject extends DbObject
     constructor: (schema) ->
         unless schema.name? || schema.alias?


Give the help route some styling

Give the help route some styling

--- src/scripts/help.coffee
+++ src/scripts/help.coffee
@@ -5,8 +5,50 @@
 #   hubot help - Displays all of the help commands that Hubot knows about.
 #   hubot help <query> - Displays all help commands that match <query>.
 #
+# URLS:
+#   /hubot/help
+#
 # Notes:
 #   These commands are grabbed from comment blocks at the top of each file.
+
+helpContents = (name, commands) ->
+
+  """
+
+
+  <title>#{name} Help</title>
+
+
+
+
#{name} Help

+

+      #{commands}
+

+
+
+  """

 module.exports = (robot) ->
   robot.respond /help\s*(.*)?$/i, (msg) ->
@@ -19,11 +61,15 @@
     emit = cmds.join "\n"

     unless robot.name.toLowerCase() is 'hubot'
-      emit = emit.replace /(H|h)ubot/g, robot.name
+      emit = emit.replace /hubot/ig, robot.name

     msg.send emit

   robot.router.get '/hubot/help', (req, res) ->
     cmds = robot.helpCommands()
+    emit = "
#{cmds.join '

'}
"
+
+    emit = emit.replace /hubot/ig, "#{robot.name}"
+
     res.setHeader 'content-type', 'text/html'
-    res.end cmds.join '
'
+    res.end helpContents robot.name, emit


being explicit with require dependencies in the shim - OCD

--- src/scripts/main.coffee
+++ src/scripts/main.coffee
@@ -4,25 +4,25 @@
 		'libs/angular-resource'              : 'libs/angular-resource.min'
 		'libs/angular-route'                 : 'libs/angular-route.min'
 	shim:
-		'controllers/gitHubController'       : deps: ['app', 'services/gitHubService']
-		'controllers/personController'       : deps: ['app', 'services/personService']
-		'controllers/personDetailsController': deps: ['app', 'services/personService']
-		'controllers/searchHistoryController': deps: ['app', 'services/messageService']
-		'directives/ngController'            : deps: ['app']
-		'directives/tab'                     : deps: ['app']
-		'directives/tabs'                    : deps: ['app', 'directives/tab']
-		'filters/twitterfy'                  : deps: ['app']
-		'interceptors/dispatcher'            : deps: ['app']
+		'controllers/gitHubController'       : deps: ['libs/angular', 'app', 'services/gitHubService']
+		'controllers/personController'       : deps: ['libs/angular', 'app', 'services/personService']
+		'controllers/personDetailsController': deps: ['libs/angular', 'app', 'services/personService']
+		'controllers/searchHistoryController': deps: ['libs/angular', 'app', 'services/messageService']
+		'directives/ngController'            : deps: ['libs/angular', 'app']
+		'directives/tab'                     : deps: ['libs/angular', 'app']
+		'directives/tabs'                    : deps: ['libs/angular', 'app', 'directives/tab']
+		'filters/twitterfy'                  : deps: ['libs/angular', 'app']
+		'interceptors/dispatcher'            : deps: ['libs/angular', 'app']
 		'libs/angular-resource'              : deps: ['libs/angular']
 		'libs/angular-route'                 : deps: ['libs/angular']
-		'services/gitHubService'             : deps: ['app', 'services/messageService']
-		'services/messageService'            : deps: ['app']
-		'services/personService'             : deps: ['app']
+		'services/gitHubService'             : deps: ['libs/angular', 'libs/angular-resource', 'app', 'services/messageService']
+		'services/messageService'            : deps: ['libs/angular', 'app']
+		'services/personService'             : deps: ['libs/angular', 'libs/angular-resource', 'app']
 		'app'                                : deps: ['libs/angular', 'libs/angular-resource', 'libs/angular-route']
-		'bootstrap'                          : deps: ['app']
-		'routes'                             : deps: ['app']
-		'run'                                : deps: ['app']
-		'views'                              : deps: ['app']
+		'bootstrap'                          : deps: ['libs/angular', 'app']
+		'routes'                             : deps: ['libs/angular', 'libs/angular-route', 'app']
+		'run'                                : deps: ['libs/angular', 'app']
+		'views'                              : deps: ['libs/angular', 'app']
 	[
 		'require'
 		'controllers/gitHubController'


network errors should be passed to callbacks.

--- lib/Paypal.coffee
+++ lib/Paypal.coffee
@@ -148,7 +148,7 @@
       if response['ACK'] is 'Failure'
         callback response['L_LONGMESSAGE0']
       else
-        callback null, response
+        callback err, response

   # Returns subscription information for an already created subscription by
   # invoking the "GetRecurringPaymentsProfileDetails" method in the PayPal API.
@@ -182,7 +182,7 @@
       if response['ACK'] is 'Failure'
         callback response['L_LONGMESSAGE0']
       else
-        callback null, response
+        callback err, response

   # Modifies the state of an existing subscription by invoking the
   # ManageRecurringPaymentsProfileStatus on the PayPal API.
@@ -223,7 +223,7 @@
       if response['ACK'] is 'Failure'
         callback response['L_LONGMESSAGE0']
       else
-        callback null, response
+        callback err, response

   # Performs the actual API request to the PayPal API endpoint.
   #