storage: code for safely closing all projects on a given host.

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -448,6 +448,7 @@
         host       : undefined  # defaults to current host, if deployed
         unset_loc  : false      # set location to undefined in the database
         umount     : false      # don't use this -- it causes ZFS deadlock, which requires machine reboots.  Horrible and not needed.
+        wait_for_replicate : false  # if true, waits for post-snapshot replication to complete
         cb         : required

     if not opts.host?
@@ -459,6 +460,17 @@

     user = username(opts.project_id)
     async.series([
+        (cb) ->
+            if opts.wait_for_replicate and opts.unset_loc
+                dbg("making a first snapshot and replicating it out, to minimize user inconvenience later")
+                snapshot
+                    project_id : opts.project_id
+                    host       : opts.host
+                    force      : true
+                    wait_for_replicate : opts.wait_for_replicate
+                    cb         : cb
+            else
+                cb()
         (cb) ->
             dbg("killing all processes")
             create_user
@@ -483,7 +495,16 @@
                                 err = undefined
                         cb(err)
         (cb) ->
+            dbg("making a snapshot and replicating it out, so we don't have to rollback later")
+            snapshot
+                project_id : opts.project_id
+                host       : opts.host
+                force      : true
+                wait_for_replicate : opts.wait_for_replicate
+                cb         : cb
+        (cb) ->
             if opts.unset_loc
+                dbg("unsetting location in project")
                 database.update
                     table : 'projects'
                     set   : {location:undefined}
@@ -492,6 +513,55 @@
             else
                 cb()
     ], opts.cb)
+
+# Close every project on a given host -- useful for putting a node into maintenance
+# mode or before a proper shutdown.  NOTE that the defaults here are different than
+# for close_project, since this is aimed at maintenance and proper shutdown.
+exports.close_all_projects = (opts) ->
+    opts = defaults opts,
+        host       : required
+        unset_loc  : true          # set location to undefined in the database
+        wait_for_replicate : true  # make sure that we successfully replicate each project everywhere, or returns an error.
+        limit      : 10
+        cb         : required
+    dbg = (m) -> winston.debug("close_all_projects(host:#{opts.host},unset_loc:#{opts.unset_loc}): #{m}")
+    errors = {}
+    projects = undefined
+    async.series([
+        (cb) ->
+            dbg("querying database...")
+            database.select
+                table   : 'projects'
+                columns : ['project_id', 'location']
+                json    : ['location']
+                limit   : 1000000   # TODO: stupidly slow
+                cb      : (err, result) ->
+                    if result?
+                        projects = (x[0] for x in result when x[1]?.host == opts.host)
+                    dbg("got #{projects.length} projects")
+                    cb(err)
+        (cb) ->
+            dbg("closing projects...")
+            f = (project_id, cb) ->
+                close_project
+                    project_id : project_id
+                    host       : opts.host
+                    unset_loc  : opts.unset_loc
+                    wait_for_replicate : opts.wait_for_replicate
+                    cb         : (err) ->
+                        if err
+                            errors[project_id] = err
+                        cb()
+            async.mapLimit(projects, opts.limit, f, cb)
+    ], (err) ->
+        if err
+            errors['err'] = err
+        if misc.len(errors) == 0
+            opts.cb()
+        else
+            opts.cb(errors)
+    )
+

 # Call "close_project"  on all projects that have been open for
 # more than ttl seconds, where opened means that location is set.
@@ -939,7 +1009,7 @@
         force      : false        # if false (the default), don't make the snapshot if diff outputs empty list of files
                                   # (note that diff ignores ~/.*), and also don't make a snapshot if one was made within
         min_snapshot_interval_s : 90    # opts.min_snapshot_interval_s seconds.
-        wait_for_replicate : false
+        wait_for_replicate : false  # wait until replication has finished *and* returns error if any replication fails.
         cb         : undefined

     if not opts.host?
@@ -999,7 +1069,7 @@
             execute_on
                 host    : opts.host
                 command : "sudo zfs snapshot #{name}"
-                timeout : 10
+                timeout : 120
                 cb      : cb
         (cb) ->
             dbg("record in database that we made a snapshot")
@@ -1309,14 +1379,14 @@
         cb         : undefined

     dbg = (m) -> winston.debug("rollback_to_snapshot(#{opts.project_id},#{opts.host},#{opts.snapshot}): #{m}")
-    snapshot = opts.snapshot
+    snapshot_name = opts.snapshot
     f = filesystem(opts.project_id)

     async.series([
         (cb) ->
-            if snapshot?
-                if snapshot.indexOf('@') == -1
-                    snapshot = opts.project_id + "@" + snapshot
+            if snapshot_name?
+                if snapshot_name.indexOf('@') == -1
+                    snapshot_name = opts.project_id + "@" + snapshot_name
                 cb()
             else
                 dbg("get most recent snapshot name")
@@ -1331,13 +1401,13 @@
                         if err
                             cb(err)
                         else
-                            snapshot = output.stdout.trim()
+                            snapshot_name = output.stdout.trim()
                             cb()
         (cb) ->
-            dbg("rollback to #{snapshot}")
+            dbg("rollback to #{snapshot_name}")
             execute_on
                 host    : opts.host
-                command : "sudo zfs rollback #{snapshot}"
+                command : "sudo zfs rollback #{snapshot_name}"
                 timeout : 300
                 cb      : cb
     ], (err) -> opts.cb?(err))
@@ -1913,26 +1983,16 @@
                                 else
                                     cb()
                     # try each of several evasive actions
-                    if output.stderr.indexOf('destination has snapshots') != -1
-                        m = "problem -- destination has snapshots that the source doesn't have -- destroying the target (really renaming)"
-                        dbg(m)
-                        destroy_project
-                            project_id : opts.project_id
-                            host       : opts.dest.host
-                            safe       : true
-                            cb         : (ignore) ->
-                                rm ()->cb(m)
-
-                    else if output.stderr.indexOf('has been modified') != -1 and output.stderr.indexOf('most recent snapshot') != -1
+                    stderr = output.stderr
+                    if stderr.indexOf('has been modified') != -1 and output.stderr.indexOf('most recent snapshot') != -1
                         m = "modified since most recent snapshot -- so rolling back to *most recent* snapshot"
                         dbg(m)
                         rollback_to_snapshot
                             project_id : opts.project_id
                             host       : opts.dest.host
                             cb         : try_again
-
-                    else if output.stderr.indexOf('cannot receive incremental stream: most recent snapshot of') != -1
-                        m = "out of sync -- destroying the target (really renaming)"
+                    else if stderr.indexOf('destination has snapshots') != -1 or stderr.indexOf('must specify -F to overwrite it') != -1 or  stderr.indexOf('cannot receive incremental stream: most recent snapshot of') != -1
+                        m = "problem -- destination has snapshots that the source doesn't have -- destroying the target (really safely renaming)"
                         dbg(m)
                         destroy_project
                             project_id : opts.project_id
@@ -2045,7 +2105,7 @@
         limit : 3   # no more than this many projects will be replicated simultaneously
         start : undefined  # if given, only takes projects.slice(start, stop) -- useful for debugging
         stop  : undefined
-        host  : undefined  # if given, only replicat projects whose current location is on this host.
+        host  : undefined  # if given, only replicate projects whose current location is on this host.
         cb    : undefined  # cb(err, {project_id:error when replicating that project})

     dbg = (m) -> winston.debug("replicate_all: #{m}")


Add unit test for POST object with existing _id

--- test/collection-spec.coffee
+++ test/collection-spec.coffee
@@ -1,5 +1,6 @@
 "use strict"

+ObjectID = require('mongodb').ObjectID
 request = require 'supertest'
 assert = require 'assert'

@@ -112,6 +113,13 @@
         assert.equal typeof res.body.documents[0], 'string'
         done() if ++count is target

+  it 'should convert _id to ObjectID if provided with one', (done) ->
+    doc = _id: new ObjectID().toString(), name: 'tuut-tuut'
+    req.post('/test?api_key=dnt').send(doc).expect(201).end (err, res) ->
+      throw err if err
+      assert.equal res.body.documents[0], doc._id
+      done()
+
   it 'should return error for missing request body', (done) ->
     req.post('/test?api_key=dnt')
       .expect(400, done)


Add babel-polyfill and proxies.

Add babel-polyfill and proxies.

--- test/conf/karma-conf.coffee
+++ test/conf/karma-conf.coffee
@@ -5,6 +5,15 @@

     # The karma adapter frameworks to use.
     frameworks: ['jspm', 'mocha', 'chai']
+
+    files: [
+      'node_modules/babel-polyfill/dist/polyfill.js'
+    ]
+
+    proxies:
+      '/src/': '/base/src/'
+      '/test/': '/base/test/'
+      '/jspm_packages/': '/base/jspm_packages/'

     # By default, karma loads node_modules sibling karma-* plug-ins.
     # However, note that the plug-ins must be installed as siblings
@@ -19,22 +28,23 @@
     # The karma-jspm option specifying the files to dynamically
     # load via SystemJS.
     jspm:
-      config: 'jspm.config.js',
-      loadFiles: ['test/unit/**/*.spec.*'],
-      serveFiles: ['src/**/*.*', 'tsconfig.json', 'typings/**/*.d.ts'],
-      stripExtension: false
+      config: 'jspm.config.js'
+      loadFiles: ['src/../test/unit/common/roman.service.spec.coffee']
+      serveFiles: ['src/**/*.*', 'tsconfig.json', 'typings/**/*.d.ts']

     # Print messages.
     client:
       captureConsole: true

     # The test specs can be written in CoffeeScript or TypeScript.
-    preprocessors:
-      '**/*.spec.coffee': 'coffee'
-      '**/*.spec.ts': 'typescript'
+    # preprocessors:
+    #   '**/*.spec.coffee': 'coffee'
+    #   '**/*.spec.ts': 'typescript'

     # The files to load is empty since karma-jspm assumes that responsibility.
-    files: []
+    files: [
+      'node_modules/babel-polyfill/dist/polyfill.js'
+    ]

     # The test results reporter.
     # Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
@@ -47,7 +57,7 @@
     # The logging level. Possible values:
     # config.LOG_DISABLE, config.LOG_ERROR, config.LOG_WARN, config.LOG_INFO, config.LOG_DEBUG
     # The log level is set in the Grunt karma task.
-    logLevel : config.LOG_DEBUG
+    # logLevel :   config.LOG_DEBUG

     # Flag indicating whether to execute the tests whenever any file changes.
     autoWatch: false
@@ -67,13 +77,22 @@
     # test case. See the Developer Guide for details.
     browsers: ['PhantomJS']

+		preprocessors:
+			'src/*.js': ['babel', 'sourcemap']
+
+		babelPreprocessor:
+			options:
+				sourceMap: 'inline'
+			sourceFileName: (file) ->
+				file.originalPath
+
     # If the browser does not capture output in the given number of milliseconds,
     # then kill it.
-    captureTimeout: 5000
+    captureTimeout: 2000

     # Allowing empty test suites is useful when commenting out tests. The
     # default is true.
-    failOnEmptyTestSuite: false
+    # failOnEmptyTestSuite: false

     # The Continuous Integration mode.
     # If true, then karma will capture the browsers, run the tests and exit.


test for CartDBGetter.fetch

test for CartDBGetter.fetch

--- test/getters/cartodb.coffee
+++ test/getters/cartodb.coffee
@@ -17,3 +17,40 @@

   assert.strictEqual(getter.indicator, indicator)
 )
+
+test(".fetch builds a request URL, queries it, and returns the data", (done) ->
+  getter = new CartoDBGetter({})
+
+  fakeCartodbURL = "http://fake.cartodb/api"
+  sinon.stub(getter, 'buildUrl', ->
+    return fakeCartodbURL
+  )
+
+  theData = {some: 'data'}
+  getStub = sinon.stub(request, 'get', (cb) ->
+    cb(null, theData)
+  )
+
+  getter.fetch().then((fetchedData)->
+    try
+      assert.isTrue(
+        getStub.calledWith(fakeCartodbURL),
+        "Expected request.get to be called with the result of @buildUrl,
+        but called with #{getStub.getCall(0).args}"
+      )
+
+      assert.strictEqual(fetchedData, theData,
+        "Expected fetch to return the results of the get request"
+      )
+      done()
+
+    catch err
+      done(err)
+
+    finally
+      getStub.restore()
+  ).fail((err) ->
+    getStub.restore()
+    done(err)
+  )
+)


post test for views

post test for views

--- test/integration/api.coffee
+++ test/integration/api.coffee
@@ -272,6 +272,28 @@
             keys = JSON.parse res.body
             keys.should.include 'ssh-rsa AAAAB3NzaC1yc2EAAAAD...mRRu21YYMK7GSE7gZTtbI65WJfreqUY472s8HVIX foo@bar.local'
             done err
+
+      describe 'Views', ->
+        context 'when I create a view on a dataset', ->
+          before (done) ->
+            request.post
+              uri: "#{serverURL}/api/#{@user}/datasets/#{@dataset.box}/views"
+              form:
+                name: 'carrottop'
+                displayName: 'Carrot Top'
+                tool: 'test-plugin'
+            , (err, res, body) =>
+              @response = res
+              @view = JSON.parse res.body
+              done()
+
+          context 'POST /api/:user/datasets/<dataset>/views', ->
+            it 'creates a new dataset', ->
+              @response.should.have.status 200
+
+            it 'returns the newly created dataset', ->
+              should.exist @view.box
+              @view.displayName.should.equal 'Carrot Top'

   describe 'Logging in as a different user', ->
     context "When I'm a staff member", ->


Clarify existing tests

Clarify existing tests

--- test/spec/startGraph.coffee
+++ test/spec/startGraph.coffee
@@ -19,13 +19,15 @@
                 alchemy.nodes.should.not.equal undefined

-            it "should define alchemy.nodes", ->
+            it "should create an object for each node in dataset", ->
                 alchemy.nodes.length.should.equal 69

+        describe "alchemy.edges", ->
+            it "should define alchemy.edges", ->
+                alchemy.edges.should.not.equal undefined

-        it "should define alchemy.edges", ->
-            alchemy.edges.length.should.equal 83
-
+            it "should create an object for each edge in dataset", ->
+                alchemy.edges.length.should.equal 83

         it "should define alchemy.force", ->
             alchemy.force.should.not.equal(undefined)


fix test to actually use invalid email address

--- test/unit/model/user.coffee
+++ test/unit/model/user.coffee
@@ -41,7 +41,7 @@
       @eventSpy.calledOnce.should.be.true

     it 'errors when email is not valid', ->
-      @attrs.email = 'tabby@example.org'
+      @attrs.email = 'tabby@example.org@DROP TABLES;'
       @user.set @attrs, validate: true
       @eventSpy.calledOnce.should.be.true


fix(tooltip): hides tooltip if state change underneath you

--- internal_packages/tooltip/lib/tooltip.cjsx
+++ internal_packages/tooltip/lib/tooltip.cjsx
@@ -47,7 +47,8 @@
   # listeners.
   onMouseOver: (e) ->
     target = @_elementWithTooltip(e.target)
-    @_onTooltipEnter(target) if target
+    if target then @_onTooltipEnter(target)
+    else if @state.display then @_hideTooltip()

   onMouseOut: (e) ->
     if @_elementWithTooltip(e.fromElement) and not @_elementWithTooltip(e.toElement)


(re) Use the new constructify() instead of construct()

--- Paws/Source/Paws.coffee
+++ Paws/Source/Paws.coffee
@@ -14,16 +14,12 @@
 # Core data-types
 # ---------------
 paws.Thing = Thing = parameterizable class Thing
-   constructor: (elements...) ->
-      it = construct this
+   constructor: constructify(return:@) (elements...) ->
+      @id = uuid.v4()
+      @metadata = new Array
+      @push elements... if elements.length

-      it.id = uuid.v4()
-      it.metadata = new Array
-      it.push elements... if elements.length
-
-      it.metadata.unshift undefined if it._?.noughtify != no
-
-      return it
+      @metadata.unshift undefined if @_?.noughtify != no

    # Creates a copy of the `Thing` it is called on. Alternatively, can be given an extant `Thing`
    # copy this `Thing` *to*, over-writing that `Thing`'s metadata. In the process, the
@@ -84,8 +80,7 @@
       if _.isArray(it)
          return it.map (el) => @from el

-   # TODO: `construct()` (or `constructify()`) this
-   constructor: (@to, @isResponsible = false) ->
+   constructor: constructify (@to, @isResponsible = false) ->

    clone: -> new Relation @to, @isResponsible

@@ -94,11 +89,9 @@

 paws.Label = Label = class Label extends Thing
-   constructor: (string) ->
-      it = construct this
-      it.alien = new String string
-      it.alien.native = this
-      return it
+   constructor: constructify(return:@) (@alien) ->
+      @alien = new String @alien
+      @alien.native = this

    clone: (to) ->
       to ?= new Label


fix html entities in episode info panel

fix html entities in episode info panel

--- src/javascripts/extensions/episode_info.coffee
+++ src/javascripts/extensions/episode_info.coffee
@@ -39,8 +39,8 @@
   panelHtml:
     """

-
{ title }

-
{ subtitle }

+

+

     """


Check for number of arguments to multiparam type class during unification and especially in instance declarations

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1509,9 +1509,8 @@
     instanceName = ctx.definitionName()

     ctx.newScope()
-    freshInstanceType = assignMethodTypes ctx, instanceName,
+    freshInstanceType = assignMethodTypes ctx, instanceConstraint, instanceName,
       classDefinition, instanceType, constraints
-    freshConstraints = freshInstanceType.constraints
     definitions = pairs wheres
     methodsDeclarations = definitionList ctx,
       (prefixWithInstanceName definitions, instanceName)
@@ -1527,22 +1526,25 @@

     # TODO: defer for class declaration if not defined
     ## if not ctx.isClassDefined className    ...
-
-    instance = (new Constrained freshConstraints,
-      (new ClassConstraint instanceType.className, freshInstanceType.type))
-    ## if overlaps ctx, instance
-    ##   malformed 'instance overlaps with another', instance
-    ## else
-    ctx.addInstance instanceName, instance
-
-    # """var #{instanceName} = new #{className}(#{listOf methods});"""
-    (jsVarDeclaration (validIdentifier instanceName),
-      (irDefinition (new Constrained freshConstraints, (tupleOfTypes methodTypes).type),
-        (jsNew className, (join superClassInstances, methods))))
+    if not freshInstanceType
+      jsNoop()
+    else
+      freshConstraints = freshInstanceType.constraints
+      instance = (new Constrained freshConstraints,
+        (new ClassConstraint instanceType.className, freshInstanceType.type))
+      ## if overlaps ctx, instance
+      ##   malformed 'instance overlaps with another', instance
+      ## else
+      ctx.addInstance instanceName, instance
+
+      # """var #{instanceName} = new #{className}(#{listOf methods});"""
+      (jsVarDeclaration (validIdentifier instanceName),
+        (irDefinition (new Constrained freshConstraints, (tupleOfTypes methodTypes).type),
+          (jsNew className, (join superClassInstances, methods))))

 # Makes sure methods are typed explicitly and returns the instance constraint
 # with renamed type variables to avoid clashes
-assignMethodTypes = (ctx, instanceName, classDeclaration, instanceType, instanceConstraints) ->
+assignMethodTypes = (ctx, typeExpression, instanceName, classDeclaration, instanceType, instanceConstraints) ->
   # First we must freshen the instance type, to avoid name clashes of type vars
   freshInstanceType = freshInstance ctx,
     (quantifyUnbound ctx,
@@ -1550,7 +1552,9 @@

   # log "mguing", classDeclaration.constraint.types, freshInstanceType.type
   sub = mostGeneralUnifier classDeclaration.constraint.types, freshInstanceType.type
-  # log sub
+  if isFailed sub
+    malformed typeExpression, 'Type doesn\'t match class type'
+    return null

   ctx.bindTypeVariables setToArray (findFree freshInstanceType)
   for name, {arity, type} of values classDeclaration.declarations
@@ -2816,10 +2820,12 @@

 toHtml = (highlighted) ->
   crawl highlighted, (word, symbol, parent) ->
-    (word.ws or '') + colorize(theme[labelOf word], symbol)
-
-labelOf = (word) ->
-  word.malformed and 'malformed' or word.label or 'normal'
+    (word.ws or '') + colorize(theme[labelOf word, parent], symbol)
+
+labelOf = (word, parent) ->
+  word.malformed and 'malformed' or
+    parent.malformed and word.symbol in allDelims and 'malformed' or
+      word.label or 'normal'

 collapse = (nodes) ->
   collapsed = ""
@@ -3518,7 +3524,9 @@
     s2 = mostGeneralUnifier (substitute s1, t1.arg), (substitute s1, t2.arg)
     joinSubs s1, s2
   else if t1.Types and t2.Types
-    if _notEmpty t1.types
+    if t1.types.length isnt t2.types.length
+      unifyFail t1, t2
+    else if _notEmpty t1.types
       s1 = mostGeneralUnifier t1.types[0], t2.types[0]
       s2 = mostGeneralUnifier (new Types t1.types[1...]), (new Types t2.types[1...])
       joinSubs s1, s2
@@ -3566,7 +3574,9 @@
       # newMapWith "could not unify", [(safePrintType t1), (safePrintType t2)]
       unifyFail t1, t2
   else if t1.Types and t2.Types
-    if _notEmpty t1.types
+    if t1.types.length isnt t2.types.length
+      unifyFail t1, t2
+    else if _notEmpty t1.types
       s1 = matchType t1.types[0], t2.types[0]
       s2 = matchType (new Types t1.types[1...]), (new Types t2.types[1...])
       s3 = mergeSubs s1, s2


storage: close when running just stops but doesn't actually close -- just stops.

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -1201,6 +1201,8 @@
             return
         async.parallel([
             (cb) =>
+                @open(cb:cb)
+            (cb) =>
                 if opts.set_quotas
                     dbg("setting all quotas")
                     @set_all_quotas(cb:cb)
@@ -1268,7 +1270,7 @@
                                 cb(err)
                             else
                                 dbg("waiting for project to stop")
-                                @once('stopped', (=>cb()))
+                                @once('opened', (=>cb()))
                 else
                     cb()
             (cb) =>


webapp/files: do not show command execution mode in public view -- issue #2273

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -678,7 +678,7 @@

     render : ->
         <Col sm=12>
-            {@render_terminal_mode()}
+            {@render_terminal_mode() if not @props.public_view}
             {<ListingHeader
                 active_file_sort = {@props.active_file_sort}
                 sort_by          = {@props.sort_by}
@@ -1622,6 +1622,7 @@
         selected_file_index: rtypes.number
         file_creation_error: rtypes.string
         num_files_displayed: rtypes.number
+        public_view        : rtypes.bool.isRequired

     getDefaultProps: ->
         file_search : ''
@@ -1721,7 +1722,7 @@
         @props.actions.setState(file_creation_error : '')

     search_submit: (value, opts) ->
-        if value[0] == TERM_MODE_CHAR
+        if value[0] == TERM_MODE_CHAR and not @props.public_view
             command = value.slice(1, value.length)
             @execute_command(command)
         else if @props.selected_file
@@ -2184,7 +2185,8 @@
                         file_creation_error = {@props.file_creation_error}
                         num_files_displayed = {visible_listing?.length}
                         create_file         = {@create_file}
-                        create_folder       = {@create_folder} />
+                        create_folder       = {@create_folder}
+                        public_view         = {public_view} />
                 </div>
                 {<div style={flex: '0 1 auto', marginLeft: '5px'}>
                     {@render_new_file()}


removed part of restify usage from Provider.coffee

--- src/steroids/data/Provider.coffee
+++ src/steroids/data/Provider.coffee
@@ -1,4 +1,6 @@
 restify = require "restify"
+
+request = require "request"

 paths = require "../paths"
 Login = require "../Login"
@@ -13,9 +15,14 @@
   @CloudReadError: class CloudReadError extends ProviderError
   @CloudWriteError: class CloudWriteError extends ProviderError

-  @apiClient = restify.createJsonClient
+  @legacyClient = restify.createJsonClient
     url: configApiBaseUrl
-  @apiClient.headers["Authorization"] = Login.currentAccessToken()
+  @legacyClient.headers["Authorization"] = Login.currentAccessToken()
+
+  @apiClient = request.defaults
+    auth:
+      user: Login.currentAccessToken()
+      password: "X"

   @forBackend: (backend) =>
     return new Promise (resolve, reject) =>
@@ -107,8 +114,11 @@
       url = "/app/#{dataHelpers.getAppId()}/raml.yaml?identification_hash=#{dataHelpers.getIdentificationHash()}"

       steroidsCli.debug "PROVIDER", "GETting from URL: #{url}"
-      @apiClient.get url, (err, req, res, obj) =>
-        @apiClient.close()
+
+      @apiClient
+        method: "get"
+        url: url
+      , (err, res, body) =>

         if err?
           steroidsCli.debug "PROVIDER", "Getting current data configuration from cloud returned failure: #{err}"
@@ -117,11 +127,11 @@

         if res.statusCode != 200
           steroidsCli.debug "PROVIDER", "Getting current data configuration from cloud returned failure: #{res['body']}"
-          reject new CloudReadError res['body']
+          reject new CloudReadError body
           return

         steroidsCli.debug "PROVIDER", "Getting current data configuration from cloud returned success"
-        resolve(res['body'])
+        resolve(body)

   @writeRamlToFile: (raml)=>
     return new Promise (resolve, reject) =>
@@ -135,8 +145,10 @@
     return new Promise (resolve, reject) =>
       steroidsCli.debug "PROVIDER", "Getting providers from cloud"

-      @apiClient.get "/app/#{dataHelpers.getAppId()}/service_providers.json", (err, req, res, obj) =>
-        @apiClient.close() #TODO: wat
+      @apiClient
+        method: "get"
+        url: "/app/#{dataHelpers.getAppId()}/service_providers.json"
+      , (err, res, body) =>

         if err?
           steroidsCli.debug "PROVIDER", "Getting providers from cloud returned failure: #{JSON.stringify(obj)}"
@@ -145,7 +157,7 @@

         steroidsCli.debug "PROVIDER", "Getting providers from cloud returned success: #{JSON.stringify(obj)}"
         result = []
-        obj.forEach (p)=>
+        body.forEach (p)=>
           result.push @fromCloudObject(p)
         resolve result

@@ -157,7 +169,7 @@
     return provider

   constructor: (@options={}) ->
-    @apiClient = Provider.apiClient
+    @legacyClient = Provider.apiClient

     #TODO Provider.fromBackend(backend)
     @backend = @options.backend
@@ -175,7 +187,7 @@

       url = "/app/#{dataHelpers.getAppId()}/service_providers.json"
       steroidsCli.debug "PROVIDER", "POSTing #{JSON.stringify(data)} to URL #{url}"
-      @apiClient.post url, data, (err, req, res, obj) =>
+      @legacyClient.post url, data, (err, req, res, obj) =>
         if obj.uid
           steroidsCli.debug "PROVIDER", "Creating a new provider #{@name} ID: #{@typeId} to cloud returned success: #{JSON.stringify(obj)}"

@@ -185,7 +197,7 @@
           steroidsCli.debug "PROVIDER", "Creating a new provider #{@name} ID: #{@typeId} to cloud returned failure: #{JSON.stringify(obj)}"
           reject new CloudWriteError err

-        @apiClient.close()
+        @legacyClient.close()

   fromCloudObject: (obj)=>
     steroidsCli.debug "PROVIDER", "Updating attributes for provider from object: #{JSON.stringify(obj)}"
@@ -201,8 +213,7 @@
       url = "/app/#{dataHelpers.getAppId()}/service_providers/#{@uid}/resources.json"

       steroidsCli.debug "PROVIDER", "GETting from URL: #{url}"
-      @apiClient.get url, (err, req, res, obj) =>
-        #@apiClient.close()
+      @legacyClient.get url, (err, req, res, obj) =>

         if err?
           steroidsCli.debug "PROVIDER", "Getting resources for provider #{@name} ID: #{@typeId} from cloud returned failure: #{err} #{JSON.stringify(obj)}"


nginx: set client body path to /tmp in dev

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -174,6 +174,8 @@
     client_header_buffer_size 4k;
     client_max_body_size 10m;

+    #{if environment is 'dev' then 'client_body_temp_path /tmp;' else ''}
+
     sendfile on;

     # for proper content type setting, include mime.types


rephrasing of exit check at the end of findSignatureBindParamsAndMakeCall method

--- fizzylogo_Objects.coffee
+++ fizzylogo_Objects.coffee
@@ -161,7 +161,12 @@
     if returnedContext? and returnedContext.returned != @
       return [returnedContext, returnedMessage]
     else
+    # note that returnedContext here is always defined if there is
+    # a fallback $nothing$ method, as there is now
+    if !returnedContext? or returnedContext.returned == @
       return [null, methodInvocationToBeChecked]
+    else
+      return [returnedContext, returnedMessage]

   # this could be native, in which case it's a JS call,


flash frame in windows

flash frame in windows

--- src/ui/views/notifications.coffee
+++ src/ui/views/notifications.coffee
@@ -106,10 +106,11 @@
             #  and mute option is not set
             if (!notifierSupportsSound || viewstate.forceCustomSound) && !viewstate.muteSoundNotification && audioEl.paused
                 audioEl.play()
-        #if not mainWindow.isVisible()
-        #    mainWindow.showInactive()
-        #    mainWindow.minimize()
-        # mainWindow.flashFrame(true)
+        if process.platform is 'win32'
+            if not mainWindow.isVisible()
+                mainWindow.showInactive()
+                mainWindow.minimize()
+            mainWindow.flashFrame(true)

 textMessage = (cont, proxied, showMessage = true) ->
     if cont?.segment?


Unhide from Tray on Notification

Unhide from Tray on Notification

--- src/ui/views/notifications.coffee
+++ src/ui/views/notifications.coffee
@@ -22,7 +22,7 @@
     {conv, notify, entity, viewstate} = models
     tonot = notify.popToNotify()

-    quietIf = (c, chat_id) -> document?.hasFocus() or conv.isQuiet(c) or entity.isSelf(chat_id)
+    quietIf = (c, chat_id) -> (remote.getCurrentWindow().isVisible() and remote.getCurrentWindow().isFocused()) or conv.isQuiet(c) or entity.isSelf(chat_id)

     tonot.forEach (msg) ->
         conv_id = msg?.conversation_id?.id
@@ -67,7 +67,7 @@
         # maybe trigger OS notification
         return if !text or quietIf(c, chat_id)

-        if viewstate.showPopUpNotifications
+        if viewstate.showPopUpNotifications and not remote.getCurrentWindow().isVisible()
             isNotificationCenter = notifier.constructor.name == 'NotificationCenter'
             #
             icon = path.join __dirname, '..', '..', 'icons', 'icon@8.png'
@@ -106,6 +106,9 @@
                 audioEl.play()
         # And we hope we don't get another 'currentWindow' ;)
         mainWindow = remote.getCurrentWindow()
+        if not mainWindow.isVisible()
+            mainWindow.showInactive()
+            mainWindow.minimize()
         mainWindow.flashFrame(true)

 textMessage = (cont, proxied) ->


Clean up after each to avoid mem-leaks

Clean up after each to avoid mem-leaks

ref: https://travis-ci.org/ngs/hubot-alias/jobs/44082045

--- test/alias_test.coffee
+++ test/alias_test.coffee
@@ -24,7 +24,7 @@
       # load help scripts to test help messages
       hubotScripts = path.resolve 'node_modules', 'hubot', 'src', 'scripts'
       robot.loadFile hubotScripts, 'help.coffee'
-
+
       user = robot.brain.userForId '1', {
         name: 'dtaniwaki'
         room: '#mocha'
@@ -42,6 +42,8 @@

   afterEach ->
     do robot.shutdown
+    do robot.server.close
+    process.removeAllListeners 'uncaughtException'

   sharedExample = (done, src, dst)->
     adapter.on "send", (envelope, strings)->


Changed check tests

Changed check tests

--- test/deps/check.coffee
+++ test/deps/check.coffee
@@ -1,39 +1,31 @@
 #
 # # Check Deps Tests
 #
+path          = require 'path'
 should        = require 'should'
-{
-  check
-  checkNode
-}             = require '../lib/checkdeps'

-describe 'check()', ->
+
+stubsdir  = path.join process.cwd(), 'build', 'test', 'stubs'

+
+describe 'checkDev()', ->
+
+describe 'checkProd()', ->
+
 describe 'checkNodeDev()', ->
-  describe 'with stub/nodeps', ->
+  checkNodeDev  = null
+  before -> {checkNodeDev} = require '../../lib/deps/check'
+
+  describe 'on stub/nodeps', ->
     it 'should callback false', (done) ->
-      checkNode
-        devDeps : true
-        prodDeps: true
+      checkNodeDev
+        dir: path.join stubsdir, 'nodeps'
         (err, result, list) ->
           should.not.exist err
           result.should.equal false
           list.should.equal []

-  describe 'with stub/devdeps', ->
-    it 'should callback true', ->
-      checkNode
-        devDeps : true
-        prodDeps: true
-        (err, result, list) ->
-          should.not.exist err
-          result.should.equal true
-          # This test is a bit unique, because
-          list.should.equal []
-
-
-


update help section

update help section

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -82,7 +82,7 @@
     attachment =
       title: "Using the Gunsmith Bot"
       title_link: "https://github.com/phillipspc/showoff/blob/master/README.md"
-      text: "In #gunsmith, you can show off your weapons by messaging the bot with your gamertag, network, and weapon slot, separated by spaces. The standard usage looks like this: \n```@gunsmithbot: MyGamerTag xbox primary```\nIf you've set up your slack profile so that your *first name* matches your gamertag, you can omit this:```@gunsmithbot: playstation special```\n If your gamertag only exists on one network, that can be omitted as well:```@gunsmithbot: heavy```\n The Gunsmith Bot will always look at the *most recently played character* on your account.\n *Special note to Xbox Users:*\n If your gamertag has any spaces in it, these will need to be substituted with underscores (\"_\") in order for the bot to recognize the input properly. This is only required when inputting the gamertag manually however; spaces are fine in your slack first name.#{admin_message}\n\n _Keep that thing oiled, guardian._"
+      text: "In #gunsmith, you can show off your weapons by messaging the bot with your gamertag, network, and weapon slot, separated by spaces. The Gunsmith Bot will always look at the *most recently played character* on your account. The standard usage looks like this: \n```@gunsmithbot: MyGamerTag xbox primary```\nIf you've set up your slack profile so that your *first name* matches your gamertag, you can omit this:```@gunsmithbot: playstation special```\n If your gamertag only exists on one network, that can be omitted as well:```@gunsmithbot: heavy```\n *Special note to Xbox Users:*\n If your gamertag has any spaces in it, these will need to be substituted with underscores (\"_\") in order for the bot to recognize the input properly. This is only required when inputting the gamertag manually however; spaces are fine in your slack first name.#{admin_message}\n\n _Keep that thing oiled, guardian._"
       mrkdwn_in: ["text"]

     payload =


When on promises

When on promises

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -58,7 +58,7 @@
       if !vendors
         return sendError(robot, res, "Unable to locate vendor: #{input[1]}")

-      itemCategoriesDefers = []
+      itemCategoriesPromises = []
       for vendorHash in vendors
         deferred = new Deferred()
         getVendorSaleItemCategories(res, vendorHash).then (saleItemCategories) ->
@@ -67,10 +67,9 @@
               return deferred.resolve(category.salesItems)
         , (err) ->
           return deferred.reject(err)
-        deferred.promise
-        itemCategoriesDefers.push deferred
-
-      Promise.all(itemCategoriesDefers).done (bountyItems) ->
+        itemCategoriesPromises.push deferred.promise
+
+      when.all(itemCategoriesPromises).done (bountyItems) ->
         console.log 'bountyItems = '
         console.log bountyItems


Tailor command list responses.

Tailor command list responses.

--- scripts/common.coffee
+++ scripts/common.coffee
@@ -15,7 +15,11 @@
         msg.send "You can find me on Twitter at http://twitter.com/masonest_ and on Facebook at http://facebook.com/masonest"

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

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


[master] wip

[master] wip

--- translator/run.coffee
+++ translator/run.coffee
@@ -1,16 +1,30 @@
 #!/usr/bin/env coffee
 Q = require 'q'
+_ = require 'lodash'
 translator = require './'
 database = require '../database'

 if require.main == module
-  sourceRedis = database.redis.getConnection 0
-  targetRedis = database.redis.getConnection 1
-  Q.ninvoke sourceRedis, 'keys', '*'
-  .then (keys) ->
-    keys = _.remove keys, (key) ->
-      key == 'progress'
-    Q.ninvoke sourceRedis, 'get', keys[0]
-    .then (entry) ->
-      console.log entry
+  Q.all [
+    database.redis.getConnection 0
+  , database.redis.getConnection 1
+  ]
+  .then (res) ->
+    sourceRedis = res[0]
+    targetRedis = res[1]
+    Q.ninvoke sourceRedis, 'keys', '*'
+    .then (keys) ->
+      keys = _.filter keys, (key) ->
+        key != 'progress'
+      Q.ninvoke sourceRedis, 'get', keys[0]
+      .then (entry) ->
+        entry = JSON.parse entry
+        translator.baidu.translate entry.title + '\n' + entry.content.replace(/<br>/g, '')
+      .then (res) ->
+        console.log res
+    .then ->
+      targetRedis.closeConnection()
+      sourceRedis.closeConnection()
+  .done()

+


add tests for paginated requests to /nodes and /packages

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -31,6 +31,50 @@

   it 'should have no default host configured', () ->
     expect(scope.ponyExpressHost).toBe(undefined)
+
+  paginateResponse = (httpBackend, baseUrl, response, action, limit=50)->
+    # 1. working pagination
+    #    it will request page 1, get it,
+    #    and request page 2 and finish
+    url = "#{baseUrl}?limit=#{limit}&page=1"
+    httpBackend.whenGET(url).respond(response)
+    httpBackend.expectGET(url)
+    url = "#{baseUrl}?limit=#{limit}&page=2"
+    httpBackend.whenGET(url).respond(410,'Gone')
+    httpBackend.expectGET(url)
+    # take the action and flush the backend
+    action()
+    httpBackend.flush()
+
+  dontPaginateResponse = (httpBackend, baseUrl, response, action, limit=50)->
+    # 2. no pagination
+    #    it will request page 1, won't get it
+    #    and try without pagination
+    url = "#{baseUrl}?limit=#{limit}&page=1"
+    httpBackend.whenGET(url).respond(410,'Gone')
+    httpBackend.expectGET(url)
+    url = "#{baseUrl}"
+    httpBackend.whenGET(url).respond(response)
+    httpBackend.expectGET(url)
+    # take the action and flush the backend
+    action()
+    httpBackend.flush()
+
+  it 'should paginate /nodes if it supports pagination', ()->
+    paginateResponse @httpBackend, '/v1/nodes', [], () -> scope.fetchNodes()
+    expect(scope.nodes.length).toBe(0)
+
+  it 'should not paginate /nodes if it doesnt supports pagination', ()->
+    dontPaginateResponse @httpBackend, '/v1/nodes', [], ()-> scope.fetchNodes()
+    expect(scope.nodes.length).toBe(0)
+
+  it 'should paginate /packages if it supports pagination', ()->
+    paginateResponse @httpBackend, '/v1/packages', [], ()-> scope.fetchPackages()
+    expect(scope.packages.length).toBe(0)
+
+  it 'should not paginate /packages if it doesnt supports pagination', ()->
+    dontPaginateResponse @httpBackend, '/v1/packages', [], ()-> scope.fetchPackages()
+    expect(scope.packages.length).toBe(0)

   it 'should be able to list /nodes', () ->
     nodes = [


Fix sed command for setting kontrol postgres login

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -403,9 +403,8 @@
       # Build postgres
       pushd $KONFIG_PROJECTROOT/go/src/socialapi/db/sql
       mkdir -p kontrol
-      sed -i -e "s/somerandompassword/$KONFIG_KONTROL_POSTGRES_PASSWORD/" kontrol/001-schema.sql
-      sed -i -e "s/kontrolapplication/$KONFIG_KONTROL_POSTGRES_USERNAME/" kontrol/001-schema.sql
-
+      sed -i -e "s/USER kontrolapplication/USER $KONFIG_KONTROL_POSTGRES_USERNAME/" kontrol/001-schema.sql
+      sed -i -e "s/PASSWORD 'kontrolapplication'/PASSWORD '$KONFIG_KONTROL_POSTGRES_PASSWORD'/" kontrol/001-schema.sql
       docker build -t koding/postgres .
       runMongoDocker
       docker run -d -p 5672:5672 -p 15672:15672                           --name=rabbitmq rabbitmq:3-management


config: remove unnecessary migrate line

config: remove unnecessary migrate line

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -561,7 +561,6 @@
       fi

       build_services
-      migrate up

     elif [ "$1" == "help" ]; then
       printHelp


trying other mq password

trying other mq password

--- config/main.production.coffee
+++ config/main.production.coffee
@@ -104,7 +104,7 @@
     host        : 'web0.beta.system.aws.koding.com'
     login       : 'PROD-k5it50s4676pO9O'
     componentUser: "prod-<component>"
-    password    : 'Dtxym6fRJXx4GJz'
+    password    : 'djfjfhgh4455__5'
     heartbeat   : 10
     vhost       : '/'
   broker        :
@@ -133,7 +133,7 @@
     mq              :
       host          : 'web0.beta.system.aws.koding.com'
       login         : 'PROD-k5it50s4676pO9O'
-      password      : 'Dtxym6fRJXx4GJz'
+      password      : 'djfjfhgh4455__5'
   pidFile       : '/tmp/koding.server.pid'
   loggr:
     push: yes


Add comment

Add comment

--- rtsp.coffee
+++ rtsp.coffee
@@ -1842,6 +1842,7 @@
         stream.rtspUploadingClient.uploadingTimestampInfo.audio.rtpTimestamp = packet.rtcpSenderReport.rtpTimestamp
         stream.rtspUploadingClient.uploadingTimestampInfo.audio.time = packet.rtcpSenderReport.ntpTimestampInMs

+# Represents an RTSP session
 class RTSPClient
   constructor: (opts) ->
     @videoPacketCount = 0


change coverage output

change coverage output

--- tests/karma/all.conf.coffee
+++ tests/karma/all.conf.coffee
@@ -32,7 +32,7 @@
     ]
     exclude: []
     coverageReporter:
-      type: 'html'
+      type: 'lcov'
       dir: '../coverage/'
     reporters: ['progress']
     preprocessors:


children obj instead of array

children obj instead of array

--- ccss.coffee
+++ ccss.coffee
@@ -19,22 +19,20 @@
 css = ''

 parse = (selector, declarations) ->
-  children = []
+  children = {}

   for property, value of declarations
     if typeof value is 'object'
-      children.push
-        selector: "#{selector} #{property}"
-        declarations: value
       delete declarations[property]
+      children["#{selector} #{property}"] = value

   css += selector + ' {\n'
   for property, value of declarations
     css += "  #{property}: #{value};\n"
   css += '}\n'

-  for child in children
-    parse child.selector, child.declarations
+  for selector, declarations of children
+    parse selector, declarations

 for selector, declarations of root
   parse selector, declarations


bin: redice fake_data users to 50

bin: redice fake_data users to 50

--- bin/fake_data.coffee
+++ bin/fake_data.coffee
@@ -84,7 +84,7 @@
   # for each app
   Promise.map games, (game) ->
     # 50 users
-    Promise.map _.range(500), (index) ->
+    Promise.map _.range(50), (index) ->
       daysToSimulate = 8
       activeDays = _.sample [1, 1, 1, 2, 3]
       joinDay = _.sample _.range(daysToSimulate)


needs role

needs role

--- shared/specs/model/notifications.spec.coffee
+++ shared/specs/model/notifications.spec.coffee
@@ -65,7 +65,7 @@
     notice = {message: 'hello world', icon: 'globe'}
     Notifications.once('change', changeListener)
     course = {id: '1', students: [{role_id: '111'}], ends_at: '2011-11-11T01:15:43.807Z'}
-    Notifications.setCourseRole(course, {})
+    Notifications.setCourseRole(course, {id: '111'})
     expect(changeListener).to.have.been.called
     active = Notifications.getActive()[0]
     expect(active.type).to.equal('course_has_ended')


IpfsAdaptor.create() method returns a new instance only if IPFS is properly configured; put() method


Update terminal-plus styles

Update terminal-plus styles

--- .atom/config.cson
+++ .atom/config.cson
@@ -56,8 +56,20 @@
   "linter-eslint":
     globalNodePath: "/Users/BBoyKelvin/.nvm/versions/node/v5.8.0"
   "terminal-plus":
-    style:
-      theme: "homebrew"
+    ansiColors:
+      normal:
+        blue:
+          red: 0
+          green: 102
+          blue: 255
+          alpha: 1
+      zBright:
+        brightBlue:
+          red: 0
+          green: 136
+          blue: 255
+          alpha: 1
+    style: {}
   "tree-view": {}
   wakatime:
     apikey: "Saved in your ~/.wakatime.cfg file"


Adjusts the atom configs.

Adjusts the atom configs.

--- .atom/config.cson
+++ .atom/config.cson
@@ -1,6 +1,22 @@
 "*":
   "atom-ide-ui":
-    use: {}
+    use:
+      "atom-ide-busy-signal": "default"
+      "atom-ide-code-actions": "default"
+      "atom-ide-code-format": "default"
+      "atom-ide-code-highlight": "default"
+      "atom-ide-console": "default"
+      "atom-ide-datatip": "default"
+      "atom-ide-debugger": "default"
+      "atom-ide-definitions": "default"
+      "atom-ide-diagnostics": "default"
+      "atom-ide-diagnostics-ui": "default"
+      "atom-ide-find-references": "default"
+      "atom-ide-global": "default"
+      "atom-ide-outline-view": "default"
+      "atom-ide-signature-help": "default"
+      "atom-ide-terminal": "default"
+      hyperclick: "default"
   "atom-python-virtualenv": {}
   "autocomplete-python":
     useKite: false


run: Error if tags are not key=value

run: Error if tags are not key=value


change to 'context' from 'this' for readability

--- lib/ensure-certs.coffee
+++ lib/ensure-certs.coffee
@@ -1,6 +1,6 @@
-module.exports = (control) ->
+module.exports = (control, context) ->

-  result = require('./read-certs') this # this is the options
+  result = require('./read-certs') context # this is the options

   if result?.error? then control.fail result.error, result.reason


menu/tools: Remove debugging output.

menu/tools: Remove debugging output.

--- Oghma/menu/tools.coffee
+++ Oghma/menu/tools.coffee
@@ -30,7 +30,6 @@
         text: 'Ruler'
         handler: ->
           O.twopoint.load( 'ruler', {}, ( attrs ) ->
-            console.debug( 'attrs = ', attrs )
             O.tableverse.create( 'ruler', attrs )
           )
       },


isortpath can evaluate from PATH

isortpath can evaluate from PATH

isort Path setting can be an absolute path or the bin name, and would
run the bin from your PATH.


Extend jasmine spec for TabPanel.

Extend jasmine spec for TabPanel.

--- spec/coffeescripts/Rwiki/TabPanelSpec.coffee
+++ spec/coffeescripts/Rwiki/TabPanelSpec.coffee
@@ -5,9 +5,9 @@
     tabContent = $('<div />').attr('id', 'tabpanel-div')
     $('#jasmine_content').append(tabContent)

-    tabPanel = new Rwiki.TabPanel({
+    tabPanel = new Rwiki.TabPanel(
       renderTo: 'tabpanel-div'
-    })
+    )

   afterEach ->
     $('#jasmine_content').empty()
@@ -26,38 +26,33 @@
       ]

       for path in paths
-        page = new Rwiki.Data.Page({path: path})
+        page = new Rwiki.Data.Page(path: path)
         tabPanel.createPageTab(page)

-      this.addMatchers({
-        toIncludeTabWithPath: (path) ->
-          result = false
-          for tab in this.actual
-            result = tab.getPagePath == path
-            break if result
-          return result
-	     })
-
-    xit "should return valid tabs for '/Home/foo/bar'", ->
+    it "should return valid tabs for '/Home/foo/bar'", ->
       tabs = tabPanel.findTabsByParentPath('/Home/foo/bar')
+      nthTabPagePath = (n) -> tabs[n].getPagePath()
+
+      expect(tabs.length).toBe(4)
+      expect(nthTabPagePath(0)).toBe('/Home/foo/bar/test')
+      expect(nthTabPagePath(1)).toBe('/Home/foo/bar/test/test 1')
+      expect(nthTabPagePath(2)).toBe('/Home/foo/bar/test/test 2')
+      expect(nthTabPagePath(3)).toBe('/Home/foo/bar')
+
+    it "should return valid tabs for '/Home/foo/test'", ->
+      tabs = tabPanel.findTabsByParentPath('/Home/foo/test')
+      nthTabPagePath = (n) -> tabs[n].getPagePath()

       expect(tabs.length).toBe(3)
-      expect(tabs).toIncludeTabWithPath('/Home/foo/bar/test')
-      expect(tabs).toIncludeTabWithPath('/Home/foo/bar/test/test 1')
-      expect(tabs).toIncludeTabWithPath('/Home/foo/bar/test/test 2')
-
-    xit "should return valid tabs for '/Home/foo/test'", ->
-      tabs = tabPanel.findTabsByParentPath('/Home/foo/test')
-
-      expect(tabs.length).toBe(2)
-      expect(tabs).toIncludeTabWithPath('/Home/foo/test/bar 1')
-      expect(tabs).toIncludeTabWithPath('/Home/foo/test/bar 2')
+      expect(nthTabPagePath(0)).toBe('/Home/foo/test')
+      expect(nthTabPagePath(1)).toBe('/Home/foo/test/bar 1')
+      expect(nthTabPagePath(2)).toBe('/Home/foo/test/bar 2')

   describe "onPageCreated handler", ->
-    page = new Rwiki.Data.Page({
+    page = new Rwiki.Data.Page(
       path: '/Home/page',
       text: 'page'
-    })
+    )
     tab = {}

     beforeEach ->


Update spec for new status bar label

Update spec for new status bar label

--- spec/package-updates-status-view-spec.coffee
+++ spec/package-updates-status-view-spec.coffee
@@ -21,4 +21,4 @@

   describe "when packages are outdated", ->
     it "adds a tile to the status bar", ->
-      expect($('status-bar .package-updates-status-view').text()).toBe '1'
+      expect($('status-bar .package-updates-status-view').text()).toBe '1 update!'


Morning!

Morning!

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -10,9 +10,9 @@

 module.exports = (robot) ->

-  # robot.hear /badger/i, (res) ->
-  #   res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
-  #
+  robot.hear /morning/i, (res) ->
+    res.send "Morning!"
+
   # robot.respond /open the (.*) doors/i, (res) ->
   #   doorType = res.match[1]
   #   if doorType is "pod bay"


[master] trim

[master] trim

--- translator/baidu-web.coffee
+++ translator/baidu-web.coffee
@@ -24,10 +24,3 @@

 exports.translate = translate

-if require.main == module
-  translate '终于开始活跃了\n'
-  .then (i) ->
-    console.log i
-  .done()
-
-


enter multi-line javascript scripts to Benders js parser

--- scripts/sandbox.coffee
+++ scripts/sandbox.coffee
@@ -14,7 +14,7 @@
 Sandbox = require('sandbox')

 module.exports = (robot) ->
-  robot.respond /(run|sandbox|js) ()(.|\s)+)/i, (msg) ->
+  robot.respond /(run|sandbox|js) ((.|\s)+)/i, (msg) ->
     sandbox = new Sandbox
     sandbox.run(msg.match[2], (output) ->
       msg.send output.result


fix HSL query formation

fix HSL query formation

--- hsl.coffee
+++ hsl.coffee
@@ -7,8 +7,10 @@

 getHslQueryString = (request, params) ->
   qry = "/hsl/prod/?request=#{request}&user=#{hslApiUsername}&pass=#{hslApiPassword}&epsg_in=4326&epsg_out=4326"
-  qry += "&#{k}=#{encodeURIComponent(v)}" for own k,v of params
-
+  for own k,v of params
+    qry += "&#{k}=#{encodeURIComponent(v)}"
+  qry
+
 hslRequest = (request, params, callback) ->
   qryString = getHslQueryString(request, params)
   logger.info "HSL outgoing: #{qryString}"


Fixed bug with moving dice after the game has finished on debug mode on:

--- server/src/Game.coffee
+++ server/src/Game.coffee
@@ -86,6 +86,7 @@
     @submittedSolutions= []

     @started = false
+    @challengeMode = false

     @rightAnswers = []
     @answerExists = false


Optimize GameState log by using two stacks

--- core.coffee
+++ core.coffee
@@ -4,7 +4,8 @@
     this.myIndex = enemyUnits.length # Index of first player unit
     for u in myUnits
       this.units.push u
-    this.log = []
+    this.logIndex = []
+    this.logValues = []

   each: (includeFriendly, callback) ->
     len = (if includeFriendly then this.units.length else this.myIndex)
@@ -19,11 +20,11 @@
   damage: (i, amt) ->
     prev = this.units[i]
     this.units[i] -= amt
-    this.log.push([i, prev ])
+    this.logIndex.push(i)
+    this.logValues.push(prev)

   undo: ->
-    [i, val] = this.log.pop()
-    this.units[i] = val
+    this.units[this.logIndex.pop()] = this.logValues.pop()

 class ProbabilityAggregator
   constructor: (units) ->
@@ -56,4 +57,9 @@
     else
       agg.addResults(state.units, probability)
   explore(0, 1)
-  return agg+  return agg
+
+d = new Date()
+for i in [1..100]
+  search([30, 2], [30, 2, 2, 2, 2], [1, 1, 1, 1, 1, 1, 1], [false, false, false, false, false, false, false, false])
+console.log new Date() - d


Model map return for fromC3mls()

Model map return for fromC3mls()

- Ensured meteor method still returns import count.
- Added support for ignoring entities from forEachEntity.

--- src/EntityImporter.coffee
+++ src/EntityImporter.coffee
@@ -227,10 +227,17 @@

     modelsPromise = Q.all(modelDfs)
     modelsPromise.fail(df.reject)
-    modelsPromise.then Meteor.bindEnvironment ->
+    modelsPromise.then Meteor.bindEnvironment (models) ->
+      modelMap = {}
+      # modelIds also includes null values for entities which were skipped and not inserted.
+      models = _.filter models, (model) -> model?
+      importCount = 0
+      _.each models, (model) ->
+        modelMap[model._id] = model
+        importCount++
+
       requirejs ['atlas/model/GeoPoint'], Meteor.bindEnvironment (GeoPoint) ->
-        importCount = modelDfs.length
-        resolve = -> df.resolve(importCount)
+        resolve = -> df.resolve(modelMap)
         Logger.info 'Imported ' + importCount + ' entities'
         missingParentIds = _.keys missingParentsIdMap
         if missingParentIds.length > 0
@@ -403,6 +410,8 @@
       model = @_mapEntity(model, args) ? model
       if args.forEachEntity
         model = args.forEachEntity.call(EntityImporter, {entity: model, c3ml: c3ml}) ? model
+        if model == false
+          return modelDf.resolve(null)

       callback = (err, insertId) ->
         if err
@@ -417,7 +426,8 @@
             Logger.error('Failed to log entity insert failure', e)
           modelDf.reject(err)
         else
-          modelDf.resolve(insertId)
+          model._id = insertId
+          modelDf.resolve(model)

       Entities.insert model, callback
       args.counterLog.increment()
@@ -495,7 +505,9 @@
         df.promise.fin -> delete importRequestMap[importId]
         Logger.info 'Import request started', importId
       Promises.runSync ->
-        promise = EntityImporter.fromAsset(args)
+        promise = EntityImporter.fromAsset(args).then (modelMap) ->
+          # Return just a count of the number of imported models.
+          _.size(modelMap)
         if df?
           df.resolve(promise)
           # Cancelling the import should return the import method.


group together tests

group together tests

--- services/web/test/unit/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee
+++ services/web/test/unit/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee
@@ -43,37 +43,40 @@
 			@updateMerger.p.writeStreamToDisk = sinon.stub().callsArgWith(3, null, @fsPath)
 			@FileTypeManager.isBinary = sinon.stub()

-		it 'should get the element id', (done)->
-			@projectLocator.findElementByPath = sinon.spy()
-			@updateMerger.mergeUpdate @user_id, @project_id, @path, @update, @source, =>
-			@projectLocator.findElementByPath.calledWith(@project_id, @path).should.equal true
-			done()
+		describe "doc updates", () ->
+			beforeEach ->
+				@doc_id = "231312s"
+				@FileTypeManager.isBinary.callsArgWith(2, null, false)
+				@projectLocator.findElementByPath = sinon.stub().callsArgWith(2, null, _id: @doc_id)
+				@updateMerger.p.processDoc = sinon.stub().callsArgWith(6)
+				@filePath = "/folder/doc.tex"

-		it 'should process update as doc when it is a doc', (done)->
-			doc_id = "231312s"
-			@FileTypeManager.isBinary.callsArgWith(2, null, false)
-			@projectLocator.findElementByPath = (_, __, cb)->cb(null, {_id:doc_id})
-			@updateMerger.p.processDoc = sinon.stub().callsArgWith(6)
-			filePath = "/folder/doc.tex"
+			it 'should get the element id', (done)->
+				@updateMerger.mergeUpdate @user_id, @project_id, @path, @update, @source, =>
+					@projectLocator.findElementByPath.calledWith(@project_id, @path).should.equal true
+					done()

-			@updateMerger.mergeUpdate @user_id, @project_id, filePath, @update, @source, =>
-				@FileTypeManager.isBinary.calledWith(filePath, @fsPath).should.equal true
-				@updateMerger.p.processDoc.calledWith(@project_id, doc_id, @user_id, @fsPath, filePath, @source).should.equal true
-				@fs.unlink.calledWith(@fsPath).should.equal true
-				done()
+			it 'should process update as doc', (done)->
+				@updateMerger.mergeUpdate @user_id, @project_id, @filePath, @update, @source, =>
+					@FileTypeManager.isBinary.calledWith(@filePath, @fsPath).should.equal true
+					@updateMerger.p.processDoc.calledWith(@project_id, @doc_id, @user_id, @fsPath, @filePath, @source).should.equal true
+					@fs.unlink.calledWith(@fsPath).should.equal true
+					done()

-		it 'should process update as file when it is not a doc', (done)->
-			file_id = "1231"
-			@projectLocator.findElementByPath = (_, __, cb)->cb(null, {_id:file_id})
-			@FileTypeManager.isBinary.callsArgWith(2, null, true)
-			@updateMerger.p.processFile = sinon.stub().callsArgWith(6)
-			filePath = "/folder/file1.png"
+		describe "file updates", () ->
+			beforeEach ->
+				@file_id = "1231"
+				@projectLocator.findElementByPath = sinon.stub().callsArgWith(2, null, _id: @file_id)
+				@FileTypeManager.isBinary.callsArgWith(2, null, true)
+				@updateMerger.p.processFile = sinon.stub().callsArgWith(6)
+				@filePath = "/folder/file1.png"

-			@updateMerger.mergeUpdate @user_id, @project_id, filePath, @update, @source, =>
-				@updateMerger.p.processFile.calledWith(@project_id, file_id, @fsPath, filePath, @source, @user_id).should.equal true
-				@FileTypeManager.isBinary.calledWith(filePath, @fsPath).should.equal true
-				@fs.unlink.calledWith(@fsPath).should.equal true
-				done()
+			it 'should process update as file when it is not a doc', (done)->
+				@updateMerger.mergeUpdate @user_id, @project_id, @filePath, @update, @source, =>
+					@updateMerger.p.processFile.calledWith(@project_id, @file_id, @fsPath, @filePath, @source, @user_id).should.equal true
+					@FileTypeManager.isBinary.calledWith(@filePath, @fsPath).should.equal true
+					@fs.unlink.calledWith(@fsPath).should.equal true
+					done()

 	describe 'deleteUpdate', (done)->
 		beforeEach ->
@@ -82,13 +85,13 @@
 			@editorController.deleteEntityWithoutLock = ->
 			@entity_id = "entity_id_here"
 			@entity = _id:@entity_id
-			@projectLocator.findElementByPath = (project_id, path, cb)=> cb(null, @entity, @type)
+			@projectLocator.findElementByPath = sinon.stub().callsArgWith(2, null, @entity, @type)
 			@editorController.deleteEntityWithoutLock = sinon.stub().callsArg(5)

-		it 'should get the element id', ->
-			@projectLocator.findElementByPath = sinon.spy()
-			@updateMerger.deleteUpdate @user_id, @project_id, @path, @source, ->
-			@projectLocator.findElementByPath.calledWith(@project_id, @path).should.equal true
+		it 'should get the element id', (done)->
+			@updateMerger.deleteUpdate @user_id, @project_id, @path, @source, =>
+				@projectLocator.findElementByPath.calledWith(@project_id, @path).should.equal true
+				done()

 		it 'should delete the entity in the editor controller with the correct type', (done)->
 			@entity.lines = []


fix modulus bug in javascript

fix modulus bug in javascript

--- app/assets/javascripts/events.coffee
+++ app/assets/javascripts/events.coffee
@@ -11,7 +11,7 @@
   selects[3].value = last_selects[8].value

   # hour overflow handling
-  endhour = (2 * parseInt(last_selects[8].value) - parseInt(last_selects[3].value)) % 24
+  endhour = ((2 * parseInt(last_selects[8].value) - parseInt(last_selects[3].value)) % 24 + 24) % 24

   # day overflow handling
   selects[5].value = parseInt(selects[5].value) + 1 if (endhour < parseInt(last_selects[8].value))


Fix another datepicker

Fix another datepicker

--- app/assets/javascripts/events.coffee
+++ app/assets/javascripts/events.coffee
@@ -8,8 +8,8 @@
   setupDatePickers =  (e) ->
     e.find('.datepicker').datepicker
       dateFormat:'D dd M yy'
-      prevText: "<i class='icon-arrow-left icon-white'/>"
-      nextText: "<i class='icon-arrow-right icon-white'/>"
+      prevText: "<-"
+      nextText: " ->"

   # Load and open the extended pane of an event
   $('.extend-button')


Fix unintentional search-replaces

Fix unintentional search-replaces

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -449,7 +449,7 @@
       sin2.post new noflo.IP 'data', 'bar',
         groups: ['bar']

-    it 'should receive and send just noflo.IP data if wanted', (done) ->
+    it 'should receive and send just IP data if wanted', (done) ->
       c = new noflo.Component
         inPorts:
           foo: datatype: 'string'
@@ -560,7 +560,7 @@
       sin2.post new noflo.IP 'data', 'bar'
       sin1.post new noflo.IP 'data', 'boo'

-    it 'should keep last data-typed noflo.IP packet for controls', (done) ->
+    it 'should keep last data-typed IP packet for controls', (done) ->
       c = new noflo.Component
         inPorts:
           foo: datatype: 'string'
@@ -1007,7 +1007,7 @@

       cnt = 0
       sout1.on 'ip', (ip) ->
-        # done new Error "Unexpectd noflo.IP: #{ip.type} #{ip.data}"
+        # done new Error "Unexpected IP: #{ip.type} #{ip.data}"

       count = 0
       sout2.on 'ip', (ip) ->


GetColors css option spec

GetColors css option spec

--- spec/GetColors.coffee
+++ spec/GetColors.coffee
@@ -107,3 +107,35 @@
         ins.beginGroup id
         ins.send canvas
         ins.endGroup()
+
+  describe 'when css color option is true', ->
+    ins_css = null
+    beforeEach ->
+      ins_css = noflo.internalSocket.createSocket()
+      c.inPorts.css.attach ins_css
+      ins_css.send true
+
+    input = 'colorful-octagon.png'
+
+    expected = [
+      "rgb(4, 251, 251)"
+      "rgb(251, 4, 4)"
+      "rgb(251, 251, 4)"
+      "rgb(4, 4, 251)"
+      "rgb(251, 4, 251)"
+      "rgb(4, 251, 4)"
+      "rgb(4, 4, 4)"
+      "rgb(221, 221, 218)"
+      "rgb(52, 52, 4)"
+    ]
+    it 'should extract the colors and output css strings', (done) ->
+      colors.once "data", (colors) ->
+        chai.expect(colors).to.be.an 'array'
+        chai.expect(colors).to.have.length expected.length
+        chai.expect(colors[0]).to.be.a 'string'
+        chai.expect(colors).to.deep.equal expected
+        done()
+      id = getCanvasWithImage input, (canvas) ->
+        ins.beginGroup id
+        ins.send canvas
+        ins.endGroup()


Fix rounding error in splitting

Fix rounding error in splitting

--- app/assets/javascripts/mix.js.coffee
+++ app/assets/javascripts/mix.js.coffee
@@ -65,7 +65,7 @@
       wavesurfer.regions.list[regionsArray()[1].region_id].update(start: 0)
     else
       # set prev's end as this end
-      replacer = (region for region_id, region of wavesurfer.regions.list when round100(region.end) == round10(deleteCandidate.start))[0]
+      replacer = (region for region_id, region of wavesurfer.regions.list when round100(region.end) == round100(deleteCandidate.start))[0]
       wavesurfer.regions.list[replacer.id].update(end: deleteCandidate.end)
   if regionId
     wavesurfer.regions.list[regionId].remove()


Update map style. Add attribution.

Update map style. Add attribution.

--- app/assets/javascripts/trails.coffee
+++ app/assets/javascripts/trails.coffee
@@ -1,3 +1,5 @@
+attribution = "© Mapbox © OpenStreetMap"
+
 if $('body.home.index').length > 0 or $('body.trails.index').length > 0
   window.mtb.map
   mapShown = false
@@ -18,10 +20,11 @@
           zoomControl: !L.Browser.mobile
           fullscreenControl: true
         })
+        window.mtb.map.attributionControl.setPrefix('')
         window.mtb.map.on('fullscreenchange', ->
           window.mtb.map.scrollWheelZoom[if window.mtb.map.isFullscreen() then 'enable' else 'disable']()
         )
-        L.mapbox.styleLayer('mapbox://styles/jcsanford/cinjp1yve001vb2nm28qarpmx').addTo(window.mtb.map)
+        L.mapbox.styleLayer('mapbox://styles/jcsanford/cio4ooe8m003uafnddveg2efw', {attribution}).addTo(window.mtb.map)
         window.mtb.map.addLayer(pointGeojsonLayer)
         window.mtb.map.setView(L.latLng(35.228082, -80.8442896), 9)
         mapShown = true
@@ -53,15 +56,16 @@
   pointGeojsonLayer = L.geoJson(mtb.trail, {
     pointToLayer: L.mapbox.marker.style
   })
-  map = L.map('map', {
+  map = window.map = L.map('map', {
     scrollWheelZoom: false
     zoomControl: !L.Browser.mobile
     fullscreenControl: true
   })
+  map.attributionControl.setPrefix('')
   map.on('fullscreenchange', ->
     map.scrollWheelZoom[if map.isFullscreen() then 'enable' else 'disable']()
   )
-  L.mapbox.styleLayer('mapbox://styles/jcsanford/cinjp1yve001vb2nm28qarpmx').addTo(map)
+  L.mapbox.styleLayer('mapbox://styles/jcsanford/cio4ooe8m003uafnddveg2efw', {attribution}).addTo(map)
   map.addLayer(pointGeojsonLayer)

   if mtb.trail.properties.map_center_latitude and mtb.trail.properties.map_center_longitude


Remove ->CONSTANT #100

Remove ->CONSTANT #100

--- lib/providers/autocomplete-provider.coffee
+++ lib/providers/autocomplete-provider.coffee
@@ -78,10 +78,10 @@
         description: if element.args.descriptions.short? then element.args.descriptions.short else ''

     # Constants and public properties
-    else
+    else if element.isProperty
       suggestions.push
         text: word,
-        type: if element.isProperty then 'property' else 'constant'
+        type: 'property'
         leftLabel: returnValues[returnValues.length - 1]
         description: if element.args.descriptions.short? then element.args.descriptions.short else ''
         className: if element.args.deprecated then 'php-atom-autocomplete-strike' else ''


Add PreferPoetMode documentation

Add PreferPoetMode documentation

--- src/PreferPoetMode.coffee
+++ src/PreferPoetMode.coffee
@@ -4,7 +4,18 @@
     level: 'ignore'
     message: 'Prefer poet mode'
     description: '''
-      TODO
+      This rule prohibits unneeded explicit parens on function
+      calls.
+      <pre>
+      <code># Some folks don't like this style of coding.
+      myFunction(a, b, c)
+      # And would rather it always be written like this:
+      myFunction a, b, c
+      </code>
+      </pre>
+      This rulle enforces that implicit parens be used whenever
+      possible since their use is
+      idiomatic CoffeeScript.
       '''
   constructor: ->


fix enemy name

fix enemy name

--- index.cjsx
+++ index.cjsx
@@ -361,6 +361,7 @@
         leastHp = 0
         if isPractice?
           leastHp = 1
+          enemyName = __ "PvP"
           [sortieHp, sortieInfo] = getDeckInfo sortieHp, sortieInfo, parseInt(postBody.api_deck_id) - 1
         [enemyHp, enemyInfo] = getEnemyInfo enemyHp, enemyInfo, body.api_ship_ke, body.api_ship_lv, body.api_maxhps, leastHp
         if body.api_formation?


Add useEnv

Add useEnv

--- index.cjsx
+++ index.cjsx
@@ -5,3 +5,4 @@
     width: 850
     height: 650
   windowURL: "file://#{__dirname}/battle-detail.html"
+  useEnv: true


dynamically set textarea rows

dynamically set textarea rows

--- app/classifier/tasks/text/index.cjsx
+++ app/classifier/tasks/text/index.cjsx
@@ -58,12 +58,15 @@
     annotation: null
     onChange: NOOP

+  getInitialState: ->
+    rows: 1
+
   render: ->
     <GenericTask question={@props.task.instruction} help={@props.task.help} required={@props.task.required}>
       <label className="answer">
-        <textarea autoFocus={@props.autoFocus} className="standard-input full" rows="5" ref="textInput" value={@props.annotation.value} onChange={@handleChange} />
+        <textarea autoFocus={@props.autoFocus} className="standard-input full" rows={@state.rows} ref="textInput" value={@props.annotation.value} onChange={@handleChange} />
       </label>
-      {if @props.task.text_tags
+      {if @props.task.text_tags
           <div className="transcription-metadata-tags">
             {for tag, i in @props.task.text_tags
               <input type="button" className="standard-button text-tag" key={i} value={tag} onClick={@setTagSelection} />}
@@ -71,6 +74,10 @@
     </GenericTask>

   handleChange: ->
+    if @refs.textInput.scrollHeight > @refs.textInput.offsetHeight
+      rows = @state.rows + 1
+      @setState rows: rows
+
     value = @refs.textInput.value
     newAnnotation = Object.assign @props.annotation, {value}
     @props.onChange newAnnotation


:lipstick: Sort exports alphabetically

:lipstick: Sort exports alphabetically

--- exports/atom-api.coffee
+++ exports/atom-api.coffee
@@ -6,8 +6,8 @@
   $: require '../src/jquery-extensions'
   $$: $$
   $$$: $$$
+  BufferedNodeProcess: require '../src/buffered-node-process'
   BufferedProcess: require '../src/buffered-process'
-  BufferedNodeProcess: require '../src/buffered-node-process'
   Directory: require '../src/directory'
   EventEmitter: require '../src/event-emitter'
   File: require '../src/file'


added js-cancel-option-sort from tp #1240

--- jsapp/xlform_model_view/view.choices.coffee
+++ jsapp/xlform_model_view/view.choices.coffee
@@ -44,6 +44,7 @@
           placeholder: "option-placeholder"
           opacity: 0.9
           scroll: false
+          cancel: "js-cancel-option-sort"
           deactivate: =>
             if @hasReordered
               @reordered()


fix typo

fix typo

--- examples/examples.cjsx
+++ examples/examples.cjsx
@@ -106,8 +106,8 @@

onPin — callback called when header is pinned

onUnpin — callback called when header is unpinned

offset — vertical offset in px before component is first unpinned

-
upTolerance — scroll telerance in px when scrolling up before component is pinned

-
downTolerance — scroll telerance in px when scrolling down before component is pinned

+
upTolerance — scroll tolerance in px when scrolling up before component is pinned

+
downTolerance — scroll tolerance in px when scrolling down before component is pinned

disable — disable the hide/show effect if you're on say mobile.


Set text before showing

Set text before showing

--- src/app/status-bar.coffee
+++ src/app/status-bar.coffee
@@ -63,8 +63,8 @@
       @head = null

     if @head
+      @branchLabel.text(@head)
       @branchArea.show()
-      @branchLabel.text(@head)
     else
       @branchArea.hide()


add performance benchmarks

add performance benchmarks


Remove taskable.js

Remove taskable.js

--- app/assets/javascripts/behaviors/taskable.js.coffee
+++ app/assets/javascripts/behaviors/taskable.js.coffee
@@ -1,21 +0,0 @@
-window.updateTaskState = (taskableType) ->
-  objType = taskableType.data
-  isChecked = $(this).prop("checked")
-  if $(this).is(":checked")
-    stateEvent = "task_check"
-  else
-    stateEvent = "task_uncheck"
-
-  taskableUrl = $("form.edit-" + objType).first().attr("action")
-  taskableNum = taskableUrl.match(/\d+$/)
-  taskNum = 0
-  $("li.task-list-item input:checkbox").each( (index, e) =>
-    if e == this
-      taskNum = index + 1
-  )
-
-  $.ajax
-    type: "PATCH"
-    url: taskableUrl
-    data: objType + "[state_event]=" + stateEvent +
-      "&" + objType + "[task_num]=" + taskNum


Removed unused coffeescript file.

Removed unused coffeescript file.

The code in the removed file was related to start page events, which are now done with angularjs.


close dates dialog on invalid save

close dates dialog on invalid save

fixes CNVS-17649

test plan:
  - with DA on
    - edit an assignment and remove the title
    - only assign it to one section and hit save
    - when you hit save the DA dialog opens
    - when you hit save on that, it closes and displays
      the no name error
  - with DA off
    - edit an assignment and remove the title
    - only give a date to one section and hit save
    - when you hit save the dates dialog opens
    - when you hit save on that, it closes and displays
      the no name error

Change-Id: I9e8f5900374f1c80428dba1dc6fafee22c6829b6
Reviewed-on: https://gerrit.instructure.com/46366
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Sean Lewis <e0580b9aacf857d9a3792f1965500f217c0fc9c9@instructure.com>
Product-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>

--- app/coffeescripts/views/assignments/EditView.coffee
+++ app/coffeescripts/views/assignments/EditView.coffee
@@ -250,7 +250,8 @@
           validationFn: -> sections
           labelFn: (section) -> section.get 'name'
           da_enabled: ENV?.DIFFERENTIATED_ASSIGNMENTS_ENABLED
-          success: =>
+          success: (dateDialog) =>
+            dateDialog.dialog('close').remove()
             ValidatedFormView::submit.call(this)
         missingDateDialog.cancel = (e) ->
           missingDateDialog.$dialog.dialog('close').remove()


fine-tune for test reliability

fine-tune for test reliability

--- spec/SpecHelper.coffee
+++ spec/SpecHelper.coffee
@@ -24,7 +24,8 @@
   srcWithRelativeTime = () ->
     now = () -> new Date().getTime()
     t0 = now()
-    relativeTime = () -> Math.floor((now() - t0) / timeUnitMillisecs)
+    relativeTime = () ->
+      Math.floor((now() - t0 + (timeUnitMillisecs * 0.3)) / timeUnitMillisecs)
     withRelativeTime = (x) -> [relativeTime(), x]
     src().map(withRelativeTime)
   @expectStreamEvents(srcWithRelativeTime, expectedEventsAndTimings)


:lipstick: Cleanup.

:lipstick: Cleanup.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -26,7 +26,7 @@
             editorView = atom.workspaceView.getActiveView()
             editor = editorView.getEditor()
             jumpyPromise = atom.packages.activatePackage 'jumpy'
-            statusBarPromise = atom.packages.activatePackage('status-bar')
+            statusBarPromise = atom.packages.activatePackage 'status-bar'
             editorView.trigger 'jumpy:toggle'

         waitsForPromise ->


Inane commentary.

Inane commentary.

--- spec/paths-spec.coffee
+++ spec/paths-spec.coffee
@@ -2,6 +2,7 @@

 # TODO: mock process.env
 # TODO: mock process.platform
+# TODO: Convert (back) to JavaScript?

 describe "JupyterPath", ->
   # Because `paths` shells out to Python, we need a chance to mock it first


Some more tests.

Some more tests.

--- spec/spec-index.coffee
+++ spec/spec-index.coffee
@@ -1,5 +1,6 @@

 assert = require 'assert'
+async = require 'async'
 { flow } = require '../src'

 describe 'flow', ->
@@ -31,7 +32,8 @@
       # Another conditional, re-enables calls again.
       -> true

-      # This will be called. All previously set locals are available, bound to this.
+      # This will be called. All previously set locals are available, bound
+      # to this.
       (done) ->
         done null, { e: @a + @b }

@@ -42,3 +44,92 @@
       assert.equal @c, undefined
       assert.equal @d, undefined
       assert.equal @e, 3
+
+  it 'should perform series of tasks', (done) ->
+    [ a, b, c ] = [ false, false, false ]
+    flow [
+
+      (done) ->
+        async.nextTick ->
+          a = 1
+          done null
+
+      (done) ->
+        async.nextTick ->
+          b = 2
+          done null
+
+      (done) ->
+        async.nextTick ->
+          c = 3
+          done null
+
+    ], (err) ->
+      assert.ifError err
+      assert.equal 1, a
+      assert.equal 2, b
+      assert.equal 3, c
+      done null
+
+  it 'should skip in control flow', (done) ->
+
+    ab = []
+
+    flow [
+
+      # Set our condition
+      (done) -> done null, { myCond: false }
+
+      # Skip
+      -> @myCond
+
+      # Won't be called.
+      (done) ->
+        ab.push 'SKIPPED'
+        done null
+
+      # Conditional, should process the rest.
+      -> not @myCond
+
+      # This should be called.
+      (done) ->
+        ab.push 'OK'
+        done null
+
+    ], (err) ->
+      assert.ifError err
+      assert.equal false, @myCond
+      assert.deepEqual ['OK'], ab
+      done null
+
+  it 'should start with locals', (done) ->
+    flow { foo: 3 }, [
+
+      (done) ->
+        async.nextTick ->
+          done null, { bar: 5 }
+
+    ], (err) ->
+      assert.ifError err
+      assert.equal @foo, 3
+      assert.equal @bar, 5
+      assert.deepEqual Object.keys(@).sort(), [ 'bar', 'foo' ]
+      done null
+
+  # it 'should compose flows', (done) ->
+  #
+  #   flows:
+  #     isoTimestamp:
+  #       [
+  #         (done) -> done null, { timestamp: 1402322190524 }
+  #         (done) -> done null, { iso: new Date(@timestamp).toISOString() }
+  #       ]
+  #
+  #   flow [
+  #
+  #     (done) ->
+  #       flow flows.isoTimestamp, (err) ->
+  #         done err, { @iso }
+  #
+  #     flows.htmlTag
+  #   ], (err) ->


css hot reloading and configure for cmdline as well

--- webpack.config.coffee
+++ webpack.config.coffee
@@ -1,4 +1,6 @@
 ExtractTextPlugin = require 'extract-text-webpack-plugin'
+isProduction = process.env.NODE_ENV is 'production'
+LOADERS = if isProduction then [] else ["react-hot", "webpack-module-hot-accept"]

 module.exports =
   cache: true
@@ -12,7 +14,7 @@
     publicPath: '/dist/'

   plugins: [
-    new ExtractTextPlugin("tutor.css")
+    #new ExtractTextPlugin("tutor.css")
   ]

   module:
@@ -21,9 +23,12 @@
     ]
     loaders: [
       { test: /\.json$/,   loader: "json-loader" }
-      { test: /\.coffee$/, loaders: ["coffee-loader" ] }
-      { test: /\.cjsx$/,   loaders: ["coffee-jsx-loader"] }
-      { test: /\.less$/,   loader: ExtractTextPlugin.extract('css!less')}
+      { test: /\.coffee$/, loaders: LOADERS.concat("coffee-loader") }
+      { test: /\.cjsx$/,   loaders: LOADERS.concat("coffee-jsx-loader") }
+      { test: /\.less$/,   loaders:
+        if isProduction then [ExtractTextPlugin.extract('css!less')]
+        else LOADERS.concat('style-loader', 'css-loader', 'less-loader')
+      }
       { test: /\.(png|jpg|svg)/, loader: 'file-loader?name=[name].[ext]'}
       { test: /\.(woff|woff2|eot|ttf)/, loader: "url-loader?limit=30000&name=[name]-[hash].[ext]" }
    ]
@@ -31,9 +36,10 @@
     extensions: ['', '.js', '.json', '.coffee', '.cjsx']

   devServer:
-    contentBase: './dist'
+    contentBase: './'
     publicPath: 'http://localhost:8000/dist/'
-    hot: true
+    hotComponents: true
+    historyApiFallback: true
     inline: true
     port: 8000
     # It suppress error shown in console, so it has to be set to false.


Implemented socket.io support in the device_bridge.

--- app/controllers/device_bridge.coffee
+++ app/controllers/device_bridge.coffee
@@ -5,7 +5,29 @@

 mqtt_client_list = []

+io = null # this will be defined in the start method
+
 data = {}
+
+publish_payload = (topic, payload) ->
+  # emit the payload over mqtt
+  # Iterate over our list of mqtt clients
+  for client in mqtt_client_list
+    # Iterate over the client's subscriptions
+    for subscription in client.subscriptions
+      # If the client has a subscription matching
+      # the packet...
+
+      c.publish(topic, payload) if subscription.test(topic)
+
+  # store the payload for REST consumption
+  try
+    data[topic] = { json: true, payload: JSON.parse(payload) }
+  catch e
+    data[topic] = { json: false, payload: payload }
+
+  # emit the payload over websocket
+  io.sockets.emit "/topics/#{topic}", data[topic].payload

 mqtt.on 'new_client', (client) ->
     console.log("New client emitted")
@@ -20,24 +42,10 @@
         # '#' is 'match anything to the end of the string' */
         # + is 'match anything but a / until you hit a /' */
         reg = new RegExp(subscription.topic.replace('+', '[^\/]+').replace('#', '.+$'));
-        client.subscriptions.push(reg);
+        client.subscriptions.push(reg)

     client.on 'publish', (packet) ->
-
-      try
-        data[packet.topic] = { json: true, payload: JSON.parse(packet.payload) }
-      catch e
-        data[packet.topic] = { json: false, payload: packet.payload }
-
-	    # Iterate over our list of clients
-	    for client in mqtt_client_list
-	      # Iterate over the client's subscriptions
-        for subscription in client.subscriptions
-          # If the client has a subscription matching
-          # the packet...
-
-          c.publish(packet.topic, packet.payload) if subscription.test(packet.topic)
-
+      publish_payload packet.topic, packet.payload

     client.on 'pingreq', (packet) ->
 	    client.pingresp()
@@ -53,15 +61,22 @@
 module.exports = (app) ->
   app.get '/topics/:topic', (req, res) ->
     topic = req.params.topic
-    if data[topic]?
-      if data[topic].json
-        res.contentType('json')
-        res.send data[topic].payload
-      else
-        # it's not a json, don't emit anything
-        res.send data[topic].payload
+    if req.accepts 'json'
+      if data[topic]?
+        if data[topic].json
+          res.contentType('json')
+          res.send data[topic].payload
+        else
+          # it's not a json, don't emit a contentType
+          res.send data[topic].payload
+      else
+        res.send "Topic not found!", 404
     else
-      res.send "Topic not found!", 404
+      value = data[topic] || { json: false, payload: "" }
+      res.render 'topic.hbs', topic: req.params.topic, value: value
+
+  # setup websockets
+  io = require('socket.io').listen(app)

 module.exports.start = (port) ->
   mqtt.server.listen(port)


Further coffeefied the code

Further coffeefied the code

--- src/coffee/obscura.coffee
+++ src/coffee/obscura.coffee
@@ -21,14 +21,14 @@
 			scale =
 				w: scale[0]
 				h:scale[1]
-		else if typeof scale != 'object'
+		else if typeof scale isnt 'object'
 			scale =
 				w:scale
 				h:scale

 		#convert percentages to actual values
-		scale.w = if typeof scale.w == 'string' and scale.w.match(/%/) then @image.width * (parseFloat(scale.w)/100) else parseFloat(scale.w)
-		scale.h = if typeof scale.h == 'string' and scale.h.match(/%/) then @image.height * (parseFloat(scale.h)/100) else parseFloat(scale.h)
+		scale.w = if typeof scale.w is 'string' and scale.w.match(/%/) then @image.width * (parseFloat(scale.w)/100) else parseFloat(scale.w)
+		scale.h = if typeof scale.h is 'string' and scale.h.match(/%/) then @image.height * (parseFloat(scale.h)/100) else parseFloat(scale.h)

 		@context.clearRect(0,0,@canvas.width,@canvas.height)
 		@load(0,0,scale.w,scale.h)


Allow custom port

Allow custom port

--- cli/Gruntfile.coffee
+++ cli/Gruntfile.coffee
@@ -7,6 +7,8 @@
 # Always `dev` in dev mode and `prod` in build mode
 BUILD_MODE = process.env.BUILD_MODE or 'dev'
 BUILD_FORM = 'default'
+# Server port
+PORT = process.env.PORT || 8888

 module.exports = (grunt) ->
   require('load-grunt-tasks') grunt,
@@ -209,7 +211,7 @@
     connect:
       server:
         options:
-          port: 8888
+          port: PORT
           hostname: '*'
           base: 'public'


monkeying with scroll position for consecutive modals

--- @client/modal.coffee
+++ @client/modal.coffee
@@ -69,55 +69,66 @@
   UNSAFE_componentWillMount: ->
     @scroll_Y = window.scrollY

+    # The timeout is b/c if we have a string of modal windows open, the scrollY position restored in the willUnmount
+    # handler won't run before this componentWillMount handler, thus losing the original scrollY position.
+    setTimeout =>
+      if !@scroll_Y
+        @scroll_Y = window.scrollY
+
   componentDidMount: ->
     @componentDidUpdate()

   componentDidUpdate: ->
+
     modal = @refs?.dialog or document.querySelector '[role=dialog]'
     return if !modal || @mounted
     @mounted = true

-    @focused_element_before_opening = document.activeElement
+    # The setTimout here is explained in the WillMount handler
+    setTimeout =>

-    ######################################
-    # For capturing focus inside the modal
-    # add all the elements inside modal which you want to make focusable
-    focusable_elements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+      @focused_element_before_opening = document.activeElement

-    # select the modal by it's id
-    @first_focusable_element = modal.querySelectorAll(focusable_elements)[0]
-    # get first element to be focused inside modal
-    focusable_content = modal.querySelectorAll(focusable_elements)
-    @last_focusable_element = focusable_content[focusable_content.length - 1]
+      ######################################
+      # For capturing focus inside the modal
+      # add all the elements inside modal which you want to make focusable
+      focusable_elements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'

-    # get last element to be focused inside modal
-    document.addEventListener 'keydown', @accessibility_on_keydown
+      # select the modal by it's id
+      @first_focusable_element = modal.querySelectorAll(focusable_elements)[0]
+      # get first element to be focused inside modal
+      focusable_content = modal.querySelectorAll(focusable_elements)
+      @last_focusable_element = focusable_content[focusable_content.length - 1]

-    try
-      modal.querySelector('input:not([disabled])')?.focus()
-    catch e
-      console.error e
+      # get last element to be focused inside modal
+      document.addEventListener 'keydown', @accessibility_on_keydown

-    #####################
-    # For preventing scroll outside of the modal, and allowing scroll within,
-    # all while making it seem like the whole page is scrollable.
-    _.extend modal.style,
-      position: 'fixed'
-      top: "0px"
-      left: "0px"
-      width: '100vw'
-      height: '100vh'
-      overflow: 'auto'
-      zIndex: 99999
-      paddingBottom: if browser.is_mobile then "150px"
+      try
+        modal.querySelector('input:not([disabled])')?.focus()
+      catch e
+        console.error e

-    scroll_bar_width = window.innerWidth - document.body.offsetWidth
-    _.extend document.body.style,
-      marginRight: scroll_bar_width
-      overflow: 'hidden'
-      position: 'fixed'
-      top: "-#{@scroll_Y}px"
-      width: '100vw'
+      #####################
+      # For preventing scroll outside of the modal, and allowing scroll within,
+      # all while making it seem like the whole page is scrollable.
+      _.extend modal.style,
+        position: 'fixed'
+        top: "0px"
+        left: "0px"
+        width: '100vw'
+        height: '100vh'
+        overflow: 'auto'
+        zIndex: 99999
+        paddingBottom: if browser.is_mobile then "150px"
+
+      scroll_bar_width = window.innerWidth - document.body.offsetWidth
+
+      _.extend document.body.style,
+        marginRight: scroll_bar_width
+        overflow: 'hidden'
+        position: 'fixed'
+        top: "-#{@scroll_Y}px"
+        width: '100vw'

@@ -127,6 +138,7 @@
     # return the focus to the element that had focus when the modal was launched
     if @focused_element_before_opening && document.body.contains(@focused_element_before_opening)
       @focused_element_before_opening.focus()
+
     document.removeEventListener 'keydown', @accessibility_on_keydown

     _.extend document.body.style,


Add test for mapping embedded keys in query helpers.

--- test/index.coffee
+++ test/index.coffee
@@ -362,6 +362,15 @@
         doc.should.eql _id: "simple_doc", foo: "bar"
         done()

+    it "should map embedded keys as well", ->
+      QueryHelper = Db.document 'queryHelperEmbed',
+        attr: 'a'
+        embed: Embed 'em',
+          attr: 'a'
+
+      query = QueryHelper._ attr: 1, embed: attr: 2
+      query.should.eql a: 1, em: a: 2
+
   describe "Convenience methods", ->
     it "should allow saving of a document", (done) ->
       doc = MyDoc()
@@ -419,7 +428,7 @@
       doc = MyDoc()
       doc.attr = 'bad-remove'
       try
-        doc.remove $inc: foo: 1, (err, result) ->
+        doc.remove (err, result) ->
       catch err
         err.message.indexOf("Cannot remove").should.equal 0
         done()


[test] RedisModel##deleteByState, ##deleteById, ##deleteAll

--- test/index.coffee
+++ test/index.coffee
@@ -414,3 +414,73 @@
       expect(count).to.equal 1

       qCleaner(queue).asCallback done
+
+  describe "##deleteByState", ->
+    it "should callback with error if state is not valid", (done) ->
+      await instance.deleteByState null, "badstate", defer err, _
+      expect(err.toString()).to.contain "Invalid state"
+      done()
+
+    it "should delete in all queues all jobs of a specific state", (done) ->
+      ideally = errify done
+      data    = {name: "testjob"}
+
+      await fakeJob queuename, data, "active", ideally defer {queue, job}
+      await fakeJob "test2", data, "active", ideally defer queueAndJob2
+
+      await instance.deleteByState null, "active", ideally defer _
+      await instance.allKeys null, ideally defer keys
+      expect(keys).to.be.empty
+      done()
+
+    it "should delete in a specific queue all jobs of a specific state", (done) ->
+      ideally = errify done
+      data    = {name: "testjob"}
+
+      await fakeJob queuename, data, "active", ideally defer {queue, job}
+      await instance.deleteByState queuename, "active", ideally defer _
+      await instance.allKeys null, ideally defer keys
+      expect(keys).to.be.empty
+      done()
+
+  describe "##deleteById", ->
+    it "should callback with error if missing queue parameter", (done) ->
+      await instance.deleteById null, 1, defer err, _
+      expect(err.toString()).to.contain "queue required"
+      done()
+
+    it "should callback with error if missing id parameter", (done) ->
+      await instance.deleteById "queuename", null, defer err, _
+      expect(err.toString()).to.contain "id required"
+      done()
+
+    it "should delete in a specific queue a job by id", (done) ->
+      ideally = errify done
+      data    = {name: "testjob"}
+
+      await fakeJob queuename, data, "active", ideally defer {queue, job}
+      await instance.deleteById queuename, job.jobId, ideally defer _
+      await instance.allKeys null, ideally defer keys
+      expect(keys).to.be.empty
+      done()
+
+  describe "##deleteAll", ->
+    it "should delete in all queues all jobs", (done) ->
+      ideally = errify done
+      data    = {name: "testjob"}
+
+      await fakeJob queuename, data, "active", ideally defer {queue, job}
+      await instance.deleteAll null, ideally defer _
+      await instance.allKeys null, ideally defer keys
+      expect(keys).to.be.empty
+      done()
+
+    it "should delete in a specific queue all jobs", (done) ->
+      ideally = errify done
+      data    = {name: "testjob"}
+
+      await fakeJob queuename, data, "active", ideally defer {queue, job}
+      await instance.deleteAll queuename, ideally defer _
+      await instance.allKeys null, ideally defer keys
+      expect(keys).to.be.empty
+      done()


Add assert messages

Add assert messages

--- test/setup.coffee
+++ test/setup.coffee
@@ -4,7 +4,7 @@
 make = require '../lib/makeAdapter'

-setup = (services={}, routes={}, config={}) ->
+setup = (services={}, routes=[], config={}) ->

   host = config.host || 'localhost'
   port = config.port || 3000
@@ -18,13 +18,13 @@
   should.exist app

   adapter = make services, routes
-  should.exist adapter
+  should.exist adapter, 'adapter should exist'

   app.use connect.bodyParser()
   app.use adapter

   server = app.listen port
-  should.exist server
+  should.exist server, 'server should exist'

   return {app, server, url}


Code refactoring

Code refactoring

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -658,10 +658,6 @@

   onCollaborationPreparing: ->

-    # Don't allow when the mounted machine isn't running.
-    unless @isMachineRunning()
-      return @stateMachine.state = @stateMachine.initialState
-
     @prepareChatSession
       success : => @stateMachine.transition 'Prepared'
       error   : => @stateMachine.transition 'ErrorPreparing'
@@ -679,10 +675,6 @@

   onCollaborationCreating: ->
-
-    # Don't allow when the mounted machine isn't running.
-    unless @isMachineRunning()
-      return @stateMachine.state = @stateMachine.initialState

     @createCollaborationSession
       success : (doc) =>


collaboration: controller add broadcastMessage

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -279,6 +279,12 @@
   # realtime related stuff

+  broadcastMessage: (options = {}) ->
+
+    message = _.assign {}, options, { origin: nick() }
+    @broadcastMessages.push message
+
+
   loadCollaborationFile: (fileId) ->

     return unless fileId


Collaboration: Log a warning for non-critical errors

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -443,7 +443,7 @@

   removeParticipantFromParticipantList: (nickname) ->
-    return throwError "participants is not set"  unless @participants
+    return warn 'participants is not set'  unless @participants

     # find the index for participant
     for participant, index in @participants.asArray()
@@ -454,7 +454,7 @@
         break

   removeParticipantFromMaps: (nickname) ->
-    return throwError "rtm is not set"   unless @rtm
+    return warn 'rtm is not set'  unless @rtm

     myWatchMapName     = "#{nickname}WatchMap"
     mySnapshotName     = "#{nickname}Snapshot"
@@ -465,7 +465,7 @@

   removeParticipantFromPermissions: (nickname)->
-    return throwError "permissions is not set"   unless @permissions
+    return warn 'permissions is not set'  unless @permissions
     # Removes the entry for the given key (if such an entry exists).
     @permissions.delete(nickname)

@@ -770,7 +770,7 @@

   cleanupCollaboration: (options = {}) ->
-    return throwError 'RealTimeManager is not set'  unless @rtm
+    return warn 'RealTimeManager is not set'  unless @rtm

     kd.utils.killRepeat @pingInterval
     @rtm?.dispose()


change size window

change size window

--- coffee/eventPage.coffee
+++ coffee/eventPage.coffee
@@ -1,3 +1,3 @@
 chrome.app.runtime.onLaunched.addListener () ->
   chrome.app.window.create '/views/index.html',
-    'bounds': { 'width': 280, 'height': 300 }
+    'bounds': { 'width': 250, 'height': 380 }


Add a `printf '%s\n'` shorthand snippet

Add a `printf '%s\n'` shorthand snippet

--- src/configs/snippets.cson
+++ src/configs/snippets.cson
@@ -428,6 +428,9 @@
 				value) name=$OPTARG ;;
 			esac
 		"""
+	printf:
+		prefix: "p"
+		body: "printf '%s\\\\n' "

 # HTML


Don't stick the nav unless it's a login box

--- resources/assets/coffee/_classes/nav.coffee
+++ resources/assets/coffee/_classes/nav.coffee
@@ -95,9 +95,13 @@
     return if !@available()
     return if !@data().visible

-    beaconPosition = @floatBeacon[0].getBoundingClientRect()
-
-    float = beaconPosition.bottom < 0
+    if @currentMode() == 'user' && !currentUser.id?
+      beaconPosition = @floatBeacon[0].getBoundingClientRect()
+
+      float = beaconPosition.bottom < 0
+    else
+      float = false
+
     @floatPopup float


Removed a placeholder test left in error.

--- src/coffee/expression/parse/lambda.spec.coffee
+++ src/coffee/expression/parse/lambda.spec.coffee
@@ -665,6 +665,4 @@
             throws:
                 reason: "duplicateParameters"
                 starts: 214
-                ends: 244
-
-        xit "nonexistent identifiers", ->+                ends: 244


example

example

--- examples/example.coffee
+++ examples/example.coffee
@@ -27,8 +27,6 @@
 			text: 'Todo 2'
 			deadline: new Date 2015, 12, 12
 		], 1000
-		@addInterceptor
-			fetch: (response) ->
 		@

 	module.directive 'todo', ->


Only allow sentence toggling from sidebar for simplicity

Fixes #27

--- annotran/static/scripts/annotator/guest.coffee
+++ annotran/static/scripts/annotator/guest.coffee
@@ -12,7 +12,6 @@
       adder: '''
         <div class="annotator-adder">
           <button class="h-icon-insert-comment" data-action="comment" title="New Translation"></button>
-          <button class="h-icon-move" data-action="lockit" title="Sentence-by-Sentence Lock Mode"></button>
         </div>
       '''

@@ -50,8 +49,6 @@
       event.stopPropagation?()
       @adder.hide()
       switch $(event.target).data('action')
-        when 'lockit'
-          this.plugins.SentenceSelection.toggleOperation()
         when 'comment'
           this.createAnnotation()
       Annotator.Util.getGlobal().getSelection().removeAllRanges()


adding micello map to add beacon interface

--- app/assets/javascripts/models/beacon.js.coffee
+++ app/assets/javascripts/models/beacon.js.coffee
@@ -23,7 +23,7 @@
 #      zoomControlOpt:
 #        position: 'RIGHT_BOTTOM'
 #    )
-     @map = new MicelloMap('rrVDMQe2qjBRuJP5dXv59kAeK77SD7','beacons-map')
+     @map = new MicelloMap('rrVDMQe2qjBRuJP5dXv59kAeK77SD7',@mapDom)

   isActive: ->
     @map != null


Use modern syntax

Use modern syntax

Signed-off-by: Dmitriy Zaporozhets <be23d75b156792e5acab51b196a2deb155d35d6a@gmail.com>

--- app/assets/javascripts/projects_list.js.coffee
+++ app/assets/javascripts/projects_list.js.coffee
@@ -32,6 +32,6 @@
       dataType: "json"

   initPagination: ->
-    $('.projects-list-holder .pagination').bind('ajax:success', (e, data) ->
+    $('.projects-list-holder .pagination').on('ajax:success', (e, data) ->
       $('.projects-list-holder').replaceWith(data.html)
     )