Fixed problem with oauthd init

Fixed problem with oauthd init

--- src/scaffolding/init/index.coffee
+++ src/scaffolding/init/index.coffee
@@ -6,10 +6,40 @@
 async = require 'async'

 module.exports = (env) ->
+
+	installPlugins = (defer, name) ->
+		async.parallel [
+			(next) ->
+				env.plugins.install("https://github.com/william26/oauthd_default_plugin_auth", process.cwd() + "/" + name)
+					.then () ->
+						next()
+					.fail (e) ->
+						next e
+			(next) ->
+				env.plugins.install("https://github.com/william26/oauthd_default_plugin_me", process.cwd() + "/" + name)
+					.then () ->
+						next()
+					.fail (e) ->
+						next e
+			(next) ->
+				env.plugins.install("https://github.com/william26/oauthd_default_plugin_request", process.cwd() + "/" + name)
+					.then () ->
+						next()
+					.fail (e) ->
+						next e
+			(next) ->
+				env.plugins.install("https://github.com/william26/oauthd_default_plugin_front^1.0.0", process.cwd() + "/" + name)
+					.then () ->
+						next()
+					.fail (e) ->
+						next e
+		], (err) ->
+			return defer.reject err if err
+			defer.resolve(name)
+
 	continue_init = (defer, name) ->
 		schema = {
 			properties:{}
-
 		}

 		schema.properties.install_default_plugin = {
@@ -27,35 +57,7 @@
 			ncp __dirname + '/../templates/basis_structure', process.cwd() + '/' + name, (err) ->
 				return defer.reject err if err
 				if res2.install_default_plugin.match(/[yY]/)
-					async.parallel [
-						(next) ->
-							env.plugins.install("https://github.com/william26/oauthd_default_plugin_auth", process.cwd() + "/" + name)
-								.then () ->
-									next()
-								.fail (e) ->
-									next e
-						(next) ->
-							env.plugins.install("https://github.com/william26/oauthd_default_plugin_me", process.cwd() + "/" + name)
-								.then () ->
-									next()
-								.fail (e) ->
-									next e
-						(next) ->
-							env.plugins.install("https://github.com/william26/oauthd_default_plugin_request", process.cwd() + "/" + name)
-								.then () ->
-									next()
-								.fail (e) ->
-									next e
-						(next) ->
-							env.plugins.install("https://github.com/william26/oauthd_default_plugin_front^1.0.0", process.cwd() + "/" + name)
-								.then () ->
-									next()
-								.fail (e) ->
-									next e
-					], (err) ->
-						return defer.reject err if err
-						defer.resolve(name)
-
+					installPlugins defer, name
 				else
 					defer.resolve(name)

@@ -105,6 +107,8 @@
 						continue_init(defer, results.name)
 					else
 						return defer.reject new Error 'Stopped'
+			else
+				continue_init(defer, results.name)


Fix bug where the ToC failed to redraw when changing pages

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -88,7 +88,7 @@
       return results

     # Proxy page events on this object
-    _proxyChange: (eventName, page, value) ->
+    @_proxyChange: (eventName, page, value) ->
       if eventName.indexOf(':currentPage') > -1
         return

@@ -99,13 +99,13 @@
       currentPage = @get('currentPage')

       if currentPage
-        currentPage.off(null, null, content._proxyChange)
+        currentPage.off(null, null, Content._proxyChange)
         currentPage.set('active', false)

       @set('currentPage', page)

       if page
-        page.on('all', (() => @_proxyChange.apply(@, arguments)))
+        page.on('all', (() => Content._proxyChange.apply(@, arguments)))
         page.set('active', true)

         if not page.get('loaded')


Added oauth2 userinfo endpoint

Added oauth2 userinfo endpoint

--- packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.coffee
+++ packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.coffee
@@ -7,6 +7,27 @@

 WebApp.connectHandlers.use oauth2server.app
+
+oauth2server.routes.get '/oauth/userinfo', (req, res, next) ->
+	if not req.headers.authorization?
+		return res.sendStatus(401).send('No token')
+
+	accessToken = req.headers.authorization.replace('Bearer ', '')
+
+	token = oauth2server.oauth.model.AccessTokens.findOne accessToken: accessToken
+
+	user = RocketChat.models.Users.findOneById(token.userId);
+
+	res.send
+		sub: user._id
+		name: user.name
+		email: user.emails[0].address
+		email_verified: user.emails[0].verified
+		department: ""
+		birthdate: ""
+		preffered_username: user.username
+		updated_at: user._updatedAt
+		picture: "#{Meteor.absoluteUrl()}avatar/#{user.username}"

 Meteor.publish 'oauthClient', (clientId) ->


colorize

colorize

--- src/server/routes.coffee
+++ src/server/routes.coffee
@@ -283,7 +283,7 @@
             if err
                 res.status(200).end()
             else
-                util.inspect(param)
+                console.log(util.inspect(param), false,2,true));
                 param = unescape(param).split("&")
                 if param is 'INVALID TOKEN'
                     res.status(200).end()


Add some multilayer tests

Add some multilayer tests

--- test/served.coffee
+++ test/served.coffee
@@ -44,7 +44,7 @@
   it 'should find /sub/b.txt', (done) ->
     get('/sub/b.txt','this is the content of sub/b.txt', done)
   it 'should pick up correct site metadata', (done) ->
-    get('/metaTestString','site:test', done)
+    get('/metaTestString','foreground:test', done)
   it 'metadata may be in a JSON file', (done) ->
     get('/sub/showmeta','b/show.test_string', done)
   it 'should be able to run a .js file', (done) ->
@@ -73,3 +73,7 @@
     get('/.no/whey',403, done)
   it 'should serve favicon.ico (which is in the base site)', (done) ->
     get('/favicon.ico',200, done)
+  it 'should find a file in the background layer site', (done) ->
+    get('/background.txt','In the background', done)
+  it 'should find a file in the common layer site', (done) ->
+    get('/common.txt','in common', done)


Fixed a told that was 2 combined tolds

Fixed a told that was 2 combined tolds

--- scripts/told.coffee
+++ scripts/told.coffee
@@ -166,7 +166,8 @@
     "Told's spaghetti",
     "The Toldman Show",
     "Mementold",
-    "Toldega Nights: The Ballad of Ricky Toldy Battold Royale",
+    "Toldega Nights: The Ballad of Ricky Toldy",
+    "Battold Royale",
     "I'll have the toldalini Alfredo, please",
     "The Big Letoldski",
     "Tolddock Saints",


chat: retrieve norloge format from attribute instead of trying to guess it client side


kite: fix a bug where the kite channel was cycling on error

- i don't think we want that

--- client/app/MainApp/kite/kite.coffee
+++ client/app/MainApp/kite/kite.coffee
@@ -44,7 +44,6 @@

   handleBrokerSubscribed:->

-
   cycleChannel: ->
     @setStopPinging()
     @channel.off()
@@ -60,9 +59,9 @@
       callbacks   : {}
     @channel.once 'pong', callback  if callback

-  setStartPinging: -> @stopPinging = false
+  setStartPinging: -> @stopPinging = yes

-  setStopPinging: -> @stopPinging = true
+  setStopPinging: -> @stopPinging = no

   handleMessageArrived: ->
     clearTimeout @unresponsiveTimeoutId
@@ -116,9 +115,7 @@
     @channel.exchange = resourceName
     @emit 'ready'

-  handleError: (err) ->
-    @cycleChannel()
-    console.error err
+  handleError: (err) -> console.error err

   handlePong: ->
     @channel.emit 'pong'


Fix tests

Fix tests

--- client/apps/edit/test/client.coffee
+++ client/apps/edit/test/client.coffee
@@ -19,9 +19,9 @@
         _: require('underscore')
         $: benv.require('jquery')
         jQuery: benv.require('jquery')
+        sd: { ARTICLE: fixtures().articles }
       window.jQuery = jQuery
       @client = rewire '../client.coffee'
-      @client.__set__ 'sd', { USER: fixtures().users, ARTICLE: fixtures().articles }
       @client.__set__ 'EditLayout', @EditLayout = sinon.stub()
       @client.__set__ 'EditHeader', @EditHeader = sinon.stub()
       @client.__set__ 'EditAdmin', @EditAdmin = sinon.stub()
@@ -58,10 +58,10 @@
       @client.article.sections.models[3].get('images')[1].image.should.equal 'https://artsy.net/artwork2.jpg'
       done()

-  it 'converts image sets components to image_collection', ->
+  it 'keeps image sets as is', ->
     @client.init()
     _.defer =>
-      @client.article.sections.models[6].get('type').should.equal 'image_collection'
+      @client.article.sections.models[6].get('type').should.equal 'image_set'
       @client.article.sections.models[6].get('layout').should.equal 'overflow_fillwidth'
       @client.article.sections.models[6].get('images')[0].image.should.equal 'https://artsy.net/artwork.jpg'
       @client.article.sections.models[6].get('images')[0].title.should.equal 'The Four Hedgehogs'
@@ -80,4 +80,10 @@
       @client.article.sections.models[3].get('images')[0].height.should.equal 90
       @client.article.sections.models[3].get('images')[1].width.should.equal 120
       @client.article.sections.models[3].get('images')[1].height.should.equal 90
+      @client.article.sections.models[6].get('images')[0].height.should.equal 90
+      @client.article.sections.models[6].get('images')[0].height.should.equal 90
+      @client.article.sections.models[6].get('images')[1].height.should.equal 90
+      @client.article.sections.models[6].get('images')[1].height.should.equal 90
+      @client.article.sections.models[6].get('images')[2].height.should.equal 90
+      @client.article.sections.models[6].get('images')[2].height.should.equal 90
       done()


Remove duplicate user path from logout link.

--- src/scripts/views/site-menu.coffee
+++ src/scripts/views/site-menu.coffee
@@ -30,11 +30,10 @@
 			li Link href: '/login', admin
 		]
 		if @props.login.isLoggedIn
-			logoutUrl = URL.getUserPath(@props.login.user._id) + '/logout'
 			ulArgs.push(
 				li key: 'new-article', Link href: newUrl, newArticle
 				li key: 'logout', Link(
-					href: logoutUrl
+					href: '/logout'
 					onClick: @handleLogout
 					logout
 				)


NaN output on NaN inputs

NaN output on NaN inputs

--- client/plugins/method/method.coffee
+++ client/plugins/method/method.coffee
@@ -54,6 +54,8 @@

   lookup = (v) ->
     table = attach 'Tier3ExposurePercentages'
+    return NaN if isNaN v[0]
+    return NaN if isNaN v[1]
     row = _.find table, (row) ->
       asValue(row.Exposure)==v[0] and asValue(row.Raw)==v[1]
     throw new Error "can't find exposure #{v[0]} and raw #{v[1]}" unless row?


Sanitize logging

Sanitize logging

--- client/src/GestureController.coffee
+++ client/src/GestureController.coffee
@@ -186,7 +186,6 @@
                 if sign.hover.minTime?
                     if sign.hover.minTime > hand.timeVisible
                         sign_ok = false
-        console.log " Here ?"

         # pitch: up (180) and down (-180)
         # roll: left (180) and right (180)
@@ -214,19 +213,15 @@
                 #    if sign.circle.fingerCount is not amount then sign_ok = false
                 if sign.circle.minCircles?
                     if sign.circle.minCircles > gesture.progress
-                        console.log "State not cool! 1"
                         sign_ok = false
                 if sign.circle.maxCircles?
                     if sign.circle.maxCircles < gesture.progress
-                        console.log "State not cool! 2"
                         sign_ok = false
                 if sign.circle.minRadius?
                     if sign.circle.minRadius > gesture.radius
-                        console.log "State not cool! 3"
                         sign_ok = false
                 if sign.circle.maxRadius?
                     if sign.circle.maxRadius < gesture.radius
-                        console.log "State not cool! 4"
                         sign_ok = false
                 #if sign.circle.twoHanded?
                 #    if gesture.handIds[0].lenght > 2 then sign_ok = false
@@ -235,7 +230,6 @@
                 if sign.circle.clockwise is false
                     if gesture.direction > 0 then sign_ok = false
                 if gesture.state is 'stop'
-                    console.log "State not cool!"
                     sign_ok = false
             if sign.swipe
                 swipe = sign.swipe
@@ -310,7 +304,7 @@
         window.feedback.confidenceMeter confidence

         # Process signs
-        console.log "Process signs", @state.signRecord
+        #console.log "Process signs", @state.signRecord
         for sign of @state.signRecord
             #console.log "Sign: ", sign
             @updateSignRecord(sign)


fix another storage-server related potential edge case (when haven't loaded from db)

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -926,7 +926,7 @@
                     cb()
                     return
                 # assign a storage server, if there are any
-                hosts = @compute_server.storage_servers.get().keySeq().toJS()
+                hosts = @compute_server.storage_servers.get()?.keySeq().toJS() ? []
                 if hosts.length == 0
                     dbg('no storage servers')
                     cb()


project raw server: tweak cache settings to avoid excessive transfers

--- src/smc-project/raw_server.coffee
+++ src/smc-project/raw_server.coffee
@@ -63,9 +63,8 @@
                 # it sets the content type to octet-stream (aka "download me") if URL query ?download exists
                 if req.query.download?
                     res.setHeader('Content-Type', 'application/octet-stream')
-                # Disable any caching -- even cloudflare obeys these headers
-                res.setHeader('Cache-Control', 'no-store, must-revalidate')
-                res.setHeader('Expires', '0')
+                # Disable optimistic caching -- cloudflare obeys these headers
+                res.setHeader('Cache-Control', 'private, no-cache, must-revalidate')
                 return next()
             raw_server.use(base, express_index(home,  {hidden:true, icons:true}))
             raw_server.use(base, express.static(home, {hidden:true}))


updated plugin.coffee

updated plugin.coffee


fix error in rendering an error (assumed all errors are strings but they need not be)

--- src/smc-webapp/editor_course.cjsx
+++ src/smc-webapp/editor_course.cjsx
@@ -1851,6 +1851,8 @@
         </Tip>

     render_error : (name, error) ->
+        if typeof(error) != 'string'
+            error = misc.to_json(error)
         if error.indexOf('No such file or directory') != -1
             error = 'Somebody may have moved the folder that should have contained the assignment.\n' + error
         else


webapp/files: only show library on kucalc

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -835,6 +835,7 @@
     displayName : 'ProjectFiles-ProjectFilesButtons'

     propTypes :
+        kucalc       : rtypes.string
         show_hidden  : rtypes.bool
         public_view  : rtypes.bool
         show_new     : rtypes.bool
@@ -870,6 +871,17 @@
         # NOTE -- snapshots aren't available except in commercial version -- they are complicated nontrivial thing that isn't usually setup...
         <Button bsSize='small' onClick={@handle_backup}>
             <Icon name='life-saver' /> Backups
+        </Button>
+
+    render_library_button: ->
+        # library only exists on kucalc, for now.
+        return if @props.kucalc != 'yes'
+        <Button
+            bsSize={'small'}
+            disabled={@props.show_library}
+            onClick={=>@props.actions.toggle_library(true)}
+        >
+            <Icon name='book' /> <HiddenSM>Library</HiddenSM>
         </Button>

     render: ->
@@ -882,13 +894,7 @@
                 >
                     <Icon name='plus-circle' /> <HiddenSM>New</HiddenSM>
                 </Button>
-                <Button
-                    bsSize={'small'}
-                    disabled={@props.show_library}
-                    onClick={=>@props.actions.toggle_library(true)}
-                >
-                    <Icon name='book' /> <HiddenSM>Library</HiddenSM>
-                </Button>
+                {@render_library_button()}
                 <Button bsSize='small' className="upload-button">
                     <Icon name='upload' /> <HiddenSM>Upload</HiddenSM>
                 </Button>
@@ -2126,6 +2132,8 @@
             is_logged_in   : rtypes.bool
         billing :
             customer       : rtypes.object
+        customize :
+            kucalc : rtypes.string

         "#{name}" :
             active_file_sort      : rtypes.object
@@ -2516,6 +2524,7 @@
                             actions      = {@props.actions}
                             show_new     = {@props.show_new}
                             show_library = {@props.show_library}
+                            kucalc       = {@props.kucalc}
                         />
                     }


Create / remove local tmp folder for sftp specs

--- src/spec/helpers/sftp.spec.coffee
+++ src/spec/helpers/sftp.spec.coffee
@@ -1,5 +1,5 @@
-fs = require 'fs'
 Q = require 'q'
+fs = require 'q-io/fs'
 _ = require 'underscore'
 Connection = require 'ssh2'
 SftpConfig = require('../../config').config.sftp
@@ -88,10 +88,11 @@
     .done()

   it 'should download file from remote server', (done) ->
-    @helpers.getFile @_sftp, FILE_REMOTE, FILE_LOCAL_DOWNLOAD
-    .then ->
-      expect(fs.existsSync(FILE_LOCAL_DOWNLOAD)).toBe true
-      done()
+    fs.makeDirectory(FOLDER_LOCAL)
+    .then => @helpers.getFile @_sftp, FILE_REMOTE, FILE_LOCAL_DOWNLOAD
+    .then -> fs.exists(FILE_LOCAL_DOWNLOAD)
+    .then -> fs.removeTree(FOLDER_LOCAL)
+    .then -> done()
     .fail (error) -> done(error)
     .done()

@@ -128,12 +129,12 @@
   it 'should download all files from remote server', (done) ->
     fileRemoteA = "#{FOLDER_REMOTE}/testA.txt"
     fileRemoteB = "#{FOLDER_REMOTE}/testB.txt"
-    @helpers.putFile @_sftp, FILE_LOCAL, fileRemoteA
+    fs.makeDirectory(FOLDER_LOCAL)
+    .then => @helpers.putFile @_sftp, FILE_LOCAL, fileRemoteA
     .then => @helpers.putFile @_sftp, FILE_LOCAL, fileRemoteB
     .then => @helpers.downloadAllFiles @_sftp, FOLDER_LOCAL, FOLDER_REMOTE
-    .then ->
-      expect(fs.existsSync("#{FOLDER_LOCAL}/testA.txt")).toBe true
-      expect(fs.existsSync("#{FOLDER_LOCAL}/testB.txt")).toBe true
-      done()
+    .then -> Q.all [fs.exists("#{FOLDER_LOCAL}/testA.txt"), fs.exists("#{FOLDER_LOCAL}/testB.txt")]
+    .then -> fs.removeTree(FOLDER_LOCAL)
+    .then -> done()
     .fail (error) -> done(error)
     .done()


fix webdriver tests

fix webdriver tests

--- test/wd/e2e.coffee
+++ test/wd/e2e.coffee
@@ -143,7 +143,7 @@
     )
     browser.switchTo().defaultContent()
     expectedDelta = {
-      ops: [{ insert: text, attributes: { size: '32px' } }]
+      ops: [{ attributes: { size: '32px' }, insert: text }]
     }
     expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
   )
@@ -164,8 +164,6 @@
     )
     browser.switchTo().defaultContent()
     expectedDelta = {
-      startLength: 1530
-      endLength: 1530
       ops: [
         { retain: 10 }
         { retain: 20, attributes: { bold: true } }
@@ -189,8 +187,6 @@
     )
     browser.switchTo().defaultContent()
     expectedDelta = {
-      startLength: 1530
-      endLength: 1530
       ops: [
         { retain: 9 }
         { retain: 1, attributes: { align: 'center' } }


Empty template now recognized as allowing markdown editing Save saves markdown and updates the xml


feat: Add new packages to packages.cson

feat: Add new packages to packages.cson

Add file-icons, terminal-plus and chester-atom-syntax packages to
packages.cson list.

--- packages.cson
+++ packages.cson
@@ -16,4 +16,7 @@
   "pigments"
   "seti-ui"
   "wakatime"
+  "file-icons"
+  "terminal-plus"
+  "chester-atom-syntax"
 ]


test: fix test statement

test: fix test statement

--- test/usb.coffee
+++ test/usb.coffee
@@ -43,7 +43,7 @@
 		device = usb.findByIds(0x59e3, 0x0a23)

 	it 'should have sane properties', ->
-		assert.ok((device.busNumber > 0), "deviceAddress must be larger than 0")
+		assert.ok((device.busNumber > 0), "busNumber must be larger than 0")
 		assert.ok((device.deviceAddress > 0), "deviceAddress must be larger than 0")
 		assert.ok((util.isArray(device.portNumbers)), "portNumbers must be an array")


Improved syntax highlighting for assignments (no tag name)

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -4,7 +4,10 @@
 name: "Marko"
 patterns: [
   {
-    include: "#comment-concise"
+    include: "#comment-concise-line"
+  }
+  {
+    include: "#comment-concise-block"
   }
   {
     include: "#comment-html"
@@ -38,9 +41,48 @@
   }
   {
     include: "#scriptlet"
+  }
+  {
+    include: "#assignment-html"
+  }
+  {
+    include: "#assignment-concise"
   }
 ]
 repository:
+  "assignment-concise":
+    applyEndPatternLast: 1
+    begin: "^\\s*"
+    beginCaptures: {}
+    comment: "A concise tag name"
+    end: "$"
+    patterns: [
+      {
+        include: "#html-line-concise"
+      }
+      {
+        include: "#attr-stuff-concise"
+      }
+    ]
+  "assignment-html":
+    applyEndPatternLast: 1
+    begin: "(<)"
+    beginCaptures:
+      "1":
+        name: "punctuation.definition.marko-tag.begin.open.html"
+    comment: "A concise tag name"
+    end: "(/>)"
+    endCaptures:
+      "1":
+        name: "punctuation.definition.marko-tag.end.self-close.html"
+    patterns: [
+      {
+        include: "#html-line-concise"
+      }
+      {
+        include: "#attr-stuff-concise"
+      }
+    ]
   "attr-assignment-concise":
     applyEndPatternLast: 1
     begin: "(?:\\s+|[,])(?:(ref)|([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z-]*)|([_a-zA-Z0-9][a-zA-Z0-9:_.-]*))\\s*([=])\\s*"
@@ -55,7 +97,7 @@
         name: "support.function.marko-attribute"
       "5":
         name: "punctuation.separator.key-value.html"
-    end: "(?=[,;]|\\s|$)"
+    end: "(?=([,;]|\\s|$))"
     patterns: [
       {
         include: "#expression"
@@ -63,7 +105,7 @@
     ]
   "attr-assignment-html":
     applyEndPatternLast: 1
-    begin: "(?:\\s+|[,])(?:(ref)|([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z-]*)|([_a-zA-Z0-9][a-zA-Z0-9:_.-]*))\\s*([=])\\s*"
+    begin: "(?:\\s+|[,])(?:(ref)|([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z0-9-]*)|([_a-zA-Z0-9][a-zA-Z0-9:_.-]*))\\s*([=])\\s*"
     beginCaptures:
       "1":
         name: "support.function.marko-attribute"
@@ -89,9 +131,29 @@
         name: "entity.other.attribute-name.html"
       "3":
         name: "support.function.marko-attribute"
-    match: "\\s+(?:([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z-]*)|([a-zA-Z0-9][a-zA-Z0-9:_.-]+))(?=\\s|/>|>|$)"
+    match: "\\s+(?:([a-zA-Z][a-zA-Z]*)|(data-[a-zA-Z0-9-]*)|([a-zA-Z0-9][a-zA-Z0-9:_.-]+))(?=\\s|/>|>|$)"
+  "attr-semicolon":
+    begin: "([;])"
+    beginCaptures:
+      "1":
+        name: "punctuation.terminator.statement.js"
+    end: "$"
+    patterns: [
+      {
+        include: "#comment-line"
+      }
+      {
+        include: "#comment-block"
+      }
+    ]
   "attr-stuff-concise":
     patterns: [
+      {
+        include: "#comment-line"
+      }
+      {
+        include: "#comment-block"
+      }
       {
         begin: "\\s[\\[]"
         end: "[\\]]"
@@ -102,6 +164,10 @@
         ]
       }
       {
+        begin: "[,]\\s*$"
+        end: "(?=\\s)"
+      }
+      {
         include: "#attr-with-arg"
       }
       {
@@ -119,10 +185,16 @@
       {
         include: "#expression"
       }
+      {
+        include: "#attr-semicolon"
+      }
     ]
   "attr-stuff-html":
     patterns: [
       {
+        include: "#comment-block"
+      }
+      {
         include: "#attr-with-arg"
       }
       {
@@ -139,6 +211,9 @@
       }
       {
         include: "#expression-no-gt"
+      }
+      {
+        include: "#attr-semicolon"
       }
     ]
   "attr-with-arg":
@@ -168,7 +243,27 @@
       "0":
         name: "punctuation.definition.string.end.xml"
     name: "string.unquoted.cdata.xml"
-  "comment-concise":
+  "comment-block":
+    begin: "/\\*"
+    beginCaptures:
+      "0":
+        name: "punctuation.definition.comment.begin.js"
+    end: "\\*/"
+    endCaptures:
+      "0":
+        name: "punctuation.definition.comment.end.js"
+    name: "comment.block.js"
+  "comment-concise-block":
+    begin: "^(\\s*)/\\*"
+    beginCaptures:
+      "0":
+        name: "punctuation.definition.comment.begin.js"
+    end: "\\*/.*"
+    endCaptures:
+      "0":
+        name: "punctuation.definition.comment.end.js"
+    name: "comment.block.js"
+  "comment-concise-line":
     begin: "^(\\s*)//"
     comment: "Single line comment (concise)"
     end: "$"
@@ -180,6 +275,22 @@
         name: "punctuation.definition.comment.html"
     end: "-->"
     name: "comment.block.html"
+  "comment-line":
+    begin: "(^[ \\t]+)?(?=//)"
+    beginCaptures:
+      "1":
+        name: "punctuation.whitespace.comment.leading.js"
+    end: "(?!\\G)"
+    patterns: [
+      {
+        begin: "//"
+        beginCaptures:
+          "0":
+            name: "punctuation.definition.comment.js"
+        end: "\\n"
+        name: "comment.line.double-slash.js"
+      }
+    ]
   doctype:
     begin: "<!"
     captures:
@@ -591,9 +702,13 @@
       {
         include: "#tag-name-html"
       }
+      {
+        include: "#assignment-html"
+      }
     ]
   "tag-name-concise":
-    begin: "^\\s*(([a-zA-Z0-9_-]+)([.#][a-zA-Z0-9_.#-]*)?)(?=(\\s|$|\\())"
+    applyEndPatternLast: 1
+    begin: "^\\s*(([a-zA-Z0-9_-]+)([.#][a-zA-Z0-9_.#-]*)?)(?=(\\s+(?![=])|$|\\())"
     beginCaptures:
       "1":
         name: "entity.name.tag.concise"
@@ -610,7 +725,8 @@
       }
     ]
   "tag-name-custom-concise":
-    begin: "^\\s*(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|await|[a-zA-Z0-9_]+([:-])[a-zA-Z0-9_:-]*|[@][a-zA-Z0-9_]+)(?=(\\s|$|[(]))"
+    applyEndPatternLast: 1
+    begin: "^\\s*(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|await|[a-zA-Z0-9_]+([:-])[a-zA-Z0-9_:-]*|[@][a-zA-Z0-9_]+)(?=(\\s+(?![=])|$|[(]))"
     beginCaptures:
       "1":
         name: "support.function.marko-tag.concise"
@@ -720,7 +836,7 @@
       }
     ]
   "tag-name-shorthand-html":
-    begin: "(<)(([a-zA-Z0-9_-]+)([#.][a-zA-Z0-9_#.:-]+))(?=(>|/>|\\s|\\())"
+    begin: "(<)(([a-zA-Z0-9_-]+)([#.][a-zA-Z0-9_#.:-]+))(?=(>|/>|\\s+(?![=])|\\())"
     beginCaptures:
       "1":
         name: "punctuation.definition.marko-tag.begin.open.html"
@@ -820,6 +936,7 @@
       }
     ]
   "tag-script-concise":
+    applyEndPatternLast: 1
     begin: "^(\\s*)(script)(?=(\\s|$|\\())"
     beginCaptures:
       "2":
@@ -857,6 +974,7 @@
       }
     ]
   "tag-style-concise":
+    applyEndPatternLast: 1
     begin: "^(\\s*)(style)(?=(\\s|$|\\())"
     beginCaptures:
       "2":


Removed testing code.

Removed testing code.

--- test/backbone_relational/test.coffee
+++ test/backbone_relational/test.coffee
@@ -251,7 +251,6 @@
       location: 'In the middle of our street'
       occupants: new Backbone.Collection()
     })
-    house.get('occupants') # force creation of the collection

     # confirm no occupants
     view_model = kb.viewModel(house)


Fix indentation.

Fix indentation.

--- test/e2e/collectionDetailSpec.coffee
+++ test/e2e/collectionDetailSpec.coffee
@@ -1,12 +1,9 @@
 #_ = require 'lodash'
-
-#webdriver = require 'selenium-webdriver'

 expect = require('./helpers/expect')()

 Page = require './helpers/page'

-describe 'E2E Testing Collection Detail', ->
 class CollectionDetailPage extends Page
   constructor: ->
     super('/quip/breast?project=QIN_Test')
@@ -23,11 +20,11 @@
   @property correlationCharts: ->
     @findAll('.qi-correlation-chart')

-describe 'E2E Testing Collection', ->
+describe 'E2E Testing Collection Detail', ->
   page = null

   before ->
-    page = new CollectionDetailPage()
+    page = new CollectionDetailPage

   it 'should load the page', ->
     expect(page.content, 'The page was not loaded')
@@ -57,37 +54,37 @@
         expect(help, 'The {qu,sugg}estion box hyperlink is missing')
           .to.eventually.include(Page.SUGGESTION_BOX_URL)

-    # The page main body test cases.
-    describe 'Chart', ->
-    	it 'should display the X-axis dropdowns', ->
-        page.xAxisDropdowns.then (dropdowns) ->
-          # The dropdowns exist.
-          expect(dropdowns, 'The X-axis dropdowns are missing')
-            .to.exist.and.not.be.empty
-          # The dropdowns are displayed.
-          for dropdown, i in dropdowns
-            expect(dropdown.isDisplayed(),
-                   "The X-axis dropdown #{ i } is initially hidden")
-              .to.eventually.be.true
+  # The page main body test cases.
+  describe 'Chart', ->
+    it 'should display the X-axis dropdowns', ->
+      page.xAxisDropdowns.then (dropdowns) ->
+        # The dropdowns exist.
+        expect(dropdowns, 'The X-axis dropdowns are missing')
+          .to.exist.and.not.be.empty
+        # The dropdowns are displayed.
+        for dropdown, i in dropdowns
+          expect(dropdown.isDisplayed(),
+                 "The X-axis dropdown #{ i } is initially hidden")
+            .to.eventually.be.true

-    	it 'should display the Y-axis dropdowns', ->
-        page.yAxisDropdowns.then (dropdowns) ->
-          # The dropdowns exist.
-          expect(dropdowns, 'The Y-axis dropdowns are missing')
-            .to.exist.and.not.be.empty
-          # The dropdowns are displayed.
-          for dropdown, i in dropdowns
-            expect(dropdown.isDisplayed(),
-                   "The Y-axis dropdown #{ i } is initially hidden")
-              .to.eventually.be.true
+    it 'should display the Y-axis dropdowns', ->
+      page.yAxisDropdowns.then (dropdowns) ->
+        # The dropdowns exist.
+        expect(dropdowns, 'The Y-axis dropdowns are missing')
+          .to.exist.and.not.be.empty
+        # The dropdowns are displayed.
+        for dropdown, i in dropdowns
+          expect(dropdown.isDisplayed(),
+                 "The Y-axis dropdown #{ i } is initially hidden")
+            .to.eventually.be.true

-      it 'should display the correlation charts', ->
-        page.correlationCharts.then (charts) ->
-          # The charts exist.
-          expect(charts, 'The correlation charts are missing')
-            .to.exist.and.not.be.empty
-          # The charts are displayed.
-          for chart, i in charts
-            expect(chart.isDisplayed(),
-                   "The correlation chart #{ i } is initially hidden")
-              .to.eventually.be.true
+    it 'should display the correlation charts', ->
+      page.correlationCharts.then (charts) ->
+        # The charts exist.
+        expect(charts, 'The correlation charts are missing')
+          .to.exist.and.not.be.empty
+        # The charts are displayed.
+        for chart, i in charts
+          expect(chart.isDisplayed(),
+                 "The correlation chart #{ i } is initially hidden")
+            .to.eventually.be.true


Allow passing a query param to field values search


Add null check to ctx.response.body.

Add null check to ctx.response.body.

--- src/middleware/messageStore.coffee
+++ src/middleware/messageStore.coffee
@@ -38,7 +38,7 @@
 	res =
 		status: ctx.response.status
 		headers: ctx.response.header
-		body: if ctx.response.body is undefined then "" else ctx.response.body.toString()
+		body: if not ctx.response.body then "" else ctx.response.body.toString()

 	transactions.Transaction.findOneAndUpdate { _id: ctx.transactionId }, { response: res, status: status }, (err, tx) ->
 		if err


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -2,9 +2,7 @@

     @accessor 'data'

-    constructor: ->
-        #@instanceDataId = $(@node).attr('data-id')
-
+    #constructor: ->
     ready: ->
         #@testData = 123
         #@oldPieData = ''
@@ -409,5 +407,4 @@
         window.oldPieData[@instanceDataId] = window.oldPieData[@instanceDataId] or []
         window.oldPieData[@instanceDataId] = piedata
         #console.log('piedata',piedata)
-        console.log('EOF: window.oldPieData[@instanceDataId]',window.oldPieData[@instanceDataId])
         return


--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -147,13 +147,13 @@
         if totalLabel.empty()
             totalLabel = svg.append('g').attr('class', 'totalLabel')

+        console.log('@oldPieData',@oldPieData)
+        @oldPieData = 1
+        console.log('@oldPieData',@oldPieData)
+
         path = pathGroup.selectAll('path.pie').data(piedata)
         path.enter().append('path').attr('class', 'pie').attr('fill', (d, i) -> color i)

-        console.log('@oldPieData',@oldPieData)
-        @oldPieData = 1
-        console.log('@oldPieData',@oldPieData)
-
         path.transition().duration(750).attrTween('d', (d,i) ->
             theOldDataInPie = @oldPieData ? piedata
             console.log('@oldPieData',@oldPieData)


#365: parse override date strings

#365: parse override date strings

--- resource/translators/reference.coffee
+++ resource/translators/reference.coffee
@@ -86,14 +86,17 @@
   enc_date: (f) ->
     return null unless f.value

-    if f.value.literal
-      return '\\bibstring{nodate}' if f.value.literal == 'n.d.'
-      return @enc_latex(@clone(f, f.value.literal))
-
-    date = @isodate(f.value)
+    value = f.value
+    value = Zotero.BetterBibTeX.parseDateToObject(value, @item.language) if typeof f.value == 'string'
+
+    if value.literal
+      return '\\bibstring{nodate}' if value.literal == 'n.d.'
+      return @enc_latex(@clone(f, value.literal))
+
+    date = @isodate(value)
     return null unless date

-    enddate = @isodate(f.value, '_end')
+    enddate = @isodate(value, '_end')
     date += "/#{enddate}" if enddate

     return @enc_latex({value: date})
@@ -476,10 +479,12 @@
         @remove(field.name)
         continue

-      Translator.debug('override: add', field)
       field = @clone(Translator.BibLaTeXDataFieldMap[field.name], field.value) if Translator.BibLaTeXDataFieldMap[field.name]
       field.replace = true
+      Translator.debug('override: add', field)
       @add(field)
+
+    Translator.debug('override: done', @fields)

     @add({name: 'type', value: @referencetype}) if @fields.length == 0


Improved REPL usability

Improved REPL usability

Text in the REPL output can now be selected & copied. Additionally,
up/down arrows now work to cycle through previous commands.


vm: temporarily disable regions in the schema

+ those were causing us some headache because apparently the feature is errant.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -52,13 +52,14 @@
       hostKite          :
         type            : String
         default         : -> null
-      region            :
-        type            : String
-        enum            : ['unknown region'
-                          [
-                            'aws' # Amazon Web Services
-                            'sj'  # San Jose
-                          ]]
+      # region            :
+      #   type            : String
+      #   enum            : ['unknown region'
+      #                     [
+      #                       'aws' # Amazon Web Services
+      #                       'sj'  # San Jose
+      #                     ]]
+      #   default         : 'aws'
       webHome           : String
       planOwner         : String
       planCode          : String


Minor lints

Minor lints

--- client/app/views/popover_screens/main.coffee
+++ client/app/views/popover_screens/main.coffee
@@ -70,6 +70,8 @@
         else
             currentCalendar = @model.get('tags')?[0] or defaultCalendar

+
+        endOffset = if @model.isAllDay() then -1 else 0
         return data = _.extend super(),
             tFormat:     tFormat
             dFormat:     dFormat
@@ -77,8 +79,7 @@
             allDay:      @model.isAllDay()
             sameDay:     @model.isSameDay()
             start:       @model.getStartDateObject()
-            end:         @model.getEndDateObject()
-                               .add((if @model.isAllDay() then -1 else 0), 'd')
+            end:         @model.getEndDateObject().add(endOffset, 'd')
             alerts: @model.get('alarms')
             guestsButtonText: @getGuestsButtonText()
             buttonText: @getButtonText()
@@ -154,8 +155,10 @@
         delta = if @model.isAllDay() then -1 else 0
         end = @model.getEndDateObject().add(delta, 'd')

-        @$('.input-start').timepicker 'setTime', @model.getStartDateObject().format(tFormat), true, true
-        @$('.input-end-time').timepicker 'setTime', end.format(tFormat), true, true
+        @$('.input-start').timepicker 'setTime',
+            @model.getStartDateObject().format(tFormat), true, true
+        @$('.input-end-time').timepicker 'setTime',
+            end.format(tFormat), true, true
         @$('.input-end-date').val end.format(dFormat)

         @$('.input-description').val @model.get('details')


model may be omitted

model may be omitted

--- client/lanes/components/shared/ControlLabel.cjsx
+++ client/lanes/components/shared/ControlLabel.cjsx
@@ -27,7 +27,7 @@
     ]

     propTypes:
-        model: Lanes.PropTypes.State.isRequired
+        model: Lanes.PropTypes.State
         invalidMsg: React.PropTypes.string
     focusInput: (ev) ->
         _.dom(ev.target).closest('.form-group')


Deprecate ThemeManager::setEnabledThemes

Suggest direct config write instead.

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/theme-manager.coffee
+++ src/theme-manager.coffee
@@ -195,10 +195,11 @@
     # the first/top theme to override later themes in the stack.
     themeNames.reverse()

-  # Public: Set the list of enabled themes.
+  # Set the list of enabled themes.
   #
   # * `enabledThemeNames` An {Array} of {String} theme names.
   setEnabledThemes: (enabledThemeNames) ->
+    Grim.deprecate("Use `atom.config.set('core.themes', arrayOfThemeNames)` instead")
     atom.config.set('core.themes', enabledThemeNames)

   ###


Modify text per suggestion in comments

Modify text per suggestion in comments

--- core/app/backbone/factlink/text_area_view.coffee
+++ core/app/backbone/factlink/text_area_view.coffee
@@ -59,11 +59,13 @@
     textBefore = currentText.substring(0, cursorPos)
     textAfter = currentText.substring(cursorPos, currentText.length)

-    spaceBefore = if /\S$/.test(textBefore) then ' ' else ''
-    spaceAfter = if /^\S/.test(textAfter) then ' ' else ''
+    if /\S$/.test(textBefore)
+      text = ' ' + text
+    if /^\S/.test(textAfter)
+      text += ' '

     @_initAutosize()
-    @model.set 'text', textBefore + spaceBefore + text + spaceAfter + textAfter
+    @model.set 'text', textBefore + text + textAfter
     @focusInput()

   _onClick: ->


Use FollowChannelButton in NotificationView

--- core/app/backbone/views/notification_view.coffee
+++ core/app/backbone/views/notification_view.coffee
@@ -50,9 +50,9 @@

   onRender: ->
     super()
-    @addBackRegion.show new AddChannelToChannelsButtonView
-                                model: @other_channel()
-                                suggested_topics: @suggested_topics()
+    add_back_button = new FollowChannelButtonView(model: @other_channel())
+
+    @addBackRegion.show add_back_button

 class NotificationInvitedView extends GenericNotificationView
   template: "notifications/invited"


Show the setpresence status as a debug message (most people won't care about it).

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -80,7 +80,7 @@

 handle 'setpresence', (r) ->
     if not r?.presence?.available?
-        console.log "setpresence: User '#{nameof entity[r?.userId?.chatId]}' does not show his/hers/it status", r
+        console.debug "setpresence: User '#{nameof entity[r?.userId?.chatId]}' do not show their status", r
     else
         entity.setPresence r.userId.chatId, r?.presence?.available


readonly file edit improvement

readonly file edit improvement

--- client/landing/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Ace.kdapplication/AppView.coffee
@@ -94,7 +94,8 @@
         content         : """

-            The file #{@getData().name} is a readonly file.
+            The file #{@getData().name} is set to readonly,
+            you won't be able to save your changes.

         """


fixed position persistance in bar charts

--- src/viz/types/bar.coffee
+++ src/viz/types/bar.coffee
@@ -71,7 +71,7 @@
       newSize = maxSize
     else if vars[discrete].persist.position.value
       bars = divisions
-      newSize = maxSize/divisions
+      newSize = maxSize
     else
       bars = p.values.length
       if vars[discrete].persist.size.value


increase request timeout now it is outside lock

--- app/coffee/WebApiManager.coffee
+++ app/coffee/WebApiManager.coffee
@@ -3,7 +3,7 @@
 Settings = require "settings-sharelatex"

 # Don't let HTTP calls hang for a long time
-MAX_HTTP_REQUEST_LENGTH = 15000 # 15 seconds
+MAX_HTTP_REQUEST_LENGTH = 30000 # 30 seconds

 # DEPRECATED! This method of getting user details via track-changes is deprecated
 # in the way we lay out our services.


Added a very simple test which should eventually check the posting and reading of chat comments in a game. For now it simply checks if there is a chat component, which there is not yet, so the test fails.

--- app/coffee/tests/browser.coffee
+++ app/coffee/tests/browser.coffee
@@ -337,6 +337,34 @@
       @assertPointIsEmpty test, 2, 2

 registerTest new PlaceStonesTest
+
+class BasicChatTest extends BrowserTest
+  names: ['BasicChatTest']
+  description: "Very basic chat functionality test"
+  numTests: 2
+  testBody: (test) =>
+    ONE_EMAIL = 'player@one.com'
+    TWO_EMAIL = 'playa@dos.es'
+
+    clearGamesForPlayer ONE_EMAIL
+    clearGamesForPlayer TWO_EMAIL
+    createGame ONE_EMAIL, TWO_EMAIL
+
+    # -- PLAYER ONE
+    # player one logs in and gets the front page;
+    # should see a page listing games
+    createLoginSession ONE_EMAIL
+    casper.thenOpen serverUrl, =>
+      @assertNumGames test, 1, 0
+
+    # select the most recent game
+    casper.thenClick (@lastGameSelector true), =>
+      # on the game page is a table with class 'goban'
+      test.assertExists '.game-chat', 'The game chat does exist.'
+
+registerTest new BasicChatTest
+
+

 class GameInterfaceTest extends BrowserTest
   names: ['GameInterfaceTest', 'game']


Add a browser test for a game ended notification.

--- app/coffee/tests/browser.coffee
+++ app/coffee/tests/browser.coffee
@@ -1011,7 +1011,7 @@
 class ResignTest extends BrowserTest
   names: ['ResignTest', 'resign']
   description: "resignation"
-  numTests: 6
+  numTests: 7
   testBody: (test) =>
     BLACK_EMAIL = "quitter@nomo.re"
     WHITE_EMAIL = "recipient@easyw.in"
@@ -1043,11 +1043,21 @@
     casper.thenClick '.finished_games_link', ->
       gamesCount = casper.evaluate -> $('#finished_games .game-list-row').length
       test.assertEqual gamesCount, 1, "White: exactly one finished game listed"
-      test.assertSelectorHasText '.game-list-row', 'White (resignation)',
+      test.assertSelectorHasText '.game-list-row',
+        'Your game has ended, white won by resignation.'
         'Check that the White win by resignation is in the finished games list'
     casper.thenClick (@lastFinishedGameSelector()), ->
       test.assertSelectorHasText '#game-result-summary',
                                  'White won by resignation'
+
+    # White logs in an is greated by a notification that they have won a game
+    createLoginSession WHITE_EMAIL
+    casper.thenOpen serverUrl, ->
+      test.assertSelectorHasText '.unread.notification',
+        'Game Finished White by Resignation',
+        'Check that white has an unread notification'
+
+

 registerTest new ResignTest


add todo regarding filtering out null objects from select

--- app/components/eui-popup.coffee
+++ app/components/eui-popup.coffee
@@ -10,7 +10,7 @@
   options: null
   listHeight: '80'
   listRowHeight: '20'
-  searchString: ''
+  searchString: null

   selection: undefined # Option currently selected
   highlighted: undefined # Option currently highlighted
@@ -55,11 +55,13 @@

     labelPath = @get('labelPath')

+    escapedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
+    regex = new RegExp(escapedQuery, 'i')
+
     filteredOptions = options.filter (item, index, self) ->
-      return if item == null
+      return true if item == null # TODO: Weird behaviour if null item is filtered out so leaving it in for now.

       label = item.get(labelPath)
-      regex = new RegExp(query, 'i')
       regex.test(label)

     return filteredOptions


Ensure user search callback is called

Ensure user search callback is called

- Prevents the user search box from hanging in the empty state

--- app/components/user-search.cjsx
+++ app/components/user-search.cjsx
@@ -10,7 +10,9 @@
     multi: true

   searchUsers: (value, callback) ->
-    unless value is ''
+    if value is ''
+      callback null, {}
+    else
       apiClient.type('users').get search: "#{value}", page_size: 10
         .then (users) =>
           opts = for user in users


Reworked to use component state

Reworked to use component state

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -76,6 +76,8 @@
     if @props.workflow and not @props.loadingSelectedWorkflow
       @loadAppropriateClassification(@props)

+    @validateUserGroup(@props)
+
   componentWillUpdate: (nextProps, nextState) ->
     @context.geordi.remember workflowID: nextProps?.workflow?.id

@@ -93,6 +95,9 @@
         @loadAppropriateClassification(nextProps)
     if nextProps.loadingSelectedWorkflow is false and nextProps.user isnt null
       @shouldWorkflowAssignmentPrompt(nextProps)
+
+    if nextProps.location.query?.group isnt @props.location.query?.group
+      @validateUserGroup(nextProps)

   shouldWorkflowAssignmentPrompt: (nextProps) ->
     # Only for Gravity Spy which is assigning workflows to logged in users
@@ -145,8 +150,8 @@
           workflow: workflow.id
           subjects: [subject.id]

-      # if @validateUserGroup()
-      #   classification.update({ 'metadata.user_group': @props.location.query.group })
+      if @state.validUserGroup
+        classification.update({ 'metadata.user_group': @props.location.query.group })

       # If the user hasn't interacted with a classification resource before,
       # we won't know how to resolve its links, so attach these manually.
@@ -213,7 +218,8 @@
       {if @props.projectIsComplete
         <FinishedBanner project={@props.project} />}

-
+      {if @state.validUserGroup
+
You are classifying as a student of your classroom.
}
       {if @state.classification?
         <Classifier
           {...@props}
@@ -309,15 +315,11 @@
               'preferences.selected_workflow': props.preferences.settings.workflow_id
             props.preferences.save()

-  validateUserGroup: ->
-    valid = false
-    console.log(@props.location.query.group)
-    if @props.location.query?.group? and @props.user?
-      apiClient.type('user_groups').get(@props.location.query.group).then (group) =>
-        console.log(group, group and group.links.users.includes(@props.user.id))
-        valid = group and group.links.users.includes(@props.user.id)
-    valid
-
+  validateUserGroup: (props) ->
+    if props.location.query?.group? and props.user?
+      apiClient.type('user_groups').get(props.location.query.group).then (group) =>
+        console.log(group, group and group.links.users.includes(props.user.id))
+        @setState({ validUserGroup: group and group.links.users.includes(props.user.id) })

 # For debugging:
 window.currentWorkflowForProject = currentWorkflowForProject


fixes bug with incorrect handling of plain literals

--- app/scripts/services/sml.coffee
+++ app/scripts/services/sml.coffee
@@ -64,7 +64,7 @@
       columns = _.keys mapping.properties[table]
       columns = _.filter columns, (i) ->
         property = mapping.properties[table][i].prefixedName[0]
-        return (literals[property] and types[property])
+        return (literals[property] or ((litearls[property] == 'Typed Literal') and types[property]))

       properties = _.map columns, (i) ->
         property = mapping.properties[table][i].prefixedName[0]
@@ -73,8 +73,11 @@
           when 'Plain Literal' then lookup[property] = getVar(i, lookup) + ' = plainLiteral(?' + i + ')'
           when 'Typed Literal' then lookup[property] = getVar(i, lookup) + ' = typedLiteral(?' + i + ', ' + types[property] + ')'
           else ''
-
-      return _.foldl properties, ((x, y) -> (x + "\n").concat(y))
+
+      if _.isEmpty properties
+        return ''
+      else
+        return _.foldl properties, ((x, y) -> (x + '\n').concat(y))

     namespacePrefixes = (mapping) ->
       baseUris = [


Add disabledPackages and misc fixes to Atom config

--- IDE/atom/config.cson
+++ IDE/atom/config.cson
@@ -2,12 +2,25 @@
   welcome:
     showOnStartup: false
   core:
+    disabledPackages: [
+      "clang-format"
+      "AtomicChar"
+      "markdown-table-formatter"
+      "wrap"
+      "atom-ctags"
+      "terminal-status"
+      "atom-terminal-panel"
+    ]
     themes: [
       "atom-dark-ui"
       "railscast-theme"
     ]
   editor:
     softWrap: true
+    showInvisibles: true
+  "symbols-tree-view":
+    autoHide: true
+    autoToggle: true
   "atom-terminal":
     surpressDirectoryArgument: false
   minimap:


groups: show default readme

groups: show default readme

--- client/app/Applications/Groups.kdapplication/Views/readmeview.coffee
+++ client/app/Applications/Groups.kdapplication/Views/readmeview.coffee
@@ -85,9 +85,10 @@
       else
         partial =
           if err?.message then err.message
-          else "Access denied! Please join the group."
+          else @getDefaultGroupReadme group.title

-      @readme = readme?.content or partial
+      #SA: is this line being used anywhere, this overrides the logic above
+      #@readme = readme?.content or partial
       @readmeView.updatePartial @utils.applyMarkdown partial
       @highlightCode()
       JView::viewAppended.call @


no warning when hiding layers

no warning when hiding layers

--- js/layer_group.coffee
+++ js/layer_group.coffee
@@ -52,6 +52,8 @@
       klass = (echoClass for echoClass in Lysertron.Layers[@echoType] when echoClass._id is forcedEchoName)[0]
       if klass
         console.log "Forced #{@echoType}:", forcedEchoName
+      else if forcedEchoName is '_'
+        console.log "Omitting #{@echoType}"
       else
         console.error "Forced #{@echoType} not found:", forcedEchoName


client: welcome: removed legacy welcome app controller

--- client/stacks/lib/views/welcome/index.coffee
+++ client/stacks/lib/views/welcome/index.coffee
@@ -1,49 +0,0 @@
-kd                 = require 'kd'
-KDViewController   = kd.ViewController
-WelcomeAppView     = require './welcomeappview'
-collectCredentials = require 'app/util/collectCredentials'
-
-module.exports = class WelcomeAppController extends KDViewController
-
-  @options =
-    name  : 'Welcome'
-    route : '/Welcome'
-
-  constructor: (options = {}, data) ->
-
-    options.view    = new WelcomeAppView
-      cssClass      : 'content-page welcome'
-
-    super options, data
-
-
-  loadView: (appView) ->
-
-    { groupsController, computeController } = kd.singletons
-    { stacks }                              = computeController
-
-    groupsController.ready -> computeController.ready ->
-
-      { providers, variables } = collectCredentials()
-
-      groupsController.getCurrentGroup().fetchMyRoles (err, roles) ->
-        return  kd.warn err  if err
-        isAdmin = 'admin' in (roles ? [])
-        if isAdmin
-          appView.putAdminInstructions()
-        else
-          appView.putUserInstructions()
-
-
-
-
-
-        # Comment-out for now we need to plan this more ~ GG
-        #
-        # console.log { isAdmin, providers, variables }
-        #
-        # if providers.length > 0
-        #   appView.putProviderInstructions providers
-
-        # if Object.keys(variables).length > 0
-        #   appView.putVariableInstructions variables


omg, it can tell what devices it has.

omg, it can tell what devices it has.


updated ObjectID fixing to only take hex chars

--- caboose-model/lib/model.coffee
+++ caboose-model/lib/model.coffee
@@ -32,9 +32,9 @@
   @__fix_query__: (query) ->
     if Array.isArray(query)
       for q in query
-        q._id = new @ObjectID(q._id) if q._id? and typeof q._id is 'string' and /[0-9a-z]{24}/i.test(q._id)
+        q._id = new @ObjectID(q._id) if q._id? and typeof q._id is 'string' and /[0-9a-f]{24}/i.test(q._id)
     else
-      query._id = new @ObjectID(query._id) if query._id? and typeof query._id is 'string' and /[0-9a-z]{24}/i.test(query._id)
+      query._id = new @ObjectID(query._id) if query._id? and typeof query._id is 'string' and /[0-9a-f]{24}/i.test(query._id)
     query

   @first: (callback) ->


debug line removed!

debug line removed!

--- lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee
+++ lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee
@@ -72,7 +72,6 @@
       if event.is_result()
         q = if @queue[event.name]?[event.id]? then @queue[event.name][event.id] else @queue[event.id]
         q?.run_callbacks(event.success, event.data)
-        console.log q
         delete @queue[event.name][event.id] if @queue[event.name]?[event.id]?
         delete @queue[event.name] if @queue[event.name]? and Object.keys(@queue[event.name]).length == 0
         delete @queue[event.id] if @queue[event.id]?


Fix tests.

Fix tests.

--- tests/dom_tests/vomnibar_test.coffee
+++ tests/dom_tests/vomnibar_test.coffee
@@ -74,7 +74,7 @@
     @completions = [{html:'foo',type:'tab',url:'http://example.com'}]
     ui.update(true)
     stub ui, "actionFromKeyEvent", -> "down"
-    ui.onKeydown eventMock
+    ui.onKeyEvent eventMock
     assert.equal 0, ui.selection

     @completions = []


Filter Empty Strings

Filter Empty Strings

So that callbacks can respond with an empty string. Convenience.

--- help/troubleshoot/all.coffee
+++ help/troubleshoot/all.coffee
@@ -24,7 +24,7 @@
       out += "\n#{ts}"
       return iter()
     ts (help) ->
-      if help?
+      if help? and help isnt ''
         help  +=  '\n'    if help[-1..] isnt '\n'
         out   +=  '\n\n'  if out isnt ''
         out   +=  help


fix user deserialize, add logout url.

fix user deserialize, add logout url.

--- src/server/controllers/auth.coffee
+++ src/server/controllers/auth.coffee
@@ -27,10 +27,15 @@
         failureRedirect: AUTH_URL
     }))

+    app.get('/logout', (req, res) ->
+        req.logout()
+        res.redirect('/')
+    )
+
     passport.serializeUser((user, done) ->
         done(null, user.id)
     )

     passport.deserializeUser((id, done) ->
-        done(null, {id})
+        Player.find(id).complete(done)
     )


fix wrong pipedreams-3b7b API usage

fix wrong pipedreams-3b7b API usage


Add GitHub Enterprise support to github-commit-link

--- src/scripts/github-commit-link.coffee
+++ src/scripts/github-commit-link.coffee
@@ -14,8 +14,11 @@
 #     Enter your Github password when prompted. When you get a response, look for the "token" value
 #
 #     See the following for more details:
-  #     http://developer.github.com/v3/oauth/#create-a-new-authorization
+#       http://developer.github.com/v3/oauth/#create-a-new-authorization
 #       https://github.com/iangreenleaf/githubot
+#   HUBOT_GITHUB_API
+#     Optional, default is https://api.github.com. Override with
+#     http[s]://yourdomain.com/api/v3/ for Enterprise installations.
 #
 # Commands:
 #   Listens for <SHA> and links to the commit for your default repo on github
@@ -31,7 +34,8 @@
         commit_sha = msg.match[1].replace /\b/, ""
         bot_github_repo = github.qualified_repo process.env.HUBOT_GITHUB_REPO
         issue_title = ""
-        github.get "https://api.github.com/repos/#{bot_github_repo}/commits/" + commit_sha, (commit_obj) ->
+        base_url = process.env.HUBOT_GITHUB_API || 'https://api.github.com'
+        github.get "#{base_url}/repos/#{bot_github_repo}/commits/" + commit_sha, (commit_obj) ->
           url = commit_obj.url.replace(/api\./,'')
           url = url.replace(/repos\//,'')
           url = url.replace(/commits/,'commit')


allow changing sent time

allow changing sent time

--- src/smc-util/syncstring_schema.coffee
+++ src/smc-util/syncstring_schema.coffee
@@ -176,9 +176,10 @@
                 db._user_set_query_patches_check(obj, account_id, project_id, cb)
             before_change : (database, old_val, new_val, account_id, cb) ->
                 if old_val?
-                    if old_val.sent and new_val.sent and new_val.sent - 0 != old_val.sent - 0   # CRITICAL: comparing dates here!
-                        cb("you may not change the sent time once it is set")
-                        return
+                    # CRITICAL: not allowing this seems to cause a lot of problems
+                    #if old_val.sent and new_val.sent and new_val.sent - 0 != old_val.sent - 0   # CRITICAL: comparing dates here!
+                    #    cb("you may not change the sent time once it is set")
+                    #    return
                     if old_val.user? and new_val.user? and old_val.user != new_val.user
                         cb("you may not change the author of a patch from #{old_val.user} to #{new_val.user}")
                         return


webapp/jupyter2: cell is clearly not defined, this should fix it -- issue #2495

--- src/smc-webapp/jupyter/actions.coffee
+++ src/smc-webapp/jupyter/actions.coffee
@@ -265,7 +265,8 @@
     clear_selected_outputs: =>
         cells = @store.get('cells')
         for id in @store.get_selected_cell_ids_list()
-            if cells.get(id).get('output')? or cell.get('exec_count')
+            cell = cells.get(id)
+            if cell.get('output')? or cell.get('exec_count')
                 @_set({type:'cell', id:id, output:null, exec_count:null}, false)
         @_sync()


jupyter: probably fix issue with focus (not sure since can't replicate)

--- src/smc-webapp/jupyter/actions.coffee
+++ src/smc-webapp/jupyter/actions.coffee
@@ -394,6 +394,8 @@
             @setState(mode: mode)
             @set_cursor_locs([])  # none
         else if mode == 'edit'
+            # no matter what, switch to focused:
+            @focus_unlock()
             if @store.get('mode') == 'edit'
                 return
             # from escape to edit


modify: file hash check :D

modify: file hash check :D

--- tasks/gulp.config.coffee
+++ tasks/gulp.config.coffee
@@ -175,12 +175,12 @@
     if page.type == 'dir'
       g.src rootDir.src + '/import/' + page.data + '/**/*'
       .pipe $.plumber()
-      .pipe $.changed(page.output)
+      .pipe $.changed(page.output, { hasChanged: $.changed.compareSha1Digest })
       .pipe g.dest rootDir.htdocs + '/' + page.output
     else
       g.src rootDir.src + '/import/' + page.data
       .pipe $.plumber()
-      .pipe $.changed(page.output)
+      .pipe $.changed(page.output, { hasChanged: $.changed.compareSha1Digest })
       .pipe g.dest rootDir.htdocs + '/' + page.output

 # lib copy process
@@ -217,7 +217,7 @@
   g.src paths.common.img.src
   .pipe $.plumber()
   # 画像に変更がない場合、出力しない
-  .pipe $.changed(paths.common.img.dest)
+  .pipe $.changed(paths.common.img.dest, { hasChanged: $.changed.compareSha1Digest })
   .pipe $.imagemin()
   .pipe g.dest paths.common.img.dest

@@ -317,7 +317,7 @@
   g.src paths.pc.img.src
   .pipe $.plumber()
   # 画像に変更がない場合、出力しない
-  .pipe $.changed(paths.pc.img.dest)
+  .pipe $.changed(paths.pc.img.dest, { hasChanged: $.changed.compareSha1Digest })
   .pipe $.imagemin()
   .pipe g.dest paths.pc.img.dest

@@ -416,7 +416,7 @@
   g.src paths.sp.img.src
   .pipe $.plumber()
   # 画像に変更がない場合、出力しない
-  .pipe $.changed(paths.sp.img.dest)
+  .pipe $.changed(paths.sp.img.dest, { hasChanged: $.changed.compareSha1Digest })
   .pipe $.imagemin()
   .pipe g.dest paths.sp.img.dest


-Changed port information into a proper variable

--- jeffreel/app.coffee
+++ jeffreel/app.coffee
@@ -7,6 +7,7 @@
 app.engine 'coffee', require('coffeecup').__express

 user = 'jeff-gerstmann'
+port = 3000

 getShowreel = (user, page, callback) ->

@@ -45,6 +46,6 @@
 	getShowreel user, page, (broadcasts, broadcasterData) ->
 		res.render 'index', broadcasts, broadcasterData

-app.listen 3000
+app.listen port

-console.log "Listening on 3000..."
+console.log "Listening on "+port


remove unused require

remove unused require

--- lib/buffer-find-and-replace-view.coffee
+++ lib/buffer-find-and-replace-view.coffee
@@ -6,7 +6,6 @@
 SearchModel = require './search-model'
 SearchResultsView = require './search-results-view'
 ResultCounterView = require './result-counter-view'
-shell = require 'shell'

 module.exports =
 class BufferFindAndReplaceView extends View


Refactored style computation in style bindings mixin

--- packages/emberella/lib/mixins/style_bindings.coffee
+++ packages/emberella/lib/mixins/style_bindings.coffee
@@ -7,7 +7,10 @@

   createStyleString: (styleName, property) ->
     value = @get property
-    return if value is undefined
+    return unless value?
+    @defineStyleProperty styleName, value
+
+  defineStyleProperty: (styleName, value) ->
     if Ember.typeOf(value) is 'number'
       value = value + @get('unitType')
     "#{styleName}:#{value};"


Add shorthand to get the active level

Add shorthand to get the active level

--- app/application.coffee
+++ app/application.coffee
@@ -65,6 +65,9 @@
     mediator.levels         = {}
     mediator.activeLevel    = ""
     mediator.std            = null
+    mediator.getActiveLevel = =>
+      mediator.levels[mediator.activeLevel]
+
     # Seal the mediator
     mediator.seal()


SplashView: no need for additional event function

--- coffee/src/views/splash_view.coffee
+++ coffee/src/views/splash_view.coffee
@@ -7,7 +7,7 @@
   className: 'splash-view'

   events: => _.extend super(),
-    'click image': 'onClickImage'
+    'click #image': => @eventhub.trigger 'show:splash'

   template: _.template '''
     <img id="image" src="<%= image %>" class="splash-image" />
@@ -29,5 +29,3 @@
     setTimeout (-> @$('#image').objectFit 'contain'), 200

     @
-
-  onClickImage: => @eventhub.trigger 'Splash:Advance'


Fix spacing

Fix spacing

--- src/trix/models/html_parser.coffee
+++ src/trix/models/html_parser.coffee
@@ -42,7 +42,7 @@
       @containerElement.style.display = "none"
       @referenceElement.parentNode.insertBefore(@containerElement, @referenceElement.nextSibling)
     else
-      @containerElement = makeElement(tagName: "div",style: { display: "none" })
+      @containerElement = makeElement(tagName: "div", style: { display: "none" })
       document.body.appendChild(@containerElement)

   removeHiddenContainer: ->


ref-el height rebadylize fix

ref-el height rebadylize fix

--- jquery-badyl.coffee
+++ jquery-badyl.coffee
@@ -1,11 +1,11 @@
 ###
 @name jquery-badyl
 @description Meet Badyl – bootstrap affix-like wheel reinvent.
-@version 1.7.56
+@version 1.7.57
 @author Se7enSky studio <info@se7ensky.com>
 ###

-###! jquery-badyl 1.7.56 http://github.com/Se7enSky/jquery-badyl###
+###! jquery-badyl 1.7.57 http://github.com/Se7enSky/jquery-badyl###

 plugin = ($) ->

@@ -93,6 +93,7 @@
 			@badylized = no
 			@state = null

+			@$refEl.css height: ""
 			@badylHeight = @$refEl.height() + @config.offset * 2

 			@$badylInner.css


Artist was added to wrong location

Artist was added to wrong location

--- resources/assets/coffee/react/profile-page/historical.coffee
+++ resources/assets/coffee/react/profile-page/historical.coffee
@@ -54,8 +54,8 @@
             a
               className: 'beatmapset-row__title'
               href: "/s/#{bmset.beatmapset_id}"
-              title: "#{bmset.title} [#{bm.version}] "
-              "#{bmset.artist} - #{bmset.title} [#{bm.version}] "
+              title: "#{bmset.artist} #{bmset.title} [#{bm.version}] "
+              "#{bmset.title} [#{bm.version}] "
               span
                 className: 'beatmapset-row__title-small'
                 bmset.artist


Fix flickering progress bar while typing

Update the view's progress element and the rendered element in the DOM

--- src/trix/views/attachment_view.coffee
+++ src/trix/views/attachment_view.coffee
@@ -40,8 +40,8 @@

     if @attachment.isPending()
       data.trixSerialize = false
-      progressElement = makeElement("progress", class: "attachment__progress", max: 100, value: 0, "data-trix-mutable": true)
-      figure.appendChild(progressElement)
+      @progressElement = makeElement("progress", class: "attachment__progress", max: 100, value: @attachment.getUploadProgress(), "data-trix-mutable": true)
+      figure.appendChild(@progressElement)

     if href = @attachment.getHref()
       element = makeElement("a", {href})
@@ -78,5 +78,6 @@
   # Attachment delegate

   attachmentDidChangeUploadProgress: ->
-    if element = @findProgressElement()
-      element.value = @attachment.getUploadProgress()
+    value = @attachment.getUploadProgress()
+    @progressElement?.value = value
+    @findProgressElement()?.value = value


Make a difference between minutes and months. They both used to have an "m" as indicator

--- core/app/backbone/components/time_ago.coffee
+++ core/app/backbone/components/time_ago.coffee
@@ -28,7 +28,7 @@
     else if time < 1 * minutes
       "now"
     else if time < 1*hours
-      @showNr(time / minutes, 'm')
+      @showNr(time / minutes, 'min')
     else if time < 1*days
       @showNr(time / hours, 'h')
     else if time < 1*months


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -21,7 +21,7 @@
   player.I.y = -400
   player.update()

-  equals player.I.y, player.I.he / 2
+  equals player.I.y, player.I.height / 2

   player.I.x = 800
   player.update()


show progress bar when loading starts

show progress bar when loading starts

--- services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
@@ -83,6 +83,7 @@
 					if url
 						scope.loading = true
 						scope.loaded = false
+						scope.progress = 1
 						# console.log 'pdfSrc =', url
 						initializePosition()
 						flashControls()


Remove on ready state test

Remove on ready state test

--- test/test_process.coffee
+++ test/test_process.coffee
@@ -214,24 +214,6 @@
     http.cat "http://127.0.0.1:#{PORT}/", "utf8", (err, data) ->
       test.ok err

-exports.testOnReadyState = (test) ->
-  test.expect 2
-
-  process = createProcess config
-
-  process.onState 'ready', ->
-    test.ok true
-
-    process.onState 'ready', ->
-      test.ok true
-
-      process.on 'exit', ->
-        test.done()
-
-      process.quit()
-
-  process.spawn()
-
 exports.testTerminate = (test) ->
   test.expect 3


add max votes and vote count data

add max votes and vote count data

--- resources/assets/coffee/react/contest-voting/vote-summary.coffee
+++ resources/assets/coffee/react/contest-voting/vote-summary.coffee
@@ -8,7 +8,10 @@
 selectedClass = 'contest__voting-star--selected'

 export VoteSummary = ({voteCount, maxVotes}) ->
-  div null,
+  div
+    className: 'js-contest-vote-summary'
+    'data-contest-max-votes': maxVotes
+    'data-contest-vote-count': voteCount
     for i in [0...maxVotes]
       className = baseClass
       className += " #{selectedClass}" if i < voteCount


Test fix: corrected use of should to assert typeof

--- test/blob.test.coffee
+++ test/blob.test.coffee
@@ -13,8 +13,8 @@
       blob.mode.should.eql "Y"
     it "assigns @id", ->
       blob.id.should.eql "abc"
-
-
+
+
   describe "#data", ->
     describe "of a file off the root", ->
       repo = git "#{__dirname}/fixtures/branched"
@@ -24,11 +24,11 @@
           blobs[0].data (err, _data) ->
             data = _data
             done err
-
+
       it "is a string", ->
-        data.should.be.a "string"
+        data.should.be.type "string"
         data.should.include "Bla"
-
+
     describe "of a file in a subdir", ->
       repo = git "#{__dirname}/fixtures/branched"
       data = null
@@ -38,8 +38,8 @@
             blobs[0].data (err, _data) ->
               data = _data
               done err
-
+
       it "is a string", ->
-        data.should.be.a "string"
+        data.should.be.type "string"
         data.should.include "!!!"


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -62,7 +62,7 @@
   equals p1.y, 0
   equals p1.x, 20

-  p2 = Collision.rayRectangle start, rect.centered()
+  p2 = Collision.rayRectangle start, rect.centeredBounds()

   equals p1.y, 0
   equals p1.x, 20


test for object contract applied to primitives

--- test/contracts.coffee
+++ test/contracts.coffee
@@ -408,11 +408,13 @@
   eq (f {foo: "bar"}), "bar", "correct object"
   throws (-> f "string"), "string instead of an object, but should complain about missing property"

-test "object contracts on object-like builtins (currently failing)", ->
+test "object contracts on object-like primitives will blame", ->
   g :: ({toString: (Any) -> Str}) -> Str
   g = (s) -> s.toString()

-  g "foo"
+  # even though a string has `toString` proxies force us
+  # to only accept objects (and functions/arrays)
+  throws (-> g "foo"), "foo is a string but expects object"

 test "array contract ... with or", ->
   Data = ?([...Num or Str])


Tweak progress bar

Tweak progress bar

--- src/turbolinks/progress_bar.coffee
+++ src/turbolinks/progress_bar.coffee
@@ -8,10 +8,8 @@
       top: 0;
       left: 0;
       height: 3px;
-      width: 0;
       background: #0076ff;
       z-index: 9999;
-      opacity: 1;
       transition: width #{DELAY}ms ease-out, opacity #{DELAY / 2}ms ease-in;
       transform: translate3d(0, 0, 0);
     }
@@ -46,13 +44,13 @@
     document.head.insertBefore(@stylesheetElement, document.head.firstChild)

   installProgressElement: ->
-    @progressElement.style.width = ""
-    @progressElement.style.opacity = ""
+    @progressElement.style.width = 0
+    @progressElement.style.opacity = 1
     document.documentElement.insertBefore(@progressElement, document.body)

   fadeProgressElement: (callback) ->
     @progressElement.style.opacity = 0
-    setTimeout(callback, DELAY)
+    setTimeout(callback, DELAY * 1.5)

   uninstallProgressElement: ->
     document.documentElement.removeChild(@progressElement)


Revert change to function-like macro highlighting

--- grammars/csound.cson
+++ grammars/csound.cson
@@ -161,18 +161,6 @@
   macroCalls:
     patterns: [
       {
-        begin: '(\\$[A-Z_a-z]\\w*\\.?)(\\()'
-        beginCaptures:
-          1:
-            name: 'entity.name.function.preprocessor.csound'
-          2:
-            name: 'punctuation.preprocessor.csound'
-        end: '\\)'
-        endCaptures:
-          0:
-            name: 'punctuation.preprocessor.csound'
-      }
-      {
         name: 'entity.name.function.preprocessor.csound'
         match: '\\$[A-Z_a-z]\\w*(?:\\.|\\b)'
       }


Update html.coffee

Update html.coffee

--- data/tags/html.coffee
+++ data/tags/html.coffee
@@ -1,4 +1,6 @@
 id: "html"
 title: "HTML"
 parent: "language"
-desc: "超文本标记语言（Hypertext Markup Language），前端的骨架。"+desc: "超文本标记语言（Hypertext Markup Language），前端的骨架。"
+reference:
+  "ECOM HTML与CSS编码规范": "http://fe.baidu.com/doc/ecom/std/html-and-css-code-style.text"


CSS Image::offset added

CSS Image::offset added

--- impl/css/level0/image.coffee
+++ impl/css/level0/image.coffee
@@ -105,6 +105,8 @@
 		source: ''
 		image: null
 		useCssBackground: false
+		offsetX: 0
+		offsetY: 0

 	DATA: DATA

@@ -166,4 +168,20 @@
 				when 'Tile'
 					data.elemStyle.backgroundRepeat = 'repeat'

-		return+		return
+
+	setImageOffsetX: (val) ->
+		data = @_impl
+		unless data.useCssBackground
+			useCssBackground @
+		data.offsetX = val
+		data.elemStyle.backgroundPosition = "#{val}px #{data.offsetY}px"
+		return
+
+	setImageOffsetY: (val) ->
+		data = @_impl
+		unless data.useCssBackground
+			useCssBackground @
+		data.offsetY = val
+		data.elemStyle.backgroundPosition = "#{data.offsetY}px #{val}px"
+		return


Fix grid view's scrolling div height

Fix grid view's scrolling div height

--- packages/emberella/lib/views/grid_view.coffee
+++ packages/emberella/lib/views/grid_view.coffee
@@ -107,7 +107,7 @@
   totalHeight: Ember.computed ->
     contentLength = parseInt(get(@, 'content.length'), 10) || 0
     totalHeight = Math.ceil(contentLength / get(@, 'columns')) * get(@, 'rowHeight')
-    Math.max totalHeight, get(@, 'height')
+    totalHeight
   .property('content.length', 'rowHeight', 'height', 'columns')

   ###


Removed white spaces

Removed white spaces

--- admin/static/coffee/tables/table.coffee
+++ admin/static/coffee/tables/table.coffee
@@ -124,7 +124,7 @@
                         ).coerceTo('array')
                     })
             )
-
+
             # This timer keeps track of the failable index query, so we can
             # cancel it when we navigate away from the table page.
             @failable_index_timer = driver.run failable_index_query, 1000, (error, result) =>


removed references to d3/svg code from ScatterRendererView

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -516,20 +516,6 @@
     safebind(this, @mget_ref('data_source'), 'change', @request_render)

-  fill_marks : (marks) ->
-    return null
-
-  position_marks : (marks) ->
-    marks.attr('cx', ((d, i) => return @screenx[i]))
-      .attr('cy', ((d, i) => return @screeny[i]))
-    return null
-
-  get_marks : () ->
-    circles = d3.select(@el).selectAll(@model.get('mark'))
-      .data(@model.get_ref('data_source').get('data'))
-
-  get_new_marks : (marks) ->
-    return marks.enter().append(@model.get('mark'))

   calc_buffer : (data) ->
     xmapper = @model.get_ref('xmapper')
@@ -549,17 +535,15 @@
     a = new Date()
     super()
     @plot_view.ctx.clearRect(0,0, @plot_view.mget('height'), @plot_view.mget('width'))
-    circles = @get_marks()
     data = @model.get_ref('data_source').get('data')
     @calc_buffer(data)
-    @plot_view.ctx.fillStyle = 'blue'
-    @plot_view.ctx.strokeStyle = 'blue'
-
-
-    _.each(@screenx, (val, i) =>
-      @addPolygon(@screenx[i], @screeny[i])
-    )
-
+    @plot_view.ctx.fillStyle = @mget('color')
+    @plot_view.ctx.strokeStyle = @mget('color')
+
+    for idx in [0..@screeny.length]
+      @addPolygon(@screenx[idx], @screeny[idx])
+
+
     @plot_view.ctx.stroke()
     return null


[Client][Activity] remove previously committed code and change hardcoded limit as getting from config

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -159,7 +159,7 @@
         group      :
           slug     : groupObj?.slug or "koding"
           id       : groupObj.getId()
-        limit      : 50
+        limit      : KD.config.activityFetchCount
         facets     : @getActivityFilter()
         withExempt : no

@@ -203,7 +203,6 @@
       callback sanitizedCache

-  FETCHING_ATTEMPT = 0
   fetchPublicActivities:(options = {})->
     {CStatusActivity} = KD.remote.api

@@ -212,24 +211,8 @@
       if prefetchedActivity and prefetchedActivity.activities and ('activities.main' not in USEDFEEDS)
         log "exhausting feed:", "activity.main"

-        if FETCHING_ATTEMPT > 0 then USEDFEEDS.push 'activities.main'
-
-        activity = $.extend {}, KD.prefetchedFeeds["activity.main"]
-        overview = activity.overview
-        firstCount = 25
-        secondCount = 50
-
-        switch ++FETCHING_ATTEMPT
-          when 1
-            firstCount = -24
-            secondCount = 50
-          when 2
-            firstCount = -50
-            secondCount = 25
-
-        overview = overview[firstCount..secondCount]
-        activity.overview = overview
-        return @prepareCacheForListing activity
+        USEDFEEDS.push 'activities.main'
+        return @prepareCacheForListing KD.prefetchedFeeds["activity.main"]

     CStatusActivity.fetchPublicActivityFeed options, (err, cache)=>
       return @emit "activitiesCouldntBeFetched", err  if err


text update for 'story so far' and 'call to action'

--- app/lib/en.coffee
+++ app/lib/en.coffee
@@ -1032,7 +1032,7 @@

   home:
     headline: "Few have witnessed what you're about to see"
-    call_to_action: 'Experience a privileged glimpse of the distant universe as observed by the SDSS and the CTIO, and tested through state-of-the-art simulations.'
+    call_to_action: 'Experience a privileged glimpse of the distant universe as observed by the SDSS and the CTIO.'

     galaxy_zoo:
       title: 'Classify Galaxies'
@@ -1132,8 +1132,9 @@
     p5: "The second phase of Galaxy Zoo was inspired by our newfound confidence in the ability of you, our volunteer classifiers, and so we asked for a closer look at just over 200,000 of the brightest of the Sloan galaxies. We asked about the number of spiral arms, the size of the galaxies' bulges and much else besides. Once again, we were thrilled with the response (and a little more prepared for it than we were for Zoo 1!) and in the 14 months the site was up we received a little more than 60 million classifications."
     p6: '''Along the way, we added more detailed images from an area of the survey known as 'Stripe 82', but it was soon time to look at the more distant Universe. The site’s third incarnation, Galaxy Zoo: Hubble drew from surveys conducted by the Hubble Space Telescope to view earlier epochs of galaxy formation. In these surveys, which involve many, many days of dedicated observing time, we can see light from galaxies, which has taken billions of years to reach us. The idea behind Galaxy Zoo: Hubble was to be able to compare galaxies then to galaxies now, giving us a clear understanding of what factors influence their growth, whether through mergers, active black holes or simply star formation. Our publications already include several results from this phase of the project.'''
     p7: "The Galaxy Zoo site relaunched with an updated design in September 2014. In this version we combined new imaging from Sloan, giving us our best ever view of the local Universe, with the most distant images yet from Hubble's CANDELS survey. The CANDELS survey makes use of the new Wide Field Camera 3 — installed during the final shuttle mission to Hubble — to take ultra-deep images of the Universe. Our first surprise in this data was that contrary to predictions, galactic bars are still found in the most distant galaxies. Stay tuned to see what else we’ll discover!"
-    p8: '''The relaunched site also allowed us more flexibility to include smaller sets of galaxies. We have included images taken with the United Kingdom Infrared Telescope (UKIRT), in Hawai'i, for the recently completed UKIDSS project. UKIDSS is the largest, deepest survey of the sky at infrared wavelengths. The UKIDSS infrared-light pictures and the SDSS visible-light pictures are different for two reasons. Firstly, older stars are brighter in infrared wavelengths (while in visible-light we see younger stars), and secondly, infrared light, like a fog light, can penetrate the dust and smoke in the centres of galaxies. Examining the UKIDSS images in Galaxy Zoo will enable us to see how old and young stars are arranged differently in the galaxies, and will reveal more of the structure in the inner parts of galaxies.'''
-    p9: '''The data being put into Galaxy Zoo can now change quickly, thanks to the dedicated work of volunteers and the "retirement" of images that have already been completed. One of the new sets of images to be classified comes from the Dark Energy Camera Legacy Survey (DECaLS), a survey which uses DECam, one of the most sensitive and widest-area cameras ever built and a key tool in our search to measure and understand dark energy. The camera, which is mounted on the 4-m Blanco telescope at CTIO in Chile, images the sky at similar wavelengths to SDSS, but with better resolution and with the ability to detect much fainter galaxies. These images are extending the reach of the science we began with Galaxy Zoo 2 and SDSS. Finally, the site also has Galaxy Zoo's first-ever images of fully simulated galaxies; these are images that have been created entirely on a computer, using sophisticated simulations of the Universe starting just after the Big Bang and then watching as dark matter, gas, and stars evolve to form the galaxies we see today. Your classifications of these images, which come from the Illustris project, are a vital test of the physics that go into these simulations. We are asking if our most sophisticated computer models can predict the distribution of galaxy morphologies seen in the real Universe.'''
+    p8: '''The relaunched site also allowed us more flexibility to include smaller sets of galaxies. We have included images taken with the United Kingdom Infrared Telescope (UKIRT), in Hawaii, for the recently completed UKIDSS project. UKIDSS is the largest, deepest survey of the sky at infrared wavelengths. The UKIDSS infrared-light pictures and the SDSS visible-light pictures are different for two reasons. Firstly, older stars are brighter in infrared wavelengths (while in visible-light we see younger stars), and secondly, infrared light, like a fog light, can penetrate the dust and smoke in the centres of galaxies. Examining the UKIDSS images in Galaxy Zoo will enable us to see how old and young stars are arranged differently in the galaxies, and will reveal more of the structure in the inner parts of galaxies.'''
+    p9: '''The data being put into Galaxy Zoo can now change quickly, thanks to the dedicated work of volunteers and the "retirement" of images that have already been completed. One of the new sets of images to be classified comes from the Dark Energy Camera Legacy Survey (DECaLS), a survey which uses DECam, one of the most sensitive and widest-area cameras ever built and a key tool in our search to measure and understand dark energy. The camera, which is mounted on the 4-m Blanco telescope at CTIO in Chile, images the sky at similar wavelengths to SDSS, but with better resolution and with the ability to detect much fainter galaxies. These images are extending the reach of the science we began with Galaxy Zoo 2 and SDSS. Recently, the site also had Galaxy Zoo's first-ever images of fully simulated galaxies; these were images that were created entirely on a computer, using sophisticated simulations of the Universe starting just after the Big Bang and then watching as dark matter, gas, and stars evolve to form the galaxies we see today. The classifications of these images, which came from the Illustris project, are a vital test of the physics that go into these simulations.'''
+    p10: '''The images on the site today include our second set of DECaLS images, this time taken from the survey's second data release. This new data release covers a larger protion of the sky than the first. Finally, we have included a set of "lost images" from SDSS that should have been included in the Galaxy Zoo 2 sample. These images will fill in the hole that currently exists in the bright galaxy datat set.'''

   team:
     title: 'The Galaxy Zoo Team'


all dom working, starting on events

all dom working, starting on events

--- tests/unit/events.coffee
+++ tests/unit/events.coffee
@@ -34,26 +34,21 @@

     casper.then ->

-      testThing = @.evaluate ->
+      count = 0
+      @.evaluate ->
+
         junction("body").append("

")
-        __utils__.echo junction("#oneTest")	# <--- this is an HTMLDivElement
+
         junction("#oneTest").one("click", () ->
-          __utils__.echo "oneTest click"
+          count++
           junction("#oneTest").addClass("oneTest")
         )
-        __utils__.echo junction("#oneTest")	# <--- this should be an HTMLDivElement
-        return junction("#oneTest")
+        document.getElementById("oneTest").click()
+        document.getElementById("oneTest").click()

-      @.echo "This is failing because after calling .one() on the #oneTest"
-      @.echo "object, it is null. It should be returning an HTMLDivElement."
-      @.echo testThing
-      @.echo "first click"
-      @.click("#" + testThing[0].id)
-      @.echo "in between clicks"
-      @.click("#" + testThing[0].id)
-      @.echo "end of clicks"

       ".oneTest".should.be.inDOM
+      count.should.equal 1

     return

@@ -141,4 +136,3 @@
     return

   return
-


feature: query nodes by selected packages

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

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -73,10 +73,45 @@
           console.log 'fetch package '+id
           n.id = id
           $scope.package[id] = n
+          $scope.updateNodeSelection()
         , fetchError('packages')

+    updateNodeSelectionFor = (packages)->
+      # get all package ids for the list of package names
+      pids = _.chain(packages).
+        # first we get all versions for this package name
+        map( (p)-> $scope.packageByName[p].versions ).
+        flatten().
+        reject( (e)-> e is undefined ).
+        # then get the package id for each package with version
+        map( (v)-> v.id ).
+        value()
+      # get all nodes for the selected packages
+      nodes = _.chain(pids).
+        map( (pid)->
+          if $scope.package[pid]? and $scope.package[pid].nodes
+            ( n.id for n in $scope.package[pid].nodes )
+          else []
+        ).
+        flatten().
+        uniq().
+        value()
+      # only keep nodes that were selected
+      ns = _.reject $scope.allNodes, (n)->
+        if _.indexOf(nodes, n.id) >= 0
+          return false
+        true
+      # update the model of nodes subset
+      $scope.nodes = ns
+
     $scope.updateNodeSelection = ()->
-      $scope.nodes = $scope.allNodes
+      # get all selected packages
+      packages =
+        ( k for k,isSelected of $scope.packageSelected when isSelected )
+      if packages.length is 0
+        $scope.nodes = $scope.allNodes
+      else
+        updateNodeSelectionFor(packages)

     updatePackageSelectionFor = (nodes)->
       # get all packages for these nodes


show result of postting time entry in log.

--- coffee/services/redmine.coffee
+++ coffee/services/redmine.coffee
@@ -243,9 +243,11 @@
     config.headers = "Content-Type": "application/xml"
     @$http(config)
       .success((args...) =>
+        @Log.info("Time Entry Posted.\t account:#{@auth.name}\tid:#{@_timeEntryData.time_entry.issue_id}\thours:#{@_timeEntryData.time_entry.hours}")
         @Analytics.sendEvent 'internal', 'submitTime', 'success', @_timeEntryData.time_entry.hours
         success(args...))
       .error((args...) =>
+        @Log.info("Time Entry Post Failed.\t account:#{@auth.name}\tid:#{@_timeEntryData.time_entry.issue_id}\thours:#{@_timeEntryData.time_entry.hours}")
         @Log.debug args
         @Analytics.sendException("Error: submitTime")
         error(args...))


Shell: removed thumbnailLink

Shell: removed thumbnailLink

--- coffee/src/shells/shell.coffee
+++ coffee/src/shells/shell.coffee
@@ -21,9 +21,6 @@

 class Shell.Model extends Backbone.Model
-
-  # Intended to be overriden by derived classes.
-  thumbnailLink: => NotImplementedError 'Shell::thumbnailLink'

   # disable Backbone's sync functionality
   sync: => NotSupportedError 'Backbone::sync'


[grunt] store config of `grunt-nunjucks-2-html` in `taskConfig` object; switchable off human readable urls

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -5,6 +5,37 @@
 ###

 module.exports = (grunt) ->
+  taskConfig =
+    autoescape          : false
+    data                : '<%= data %>'
+    path:
+      build             : grunt.config('path.build.root')
+      layouts           : grunt.config('path.source.layouts')
+      nunjucksEnv       : grunt.config('path.source.layouts')
+      locales           : grunt.config('path.source.locales')
+    files:
+      cwd               : '<%= path.source.layouts %>/'
+      src               : ['{,**/}*.{nj,html}', '!{,**/}_*.{nj,html}']
+      dest              : '<%= path.build.root %>/'
+      ext               : '.html'
+    humanReadableUrls:
+      enabled           : true
+      exclude           : /^(index|\d{3})$/
+    i18n:
+      locales           : grunt.config('data.site.locales')
+      baseLocale        : grunt.config('data.site.baseLocale')
+      defaultDomain     : 'messages'
+    numberDefaultFormat : '0,0[.]00'
+    urlify:
+      addEToUmlauts     : true
+      szToSs            : true
+      spaces            : '-'
+      toLower           : true
+      nonPrintable      : '-'
+      trim              : true
+      failureOutput     : 'non-printable-url'
+
+
   _            = require('lodash')
   path         = require('path')
   numbro       = require('numbro')
@@ -17,23 +48,23 @@
   marked       = require('marked')
   markdown     = require('nunjucks-markdown')
   urlify       = require('urlify').create({
-    addEToUmlauts: true
-    szToSs: true
-    spaces: '-'
-    toLower: true
-    nonPrintable: '-'
-    trim: true
-    failureOutput: 'non-printable-url'
+    addEToUmlauts : taskConfig.urlify.addEToUmlauts
+    szToSs        : taskConfig.urlify.szToSs
+    spaces        : taskConfig.urlify.spaces
+    toLower       : taskConfig.urlify.toLower
+    nonPrintable  : taskConfig.urlify.nonPrintable
+    trim          : taskConfig.urlify.trim
+    failureOutput : taskConfig.urlify.failureOutput
   })

-  locales       = grunt.config('data.site.locales')
-  baseLocale    = grunt.config('data.site.baseLocale')
-
-  defaultDomain = 'messages'
-
-  buildDir      = grunt.config('path.build.root')
-  layoutsDir    = grunt.config('path.source.layouts')
-  localesDir    = grunt.config('path.source.locales')
+  locales       = taskConfig.i18n.locales
+  baseLocale    = taskConfig.i18n.baseLocale
+  defaultDomain = taskConfig.i18n.defaultDomain
+
+  buildDir      = taskConfig.path.build
+  layoutsDir    = taskConfig.path.layouts
+  localesDir    = taskConfig.path.locales
+

   ###*
    * Replace placeholders with provided values via `sprintf` or `vsprintf`. Function will choice
@@ -60,7 +91,7 @@
    * @return {string} Renamed path
   ###
   humanReadableUrl = (pagepath) ->
-    exclude  = /^(index|\d{3})$/
+    exclude  = taskConfig.humanReadableUrls.exclude

     ext      = path.extname(pagepath)
     basename = path.basename(pagepath, ext)
@@ -118,10 +149,10 @@
       @[currentLocale] = {}

       @[currentLocale].options =
-        paths: '<%= path.source.layouts %>/'
-        autoescape: false
-        data:  '<%= data %>'
-        configureEnvironment: (env) ->
+        paths                : taskConfig.path.nunjucksEnv
+        autoescape           : taskConfig.autoescape
+        data                 : taskConfig.data
+        configureEnvironment : (env) ->
           # ==========
           # Extensions
           # ==========
@@ -383,12 +414,12 @@
            * @todo There are few issues with current lib:
            *       * https://github.com/foretagsplatsen/numbro/issues/111
            *       * https://github.com/foretagsplatsen/numbro/issues/112
-           * @param {number} value                  Number which should be formatted
-           * @param {string} format = '0,0[.]00'    Pattern as per http://numbrojs.com/format.html
-           * @param {string} locale = currentLocale Locale name as per https://github.com/foretagsplatsen/numbro/tree/master/languages
+           * @param {number} value                                   Number which should be formatted
+           * @param {string} format = taskConfig.numberDefaultFormat Pattern as per http://numbrojs.com/format.html
+           * @param {string} locale = currentLocale                  Locale name as per https://github.com/foretagsplatsen/numbro/tree/master/languages
            * @return {string} Formatted number
           ###
-          env.addFilter 'number', (value, format = '0,0[.]00', locale = currentLocale) ->
+          env.addFilter 'number', (value, format = taskConfig.numberDefaultFormat, locale = currentLocale) ->
             numbro.setLanguage(locale)
             numbro(value).format(format)

@@ -429,12 +460,13 @@

       @[currentLocale].files =  [
         expand: true
-        cwd: '<%= path.source.layouts %>/'
-        src: ['{,**/}*.{nj,html}', '!{,**/}_*.{nj,html}']
-        dest: '<%= path.build.root %>/' + localeDir
-        ext: '.html'
+        cwd: taskConfig.files.cwd + '/'
+        src: taskConfig.files.src
+        dest: taskConfig.files.dest + '/' + localeDir
+        ext: taskConfig.files.ext
         rename: (dest, src) =>
-          src = humanReadableUrl(src)
+          if taskConfig.humanReadableUrls.enabled
+            src = humanReadableUrl(src)
           path.join(dest, src)
       ]


[grunt] fix `_d()` Nunjucks' global function

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -186,13 +186,13 @@
           ###*
            * Expose `moment.js` to Nunjucks' for parsing, validation, manipulation, and displaying dates
            * @tutorial http://momentjs.com/docs/
-           * @param {string} locale = currentLocale Locale name, which should be used by default
-           * @param {*}      param...               Any parameters, which should be passed to `moment.js`
+           * @note Will set locale to `currentLocale` before running. To override use `_d(...).locale('de').format(...)`
+           * @param {*} param... Any parameters, which should be passed to `moment.js`
            * @return {moment} `moment.js` expression for further use
           ###
-          env.addGlobal '_d', (locale = currentLocale, params...) ->
-            moment.locale(locale);
-            moment.apply params
+          env.addGlobal '_d', (params...) ->
+            moment.locale(currentLocale);
+            moment.apply null, params

           # --------------
           # i18n functions


Fixes alternating load issue.

Fixes alternating load issue.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -34,6 +34,7 @@
       @secondChar = null

   clearJumpMode: ->
+      @jumpMode = !@jumpMode
       @clearKeys()
       $('#status-bar-jumpy').html("")
       atom.workspaceView.eachEditorView (e) -> e.removeClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'


Added MD File Extension

Added MD File Extension

Cause you know, Often READMEs take up quite the amount - hey?

--- lib/line-count.coffee
+++ lib/line-count.coffee
@@ -42,8 +42,11 @@
   "ld"
   "lua"
   "ls"
+  "m"
+  "md"
   "ml"
   "mli"
+  "mm"
   "mochi"
   "monkey"
   "mustache"
@@ -68,8 +71,6 @@
   "vue"
   "xml"
   "yaml"
-  "m"
-  "mm"
 ]

 pad = (num, w) ->


:fire: Remove old linter

:fire: Remove old linter

--- lib/linter-old.coffee
+++ lib/linter-old.coffee
@@ -1,128 +0,0 @@
-Path = require 'path'
-{CompositeDisposable} = require 'atom'
-
-class LinterTrace
-  constructor: (@Message, @File, @Position) ->
-
-class LinterMessage then constructor: (@Message, @File, @Position, @Trace) ->
-
-class LinterError extends LinterMessage
-
-class LinterWarning extends LinterMessage
-
-class Linter
-
-  Subscriptions: null
-  SubLintOnFly: null
-  InProgress: false
-  InProgressFly: false
-  LintOnFly: true
-  View: null
-  ViewPanel: null
-  StatusBar: null
-  Messages:[]
-  MessagesRegular:[]
-  MessagesFly:[]
-  Linters: []
-
-  constructor: ->
-    @View = new (require './view')(this)
-    @ViewPanel = atom.workspace.addBottomPanel item: @View.root, visible: false
-
-    @Subscriptions = new CompositeDisposable
-    @SubLintOnFly = new CompositeDisposable
-
-    @Subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
-      return unless atom.workspace.getActiveTextEditor()
-      @lint()
-
-    @Subscriptions.add atom.workspace.observeTextEditors (editor) =>
-      return unless editor.getPath()
-      editor.onDidSave(@lint.bind(@, false))
-      @Subscriptions.add editor.onDidChangeCursorPosition ({newBufferPosition}) =>
-        @View.updateBubble(newBufferPosition)
-      return unless @LintOnFly
-      @SubLintOnFly.add editor.onDidStopChanging @lint.bind(@, true)
-  #
-  # We have two types of linters, Regular and OnFly, if we store both of their
-  # messages in one array, we'll lose the messages of regular ones, once we do
-  # lintOnChange, so therefore I've modified the lint function to execute only the
-  # ones that are required and then concat with the other.
-  # When the save event is triggered, the onChange is false so it triggers the onFly
-  # ones too.
-  #
-  lint: (onChange) ->
-    onChange = Boolean onChange
-    return if @progress onChange
-    # We need to consume both onFly and Regular linters on save
-    @lint true unless onChange
-
-    ActiveEditor = atom.workspace.getActiveTextEditor()
-    Buffer = ActiveEditor.getBuffer()
-    return unless ActiveEditor
-
-    Scopes = ActiveEditor.scopeDescriptorForBufferPosition(ActiveEditor.getCursorBufferPosition()).scopes
-    Promises = []
-
-    @Linters.forEach (Linter) ->
-      return if onChange and not Linter.lintOnFly
-      return if (not onChange) and Linter.lintOnFly
-      Matching = Scopes.filter (Entry) -> Linter.scopes.indexOf(Entry) isnt -1
-      return unless Matching.length
-      Promises.push Linter.lint(
-        ActiveEditor, Buffer,
-        {
-          Error: LinterError,
-          Warning: LinterWarning,
-          Trace: LinterTrace
-        },
-        onChange
-      )
-
-    Promise.all(Promises).then (Results) =>
-      @progress onChange, false
-      Messages = []
-      for Result in Results
-        continue if (not Result) or (typeof Result) isnt 'object'
-        if Result instanceof Array
-          Messages = Messages.concat(Result)
-      if onChange
-        @MessagesFly = Messages
-      else
-        @MessagesRegular = Messages
-      @Messages = @MessagesFly.concat(@MessagesRegular)
-      @render()
-    , =>
-      console.error arguments
-      @progress onChange, false
-
-  render: ->
-    # Update `LeftTile` of `StatusBar`
-    @View.updateLeftTile @Messages.length
-
-    if not @Messages.length
-      @ViewPanel.hide() if @ViewPanel.isVisible()
-      @View.remove()
-    else
-      @View.update()
-      @ViewPanel.show() unless @ViewPanel.isVisible()
-
-  deactivate: ->
-    @ViewPanel.destroy()
-    @View.remove()
-    @SubLintOnFly.dispose()
-    @Subscriptions.dispose()
-
-  progress: (onChange, newValue) ->
-    if typeof newValue is 'undefined'
-      if onChange
-        return @InProgressFly
-      else
-        return @InProgress
-    else
-      if onChange
-        @InProgressFly = newValue
-      else
-        @InProgress = newValue
-
-module.exports = Linter