Added new options noFLAC2MP3, sabreDownloadsEnabled and downloadExpireDays

--- cs_app/settings.coffee
+++ cs_app/settings.coffee
@@ -27,7 +27,10 @@
     { stage: 2, name: 'port', desc: 'the port sabRE listens on.', default: 3000, type: 'int' }
     { stage: 2, name: 'useCurl', desc: 'check urls with curl before enqueueing to SABnzbd?', default: false, type: 'bool' }
     { stage: 2, name: 'hideOtherUsersData', desc: 'when enabled, users can only see stuff they enqueued. files from other users are hidden.', default: false, type: 'bool' }
+    { stage: 2, name: 'downloadExpireDays', desc: 'when set to a value >0 downloads will be hidden when configured number of days have elapsed since the completion of the download.', default: 0, type: 'int' }
     { stage: 2, name: 'noPostProcess', desc: 'when this is enabled no postprocessing (creation of tar archive) will take place. instead sabRE tries to set the category of the download to the currently logged in user\'s name so you can set up shares on the server to let users download the files directly. when enabling this, don\'t forget to configure categories in SABnzbd, one for each user. otherwise files will be downloaded to the regular download dir without placing them in separate directories for each user. enabled postprocessing requires sabRE to run on the same machine as SABnzbd.', default: false, type: 'bool' }
+    { stage: 2, name: 'noFLAC2MP3', desc: 'when this is enabled the option to convert downloaded .flac files to .mp3 files will not be provided in the user interface. this feature requires the applications "flac", "metaflac" and "lame".', default: false, type: 'bool' }
+    { stage: 2, name: 'sabreDownloadsEnabled', desc: 'when enabled sabRE allows users to download files from the webinterface. this only works if you did not disable postprocessing before.', default: true, type: 'bool' }
     { stage: 2, name: 'logFile', desc: 'file where sabRE logs into.', default: '../data/log.json', type: 'file', mustExist: false, wizardEnabled: false }
     { stage: 2, name: 'userFile', desc: 'sabRE\'s user/password "database".', default: '../data/users.json', type: 'file', wizardEnabled: false }
     { stage: 2, name: 'nzbUploadDir', desc: 'directory where uploaded .nzb files will be stored. files will be deleted after they have been enqueued.', default: '/tmp/', type: 'dir' }
@@ -82,7 +85,10 @@
           @[s.name] = val
           @loadedKeys.push s.name
         else
-          throw _.sprintf 'setting "%s" with value "%s" is invalid', s.name, val
+          if val is undefined
+            throw _.sprintf 'setting "%s" is undefined', s.name
+          else
+            throw _.sprintf 'setting "%s" with value "%s" is invalid', s.name, val
       if not @validateSettings()
         throw 'could not validate settings'
       @loaded = true
@@ -141,8 +147,6 @@
   @validateSettings = (stage) ->
     for s in _.filter(@configSchema, (s) -> s.stage is stage)
       if not @validateField @[s.name], s
-        console.log @[s.name]
-        console.log s.name
         process.exit 1
         return false
     return true
@@ -440,7 +444,7 @@
              an error occured loading the sabRE configuration, the error was:
              %s.
              please fix settings.json and run sabRE again. if you want to recreate the file,
-             just delete it and run sabRE again so the setup wizard can be used.
+             just delete it and run sabRE again so the setup wizard will start up.

              '''
       console.log _.sprintf msg, @errorMsg


my glass

my glass

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -11,6 +11,9 @@
 #   hubot what would Dr Matt say
 #   hubot awake your brothers
 #   hubot define bikeshedding
+#   hubot azure DevOps status
+#   hubot my glass is very empty
+#   hubot my glass is empty
 #
 # Author:
 #   Mateusz Kopij <sagasu>


starting to add some docco & markdown flavoured documentation

--- scripts/github.coffee
+++ scripts/github.coffee
@@ -1,3 +1,13 @@
+## Reportocat
+# *Reportocat* is a Chrome Extension to allow you to quickly log issues to GitHub projects in sessions.
+#
+# The application is written in CoffeeScript using the **Americano** MVP Framework.
+
+
+## GitHubPresenter ##
+# This is the Main Presenter for the Reportocat application. This controls
+# the workflow between views and is responsible for maintaining overall
+# state.
 class GitHubPresenter extends Presenter

   @PRESS =
@@ -45,7 +55,10 @@
   makeForward: (toShow) ->
     toShow.ensureBound()
     @display.replaceWidget toShow.getDisplay().asWidget()
-
+
+## GitHubDisplay ##
+# This is the container for all of the views within the application.
+# It's pretty much just a wrapper for the views of the sub-presenters to live.
 class GitHubDisplay extends Display

   constructor: ->
@@ -70,6 +83,9 @@
     @appPanelContent.empty()
     @appPanelContent.append widget

+## LoginPresenter ##
+# The Login Presenter handles the login process through oauth, as well as
+# displaying the login view to the user.
 class LoginPresenter extends Presenter

   @PRESS =
@@ -87,7 +103,8 @@
       @service.logout()
       @service.startAuth()

-
+## LoginDisplay ##
+# Displays the OAuth button, and instructions to the user.
 class LoginDisplay extends Display

   constructor: ->
@@ -103,7 +120,9 @@
   getOAuthButton: -> @oauthButton[0]

   asWidget: -> @loginPanel[0]
-
+
+## StartSessionPresenter ##
+# This bad boy gets the session workflow started anew
 class StartSessionPresenter extends Presenter

   @PRESS =
@@ -115,7 +134,10 @@
   onBind: ->
     @registerHandler "click", @display.getStartSessionButton(), (event) =>
       @eventBus.fire "newSession"
-
+
+## StartSessionDisplay ##
+# Displays instructions, as well as the action button for the user to start the
+# session.
 class StartSessionDisplay extends Display

   constructor: ->
@@ -135,6 +157,9 @@
   getStartSessionButton: -> @startSessionButton[0]
   asWidget: -> @sessionPanel[0]

+## RepoListPresenter ##
+# In charge of displaying a list of Repositories for the user to choose from
+# during the Start Session workflow.
 class RepoListPresenter extends Presenter

   @PRESS =
@@ -166,7 +191,9 @@

     @display.addRepo repoListItemPresenter.getDisplay().asWidget()
     @repos.push(repo)
-
+
+## RepoListDisplay ##
+# Displays a sexy list of repos for the presenter.
 class RepoListDisplay extends Display

   constructor: ->
@@ -187,7 +214,9 @@
     @listItems = []
     @list.empty()

-
+## RepoListItemPresenter ##
+# In charge of an individual Repository that lives in the RepoListPresenter,
+# it's display and events.
 class RepoListItemPresenter extends Presenter
   @PRESS =
     display: 'RepoListItemDisplayType'
@@ -205,7 +234,9 @@
         @display.showForked()

   setRepo: (@repo) ->
-
+
+## RepoListItemDisplay ##
+# Visual representation of our little RepoListItemPresenter
 class RepoListItemDisplay extends Display
   constructor: ->
     @panel = $ '

'
@@ -228,6 +259,8 @@

   asWidget: -> @panel[0]

+## GitHubRepo ##
+# A Model class that represents a GitHub Repository.
 class GitHubRepo
   constructor: (@json) ->
     @apiUrl = @json.url
@@ -246,7 +279,8 @@
     @pushedAt = @json.pushed_at
     @createdAt = @json.created_at

-
+## GitHubUser ##
+# A Model class that represents a GitHub User.
 class GitHubUser
   constructor: (@json) ->
     @id = @json.id
@@ -255,6 +289,10 @@
     @githubUrl = @json.url

+## GitHubService ##
+# The GitHub API interaction object. Makes calls into the background page's
+# oauth object on behalf of the application. Add to me if you ever need to talk
+# to GitHub.
 class GitHubService

   @PRESS =
@@ -264,25 +302,44 @@
     eventBus: 'getEventBus'
     logger: 'getLogger'

+  ##### constructor ####
+  # Builds a new GitHub service object with the api root provided
   constructor: (@oauth) ->
     @api_root = "https://api.github.com"

-
+  ##### logout ####
+  # Clears all OAuth Tokens stored for this application from localstorage.
   logout: -> @oauth.clearTokens()

+  ##### startAuth ####
+  # Start the OAuth flow in the gadget, fires the loginSuccess event on complete.
   startAuth: ->
     @oauth.authorize =>
       @eventBus.fire "loginSuccess"
-
+
+  ##### getUserRepoList ####
+  # Gets all of the repos that the user has access to (private and public)
+  #
+  #  - callback {function} A callback function that expects a list of *GitHubRepo*
   getUserRepoList: (callback) ->
     @oauth.sendSignedRequest @api_root + '/user/repos', (responseText, xhr) =>
       jsonResult = $.parseJSON responseText
       repos = (new GitHubRepo repo for repo in jsonResult)
       callback repos
-
+
+  ##### loggedIn ####
+  # A Check with the background page's oauth object to see if there are any
+  # tokens living for our app.
   loggedIn: -> @oauth.hasToken()
+
+  ##### inSession ####
+  # A check to see if a Session has been started yet by the user.
   inSession: ->

+## Application ##
+# The Application class brings together all of the initial services for
+# the app, adds them to the EspressoMachine for future presenters to bind to.
+# Then handles the initial call into the main presenter.
 class Application

   esp = new EspressoMachine
@@ -291,6 +348,7 @@
   hasRun = false

   run: ->
+    # only run the application once
     if hasRun
       return
     hasRun = true
@@ -300,7 +358,7 @@
     esp.register('getLogger', -> logger)
     esp.register('getRootPanel', -> $('#application'))

-    # create singleton the display classes and register with the Espresso Machine
+    # create the singleton display classes and register with the Espresso Machine
     ghDisplay = new GitHubDisplay
     loginDisplay = new LoginDisplay
     repoListDisplay = new RepoListDisplay
@@ -314,17 +372,18 @@
     # create the factory display classes and register them with the Espresso Machine.
     esp.register 'RepoListItemDisplayType', -> new RepoListItemDisplay

-    # reach into the background.html page for the oauth info.
+    # reach into the background.html page for the oauth info &
+    # register the chrome extension's oauth with the Espresso Machine
     backgroundPage = chrome.extension.getBackgroundPage()
-    # register the chrome extension's oauth with the Espresso Machine
     esp.register('getOAuth', -> backgroundPage.oauth)

-    # press the GitHub service which relies on the oauth.
+    # press the GitHub service which relies on the oauth, then register it
+    # with the Espresso Machine.
     gh = esp.create(GitHubService)
-
-    # Register the github service.
     esp.register 'getService', -> gh

+    # Create and register the initial presenters that the GitHub Presenter
+    # needs to start up the application.
     loginPresenter = esp.create LoginPresenter
     esp.register 'getLoginPresenter', -> loginPresenter

@@ -334,13 +393,15 @@
     startSessionPresnter = esp.create StartSessionPresenter
     esp.register 'getStartSessionPresenter', -> startSessionPresnter

+    # Finally, start your engines and bind the main presenter.
     ghPresenter = esp.create GitHubPresenter
     ghPresenter.bind()

+    # Add the main presenter's widget to the RootPanel
     esp.getRootPanel().empty()
     esp.getRootPanel().append ghPresenter.getDisplay().asWidget()
-
-
+
+
 @ready =>
   @app = new Application()
   @app.run()


Delete google.coffee

Delete google.coffee

--- scripts/google.coffee
+++ scripts/google.coffee
@@ -1,28 +0,0 @@
-Code  Issues 35  Pull requests 14  Pulse
-src/scripts/google.coffee
-
-# Description:
-#   Returns the URL of the first google hit for a query
-#t
-# Dependencies:
-#   None
-#
-# Configuration:
-#   None
-#
-# Commands:
-#   hubot google me <query> - Googles <query> & returns 1st result's URL
-#
-# Author:
-#   searls
-
-module.exports = (robot) ->
-  robot.respond /(google)( me)? (.*)/i, (msg) ->
-    googleMe msg, msg.match[3], (url) ->
-      msg.send url
-
-googleMe = (msg, query, cb) ->
-  msg.http('http://www.google.com/search')
-    .query(q: query)
-    .get() (err, res, body) ->
-      cb body.match(/class="r"><a href="\/url\?q=([^"]*)(&sa.*)">/)?[1] || "Sorry, Google had zero results for '#{query}'"


Make listening on UDP port optional

Make listening on UDP port optional

--- pushd.coffee
+++ pushd.coffee
@@ -86,7 +86,7 @@

 port = settings?.server?.tcp_port ? 80
 app.listen port
-logger.log "Listening on port #{port}"
+logger.log "Listening on tcp port #{port}"

 # UDP Event API
@@ -119,4 +119,7 @@
                     return
             logger.log("UDP/#{method} #{req.pathname} #{status}") if settings.server?.access_log

-udpApi.bind settings?.server?.udp_port ? 80
+port = settings?.server?.udp_port
+if port?
+    udpApi.bind port
+    logger.log "Listening on udp port #{port}"


make tests run again

make tests run again

--- test/error.coffee
+++ test/error.coffee
@@ -14,7 +14,7 @@
           throw x

       hinoki.get(c, 'a').catch hinoki.errors.ErrorInResolvers, (error) ->
-        test.equal error.message, "error in resolvers for 'a' (a). original error message: fail"
+        test.equal error.message, "error in resolvers for 'a' (a). original error: Error: fail"
         test.deepEqual error.path, ['a']
         test.equal error.error, x
         test.ok not c.promisesAwaitingResolution?
@@ -28,7 +28,7 @@
           x

       hinoki.get(c, 'a').catch hinoki.errors.ErrorInResolvers, (error) ->
-        test.equal error.message, "error in resolvers for 'a' (a). original error message: fail"
+        test.equal error.message, "error in resolvers for 'a' (a). original error: Error: fail"
         test.deepEqual error.path, ['a']
         test.equal error.error, x
         test.ok not c.promisesAwaitingResolution?
@@ -177,7 +177,7 @@
         a: -> throw exception

     hinoki.get(c, 'a').catch hinoki.errors.ErrorInFactory, (error) ->
-      test.equal error.message, "error in factory for 'a'. original error message: fail"
+      test.equal error.message, "error in factory for 'a'. original error: Error: fail"
       test.deepEqual error.path, ['a']
       test.equal error.container, c
       test.equal error.error, exception
@@ -208,7 +208,7 @@
         a: -> Promise.reject rejection

     hinoki.get(c, 'a').catch hinoki.errors.PromiseRejected, (error) ->
-      test.equal error.message, "promise returned from factory for 'a' was rejected. original error message: fail"
+      test.equal error.message, "promise returned from factory for 'a' was rejected. original error: Error: fail"
       test.deepEqual error.path, ['a']
       test.equal error.container, c
       test.equal error.error, rejection


add tests stubs for deployers with no config

--- test/index.coffee
+++ test/index.coffee
@@ -28,7 +28,7 @@
       project.config = {}
       project.is_configured().should.be.true

-    it 'should be condigured if a shipfile is present at root', ->
+    it 'should be configured if a shipfile is present at root', ->
       dir = path.join(_path, 'api/one_deployer')
       project = new Ship(root: dir, deployer: 's3')
       project.is_configured().should.be.true
@@ -46,6 +46,8 @@
         .should.throw('you must specify these keys: secret_key access_key')
       should.not.exist(project.config)

+    it 'should not error if the deployer has no config requirements'
+
   describe 'config_prompt', ->

     it 'should prompt the user to enter config info via command line', ->
@@ -57,6 +59,8 @@
         .tap (res) -> res.should.deep.equal(secret_key: '1', access_key: '2')
         .tap -> project.config.should.deep.equal(secret_key: '1', access_key: '2')
         .should.be.fulfilled
+
+    it 'should not activate the prompt if deployer has no config requirements'

   describe 'write_config', ->

@@ -98,3 +102,5 @@
       project = new Ship(root: dir, deployer: 'nowhere')
       project.deploy()
         .should.be.rejectedWith('you must specify these keys: nothing')
+
+    it 'should not error if not configured and deployer has no config requirements'


roster tab animation disable to workaround bug in react-bootstrap

--- src/components/course-settings/roster.cjsx
+++ src/components/course-settings/roster.cjsx
@@ -91,7 +91,7 @@
     noPeriodMessage =
       <NoPeriods noPanel={true} />

-    <BS.Tabs activeKey={@state.key} onSelect={@handleSelect}>
+    <BS.Tabs activeKey={@state.key} onSelect={@handleSelect} animation={false}>
       <div className='period-edit-ui'>
         <AddPeriodLink courseId={@props.courseId} periods={course.periods} />
         {renameButton if periods}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -24,6 +24,7 @@
   equals destroySpy, false

 test "should remove objects outside of activeBounds", 2, ->
+  log App.width
   obj = GameObject
     x: 50
     y: 50


Better Value.hacks fixturing

Better Value.hacks fixturing

--- test/value.coffee
+++ test/value.coffee
@@ -1,9 +1,10 @@
 Value = require('../lib/autoprefixer/value')
+utils = require('../lib/autoprefixer/utils')

 describe 'Value', ->
   beforeEach ->
     @calc = new Value('calc', ['-o-'])
-    @hacks = Value.hacks
+    @hacks = utils.clone Value.hacks

   afterEach ->
     Value.hachs = @hacks


Added a test

Added a test

--- test/streams/utils.coffee
+++ test/streams/utils.coffee
@@ -1,4 +1,41 @@
 #
 # Utility Stream Tests
 #
+{
+  Readable
+  Writeable
+}             = require 'stream'
+should        = require 'should'

+
+
+
+describe 'ReadPiper()', ->
+  ReadPiper = null
+  before -> {ReadPiper} = require '../../lib/streams/utils'
+
+  it 'should preserve data order', (done) ->
+    source  = new Readable()
+    source1 = new Readable()
+    source2 = new Readable()
+
+    piper1  = new ReadPiper source1
+    piper2  = new ReadPiper source2
+
+    dest    = new Writeable()
+
+    # now that we have all of our streams created, hook them up
+    source.pipe(piper1).pipe(piper2).pipe(dest)
+
+    # Write to our source streams *out of order*
+    source1.write 'b'
+    source2.write 'c'
+    source.write  'a'
+
+    # And finally watch our writeable for data
+    d = ''
+    dest.on 'data', (chunk) -> d += chunk
+    dest.on 'end',  ->
+      d.should.equal 'abc'
+      done()
+


fix job runner

fix job runner

--- server/api/job.coffee
+++ server/api/job.coffee
@@ -203,64 +203,61 @@
   return false if typeof proc isnt 'object'
   proc.timeout = Date.now() + proc.limit if proc.limit?
   proc.args = JSON.stringify proc.args if proc.args? and typeof proc.args is 'object' # in case a process is passed directly with non-string args
-  if not proc._id? or job_process.get(proc._id)? # make sure the process has not already been picked up by another cluster machine (this may still result in occasional duplicate jobs, but this is not a terrible additional load)
-    proc._id = job_processing.insert(proc,undefined,undefined,proc.limit?) # in case was passed a process directly - need to catch the ID
-    job_process.remove proc._id
-    API.log {msg:'Processing ' + proc._id,process:proc,level:'debug',function:'API.job.process'}
-    fn = if proc.function.indexOf('API.') is 0 then API else global
-    fn = fn[p] for p in proc.function.replace('API.','').split('.')
+  proc._id = job_processing.insert(proc,undefined,undefined,proc.limit?) # in case was passed a process directly - need to catch the ID
+  job_process.remove proc._id
+  API.log {msg:'Processing ' + proc._id,process:proc,level:'debug',function:'API.job.process'}
+  fn = if proc.function.indexOf('API.') is 0 then API else global
+  fn = fn[p] for p in proc.function.replace('API.','').split('.')
+  try
+    proc.result = {}
+    args = proc # default just send the whole process as the arg
+    if proc.args?
+      if typeof proc.args is 'string'
+        try
+          args = JSON.parse proc.args
+        catch
+          args = proc.args
+      else
+        args = proc.args
+    if typeof args is 'object' and typeof proc.args is 'string' and proc.args.indexOf('[') is 0
+      # save results keyed by function, so assuming functions produce results of same shape, they fit in index
+      proc.result[proc.function] = fn.apply this, args
+    else
+      proc.result[proc.function] = fn args
+  catch err
+    proc.result = {error: err.toString()}
+    API.log msg: 'Job process error', error: err, string: err.toString(), process: proc, level: 'debug'
+    if API.settings.log.level in ['debug','all']
+      console.log JSON.stringify err
+      console.log err.toString()
+      console.log proc
+  if proc.save isnt false # direct limit processes don't need to save their results, maybe others won't bother either
     try
-      proc.result = {}
-      args = proc # default just send the whole process as the arg
-      if proc.args?
-        if typeof proc.args is 'string'
-          try
-            args = JSON.parse proc.args
-          catch
-            args = proc.args
-        else
-          args = proc.args
-      if typeof args is 'object' and typeof proc.args is 'string' and proc.args.indexOf('[') is 0
-        # save results keyed by function, so assuming functions produce results of same shape, they fit in index
-        proc.result[proc.function] = fn.apply this, args
-      else
-        proc.result[proc.function] = fn args
-    catch err
-      proc.result = {error: err.toString()}
-      API.log msg: 'Job process error', error: err, string: err.toString(), process: proc, level: 'debug'
-      if API.settings.log.level in ['debug','all']
-        console.log JSON.stringify err
-        console.log err.toString()
-        console.log proc
-    if proc.save isnt false # direct limit processes don't need to save their results, maybe others won't bother either
+      job_result.insert proc # if this fails, we stringify and save that way
+    catch
       try
-        job_result.insert proc # if this fails, we stringify and save that way
-      catch
-        try
-          proc.result.string = JSON.stringify proc.result[proc.function] # try saving as string then change it back
-          delete proc.result[proc.function]
-          job_result.insert proc
-          proc.result[proc.function] = JSON.parse(pros.result.string)
-          delete proc.result.string
-    job_processing.remove(proc._id) if not proc.limit or Date.now() >= proc.timeout
-    if proc.repeat
-      proc.counter ?= 1
-      proc.original ?= proc._id
-      pn = JSON.parse(JSON.stringify(proc))
-      pn.repeat -= 1 if pn.repeat isnt true
-      pn.counter += 1
-      pn._id = Random.id()
-      job_process.insert pn
-    else if proc.order
-      job_process.update {job: proc.job, order: proc.order+1}, {available:true}
-    job_job.each 'job.processes.process:'+proc._id, (job) -> API.job.progress(job,300000) if job._id isnt proc._id and not job_processing.get(job._id)?
-    if proc.callback
-      cb = API
-      cb = cb[c] for c in proc.callback.replace('API.','').split('.')
-      cb null, proc # TODO should this go in timeout?
-    return proc
-  else
-    return false
+        proc.result.string = JSON.stringify proc.result[proc.function] # try saving as string then change it back
+        delete proc.result[proc.function]
+        job_result.insert proc
+        proc.result[proc.function] = JSON.parse(pros.result.string)
+        delete proc.result.string
+  job_processing.remove(proc._id) if not proc.limit or Date.now() >= proc.timeout
+  if proc.repeat
+    proc.counter ?= 1
+    proc.original ?= proc._id
+    pn = JSON.parse(JSON.stringify(proc))
+    pn.repeat -= 1 if pn.repeat isnt true
+    pn.counter += 1
+    pn._id = Random.id()
+    job_process.insert pn
+  else if proc.order
+    job_process.update {job: proc.job, order: proc.order+1}, {available:true}
+  job_job.each 'job.processes.process:'+proc._id, (job) -> API.job.progress(job,300000) if job._id isnt proc._id and not job_processing.get(job._id)?
+  if proc.callback
+    cb = API
+    cb = cb[c] for c in proc.callback.replace('API.','').split('.')
+    cb null, proc # TODO should this go in timeout?
+  return proc

 API.job._ignores = {}
 API.job.next = (ignore=[]) ->


converted slashes in labels to dots

converted slashes in labels to dots

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -34,7 +34,6 @@
       throw new Meteor.Error "Application #{app}:#{version} does not exist"

     project = Settings.get('project')
-    options = _.extend {"dataDir": Settings.get('dataDir')}, options

     options.storageBucket =
       if bucket = options.storageBucket
@@ -66,6 +65,7 @@

     definition = YAML.load def

+
     Instances.upsert {name: instance}, $set:
       images: (service.image for serviceName, service of definition)
       compose: definition
@@ -80,36 +80,51 @@
     # though Docker events and inspect information.
     # This way we can relate containers and events
     for serviceName, service of definition
+      service.container_name = "#{project}-#{instance}-#{serviceName}"
+      service.restart = 'unless-stopped'
       service.labels =
-        'bigboat/instance/name': instance
-        'bigboat/service/name': serviceName
-        'bigboat/container/type': 'service'
-        'bigboat/application/name': app
-        'bigboat/application/version': version
-        'bigboat/agent/url': agentUrl
-        'bigboat/startedBy': user._id
-        'bigboat/storage/bucket': options.storageBucket
-        'bigboat/instance/endpoint': bigboatCompose[serviceName]?.endpoint
-        'bigboat/instance/endpoint/protocol': bigboatCompose[serviceName]?.protocol
-      service.restart = 'unless-stopped'
+        'bigboat.instance.name': instance
+        'bigboat.service.name': serviceName
+        'bigboat.service.type': 'service'
+        'bigboat.application.name': app
+        'bigboat.application.version': version
+        'bigboat.agent.url': agentUrl
+        'bigboat.startedBy': user._id
+        'bigboat.storage.bucket': options.storageBucket
+        'bigboat.instance.endpoint.path': bigboatCompose[serviceName]?.endpoint
+        'bigboat.instance.endpoint.protocol': bigboatCompose[serviceName]?.protocol
+        'bigboat.container.map_docker':  if bigboatCompose[serviceName]?.map_docker then 'true' else undefined
+        'bigboat.container.enable_ssh':  if bigboatCompose[serviceName]?.enable_ssh then 'true' else undefined
+
+      if bigboatCompose[serviceName].enable_ssh
+        definition["bb-ssh-#{serviceName}"] =
+          image: 'jeroenpeeters/docker-ssh'
+          container_name: "#{project}-#{instance}-#{serviceName}-ssh"
+          environment:
+            CONTAINER: "#{project}-#{instance}-#{serviceName}"
+            AUTH_MECHANISM: 'noAuth'
+            HTTP_ENABLED: 'false'
+            CONTAINER_SHELL: 'bash'
+          labels:
+            'bigboat.instance.name': instance
+            'bigboat.service.name': serviceName
+            'bigboat.service.type': 'ssh'
+            'bigboat.container.map_docker': 'true'
+          restart: 'unless-stopped'

     console.log 'xxx', definition

     callOpts =
       responseType: "buffer"
       data:
-        dir: dir
-
         app:
           name: app
           version: version
           definition: definition
           bigboatCompose: bigboatCompose
-          parameter_key: '_#_'
         instance:
           name: instance
-          options: _.extend({}, options, { project: project })
-          parameters: parameters
+          options: options
         bigboat:
           url: process.env.ROOT_URL
           statusUrl: "#{process.env.ROOT_URL}/api/v1/state/#{instance}"


hub/stats: also collect info about lean, rtex and rnw files

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -2330,8 +2330,10 @@
             )
             SELECT ext, cnt
             FROM ext_count
-            WHERE ext IN ('sagews', 'ipynb', 'tex', 'txt', 'py', 'md', 'sage', 'term', 'rnw', 'rmd', 'rst',
-                          'png', 'svg', 'jpeg', 'jpg', 'pdf', 'tasks', 'course', 'sage-chat', 'chat')
+            WHERE ext IN ('sagews', 'ipynb', 'tex', 'rtex', 'rnw',
+                          'rmd', 'txt', 'py', 'md', 'sage', 'term', 'rst', 'lean',
+                          'png', 'svg', 'jpeg', 'jpg', 'pdf',
+                          'tasks', 'course', 'sage-chat', 'chat')
             ORDER BY ext
             """


hub/stats: get rid of ttl offset, since we are not using cronjob

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -2525,7 +2525,6 @@
     # in cache for ttl seconds.
     get_stats: (opts) =>
         opts = defaults opts,
-            ttl_dt : 15       # 15 secs subtracted from ttl to compensate for computation duration when called via a cronjob
             ttl    : 5*60     # how long cached version lives (in seconds)
             ttl_db : 30       # how long a valid result from a db query is cached in any case
             update : true     # true: recalculate if older than ttl; false: don't recalculate and pick it from the DB (locally cached for ttl secs)
@@ -2537,9 +2536,8 @@
             (cb) =>
                 dbg("using cached stats?")
                 if @_stats_cached?
-                    # decide if cache should be used -- tighten interval if we are allowed to update
-                    offset_dt = if opts.update then opts.ttl_dt else 0
-                    is_cache_recent = @_stats_cached.time > misc.seconds_ago(opts.ttl - offset_dt)
+                    # decide if cache should be used
+                    is_cache_recent = @_stats_cached.time > misc.seconds_ago(opts.ttl)
                     # in case we aren't allowed to update and the cache is outdated, do not query db too often
                     did_query_recently = @_stats_cached_db_query > misc.seconds_ago(opts.ttl_db)
                     if is_cache_recent or did_query_recently


rename sub

rename sub

--- server/voyager.coffee
+++ server/voyager.coffee
@@ -29,7 +29,7 @@
                 @_stats = new ServerStats()
                 @startStatsTransmit()
                 @_ddpclient.on "message", @onMessage
-                @_ddpclient.subscribe "appEvents", [@_voyagerApiKey], (error, result) =>
+                @_ddpclient.subscribe "serverEvents", [@_voyagerApiKey], (error, result) =>
                     if error
                         console.error "Could not subscribe to events - stopping stats transmission"
                         @stopStatsTransmit()
@@ -65,7 +65,7 @@
             console.error "Invalid log level"
             return

-        log =
+        log =
             level: level
             createdAt: new Date().getTime()
             message: message


event: proper x-forwards-for parsing to deal with multiple reverse-proxies

--- services/event.coffee
+++ services/event.coffee
@@ -39,10 +39,12 @@
         _.defaults tags, inviterTags

   getFields: (req, user, userFields = {}, inviter = null) ->
+    forwardedFor = req.headers['x-forwarded-for'] or
+      req.connection.remoteAddress or ''
     fields = _.defaults {
       userId: user.id
       sessionId: user.sessionId
-      ip: req.headers['x-forwarded-for'] or req.connection.remoteAddress
+      ip: forwardedFor.split(',')[0].trim() or null
     }, userFields

     if inviter


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/camera.coffee
+++ test/camera.coffee
@@ -3,6 +3,6 @@
 test "create", ->
   ok Camera()

+test "", -
 # Clear out the module
 module()
-


first commit

first commit

--- snippets/element.cson
+++ snippets/element.cson
@@ -10,8 +10,13 @@

           });
       """
-    "message":
-      "prefix": "msg",
+    "message-error":
+      "prefix": "msgerror",
       "body": """
-      this.$message('','sucess|error');
+      this.$message.error('');
       """
+    "message-succ":
+      "prefix": "msgsucc",
+      "body": """
+      this.$message.success('');
+      """


KodingAppsController: Use domId instead using css-class to identify lazyloaded scripts

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -111,7 +111,7 @@
   @appendScriptElement = (type, {identifier, url, callback})->

     identifier = identifier.replace /\./g, '_'
-    cssClass   = "internal-#{type}-#{identifier}"
+    domId      = "internal-#{type}-#{identifier}"
     vmName     = FSHelper.getVMNameFromPath url
     tagName    = type

@@ -121,7 +121,7 @@
       file = FSHelper.createFileFromPath url
       file.fetchContents (err, partial)->
         return  if err
-        obj = new KDCustomHTMLView {cssClass, tagName, partial}
+        obj = new KDCustomHTMLView {domId, tagName, partial}

         if type is 'script'
           obj.once 'viewAppended', -> callback null
@@ -146,8 +146,9 @@
         bind       = "load"
         load       = -> callback null

-      $('head')[0].appendChild (new KDCustomHTMLView {
-        cssClass, tagName, attributes, bind, load
+
+      document.head.appendChild (new KDCustomHTMLView {
+        domId, tagName, attributes, bind, load
       }).getElement()

   # #


instructions: comment-out broken pages

instructions: comment-out broken pages

--- client/app/MainApp/book/bookdata.coffee
+++ client/app/MainApp/book/bookdata.coffee
@@ -120,55 +120,55 @@
     routeURL  : 'Develop'
   ,

-    cssClass  : "develop enviroments"
-    content   : """
Control Your Virtual Machine!

-
It's easy to control your Virtual Machine(s)! Some basic actions you can perform are listed below:

-

-
Turn your Virtual Machine on and off

-
Re-Initialize your Virtual Machine

-
Delete your Virtual Machine and start with a fresh one

-
Checkout the Virtual Machine menu for more features

-

-                """
-    section   : 3
-    parent    : 5
-    showHow   : yes
-    howToSteps: ['showVMMenu']
-    menuItem  : "Develop"
-
-  ,
-    cssClass  : "develop enviroments more"
-    content   : """
Open Virtual Machines in your Terminal

-
If you have more than 1 Virtual Machine, you can open that Virtual Machine's
-                   menu by clicking terminal icon on Virtual Machine menu.

-                """
-    section   : 4
-    parent    : 5
-    showHow   : yes
-    howToSteps: ['openVMTerminal']
-    menuItem  : "Develop"
-  ,
-    cssClass  : "develop more-1"
-    content   : """
You can view your recently opened files by moving your cursor to the footer area. A new section will slide up displaying your recently opened files!

-                """
-    section   : 5
-    parent    : 5
-    routeURL  : "/Develop"
-    showHow   : yes
-    howToSteps: ['showRecentFiles']
-    menuItem  : "Develop"
-  ,
-    cssClass  : "develop buy more-1"
-    content   : """
Need more Virtual Machines?

-
It's easy to buy more Virtual Machines. If you need more space, just buy new one!

-                """
-    section   : 6
-    parent    : 5
-    routeURL  : "/Develop"
-    showHow   : yes
-    howToSteps: ['showNewVMMenu']
-    menuItem  : "Develop"
-  ,
+  #   cssClass  : "develop enviroments"
+  #   content   : """
Control Your Virtual Machine!

+  #
It's easy to control your Virtual Machine(s)! Some basic actions you can perform are listed below:

+  #

+  #
Turn your Virtual Machine on and off

+  #
Re-Initialize your Virtual Machine

+  #
Delete your Virtual Machine and start with a fresh one

+  #
Checkout the Virtual Machine menu for more features

+  #

+  #               """
+  #   section   : 3
+  #   parent    : 5
+  #   showHow   : yes
+  #   howToSteps: ['showVMMenu']
+  #   menuItem  : "Develop"
+
+  # ,
+  #   cssClass  : "develop enviroments more"
+  #   content   : """
Open Virtual Machines in your Terminal

+  #
If you have more than 1 Virtual Machine, you can open that Virtual Machine's
+  #                  menu by clicking terminal icon on Virtual Machine menu.

+  #               """
+  #   section   : 4
+  #   parent    : 5
+  #   showHow   : yes
+  #   howToSteps: ['openVMTerminal']
+  #   menuItem  : "Develop"
+  # ,
+  #   cssClass  : "develop more-1"
+  #   content   : """
You can view your recently opened files by moving your cursor to the footer area. A new section will slide up displaying your recently opened files!

+  #               """
+  #   section   : 5
+  #   parent    : 5
+  #   routeURL  : "/Develop"
+  #   showHow   : yes
+  #   howToSteps: ['showRecentFiles']
+  #   menuItem  : "Develop"
+  # ,
+  #   cssClass  : "develop buy more-1"
+  #   content   : """
Need more Virtual Machines?

+  #
It's easy to buy more Virtual Machines. If you need more space, just buy new one!

+  #               """
+  #   section   : 6
+  #   parent    : 5
+  #   routeURL  : "/Develop"
+  #   showHow   : yes
+  #   howToSteps: ['showNewVMMenu']
+  #   menuItem  : "Develop"
+  # ,
     cssClass  : "develop more-1"
     content   : """
It's easy to change your homepage! Currently:  http://{{#(profile.nickname)}}.kd.io


Better CS.

Better CS.

--- add-admin.coffee
+++ add-admin.coffee
@@ -46,15 +46,15 @@

 utils.user.userExists(conn, u, (error, result) ->
-	if (!!error)
+	if error?
 		console.log 'Error: '+require('util').inspect(error)
 		process.exit 1
-	else if (result == true)
+	else if result == true
 		console.log "User `#{u}` already exists."
 		process.exit 1
 	else
 		utils.user.registerUser(conn, u, p, config.PERMISSIONS_ADMIN, (error, result) ->
-			if (!!error)
+			if error?
 				console.log 'Error: '+require('util').inspect(error)
 				process.exit 1
 			else


unit coffee update

unit coffee update

--- cms/static/coffee/src/views/unit.coffee
+++ cms/static/coffee/src/views/unit.coffee
@@ -227,7 +227,8 @@
   saveName: =>
     # Treat the metadata dictionary as immutable
     metadata = $.extend({}, @model.get('metadata'))
-    metadata.display_name = @$('.unit-display-name-input').val()
+    metadata.display_name = $('.unit-display-name-input').val()
+    metadata.unlock_term = $('.unit-unlock-term-input').val()
     @model.save(metadata: metadata)
     # Update name shown in the right-hand side location summary.
     $('.unit-location .editing .unit-name').html(metadata.display_name)
@@ -262,7 +263,8 @@
   saveTerm: =>
     # Treat the metadata dictionary as immutable
     metadata = $.extend({}, @model.get('metadata'))
-    metadata.unlock_term = @$('.unit-unlock-term-input').val()
+    metadata.display_name = $('.unit-display-name-input').val()
+    metadata.unlock_term = $('.unit-unlock-term-input').val()
     @model.save(metadata: metadata)
     # Update name shown in the right-hand side location summary.
     $('.unit-location .editing .unit-term').html(metadata.unlock_term)


Remove accidentally-included specs from cherry-pick conflict

--- spec/pane-spec.coffee
+++ spec/pane-spec.coffee
@@ -218,35 +218,6 @@
       pane.setPendingItem("fake item two")
       expect(callbackCalled).toBeTruthy()

-  describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
-    it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
-      pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))
-      [item1, item2, item3, item4, item5] = pane.getItems()
-      pane.itemStack = [item3, item1, item2, item5, item4]
-
-      pane.activateItem(item4)
-      expect(pane.getActiveItem()).toBe item4
-      pane.activateNextRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item5
-      pane.activateNextRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item2
-      pane.activatePreviousRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item5
-      pane.activatePreviousRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item4
-      pane.activatePreviousRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item3
-      pane.activatePreviousRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item1
-      pane.activateNextRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item3
-      pane.activateNextRecentlyUsedItem()
-      expect(pane.getActiveItem()).toBe item4
-      pane.activateNextRecentlyUsedItem()
-      pane.moveActiveItemToTopOfStack()
-      expect(pane.getActiveItem()).toBe item5
-      expect(pane.itemStack[4]).toBe item5
-
   describe "::activateNextItem() and ::activatePreviousItem()", ->
     it "sets the active item to the next/previous item, looping around at either end", ->
       pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))


Tweak

Tweak

--- test/generators/relational/has_many.coffee
+++ test/generators/relational/has_many.coffee
@@ -190,7 +190,7 @@
           assert.ok(!err, "No errors: #{err}")
           assert.ok(owner, 'found owners')
           reverses = owner.get('reverses').models
-          assert.equal(reverses.length, 2, "loaded correct models.")
+          assert.equal(reverses.length, 2, "loaded correct models. Expected: #{2}. Actual: #{reverses.length}.")
           reverse_ids = (reverse.id for reverse in reverses)

           Owner.cursor({id: {$ne: owner.id}}).include('reverses').toModel (err, another_owner) ->
@@ -199,7 +199,7 @@
             assert.ok(owner.id isnt another_owner.id, "loaded a model with a different id.")

             another_reverses = another_owner.get('reverses').models
-            assert.equal(another_reverses.length, 2, "loaded correct models.")
+            assert.equal(another_reverses.length, 2, "loaded correct models. Expected: #{2}. Actual: #{another_reverses.length}.")
             another_reverse_ids = (reverse.id for reverse in another_reverses)
             moved_reverse_id = another_reverse_ids[0]
             moved_reverse_json = another_reverses[0].toJSON()
@@ -228,10 +228,10 @@

                   owner.get 'reverses', (err, updated_reverses) ->
                     assert.ok(!err, "No errors: #{err}")
-                    assert.equal(updated_reverses.length, 3, "loaded correct models.")
+                    assert.equal(updated_reverses.length, 3, "loaded correct models. Expected: #{3}. Actual: #{updated_reverses.length}.")
                     updated_reverse_ids = (reverse.id for reverse in updated_reverses)

-                    assert.equal(updated_reverse_ids.length, 3, "Moved the reverse")
+                    assert.equal(updated_reverse_ids.length, 3, "Moved the reverse. Expected: #{3}. Actual: #{updated_reverse_ids.length}")
                     assert.ok(_.contains(updated_reverse_ids, moved_reverse_id), "Moved the reverse_id")
                     updated_moved_reverse = updated_reverses[_.indexOf(updated_reverse_ids, moved_reverse_id)]

@@ -246,7 +246,7 @@
           assert.ok(!err, "No errors: #{err}")
           assert.ok(owner, 'found owners')
           reverses = owner.get('reverses').models
-          assert.equal(reverses.length, 2, "loaded correct models.")
+          assert.equal(reverses.length, 2, "loaded correct models. Expected: #{2}. Actual: #{reverses.length}.")
           reverse_ids = (reverse.id for reverse in reverses)

           Owner.cursor({id: {$ne: owner.id}}).include('reverses').toModel (err, another_owner) ->
@@ -255,7 +255,7 @@
             assert.ok(owner.id isnt another_owner.id, "loaded a model with a different id.")

             another_reverses = another_owner.get('reverses').models
-            assert.equal(another_reverses.length, 2, "loaded correct models.")
+            assert.equal(another_reverses.length, 2, "loaded correct models. Expected: #{2}. Actual: #{another_reverses.length}.")
             another_reverse_ids = (reverse.id for reverse in another_reverses)
             moved_reverse_id = another_reverse_ids[0]
             moved_reverse_json = another_reverses[0].toJSON()
@@ -285,7 +285,7 @@
                     assert.equal(updated_reverses.length, 3, "loaded correct models.")
                     updated_reverse_ids = (reverse.id for reverse in updated_reverses)

-                    assert.equal(updated_reverse_ids.length, 3, "Moved the reverse")
+                    assert.equal(updated_reverse_ids.length, 3, "Moved the reverse. Expected: #{3}. Actual: #{updated_reverse_ids.length}")
                     assert.ok(_.contains(updated_reverse_ids, moved_reverse_id), "Moved the reverse_id")
                     updated_moved_reverse = updated_reverses[_.indexOf(updated_reverse_ids, moved_reverse_id)]

@@ -300,7 +300,7 @@
           assert.ok(!err, "No errors: #{err}")
           assert.ok(owner, 'found owners')
           reverses = owner.get('reverses').models
-          assert.equal(reverses.length, 2, "loaded correct models.")
+          assert.equal(reverses.length, 2, "loaded correct models. Expected: #{2}. Actual: #{reverses.length}.")
           reverse_ids = (reverse.id for reverse in reverses)

           Owner.cursor({id: {$ne: owner.id}}).include('reverses').toModel (err, another_owner) ->
@@ -309,7 +309,7 @@
             assert.ok(owner.id isnt another_owner.id, "loaded a model with a different id.")

             another_reverses = another_owner.get('reverses').models
-            assert.equal(another_reverses.length, 2, "loaded correct models.")
+            assert.equal(another_reverses.length, 2, "loaded correct models. Expected: #{2}. Actual: #{another_reverses.length}.")
             another_reverse_ids = (reverse.id for reverse in another_reverses)
             moved_reverse_id = another_reverse_ids[0]
             moved_reverse_json = another_reverses[0].toJSON()


Slow does polling tests so that they are more robust

--- test/rebuildDHIS2resourceTableTests.coffee
+++ test/rebuildDHIS2resourceTableTests.coffee
@@ -95,7 +95,7 @@

     it 'should poll tasks until completed is returned', (done) ->
       config.getConf()['ilr-to-dhis']['dhis2-url'] = 'https://localhost:7130'
-      server.pollTask out, 'RESOURCETABLE_UPDATE', 10, 50, (err) ->
+      server.pollTask out, 'RESOURCETABLE_UPDATE', 20, 100, (err) ->
         should.not.exist err
         timesTargetCalled.should.be.exactly 4
         authPresent.should.be.true()
@@ -103,21 +103,21 @@

     it 'should return an error if something goes wrong querying tasks', (done) ->
       config.getConf()['ilr-to-dhis']['dhis2-url'] = 'https://localhost:7131'
-      server.pollTask out, 'RESOURCETABLE_UPDATE', 10, 50, (err) ->
+      server.pollTask out, 'RESOURCETABLE_UPDATE', 20, 100, (err) ->
         should.exist err
         err.message.should.be.exactly 'Incorrect status code recieved, 500'
         done()

     it 'should timeout', (done) ->
       config.getConf()['ilr-to-dhis']['dhis2-url'] = 'https://localhost:7130'
-      server.pollTask out, 'RESOURCETABLE_UPDATE', 40, 50, (err) ->
+      server.pollTask out, 'RESOURCETABLE_UPDATE', 80, 100, (err) ->
         should.exist err
         err.message.should.be.exactly 'Polled tasks endpoint 2 time and still not completed, timing out...'
         done()

     it 'should record an orchestration', (done) ->
       config.getConf()['ilr-to-dhis']['dhis2-url'] = 'https://localhost:7130'
-      server.pollTask out, 'RESOURCETABLE_UPDATE', 10, 50, (err) ->
+      server.pollTask out, 'RESOURCETABLE_UPDATE', 20, 100, (err) ->
         should.not.exist err
         orchestrations.length.should.be.exactly 1
         orchestrations[0].name.should.be.exactly 'Polled DHIS resource rebuild task 4 times'


Fix handler return for escape.

Fix handler return for escape.

We've consumed the event here, so we should suppress it.

--- content_scripts/mode_key_handler.coffee
+++ content_scripts/mode_key_handler.coffee
@@ -45,7 +45,7 @@
     else if isEscape and HelpDialog?.showing
       @keydownEvents[event.keyCode] = true
       HelpDialog.hide()
-      @stopBubblingAndTrue
+      false # Suppress event.
     else if isEscape
       @continueBubbling
     else if @isMappedKey keyChar


Add Japanese, reorg language list

Add Japanese, reorg language list

--- app/index.coffee
+++ app/index.coffee
@@ -44,9 +44,10 @@
 languageManager = new LanguageManager
   translations:
     en: label: 'English', strings: enUs
-    zh_cn: label: '简体中文 Chinese (Simplified)', strings: './translations/zh-cn.json'
-    zh_tw: label: '繁體中文 Chinese (Traditional)', strings: './translations/zh-tw.json'
-    ru: label: "русский", strings: "./translations/ru.json"
+    zh_cn: label: '简体中文', strings: './translations/zh-cn.json'
+    zh_tw: label: '繁體中文', strings: './translations/zh-tw.json'
+    ru: label: 'русский', strings: './translations/ru.json'
+    ja: label: '日本語', strings: './translations/ja.json'
     de: label: 'Deutsch', strings: './translations/de.json'
     fr: label: 'Français', strings: './translations/fr.json'
     pl: label: 'Polski', strings: './translations/pl.json'


Simplified _throw_unknown_expectation() parameters.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -3,6 +3,10 @@

 #
 # mock()
+#
+# mock (mocked_book) ->
+#   mocked_book.expects("read")
+#   ...
 #
 publish.mock = (test_function) ->
   try
@@ -122,7 +126,7 @@
   @build_mocked_method: (method_name) ->
     (args...) ->
       expectation = AllExpectations.find_expectation(@, method_name, args...)
-      _throw_unknown_expectation("#{method_name}(#{args})") unless expectation?
+      _throw_unknown_expectation(method_name, args) unless expectation?
       AllExpectations.check_for_duplicate_expectations()
       expectation._called = yes
       throw expectation._throws if expectation._throws?
@@ -130,8 +134,8 @@

   # private

-  _throw_unknown_expectation = (expectation) ->
-    throw new Error( "#{expectation} does not match any expectations" )
+  _throw_unknown_expectation = (method_name, args) ->
+    throw new Error( "#{method_name}(#{args}) does not match any expectations" )

 #


Mount only new app <back>

Mount only new app <back>

--- src/apps/index.coffee
+++ src/apps/index.coffee
@@ -4,108 +4,114 @@
 app = module.exports = express()

 # Normal routing
-console.time('actions')
-app.use(require('../apps/actions'))
-console.timeEnd('actions')
+# console.time('actions')
+# app.use(require('../apps/actions'))
+# console.timeEnd('actions')

-console.time('feed')
-app.use(require('../apps/feed'))
-console.timeEnd('feed')
+# console.time('feed')
+# app.use(require('../apps/feed'))
+# console.timeEnd('feed')

-console.time('home')
-app.use(require('../apps/home'))
-console.timeEnd('home')
+# console.time('home')
+# app.use(require('../apps/home'))
+# console.timeEnd('home')

-console.time('blog')
-app.use(require('../apps/blog'))
-console.timeEnd('blog')
+# console.time('blog')
+# app.use(require('../apps/blog'))
+# console.timeEnd('blog')

-console.time('authentication')
-app.use(require('../apps/authentication'))
-console.timeEnd('authentication')
+# console.time('authentication')
+# app.use(require('../apps/authentication'))
+# console.timeEnd('authentication')

-console.time('confirmation')
-app.use(require('../apps/confirmation'))
-console.timeEnd('confirmation')
+# console.time('confirmation')
+# app.use(require('../apps/confirmation'))
+# console.timeEnd('confirmation')

-console.time('getting_started')
-app.use(require('../apps/getting_started'))
-console.timeEnd('getting_started')
+# console.time('getting_started')
+# app.use(require('../apps/getting_started'))
+# console.timeEnd('getting_started')

-console.time('tools')
-app.use(require('../apps/tools'))
-console.timeEnd('tools')
+# console.time('tools')
+# app.use(require('../apps/tools'))
+# console.timeEnd('tools')

-console.time('util')
-app.use(require('../apps/util'))
-console.timeEnd('util')
+# console.time('util')
+# app.use(require('../apps/util'))
+# console.timeEnd('util')

-console.time('about')
-app.use(require('../apps/about'))
-console.timeEnd('about')
+# console.time('about')
+# app.use(require('../apps/about'))
+# console.timeEnd('about')

-console.time('search')
-app.use(require('../apps/search'))
-console.timeEnd('search')
+# console.time('search')
+# app.use(require('../apps/search'))
+# console.timeEnd('search')

-console.time('manage')
-app.use(require('../apps/manage'))
-console.timeEnd('manage')
+# console.time('manage')
+# app.use(require('../apps/manage'))
+# console.timeEnd('manage')

-console.time('marklet')
-app.use(require('../apps/marklet'))
-console.timeEnd('marklet')
+# console.time('marklet')
+# app.use(require('../apps/marklet'))
+# console.timeEnd('marklet')

-console.time('import')
-app.use(require('../apps/import'))
-console.timeEnd('import')
+# console.time('import')
+# app.use(require('../apps/import'))
+# console.timeEnd('import')

-console.time('settings')
-app.use(require('../apps/settings'))
-console.timeEnd('settings')
+# console.time('settings')
+# app.use(require('../apps/settings'))
+# console.timeEnd('settings')

-console.time('onboarding')
-app.use(require('../apps/onboarding'))
-console.timeEnd('onboarding')
+# console.time('onboarding')
+# app.use(require('../apps/onboarding'))
+# console.timeEnd('onboarding')

-console.time('graphql')
-app.use(require('../apps/graphql'))
-console.timeEnd('graphql')
+# console.time('graphql')
+# app.use(require('../apps/graphql'))
+# console.timeEnd('graphql')

-console.time('examples')
-app.use(require('../apps/examples'))
-console.timeEnd('examples')
+# console.time('examples')
+# app.use(require('../apps/examples'))
+# console.timeEnd('examples')

-console.time('vanity_redirects')
-app.use(require('../apps/vanity_redirects'))
-console.timeEnd('vanity_redirects')
+# console.time('vanity_redirects')
+# app.use(require('../apps/vanity_redirects'))
+# console.timeEnd('vanity_redirects')

-console.time('deeplink')
-app.use(require('../apps/deeplink'))
-console.timeEnd('deeplink')
+# console.time('deeplink')
+# app.use(require('../apps/deeplink'))
+# console.timeEnd('deeplink')

-console.time('apple-site-association')
-app.use(require('../apps/apple_site_association'))
-console.timeEnd('apple-site-association')
+# console.time('apple-site-association')
+# app.use(require('../apps/apple_site_association'))
+# console.timeEnd('apple-site-association')

-# Dynamic routing (in order)
-console.time('profile')
-app.use(require('../apps/profile'))
-console.timeEnd('profile')
+# # Dynamic routing (in order)
+# console.time('profile')
+# app.use(require('../apps/profile'))
+# console.timeEnd('profile')

-console.time('block')
-app.use(require('../apps/block'))
-console.timeEnd('block')
+# console.time('block')
+# app.use(require('../apps/block'))
+# console.timeEnd('block')

-console.time('new_channel')
-app.use(require('../apps/new_channel'))
-console.timeEnd('new_channel')
+# console.time('new_channel')
+# app.use(require('../apps/new_channel'))
+# console.timeEnd('new_channel')

-console.time('rss')
-app.use(require('../apps/rss'))
-console.timeEnd('rss')
+# console.time('rss')
+# app.use(require('../apps/rss'))
+# console.timeEnd('rss')

-# Ennvironment specific
-switch NODE_ENV
-  when 'development'
-    app.use require '../apps/statuses'
+# # Ennvironment specific
+# switch NODE_ENV
+#   when 'development'
+#     app.use require '../apps/statuses'
+
+
+# New Application
+console.time('app')
+app.use(require('../apps/app'))
+console.timeEnd('app')


Fixed bug from last commit that stopped the app from running - woops

--- src/background.coffee
+++ src/background.coffee
@@ -7,7 +7,7 @@
 currentApp = null

 appIsRunning = ->
-  not currentApp?.contentWindow.closed
+  currentApp and not currentApp.contentWindow.closed

 onCreated = (win) ->
   currentApp = win


Re-implement Background#update to show more of each image.

Uses #parallaxTolerance. Minor refactoring included.

--- src/background.coffee
+++ src/background.coffee
@@ -61,18 +61,22 @@
         factor = (zIndex / @layers.length) ** 2 * @parallaxFactor
         # Add buffer to further constrain.
         factor = (factor + @parallaxBuffer / 2) / @parallaxBuffer
-        factor = Math.min 1, factor
-        # Set scroll factor.
-        layer.scrollFactor = factor
-
+        # Set scroll factor and inverse.
+        layer.scrollFactor = Math.min 1, factor
+        layer.scrollResistance = Math.max 0, (1 - factor)

       @debug 'layers', @layers

     update: ->
-      for {image, scrollFactor} in @layers
-        # y-offset decreases with closer layers
-        image.y = @camera.y * (1 - scrollFactor)
+      for {image, zIndex, scrollFactor, scrollResistance} in @layers
+        # Closer images change more with camera.
+        image.y = @camera.y * scrollResistance

+        # Shift based on scroll factor for full visibility of current bg images.
+        unless zIndex is 1 # Not farthest.
+          image.y += @parallaxTolerance
+          unless zIndex is @topZIndex # Or nearest.
+            image.y -= @parallaxTolerance * scrollFactor ** (1 / 3)

   _.extend Background::, Helpers.DebugMixin


small fix

small fix

--- src/base_model.coffee
+++ src/base_model.coffee
@@ -64,19 +64,19 @@
       current_value = @get(key)
       for validator, v of value
         klass = switch validator
-        when "length"       then new LengthValidator()
-        when "exclusion"    then new ExclusionValidator()
-        when "inclusion"    then new InclusionValidator()
-        when "format"       then new FormatValidator()
-        when "numericality" then new NumericalityValidator()
-        when "presence"     then new PresenceValidator()
+          when "length"       then new LengthValidator()
+          when "exclusion"    then new ExclusionValidator()
+          when "inclusion"    then new InclusionValidator()
+          when "format"       then new FormatValidator()
+          when "numericality" then new NumericalityValidator()
+          when "presence"     then new PresenceValidator()

         r = klass.validate(current_value, v)
         if !r
           e = if typeof(v) is "object"
-            v["error"] || klass.error_message()
-          else
-            klass.error_message()
+                v["error"] || klass.error_message()
+              else
+                klass.error_message()

           (@errors["#{key}"] ?= []).push("#{e}")


Add help subcommand

Add help subcommand

--- src/bin/knodeo.coffee
+++ src/bin/knodeo.coffee
@@ -31,6 +31,14 @@
 config.description 'Create a new thing'
 config.action ()->
   console.log "This command dumps the configuration"
+
+# ## `knodeo help`
+
+help = program.command 'help'
+help.description 'Create a new thing'
+help.action ()->
+  require('../lib/about')
+

 # # Parameterized Subcommands


add subscriber blueprints

add subscriber blueprints

--- src/blueprints.coffee
+++ src/blueprints.coffee
@@ -88,10 +88,11 @@
       url:    '/library/daisho'

 models = [
+  'order'
+  'note'
+  'product'
+  'subscribers'
   'user'
-  'order'
-  'product'
-  'note'
   'wallet'
 ]


fork2 server should build and run a project

--- src/cli/server.coffee
+++ src/cli/server.coffee
@@ -1,10 +1,22 @@
+path = require 'path'
+
 class Server extends require("./base")
   name: "server"

-  summary: "Starts the fork2 web server."
+  summary: "Build and run a project."

   doc: """
-  fork2 server $PROJECT_PATH --port $PORT
+  fork2 server [PROJECT_PATH] [--port PORT]
+
+  PROJECT_PATH defaults to curren working directory.
+
+  Running this command is equivalent to running,
+
+    `fork2 build-project $PROJECT_PATH $PROJECT_PATH/.workspace`
+
+  then running,
+
+    `fork2 run $PROJECT_PATH/.workspace`
   """

   configParser: ->
@@ -14,7 +26,17 @@

   run: (args) ->
     args = @parse(args)
-    app = require("../server/app").create()
-    app.start(args.port)
+
+    @projectPath = path.normalize(args._[0] || process.cwd())
+    @workspacePath = path.join @projectPath, ".workspace"
+
+    @buildProject()
+    @runProject(args.port)
+
+  runProject: (port) ->
+    @sh("fork2 run #{@workspacePath} --port=#{port}")
+
+  buildProject: ->
+    @sh("fork2 build-project #{@projectPath}")

 module.exports = new Server()


local services settings

local services settings

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -89,7 +89,7 @@
       # In the second form, the optional params can be overridden through parameters at data.api calls
       #
       # The normalized form is the first one.
-      #
+      #
       # @param {Array} the parameters for the API calls
       # @return {Array} The normalized form of parameters
       ###
@@ -227,7 +227,6 @@
         collection      = new Collection
         collectionURI   = path
         collection.url  = path
-
         collection.on 'all', ->
           args = slice.call(arguments)
           eventName = ("collection." + collectionURI.replace(/\//g, ".") + '.' + args[0])
@@ -275,6 +274,7 @@
       onRemoteReady = (remoteConfig)->
         data = remoteConfig.data
         env.config.services = remoteConfig.services
+        env.sandbox.config.services = remoteConfig.services
         for m in ['me', 'app', 'org']
           attrs = data[m]
           if attrs


Added DOMPropertyUpdate RPC method on client.

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -10,6 +10,7 @@
     #   'targetID'
     #   'args'
     DOMUpdate : (params) ->
+        console.log "Processing DOMUpdate"
         target = @nodes.get(params.targetID)
         method = params.method
         rvID = params.rvID
@@ -17,9 +18,6 @@
         if target[method] == undefined
             throw new Error "Tried to process an invalid method: #{method}"

-        # TODO: this is just for testing!
-        if method == 'innerHTML'
-            return target.innerHTML = args[0]
         rv = target[method].apply(target, args)
         if rv == undefined
             return
@@ -29,7 +27,26 @@
         if rvID? && /^node\d+$/.test(rvID)
             if rv[@nodes.propName] == undefined
                 @nodes.add(rv, rvID)
+        #@_printMethodCall(target, method, args, rvID)
         rv
+
+    DOMPropertyUpdate : (params) ->
+        console.log "Processing DOMPropertyUpdate"
+        target = @nodes.get(params.targetID)
+        prop = params.prop
+        value = params.value
+        if /^node\d+$/.test(value)
+            value = @nodes.unscrub(value)
+        return target[prop] = value
+
+    _printMethodCall : (node, method, args, rvID) ->
+        args = @nodes.scrub(args)
+        nodeName = node.name || node.nodeName
+        argStr = ""
+        for arg in args
+            argStr += "#{arg}, "
+        argStr = argStr.replace(/,\s$/, '')
+        console.log "#{rvID} = #{nodeName}.#{method}(#{argStr})"

     #TODO: do i still need this or is this cruft?
     #TODO: at least rename to remove "Env"


Minor changes to the API. IMO cleaner messages and exceptions instead of error logs

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -29,7 +29,7 @@
         next = args.shift()

         if (typeof path != 'string')
-          return console.error("Invalid path passed to Hull.api() : ", path)
+          throw new TypeError("Invalid path passed to Hull.api() : " + JSON.stringify(path));

         while (next)
           type = typeof next
@@ -40,11 +40,10 @@
               callback = next
             else if (!errback)
               errback = next
-
           else if (type == 'object' && !params)
             params = next
           else
-            throw new Error("Invalid argument passed to Hull.api(): " + next)
+            throw new TypeError("Invalid argument passed to Hull.api(): " + next)

           next = args.shift()

@@ -52,7 +51,7 @@
         params ?= {}

         callback ?= ->
-        errback  ?= (err, data)-> console.error('Uncaught error: ', err, data)
+        errback  ?= (err, data)-> console.error('The request has failed: ', err, data)

         ret = [{ path: path, method: method, params: params }, callback, errback]


Ensure node process exit code is set

Ensure node process exit code is set

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -19,8 +19,12 @@

 module.exports = class

-  # workaround to make sure that all logger streams are flushed properly before node terminates
-  process.on 'exit', => process.exit(@exitCode)
+  # workaround to make sure that all open logger streams(i.e.: bunyan) are flushed properly before node terminates
+  @_exitCode = null
+  @_setExitCode: (code) -> @_exitCode = code
+  process.on 'exit', =>
+    console.log 'EXIT WITH CODE: ' + @_exitCode
+    process.exit(@_exitCode)

   @run: (argv) ->
     program
@@ -48,7 +52,7 @@
       .option '--categories [file]', 'XML file containing categories to import'
       .option '--safeCreate', 'If defined, importer will check for product existence (by ProductId attribute mapping) in SPHERE.IO before sending create new product request'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --mapping <file> --config [file] --products <file> --manufacturers [file] --categories [file]'
-      .action (opts) ->
+      .action (opts) =>

         validateGlobalOpts(opts, CONS.CMD_IMPORT_PRODUCTS)
         logger = createLogger(opts)
@@ -65,12 +69,12 @@
             .then (result) ->
               importer = new Products resources.options
               processSftpImport(resources, importer, 'products')
-          .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+          .then =>
+            @_setExitCode 0
+          .fail (error) =>
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1
         else # use command line arguments to load import/export files
           validateCredentialsOpts(opts, CONS.CMD_IMPORT_PRODUCTS)
           validateOpt(opts.products, 'products', CONS.CMD_IMPORT_PRODUCTS)
@@ -93,11 +97,11 @@
             importer = new Products options
             importFn(importer, opts.products, mapping, logger)
           .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+            @_setExitCode 0
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1

     program
       .command CONS.CMD_IMPORT_PRODUCTS_UPDATES
@@ -115,11 +119,11 @@
             importer = new ProductUpdates resources.options
             processSftpImport(resources, importer, 'productUpdates')
           .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+            @_setExitCode 0
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1
         else # use command line arguments to load import/export files
           validateCredentialsOpts(opts, CONS.CMD_IMPORT_PRODUCTS_UPDATES)
           validateOpt(opts.products, 'products', CONS.CMD_IMPORT_PRODUCTS_UPDATES)
@@ -131,11 +135,11 @@
             importer = new ProductUpdates options
             importFn(importer, opts.products, mapping, logger)
           .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+            @_setExitCode 0
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1

     program
       .command CONS.CMD_EXPORT_ORDERS
@@ -155,11 +159,11 @@
             exporter = new Orders resources.options
             processSftpExport(resources, exporter, 'orders')
           .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+            @_setExitCode 0
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1
         else # use command line arguments to load import/export files
           validateCredentialsOpts(opts, CONS.CMD_EXPORT_ORDERS)
           validateOpt(opts.target, 'target', CONS.CMD_EXPORT_ORDERS)
@@ -176,16 +180,16 @@
             .then ->
               # output result after order export post processing
               exporter.outputSummary()
-              @exitCode = 0
+              @_setExitCode 0
             .fail (error) ->
               exporter.outputSummary()
               logger.error error, 'Oops, something went wrong!'
               logger.error error.stack if error.stack
-              @exitCode = 1
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+              @_setExitCode 1
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1

     program
       .command CONS.CMD_IMPORT_ORDERS_STATUS
@@ -205,11 +209,11 @@
             importer = new OrderStatus resources.options
             processSftpImport(resources, importer, 'orderStatus')
           .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+            @_setExitCode 0
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1
         else # use command line arguments to load import/export files
           validateCredentialsOpts(opts, CONS.CMD_IMPORT_ORDERS_STATUS)
           validateOpt(opts.status, 'status', CONS.CMD_IMPORT_ORDERS_STATUS)
@@ -222,11 +226,11 @@
             importer = new OrderStatus options
             importFn(importer, opts.status, mapping, logger)
           .then ->
-            @exitCode = 0
-          .fail (error) ->
-            logger.error error, 'Oops, something went wrong!'
-            logger.error error.stack if error.stack
-            @exitCode = 1
+            @_setExitCode 0
+          .fail (error) ->
+            logger.error error, 'Oops, something went wrong!'
+            logger.error error.stack if error.stack
+            @_setExitCode 1

     validateOpt = (value, varName, commandName) ->
       if not value


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collidable.coffee
+++ src/collidable.coffee
@@ -12,6 +12,7 @@
       allowCollisions: ANY
       immovable: false
       touching: NONE
+      velocity: Point(0, 0)

     self.attrAccessor(
       "immovable"


Make entire non-editable socket a click target for dropdowns

--- src/controller.coffee
+++ src/controller.coffee
@@ -2159,8 +2159,8 @@
         @setTextInputFocus hitTestResult
         @redrawMain()

-      if hitTestResult.hasDropdown() and
-          mainPoint.x - @view.getViewNodeFor(hitTestResult).bounds[0].x < helper.DROPDOWN_ARROW_WIDTH
+      if hitTestResult.hasDropdown() and ((not hitTestResult.editable()) or
+          mainPoint.x - @view.getViewNodeFor(hitTestResult).bounds[0].x < helper.DROPDOWN_ARROW_WIDTH)
         @showDropdown hitTestResult

       @textInputSelecting = false


Measure font height

Measure font height

--- src/controller.coffee
+++ src/controller.coffee
@@ -155,7 +155,7 @@

       @standardViewSettings =
         padding: 5
-        indentWidth: 15
+        indentWidth: getFontHeight 15, 'Courier New'
         indentTongueHeight: 20
         tabOffset: 10
         tabWidth: 15
@@ -2751,7 +2751,7 @@
       @paletteHeader.style.fontSize = "#{fontSize}px"
       @gutter.style.fontSize = "#{fontSize}px"

-      @view.opts.textHeight = fontSize
+      @view.opts.textHeight = getFontHeight @fontFamily, @fontSize
       @view.clearCache()

       @dragView.opts.textHeight = fontSize
@@ -2762,6 +2762,8 @@
     draw._setGlobalFontFamily fontFamily

     @fontFamily = fontFamily
+
+    @view.opts.textHeight = getFontHeight @fontFamily, @fontSize

     @view.clearCache(); @dragView.clearCache()
     @gutter.style.fontFamily = fontFamily
@@ -3234,6 +3236,28 @@
     if changedBox
       @gutter.style.height = "#{Math.max @mainScroller.offsetHeight, treeView.totalBounds.height}px"

+  getFontHeight = (family, size) ->
+    testElement = document.createElement 'span'
+    testElement.innerHTML = 'Hg'
+    testElement.style.fontSize = size; testElement.style.fontFamily = family
+
+    testPartner = document.createElement 'div'
+    testPartner.style.display = 'inline-block'
+    testPartner.style.width = '1px'; testPartner.style.height = '0px'
+
+    testWrapper = document.createElement 'div'
+    testWrapper.style.left = testWrapper.style.top = '-9999px'
+
+    testWrapper.appendChild testElement; testWrapper.appendChild testPartner
+
+    document.body.appendChild testWrapper
+
+    testPartner.style.verticalAlign = 'bottom'
+    descent = testPartner.offsetTop - testElement.offsetTop
+
+    return testPartner.offsetTop - testElement.offsetTop
+
+
   # DEBUG CODE
   # ================================
   Editor::dumpNodeForDebug = (hitTestResult, line) ->


[Cleanup] Remove "Logged in as" message

[Cleanup] Remove "Logged in as" message

Due to how meteor works, it is output very often and drowns out other
console output.

--- app/lib/router.coffee
+++ app/lib/router.coffee
@@ -162,7 +162,4 @@
         @redirect loginRedirect
         pause()

-      console.log("Logged in as:")
-      console.log(Meteor.user())
-
   , { except: ['login', 'logout', 'signup', 'game', 'highscores'] }


Fix regex matching to get the environment

--- app/partials/app.cjsx
+++ app/partials/app.cjsx
@@ -70,7 +70,7 @@
   getEnv: ->
     reg = /\W?env=(\w+)/
     browser_env = window?.location?.search?.match(reg)
-    @state?.env || browser_env || 'staging'
+    @state?.env || browser_env?[1] || 'staging'

   getInitialState: ->
     user: null


SidebarMachineBox: Added selectWorkspace/deselectWorkspace methods.

--- client/Social/Activity/sidebar/sidebarmachinebox.coffee
+++ client/Social/Activity/sidebar/sidebarmachinebox.coffee
@@ -90,6 +90,19 @@
     @isListCollapsed = no

+  selectWorkspace: (workspace) ->
+
+    @expandList()
+    @deselectWorkspaces()
+    @forEachWorkspaceItem (item) ->
+      if item.getData().slug is workspace.slug
+        item.setClass 'selected'
+
+
+  deselectWorkspaces: ->
+
+    @forEachWorkspaceItem (item) -> item.unsetClass 'selected'
+

   forEachWorkspaceItem: (callback) ->


chat: in progress lock for `listPreviousReplies` method [#78432058]

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -142,22 +142,31 @@
   hasSameOwner = (a, b) -> a.getData().account._id is b.getData().account._id

-  listPreviousReplies: (event) ->
-
-    @listController.showLazyLoader()
-
-    {appManager} = KD.singletons
-    first         = @listController.getItemsOrdered().first
-    return  unless first
-
-    from         = first.getData().createdAt
-
-    @fetch {from, limit: 10}, (err, items = []) =>
-      @listController.hideLazyLoader()
-
-      return KD.showError err  if err
-
-      items.forEach @lazyBound 'loadMessage'
+  listPreviousReplies: do ->
+
+    inProgress = false
+
+    (event) ->
+
+      return  if inProgress
+
+      inProgress = true
+      @listController.showLazyLoader()
+
+      {appManager} = KD.singletons
+      first         = @listController.getItemsOrdered().first
+      return  unless first
+
+      from         = first.getData().createdAt
+
+      @fetch {from, limit: 10}, (err, items = []) =>
+        @listController.hideLazyLoader()
+
+        return KD.showError err  if err
+
+        items.forEach @lazyBound 'loadMessage'
+
+        inProgress = false

   messageAdded: (item, index) ->


Workspace: Improved pane resizing for split views.

--- client/app/CommonViews/workspace/workspacelayout.coffee
+++ client/app/CommonViews/workspace/workspacelayout.coffee
@@ -19,8 +19,13 @@
         views.push wrapper

     splitView = new SplitViewWithOlderSiblings { type, sizes, views }
-    splitView.on "ResizeDidStop", =>
-      for pane in @getDelegate().panes
-        pane.emit "PaneResized"
+    splitView.on "ResizeDidStop", => @emitResizedEventToPanes()

-    return splitView+    splitView.on "viewAppended", =>
+      splitView.resizers[0].on "DragInAction", =>
+        @emitResizedEventToPanes()
+
+    return splitView
+
+  emitResizedEventToPanes: ->
+    pane.emit "PaneResized" for pane in @getDelegate().panes


Remove CoffeeScript Gruntfile.

Remove CoffeeScript Gruntfile.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,113 +0,0 @@
-module.exports = (grunt) ->
-    @loadNpmTasks('grunt-bump')
-    @loadNpmTasks('grunt-contrib-clean')
-    @loadNpmTasks('grunt-contrib-concat')
-    @loadNpmTasks('grunt-contrib-connect')
-    @loadNpmTasks('grunt-contrib-jshint')
-    @loadNpmTasks('grunt-contrib-uglify')
-    @loadNpmTasks('grunt-contrib-watch')
-    @loadNpmTasks('grunt-jscs-checker')
-    @loadNpmTasks('grunt-karma')
-    @loadNpmTasks('grunt-ngmin')
-
-    @initConfig
-        jshint:
-            all: [ '{src,test}/**/*.js', '!src/plural.js' ]
-            options:
-                jshintrc: '.jshintrc'
-
-        jscs:
-            src:
-                options:
-                    config: '.jscs.json'
-                files:
-                    src: [ '{src,test}/**/*.js', '!src/plural.js' ]
-
-        concat:
-            dist:
-                files:
-                    'dist/angular-gettext.js': ['src/index.js', 'src/*.js']
-
-        uglify:
-            dist:
-                files:
-                    'dist/angular-gettext.min.js': 'dist/angular-gettext.js'
-
-        clean:
-            all: ['dist']
-
-        watch:
-            options:
-                livereload: true
-            all:
-                files: ['src/**.js', 'test/*/*']
-                tasks: ['build', 'karma:unit:run', 'karma:unit_nojquery:run', 'karma:e2e:run']
-            unit:
-                files: ['src/**.js', 'test/unit/*']
-                tasks: ['build', 'karma:unit:run', 'karma:unit_nojquery:run']
-            e2e:
-                files: ['src/**.js', 'test/{e2e,fixtures}/*']
-                tasks: ['build', 'karma:e2e:run']
-
-        ngmin:
-            dist:
-                files:
-                    'dist/angular-gettext.js': 'dist/angular-gettext.js'
-
-        connect:
-            e2e:
-                options:
-                    port: 9000
-                    hostname: '0.0.0.0'
-                    middleware: (connect) ->
-                        return [
-                            connect.static(__dirname)
-                        ]
-
-        karma:
-            unit:
-                configFile: 'test/configs/unit.conf.js'
-                browsers: ['PhantomJS']
-                background: true
-            unit_nojquery:
-                configFile: 'test/configs/unit-nojquery.conf.js'
-                browsers: ['PhantomJS']
-                background: true
-            unitci:
-                configFile: 'test/configs/unit.conf.js'
-                browsers: ['Firefox', 'PhantomJS']
-                singleRun: true
-                reporters: ['dots', 'junit']
-                junitReporter:
-                    outputFile: 'unit-results.xml'
-            unitci_nojquery:
-                configFile: 'test/configs/unit-nojquery.conf.js'
-                browsers: ['Firefox', 'PhantomJS']
-                singleRun: true
-                reporters: ['dots', 'junit']
-                junitReporter:
-                    outputFile: 'unit-results.xml'
-            e2e:
-                configFile: 'test/configs/e2e.conf.js'
-                browsers: ['PhantomJS']
-                background: true
-            e2eci:
-                configFile: 'test/configs/e2e.conf.js'
-                browsers: ['Firefox', 'PhantomJS']
-                singleRun: true
-                reporters: ['dots', 'junit']
-                junitReporter:
-                    outputFile: 'e2e-results.xml'
-
-        bump:
-            options:
-                files: ['package.json', 'bower.json']
-                commitFiles: ['-a']
-                pushTo: 'origin'
-
-    @registerTask 'default', ['test']
-    @registerTask 'build', ['clean', 'jshint', 'jscs', 'concat', 'ngmin', 'uglify']
-    @registerTask 'test', ['build', 'connect:e2e', 'karma:unit', 'karma:unit_nojquery', 'karma:e2e', 'watch:all']
-    @registerTask 'test_unit', ['build', 'karma:unit', 'karma:unit_nojquery', 'watch:unit']
-    @registerTask 'test_e2e', ['build', 'connect:e2e', 'karma:e2e', 'watch:e2e']
-    @registerTask 'ci', ['build', 'karma:unitci', 'karma:unitci_nojquery', 'connect:e2e', 'karma:e2eci']


Back to grunt server for local development

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -175,11 +175,12 @@
   ###
   # Alias Tasks
   #
-  # - `grunt` for local development
-  # - `grunt build` for deployment
+  # - `grunt server` for local development
+  # - `grunt build` when deploying
   ###

-  grunt.registerTask('default',   [ 'clean', 'validate', 'prepare', 'express' ])
+  grunt.registerTask('default',   [ 'validate', 'prepare' ])
+  grunt.registerTask('server',    [ 'clean', 'default', 'express' ])
   grunt.registerTask('build',     [ 'clean', 'prepare', 'optimize' ])
   grunt.registerTask('validate',  [ 'jshint' ])
   grunt.registerTask('prepare',   [ 'less', 'ngtemplates', 'concat', 'copy' ])


fixing a bug in Gruntfile that caused watches to fail

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,7 +29,7 @@
 				]

 		copy:
-			dev:
+			client:
 				files: [
 					expand: true
 					dot: true
@@ -42,7 +42,9 @@
 					cwd: appConfig.root
 					src: ['public/**']
 					dest: appConfig.tmp
-				,
+				]
+			server:
+				files: [
 					expand: true
 					dot: true
 					cwd: appConfig.root
@@ -106,7 +108,7 @@
 			options:
 				nospawn: true
 			server:
-				files: [appConfig.root + "/app/**/*.{coffee,js}", appConfig.root + "/views/**/*.jade", "!**/angular/**"]
+				files: [appConfig.root + "/app/**/*.{coffee,js,jade}", appConfig.root + "/views/**/*.jade", "!**/angular/**"]
 				tasks: ['copy:server', 'coffee:server', 'notify:watch']
 			client:
 				files: [appConfig.app + "/angular/{,*/}*.{coffee,js}"]
@@ -129,7 +131,7 @@
 				options:
 					logConcurrentOutput: true

-	grunt.registerTask 'build', ['copy:dev', 'coffee', 'less']
+	grunt.registerTask 'build', ['copy:client', 'copy:server', 'coffee', 'less']
 	grunt.registerTask 'dev', ['clean', 'build', 'concurrent:dev']
 	grunt.registerTask 'dist', ['clean', 'build', 'ngmin', 'copy:dist']


use variables for setting.

use variables for setting.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,9 +1,15 @@
 module.exports = (grunt) ->
+
+  # set variables
+  config =
+    app: 'app',
+    dist: 'dist',
+    manifest: grunt.file.readJSON('app/manifest.json'),

   # configure
   grunt.initConfig

-    pkg: grunt.file.readJSON('package.json')
+    config: config

     connect:
       server:


chore(gruntfile): fix license string in banner

Template tag was incorrect and hence rendering empty: "Licensed under the  license", presumably
since c2578cd.

Closes #949

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,7 +11,7 @@
  * <%= pkg.homepage %>\n
  *\n
  * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;\n
- * Licensed under the <%= _.pluck(pkg.licenses, "type").join(", ") %> license */'
+ * Licensed under the <%= pkg.license %> license */'
           linebreak: true
         files:
           src: ['./hammer.js','./hammer.min.js']


No more uglify since tests keep failing

No more uglify since tests keep failing

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -335,7 +335,7 @@
 		'less'
 		'ngtemplates'
 		'concat'
-		'uglify'
+		#'uglify'
 		'clean:js'
 		'cacheBust'
 		'includeSource'


Removed gruntfile task to push to gh-pages branch

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -101,9 +101,6 @@
     run "git push origin master:master"
     run "git push --tags"

-  grunt.registerTask 'publish', "Publish docs for the current release", ->
-    run "git push origin master:gh-pages"
-
   shell = require 'shelljs'
   run = (command) ->
     grunt.log.write "Running `#{command}`..."


Treat all calls to grunt.log.writeln as grunt.verbose.writeln

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -8,6 +8,9 @@
 packageJson = require './package.json'

 module.exports = (grunt) ->
+  if not grunt.option('verbose')
+    grunt.log.writeln = (args...) -> grunt.log
+
   [major, minor, patch] = packageJson.version.split('.')
   if process.platform is 'win32'
     appName = 'Atom'


Add a cache-control header

Add a cache-control header

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -61,6 +61,9 @@
         bucket: "bugsnagcdn"
         access: "public-read"
         gzip: true
+        headers: {
+          'Cache-Control': 'max-age: 315360000'
+        }

       release:
         upload: [{


add enchant.Event test

add enchant.Event test

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -118,6 +118,11 @@
           options:
             log: true
             reporter: 'Spec'
+        core:
+          src: ['tests/mocha/enchant.js/*.html']
+          options:
+            log: true
+            reporter: 'Spec'

       exec:
         whatever:


Grunt: Bump line length limit to 100

Grunt: Bump line length limit to 100

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,7 +17,7 @@
           src: ['spec/*.coffee', 'src/*.coffee', 'src/runtimes/*.coffee']
         options:
           max_line_length:
-            value: 80
+            value: 100
             level: 'warn'

     # Protocol tests


Fix testing task

Fix testing task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,6 +1,6 @@
 ###
  * grunt-choose
- * https://github.com/Leny/grunt-choose
+ * https://github.com/leny/grunt-choose
  *
  * Copyright (c) 2014 Leny
  * Licensed under the MIT license.
@@ -73,7 +73,7 @@
   grunt.loadTasks "tasks"

   grunt.registerTask "message", "Display a message", ( arg ) ->
-    grunt.log.writeln switch
+    grunt.log.writeln switch( arg )
       when "one" then "Hey ! This is task message:one !"
       when "two" then "Hey ! This is task message:two !"
       when "three" then "Hey ! This is task message:three !"


gruntfile: Remove references to fibers.

gruntfile: Remove references to fibers.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -15,13 +15,6 @@

 ###
 module.exports = (grunt) ->
-  # This is used to toggle Fiber testing
-  try
-    require.resolve 'mocha-fibers'
-    mocha_fibers = 'mocha-fibers'
-  catch err
-    mocha_fibers = null
-
   grunt.initConfig
     pkg: grunt.file.readJSON 'package.json'

@@ -80,7 +73,6 @@
           require: ['coffee-script/register', 'chai']
           reporter: 'spec'
           slow: 2
-          ui: mocha_fibers
           # bail: true  # Stop after first failure

     # Define tasks which can be executed concurrently for faster builds


Remove hardcoded package name, derive from package.json

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -196,7 +196,7 @@
                         variable: 'data'

                 modules: [
-                    name: 'cilantro'
+                    name: pkg.name
                 ]

                 # Post analysis of built files
@@ -364,14 +364,14 @@
         run "git tag #{ pkg.version }"

     grunt.registerTask 'release-binaries', 'Create a release binary for upload', ->
-        releaseDirName = "cilantro-#{ pkg.version }"
-
-        run 'rm -rf cilantro'
-        run 'mkdir -p cilantro'
-        run 'cp -r dist/* cilantro'
-        run "zip -r #{ releaseDirName }.zip cilantro"
-        run "tar -Hzcf #{ releaseDirName }.tar.gz cilantro"
-        run 'rm -rf cilantro'
+        releaseDirName = "#{ pkg.name }-#{ pkg.version }"
+
+        run "rm -rf #{ pkg.name }"
+        run "mkdir -p #{ pkg.name }"
+        run "cp -r dist/* #{ pkg.name }"
+        run "zip -r #{ releaseDirName }.zip #{ pkg.name }"
+        run "tar -Hzcf #{ releaseDirName }.tar.gz #{ pkg.name }"
+        run "rm -rf #{ pkg.name }"

     grunt.registerTask 'release-help', 'Prints the post-release steps', ->
         grunt.log.ok 'Push the code and tags: git push origin develop && git push --tags'


デバッグメッセージを削除

デバッグメッセージを削除

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,7 +31,6 @@
                     enabled: false

             ts: (filepath) ->
-                console.log filepath
                 grunt.config ['typescript', 'dev', 'src'], [filepath]
                 ['typescript:dev']


Remove module and add response

Remove module and add response

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -18,10 +18,10 @@

     resources:
       src: [
-        "src/module.coffee"
         "src/emitter.coffee"
         "src/helpers.coffee"
         "src/request.coffee"
+        "src/response.coffee"
       ]
       spec: ["spec/*.coffee"]


Remove closure library from watched dirs.

There is no reason to watch this directory.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -174,7 +174,6 @@
     esteWatch:
       options:
         dirs: [
-          'bower_components/closure-library/**/'
           'bower_components/este-library/este/**/'
           'client/**/{js,css}/**/'
           'server/**/'


wip #127

wip #127

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -171,11 +171,11 @@

       sass:
         files: '_assets/**/*.sass'
-        tasks: [ 'sass' ]
+        tasks: [ 'sass', 'cssmin' ]

       coffee:
         files: '_assets/**/*.coffee'
-        tasks: [ 'coffee' ]
+        tasks: [ 'coffee', 'uglify' ]

       jade:
         files: '_includes/**/*.jade'


Minifica Buy Button

Minifica Buy Button

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -50,6 +50,7 @@
 					'build/js/portal-quantity-selector-with-template.min.js': ['build/js/portal-quantity-selector-with-template.js']
 					'build/js/portal-accessories-selector-with-template.min.js': ['build/js/portal-accessories-selector-with-template.js']
 					'build/js/portal-price-with-template.min.js': ['build/js/portal-price-with-template.js']
+					'build/js/portal-buy-button.min.js': ['build/js/portal-buy-button.js']
 					'build/js/portal-notify-me-with-template.min.js': ['build/js/portal-notify-me-with-template.js']
 					'build/js/portal-minicart-with-template.min.js': ['build/js/portal-minicart-with-template.js']


Mark indentation issues as errors

Mark indentation issues as errors

All existing issues have been resolved.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -106,8 +106,6 @@

     coffeelint:
       options:
-        indentation:
-          level: 'warn'
         no_empty_param_list:
           level: 'error'
         max_line_length:


Remove debug task config

Remove debug task config

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -180,9 +180,7 @@
         tasks: ["shell:server", "watch"]

       dist:
-        # tasks: ["htmlmin", "xmlmin", "cssmin"]
-        tasks: ["xmlmin"]
-
+        tasks: ["htmlmin", "xmlmin", "cssmin"]

     clean: [".tmp", "<%= core.dist %>/*"]


Report jasmine errors

Report jasmine errors

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -24,7 +24,9 @@
     shell.exec 'pegjs --allowed-start-rules system,type,attribute,heading lib/syntax/parser.pegjs lib/syntax/parser.js'

   grunt.registerTask 'jasmine_node', ->
-    shell.exec './node_modules/jasmine-node/bin/jasmine-node --coffee specs/'
+    res = shell.exec './node_modules/jasmine-node/bin/jasmine-node --coffee specs/'
+    unless res.code == 0
+      grunt.util.error("jasmine tests failed")

   grunt.loadNpmTasks 'grunt-cucumber'
-  grunt.loadNpmTasks 'grunt-coffeelint'
+  grunt.loadNpmTasks 'grunt-coffeelint'


Add id to every slide

Add id to every slide

--- app/user.coffee
+++ app/user.coffee
@@ -32,7 +32,7 @@
     if !data.registered
       url = storage.getItemSync('url') + "/register/" + data.registerID
       data.registerLink = url
-    data.slides = data.slides.map (s) -> s.data
+    data.slides = data.slides.map (s, i) -> _.assign(s.data, id: i)
     return data

   deleteSlide: (id, cb) ->


Profile: Prevented "Load member list, success" event to be sent to mixpanel with each lazyload

--- client/Social/Members/AppController.coffee
+++ client/Social/Members/AppController.coffee
@@ -24,6 +24,8 @@

     super options, data

+    @once "MemberListLoaded", ->
+      KD.mixpanel "Load member list, success"

   createContentDisplay:(model, callback=->)->
     KD.singletons.appManager.setFrontApp this
@@ -125,7 +127,7 @@
           dataSource         : (selector, options, callback)=>
             group = KD.getGroup()
             group.fetchMembers selector, options, (err, res)=>
-              KD.mixpanel "Load member list, success"  unless err
+              @emit "MemberListLoaded"  unless err
               callback err, res

       sort                  :


Social: add newline

Social: add newline

--- workers/social/lib/social/models/group/validators.coffee
+++ workers/social/lib/social/models/group/validators.coffee
@@ -49,4 +49,4 @@
     {delegate} = client.connection
     roleSelector = getRoleSelector delegate, group, permission, permissionSet
     return callback null, yes  if roleSelector is -1
-    Relationship.count roleSelector, createExistenceCallback callback+    Relationship.count roleSelector, createExistenceCallback callback


:art: Make adding disposables an array

:art: Make adding disposables an array

--- atom-main.coffee
+++ atom-main.coffee
@@ -33,44 +33,52 @@
       rowHeightStream: atoms.fromConfig('atom-notational.rowHeight')
     )

-    @disposableAdd atomAdaptions
-    @disposableAdd notationalPanel
+    # Side effects
+    @disposableAdds [
+      atomAdaptions
+      notationalPanel

-    # Side effects
-    # Create panel 1st time the element is created
-    @disposableAdd notationalPanel.elementProp.onValue (el) =>
-      @panel = atom.workspace.addTopPanel(item: el) unless @panel
+      # Create panel 1st time the element is created
+      notationalPanel.elementProp.onValue (el) =>
+            @panel = atom.workspace.addTopPanel(item: el) unless @panel

-    # Persist resized body height
-    @disposableAdd notationalPanel.resizedBodyHeightProp.debounce(500).onValue (newHeight) ->
-      atom.config.set('atom-notational.bodyHeight', newHeight)
+      # Persist resized body height
+      notationalPanel.resizedBodyHeightProp.debounce(500).onValue (newHeight) ->
+        atom.config.set('atom-notational.bodyHeight', newHeight)

-    # Handle selected item
-    @disposableAdd notationalPanel.selectedItemProp.onValue (selectedItem) ->
-      # TODO: for now only preview files if the preview tabs are enabled
-      if atom.config.get('tabs.usePreviewTabs') and selectedItem
-        atom.workspace.open Path.join(selectedItem.projectPath, selectedItem.relPath), {
-          activatePane: false # keep focus in the top-pane
-        }
+      # Handle selected item
+      notationalPanel.selectedItemProp.onValue (selectedItem) ->
+        # TODO: for now only preview files if the preview tabs are enabled
+        if atom.config.get('tabs.usePreviewTabs') and selectedItem
+          atom.workspace.open Path.join(selectedItem.projectPath, selectedItem.relPath), {
+            activatePane: false # keep focus in the top-pane
+          }

-    # TODO: move sampleBy inside panel?
-    @disposableAdd notationalPanel.selectedItemProp.sampledBy(notationalPanel.openSelectedStream).onValue (selectedItem) ->
-      if selectedItem
-        atom.workspace.open Path.join(selectedItem.projectPath, selectedItem.relPath)
+      # Open item on selected event
+      # TODO: move sampleBy inside panel?
+      notationalPanel.selectedItemProp.sampledBy(notationalPanel.openSelectedStream).onValue (selectedItem) ->
+        if selectedItem
+          atom.workspace.open Path.join(selectedItem.projectPath, selectedItem.relPath)

-    # Handle panel
-    @disposableAdd notationalPanel.hideStream.onValue =>
-      @panel.hide()
-      atom.workspace.getActivePane()?.activate()
+      # Hide panel on hide evnt
+      notationalPanel.hideStream.onValue =>
+        @panel.hide()
+        atom.workspace.getActivePane()?.activate()

-    @disposableAdd focusStream().onValue =>
-      @panel.show() unless @panel.isVisible()
-      input = @panel.getItem().querySelector('.search')
-      input.select()
-      input.focus()
+      # Show panel and focus on input on event
+      focusStream().onValue =>
+        @panel.show() unless @panel.isVisible()
+        input = @panel.getItem().querySelector('.search')
+        input.select()
+        input.focus()
+    ]

+  disposableAdds: (disposalActions) ->
+    @disposableAdd(fn) for fn in disposalActions
+
   disposableAdd: (disposalAction) ->
+    throw new Error('must be a function') if typeof disposalAction isnt 'function'
     @disposables.add new Disposable(disposalAction)


Revert "Re-enable the minimap package"

Revert "Re-enable the minimap package"

This reverts commit fcac79bd1252aa02ec545034ffb4d32a64e267a9.

--- atom/config.cson
+++ atom/config.cson
@@ -21,6 +21,7 @@
   'projectHome': '/Volumes/Data/Users/Lee/Source'
   'disabledPackages': [
     'autosave'
+    'minimap'
   ]
   'ignoredNames': [
     '.git'


fix php tab length

fix php tab length

--- atom/config.cson
+++ atom/config.cson
@@ -1,5 +1,11 @@
 "*":
+  core: {}
+  editor:
+    invisibles: {}
   "exception-reporting":
     userId: "ca4ba82f-55d9-6783-63e8-7a2a1588920c"
   welcome:
     showOnStartup: false
+".html.php.text":
+  editor:
+    tabLength: 4


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -19,8 +19,7 @@
   editor:
     autoIndentOnPaste: false
     scrollPastEnd: true
-    invisibles:
-      {}
+    invisibles: {}
     fontSize: 13
   "spell-check":
     grammars: [
@@ -40,6 +39,7 @@
     layoutMode: "Compact"
   "autocomplete-plus":
     enableAutoActivation: false
+    minimumWordLength: 1
   "git-projects":
     rootPath: "~/github;~/src"
     maxDepth: 1
@@ -47,6 +47,11 @@
     lightLevelToSwitch: 5904360
   "notational-velocity":
     directory: "/Users/j/.notes"
+  tabs:
+    enableVcsColoring: true
+  "autocomplete-emojis":
+    enableUnicodeEmojis: false
+  "markdown-writer": {}
 ".git-commit.text":
   editor:
     preferredLineLength: 72


atom config changes

atom config changes

--- atom/config.cson
+++ atom/config.cson
@@ -12,7 +12,7 @@
       "monokai"
     ]
   editor:
-    fontSize: 16
+    fontSize: 13
     showIndentGuide: true
   "exception-reporting":
     userId: "a2a8db36-414a-582f-d9f1-b20f16147aa1"