integration tests refactoring

integration tests refactoring

--- test/integrationSpec.coffee
+++ test/integrationSpec.coffee
@@ -1,9 +1,6 @@
 define ['main'], (JasmineSugar) ->
-  fdescribe 'integration tests with JasmineEmulator(JE)', ->
-    context                 =
-    collectionInIt          =
-    collectionInDescribe    =
-    collectionOutOfDescribe = undefined
+  describe 'integration tests with JasmineEmulator(JE)', ->
+    context = undefined

     beforeEach ->
       Jasmine = getEnv: -> JE
@@ -11,65 +8,20 @@

       JasmineSugar.setup(context)

-      context.describe 'test', ->
-        collection.is 'something'
-
-        @it -> # using JasmineSugar it method ;)
-          collectionInIt = collection
-        collectionInDescribe = collection
-
-      collectionOutOfDescribe = collection if collection?
-
-      JE.run(context)
-
-      collectionOutOfTestCase = collection if collection?
-
-    describe 'collection in it block', ->
-      it 'should be defined', ->
-        expect(collectionInIt).toBeDefined()
-
-      it 'should be eq something', ->
-        expect(collectionInIt).toEqual 'something'
-
-    describe 'collection in describe block', ->
-      it 'should be defined', ->
-        expect(collectionInDescribe).toBeDefined()
-
-      it 'should be DSL object', ->
-        expect(collectionInDescribe).toHaveProperties ['is']
-
-    describe 'collection in outer context', ->
-      it ''
-
-    it 'with plain type one declaration', ->
-      context.describe 'test', ->
-
-        collection.is 'something'
-
-        @it -> # using JasmineSugar it method ;)
-          # jasmine it's context
-          expect(collection).toBeDefined()
-          expect(collection).toEqual 'something'
-
-        # jasmine describe's context
-        expect(collection?).toBeTruthy()
-        expect(collection).toHaveProperties ['is']
-
-      # outer context before tests run
-      expect(collection?).toBeFalsy()
-
-      JE.run(context)
-
-      # outer context after tests run
-      expect(collection?).toBeFalsy()
-
-    it 'with plain type multiple declarations', ->
+    it 'without dependencies', ->
       context.describe 'test', ->

         another   .is 'something else'
         collection.is 'something'

         @it -> # using JasmineSugar it method )
+          # jasmine it context
+          expect(collection).toBeDefined()
+          expect(collection).toEqual 'something'
+          expect(another).toBeDefined()
+          expect(another).toEqual 'something else'
+
+        @it 'anoter one', -> # using JasmineSugar it method )
           # jasmine it context
           expect(collection).toBeDefined()
           expect(collection).toEqual 'something'
@@ -92,27 +44,19 @@
       expect(collection?).toBeFalsy()
       expect(another?).toBeFalsy()

-    xit 'with plain type multiple declarations when collide with outer variables'
+    xit 'without dependencies when collide with outer variables'

-    xit 'with plain type multiple declarations when collide with context methods names'
+    xit 'without dependencies when collide with context methods names'

-    xit 'with function one declaration'
+    xit 'with dependencies in latter describe'

-    xit 'with function multiple declarations'
-
-    xit 'with function multiple declarations when collide with outer variables'
-
-    xit 'with function multiple declarations when collide with context methods names'
+    xit 'with dependencies in parent and latter describes'

 # todo: specs that spies beforeEach and afterEach calls - in callbackFactorySpec
 # todo: specs that checks Store changes between beforeEach and afterEach calls - in callbackFactorySpec
 #   for that case may be suitable to use in callbackFactorySpec JE
 #   and in JE implement methods which allows to separately run befores, tests and afters
 # todo: specs with multiple it calls in one describe
-# todo: rewrite integration tests:
-#   - move JE describe in beforeEach
-#   - retrieve needed vars from multiple places in JE's describe
-#   - and then test this in it blocks

 ## Planning DSL example:
 #


Fix createClient deprecation

Fix createClient deprecation

--- test/lib/test_helper.coffee
+++ test/lib/test_helper.coffee
@@ -65,8 +65,7 @@
 exports.createRequester = createRequester = (port) ->
   (method, path, headers, callback) ->
     callback = headers unless callback
-    client   = http.createClient port
-    request  = client.request method, path, headers
+    request  = http.request {method, path, port, headers}

     if data = headers.data
       delete headers.data


Update autom config

Update autom config

--- atom/atom/config.cson
+++ atom/atom/config.cson
@@ -48,7 +48,6 @@
     audioBeep: false
     disabledPackages: [
       "feedback"
-      "symbols-view"
       "save-session"
       "file-type-icons"
       "autocomplete-bibtex"
@@ -77,7 +76,6 @@
       space: "•"
       tab: "›"
     showInvisibles: true
-    tabType: "soft"
     useHardwareAcceleration: true
   "exception-reporting":
     userId: "7652af63-0a80-ab67-85f4-9d3eebd7294e"
@@ -143,7 +141,7 @@
     locked: true
     silent: true
   "one-dark-ui":
-    fontSize: 10
+    fontSize: 13
     layoutMode: "Compact"
   "one-light-ui":
     fontSize: 10


changed log level

changed log level

--- both/publisher.coffee
+++ both/publisher.coffee
@@ -6,7 +6,7 @@
     if Meteor.isServer
       Publisher._log
         msg: 'setupTemplate called on server'
-        level: 'warn'
+        level: 'info'

     if Meteor.isClient
       template = Template[templateName]


adding return values for destructuring assignment.

--- nodes.coffee
+++ nodes.coffee
@@ -617,7 +617,9 @@
         idx: new LiteralNode(idx) unless typeof idx is 'object'
         val: new ValueNode(new LiteralNode(val_var), [new access_class(idx)])
       assigns.push(new AssignNode(obj, val).compile(o))
-    assigns.join("\n")
+    code: assigns.join("\n")
+    code += '\n' + @idt() + 'return ' + @variable.compile(o) + ';' if o.returns
+    code

   compile_splice: (o) ->
     name:   @variable.compile(merge(o, {only_first: true}))


resolving merge

resolving merge

--- nodes.coffee
+++ nodes.coffee
@@ -576,13 +576,13 @@
     return (if @front then '({})' else '{}') unless props.length
     for prop, i in props
       if prop instanceof Splat or (prop.variable or prop).base instanceof Parens
-        rest = @properties.splice i
+        rest = props.splice i, 1/0
         break
     idt         = o.indent += TAB
     nonComments = (prop for prop in @properties when prop not instanceof Comment)
     lastNoncom  = last nonComments
-    props = for prop, i in @properties
-      join = if i is @properties.length - 1
+    props = for prop, i in props
+      join = if i is props.length - 1
         ''
       else if prop is lastNoncom or prop instanceof Comment
         '\n'


michaelficarra's suggestion to make object comprehensions' hasOwnProperty check a continue, instead of a wrapped if.

--- nodes.coffee
+++ nodes.coffee
@@ -1262,6 +1262,7 @@
     rvar:           scope.freeVariable() unless topLevel
     ivar:           if range then name else if codeInBody then scope.freeVariable() else index or scope.freeVariable()
     varPart:        ''
+    guardPart:      ''
     body:           Expressions.wrap([@body])
     if range
       sourcePart:   source.compileVariables(o)
@@ -1292,14 +1293,11 @@
     else
       varPart:      (namePart or '') and (if @pattern then namePart else "${@idt(1)}$namePart;\n")
     if @object
-      if @raw
-        forPart:    "$ivar in $svar"
-      else
-        forPart:    "$ivar in $svar) { if (${utility('hasProp')}.call($svar, $ivar)"
+      forPart:      "$ivar in $svar"
+      guardPart:    "\n${@idt(1)}if (!${utility('hasProp')}.call($svar, $ivar)) continue;" unless @raw
     body:           body.compile(merge(o, {indent: @idt(1), top: true}))
     vars:           if range then name else "$name, $ivar"
-    close:          if @object and not @raw then '}}' else '}'
-    "${sourcePart}for ($forPart) {\n$varPart$body\n$@tab$close$returnResult"
+    "${sourcePart}for ($forPart) {$guardPart\n$varPart$body\n$@tab}$returnResult"

 #### IfNode


Fixing #700. Block comment for constructor now appears above the constructor declaration.

--- nodes.coffee
+++ nodes.coffee
@@ -787,7 +787,7 @@
         new Call applied, [new Literal('this'), new Literal('arguments')]
       ]))
     else
-      constructor = new Code
+      constructor = new Code [], new Expressions [new Return new Literal 'this']

     for prop in @properties
       [pvar, func] = [prop.variable, prop.value]
@@ -803,6 +803,7 @@
         variable = new Value variable
         variable.namespaced = 0 < className.indexOf '.'
         constructor = func
+        constructor.comment = props.expressions.pop() if props.expressions[props.expressions.length - 1] instanceof Comment
         continue
       if func instanceof Code and func.bound
         if prop.context is 'this'
@@ -994,9 +995,10 @@
     params = (param.compile(o) for param in params)
     @body.makeReturn() unless empty
     (o.scope.parameter(param)) for param in params
+    comm  = if @comment then @comment.compile(o) + '\n' else ''
     o.indent = @idt 2 if @className
     code  = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else ''
-    open  = if @className then "(function() {\n#{@idt(1)}function #{@className}(" else "function("
+    open  = if @className then "(function() {\n#{comm}#{@idt(1)}function #{@className}(" else "function("
     close = if @className then "#{code and @idt(1)}};\n#{@idt(1)}return #{@className};\n#{@tab}})()" else "#{code and @tab}}"
     func  = "#{open}#{ params.join(', ') }) {#{code}#{close}"
     o.scope.endLevel()


Try fix tests again

Try fix tests again

--- test/phabs_hear_test.coffee
+++ test/phabs_hear_test.coffee
@@ -718,9 +718,13 @@
           .get('/api/phid.lookup')
           .reply(200, { result: {
             'D55': {
-              'status': 'open',
-              'title': 'some diff',
-              'uri': 'http://example.com/D55'
+              'phid': 'PHID-DREV-hqztsdcva3jkucu4mmv2',
+              'uri': 'http://example.com/D55',
+              'typeName': 'Differential Revision',
+              'type': 'DREV',
+              'name': 'D55',
+              'fullName': 'D55: some diff',
+              'status': 'open'
             }
           } })

@@ -743,9 +747,13 @@
           .get('/api/phid.lookup')
           .reply(200, { result: {
             'D55': {
-              'status': 'closed',
-              'title': 'some diff',
-              'uri': 'http://example.com/D55'
+              'phid': 'PHID-DREV-hqztsdcva3jkucu4mmv2',
+              'uri': 'http://example.com/D55',
+              'typeName': 'Differential Revision',
+              'type': 'DREV',
+              'name': 'D55',
+              'fullName': 'D55: some diff',
+              'status': 'closed'
             }
           } })


Exit with an error code 1 if could not write a compiled JavaScript file or a source map

Currently CoffeeScript reports a success even if it failed to write a
compiled file to disk. This behavior confuses automated tools such as
test runners which may return false positives if a test failed to
compile because of a file lock.

--- command.coffee
+++ command.coffee
@@ -65,7 +65,7 @@
 # `--` will be passed verbatim to your script as arguments in `process.argv`
 exports.run = ->
   parseOptions()
-  # Make the REPL *CLI* use the global context so as to (a) be consistent with the
+  # Make the REPL *CLI* use the global context so as to (a) be consistent with the
   # `node` REPL CLI and, therefore, (b) make packages that modify native prototypes
   # (such as 'colors' and 'sugar') work as expected.
   replCliOpts = useGlobal: yes
@@ -346,12 +346,14 @@
       fs.writeFile jsPath, js, (err) ->
         if err
           printLine err.message
+          process.exit 1
         else if opts.compile and opts.watch
           timeLog "compiled #{sourcePath}"
     if generatedSourceMap
       fs.writeFile sourceMapPath, generatedSourceMap, (err) ->
         if err
           printLine "Could not write source map: #{err.message}"
+          process.exit 1
   fs.exists jsDir, (itExists) ->
     if itExists then compile() else mkdirp jsDir, compile


tiny edit

tiny edit

--- command.coffee
+++ command.coffee
@@ -30,7 +30,7 @@
   ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
   ['-o', '--output [DIR]',    'set the directory for compiled JavaScript']
   ['-j', '--join [FILE]',     'concatenate the scripts before compiling']
-  ['-w', '--watch',           'watch scripts and repeat enabled task(s) for any that change']
+  ['-w', '--watch',           'watch scripts and repeat enabled tasks for any that change']
   ['-p', '--print',           'print the compiled JavaScript to stdout']
   ['-l', '--lint',            'pipe the compiled JavaScript through JavaScript Lint']
   ['-s', '--stdio',           'listen for and compile scripts over stdio']


fix the printJson method of koding-config-manager

--- node_modules_koding/koding-config-manager/config.coffee
+++ node_modules_koding/koding-config-manager/config.coffee
@@ -5,6 +5,7 @@
 traverse        = require 'traverse'
 nodePath        = require 'path'
 {extend}        = require 'underscore'
+JsPath          = require 'jspath'

 class Config
   #
@@ -21,7 +22,7 @@
     return config

   @printJson:(name)->
-    console.log JSON.stringify new Config name
+    console.log JSON.stringify @load name

   @check =(path)->
     normalizePathForSchema = (aPath, p=[])->


Bug found.

Bug found.

--- src/content/illustView.coffee
+++ src/content/illustView.coffee
@@ -31,6 +31,7 @@
     updateStat()
     download = ->
       url = []
+      #FIXME Can't download multi page illustration with type 'illustration'
       switch work.type
         when "illustration"
           url[0] = [work.image_urls.large, 0]


mapSeries -> eachSeries

mapSeries -> eachSeries

--- app/coffee/UpdateManager.coffee
+++ app/coffee/UpdateManager.coffee
@@ -39,7 +39,7 @@
 			return callback(error) if error?
 			if updates.length == 0
 				return callback()
-			async.mapSeries updates,
+			async.eachSeries updates,
 				(update, cb) -> UpdateManager.applyUpdate project_id, doc_id, update, cb
 				callback


Use special search phrase instead of name.

--- app/assets/javascripts/app/controllers/candidates.js.coffee
+++ app/assets/javascripts/app/controllers/candidates.js.coffee
@@ -23,9 +23,10 @@
     @bbox       = $('#app').data('bbox')
     @name       = $('#app').data('name')
     @place      = $('#app').data('place')
+    @phrase     = $('#app').data('phrase')
     @params = {
       url: "http://nominatim.openstreetmap.org/search?" + $.param(
-        q: "#{@name}, #{@place}"
+        q: "#{@phrase}"
         viewbox: @bbox
         format: "jsonv2"
         addressdetails: 1


Handling missing options in shipping filter call

--- app/assets/javascripts/darkswarm/filters/shipping.js.coffee
+++ app/assets/javascripts/darkswarm/filters/shipping.js.coffee
@@ -1,9 +1,10 @@
-Darkswarm.filter 'shipping', ()->
+Darkswarm.filter 'shipping', ()->
   (objects, options)->
     objects ||= []
-    options ?= null
-
-    if options.pickup and !options.delivery
+
+    if !options
+      objects
+    else if options.pickup and !options.delivery
       objects.filter (obj)->
         obj.pickup
     else if options.delivery and !options.pickup


use context instead of content

use context instead of content

--- app/components/eui-popup.coffee
+++ app/components/eui-popup.coffee
@@ -184,33 +184,28 @@
       # creates Label property based on specified labelPath
       labelPathDidChange: (->
         labelPath = @get 'labelPath'
-        Ember.defineProperty(this, 'label', Ember.computed.alias("content.#{labelPath}"))
+        Ember.defineProperty(this, 'label', Ember.computed.alias("context.#{labelPath}"))
         @notifyPropertyChange 'label'
-      ).observes 'content', 'labelPath'
+      ).observes 'context', 'labelPath'

       initializeLabelPath: (->
         @labelPathDidChange()
       ).on 'init'
-
-      # TODO: Unsure why this is not done automatically. Without this @get('content') returns undefined.
-      updateContext: (context) ->
-        @_super context
-        @set 'content', context

       isHighlighted: Ember.computed ->
         options = @get('controller.filteredOptions')
         index = @get('controller.highlightedIndex')
         option = options[index]

-        option is @get('content')
-      .property 'controller.highlightedIndex', 'content'
+        option is @get('context')
+      .property 'controller.highlightedIndex', 'context'

       isSelected: Ember.computed ->
-        @get('controller.selection') is @get('content')
-      .property 'controller.selection', 'content'
+        @get('controller.selection') is @get('context')
+      .property 'controller.selection', 'context'

       click: ->
-        option = @get('content')
+        option = @get('context')
         event = @get('controller.event')

         if event == 'select'
@@ -224,7 +219,7 @@

       mouseEnter: ->
         options = @get('controller.filteredOptions')
-        hoveredOption = @get('content')
+        hoveredOption = @get('context')

         for option, index in options
           if option == hoveredOption


Do not send a paramter with the current time when requesting a refresh feed job state via Ajax.

It was keeping clients from leveraging HTTP caching.

--- app/assets/javascripts/ng-services/ng-JobStateSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-JobStateSvc.js.coffee
@@ -39,8 +39,7 @@
       timer = $timeout ->
         # Remove this timer from the list so that another update can be scheduled for 5 seconds in the future
         delete $rootScope.refresh_job_state_timers[job_id]
-        now = new Date()
-        $http.get("/api/refresh_feed_job_states/#{job_id}.json?time=#{now.getTime()}")
+        $http.get("/api/refresh_feed_job_states/#{job_id}.json")
         .success (data)->
           # Update the current state of the job in the root scope
           job = findSvc.find_refresh_feed_job job_id


social: add optional successFn for calling in successful requests via socialapicontroller

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -269,7 +269,7 @@

   requester = (req) ->
     (options, callback)->
-      {fnName, validate, mapperFn, defaults, apiType} = req
+      {fnName, validate, mapperFn, defaults, apiType, successFn} = req
       # set default mapperFn
       mapperFn or= (value) -> return value
       if validate?.length > 0
@@ -293,6 +293,7 @@

       api[fnName] options, (err, result)->
         return callback err if err
+        successFn result if successFn and typeof successFn is "function"
         return callback null, mapperFn result

   cacheItem: (item) ->


kite/newkite.coffee: fix err handling on client side. Pass it back.

Signed-off-by: Fatih Arslan <a6ac56298ee94ffaa2bb5159c2f2185ca646d70a@koding.com>

--- client/app/MainApp/kite/newkite.coffee
+++ client/app/MainApp/kite/newkite.coffee
@@ -37,6 +37,7 @@
     @ws.onerror   = @bound 'onError'

   getKiteAddr : (connect=no)->
+    log "calling getKiteAddr"
     NewKite.getKites @kitename, (err, data) =>
       if err
         log "kontrol request error", err
@@ -100,11 +101,7 @@
         when 'ping'             then @bound 'handlePing'
         else (@localStore.get method) ? ->

-    if err?.message?
-      KD.utils.defer => @setBackoffTimeout @bound "getKiteAddr"
-    else
       callback.apply this, @unscrub args
-

   onError: (evt) ->
     # log "#{@kitename}: error #{evt.data}"


disable /Environments

disable /Environments

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -230,7 +230,7 @@
       '/:name?/Account'                 : createSectionHandler 'Account'
       '/:name?/Demos'                   : createSectionHandler 'Demos'
       '/:name?/Dashboard'               : createSectionHandler 'Dashboard'
-      '/:name?/Environments'            : createSectionHandler 'Environments'
+      #'/:name?/Environments'            : createSectionHandler 'Environments'

       # group dashboard
       # '/:name?/Dashboard'               : (routeInfo, state, route)->


remove vocab search

remove vocab search

--- src/components/search.cjsx
+++ src/components/search.cjsx
@@ -18,25 +18,12 @@
     ExerciseStore.once 'loaded', @displayExercise
     ExerciseActions.load(exerciseId)

-  loadVocabulary: (vocabId) ->
-    @setState({vocabId})
-    ExerciseStore.once 'loaded', @displayVocab
-    ExerciseActions.load(vocabId)
-
   onFindExercise: ->
     @loadExercise(this.refs.exerciseId.getDOMNode().value)
-
-  onFindVocabulary: ->
-    @loadVocabulary(this.refs.vocabularyId.getDOMNode().value)

   onExerciseKeyPress: (ev) ->
     return unless ev.key is 'Enter'
     @loadExercise(this.refs.exerciseId.getDOMNode().value)
-    ev.preventDefault()
-
-  onVocabKeyPress: (ev) ->
-    return unless ev.key is 'Enter'
-    @loadVocabulary(this.refs.vocabularyId.getDOMNode().value)
     ev.preventDefault()

   render: ->
@@ -57,22 +44,6 @@

         </BS.Col>
       </BS.Row>
-
Edit vocabulary term:

-      <BS.Row>
-        <BS.Col sm=3>
-

-            <input type="text"
-              className="form-control"
-              onKeyPress={@onVocabKeyPress}
-              ref="vocabularyId" placeholder="Vocabulary ID"/>
-
-              <button className="btn btn-default load"
-                type="button" onClick={@onFindVocabulary}
-              >Load</button>
-
-

-        </BS.Col>
-      </BS.Row>


remove row when deleted

remove row when deleted

--- client/lanes/components/grid/Body.cjsx
+++ client/lanes/components/grid/Body.cjsx
@@ -10,14 +10,17 @@
     onEditCancel: (model) ->
         if model.isNew()
             @props.query.results.removeRow(@state.selectedIndex)
-            this.refs.grid.data.splice(0, 1)
         this.hideEditor()

     hideEditor: ->
-        @setState(selectedIndex: null, selectedModel: null)
+        # BUG? Without the defer, render is called with old state
+        _.defer => @setState(selectedIndex: null, selectedModel: null)

-    onEditSave: (model) ->
-        @props.query.results.saveModelChanges(model, @state.selectedIndex)
+    onEditSave: (model, isDeleted = false) ->
+        if (isDeleted)
+            @props.query.results.removeRow(@state.selectedIndex)
+        else
+            @props.query.results.saveModelChanges(model, @state.selectedIndex)
         this.hideEditor()

     componentWillReceiveProps: (nextProps) ->
@@ -70,15 +73,14 @@
         results = @props.query.results
         row = results.rowRepresentation(rowNum)
         for field, index in @props.query.fields.models when field.visible
-            fields.push @renderColumn(rowNum, index, field, results)
+            fields.push @renderColumn(rowNum, index, field, results, row)

         onClick = _.partial(@onRowClick, _, row, rowNum)

             {fields}

-    Editor: ->
-        return null unless @isEditing()
+    renderEditor: ->
         editor = if true == @props.editor
             Lanes.Components.Grid.RowEditor
         else
@@ -102,7 +104,7 @@

     render: ->

-            <@Editor />
+            {@renderEditor() if @isEditing()}
             <Lanes.Vendor.List
                 useTranslate3d={true}
                 isEditing={!!@state.selectedModel}


test_1.5.0_plus runs all tests with updated tokens function

--- test/test_1.5.0_plus.coffee
+++ test/test_1.5.0_plus.coffee
@@ -2,35 +2,26 @@
 vows = require 'vows'
 assert = require 'assert'
 CoffeeScript = require 'coffee-script'
+CoffeeScript.old_tokens = CoffeeScript.tokens
+CoffeeScript.tokens = (text) ->
+    tokens = CoffeeScript.old_tokens(text)
+    for token in tokens
+        if typeof token[2] == "number"
+            token[2] = {first_line: token[2]}
+        token
 coffeelint = require path.join('..', 'lib', 'coffeelint')

-vows.describe('CoffeeScript 1.5.0+').addBatch({
+batches = []
+vow = vows.describe("CoffeeScript 1.5.0+")

-    "lineNumber" :
+require('fs').readdirSync('./test').forEach (file) ->
+    if file.match(/\.coffee$/) and not file.match(/test_1\.5\.0_plus.coffee$/)
+        test = require "./#{file}"
+        for own test_name, suite of test
+            batches.push({})
+            for batch in suite.batches
+                new_batch = {}
+                new_batch[test_name] = batch.tests
+                vow = vow.addBatch(new_batch)

-        topic : () ->
-            """
-            x = 1234;
-            y = 1234; z = 1234
-            """
-
-        'work with 1.5.0+ tokens' : (source) ->
-            CoffeeScript.old_tokens = CoffeeScript.tokens
-            CoffeeScript.tokens = (text) ->
-                tokens = CoffeeScript.old_tokens(text)
-                CoffeeScript.updated_tokens_called = true
-                for token in tokens
-                    if typeof token[2] == "number"
-                        token[2] = {first_line: token[2]}
-                    token
-            assert.isUndefined(CoffeeScript.updated_tokens_called)
-            errors = coffeelint.lint(source)
-            assert.isArray(errors)
-            assert.lengthOf(errors, 1)
-            assert.isTrue(CoffeeScript.updated_tokens_called)
-            error = errors[0]
-            assert.equal(error.lineNumber, 1)
-            assert.equal(error.message, "Line contains a trailing semicolon")
-            assert.equal(error.rule, 'no_trailing_semicolons')
-
-}).export(module)
+vow.export(module)


Minor refactoring to format function name PP-8

--- client/src/scripts/scoring-view.coffee
+++ client/src/scripts/scoring-view.coffee
@@ -186,7 +186,7 @@
         'ROC Curve'
       ]

-      format8f = d3.format '.4f' # precision = 4
+      format4f = d3.format '.4f' # precision = 4

       scoreWithLowestError = min scores, (score) -> score.result.metrics.error

@@ -202,20 +202,20 @@
           model.model_category
           model.response_column_name
           model.parameters
-          (format8f metrics.error) + errorBadge #TODO change to bootstrap badge
-          format8f auc.AUC
+          (format4f metrics.error) + errorBadge #TODO change to bootstrap badge
+          format4f auc.AUC
           head auc.threshold_criteria
           head auc.threshold_for_criteria
-          format8f head auc.F1_for_criteria
-          format8f head auc.accuracy_for_criteria
-          format8f head auc.precision_for_criteria
-          format8f head auc.recall_for_criteria
-          format8f head auc.specificity_for_criteria
-          format8f head auc.max_per_class_error_for_criteria
+          format4f head auc.F1_for_criteria
+          format4f head auc.accuracy_for_criteria
+          format4f head auc.precision_for_criteria
+          format4f head auc.recall_for_criteria
+          format4f head auc.specificity_for_criteria
+          format4f head auc.max_per_class_error_for_criteria
           createROC auc.confusion_matrices
         ]

-      rows.unshift header
+      unshift rows, header
       rows

     renderTable = (grid) ->


Hacky fix for Kithgard Mastery walls not updating properly after dynamically being spawned.

--- app/lib/surface/LankBoss.coffee
+++ app/lib/surface/LankBoss.coffee
@@ -276,6 +276,8 @@

   onNewWorld: (e) ->
     @world = @options.world = e.world
+    # Clear obstacle cache for this level, since we are spawning walls dynamically
+    @cachedObstacles = false if e.finished and /kithgard-mastery/.test window.location.href

   play: ->
     lank.play() for lank in @lankArray


removing debug

removing debug

--- app/lib/zooniverse/Proxy.coffee
+++ app/lib/zooniverse/Proxy.coffee
@@ -13,7 +13,6 @@

 class Proxy extends Spine.Module
   @extend Spine.Events
-  alert("")
   @iframe = $("")
   @iframe.css display: 'none'
   @iframe.appendTo 'body'


fixed order agg error

fixed order agg error

--- src/core/fetch/sort.coffee
+++ src/core/fetch/sort.coffee
@@ -23,7 +23,7 @@
       value = d[key]

     if [vars.data.keys[key], vars.attrs.keys[key]].indexOf("number") >= 0
-      agg = vars.order.agg.value or vars.aggs.value[sortKey] or "sum"
+      agg = vars.order.agg.value or vars.aggs.value[key] or "sum"
       value = d3[agg] value
     else
       value = value[0] if value instanceof Array


three stars if better than even

three stars if better than even

--- src/layout/done_dialog.coffee
+++ src/layout/done_dialog.coffee
@@ -24,7 +24,7 @@
     extra_moves = [ticks_diff, clicks_diff, bricks_diff].reduce (sum, diff) ->
       sum + if diff < 0 then 0 else diff

-    if extra_moves == 0
+    if extra_moves <= 0
       3
     else if extra_moves <= 3
       2


Update kitteh zoo example description (#4614)

Removes ID from manifest, more correctly describes subject_id versus user-supplied columns that may contain an ID from their own metadata

--- app/pages/lab/help/example.cjsx
+++ app/pages/lab/help/example.cjsx
@@ -105,11 +105,15 @@

       When you actually build your own project, there won't be an example set of images already loaded for you. Get started uploading a set of subjects for your project by clicking on the "New Subject Set" button on the left hand side of the screen. That will bring you to the _Subject Uploader_.

-      It's easiest if you have all of your images in a single folder along with a manifest file, and you will upload both at the same time. The manifest file lists all the images we want to upload and links each  image to additional information, also called metadata, such as date, time, and photographer. There is more information about the manifest in the "Details" section below, but for now you can see an example manifest file in the ["Kitteh" zip file](https://data.zooniverse.org/tutorial/kitteh_zoo.zip).
-
-      Click on the "Upload Subjects" box and navigate to the "Kitteh" folder you downloaded. The easiest thing to do is simply hit _cmd + a_ (on Mac, or _ctrl + a_ on Windows) to select everything in that folder. Note that the subject uploader ignores the excel file and the other folders. Click "open" to select those images and the manifest file for upload. The project uploader now indicates that the "Cat project manifest.csv" has 29 subjects for upload.
-
-      Click "Upload" to start the process. It might take a little while, but when everything is uploaded, you'll see a list of all of the subjects. The numbers to the left are unique identifiers for each subject, and the icons to the right let you preview or delete each subject.
+      If every subject you want classified is a single image, _and_ if the image file name contains enough information for you to be able to precisely match the subject with the rest of your data later, then this is pretty easy: you can just drag and drop the images into the Subject Uploader (or click within the "Upload Subjects" box and choose the files in the pop-up menu) and it will automatically detect that it needs to make subjects out of each file.
+
+      Much of the time, though, project builders need to keep some data associated with their images in the classification interface. For example, in Kitteh Zoo all the images are Creative Commons licensed and need to have attribution attached when they are displayed. With a simple file called a _manifest_, we can associate the needed information with each image. Below you can find more details about the manifest in the section _DETAILS - Subject sets and manifest details, a.k.a. "What is a manifest?"_ For now we will assume the manifest is created and the files are ready. You can see an example manifest file in the ["Kitteh" zip file](https://data.zooniverse.org/tutorial/kitteh_zoo.zip).
+
+      People often find it easiest to have all of the subject images in a single folder along with a manifest file; you will upload both at the same time. However you organize your files, pay attention to the details and keep records, as it may be important for your data analysis later (e.g. when you download your classifications of subjects and need to know how to correctly link those back to all the rest of your metadata).
+
+      Click on the "Upload Subjects" box and navigate to the "Kitteh" folder you downloaded. The easiest thing to do is simply hit _cmd + a_ (on Mac, or _ctrl + a_ on Windows) to select everything in that folder. Note that the subject uploader ignores the excel file and the other folders. Click "open" to select those images and the manifest file for upload. The project uploader now indicates that the "Cat project manifest.csv" has 30 subjects for upload. (If we had more subjects to upload, we'd do them in batches of no more than 1,000 at a time.)
+
+      Click "Upload" to start the process. When everything is uploaded, you'll see a list of all of the subjects. The numbers to the left are unique identifiers for each subject, and the icons to the right let you preview or delete each subject.

       Subject sets can be pretty powerful, and sometimes complex. You can have a single subject set that you add to over time, or have multiple subject sets, say, from different years or places. You can have different subject sets for different workflows, but you don't have to. You can even have multiple images in a given subject. For more details and advice on creating and structuring subject sets and associated manifests, check out the details section below.

@@ -120,7 +124,7 @@
         - **Research Case, FAQ, Results, and Education**: You can add a lot of information in these pages to help volunteers better understand the motivation for your project, the best approaches for classifying, and the outcomes of your project.
         - **Collaborators**: Add people to your team and specify what their roles are so that they have the right access to the tools they need (including access to the project before it's public).
         - **Workflows**: A workflow is the sequence of tasks that you're asking volunteers to perform. For example, you might want to ask volunteers to answer questions about your images, or to mark features in your data, or both. The workflow is where you define those tasks and set out the order in which the volunteers will do them. Your project might have multiple workflows (if you want to set different tasks for different image sets).
-      - **Subjects**: A subject is a unit of data to be analyzed. A subject can include one or more images that will be analyzed at the same time by volunteers. A subject set consists of a list of subjects (the "manifest") defining their properties, and the images themselves. Feel free to group subjects into sets in the way that is most useful for your research. Many projects will find it's best to just have all their subjects in 1 set, but not all.
+      - **Subjects**: A subject is a unit of data to be analyzed. A subject can include one or more images that will be analyzed at the same time by volunteers. A subject set consists of a list of subjects (the "manifest") defining their properties, and the images themselves. Feel free to group subjects into sets in the way that is most useful for your research. Many projects will find it's best to just have all their subjects in 1 set, but not all: larger projects often find it essential to group subjects into multiple sets.

       ### DETAILS - Workflows

@@ -128,7 +132,7 @@

       _Workflow Name_: Give your workflow a short, but descriptive name. If you have multiple workflows and give volunteers the option of choosing which they want to work on, this name will appear on a button instead of "Get started!"

-      _Version_: Version indicates which version of the workflow you are on. Every time you save changes to a workflow, you create a new version. Big changes, like adding or deleting questions, will change the version by a whole number: 1.0 to 2.0, etc. Smaller changes, like modifying the help text, will change the version by a decimal, e.g. 2.0 to 2.1. The version is tracked with each classification in case you need it when analyzing your data.
+      _Version_: Version indicates which version of the workflow you are on. Every time you save changes to a workflow, you create a new version. Big changes, like adding or deleting questions, will change the version by the number to the left of the ".": 1.0 to 2.0, etc. Smaller changes, like modifying the help text, will change the integer to the right of the ".", e.g. 2.0 to 2.1. The version is tracked with each classification in case you need it when analyzing your data. _Note:_ the version looks like a decimal number, _but it is not_: it is a string with two numbers separated by a "." character. So, for example, version 2.1 is not the same as version 2.10.

       _Tasks_: There are two main types of tasks: questions and drawing. For question tasks, the volunteer chooses from a list of answers but does not mark or draw on the image. In drawing tasks, the volunteer marks or draws directly on the image using tools that you specify. They can also give sub-classifications for each mark. Note that you can set the first task from the drop-down menu.

@@ -138,7 +142,7 @@

       You can use markdown in the main text.

-      _Help Text_: Add text and images for a pop-up help window. This is shown next to the main text of the task in the main classification interface, when the volunteer clicks a button asking for help. You can use markdown in this text, and link to other images to help illustrate your description. The help text can be as long as you need, but you should try to keep it simple and avoid jargon. One thing that is useful in the help text is a concise description of why you are asking for this particular information.
+      _Help Text_: Add text and images for a pop-up help window. This is shown next to the main text of the task in the main classification interface, when the volunteer clicks a button asking for help. You can use markdown in this text, and link to other images to help illustrate your description. The help text can be as long as you need, but you should try to keep it simple and avoid jargon. One thing that is useful in the help text is a concise description of why you are asking for this particular information. This help information often overlaps with information in the Field Guide, but it is an opportunity to provide extra detail about each specific task.

       ### DETAILS - Project Details:

@@ -159,13 +163,13 @@

       ### DETAILS - Additional Content

-      _Research Case, FAQ, Results, and Education_: These pages are where you really get to share all the cool things about your project. All of these pages use Markdown (see link above) to format text and display images.
+      _Research, FAQ, Results, and Education_: These pages are where you really get to share all the cool things about your project. All of these pages use Markdown (see link above) to format text and display images.

       [![Project additional content editor screenshot](/assets/how-to-lab/how-to-12.jpg)](/assets/how-to-lab/how-to-12.jpg)

-      _Research case_: Explain your research to your audience here in as much detail as you'd like. This page displays no matter what, since explaining your motivation to volunteers is critical for the success of your project!
-
-      _Results_: Once your project has hit its stride, share the results of your project with your volunteers here. This page will only display if you add content to it.
+      _Research_: Explain your research case to your audience here in as much detail as you'd like. This page displays no matter what, since explaining your motivation to volunteers is critical for the success of your project!
+
+      _Results_: Once your project has hit its stride, share the results of your project with your volunteers here. Volunteers really value feedback about how their inputs were used to help the research. This page will only display if you add content to it.

       _FAQ_: Add details here about your research, how to classify, and what you plan to do with the classifications. This page can evolve as your project does so that your active community members have a resource to point new users to. This page will only display if you add content to it.

@@ -205,7 +209,7 @@

       *The condensed answer:*

-      A manifest is a file that tells our software how to combine the images you have into units of data (subjects) to be classified. The manifest also allows you to link your classifications back to the rest of your data. A manifest is formatted as a CSV file with 1 line per subject, with a unique identifier and the names of images to be associated with a subject on each row (with additional information often included in other fields as well). There is an example in the ["Kitteh" zip file](https://data.zooniverse.org/tutorial/kitteh_zoo.zip).
+      A manifest is a file that tells our software how to combine the images you have into units of data (subjects) to be classified. The manifest also allows you to link your classifications back to the rest of your data. A manifest is formatted as a CSV file with 1 line per subject, with the names of images to be associated with a subject on each row (with additional information often included in other fields as well). There is an example in the ["Kitteh" zip file](https://data.zooniverse.org/tutorial/kitteh_zoo.zip).

       *The full answer:*

@@ -213,20 +217,20 @@

       To understand the format, let's start with the first few lines from the Kitteh Zoo manifest:

-          subject_id,image_name_1,origin,link,attribution,license,#secret_description
-          1,6672150457_420d61007d_b.jpg,Flickr,https://www.flickr.com/photos/aigle_dore/6672150457,Moyan Brenn,Creative Commons - share adapt attribute,sleepy striped kitteh is unsuspecting of paparazzi
-          2,8300920648_d4a21bba59_z,Flickr,https://www.flickr.com/photos/aigle_dore/8300920648,Moyan Brenn,Creative Commons - share adapt attribute,grandfather kitteh has ear hair. a lot of it
-          3,6713782851_82fc8c73e5_z.jpg,Flickr,https://www.flickr.com/photos/hellie55/6713782851,hehaden,Creative Commons - share adapt attribute,juvenile kittehs practice break-in at the catnip factory
-
-      The first line of the file is a header line that specifies the name of each of the manifest fields. In this case, our manifest has 7 fields (or columns), called "subject\_id", "image\_name", "origin", "link", "attribution", "license" and “#secret\_description”. They are separated by commas: this is what's known as a "comma separated values" file, or CSV file.
-
-      After the first line, each row of the file contains information about 1 subject. The first field, corresponding with the "subject\_id" header, is a unique number that identifies the subject. The second field, which aligns with the "image\_name" header, contains the name of the image that's associated with that subject. These 2 fields are critically important: the image name is obviously important, and a unique identifier is important for matching your classifications to the rest of your data.
-
-      All the other fields are optional, but in general having more information in the manifest is better. Most projects include additional information in the manifest that helps them match the classifications and subjects to the other data they need for their research. The additional information in the manifest can also be made available to volunteers as they classify or in the Talk discussion tool (some very keen volunteers find this extremely useful). Any fields with names that begin with “#” or “//” will *not* be shown to volunteers, such as the “#secret\_description” field in Kitteh Zoo. These hidden fields will still be returned to you in the classification file, so you can use these to include information helpful to your research without worrying about whether it might affect the classifications themselves. There might be information that could helpful for the volunteers in discussions or further exploration in the Talk discsussion tool, but could bias the classifications if visible to volunteers while classifying. Any fields with names that begin with "!" *will not* be accessible to volunteers in the classification interface, but *will be*  available on Talk after classification. Information in fields that *don’t* begin with a “#”, “//”, or '!' will always be accessible to volunteers in both the classification interface and the Talk discussion tool.
-
-      For now, let’s assume you’re just including the bare minimum of information, like:
-
-          id,image
+          image,!origin,link,attribution,license,#secret_description
+          6672150457_420d61007d_b.jpg,Flickr,https://www.flickr.com/photos/aigle_dore/6672150457,Moyan Brenn,Creative Commons - share adapt attribute,sleepy striped kitteh is unsuspecting of paparazzi
+          8300920648_d4a21bba59_z.jpg,Flickr,https://www.flickr.com/photos/aigle_dore/8300920648,Moyan Brenn,Creative Commons - share adapt attribute,grandfather kitteh has ear hair. a lot of it
+          6713782851_82fc8c73e5_z.jpg,Flickr,https://www.flickr.com/photos/hellie55/6713782851,hehaden,Creative Commons - share adapt attribute,juvenile kittehs practice break-in at the catnip factory
+
+      The first line of the file is a header line that specifies the name of each of the manifest fields. In this case, our manifest has 6 fields (or columns), called "image", "!origin", "link", "attribution", "license" and “#secret\_description”. They are separated by commas: this is what's known as a "comma separated values" file, or CSV file.
+
+      After the first line, each row of the file contains information about 1 subject. The first field, which aligns with the "image" header, contains the name of the image that's associated with that subject. This field is critically important as it tells the project builder which images to make into subjects.
+
+      All the other fields are optional, but in general having more information in the manifest is better. Most projects include additional information in the manifest that helps them match the classifications and subjects to the other data they need for their research. For example, many projects include a separate ID that they have generated and which can be used later as a unique identifier to match classification data to their own metadata (for other projects, the unique identifier is the filename). The additional information in the manifest can also be made available to volunteers as they classify or in the Talk discussion tool (some very keen volunteers find this extremely useful). Any fields with names that begin with “#” or “//” will *never* be shown to volunteers, such as the “#secret\_description” field in Kitteh Zoo. These hidden fields will still be returned to you in the classification file, so you can use these to include information helpful to your research without worrying about whether it might affect the classifications themselves. There might be information that could helpful for the volunteers in discussions or further exploration in the Talk discsussion tool, but could bias the classifications if visible to volunteers while classifying. Any fields with names that begin with "!" *will not* be accessible to volunteers in the classification interface, but *will be*  available on Talk after classification (such as the “!origin” field above). Information in fields that *don’t* begin with a “#”, “//”, or '!' will always be accessible to volunteers in both the classification interface and the Talk discussion tool.
+
+      For now, let’s assume you’re just including minimal information, like:
+
+          my_own_id,the_image
           1,kitteh_in_box.jpg
           2,kitteh_stalking.jpg
           3,kitteh_losing_balance.jpg
@@ -235,12 +239,12 @@

       Using a manifest CSV file also makes it very easy to create subjects with multiple images:

-          id,image1,image2
+          my_own_id,image1,image2
           1,kitteh_in_box.jpg,kitteh_eating_box.jpg
           2,kitteh_stalking.jpg,kitteh_pounced.jpg
           3,kitteh_losing_balance.jpg,kitteh_falling_off_sofa.jpg

-      If you upload this manifest plus the 6 images named in it, the Zooniverse software will create 3 subjects with 2 images each. When these subjects come up in the classification interface, volunteers will be able to flash between the images or switch between them manually.
+      If you upload this manifest plus the 6 images named in it, the Zooniverse software will create 3 subjects with 2 images each. When these subjects come up in the classification interface, volunteers will be able to flash between the images or switch between them manually. Later on, when these subjects are classified, the subject\_id assigned by the project builder can be used with a subject export to match each classification back to this information.

       You can create a manifest file in a simple text editor (such as TextEdit or Notepad), although this method is prone to errors like missed or extra commas. People often find it easiest to create manifest files using spreadsheet software such as Google Sheets, iWork Numbers or Microsoft Excel. Creating and maintaining a manifest using a spreadsheet makes the manifest easy to read, and you can export it to CSV format when you're ready to upload your subjects. You can also open existing CSV files in spreadsheet software.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -60,6 +60,7 @@
   @returns {Array} An array of the objects found
   ###
   each: (selector) ->
+    ;

   find: (selector) ->
     results = []


Updated event map

Updated event map

--- src/events/eventmap.coffee
+++ src/events/eventmap.coffee
@@ -11,7 +11,7 @@
       try
         result = JSON.stringify @events, (key, value) ->
           value = value.toString() if typeof value is 'function'
-          value
+          value
       catch err
         console.error "Error while serializing eventmap: #{err}"
       result
@@ -21,7 +21,7 @@
         events = JSON.parse string, (key, value) ->
           if value.indexOf('function') is 0
             value = new Function(value)()
-          value
+          value
       catch err
         console.error "Error while deserializing eventmap: #{err}"
         return false


Removed autoscrolling

Removed autoscrolling

--- app/views/employers_view.coffee
+++ app/views/employers_view.coffee
@@ -152,10 +152,10 @@
   renderCandidatesAndSetupScrolling: =>
     @render()
     $(".nano").nanoScroller()
-    if window.history?.state?.lastViewedCandidateID
-      $(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
-    else if window.location.hash.length is 25
-      $(".nano").nanoScroller({scrollTo:$(window.location.hash)})
+    #if window.history?.state?.lastViewedCandidateID
+    #  $(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
+    #else if window.location.hash.length is 25
+    #  $(".nano").nanoScroller({scrollTo:$(window.location.hash)})

   checkForEmployerSignupHash: =>
     if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get("permissions"))


Change instance variable to lower case

Change instance variable to lower case

--- app/views/home-page-view.coffee
+++ app/views/home-page-view.coffee
@@ -19,6 +19,7 @@
     super
     new Std()
     @skipFrame = true
+    @entitySpawnManager = new EntitySpawnManager()

     # developement only, will be removed in release build
     mediator.PlayWithSounds = confirm("Load Sounds?")
@@ -31,7 +32,6 @@
     @soundManager = new SoundManager() if mediator.PlayWithSounds
     @inputManager = new InputManager()

-    @EntitySpawnManager = new EntitySpawnManager()

     @loadLevel(level)

@@ -54,7 +54,7 @@
     mediator.activeLevel = level
     mediator.entities = []
     @physicsManager.setup()
-    @EntitySpawnManager.initialSpawn()
+    @entitySpawnManager.initialSpawn()
     if mediator.PlayWithSounds
       @subscribeEvent 'soundsLoaded:'+level, =>
         @soundManager.playSound(level+'theme',mediator.levels[level].soundList, 1, true)


Amended "if data" checks in FileModel to "if data?"

Signed-off-by: Richard Antecki <320bca71fc381a4a025636043ca86e734e31cf8b@antecki.id.au>

--- src/lib/models/file.coffee
+++ src/lib/models/file.coffee
@@ -320,7 +320,7 @@
 			data = attrs.data
 			delete attrs.data
 			delete @attributes.data
-		if data
+		if data?
 			@setData(data)

 		# Meta
@@ -375,7 +375,7 @@

 		# If data is set, use that as the buffer
 		data = file.getData()
-		if data
+		if data?
 			buffer = new Buffer(data)
 			file.setBuffer(buffer)


Possible fix for cannot dispose of undefined error

--- lib/format.coffee
+++ lib/format.coffee
@@ -108,8 +108,8 @@
                 @format(state)

           @editorCloseSubscriptions[editor.id] = buffer.onDidDestroy =>
-            @editorSaveSubscriptions[editor.id].dispose()
-            @editorCloseSubscriptions[editor.id].dispose()
+            @editorSaveSubscriptions[editor.id]?.dispose()
+            @editorCloseSubscriptions[editor.id]?.dispose()

             delete @editorSaveSubscriptions[editor.id]
             delete @editorCloseSubscriptions[editor.id]


Added some simple tests

Added some simple tests

--- dev/tests/angular-database.coffee
+++ dev/tests/angular-database.coffee
@@ -0,0 +1,40 @@
+'use strict'
+
+describe 'SelfieTrip', ->
+    $database = null
+
+    beforeEach ->
+        module 'ngDatabase'
+        return
+
+    beforeEach ->
+        inject ($injector) ->
+            $database = $injector.get '$database'
+            return
+        return
+
+    it "Should have sql propertie", ->
+        expect(typeof $database.sql).toBe 'object'
+        expect($database.sql).toEqual null
+        return
+
+    it "Should have create table method", ->
+        expect(typeof $database.createTable).toBe 'function'
+        return
+
+    it "Should have select method", ->
+        expect(typeof $database.select).toBe 'function'
+        return
+
+    it "Should have insert method", ->
+        expect(typeof $database.insert).toBe 'function'
+        return
+
+    it "Should have update method", ->
+        expect(typeof $database.update).toBe 'function'
+        return
+
+    it "Should have where method", ->
+        expect(typeof $database.where).toBe 'function'
+        return
+    return


Removed GOPATH from Atom's config.cson

Removed GOPATH from Atom's config.cson

Don't think it's necessary(?) as $GOPATH is set in https://github.com/holman/dotfiles/blob/master/go/path.zsh

https://github.com/joefitzgerald/go-config/wiki/GOPATH

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -26,8 +26,6 @@
     invisibles: {}
     showIndentGuide: true
     softWrapAtPreferredLineLength: true
-  "go-plus":
-    goPath: "/Users/holman/Code/go"
   "linter-eslint":
     useGlobalEslint: true
   "release-notes":


atom settings

atom settings

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -23,10 +23,8 @@
     showIndentGuide: true
     fontFamily: "Inconsolata, Monaco, Consolas, 'Courier New', Courier"
     invisibles: {}
-    scrollPastEnd: true
     zoomFontWhenCtrlScrolling: true
     confirmCheckoutHeadRevision: false
-    fontSize: 15
     backUpBeforeSaving: true
   core:
     closeDeletedFileTabs: true
@@ -137,7 +135,6 @@
   "project-manager": {}
   docblockr: {}
   "git-blame": {}
-  "project-manager": {}
   linter:
     showErrorPanel: false
   "linter-eslint":


r=self adding function to get git commits

--- core.coffee
+++ core.coffee
@@ -43,6 +43,7 @@
 						forks: forks_count
 					repo.save()
 					exports.fetchRepoLanguages repo
+					exports.fetchRepoCommits repo, ''

 fetchRepoLanguages = (repo) ->
 	apiJobs.push ->
@@ -54,6 +55,43 @@
 				repo.languages.push language: language, lineCount: lineCount
 			repo.save()

+fetchRepoCommits = (repo, date) ->
+	apiJobs.push ->
+		githubApi
+			url: "/repos/#{repo.full_name}/commits"
+			qs:
+				until: date
+				per_page: 100
+		, (err, response, body) ->
+			items = JSON.parse(body)
+			items.forEach (item) ->
+				getUserOrCreateUser (item.author.login, user) ->
+					commit = new db.Commit
+						sha: item.sha
+						author: user._id
+						repository: repo._id
+						timestamp: item.commit.author.date
+					commit.save(err, commit) ->
+						if not err?
+							exports.fetchCommit repo, commit
+
+getUserOrCreateUser = (username, callback) ->
+	db.User.findOne username: username, (err, resp) ->
+		if err?
+			user = new db.User
+				username: username
+			user.save (err, use)  ->
+				return callback err if err?
+				return callback null, use
+		else
+			return callback null, resp
+
+fetchCommit = (repo, commit) ->
+	apiJobs.push ->
+		githubApi
+			url: "/repos/#{repo.full_name}/commits/#{commit.sha}"
+		, (err, response, body) ->
+
 exports.startJobs = ->
 	setInterval ( ->
 		doSearchJobs = searchJobs[...globals.searchRequestsPerMinute]


bug in org

bug in org

--- src/methods/organization-methods.coffee
+++ src/methods/organization-methods.coffee
@@ -71,7 +71,7 @@
   Patch an organization
   ###
   patch: (organizationId, obj = {}, options = {}, cb = ->) =>
-    return cb new Error "organizationId parameter is required." unless apporganizationIdId
+    return cb new Error "organizationId parameter is required." unless organizationId
     settings =
       exclude : UPDATE_EXCLUDEFIELDS
     mongooseRestHelper.patch @models.Organization,organizationId, settings, obj, options, cb


Tests referenced wrong objects.

Tests referenced wrong objects.

Didn't really influence tests, but was still wrong.

--- tests.coffee
+++ tests.coffee
@@ -280,7 +280,7 @@
   test.equal Post.Meta.schema, '1.0.0' if Meteor.isServer
   test.equal Post.Meta.migrations, [] if Meteor.isServer
   test.equal _.size(Post.Meta.fields), 7
-  test.instanceOf Post.Meta.fields.author, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.author, Post._ReferenceField
   test.isNull Post.Meta.fields.author.ancestorArray, Post.Meta.fields.author.ancestorArray
   test.isTrue Post.Meta.fields.author.required
   test.equal Post.Meta.fields.author.sourcePath, 'author'
@@ -293,7 +293,7 @@
   test.equal Post.Meta.fields.author.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal Post.Meta.fields.author.reverseName, 'posts'
   test.equal Post.Meta.fields.author.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf Post.Meta.fields.subscribers, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.subscribers, Post._ReferenceField
   test.equal Post.Meta.fields.subscribers.ancestorArray, 'subscribers'
   test.isTrue Post.Meta.fields.subscribers.required
   test.equal Post.Meta.fields.subscribers.sourcePath, 'subscribers'
@@ -306,7 +306,7 @@
   test.equal Post.Meta.fields.subscribers.fields, []
   test.isNull Post.Meta.fields.subscribers.reverseName
   test.equal Post.Meta.fields.subscribers.reverseFields, []
-  test.instanceOf Post.Meta.fields.reviewers, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.reviewers, Post._ReferenceField
   test.equal Post.Meta.fields.reviewers.ancestorArray, 'reviewers'
   test.isTrue Post.Meta.fields.reviewers.required
   test.equal Post.Meta.fields.reviewers.sourcePath, 'reviewers'
@@ -320,7 +320,7 @@
   test.isNull Post.Meta.fields.reviewers.reverseName
   test.equal Post.Meta.fields.reviewers.reverseFields, []
   test.equal _.size(Post.Meta.fields.subdocument), 3
-  test.instanceOf Post.Meta.fields.subdocument.person, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.subdocument.person, Post._ReferenceField
   test.isNull Post.Meta.fields.subdocument.person.ancestorArray, Post.Meta.fields.subdocument.person.ancestorArray
   test.isFalse Post.Meta.fields.subdocument.person.required
   test.equal Post.Meta.fields.subdocument.person.sourcePath, 'subdocument.person'
@@ -333,7 +333,7 @@
   test.equal Post.Meta.fields.subdocument.person.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal Post.Meta.fields.subdocument.person.reverseName, 'subdocumentPosts'
   test.equal Post.Meta.fields.subdocument.person.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf Post.Meta.fields.subdocument.persons, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.subdocument.persons, Post._ReferenceField
   test.equal Post.Meta.fields.subdocument.persons.ancestorArray, 'subdocument.persons'
   test.isTrue Post.Meta.fields.subdocument.persons.required
   test.equal Post.Meta.fields.subdocument.persons.sourcePath, 'subdocument.persons'
@@ -346,7 +346,7 @@
   test.equal Post.Meta.fields.subdocument.persons.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal Post.Meta.fields.subdocument.persons.reverseName, 'subdocumentsPosts'
   test.equal Post.Meta.fields.subdocument.persons.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf Post.Meta.fields.subdocument.slug, Person._GeneratedField
+  test.instanceOf Post.Meta.fields.subdocument.slug, Post._GeneratedField
   test.isNull Post.Meta.fields.subdocument.slug.ancestorArray, Post.Meta.fields.subdocument.slug.ancestorArray
   test.isTrue _.isFunction Post.Meta.fields.subdocument.slug.generator
   test.equal Post.Meta.fields.subdocument.slug.sourcePath, 'subdocument.slug'
@@ -360,7 +360,7 @@
   test.isUndefined Post.Meta.fields.subdocument.slug.reverseName
   test.isUndefined Post.Meta.fields.subdocument.slug.reverseFields
   test.equal _.size(Post.Meta.fields.nested), 3
-  test.instanceOf Post.Meta.fields.nested.required, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.nested.required, Post._ReferenceField
   test.equal Post.Meta.fields.nested.required.ancestorArray, 'nested'
   test.isTrue Post.Meta.fields.nested.required.required
   test.equal Post.Meta.fields.nested.required.sourcePath, 'nested.required'
@@ -373,7 +373,7 @@
   test.equal Post.Meta.fields.nested.required.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal Post.Meta.fields.nested.required.reverseName, 'nestedPosts'
   test.equal Post.Meta.fields.nested.required.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf Post.Meta.fields.nested.optional, Person._ReferenceField
+  test.instanceOf Post.Meta.fields.nested.optional, Post._ReferenceField
   test.equal Post.Meta.fields.nested.optional.ancestorArray, 'nested'
   test.isFalse Post.Meta.fields.nested.optional.required
   test.equal Post.Meta.fields.nested.optional.sourcePath, 'nested.optional'
@@ -386,7 +386,7 @@
   test.equal Post.Meta.fields.nested.optional.fields, ['username']
   test.isNull Post.Meta.fields.nested.optional.reverseName
   test.equal Post.Meta.fields.nested.optional.reverseFields, []
-  test.instanceOf Post.Meta.fields.nested.slug, Person._GeneratedField
+  test.instanceOf Post.Meta.fields.nested.slug, Post._GeneratedField
   test.equal Post.Meta.fields.nested.slug.ancestorArray, 'nested'
   test.isTrue _.isFunction Post.Meta.fields.nested.slug.generator
   test.equal Post.Meta.fields.nested.slug.sourcePath, 'nested.slug'
@@ -399,7 +399,7 @@
   test.equal Post.Meta.fields.nested.slug.fields, ['body', 'nested.body']
   test.isUndefined Post.Meta.fields.nested.slug.reverseName
   test.isUndefined Post.Meta.fields.nested.slug.reverseFields
-  test.instanceOf Post.Meta.fields.slug, Person._GeneratedField
+  test.instanceOf Post.Meta.fields.slug, Post._GeneratedField
   test.isNull Post.Meta.fields.slug.ancestorArray, Post.Meta.fields.slug.ancestorArray
   test.isTrue _.isFunction Post.Meta.fields.slug.generator
   test.equal Post.Meta.fields.slug.sourcePath, 'slug'
@@ -412,7 +412,7 @@
   test.equal Post.Meta.fields.slug.fields, ['body', 'subdocument.body']
   test.isUndefined Post.Meta.fields.slug.reverseName
   test.isUndefined Post.Meta.fields.slug.reverseFields
-  test.instanceOf Post.Meta.fields.tags, Person._GeneratedField
+  test.instanceOf Post.Meta.fields.tags, Post._GeneratedField
   test.equal Post.Meta.fields.tags.ancestorArray, 'tags'
   test.isTrue _.isFunction Post.Meta.fields.tags.generator
   test.equal Post.Meta.fields.tags.sourcePath, 'tags'
@@ -700,7 +700,7 @@
   test.equal SpecialPost.Meta.schema, '1.0.0' if Meteor.isServer
   test.equal SpecialPost.Meta.migrations, [] if Meteor.isServer
   test.equal _.size(SpecialPost.Meta.fields), 8
-  test.instanceOf SpecialPost.Meta.fields.author, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.author, SpecialPost._ReferenceField
   test.isNull SpecialPost.Meta.fields.author.ancestorArray, SpecialPost.Meta.fields.author.ancestorArray
   test.isTrue SpecialPost.Meta.fields.author.required
   test.equal SpecialPost.Meta.fields.author.sourcePath, 'author'
@@ -713,7 +713,7 @@
   test.equal SpecialPost.Meta.fields.author.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal SpecialPost.Meta.fields.author.reverseName, 'posts'
   test.equal SpecialPost.Meta.fields.author.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf SpecialPost.Meta.fields.subscribers, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.subscribers, SpecialPost._ReferenceField
   test.equal SpecialPost.Meta.fields.subscribers.ancestorArray, 'subscribers'
   test.isTrue SpecialPost.Meta.fields.subscribers.required
   test.equal SpecialPost.Meta.fields.subscribers.sourcePath, 'subscribers'
@@ -726,7 +726,7 @@
   test.equal SpecialPost.Meta.fields.subscribers.fields, []
   test.isNull SpecialPost.Meta.fields.subscribers.reverseName
   test.equal SpecialPost.Meta.fields.subscribers.reverseFields, []
-  test.instanceOf SpecialPost.Meta.fields.reviewers, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.reviewers, SpecialPost._ReferenceField
   test.equal SpecialPost.Meta.fields.reviewers.ancestorArray, 'reviewers'
   test.isTrue SpecialPost.Meta.fields.reviewers.required
   test.equal SpecialPost.Meta.fields.reviewers.sourcePath, 'reviewers'
@@ -740,7 +740,7 @@
   test.isNull SpecialPost.Meta.fields.reviewers.reverseName
   test.equal SpecialPost.Meta.fields.reviewers.reverseFields, []
   test.equal _.size(SpecialPost.Meta.fields.subdocument), 3
-  test.instanceOf SpecialPost.Meta.fields.subdocument.person, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.subdocument.person, SpecialPost._ReferenceField
   test.isNull SpecialPost.Meta.fields.subdocument.person.ancestorArray, SpecialPost.Meta.fields.subdocument.person.ancestorArray
   test.isFalse SpecialPost.Meta.fields.subdocument.person.required
   test.equal SpecialPost.Meta.fields.subdocument.person.sourcePath, 'subdocument.person'
@@ -753,7 +753,7 @@
   test.equal SpecialPost.Meta.fields.subdocument.person.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal SpecialPost.Meta.fields.subdocument.person.reverseName, 'subdocumentPosts'
   test.equal SpecialPost.Meta.fields.subdocument.person.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf SpecialPost.Meta.fields.subdocument.persons, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.subdocument.persons, SpecialPost._ReferenceField
   test.equal SpecialPost.Meta.fields.subdocument.persons.ancestorArray, 'subdocument.persons'
   test.isTrue SpecialPost.Meta.fields.subdocument.persons.required
   test.equal SpecialPost.Meta.fields.subdocument.persons.sourcePath, 'subdocument.persons'
@@ -766,7 +766,7 @@
   test.equal SpecialPost.Meta.fields.subdocument.persons.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal SpecialPost.Meta.fields.subdocument.persons.reverseName, 'subdocumentsPosts'
   test.equal SpecialPost.Meta.fields.subdocument.persons.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf SpecialPost.Meta.fields.subdocument.slug, Person._GeneratedField
+  test.instanceOf SpecialPost.Meta.fields.subdocument.slug, SpecialPost._GeneratedField
   test.isNull SpecialPost.Meta.fields.subdocument.slug.ancestorArray, SpecialPost.Meta.fields.subdocument.slug.ancestorArray
   test.isTrue _.isFunction SpecialPost.Meta.fields.subdocument.slug.generator
   test.equal SpecialPost.Meta.fields.subdocument.slug.sourcePath, 'subdocument.slug'
@@ -780,7 +780,7 @@
   test.isUndefined SpecialPost.Meta.fields.subdocument.slug.reverseName
   test.isUndefined SpecialPost.Meta.fields.subdocument.slug.reverseFields
   test.equal _.size(SpecialPost.Meta.fields.nested), 3
-  test.instanceOf SpecialPost.Meta.fields.nested.required, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.nested.required, SpecialPost._ReferenceField
   test.equal SpecialPost.Meta.fields.nested.required.ancestorArray, 'nested'
   test.isTrue SpecialPost.Meta.fields.nested.required.required
   test.equal SpecialPost.Meta.fields.nested.required.sourcePath, 'nested.required'
@@ -793,7 +793,7 @@
   test.equal SpecialPost.Meta.fields.nested.required.fields, ['username', 'displayName', 'field1', 'field2']
   test.equal SpecialPost.Meta.fields.nested.required.reverseName, 'nestedPosts'
   test.equal SpecialPost.Meta.fields.nested.required.reverseFields, ['body', 'subdocument.body', 'nested.body']
-  test.instanceOf SpecialPost.Meta.fields.nested.optional, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.nested.optional, SpecialPost._ReferenceField
   test.equal SpecialPost.Meta.fields.nested.optional.ancestorArray, 'nested'
   test.isFalse SpecialPost.Meta.fields.nested.optional.required
   test.equal SpecialPost.Meta.fields.nested.optional.sourcePath, 'nested.optional'
@@ -806,7 +806,7 @@
   test.equal SpecialPost.Meta.fields.nested.optional.fields, ['username']
   test.isNull SpecialPost.Meta.fields.nested.optional.reverseName
   test.equal SpecialPost.Meta.fields.nested.optional.reverseFields, []
-  test.instanceOf SpecialPost.Meta.fields.nested.slug, Person._GeneratedField
+  test.instanceOf SpecialPost.Meta.fields.nested.slug, SpecialPost._GeneratedField
   test.equal SpecialPost.Meta.fields.nested.slug.ancestorArray, 'nested'
   test.isTrue _.isFunction SpecialPost.Meta.fields.nested.slug.generator
   test.equal SpecialPost.Meta.fields.nested.slug.sourcePath, 'nested.slug'
@@ -819,7 +819,7 @@
   test.equal SpecialPost.Meta.fields.nested.slug.fields, ['body', 'nested.body']
   test.isUndefined SpecialPost.Meta.fields.nested.slug.reverseName
   test.isUndefined SpecialPost.Meta.fields.nested.slug.reverseFields
-  test.instanceOf SpecialPost.Meta.fields.slug, Person._GeneratedField
+  test.instanceOf SpecialPost.Meta.fields.slug, SpecialPost._GeneratedField
   test.isNull SpecialPost.Meta.fields.slug.ancestorArray, SpecialPost.Meta.fields.slug.ancestorArray
   test.isTrue _.isFunction SpecialPost.Meta.fields.slug.generator
   test.equal SpecialPost.Meta.fields.slug.sourcePath, 'slug'
@@ -832,7 +832,7 @@
   test.equal SpecialPost.Meta.fields.slug.fields, ['body', 'subdocument.body']
   test.isUndefined SpecialPost.Meta.fields.slug.reverseName
   test.isUndefined SpecialPost.Meta.fields.slug.reverseFields
-  test.instanceOf SpecialPost.Meta.fields.tags, Person._GeneratedField
+  test.instanceOf SpecialPost.Meta.fields.tags, SpecialPost._GeneratedField
   test.equal SpecialPost.Meta.fields.tags.ancestorArray, 'tags'
   test.isTrue _.isFunction SpecialPost.Meta.fields.tags.generator
   test.equal SpecialPost.Meta.fields.tags.sourcePath, 'tags'
@@ -845,7 +845,7 @@
   test.equal SpecialPost.Meta.fields.tags.fields, ['body', 'subdocument.body', 'nested.body']
   test.isUndefined SpecialPost.Meta.fields.tags.reverseName
   test.isUndefined SpecialPost.Meta.fields.tags.reverseFields
-  test.instanceOf SpecialPost.Meta.fields.special, Person._ReferenceField
+  test.instanceOf SpecialPost.Meta.fields.special, SpecialPost._ReferenceField
   test.isNull SpecialPost.Meta.fields.special.ancestorArray, SpecialPost.Meta.fields.special.ancestorArray
   test.isTrue SpecialPost.Meta.fields.special.required
   test.equal SpecialPost.Meta.fields.special.sourcePath, 'special'


Timeout increase.

Timeout increase.

"update all" tests otherwise fail now.

--- tests.coffee
+++ tests.coffee
@@ -1,4 +1,4 @@
-WAIT_FOR_DATABASE_TIMEOUT = 1000 # ms
+WAIT_FOR_DATABASE_TIMEOUT = 1500 # ms

 # The order of documents here tests delayed definitions


Updated comment.

Updated comment.

--- prototypes/drawing/coffeescripts/models/link.coffee
+++ prototypes/drawing/coffeescripts/models/link.coffee
@@ -13,8 +13,10 @@
     @bind "save", @addToNodes
     @bind "destroy", @removeFromNodes

-  # extract the information needed to reconstruct
-  # the path used to draw this link (and nothing more)
+  # Spine.Model.Local uses JSON for seralisation and hence
+  # cannot contain cyclic structures. Paper.js's paths
+  # are sometimes cyclic, so we filter the Paper.js structure
+  # to the bare essentials (which will be acyclic)
   moveTo: (segments) =>
     @segments = (for s in segments
       point: {x: s.point.x, y: s.point.y}


trackJs error messages on skipLogic failed parsing

--- jsapp/xlform_model_view/mv.skipLogicHelpers.coffee
+++ jsapp/xlform_model_view/mv.skipLogicHelpers.coffee
@@ -172,6 +172,8 @@
           criteria.push @build_empty_criterion()

       catch e
+        trackJs?.console.log("SkipLogic cell: #{serialized_criteria}")
+        trackJs?.console.error("could not parse skip logic. falling back to hand-coded")
         return false
       return [criteria, parsed.operator]


archiving feature

archiving feature

--- src/mailButler.appsscript.coffee
+++ src/mailButler.appsscript.coffee
@@ -81,25 +81,28 @@
       # refresh its state
       message.refresh()

-      if @hasLabel @LABEL_OUTBOX, message
+      # get the thread the email belongs to
+      # message.getThread() seems to return the wrong number of messages (only one message)
+      thread = GmailApp.getThreadById message.getThread().getId()
+
+      # refresh the thread
+      thread.refresh()
+
+      if @hasLabel @LABEL_OUTBOX, thread
         # Has the outbox label
         Logger.log "Start processing message with ID '%s'", messageId

         # remove the outbox label from the message
-        @removeLabel @LABEL_OUTBOX, message
+        @removeLabel @LABEL_OUTBOX, thread

         # By default we assume there was no answer - this is also correct if the user doesn't care about answers
         hasAnswer = false

         if props.how.noanswer
           # all the actions shall only take place if there was no answer
-
-          # get the thread the email belongs to
-          # message.getThread() seems to return the wrong number of messages (only one message)
-          thread = GmailApp.getThreadById message.getThread().getId()
-
+
           # find the date of the last message
-          dateOfLastMessage = thread.refresh().getLastMessageDate()
+          dateOfLastMessage = thread.getLastMessageDate()
           Logger.log "Action was scheduled on '%s' and date of last message was %s ", new Date(props.scheduled).toUTCString(), dateOfLastMessage.toUTCString()

           # check if that message was sent after or on the scheduling date
@@ -114,10 +117,10 @@
           message.star()  if props.how.star and not message.isStarred()

           # mark it unread only if not unread and marking unread is enabled
-          message.markUnread()  if props.how.unread and not message.isUnread()
+          thread.markUnread()  if props.how.unread and not thread.isUnread()

           # move it to inbox only if not in inbox already and moving is enabled
-          GmailApp.moveThreadToInbox thread ? message.getThread()  if props.how.inbox and not message.isInInbox()
+          GmailApp.moveThreadToInbox thread ? message.getThread()  if props.how.inbox and not thread.isInInbox()

         else
           # There was an answer on this thread
@@ -138,19 +141,19 @@
     GmailApp.getUserLabelByName(name) ? (GmailApp.createLabel(name) if create)

   # Add a label to a message (thread) - create newly if needed
-  @addLabel: (name, message) ->
-    @getLabel(name, true)?.addToThread message.getThread()
+  @addLabel: (name, thread) ->
+    @getLabel(name, true)?.addToThread thread
     return

   # Checks if a given message (thread) has a specified label attached
-  @hasLabel: (name, message) ->
-    for label in message.getThread().getLabels()
+  @hasLabel: (name, thread) ->
+    for label in thread.getLabels()
       return true if label.getName() is name
     false

   # Removes a label from a message (thread)
-  @removeLabel: (name, message) ->
-    @getLabel(name, false)?.removeFromThread message.getThread()
+  @removeLabel: (name, thread) ->
+    @getLabel(name, false)?.removeFromThread thread
     return

   @normalize: (messageId, noPrefix) ->
@@ -206,8 +209,13 @@
       throw "No action (star, marking unread, move to inbox) specified"

     MailButler.storeButlerMail props
-    MailButler.addLabel MailButler.LABEL_BASE, message
-    MailButler.addLabel MailButler.LABEL_OUTBOX, message
+
+    thread = GmailApp.getThreadById message.getThread().getId()
+    thread.moveToArchive() if String(form.archive) is "true"
+
+    MailButler.addLabel MailButler.LABEL_BASE, thread
+    MailButler.addLabel MailButler.LABEL_OUTBOX, thread
+
     return

 doGet = (request) ->
@@ -271,7 +279,7 @@
 _test = ->
   doGet
     parameter:
-      msgId: "13a1a6948cb7471f"
+      msgId: "13a1a6948cb7471f" # works with joscha@feth.com only
       when: "delta:"+ (2 * 1000 * 60)
       inbox: true
       unread: true


ensure contenteditable is always set on mentions

--- src/mentions-kinder.coffee
+++ src/mentions-kinder.coffee
@@ -169,7 +169,11 @@
   # TODO keep <br>'s
   cleanNode: (node)->
     # dont clean text nodes or mention nodes
-    unless node.nodeType == 3 || node.nodeName == 'BR' || $(node).attr('serialized-mention')
+    if node.nodeType == 3 || node.nodeName == 'BR'
+      # do nothing
+    else if $(node).attr('serialized-mention')
+      $(node).attr('contenteditable', false) # ensure contenteditable is set after paste
+    else
       # clean all children and replace node with them
       if node.childNodes?.length > 0
         @cleanChildNodes(node)


do it the right way

do it the right way

--- vendor/assets/javascripts/fustrate/_prototypes.coffee
+++ vendor/assets/javascripts/fustrate/_prototypes.coffee
@@ -1,8 +1,10 @@
 # Replicate a few common prototype methods on default objects

 Array::compact = (strings = true) ->
-  @forEach (element, index) =>
-    @splice(index, 1) if element in [undefined, null] or (strings and element is '')
+  @forEach (el, index) =>
+    return unless el is undefined or el is null or (strings and el is '')
+
+    @splice(index, 1)

   @


Client: Activity: Feed: Updaetd render method of messageItemMenu component to return null when if check doesn't pass

--- client/activity/lib/components/messageitemmenu/index.coffee
+++ client/activity/lib/components/messageitemmenu/index.coffee
@@ -189,20 +189,20 @@

     { message } = @props

-    return null unless message
-
-    if (message.get('accountId') is whoami().socialApiId) or checkFlag('super-admin')
-

-        <ButtonWithMenu items={@getMenuItems()} />
-        <BlockUserModal
-          {...@getBlockUserModalProps()}
-          isOpen={@state.isBlockUserModalVisible} />
-        <MarkUserAsTrollModal
-          {...@getMarkUserAsTrollModalProps()}
-          isOpen={@state.isMarkUserAsTrollModalVisible} />
-        <ActivityPromptModal
-          {...@getDeleteItemModalProps()}
-          isOpen={@state.isDeleting}>
-          Are you sure you want to delete this post?
-        </ActivityPromptModal>
-

+    return null  unless message
+    return null  if (message.get('accountId') isnt whoami().socialApiId) and not checkFlag('super-admin')
+
+

+      <ButtonWithMenu items={@getMenuItems()} />
+      <BlockUserModal
+        {...@getBlockUserModalProps()}
+        isOpen={@state.isBlockUserModalVisible} />
+      <MarkUserAsTrollModal
+        {...@getMarkUserAsTrollModalProps()}
+        isOpen={@state.isMarkUserAsTrollModalVisible} />
+      <ActivityPromptModal
+        {...@getDeleteItemModalProps()}
+        isOpen={@state.isDeleting}>
+        Are you sure you want to delete this post?
+      </ActivityPromptModal>
+


InviteSomeoneView: Implemented invitation send.

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -55,6 +55,33 @@
       cssClass : 'solid medium green invite-members'
       callback : @bound 'inviteMembers'

+
+  inviteMembers: ->
+
+    invites = []
+
+    for view in @inputViews
+      return unless view.email.validate()
+
+      invites.push view.serialize()
+
+
+    remote.api.JInvitation.create invitations: invites, (err) =>
+      if err
+        return new KDNotificationView
+          title    : 'Failed to send some invites, please try again.'
+          duration : 5000
+
+      for view in @inputViews by -1
+        if view.getOptions().cancellable then view.destroy()
+        else
+          input.setValue ''  for input in view.inputs
+
+      new KDNotificationView
+        title    : 'All invites sent.'
+        duration : 5000
+
+
   createInformationView: ->

     @addSubView new KDCustomHTMLView


Fix copy file url issue

Fix copy file url issue

--- client/app/MainApp/filetree/itemsubviews/copyurlview.coffee
+++ client/app/MainApp/filetree/itemsubviews/copyurlview.coffee
@@ -5,7 +5,7 @@

     @path = FSHelper.plainPath @getData().path
     @publicPath = @path.replace \
-      ///.*\/(.*\.#{KD.config.userSitesDomain})\/(.*)///, 'http://$1/$2'
+      ////home/(.*)/Web/(.*)///, "http://$1.#{KD.config.userSitesDomain}/$2"

     @inputUrlLabel  = new KDLabelView
       cssClass      : 'public-url-label'


Remove returns errors in parse()

Remove returns errors in parse()

--- lib/ripper.coffee
+++ lib/ripper.coffee
@@ -182,7 +182,7 @@
     'source.litcoffee'
   ]

-  constructor: (@editor) ->
+  constructor: ->
     @lexer = new Lexer

   destruct: ->
@@ -196,12 +196,19 @@
     try
       @tokens = @lexer.tokenize code, {}
       @nodes = Ripper.generateNodes parse @tokens
+      callback()
     catch err
       updateSyntaxError err, code
-      callback? [ err ] #TODO deprecate
-      return [ err ]
-    callback?() #TODO deprecate
-    return null
+      { location, message } = err
+      if location? and message?
+        callback [
+          range  : locationDataToRange location
+          message: message
+        ]
+      else
+        # Logs uncaught parse error.
+        console.warn err
+        callback()

   find: (point) ->
     return [] unless @nodes?


throwing a monkey wrench at travis

throwing a monkey wrench at travis

--- lib/scrape.coffee
+++ lib/scrape.coffee
@@ -189,6 +189,5 @@
     domParse
     elToString
     executeXPath
-    executeCSSQuery
     main
 }


Don't use WorkspaceView in VimCommandModeInputView

--- lib/view-models/vim-command-mode-input-view.coffee
+++ lib/view-models/vim-command-mode-input-view.coffee
@@ -8,8 +8,6 @@
         @subview 'editor', new TextEditorView(mini: true)

   initialize: (@viewModel, opts = {})->
-    @editor.setFontSize(atom.config.get('vim-mode.commandModeInputViewFontSize'))
-
     if opts.class?
       @editorContainer.addClass opts.class

@@ -17,21 +15,11 @@
       @editorContainer.addClass 'hidden-input'

     @singleChar = opts.singleChar
-
     @defaultText = opts.defaultText ? ''

-    unless atom.workspaceView?
-      # We're in test mode. Don't append to anything, just initialize.
-      @focus()
-      @handleEvents()
-      return
-
-    statusBar = atom.workspaceView.find('.status-bar')
-
-    if statusBar.length > 0
-      @insertBefore(statusBar)
-    else
-      atom.workspace.getActivePane().append(@)
+    # If in test mode, don't append to anything, just initialize.
+    if atom.workspace?
+      atom.workspace.addBottomPanel(item: this, priority: 100)

     @focus()
     @handleEvents()
@@ -68,5 +56,4 @@

   remove: =>
     @stopHandlingEvents()
-    atom.workspaceView.focus() if atom.workspaceView?
     super()


Adding status codes no retryable

Adding status codes no retryable

--- src/processors/job/job.processor.coffee
+++ src/processors/job/job.processor.coffee
@@ -19,7 +19,7 @@
     headers: _getHeaders HeadersForRequest
   }, _.isUndefined

-module.exports = (generateOptions) -> ({ message, meta: { dequeueCount }}) ->
+module.exports = (generateOptions, nonRetryable = [400]) -> ({ message, meta: { dequeueCount }}) ->
   messageOptions = _cleanOptions message
   notificationsApi = new NotificationsApi messageOptions.headers["Authorization"]
   options = _.merge {}, generateOptions(messageOptions), resolveWithFullResponse: yes
@@ -28,5 +28,5 @@
   .promise()
   .tap ({ statusCode }) -> notificationsApi.success { message, statusCode }
   .catch ({ statusCode, error }) ->
-    throw { statusCode, error } unless dequeueCount >= MAX_DEQUEUE_COUNT or statusCode is 400
+    throw { statusCode, error } unless dequeueCount >= MAX_DEQUEUE_COUNT or statusCode in nonRetryable
     notificationsApi.fail { message, statusCode, error, request: { options } }


force utf8 reading of CSON files to fix reading of arrays

--- lib/server.coffee
+++ lib/server.coffee
@@ -36,7 +36,7 @@
   readCSON = (file, next) ->
     fs.exists file, (exists) ->
       if exists
-        CSON.parseFile file, next
+        CSON.parseFile file, 'utf8', next
       else
         next "#{file} does not exist"

@@ -368,6 +368,7 @@
           return console.log(err) if err
           delete config.changelog
           ref = firebase.child "users/#{user}/developer/listener"
+
           config.modified =
             time: Date.now()
             base: path.basename file
@@ -378,7 +379,8 @@
             # TODO: deprecate
             file_ext: path.extname(file).replace '.', ''
             file_name: path.basename file, path.extname(file)
-          ref.set config
+          ref.set config, (err) ->
+            console.log(err) if err

   # Middleware


The step function that is going to interpolate point now keeps track of the last time it was called for a better interpolation. Related to #373

--- admin/static/coffee/vis.coffee
+++ admin/static/coffee/vis.coffee
@@ -236,12 +236,13 @@
     class @InterpolatingCache
         constructor: (num_data_points, num_interpolation_points, get_data_fn) ->
             @ndp = num_data_points
-            @nip = num_interpolation_points
+            @nip = num_interpolation_points # =Pixels/Sec
             @get_data_fn = get_data_fn
             @values = []
             for i in [0..(@ndp-1)]
                 @values.push(0)
             @next_value = null
+            @last_date = Date.now()

         step: (num_points) ->
             # First, grab newest data
@@ -251,21 +252,24 @@
             return @values.slice(-num_points)

         push_data: ->
-            # Check if we need to restart interpolation
-            current_value = @get_data_fn()
-            if @next_value isnt current_value
-                @start_value = @values[@values.length - 1]
-                @next_value = current_value
+            elapsed_time = Date.now()-@last_date
+            current_value = @values[@values.length - 1]
+
+            next_value = @get_data_fn()
+
+            if elapsed_time > 1/@nip
+                missing_pixels = elapsed_time*@nip
+                for i in [1..missing_pixels]
+                    @values.push current_value+(next_value-current_value)/(elapsed_time*@nip)*i
                 @interpolation_step = 1
-
-            if @values[@values.length - 1] is @next_value
-                value_to_push = @next_value
             else
-                value_to_push = @start_value + ((@next_value - @start_value) / @nip * @interpolation_step)
-                @interpolation_step += 1
-                if @interpolation_step > @nip
-                    value_to_push = @next_value
-            @values.push(value_to_push)
+                if @interpolation_step > @mip
+                    @values.push next_value
+                    @interpolation_step++
+                else
+                    @values.push current_value+(next_value-current_value)/(elapsed_time*@nip)*@interpolation_step
+                @interpolation_step++
+            @last_date = Date.now()

             # Trim the cache
             if @values.length > @ndp


improve some refresh cases

improve some refresh cases

--- server/processes/mailbox_refresh_fast.coffee
+++ server/processes/mailbox_refresh_fast.coffee
@@ -61,6 +61,7 @@
             @refreshCreatedAndUpdated
             @checkNeedDeletion
             @refreshDeletion
+            @storeLastSync
         ], callback

     fetchChanges: (next) =>
@@ -116,7 +117,6 @@
             if message and not _.xor(message.flags, flags).length
                 setImmediate next
             else if message
-                @noChange = false
                 message.updateAttributes {flags}, next
             else
                 Message.fetchOrUpdate @mailbox, {mid, uid}, (err, info) =>
@@ -217,14 +217,16 @@
         ], callback

-    storeLastSync: (callback) ->
+    storeLastSync: (callback) =>
         if @newImapTotal isnt @mailbox.lastTotal or
            @newHighestModSeq isnt @mailbox.lastHighestModSeq

-            @mailbox.updateAttributes
+            changes =
                 lastHighestModSeq: @newHighestModSeq
                 lastTotal: @newImapTotal
                 lastSync: new Date().toISOString()
-            , callback
-
-
+            @mailbox.updateAttributes changes, callback
+
+        else callback null
+
+


Fixes issue with boolean query args being sent with "false" as value

--- app/adapters/variant.coffee
+++ app/adapters/variant.coffee
@@ -9,7 +9,7 @@
         # If boolean query parameter
         if value is yes
           queryString += "#{key}&"
-        else if value isnt 'undefined'
+        else if value not in ['undefined', 'false']
           queryString += "#{key}=#{value}&"

     # Return the query string after removing trailing '&'


Print an error when no container has been found

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -100,6 +100,7 @@
   _textContainer: ($el) ->
     for el in $el.parents()
       return el if $(el).css('display') == 'block'
+    console.error 'FactlinkJailRoot: No text container found for ', $el

   _updatePosition: ->
     top = @$nearEl.offset().top + @$nearEl.outerHeight()/2 - @frame.$el.outerHeight()/2


Server: Tests: remove daisy

Server: Tests: remove daisy

--- servers/lib/server/handlers/verifytoken.test.coffee
+++ servers/lib/server/handlers/verifytoken.test.coffee
@@ -1,10 +1,9 @@
 Bongo                                     = require 'bongo'
 koding                                    = require './../bongo'
-request                                   = require 'request'
-
-{ daisy }                                 = Bongo
-{ expect }                                = require 'chai'
-{ generateUrl
+{ async
+  expect
+  request
+  generateUrl
   generateRandomEmail
   generateRandomString
   generateRandomUsername }                = require '../../../testhelper'
@@ -34,28 +33,26 @@
       body    :
         email : generateRandomEmail()

-    queue       = []
-    methods     = ['post', 'put', 'patch', 'delete']
-
-    addRequestToQueue = (queue, method) -> queue.push ->
+    queue   = []
+    methods = ['post', 'put', 'patch', 'delete']
+
+    addRequestToQueue = (queue, method) -> queue.push (next) ->
       verifyTokenRequestParams.method = method
       request verifyTokenRequestParams, (err, res, body) ->
-        expect(err)             .to.not.exist
-        expect(res.statusCode)  .to.be.equal 404
-        queue.next()
+        expect(err).to.not.exist
+        expect(res.statusCode).to.be.equal 404
+        next()

     for method in methods
       addRequestToQueue queue, method

-    queue.push -> done()
-
-    daisy queue
+    async.series queue,done

   it 'should redirect to VerificationFailed page if token is invalid', (done) ->

-    token                     = 'invalidToken'
-    url                       = generateUrl
+    token = 'invalidToken'
+    url   = generateUrl
       route : "Verify/#{token}"

     verifyTokenRequestParams = generateVerifyTokenRequestParams
@@ -63,9 +60,9 @@
       token : token

     request.get verifyTokenRequestParams, (err, res, body) ->
-      expect(err)               .to.not.exist
-      expect(res.statusCode)    .to.be.equal 200
-      expect(res.request.href)  .to.contain 'VerificationFailed'
+      expect(err).to.not.exist
+      expect(res.statusCode).to.be.equal 200
+      expect(res.request.href).to.contain 'VerificationFailed'
       done()

@@ -78,7 +75,7 @@

     queue = [

-      ->
+      (next) ->
         # generationg a new user
         registerRequestParams = generateRegisterRequestParams
           body       :
@@ -86,11 +83,11 @@
             username : username

         request.post registerRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 200
-          queue.next()
-
-      ->
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          next()
+
+      (next) ->
         # generating a new token
         certificate = new JPasswordRecovery {
           email        : email
@@ -102,9 +99,9 @@

         certificate.save (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expecting newly created token to be verified
         url                       = generateUrl
           route : "Verify/#{token}"
@@ -114,16 +111,14 @@
           token : token

         request.get verifyTokenRequestParams, (err, res, body) ->
-          expect(err)               .to.not.exist
-          expect(res.statusCode)    .to.be.equal 200
-          expect(res.request.href)  .to.contain 'Verified'
-          queue.next()
-
-      -> done()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(res.request.href).to.contain 'Verified'
+          next()

     ]

-    daisy queue
+    async.series queue, done

   it 'should redirect to VerificationFailed page if token is expired, redeemed or invalidated', (done) ->
@@ -136,13 +131,13 @@
     url         = generateUrl
       route : "Verify/#{token}"

-    verifyTokenRequestParams  = generateVerifyTokenRequestParams
+    verifyTokenRequestParams = generateVerifyTokenRequestParams
       url   : url
       token : token

     queue = [

-      ->
+      (next) ->
         # generationg a new user
         registerRequestParams = generateRegisterRequestParams
           body       :
@@ -150,11 +145,11 @@
             username : username

         request.post registerRequestParams, (err, res, body) ->
-          expect(err)             .to.not.exist
-          expect(res.statusCode)  .to.be.equal 200
-          queue.next()
-
-      ->
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          next()
+
+      (next) ->
         # generating a new token
         certificate = new JPasswordRecovery {
           email        : email
@@ -166,83 +161,81 @@

         certificate.save (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expiring token
         certificate.expire (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expecting expired token not to pass verification
         request.get verifyTokenRequestParams, (err, res, body) ->
-          expect(err)               .to.not.exist
-          expect(res.statusCode)    .to.be.equal 200
-          expect(res.request.href)  .to.contain 'VerificationFailed'
-          queue.next()
-
-      ->
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(res.request.href).to.contain 'VerificationFailed'
+          next()
+
+      (next) ->
         # activating token again
         certificate.update { $set: { status: 'active' } }, (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expecting activated token to pass verification
         request.get verifyTokenRequestParams, (err, res, body) ->
-          expect(err)               .to.not.exist
-          expect(res.statusCode)    .to.be.equal 200
-          expect(res.request.href)  .to.contain 'Verified'
-          queue.next()
-
-      ->
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(res.request.href).to.contain 'Verified'
+          next()
+
+      (next) ->
         # redeeming token
         certificate.redeem (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expecting redeemed token not to pass verification
         request.get verifyTokenRequestParams, (err, res, body) ->
-          expect(err)               .to.not.exist
-          expect(res.statusCode)    .to.be.equal 200
-          expect(res.request.href)  .to.contain 'VerificationFailed'
-          queue.next()
-
-      ->
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(res.request.href).to.contain 'VerificationFailed'
+          next()
+
+      (next) ->
         # activating token again
         certificate.update { $set: { status: 'active' } }, (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expecting activated token to pass verification
         request.get verifyTokenRequestParams, (err, res, body) ->
-          expect(err)               .to.not.exist
-          expect(res.statusCode)    .to.be.equal 200
-          expect(res.request.href)  .to.contain 'Verified'
-          queue.next()
-
-      ->
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(res.request.href).to.contain 'Verified'
+          next()
+
+      (next) ->
         # invalidating token
         certificate.update { $set: { status: 'invalidated' } }, (err) ->
           expect(err).to.not.exist
-          queue.next()
-
-      ->
+          next()
+
+      (next) ->
         # expecting invalidated token not to pass verification
         request.get verifyTokenRequestParams, (err, res, body) ->
-          expect(err)               .to.not.exist
-          expect(res.statusCode)    .to.be.equal 200
-          expect(res.request.href)  .to.contain 'VerificationFailed'
-          queue.next()
-
-      -> done()
+          expect(err).to.not.exist
+          expect(res.statusCode).to.be.equal 200
+          expect(res.request.href).to.contain 'VerificationFailed'
+          next()

     ]

-    daisy queue
+    async.series queue, done

 runTests()