Domains: Added websocket

Domains: Added websocket

--- app/scripts/controllers/domains.coffee
+++ app/scripts/controllers/domains.coffee
@@ -1,13 +1,17 @@
 'use strict'

 class DomainsCtrl
-  constructor: (@scope, @DomainsFcty, @MostCommonViolationsFcty) ->
+  constructor: (@scope, @DomainsFcty, @MostCommonViolationsFcty, @WebSocketFcty) ->
     @domainsVisible = false
     @groupsVisible = true
     @mostFrequentVisible = false

     @getDomainData()
     @getMostCommonViolations()
+
+    @WebSocketFcty.on((message) =>
+      @getDomainData()
+    )

   _fillDomains: (domains) =>
     @domains = domains
@@ -36,6 +40,6 @@

 angular.module('holmesApp')
-  .controller 'DomainsCtrl', ($scope, DomainsFcty, MostCommonViolationsFcty) ->
+  .controller 'DomainsCtrl', ($scope, DomainsFcty, MostCommonViolationsFcty, WebSocketFcty) ->

-    $scope.model = new DomainsCtrl($scope, DomainsFcty, MostCommonViolationsFcty)
+    $scope.model = new DomainsCtrl($scope, DomainsFcty, MostCommonViolationsFcty, WebSocketFcty)


Add gone with the wind

Add gone with the wind

--- scripts/movie-quotes.coffee
+++ scripts/movie-quotes.coffee
@@ -4,5 +4,8 @@
 #

 module.exports = (robot) ->
-  robot.hear /.*one (in|out of) a million*/, (msg) ->
+  robot.hear /.*one (in|out of) a million*/i, (msg) ->
     msg.send "so you're telling me there's a chance: http://www.youtube.com/watch?v=yCFB2akLh4s"
+
+  robot.hear /.*I don't give.*/i, (msg) ->
+    msg.send "relevant: http://www.youtube.com/watch?v=-6Pbc8SQwV8"


require fs in fs util spec so that it will pass

--- spec/fs-util-spec.coffee
+++ spec/fs-util-spec.coffee
@@ -1,3 +1,4 @@
+fs = require 'fs'
 fsUtil = require '../lib/fs-util'
 path = require 'path'


Only call getFileStream callback once

Only call getFileStream callback once

--- app/coffee/FSPersistorManager.coffee
+++ app/coffee/FSPersistorManager.coffee
@@ -27,7 +27,10 @@
         return callback err
       @sendFile location, target, fsPath, callback

-  getFileStream: (location, name, callback = (err, res)->)->
+  getFileStream: (location, name, _callback = (err, res)->) ->
+    callback = (args...) ->
+      _callback(args...)
+      _callback = () ->
     filteredName = filterName name
     logger.log location:location, name:filteredName, "getting file"
     sourceStream = fs.createReadStream "#{location}/#{filteredName}"
@@ -38,6 +41,8 @@
       else
         callback err
     sourceStream.on 'readable', () ->
+      # This can be called multiple times, but the callback wrapper
+      # ensures the callback is only called once
       callback null, sourceStream


Revert "Make S3 Key/Secret pair optional."

This reverts commit 65122e603099a4f9ef5c5186b7eb8832340a993f.

knox requires the key and secret arguments.

--- app/coffee/S3PersistorManager.coffee
+++ app/coffee/S3PersistorManager.coffee
@@ -16,8 +16,8 @@
 buildDefaultOptions = (bucketName, method, key)->
 	return {
 			aws:
-				key: settings.filestore.s3?.key
-				secret: settings.filestore.s3?.secret
+				key: settings.filestore.s3.key
+				secret: settings.filestore.s3.secret
 				bucket: bucketName
 			method: method
 			timeout: thirtySeconds
@@ -28,10 +28,10 @@

 	sendFile: (bucketName, key, fsPath, callback)->
 		s3Client = knox.createClient
-			key: settings.filestore.s3?.key
-			secret: settings.filestore.s3?.secret
+			key: settings.filestore.s3.key
+			secret: settings.filestore.s3.secret
 			bucket: bucketName
-		putEventEmitter = s3Client.putFile fsPath, key, (err, res)->
+		putEventEmiter = s3Client.putFile fsPath, key, (err, res)->
 			if err?
 				logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath,"something went wrong uploading file to s3"
 				return callback(err)
@@ -44,8 +44,8 @@
 			LocalFileWriter.deleteFile fsPath, (err)->
 				logger.log res:res,  bucketName:bucketName, key:key, fsPath:fsPath,"file uploaded to s3"
 				callback(err)
-		putEventEmitter.on "error", (err)->
-			logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath, "error received uploading file to s3"
+		putEventEmiter.on "error", (err)->
+			logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath, "error emmited on put of file"
 			callback err

 	sendStream: (bucketName, key, readStream, callback)->
@@ -62,8 +62,8 @@
 		callback = _.once callback
 		logger.log bucketName:bucketName, key:key, "getting file from s3"
 		s3Client = knox.createClient
-			key: settings.filestore.s3?.key
-			secret: settings.filestore.s3?.secret
+			key: settings.filestore.s3.key
+			secret: settings.filestore.s3.secret
 			bucket: bucketName
 		s3Stream = s3Client.get(key)
 		s3Stream.end()
@@ -76,12 +76,12 @@
 	copyFile: (bucketName, sourceKey, destKey, callback)->
 		logger.log bucketName:bucketName, sourceKey:sourceKey, destKey:destKey, "copying file in s3"
 		s3Client = knox.createClient
-			key: settings.filestore.s3?.key
-			secret: settings.filestore.s3?.secret
+			key: settings.filestore.s3.key
+			secret: settings.filestore.s3.secret
 			bucket: bucketName
 		s3Client.copyFile sourceKey, destKey, (err)->
 			if err?
-				logger.err err:err, bucketName:bucketName, sourceKey:sourceKey, destKey:destKey, "something went wrong copying file in s3"
+				logger.err err:err, bucketName:bucketName, sourceKey:sourceKey, destKey:destKey, "something went wrong copying file in aws"
 			callback(err)

 	deleteFile: (bucketName, key, callback)->
@@ -89,7 +89,7 @@
 		options = buildDefaultOptions(bucketName, "delete", key)
 		request options, (err, res)->
 			if err?
-				logger.err err:err, res:res, bucketName:bucketName, key:key, "something went wrong deleting file in s3"
+				logger.err err:err, res:res, bucketName:bucketName, key:key, "something went wrong deleting file in aws"
 			callback(err)

 	deleteDirectory: (bucketName, key, _callback)->
@@ -100,12 +100,12 @@

 		logger.log key: key, bucketName: bucketName, "deleting directory"
 		s3Client = knox.createClient
-			key: settings.filestore.s3?.key
-			secret: settings.filestore.s3?.secret
+			key: settings.filestore.s3.key
+			secret: settings.filestore.s3.secret
 			bucket: bucketName
 		s3Client.list prefix:key, (err, data)->
 			if err?
-				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in s3"
+				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"
 				return callback(err)
 			keys = _.map data.Contents, (entry)->
 				return entry.Key
@@ -116,13 +116,13 @@
 		options = buildDefaultOptions(bucketName, "head", key)
 		request options, (err, res)->
 			if err?
-				logger.err err:err, res:res, bucketName:bucketName, key:key, "something went wrong checking file in s3"
+				logger.err err:err, res:res, bucketName:bucketName, key:key, "something went wrong checking file in aws"
 				return callback(err)
 			if !res?
 				logger.err err:err, res:res, bucketName:bucketName, key:key, "no response object returned when checking if file exists"
 				err = new Error("no response from s3 #{bucketName} #{key}")
 				return callback(err)
 			exists = res.statusCode == 200
-			logger.log bucketName:bucketName, key:key, exists:exists, "checked if file exists in s3"
+			logger.log bucketName:bucketName, key:key, exists:exists, "checked if file exsists in s3"
 			callback(err, exists)


linux monitor

linux monitor

--- plugin/linux/monitor.coffee
+++ plugin/linux/monitor.coffee
@@ -1,40 +1,67 @@
 child_process = require 'child_process'
+fs = require 'fs'

 config = require '../../config.coffee'

-last_plist = {}
-
-account_cache = []
-
-account_cache_sample = [
-  username: 'jysperm'
-]
+last_plist = []
+passwd_cache = {}

 exports.run = ->
   setInterval exports.monitoring, config.plugins.linux.cycle

+exports.loadPasswd = (callback) ->
+  fs.readFile '/etc/passwd', (err, content) ->
+    content = content.split '\n'
+
+    passwd_cache = {}
+
+    for line in content
+      if line
+        [username, , uid] = line.split ':'
+        passwd_cache[uid] = username
+
+    callback()
+
 exports.monitoring = ->
-  child_process.exec "ps awufx", (err, stdout, stderr) ->
-    plist = stdout.split('\n')[1...-1]
+  exports.loadPasswd ->
+    child_process.exec "ps awufx", (err, stdout, stderr) ->
+      plist = stdout.split('\n')[1...-1]

-    plist = _.filter plist, (item) ->
-      return item[..3] == 'root'
+      plist = _.reject plist, (item) ->
+        return item[..3] == 'root'

-    plist = _.map plist, (item) ->
-      rx = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/
-      result = rx.exec item
-      return {
-        user: result[1]
-        pid: result[2]
-        cpu_per: result[3]
-        mem_per: result[4]
-        vsz: result[5]
-        rss: result[6]
-        tty: result[7]
-        stat: result[8]
-        start: result[9]
-        time: result[10]
-        command: result[11]
-      }
+      plist = _.map plist, (item) ->
+        rx = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/
+        result = rx.exec item
+        return {
+          user: result[1]
+          pid: result[2]
+          cpu_per: result[3]
+          mem_per: result[4]
+          vsz: result[5]
+          rss: result[6]
+          tty: result[7]
+          stat: result[8]
+          start: result[9]
+          time: result[10]
+          command: result[11]
+        }

-    last_plist = plist
+      async.parallel [
+        (callback) ->
+          exports.monitoringCpu plist, callback
+      ]
+
+      last_plist = plist
+
+exports.monitoringCpu = (plist, callback) ->
+  exist_process = _.filter plist, (item) ->
+    return _.find last_plist, (i) ->
+      return i.pid == item.pid and i.user == item.user and i.command == item.command
+
+  new_process = _.fliter plist, (item) ->
+    return not _.find last_plist, (i) ->
+      return i.pid == item.pid and i.user == item.user and i.command == item.command
+
+
+exports.monitoringMemory = (plist, callback) ->


Don't assume an empty collection with return a contents array

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -55,7 +55,7 @@
       # Only setup a toc for a book
       if type isnt 'book' then return response

-      response.contents = response.tree.contents
+      response.contents = response.tree.contents or []

       depth = 0
       page = 1
@@ -64,6 +64,7 @@
       # information on each node of the tree prior to the tree being processed
       # by backbone-associations.
       traverse = (o = {}) =>
+        o.contents or= []
         for item in o.contents
           item.book = @
           item.depth = depth


Changed default for Vega Messages to internalRelease: false

--- app/schemas/models/level.ozar.coffee
+++ app/schemas/models/level.ozar.coffee
@@ -104,7 +104,7 @@
   move: c.object {title: 'Move', description: 'Tell the Thang to move.', required: ['target'], default: {target: {}, duration: 500}},
     target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.', default: {x: 20, y: 20}}
     duration: {title: 'Duration', description: 'Number of milliseconds over which to move, or 0 for an instant move.', type: 'integer', minimum: 0, format: 'milliseconds'}
-  tutorial: c.object {title: 'Tutorial', description: 'Move Vega around on the screen as a step by step tutorial to explain things.', default: {internalRelease: true}},
+  tutorial: c.object {title: 'Tutorial', description: 'Move Vega around on the screen as a step by step tutorial to explain things.', default: {internalRelease: false}},
     internalRelease: {title: 'Internal Release', description: 'Only show this step to admins or accounts with @codecombat emails', type: 'boolean'}
     position: c.shortString(
       title: 'Dialog Position'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -119,5 +119,6 @@

   4.circularPoints (p, i) ->
     equals p.x.toFixed(5), points[i].x.toFixed(5), "#{p.x} == #{points[i].x}"
+    equals p.x.toFixed(5), points[i].x.toFixed(5), "#{p.x} == #{points[i].x}"

 module undefined


added a failing test for doing parallel requests to a file that needs compililing

--- test/otf_compiler_spec.coffee
+++ test/otf_compiler_spec.coffee
@@ -6,6 +6,7 @@
 fs = require 'fs'
 express = require 'express'
 request = require 'request'
+async = require 'async'

 describe 'OnTheFlyCompiler', ->
   bundle = {}
@@ -89,6 +90,15 @@
         fs.writeFileSync __dirname + '/files/stylus/typography.styl', oldContent, 'utf8'
         done()

+  it 'should not cause any errors when parallel requests comes in at the same time', (done) ->
+    async.forEach [1..4], (i, cb) ->
+      request.get 'http://localhost:1338/generated/stylus/main.css', (err, res) ->
+        expect(res.statusCode).to.equal(200)
+        cb(err)
+    , (err) ->
+      done(err)
+
+
   describe 'Error handling', ->
     it 'should respond with 500 when requesting a coffee file with syntax errors', (done) ->
       bundle.js.addFile(__dirname + '/files/coffee/syntax_error.coffee')


copy url to clipboard

copy url to clipboard

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -633,6 +633,13 @@
           $ document.createElement 'button'
             .appendTo td
             .addClass 'btn btn-info btn-xs'
+            .prepend @createIcon 'paste'
+            .data 'url', "http://#{@bucket}.b0.upaiyun.com#{file.url}"
+            .click (ev)=>
+              @gui.Clipboard.get().set $(ev.currentTarget).data('url'), 'text'
+          $ document.createElement 'button'
+            .appendTo td
+            .addClass 'btn btn-info btn-xs'
             .prepend @createIcon 'edit'
             .data 'url', file.url
             .data 'filename', file.filename
@@ -644,6 +651,7 @@
                 default_action: 'editor'
                 editor_url: $(ev.currentTarget).data 'url'
                 editor_filename: $(ev.currentTarget).data 'filename'
+
     cb null
 @jump_editor = =>
   $ '#login, #filelist'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -66,7 +66,7 @@

   equals player.I.velocity.y, 0

-test "should handle
+test "should handle moving diagonally

 test "should correctly track facing direction", ->
   ok player.I.facing.equal(Point(1, 0))


Fixed tests.

Fixed tests.

--- test/specs/router-spec.coffee
+++ test/specs/router-spec.coffee
@@ -58,10 +58,13 @@
             spyOn $location, 'search'
             spyOn $location, 'replace'
             hrefSpy = spyOn routerProvider, 'href'
-            hrefSpy.and.returnValue 'href'
+            hrefSpy.and.returnValue
+                uri: 'href'
+                uriWithSearch: '/href?index=0'
+                search: {}
             $router.go 'namespace', 'params', replace: yes
             expect(routerProvider.isReloadAtThisRender).toBeFalsy()
-            expect(hrefSpy).toHaveBeenCalledWith 'namespace', 'params', {}
+            expect(hrefSpy).toHaveBeenCalledWith 'namespace', 'params'
             expect($location.path).toHaveBeenCalledWith 'href'
             expect($location.search).toHaveBeenCalledWith {}
             expect($location.replace).toHaveBeenCalled()
@@ -86,15 +89,20 @@
             result = $router.href 'home',
                 key: 'test'
                 index: 0
-            expect(result).toBe '/test?index=0'
+            expect(result).toEqual
+                uri: '/test'
+                uriWithSearch: '/test?index=0'
+                search: index: 0
         it '$router.href() with search argument.', inject ($router) ->
             routerProvider.rules =
                 home:
                     hrefTemplate: '/{key}'
-            search = {}
-            result = $router.href 'home', key: 'test', index: 0, search
-            expect(result).toBe '/test'
-            expect(search).toEqual index: 0
+            result = $router.href 'home', key: 'test', index: 0
+            expect(result).toEqual
+                uri: '/test'
+                uriWithSearch: '/test?index=0'
+                search:
+                    index: 0

         it '$router.oldState and $routerProvider.oldState are the same object', inject ($router) ->
             expect($router.oldState).not.toBeNull()


fix traceback that could only happen under heavy load (when database can't be read)

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -1609,7 +1609,7 @@
         m += if opts.target? then ",target='#{opts.target}')" else ")"
         dbg = @dbg(m)
         dbg("")
-        if @compute_server.storage_servers.get().size == 0
+        if (@compute_server.storage_servers.get()?.size ? 0) == 0
             dbg('no storage servers -- so all _storage_requests trivially done')
             opts.cb?()
             return


Bold more keywords

Bold more keywords

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

     render_create_button : ->
         <Button
-            style   = {fontSize:'40px', color:'#888', maxWidth:'100%', textAlign:'left'}
+            style   = {fontSize:'40px', color:'#888', maxWidth:'100%'}
             onClick = {=>@handle_click()} >
             <Icon name='plus-circle' /> {@button_text()}
         </Button>
@@ -391,14 +391,14 @@
             if last_folder_index isnt @props.file_search.indexOf('/')
                 # More than one sub folder

-                    Folder path
+
                             {@props.file_search}
-                         will be created if non-existant
+                         will be created as a folder path if non-existant

             else
                 # Only one folder

-                    Creates a folder named
+                    Creates a folder named
                         {@props.file_search}


Moved atom.loadPackage to a spec-only helper function

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -12,7 +12,7 @@
   beforeEach ->
     new RootView(require.resolve('fixtures/sample.js'))
     rootView.enableKeymap()
-    finderView = atom.loadPackage("fuzzy-finder").packageMain.createView()
+    finderView = window.loadPackage("fuzzy-finder").packageMain.createView()

   afterEach ->
     rootView.deactivate()


Unfocus specs

Unfocus specs

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -3,7 +3,7 @@
 PathLoader = require '../lib/path-loader'
 path = require 'path'

-fdescribe 'FuzzyFinder', ->
+describe 'FuzzyFinder', ->
   [finderView] = []

   beforeEach ->


Change append order

Change append order

--- src/CytoscapeActionbar.coffee
+++ src/CytoscapeActionbar.coffee
@@ -18,11 +18,10 @@
         $element = $("")

         if element.condition
-          $condition = $(element.condition)
-          $actionbar.append($condition)
-          $($actionbar[0].lastChild).append($element)
-        else
-          $actionbar.append($element)
+          $condition = $(element.condition);
+          $element = $condition.append($element);
+
+        $actionbar.append($element)

         $element
         .on('click', ->


(- fix) Repair infrastructure's presence on root locals

--- Paws/Source/Paws.coffee
+++ Paws/Source/Paws.coffee
@@ -18,8 +18,8 @@
    code = Paws.parser.parse code, root: true if typeof code == 'string'
    code = new Execution code

-   Thing.with(names: yes).construct(Paws.primitives 'infrastructure').clone code.locals
-   Thing.with(names: yes).construct(Paws.primitives 'implementation').clone code.locals
+   code.locals.push Thing.with(names: yes).construct(Paws.primitives 'infrastructure').at 1
+   code.locals.push Thing.with(names: yes).construct(Paws.primitives 'implementation').at 1

    return code


Set encoding after reboot, and misc fixes

--- IDE/atom/init.coffee
+++ IDE/atom/init.coffee
@@ -4,12 +4,25 @@
 gbk_exclude_path = [
 ]

-atom.workspace.onDidOpen (event) ->
-  editor = event.item
-  path = event.uri
+# console.debug(atom.workspace.getTextEditors())
+
+set_encoding_for = (editor, path) ->
   gbk_paths.forEach (regex) ->
     if regex.test(path)
+      console.debug "#{path} matches #{regex}, examining gbk_exclude_path..."
+      is_excluded = false
       gbk_exclude_path.forEach (exclude_regex) ->
-        unless exclude_regex.test(path)
-          console.log "#{path} matches #{regex}, set encoding to GBK"
-          editor.setEncoding('gbk')
+        if exclude_regex.test(path)
+          is_excluded = true
+      unless is_excluded
+        console.info "#{path} matches #{regex}, set encoding to GBK"
+        editor.setEncoding('gbk')
+
+atom.workspace.getTextEditors().forEach (editor) ->
+  path = editor.getPath()
+  set_encoding_for(editor, path)
+
+atom.workspace.onDidOpen (event) ->
+  # editor = event.item
+  path = event.uri
+  set_encoding_for(editor, path)


corrects exception as method does not access viewstate

--- src/ui/views/notifications.coffee
+++ src/ui/views/notifications.coffee
@@ -36,7 +36,7 @@

         if msg.chat_message?
             return unless msg.chat_message?.message_content?
-            text = textMessage msg.chat_message.message_content, proxied
+            text = textMessage msg.chat_message.message_content, proxied, viewstate.showMessageInNotification
         else if msg.hangout_event?.event_type == 'START_HANGOUT'
             text = i18n.__ "call.incoming:Incoming call"
             callNeedAnswer[conv_id] = true
@@ -105,9 +105,9 @@
         mainWindow = remote.getCurrentWindow()
         mainWindow.flashFrame(true)

-textMessage = (cont, proxied) ->
+textMessage = (cont, proxied, showMessage = true) ->
     if cont?.segment?
-      unless viewstate.showMessageInNotification
+      unless showMessage
           i18n.__('conversation.new_message:New message received')
       else
           segs = for seg, i in cont?.segment ? []


[ci skip] update team plan data

[ci skip] update team plan data

--- workers/social/lib/social/models/computeproviders/teamplans.coffee
+++ workers/social/lib/social/models/computeproviders/teamplans.coffee
@@ -24,12 +24,12 @@
     restrictions       : BASIC_RESTRICTIONS

   trial                :
-    member             : 5  # max number, can be overwritten in group data
+    member             : 3  # max number, can be overwritten in group data
                             # by a super-admin (an admin in Koding group)
-    validFor           : 30 # in days (1 month)
+    validFor           : 21 # in days (1 month)
     instancePerMember  : 1  # allows one instance per member
-    allowedInstances   : [ 't2.nano', 't2.micro' ]
-    maxInstance        : 10 # maximum instance count for this group (total)
+    allowedInstances   : [ 't2.nano' ]
+    maxInstance        : 3 # maximum instance count for this group (total)
     storagePerInstance : 5  # means 25GB storage for this plan in total (max).
                             # 5 member x 1 instancePerMember = 5 instances
                             # 5GB per instance x 5 instances = 25GB in total


Github: getConfig: simply active host for Github

--- workers/social/lib/social/models/gitprovider/githubprovider.coffee
+++ workers/social/lib/social/models/gitprovider/githubprovider.coffee
@@ -8,6 +8,8 @@
 KodingError = require '../../error'

 module.exports = GitHubProvider =
+
+  getConfig: -> [ null, { host: Constants.GITHUB_HOST } ]

   importStackTemplateData: (importParams, user, callback) ->


Use white text color for dark slice color

--- bokehjs/src/coffee/api/charts.coffee
+++ bokehjs/src/coffee/api/charts.coffee
@@ -15,6 +15,15 @@
   return result

 num2hexcolor = (num) -> sprintf("#%06x", num)
+hexcolor2rgb = (color) ->
+  r = parseInt(color.substr(1, 2), 16)
+  g = parseInt(color.substr(3, 2), 16)
+  b = parseInt(color.substr(5, 2), 16)
+  return [r, g, b]
+
+is_dark = ([r, g, b]) ->
+  l = 1 - (0.299*r + 0.587*g + 0.114*b)/255
+  return l >= 0.6

 pie = (data, opts) ->
   labels = _.clone(data.labels)
@@ -61,6 +70,7 @@
     palette = palettes[opts.palette ? "Spectral11"].map(num2hexcolor)

   colors = ( palette[i % palette.length] for i in [0...normalized_values.length] )
+  text_colors = colors.map((c) -> if is_dark(hexcolor2rgb(c)) then "white" else "black")

   to_cartesian = (r, alpha) -> [r*Math.cos(alpha), r*Math.sin(alpha)]

@@ -84,6 +94,7 @@
       end_angles: end_angles,
       text_angles: text_angles,
       colors: colors,
+      text_colors: text_colors,
       text_cx: text_cx,
       text_cy: text_cy,
     }
@@ -111,7 +122,8 @@
     x: {field: "text_cx"}, y: {field: "text_cy"},
     text: {field: opts.slice_labels ? "labels"},
     angle: {field: "text_angles"},
-    text_align: "center", text_baseline: "middle", text_font_size: "9pt",
+    text_align: "center", text_baseline: "middle",
+    text_color: {field: "text_colors"}, text_font_size: "9pt",
   })
   r2 = new models.GlyphRenderer({
     data_source: source,


Add message debugging

Add message debugging

--- src/EntityManager.coffee
+++ src/EntityManager.coffee
@@ -88,6 +88,7 @@
       @cleanUpOldLevel()

   processIncoming:(data, remote)->
+    console.debug(data) unless data.message == "update"
     if data.message == "initEntity"
       if (!data.forLevel && !@level) || data.forLevel == @level
         @spawnRemoteEntity(data.type, data.id, remote.peer, data.state)


Pressing <esc> while in artwork zoom returns to the artwork

--- apps/artwork/client/deep-zoom.coffee
+++ apps/artwork/client/deep-zoom.coffee
@@ -23,6 +23,8 @@

     @zoomTo           = _.throttle @_zoomTo, 50
     @detectActivity   = _.throttle @_detectActivity, 500
+
+    (@$window = $(window)).on 'keyup', @escape

   _detectActivity: (e) ->
     @$el.attr 'data-focus', 'active'
@@ -93,6 +95,10 @@
   _zoomTo: (e) ->
     @viewer.viewport.zoomTo parseFloat(e.currentTarget.value)

+  escape: (e) =>
+    return unless e.which is 27
+    @return()
+
   # Close by just returning to the base artwork route
   # The router will call #remove on this view
   return: ->
@@ -105,4 +111,5 @@
     Transition.fade @$el,
       duration: 500
       out: =>
+        @$window.off 'keyup', @escape
         DeepZoomView.__super__.remove.apply(this, arguments)


Fix display of UUID columns in frame view.

--- h2o-web/src/scripts/frame-view.coffee
+++ h2o-web/src/scripts/frame-view.coffee
@@ -1,8 +1,14 @@
 Steam.FrameView = (_, _frame) ->
-  createRow = (attribute, columns) ->
+  createSummaryRow = (attribute, columns) ->
     header: attribute
     cells: map columns, (column) ->
       switch column.type
+        when 'uuid'
+          switch attribute
+            when 'min', 'max', 'mean', 'sigma', 'cardinality'
+              '-'
+            else
+              column[attribute]
         when 'enum'
           switch attribute
             when 'min', 'max', 'mean', 'sigma'
@@ -23,7 +29,7 @@
     cells: map columns, (column) ->
       switch column.type
         when 'uuid'
-          'TODO'
+          column.str_data[index]
         when 'enum'
           column.domain[column.data[index]]
         else
@@ -35,7 +41,7 @@
     push attributes, 'missing' if some columns, (column) -> column.missing > 0
     rows = []
     for attribute in attributes
-      rows.push createRow attribute, columns
+      rows.push createSummaryRow attribute, columns
     rows

   createDataRows = (offset, rowCount, columns) ->
@@ -45,7 +51,7 @@
     rows

   createFrameTable = (offset, rowCount, columns) ->
-    header: createRow 'label', columns
+    header: createSummaryRow 'label', columns
     summaryRows: createSummaryRows columns
     dataRows: createDataRows offset, rowCount, columns


Register deserializer for Project class

Register deserializer for Project class

--- src/app/project.coffee
+++ src/app/project.coffee
@@ -11,6 +11,7 @@

 module.exports =
 class Project
+  registerDeserializer(this)

   @deserialize: (state) ->
     new Project(state.path, state.grammarOverridesByPath)
@@ -29,6 +30,7 @@
     @buffers = []

   serialize: ->
+    deserializer: 'Project'
     path: @getPath()
     grammarOverridesByPath: @grammarOverridesByPath


Move mixpanel calls inside corresponding methods

--- src/application.coffee
+++ src/application.coffee
@@ -39,6 +39,7 @@
 	return -> publishQueue.push(arguments)

 exports.kill = kill = (app) ->
+	utils.mixpanelTrack('Application kill', app)
 	docker.listContainersAsync(all: 1)
 	.then (containers) ->
 		Promise.all(
@@ -58,6 +59,7 @@
 	docker.getImage(app.imageId).inspectAsync()
 	.catch (error) ->
 		console.log("Pulling image:", app.imageId)
+		utils.mixpanelTrack('Application install', app)
 		docker.createImageAsync(fromImage: app.imageId)
 		.then (stream) ->
 			return new Promise (resolve, reject) ->
@@ -189,16 +191,13 @@

 			# Delete all the ones to remove in one go
 			Promise.map toBeRemoved, (imageId) ->
-				app = apps[imageId]
-				utils.mixpanelTrack('Application remove', app)
-				kill(app)
+				kill(apps[imageId])
 				.then ->
 					knex('app').where('imageId', imageId).delete()
 			.then ->
 				# Then install the apps and add each to the db as they succeed
 				installingPromises = toBeInstalled.map (imageId) ->
 					app = remoteApps[imageId]
-					utils.mixpanelTrack('Application install', app)
 					start(app)
 					.then ->
 						knex('app').insert(app)


editor:load (i need this for my ~/.atomicity/settings.coffee)

atom.on 'editor:load', (editor) ->
  editor.ace.setShowInvisibles no

--- src/atom/editor.coffee
+++ src/atom/editor.coffee
@@ -7,6 +7,8 @@
 {EditSession} = require 'ace/edit_session'
 {UndoManager} = require 'ace/undomanager'

+# Events:
+#   editor:load (editor) -> Called when an editor is loaded.
 module.exports =
 class Editor extends Resource
   window.resourceTypes.push this
@@ -90,6 +92,7 @@

     window.setTitle @title()
     @dirty = false
+    atom.trigger 'editor:load', this

     true


show bucket creation date in human form with moment.fromNow()

--- imports/ui/lists/DataBucketsList.cjsx
+++ imports/ui/lists/DataBucketsList.cjsx
@@ -1,5 +1,6 @@
 React               = require 'react'
 filesize            = require 'filesize'
+moment              = require 'moment'
 {Box, List, ListItem } = require 'grommet'

@@ -17,6 +18,9 @@
             {bucket.name}
             {filesize bucket.size or 0}
           </Box>
+          <Box direction='row' pad='none'>
+            Created {moment(bucket.created).fromNow()}
+          </Box>
         </ListItem>
       }
     </List>


fixed a syntax error

fixed a syntax error

--- test/modes/normal.coffee
+++ test/modes/normal.coffee
@@ -24,7 +24,7 @@
     deepEqual normal.parse('4W'), action: 'navigateNextWORD', args: {times: 4}
     deepEqual normal.parse('B'), action: 'navigateBackWORD'
     deepEqual normal.parse('w'), action: 'navigateNextWord'
-    deepEqual normal.parse('3e'), action: 'navigateWordEnd', args {times: 3}
+    deepEqual normal.parse('3e'), action: 'navigateWordEnd', args: {times: 3}
     deepEqual normal.parse('b'), action: 'navigateBackWord'

   test 'normal mode jump parsing', ->


do not populate user test data when clicking on cart link

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -30,7 +30,7 @@
       @eventUpdateCartNavCounter()
       @eventUpdateConfig()
       @eventSelectLicense()
-      @eventFillORderFormFromQueryString()
+      @eventFillOrderFormFromQueryString()
       @eventFillCartFromQueryString()
       @eventRenderCart()
       @eventRemoveCartItem()
@@ -676,9 +676,9 @@
         event.preventDefault()
         link = $(event.target)
         data = @_getOrderPermalink()
-        document.location = "?" + $.param(data) + '&user=uk' + '&test=1'
-
-  eventFillORderFormFromQueryString: ->
+        # document.location = "?" + $.param(data) + '&user=uk' + '&test=1'
+
+  eventFillOrderFormFromQueryString: ->
     if $(@cartPage).length > 0
       query = window.location.search.substring(1)
       urlParams = new URLSearchParams(window.location.search)


Moderation: Filter deleted/child topics from popular topics

--- workers/social/lib/social/models/activeitems.coffee
+++ workers/social/lib/social/models/activeitems.coffee
@@ -30,6 +30,7 @@
     topic   :
       klass : JTag
       as    : ["developer", "follower", "post"]
+      select: status : $nin : ["deleted", "synonym"]

   # Returns topics in following order:
   #   * Active topics in the last day
@@ -54,7 +55,9 @@

   @fetchRandomUsers = (callback)-> JAccount.some {}, {limit:10}, callback

-  @fetchRandomTopics = (callback)-> JTag.some {}, {limit:10}, callback
+  @fetchRandomTopics = (callback)->
+    {select: selector} = nameMapping.topic
+    JTag.some selector, {limit:10}, callback

   @_fetchUsers = (options={}, callback)->
     {client}   = options
@@ -81,7 +84,7 @@
     [callback, options] = [options, callback]  unless callback
     {name, nin} = options
     mapping     = nameMapping[name]
-    {klass, as} = mapping
+    {klass, as, select} = mapping

     greater = (new Date(Date.now() - 1000*60*60*24))

@@ -89,6 +92,7 @@
       sourceName : klass.name
       as         : $in  : as
       timestamp  : $gte : greater
+      targetName : $ne : "CFolloweeBucketActivity"
     }

     matcher.sourceId = $nin : nin  if nin
@@ -118,8 +122,10 @@
         if missing > 0
           existingIds = instances.map (i) -> i._id
           existingIds = nin.concat existingIds  if nin
-
-          klass.some {sourceId: $nin : existingIds}, {limit:missing}, (err, randomInstances)->
+          selector = {}
+          selector = select if select
+          selector.sourceId = {$nin : existingIds}
+          klass.some selector, {limit:missing}, (err, randomInstances)->
             instances = instances.concat randomInstances  unless err
             queue.next()
         else


social: use concat instead of flatten

social: use concat instead of flatten

--- workers/social/lib/social/models/activeitems.coffee
+++ workers/social/lib/social/models/activeitems.coffee
@@ -59,7 +59,7 @@

       @fetchItems {name: "user", count:missing, nin:existingIds}, (err, activeMembers)->
         return callback err  if err
-        members = _.flatten activeMembers, onlineMembers
+        members = onlineMembers.concat activeMembers
         callback null, members

   # General method that returns popular items in the last day. If none
@@ -110,7 +110,7 @@
             return callback err  if err

             # first array must contain entries or _ returns []
-            instances = _.flatten randomInstances, instances
+            instances = instances.concat randomInstances
             callback null, instances
         else
           callback null, instances


Social: added new property into group to hold its default channels

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -303,8 +303,12 @@
         plan        : String
         paymentQuota: Number
       disabledFeatures: Object
-
-      stackTemplates: [ ObjectId ]
+      stackTemplates  : [ ObjectId ]
+      # DefaultChannels holds the default channels for a group, when a user joins
+      # to this group, participants will be automatically added to regarding
+      # channels
+      # those should be social api channel ids
+      defaultChannels : [ String ]

     broadcastableRelationships : [
       'member', 'moderator', 'admin'


Sidebar: Set user data in page payload

Sidebar: Set user data in page payload

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -15,6 +15,7 @@
   currentGroup     = null
   userMachines     = null
   userWorkspaces   = null
+  sidebarEnvironmentData = null

   {bongoModels, client, slug} = options

@@ -32,6 +33,7 @@
     userAccount          = JSON.stringify delegate
     userMachines         = JSON.stringify userMachines
     userWorkspaces       = JSON.stringify userWorkspaces
+    sidebarEnvironmentData = JSON.stringify sidebarEnvironmentData

     """
     <!-- SEGMENT.IO -->
@@ -73,6 +75,8 @@
     " else '' }

     #{if argv.t then "<script src=\"/a/js/tests.js\"></script>" else ''}
+
+    <script>window.sidebarEnvironmentData = #{sidebarEnvironmentData}</script>
     """

   selector =
@@ -106,6 +110,10 @@
         console.log err  if err
         userMachines = machines or []
         queue.fin()
+    ->
+      bongoModels.Sidebar.fetchEnvironment client, (err, data) ->
+        sidebarEnvironmentData = data
+        queue.fin()
   ]

   dash queue, -> callback null, createHTML(), socialapidata


Fixed: test from a previous commit [which was not built so not detected]

--- tests/account_tests.coffee
+++ tests/account_tests.coffee
@@ -105,7 +105,6 @@
         describe "Keys reloading (the user already has master/slave keys)" \
         , ->
             before cleanRequest
-            before (done) -> encryption.logOut done

             it "When a request is sent to reload the keys", (done) ->
                 @cozyPwd = "password"


NEW: "finally" handler for each gesture-recognizer IMPROVED: pointerClick is handled correctly (won't fire if gesture was recognized)

--- source/Art/Engine/Events/GestureRecognizer.coffee
+++ source/Art/Engine/Events/GestureRecognizer.coffee
@@ -5,47 +5,70 @@
 {log, defineModule, inspect, merge, clone, peek, first, BaseClass, isPlainObject, clone, abs, isFunction, select, objectWithout} = Foundation
 {point, rect, matrix} = Atomic

+###
+TODO:
+  add 'finally' handler that gets called after 'end' or 'cancel'
+###
+
 defineModule module, class GestureRecognizer extends BaseClass
   @createGestureRecognizer: (o)->
     gr = new GestureRecognizer o
     gr.getPointerHandlers()

-  # gestureRecognizers is a plain Object
-  #   keys name each of your recognizers. "horizontal" and "vertical" are special only in that they have default recognize functions.
-  #   values can be:
-  #     begin: (e) ->
-  #       IN: e: the original pointerDown PointerEvent
-  #       When the gesture is recognized, this is called with the original pointerDown event.
-  #     move: (e) ->
-  #       IN: e: a pointerMove PointerEvent
-  #       When the gesture is recognized, this is called with the pointerMove event that triggered the recognition.
-  #       This is then called with each additional pointerMove no matter what it is.
-  #     end: (e) ->
-  #       IN: e: the pointerUp PointerEvent
-  #       This is called with the pointerUp event, and the gesture "ends.""
-  #     resume: (e) ->
-  #       IN: e: a pointerDown PointerEvent
-  #       OUT: return true to guarantee the new touch-start is "recognized" as this gesture
-  #       After the gesture has happend once, the next pointerDown event calls this to see if the gesture
-  #       should "resume." This is used in the ScrollElement to continually capture all touch events while momentum-scrolling.
-  #     recognize: (e) ->
-  #       IN: e: a pointerMove PointerEvent
-  #       OUT: true if the gesture was recognized
-  #       The first time the pointer moves outside the "dead zone", this gets called.
-  #       If "true" is returned, then the "begin" and "move" functions will be invoked.
-  #       If "false" then this gesture will not be "recognized" until the next pointerDown.
-  #       NOTE: required unless the name is "horizontal" or "vertical"
-  # gestureRecognizers can also have normal pointer* event handlers:
-  #   pointerDown:    (e) -> # always fires with the natural pointerDown
-  #   pointerMove:    (e) -> # fires if a gesture hasn't been recognized yet
-  #   pointerUp:      (e) -> # only fires if a gesture wasn't recognized
-  #   pointerCancel:  (e) -> # fires when a gesture is recognized OR if a natural pointerCancel comes in before a gesture is recognized
-  # NOTE: Why specify pointer* handlers with the gestures?
-  #   All other objects will get normal pointer* events, get pointerCancel and stop getting events when a gesture is recognized.
-  #   This doesn't work on the object with the gesture recognizer, since it still needs to receive natural pointer-events to drive the gesture.
-  #   This solves the problem. Passing pointer* handlers to the gesture-recognizer works just like all other objects w.r.t. gestures.
-  #
-  # TODO: I haven't decided how to handle multiple-touches (multiple active PointerEvents).
+  ###
+  gestureRecognizers is a plain Object
+    keys name each of your recognizers. "horizontal" and "vertical" are special only in that they have default recognize functions.
+
+    pointerUp/pointerDown/pointerCancel/pointerMove/pointerClick: (e) ->
+      These 5 'normal' events are passed through IFF a gesture hasn't been recognized yet (or was never recognized).
+
+    horizontal/vertical:
+      begin: (e) ->
+        IN: e: the original pointerDown PointerEvent
+        When the gesture is recognized, this is called with the original pointerDown event.
+
+      move: (e) ->
+        IN: e: a pointerMove PointerEvent
+        When the gesture is recognized, this is called with the pointerMove event that triggered the recognition.
+        This is then called with each additional pointerMove no matter what it is.
+
+      cancel: (e) ->
+        IN: e: the pointerCancel PointerEvent
+        Called if the gesture is canceled
+
+      finally: (e) ->
+        IN: e: either a pointerUp or pointerCancel PointerEvent
+        Called after every 'cancel' OR 'end'. Basically like a 'try finally'
+
+      end: (e) ->
+        IN: e: the pointerUp PointerEvent
+        This is called with the pointerUp event, and the gesture "ends.""
+
+      resume: (e) ->
+        IN: e: a pointerDown PointerEvent
+        OUT: return true to guarantee the new touch-start is "recognized" as this gesture
+        After the gesture has happend once, the next pointerDown event calls this to see if the gesture
+        should "resume." This is used in the ScrollElement to continually capture all touch events while momentum-scrolling.
+
+      recognize: (e) ->
+        IN: e: a pointerMove PointerEvent
+        OUT: true if the gesture was recognized
+        The first time the pointer moves outside the "dead zone", this gets called.
+        If "true" is returned, then the "begin" and "move" functions will be invoked.
+        If "false" then this gesture will not be "recognized" until the next pointerDown.
+        NOTE: required unless the name is "horizontal" or "vertical"
+  gestureRecognizers can also have normal pointer* event handlers:
+    pointerDown:    (e) -> # always fires with the natural pointerDown
+    pointerMove:    (e) -> # fires if a gesture hasn't been recognized yet
+    pointerUp:      (e) -> # only fires if a gesture wasn't recognized
+    pointerCancel:  (e) -> # fires when a gesture is recognized OR if a natural pointerCancel comes in before a gesture is recognized
+  NOTE: Why specify pointer* handlers with the gestures?
+    All other objects will get normal pointer* events, get pointerCancel and stop getting events when a gesture is recognized.
+    This doesn't work on the object with the gesture recognizer, since it still needs to receive natural pointer-events to drive the gesture.
+    This solves the problem. Passing pointer* handlers to the gesture-recognizer works just like all other objects w.r.t. gestures.
+
+  TODO: I haven't decided how to handle multiple-touches (multiple active PointerEvents).
+  ###

   pointerHandlers = ["pointerDown", "pointerUp", "pointerMove", "pointerCancel"]
   constructor: (gestureRecognizers)->
@@ -92,13 +115,18 @@
         pointerUp:     (e) =>
           if @_activeGesture
             @_activeGesture.end? e
+            @_activeGesture.finally? e
             @_activeGesture = null
           else
             @_nonGestureHandlers.pointerUp? e
+            @_nonGestureHandlers.pointerClick? e
+
+        pointerClick: (e) => # ignored, handled internally

         pointerCancel: (e) =>
           if @_activeGesture
             @_activeGesture.cancel? e
+            @_activeGesture.finally? e
             @_activeGesture = null
           else
             @_nonGestureHandlers.pointerCancel? e


Add helper for cache tests, make counter global.

This prevents test collisions and race conditions.

--- test/shared/cache.coffee
+++ test/shared/cache.coffee
@@ -35,15 +35,17 @@
       result.should.equal true
       fn err

-test = {}
+test =
+  counter: 0
+  name: ->
+    "impromptu-cache-test-#{test.counter++}"

 test.base = (CacheClass, options = {}) ->
   impromptu = new Impromptu
-  counter = 0
   cache = null

   beforeEach ->
-    cache = new CacheTests CacheClass, options, impromptu, "impromptu-cache-api-test-#{counter++}"
+    cache = new CacheTests CacheClass, options, impromptu, test.name()

   it 'should create an instance', ->
     should.exist cache
@@ -61,7 +63,7 @@
   it 'should set a value synchronously', (done) ->
     optionsSync = _.clone options
     optionsSync.update = -> 'value'
-    cacheSync = new CacheTests CacheClass, optionsSync, impromptu, "impromptu-cache-api-test-#{counter++}"
+    cacheSync = new CacheTests CacheClass, optionsSync, impromptu, test.name()

     async.series [
       (fn) -> cacheSync.getShouldEqualFallback fn
@@ -85,7 +87,7 @@
     background: true

   it 'should update when background is set', (done) ->
-    cached = new CacheClass background, 'should-update',
+    cached = new CacheClass background, test.name(),
       update: (fn) ->
         done()
         fn null, 'value'
@@ -93,11 +95,12 @@
     cached.run ->

   it 'should fetch cached values', (done) ->
-    updater = new CacheClass background, 'should-fetch',
+    name = test.name()
+    updater = new CacheClass background, name,
       update: (fn) ->
         fn null, 'value'

-    fetcher = new CacheClass impromptu, 'should-fetch',
+    fetcher = new CacheClass impromptu, name,
       update: (fn) ->
         should.fail 'Update should not run.'
         fn null, 'value'


Fix tests

Fix tests

--- test/src/htmltest.coffee
+++ test/src/htmltest.coffee
@@ -17,32 +17,32 @@
     ''').serialize()
   expectedSerialization = '''<document><block
       precedence="1"
-      color="cyan"
+      color="lightblue"
       socketLevel="0"
       classes="#documentType">&lt;!doctype html&gt;</block>
     <block
       precedence="1"
-      color="amber"
+      color="lightblue"
       socketLevel="0"
       classes="html">&lt;html&gt;<indent
       prefix="  "
       classes="html">
     <block
       precedence="1"
-      color="cyan"
+      color="lightblue"
       socketLevel="0"
       classes="head">&lt;head&gt;<indent
       prefix="  "
       classes="head">
     <block
       precedence="1"
-      color="green"
+      color="lightblue"
       socketLevel="0"
       classes="title">&lt;title&gt;<indent
       prefix="  "
       classes="title"><block
       precedence="1"
-      color="cyan"
+      color="lightgreen"
       socketLevel="0"
       classes="#text"><socket
       precedence="0"
@@ -51,14 +51,14 @@
     &lt;/head&gt;</block>
     <block
       precedence="1"
-      color="return"
+      color="lightblue"
       socketLevel="0"
       classes="body">&lt;body&gt;<indent
       prefix="  "
       classes="body">
     <block
       precedence="1"
-      color="cyan"
+      color="lightgreen"
       socketLevel="0"
       classes="#text"><socket
       precedence="0"
@@ -84,21 +84,21 @@
     ''').serialize()
   expectedSerialization = '''<document><block
       precedence="1"
-      color="amber"
+      color="lightblue"
       socketLevel="0"
       classes="html">&lt;html&gt;<indent
       prefix="  "
       classes="html">
     <block
       precedence="1"
-      color="deeppurple"
+      color="purple"
       socketLevel="0"
       classes="p">&lt;p&gt;<indent
       prefix="  "
       classes="p">
     <block
       precedence="1"
-      color="cyan"
+      color="lightgreen"
       socketLevel="0"
       classes="#text"><socket
       precedence="0"
@@ -123,20 +123,20 @@
     ''').serialize()
   expectedSerialization = '''<document><block
       precedence="1"
-      color="blue"
+      color="purple"
       socketLevel="0"
       classes="ul">&lt;ul&gt;<indent
       prefix="  "
       classes="ul">
     <block
       precedence="1"
-      color="pink"
+      color="purple"
       socketLevel="0"
       classes="li">&lt;li&gt;<indent
       prefix="  "
       classes="li"><block
       precedence="1"
-      color="cyan"
+      color="lightgreen"
       socketLevel="0"
       classes="#text"><socket
       precedence="0"
@@ -144,13 +144,13 @@
       classes="#text">Item1</socket></block></indent></block>
     <block
       precedence="1"
-      color="pink"
+      color="purple"
       socketLevel="0"
       classes="li">&lt;li&gt;<indent
       prefix="  "
       classes="li"><block
       precedence="1"
-      color="cyan"
+      color="lightgreen"
       socketLevel="0"
       classes="#text"><socket
       precedence="0"
@@ -173,7 +173,7 @@
     ''').serialize()
   expectedSerialization = '''<document><block
       precedence="1"
-      color="return"
+      color="lightblue"
       socketLevel="0"
       classes="body">&lt;body <socket
       precedence="0"
@@ -199,7 +199,7 @@
     ''').serialize()
   expectedSerialization = '''<document><block
       precedence="1"
-      color="return"
+      color="lightblue"
       socketLevel="0"
       classes="body">&lt;body <socket
       precedence="0"


Reqwest adapter: only append a '?' if also appending query params

--- src/batman.solo.coffee
+++ src/batman.solo.coffee
@@ -347,13 +347,13 @@
   unless @get('formData')
     options.headers['Content-type'] = @get('contentType')

-  if options.method is 'GET'
-    options.url += "?#{param(data)}"
-  else if options.method in ['PUT', 'POST']
+  if options.method in ['PUT', 'POST']
     if @get('formData')
-      options.data = @constructor.objectToFormData(options.data)
+      options.data = @constructor.objectToFormData(data)
     else
       options.data = param(data)
+  else if options.method is 'GET' and (params = param(data))
+    options.url += "?#{params}"

   # Fires the request. Grab a reference to the xhr object so we can get the status code elsewhere.
   xhr = (reqwest options).request


Fix Reqwest adapter to use query params for GET requests

--- src/batman.solo.coffee
+++ src/batman.solo.coffee
@@ -321,6 +321,7 @@
     add prefix, obj

 Batman.Request::send = (data) ->
+  data ?= @get('data')
   @fire 'loading'

   options =
@@ -342,17 +343,17 @@

     complete: =>
       @fire 'loaded'
-
-  if options.method in ['PUT', 'POST']
-    unless @get('formData')
-      options.headers['Content-type'] = @get 'contentType'
-      data = data || @get('data')
-      if options.method is 'GET'
-        options.url += param(data)
-      else
-        options.data = param(data)
+
+  unless @get('formData')
+    options.headers['Content-type'] = @get('contentType')
+
+  if options.method is 'GET'
+    options.url += "?#{param(data)}"
+  else if options.method in ['PUT', 'POST']
+    if @get('formData')
+      options.data = @constructor.objectToFormData(options.data)
     else
-      options.data = @constructor.objectToFormData(options.data)
+      options.data = param(data)

   # Fires the request. Grab a reference to the xhr object so we can get the status code elsewhere.
   xhr = (reqwest options).request


support per-file refresh for search

support per-file refresh for search


First steps towards sync support in Amazon EC2.


removed debug output

removed debug output

--- imports/ui/pages/NewInstancePage.cjsx
+++ imports/ui/pages/NewInstancePage.cjsx
@@ -8,7 +8,6 @@

   getAppParams: ->
     params = @props.selectedApp.dockerCompose?.match /(?:\{\{)([\d|\w|_|-]*?)(?=\}\})/g
-    console.log params
     if params?.length
       _.uniq(params.map (p) -> p.replace('{{', '').trim())
     else


Show full message headers when clicking on attachment icon

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -248,7 +248,7 @@
                         p className: 'sender',
                             @renderAddress 'from'
                             i
-                                className: 'toggle-headers fa fa-toggle-up'
+                                className: 'toggle-headers fa fa-toggle-up clickable'
                                 onClick: @toggleHeaders
                         p className: 'receivers',
                             span null, t "mail receivers"
@@ -279,12 +279,14 @@
                 span className: 'participants', @getParticipants prepared
                 if @state.active
                     i
-                        className: 'toggle-headers fa fa-toggle-down'
+                        className: 'toggle-headers fa fa-toggle-down clickable'
                         onClick: @toggleHeaders
                 #span className: 'subject', @props.message.get 'subject'
                 span className: 'hour', prepared.date
                 span className: "flags",
-                    i className: 'attach fa fa-paperclip'
+                    i
+                        className: 'attach fa fa-paperclip clickable'
+                        onClick: @toggleHeaders
                     i className: 'fav fa fa-star'
                 #if @props.inConversation
                 #    toggleActive


change loading tool message

change loading tool message

--- client/code/view/tool_content.coffee
+++ client/code/view/tool_content.coffee
@@ -7,7 +7,7 @@

   render: ->
     @$el.empty()
-    @$el.html """
Loading #{@model.get 'name'} tool
"""
+    @$el.html """
Loading tool
"""
     @model.install (ajaxObj, status) =>
       if status == 'success'
         @model.setup (buffer) =>


adding richard

adding richard

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -78,19 +78,19 @@
     msg.send "Praise to the Lord! Hail to you o mighty Prem."

   robot.respond /who will be victorious/i, (msg)->
-    msg.send "The Offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"
+    msg.send "The Offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :richard: :drmatt:"

   robot.respond /who will be triumphant/i, (msg)->
-    msg.send "The Offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"
+    msg.send "The Offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :richard: :drmatt:"

   robot.respond /who will prevail/i, (msg)->
-    msg.send "The Offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"
+    msg.send "The Offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :richard: :drmatt:"

   robot.respond /who will unleash righteous vengeance upon evil/i, (msg)->
-    msg.send "The heroes of offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"
+    msg.send "The heroes of offshore team! :ahsan: :sachin: :prem: :kostas: :chris: :richard: :drmatt:"

   robot.respond /when all hope is lost and darkness is upon us/i, (msg)->
-    msg.send "The offshore team shall know no fear! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"
+    msg.send "The offshore team shall know no fear! :ahsan: :sachin: :prem: :kostas: :chris: :richard: :drmatt:"

   robot.respond /when Prem is gone and we are lost in darkness/i, (msg)->
     msg.send "The offshore team shall know no fear! The offshore team will be triumphant! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"


Minimal fixed to get userBadge working

Minimal fixed to get userBadge working

--- client/lib/ConnectedComponent.coffee
+++ client/lib/ConnectedComponent.coffee
@@ -12,7 +12,7 @@
             super(props)
             @handleReload = @handleReload.bind(this)

-        @urls: () ->
+        urls: () ->
             Component.urls(@props)

         dataLoaded: () ->
@@ -48,11 +48,11 @@
                 clearTimeout(@timeout)

         componentDidUpdate: (prevProps, prevState) ->
-            if not deepEqual(Component.url(prevProps), Component.url(@props))
+            if not deepEqual(Component.urls(prevProps), Component.urls(@props))
                 @requestData()

         requestData: () ->
-            promises = [@props.getData(url, true) for key, url in @urls()]
+            promises = [@props.getData(url, true) for key, url of @urls()]
             return promises

         handleReload: () ->
@@ -61,11 +61,11 @@
         requestDataAndSetTimeout: () ->
             try
                 await Promise.all(@requestData())
-                console.log "Updated data", @url()
+                console.log "Updated data", @urls()
             catch e
-                console.log "Can't reload data", @url(), e
+                console.log "Can't reload data", @urls(), e
             if Component.timeout?()
-                console.log "Setting timeout", @url()
+                console.log "Setting timeout", @urls()
                 @timeout = setTimeout((() => @requestDataAndSetTimeout()), Component.timeout())

@@ -78,7 +78,6 @@
         return
             getData: (url, force) -> dispatch(actions.getData(url, force))
             saveDataPromises: (promise) -> dispatch(actions.saveDataPromises(promise))
-            reloadMyData: () -> dispatch(actions.postLogin())
             dispatch: dispatch

     return connect(mapStateToProps, mapDispatchToProps)(Result)


Output error and reply from Twitter response more separately

--- scripts/tweeter.coffee
+++ scripts/tweeter.coffee
@@ -78,8 +78,10 @@
     twit.post "statuses/update",
       status: update
     , (err, reply) ->
-      console.log "===== Twitter responses:"
-      console.log err, reply
+      console.log "===== Twitter error:"
+      console.log err
+      console.log "===== Twitter response:"
+      console.log reply
       if err
         data = JSON.parse(err.data).errors[0]
         msg.reply "I can't do that. #{data.message} (error #{data.code})"


activity feed: use bound

activity feed: use bound

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -339,7 +339,7 @@
       KD.utils.getTimedOutCallbackOne
         name      : "populateActivity",
 #        onSuccess : -> KD.logToMixpanel "refresh activity feed success"
-        onTimeout : @recover.bind this
+        onTimeout : @bound 'recover'

   recover:->
     #KD.logToMixpanel "activity feed render failed; recovering"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -12,7 +12,7 @@
   Object.reverseMerge I,
     flashTargetAlpha: 0

-  defaultParams =
+  defaultP =
     duration: 30
     color: 'black'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -5,6 +5,8 @@
     width: 8
     height: 8
     activeBounds: Rectangle(0, 0, App.width, App.height)
+
+  log I.activeBounds

   self.bind 'update', ->
     self.destroy() unless I.activeBounds.left <= I.x <= I.activeBounds.right


live on LOL

live on LOL

--- src/coffee/Main.coffee
+++ src/coffee/Main.coffee
@@ -76,7 +76,7 @@
 		@context = new AudioContext()
 		@masterGain = @context.createGain()
 		@masterGain.gain.value = 1
-		live = false
+		live = true

 		if(!live)
 			@masterGain.connect(@context.destination)


Add function getConfig, title changing support

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -1,7 +1,7 @@
-Koishumi = ((W)->
+Koishumi = ((W, D) ->
   hash = ''

-  time = (value)->
+  time = (value) ->
     month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']

     month = month[+value[1].replace(/^0/, '') - 1]
@@ -9,8 +9,8 @@

     month + ' ' + day + ', ' + value[0]

-  loadScript = (url)->
-    script = document.createElement 'script'
+  loadScript = (url) ->
+    script = D.createElement 'script'

     script.src = url
     script.async = true
@@ -18,12 +18,18 @@
     script.onload = ->
       this.remove()

-    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild script
+    (D.getElementsByTagName('head')[0] || D.getElementsByTagName('body')[0]).appendChild script
+
+  getConfig = (name, context, extra) ->
+    return '' if context and !config[context]
+
+    value = (config[context] || config)[name]
+    return if value then value + (if extra then extra else '') else ''

   getList = ->
-    loadScript 'https://api.github.com/repos/' + config.github.repo + (if config.github.path then config.github.path else '') + '/contents/?' + (if config.github.branch then 'ref=' + config.github.branch + '&' else '') + 'callback=Koishumi.showList'
+    loadScript 'https://api.github.com/repos/' + getConfig('repo', 'github') + getConfig('path', 'github') + '/contents/?' + getConfig('branch', 'github', '&') + 'callback=Koishumi.showList'

-  showList = (data)->
+  showList = (data) ->
     data = data.data

     i = 0
@@ -39,25 +45,28 @@
         date: time(path)
       }

-    document.getElementById('main').innerHTML = template 'posts-list', {posts: posts}
+    D.title = 'Home'
+    D.getElementById('main').innerHTML = template 'posts-list', {posts: posts}

-  getArticle = (path)->
+  getArticle = (path) ->
     path = path.replace(/\/$/, '').split '/'

     request = new XMLHttpRequest()
-    request.open 'GET', 'https://raw.githubusercontent.com/'  + config.github.repo + '/' + (if config.github.branch then config.github.branch + '/' else '')+ (if config.github.path then config.github.path else '') + encodeURIComponent path.join('-') + '.md'
+    request.open 'GET', 'https://raw.githubusercontent.com/'  + getConfig('repo', 'github') + '/' + getConfig('branch', 'github', '/') + getConfig('path', 'github') + encodeURIComponent path.join('-') + '.md'

     request.onload = ->
       if request.status >= 200 and request.status < 400
         data = request.responseText
+        name = path.pop()

-        document.getElementById('main').innerHTML = template 'article', {
-            title: path.pop(),
+        D.title = name
+        D.getElementById('main').innerHTML = template 'article', {
+            title: name,
             date: time(path),

             url: location.href,
             comment: {
-              type: if config.comment.type then config.comment.type else ''
+              type: getConfig 'type', 'comment'
             },

             content: (new showdown.Converter).makeHtml data
@@ -74,14 +83,14 @@
                 }
               else
                 setTimeout ->
-                  loadScript '//' + config.comment.shortname + '.disqus.com/embed.js'
+                  loadScript '//' + getConfig('shortname', 'comment') + '.disqus.com/embed.js'
             , 1000

           when 'duoshuo'
             setTimeout ->
               if W.DUOSHUO then DUOSHUO.EmbedThread '.ds-thread'
               else
-                W.duoshuoQuery = {short_name: config.comment.shortname}
+                W.duoshuoQuery = {short_name: getConfig('shortname', 'comment')}
                 loadScript '//static.duoshuo.com/embed.js'
             , 1000

@@ -93,7 +102,7 @@
     hash = decodeURIComponent(location.hash.substr(2))
     if hash == 'home' then getList() else getArticle(hash)

-  return console.log 'Cannot find any available repo. Complete config please.' if !config.github or !config.github.repo
+  return console.log 'Cannot find any available repo. Complete config please.' if !getConfig('repo', 'github')

   W.onhashchange = matching
   matching()
@@ -101,4 +110,4 @@
   {
     showList: showList
   }
-)(this)+)(this, document)


Check an error when we fail to find an appropriate file by default (WNdb doesn't contain exceptions)

--- test/wordnet_test.coffee
+++ test/wordnet_test.coffee
@@ -24,6 +24,11 @@
       done()

   it 'should load exclusions correctly', (done) ->
-    wordnet.loadExclusions () ->
+    wordnet.loadExclusions (err) ->
+
+      ## Since the default WNdb doesn't include exclusions, expect an error
+      should.exist(err)
+      err.should.have.property('code', 'ENOENT')
+
       done()


Remove unused file

Remove unused file

--- public/assets/js/interactive.coffee
+++ public/assets/js/interactive.coffee
@@ -1,154 +0,0 @@
-# Interactive mode
-
-class Dispatcher
-  listeners = []
-
-  register: (cmd) ->
-    name = cmd.name?()
-
-    throw "Command does not define a name" unless name
-
-    console.info "Registering", name
-
-    if listeners[name]?
-      throw "Another listener has already registered command #{name}"
-    if cmd instanceof Command
-      listeners[name] = cmd
-
-  dispatch: (pattern) ->
-    input  = pattern.split /\s/ or []
-    output = new ConsoleOutput()
-
-    return unless input[0]
-
-    if listeners[input[0]]?
-      listeners[input[0]].run input, output, @
-    else
-      output.send "No command '#{input[0]}' found"
-
-  getListeners: () -> listeners
-
-class Runner
-  constructor: () ->
-    @setup()
-    @question()
-
-  answer: (answer) =>
-    if answer == "exit"
-      @close()
-    else
-      @dispatcher.dispatch answer
-      @question()
-
-  question: () ->
-    throw "question() not implemented"
-
-  setup: () =>
-    @dispatcher = new Dispatcher(@.getOutputInterface());
-
-    @dispatcher.register new HelpCommand()
-    @dispatcher.register new WhoamiCommand()
-    @dispatcher.register new UptimeCommand()
-
-  getOutputInterface: () ->
-    throw "getOutputInterface() not implemented"
-
-class CliRunner extends Runner
-  constructor: () ->
-    readline = require('readline');
-    @rl = readline.createInterface({
-      input: process.stdin,
-      output: process.stdout
-    })
-
-    super
-
-  question: () =>
-    @rl.question("you@thatmattpattis.at:~$ ", @answer)
-
-  close: () =>
-    @rl.close()
-
-  getOutputInterface: () -> new ReadlineOutput
-
-class NullOutput
-  send: () ->
-
-class ConsoleOutput
-  send: (text) ->
-    console.log text
-
-class ReadlineOutput
-  constructor: (rl) ->
-    @rl = rl
-  send: (text) =>
-    @rl.write text
-
-class Command
-  name: () ->
-
-  help: (o) ->
-    o.send "#{name()}"
-    o.send "    #{description}"
-    if @usage
-      o.send ""
-      o.send "USAGE:  #{usage}"
-
-  run: () ->
-    throw "run() method not implemented"
-
-class HelpCommand extends Command
-  name: () -> "help"
-
-  run: (i, o, dispatcher) ->
-    patterns = []
-    for pattern, listener of dispatcher.getListeners()
-      patterns.push pattern
-
-    patterns.sort()
-    for pattern in patterns
-      o.send pattern
-
-class WhoamiCommand extends Command
-  name: () -> "whoami"
-
-  run: (i, o) ->
-    o.send "Matthew Patterson <matthew.s.patterson@gmail.com>"
-
-class UptimeCommand extends Command
-  name: () -> "uptime"
-
-  run: (i, o) ->
-    curr  = new Date
-    birth = new Date("1988-04-26")
-
-    years = months = days = 0
-
-    while curr > birth
-      switch
-        when curr.getFullYear() > birth.getFullYear() and curr.getMonth() > birth.getMonth()
-          years += 1
-          curr.setYear(curr.getFullYear() - 1)
-        when curr.getFullYear() > birth.getFullYear() and curr.getMonth() >= birth.getMonth() and curr.getDate() >= birth.getDate()
-          years += 1
-          curr.setYear(curr.getFullYear() - 1)
-        when curr.getFullYear() > birth.getFullYear() and curr.getMonth() >= birth.getMonth()
-          months += 1
-          curr.setMonth(curr.getMonth() - 1)
-        when curr.getFullYear() > birth.getFullYear() and curr.getMonth() < birth.getMonth()
-          months += 1
-          curr.setMonth(curr.getMonth() - 1)
-        when curr.getMonth() > birth.getMonth() and curr.getDate() >= birth.getDate()
-          months += 1
-          curr.setMonth(curr.getMonth() - 1)
-        when curr.getMonth() > birth.getMonth() and curr.getDate() < birth.getDate()
-          days += 1
-          curr.setDate(curr.getDate() - 1)
-        when curr.getDate() > birth.getDate()
-          days += 1
-          curr.setDate(curr.getDate() - 1)
-
-    o.send "#{years} years, #{months} months, #{days} days"
-
-if module?.exports
-  new CliRunner()


set default parameters for other types

set default parameters for other types

--- jsapp/xlform/src/model.configs.coffee
+++ jsapp/xlform/src/model.configs.coffee
@@ -126,6 +126,8 @@
     image:
       label:
         value: "Point and shoot! Use the camera to take a photo"
+      parameters:
+        value: "max-pixels="
     video:
       label:
         value: "Use the camera to record a video"
@@ -175,6 +177,12 @@
     acknowledge:
       label:
         value: "Acknowledge"
+    select_one:
+      parameters:
+        value: "randomize="
+    select_multiple:
+      parameters:
+        value: "randomize="

   configs.columns = ["type", "name", "label", "hint", "required", "relevant", "default", "constraint"]


core_object

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -168,10 +168,7 @@

     @name include
     @methodOf Core#
-
-    @param {Module} Module the module to include. A module is a constructor
-    that takes two parameters, I and self, and returns an object containing the
-    public methods to extend the including object with.
+    @param {Module} Module the module to include. A module is a constructor that takes two parameters, I and self, and returns an object containing the public methods to extend the including object with.
     ###
     include: (Module) ->
       self.extend Module(I, self)


core_object

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -17,25 +17,25 @@
     External access to instance variables. Use of this property should be avoided
     in general, but can come in handy from time to time.

-

-I =
-  r: 255
-  g: 0
-  b: 100
+

+    I =
+      r: 255
+      g: 0
+      b: 100

-myObject = Core(I)
+    myObject = Core(I)

-# a bad idea most of the time, but it's
-# pretty convenient to have available.
-myObject.I.r
-# => 255
+    # a bad idea most of the time, but it's
+    # pretty convenient to have available.
+    myObject.I.r
+    # => 255

-myObject.I.g
-# => 0
+    myObject.I.g
+    # => 0

-myObject.I.b
-# => 100
-

+    myObject.I.b
+    # => 100
+

     @name I
     @fieldOf Core#
@@ -57,8 +57,8 @@
     myObject.r(254)
     myObject.r()

- => 254
-

+    => 254
+

     @name attrAccessor
     @methodOf Core#


added an update method

added an update method

--- src/class_methods.coffee
+++ src/class_methods.coffee
@@ -77,10 +77,15 @@
       if(!callback?)
         callback = () ->

-      instance = new @(document)
       @sendCreateMessage _.pluralize(_.uncapitalize(@name)), document, callback
       return instance

+    update: (query, document, callback) ->
+      if(!callback?)
+        callback = () ->
+
+      @sendUpdateMessage _.pluralize(_.uncapitalize(@name)), query, document, callback
+
     register: (liveDocumentClass) ->
       classes[liveDocumentClass.name] = liveDocumentClass


Remove querystring to prevent extra urlencode 💀

--- src/dnt-connect.coffee
+++ src/dnt-connect.coffee
@@ -1,5 +1,4 @@
 crypto = require 'crypto'
-stringify = require('querystring').stringify

 CONNECT_URL = 'https://www.turistforeningen.no/connect'

@@ -137,10 +136,7 @@
     timestamp: Math.floor(new Date().getTime() / 1000)
   , iv = crypto.randomBytes 16

-  "#{CONNECT_URL}/#{type}/?" + stringify
-    client: @client
-    data: data
-    hmac: hmac
+  "#{CONNECT_URL}/#{type}/?client=#{@client}&data=#{data}&hmac=#{hmac}"

 ###
 #


Use @mini

Use @mini

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -81,7 +81,7 @@
         softWrap: false
         tabLength: 2
         softTabs: true
-        mini: mini
+        mini: @mini

     props = defaults({@editor, parentView: this}, props)
     @component = React.renderComponent(EditorComponent(props), @element)


:memo: Mark more EditorView methods private

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -977,9 +977,11 @@
     @setWidthInChars()
     @editor.setSoftWrap(not @editor.getSoftWrap())

+  # Private:
   calculateWidthInChars: ->
     Math.floor(@scrollView.width() / @charWidth)

+  # Private:
   calculateHeightInLines: ->
     Math.ceil($(window).height() / @lineHeight)

@@ -1064,6 +1066,7 @@
     super
     atom.workspaceView?.focus()

+  # Private:
   beforeRemove: ->
     @trigger 'editor:will-be-removed'
     @removed = true
@@ -1614,6 +1617,7 @@
         index++
     left

+  # Private:
   measureToColumn: (lineElement, tokenizedLine, screenColumn, lineStartBufferColumn) ->
     left = oldLeft = index = 0
     iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFilter)
@@ -1659,6 +1663,7 @@

     returnLeft ? left

+  # Private:
   getCharacterWidthCache: (scopes, char) ->
     scopes ?= NoScope
     obj = EditorView.characterWidthCache
@@ -1667,6 +1672,7 @@
       return null unless obj?
     obj[char]

+  # Private:
   setCharacterWidthCache: (scopes, char, val) ->
     scopes ?= NoScope
     obj = EditorView.characterWidthCache
@@ -1675,6 +1681,7 @@
       obj = obj[scope]
     obj[char] = val

+  # Private:
   clearCharacterWidthCache: ->
     EditorView.characterWidthCache = {}


Return if pressed target is null

Return if pressed target is null

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -325,10 +325,12 @@
     else
       @theTarget = document.elementFromPoint e.clientX, e.clientY

-    if @theTarget.nodeName and @theTarget.nodeName.toLowerCase() isnt 'a' and (@theTarget.nodeType is 3 or @theTarget.nodeType is 1)
+    if @theTarget?.nodeName and @theTarget.nodeName.toLowerCase() isnt 'a' and (@theTarget.nodeType is 3 or @theTarget.nodeType is 1)
       @oldTarget = @theTarget
       @parents = $(@theTarget).parentsUntil('ul li')
       @theTarget = @parents[@parents.length-1] or @oldTarget
+
+    if @theTarget is null then return

     @theTarget.className+= ' pressed'
     @theTarget.addEventListener 'touchmove', @onTouchMove, false


fsSelect: clean dropdownItems when new items value is set

--- src/coffee/select.coffee
+++ src/coffee/select.coffee
@@ -53,6 +53,8 @@
         $scope.dropdownItems = $filter('filter')(($scope.items || []), $scope.search).concat($scope.dynamicItems())

     $scope.$watch 'active', (q) -> updateDropdown()
+    $scope.$watch 'items', (q) -> $scope.dropdownItems = []
+
     $scope.$watch 'search', (q) ->
       if angular.isFunction($scope.items)
         if $scope.searchTimeout


switch properties depending on task type

--- src/flux/scores.coffee
+++ src/flux/scores.coffee
@@ -146,8 +146,13 @@
       task.completed_on_time_step_count + task.completed_accepted_late_step_count

     getCompletedPercent: (task) ->
-      score = task.correct_on_time_exercise_count + task.correct_accepted_late_exercise_count
-      percent = Math.round( (score / task.exercise_count) * 100 )
+      if task.type is 'homework'
+        score = task.correct_on_time_exercise_count + task.correct_accepted_late_exercise_count
+        percent = Math.round( (score / task.exercise_count) * 100 )
+      else
+        score = task.correct_on_time_step_count + task.correct_accepted_late_step_count
+        percent = Math.round( (score / task.step_count) * 100 )
+

     hasAdditionalLateWork: (task) ->
       task.completed_accepted_late_step_count and (
@@ -159,8 +164,12 @@
     hasLateWork: (task) ->
       ScoresConfig.exports.taskLateStepCount(task) > 0
     taskLateStepCount: (task) ->
-      task.completed_step_count - task.completed_on_time_step_count +
-        task.completed_accepted_late_step_count
+      if task.type is 'homework'
+        task.completed_exercise_count - task.completed_on_time_exercise_count +
+          task.completed_accepted_late_exercise_count
+      else
+        task.completed_step_count - task.completed_on_time_step_count +
+          task.completed_accepted_late_step_count

     getHumanProgress: (task) ->
       complete = ScoresConfig.exports.getCompletedSteps(task)


GameObject.Effect

GameObject.Effect

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -164,7 +164,7 @@
   "Rotatable"
   "TimedEvents"
   "Tween"
-  "GameObject"
+  "GameObject.Effect"
 ]

 ###*


Fix: どこかでもエラーしたときに適用失敗と表示されないのを修正

Fix: どこかでもエラーしたときに適用失敗と表示されないのを修正

--- src/gui/js/repo.coffee
+++ src/gui/js/repo.coffee
@@ -257,6 +257,7 @@
       deleteMods.push({repo: repo, name: $mod.getAttribute("data-path")})

     $("#progress").modal({ keyboard: false, backdrop: "static" })
+    errored = false
     applyMod.applyMods(addMods, deleteMods, (done, type, mod, err) ->
       $checkbox = $("input[data-path=\"#{mod.name}\"]")
       if done
@@ -274,6 +275,7 @@
               when "en" then p.addLog("#{mod.name} - Removed Successfully")
               when "ru" then p.addLog("#{mod.name} - Удалено успешно")
       else
+        errored = true
         switch type
           when "add"
             switch lang
@@ -287,11 +289,11 @@
               when "ru" then p.addLog("#{mod.name} - Не удалось удалить(#{err})")
       return
     ).then( ->
-      p.changePhase("done")
-    ).catch( (err) ->
-      p.changePhase("failed")
-      p.nextLog()
-      p.addLog(err)
+      if errored
+        p.changePhase("done")
+      else
+        p.changePhase("failed")
+      return
     )
   return
 )


add support for inert keys pgup/pgdown in smartnumber

--- app/assets/javascripts/angular/directives/smartNumber.js.coffee
+++ app/assets/javascripts/angular/directives/smartNumber.js.coffee
@@ -7,6 +7,10 @@

     # 0 = tab, 8 = backspace , 13 = enter, 46 = delete, 37 = left arrow, 39 = right arrow
     controlKeys = [0,8,13, 46, 37, 39]
+
+    # 37 = left arrow, 39 = right arrow, 9 = enter, 33 = page up, 34 = page down
+    inertKeys = [37, 39, 9, 33, 34]
+
     keyCodes = { 48:0, 49:1, 50:2, 51:3, 52:4, 53:5, 54:6, 55:7, 56:8, 57:9 }

     getOptions = (scope, attrs) ->
@@ -37,6 +41,9 @@

     isControlKey = (which) ->
       controlKeys.indexOf(which) >= 0
+
+    isInertKey = (which) ->
+      inertKeys.indexOf(which) >= 0

     invalidInput = (elem, event) ->
       invalidKey(event) or invalidZero(elem, event)
@@ -236,8 +243,8 @@

             elem.on 'keydown', (event) ->
               keycode = event.which
-              killEvent(event) unless keycode == 37 || keycode == 39 || keycode == 9
-
+
+              killEvent(event) unless isInertKey(keycode)

               return if invalidInput(elem, event)
               # unless isControlKey(keycode)


Fix admin/receipts miss-redirection

Fix admin/receipts miss-redirection

--- app/assets/javascripts/app/controllers/admin/receipts.js.coffee
+++ app/assets/javascripts/app/controllers/admin/receipts.js.coffee
@@ -146,7 +146,7 @@
     Receipt.unbind 'refresh'
     Receipt.one 'refresh', =>
       receipt = Receipt.find(id)
-      window.location = "/people/#{receipt.owner_id}?folding=person_affairs"
+      window.location = "/people/#{receipt.owner_id}#affairs"
     Receipt.fetch(id: id)

   stack_export_window: (e) ->


Removed some redundant stuff, made keypresses work with keydown event

--- app/assets/javascripts/lib/mobile/autocomplete_mobile.js.coffee
+++ app/assets/javascripts/lib/mobile/autocomplete_mobile.js.coffee
@@ -34,6 +34,7 @@
       uri: 'uri'

     constructor: (@args) ->
+      alert 'started'
       @el = document.getElementById(args.id)
       @init() if @el

@@ -52,17 +53,19 @@
         @_searchFor e.currentTarget.value
       , false

-      @el.addEventListener 'keypress', (e) =>
+      @el.addEventListener 'keydown', (e) =>
         if @showingList
           @_handleKeypress e

-      # this needs to be fixed - doesn't pick up span click
       @el.parentNode.addEventListener 'click', (e) =>
-        if e.target.tagName == 'LI'
-          @el.value = e.target.textContent
-          @_removeResults()
+        if e.target.tagName == 'A'
+          if e.target.getAttribute('href') == '#'
+            e.preventDefault()
+            @el.value = e.target.textContent
+            @_removeResults()

     _handleKeypress: (e) ->
+      console.log e
       if e.keyCode == 40 # down arrow
         e.preventDefault()
         @_highlightDown()
@@ -112,27 +115,25 @@
     _searchFor: (searchTerm)  ->
       if searchTerm && searchTerm.length >= 3
         @searchTerm = searchTerm
-
-        if not @throttled
-          @_doRequest @searchTerm
-
+        @_doRequest @searchTerm
       else if @showingList
         @_removeResults()

     _doRequest: ->
-      myRequest = new XMLHttpRequest()
-      myRequest.addEventListener 'readystatechange', =>
-        if myRequest.readyState == 4
-          if myRequest.status == 200
-            @_updateUI JSON.parse myRequest.responseText
+      if not @throttled
+        myRequest = new XMLHttpRequest()
+        myRequest.addEventListener 'readystatechange', =>
+          if myRequest.readyState == 4
+            if myRequest.status == 200
+              @_updateUI JSON.parse myRequest.responseText

-      myRequest.open 'get', @_generateURI(@args.uri, @args.scope)
-      myRequest.setRequestHeader 'Accept', '*/*'
-      myRequest.send()
-      @throttled = true
-      window.setTimeout =>
-        @throttled = false
-      , 200
+        myRequest.open 'get', @_generateURI(@args.uri, @args.scope)
+        myRequest.setRequestHeader 'Accept', '*/*'
+        myRequest.send()
+        @throttled = true
+        window.setTimeout =>
+          @throttled = false
+        , 200

     _generateURI: (searchURI, scope) ->
       uri = "#{searchURI}#{@searchTerm}"
@@ -140,7 +141,7 @@
       uri

     _removeResults: ->
-      @el.parentNode.removeChild @resultsList
+      @el.parentNode.removeChild @resultsList if @resultsList
       @showingList = false

     _updateUI: (searchResults) ->
@@ -162,22 +163,17 @@

     _createListItem: (item) ->
       listItem = document.createElement 'LI'
-      listItem.className = 'autocomplete__result'
-
-      if @responseMap.type
-        listItem.classList.add 'autocomplete_result__type'
-        listItem.classList.add "autocomplete__result__type--#{item[@responseMap.type]}"
-
-      if @responseMap.uri
-        listItem.appendChild @_createAnchor(item)
-      else
-        listItem.innerHTML = @_highlightText item[@responseMap.title], @searchTerm
-
+      listItem.appendChild @_createAnchor(item)
       listItem

     _createAnchor: (item) ->
       anchor = document.createElement 'A'
-      anchor.href = item[@responseMap.uri]
+      anchor.href = if @responseMap.uri then item[@responseMap.uri] else '#'
+      anchor.className = 'autocomplete__result'
+
+      if @responseMap.type
+        anchor.classList.add 'autocomplete__result__type'
+        anchor.classList.add "autocomplete__result__type--#{item[@responseMap.type]}"

       if @searchTerm
         anchor.innerHTML = @_highlightText item[@responseMap.title], @searchTerm
@@ -187,4 +183,4 @@

     _highlightText: (text, term) ->
       regex = new RegExp term, 'ig'
-      text.replace regex, "$&"
+      text.replace regex, "$&"


added html watching

added html watching

--- grunt/contrib-watch.coffee
+++ grunt/contrib-watch.coffee
@@ -50,6 +50,12 @@
       options:
         livereload: true
         spawn: false
+    html:
+      files: [
+        'assets/partial/**/*.html'
+      ]
+      options:
+        livereload: true
     less:
       files: [
         '<%= less.dev.options.paths[0] %>/**/*.less'


Fixes "double-click-to-rename" feature for taxonomies tree.

--- backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
@@ -86,6 +86,8 @@

 root = exports ? this
 root.setup_taxonomy_tree = (taxonomy_id) ->
+  $taxonomy_tree = $("#taxonomy_tree")
+
   if taxonomy_id != undefined
     # this is defined within admin/taxonomies/edit
     root.base_url = Spree.url(Spree.routes.taxonomy_taxons_path)
@@ -130,7 +132,7 @@
               taxon_tree_menu(obj, this)
           plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]

-        $("#taxonomy_tree").jstree(conf)
+        $taxonomy_tree.jstree(conf)
           .bind("move_node.jstree", handle_move)
           .bind("remove.jstree", handle_delete)
           .bind("create.jstree", handle_create)
@@ -138,8 +140,8 @@
           .bind "loaded.jstree", ->
             $(this).jstree("core").toggle_node($('.jstree-icon').first())

-    $("#taxonomy_tree a").on "dblclick", (e) ->
-      $("#taxonomy_tree").jstree("rename", this)
+    $taxonomy_tree.on "dblclick", "a", (e) ->
+      $taxonomy_tree.jstree("rename", this)

     # surpress form submit on enter/return
     $(document).keypress (e) ->


Refactor

Refactor

--- unicode_eastasianwidth.coffee
+++ unicode_eastasianwidth.coffee
@@ -63,23 +63,16 @@
   width

 unicodeEastAsianWidth.truncate = (string, length, suffix) ->
+  if width(string) <= length
+    return string
+  slen = width(suffix)
   ret = ""
-  c = undefined
-  clen = undefined
-  counter = 0
-  chars = string.split("")
-  i = undefined
-  l = undefined
-  slen = width(suffix)
-  return string if width(string) <= length
-  i = 0
-  l = chars.length
-
-  while i < l and counter < length
-    c = chars[i]
+  count = 0
+  for c in string.split("")
     clen = width(c)
-    return ret + suffix  if counter + clen + slen > length
+    if count + clen + slen > length
+      ret += suffix
+      break
     ret += c
-    counter += clen
-    i++
+    count += clen
   ret


keep status column for #241

keep status column for #241

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -390,6 +390,7 @@
         useJournalAbbreviation default 'false' CHECK(useJournalAbbreviation in ('true', 'false')),

         exportedRecursively CHECK(exportedRecursively in ('true', 'false')),
+        status,

         UNIQUE (path)
         )


texnotes

texnotes

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -1074,6 +1074,31 @@

   HTMLNode::alias(['pre', 'b', 'p', 'div', 'ul', 'li'])

-
-
-
+Zotero.BetterBibTeX.pandoc = new class
+  constructor: ->
+    env = Components.classes["@mozilla.org/process/environment;1"].getService(Components.interfaces.nsIEnvironment)
+
+    if env.exists('PATH')
+      @searchpath = env.get('PATH').split((if Zotero.isWin then ';' else ':'))
+    else
+      @searchpath = []
+
+    @executable = 'pandoc'
+    @executable += '.exe' if Zotero.isWin
+
+    @pandoc = null
+    path = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile)
+    for dir in @searchpath
+      path.initWithPath(dir)
+      path.append(@executable)
+      if path.exists()
+        @pandoc = path.path
+        break
+
+  convert: (src, target) ->
+    throw new Error("#{@executable} not found in #{JSON.stringify(@searchpath)}") unless @pandoc
+    return
+
+    path = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile)
+    path.initWithPath(src)
+    path.remove()


:memo: Adding documentation

:memo: Adding documentation

--- lib/indentation-indicator-view.coffee
+++ lib/indentation-indicator-view.coffee
@@ -7,23 +7,29 @@
     @div class: 'inline-block', =>
       @span 'foo:42', class: 'indentation-indicator', outlet: 'text'

-  # Initializes the view by subscribing to various events.
+  # Public: Initializes the view by subscribing to various events.
   #
   # statusBar - {StatusBar} of the application
   initialize: (@statusBar) ->
     @subscribe @statusBar, 'active-buffer-changed', @update

+  # Internal: Creates the text for the indicator.
+  #
+  # softTabs - A {Boolean} indicating whether soft tabs are enabled.
+  # length - The {Number} of spaces that a tab is considered equivalent to.
+  #
+  # Returns the {String} containing the text for the indicator.
   formatText: (softTabs, length) ->
     type = if softTabs then "Spaces" else "Tabs"
     "#{type}:#{length}"

-  # Gets the currently active `Editor`.
+  # Internal: Gets the currently active `Editor`.
   #
   # Returns the {Editor} that is currently active or `null` if there is not one active.
   getActiveEditor: ->
     atom.workspace.getActiveEditor()

-  # Updates the indicator based on the current state of the application.
+  # Internal: Updates the indicator based on the current state of the application.
   update: =>
     editor = @getActiveEditor()
     if editor?
@@ -31,6 +37,6 @@
     else
       @text.hide()

-  # Tear down any state and detach.
+  # Public: Tear down any state and detach.
   destroy: ->
     @remove()


Mount path option takes precedence

Mount path option takes precedence

--- client/finder/lib/filetree/controllers/nfindercontroller.coffee
+++ client/finder/lib/filetree/controllers/nfindercontroller.coffee
@@ -109,7 +109,7 @@

     { uid } = machine
     mRoots  = (@appStorage.getValue 'machineRoots') or {}
-    path    = mRoots[uid] or options.mountPath or '/'
+    path    = options.mountPath or mRoots[uid] or '/'
     path   ?= '/root'  if machine.isManaged()
     path   ?= if owner = machine.getOwner()
     then "/home/#{owner}"


MediaBanner created

MediaBanner created

--- @client/banner.coffee
+++ @client/banner.coffee
@@ -1016,6 +1016,137 @@
           list_style: opts.tabs_list_style or {}

+
+
+
+
+
+
+
+
+
+window.MediaBanner = (opts) ->
+  homepage = fetch('location').url == '/'
+  subdomain = fetch '/subdomain'
+  edit_banner = fetch 'edit_banner'
+
+  opts ?= {}
+  opts.tab_background_color ?= (if edit_banner.editing then edit_banner.text_background_css) or customization('banner')?.text_background_css or '#666'
+
+  if !homepage
+    return  DIV
+              style:
+                backgroundColor: 'white'
+
+              DIV
+                style:
+                  margin: 'auto'
+                  fontSize: 43
+                  padding: '10px 20px'
+
+                back_to_homepage_button {fontSize: 32}, translator 'engage.back_to_homepage', 'Homepage'
+
+
+  has_image_background = edit_banner.masthead_preview != '*delete*' && (edit_banner.masthead_preview || customization('banner')?.background_image_url || opts.backgroundImage)
+  if has_image_background
+    bg = edit_banner.masthead_preview or customization('banner')?.background_image_url or opts.backgroundImage
+
+  convert_opacity = (value) ->
+    if !value
+      '00'
+    else
+      parseInt(value).toString(16)
+
+  is_dark_theme = !is_light_background()
+
+  DIV
+    id: 'banner'
+    className: if is_dark_theme then 'dark'
+    style:
+      position: 'relative'
+      color: if is_dark_theme then 'white' else 'black'
+      backgroundColor: '#eee'
+      padding: '0px 2px'
+      borderBottom: '1px solid black'
+
+    DIV
+      style:
+        paddingTop: 28
+        backgroundColor: 'white'
+
+      EditBanner()
+
+      CustomizeLogo()
+
+      CustomizeBackground()
+
+
+      CustomizeTitle
+        title: opts.header
+        style: _.defaults {}, opts.header_text_style or {},
+          fontSize: 60
+          fontWeight: 800
+          fontFamily: header_font()
+          textAlign: 'left'
+          marginLeft: 36
+          marginBottom: 22
+
+      if customization('homepage_tabs')
+        HomepageTabs
+          tab_style: _.defaults {}, opts.tab_style or {},
+            textTransform: 'uppercase'
+            fontFamily: customization('font')
+            fontWeight: 600
+            fontSize: 16
+            padding: '16px 8px 24px'
+            color: if is_dark_theme then 'white' else 'black'
+            # borderBottom: '1px solid'
+            # borderColor: 'transparent'
+
+          tab_wrapper_style: _.defaults {}, opts.tab_wrapper_style or {},
+            backgroundColor: 'transparent' #opts.tab_background_color # '#005596'
+            margin: '0 6px'
+          active_style: _.defaults {}, opts.tab_active_style or {},
+            backgroundColor: 'white'
+            color: if is_dark_theme then 'white' else 'black'
+            # borderColor: if is_dark_theme then 'white' else 'black'
+            textDecoration: 'underline'
+          active_tab_wrapper_style: _.defaults {}, opts.active_tab_wrapper_style or {},
+            backgroundColor: opts.tab_background_color
+          hovering_tab_wrapper_style: _.defaults {}, opts.active_tab_wrapper_style or {},
+            backgroundColor: opts.tab_background_color
+          wrapper_style: _.defaults {}, opts.tabs_wrapper_style or {},
+            marginTop: 0
+            top: 0
+            borderTop: '1px solid #ccc'
+            boxShadow: '0 1px 3px rgba(0,0,0,.15)'
+
+          list_style: _.defaults {}, opts.tabs_list_style or {},
+            textAlign: 'left'
+            width: '100%'
+            paddingLeft: 36 - 16
+
+
+    if has_image_background
+      DIV
+        style:
+          padding: '20px 36px'
+
+        IMG
+          src: bg
+          style:
+            objectFit: 'cover' #'contain' 'cover'
+            display: 'block'
+            height: 260
+            width: '100%'
+
+
+
+
+
+
+
+
 # A small header with text and optionally a logo
 window.ShortHeader = (opts) ->
   subdomain = fetch '/subdomain'


Use destructuring assignment for more readability.

--- src/createProject.coffee
+++ src/createProject.coffee
@@ -39,8 +39,8 @@
     -> applyEach @renamePairs, rename
   ]

-replace = (string, replacement) ->
-  string.replace replacement[0], replacement[1]
+replace = (string, [search,replace]) ->
+  string.replace search, replace

 findPlaceholderNames = (contents) ->
   return [] unless matches = contents.toString().match  /---([a-z]+(-[a-z]+)*)---/g


Fixed another Travis build error

Fixed another Travis build error

--- server/blogpost.coffee
+++ server/blogpost.coffee
@@ -11,10 +11,10 @@
   # If there is no Tumblr API key just return.
   # That way cache will stay empty. To client it
   # will look like there are no blog posts.
-  return unless Meteor.settings.private.thumblr.apikey
+  return unless Meteor.settings.private?.tumblr?.apikey

   Meteor.setTimeout updateCache, CACHE_UPDATE_INTERVAL
-  apikey = Meteor.settings.private.thumblr.apikey
+  apikey = Meteor.settings.private.tumblr.apikey
   url = "http://api.tumblr.com/v2/blog/peerlibrary.tumblr.com/posts?api_key=" + apikey
   try
     posts = HTTP.get url,


textarea.active returns boolean

textarea.active returns boolean

--- www/src/views/helper/textarea.coffee
+++ www/src/views/helper/textarea.coffee
@@ -12,7 +12,7 @@
         template: _.template html

         @active: ->
-            _.size _.find cachedText, (v) -> v? and v.length > 0
+            !!(_.size _.find cachedText, (v) -> v? and v.length > 0)

         events:
             "keydown textarea": "preEdit"


fixed simple edx openended problem

fixed simple edx openended problem

--- common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
@@ -63,8 +63,6 @@
     else
       @createXMLEditor()

-    @alertTaskRubricModification()
-
   ###
   Creates the XML Editor and sets it as the current editor. If text is passed in,
   it will replace the text present in the HTML template.
@@ -95,8 +93,6 @@
       # Hide markdown-specific toolbar buttons
       $(@element.find('.editor-bar')).hide()

-  alertTaskRubricModification: ->
-    return alert("Before you edit, please note that if you alter the tasks block or the rubric block of this question after students have submitted responses, it may result in their responses and grades being deleted!  Use caution when altering problems that have already been released to students.")
   ###
   Have the user confirm the one-way conversion to XML.
   Returns true if the user clicked OK, else false.
@@ -261,7 +257,7 @@
                 config = "peer_grading.conf"
                 groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});
               } else if (type=="edx") {
-                config = "edX"
+                config = "openended"
                 groupString += template(open_ended_template,{min_max_string: min_max_string, grading_config: config});
               } else if (type=="ai") {
                                        config = "ml_grading.conf"


inject depend

inject depend

--- src/infolis-web.coffee
+++ src/infolis-web.coffee
@@ -49,6 +49,7 @@
 			throw e

 		@app.mongooseJSONLD = new MongooseJSONLD(
+			mongoose: Mongoose
 			baseURI: CONFIG.baseURI
 			apiPrefix: CONFIG.apiPrefix
 			schemaPrefix: CONFIG.schemaPrefix


* [test] use sinon.stub to mock method

* [test] use sinon.stub to mock method

--- test/get-contributors-test.coffee
+++ test/get-contributors-test.coffee
@@ -14,6 +14,7 @@
 fakeResults = []
 fakeError = null
 # mock this method
+###
 contributors.constructor::list = sinon.spy (options, callback)->
   options = options || {}
   if fakeError?
@@ -21,6 +22,15 @@
   else
     callback(null, fakeResults)
   return
+###
+sinon.stub contributors, 'list', (options, callback)->
+  options = options || {}
+  if fakeError?
+    callback(fakeError)
+  else
+    callback(null, fakeResults)
+  return
+

 getContributors = require '../src/'


[bug] dont break if message has no text (html draft)

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -61,12 +61,13 @@
         text = message.get 'text'
         html = message.get 'html'
         urls = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/gim
-        rich = text.replace urls, '$1', 'gim'
-        rich = rich.replace(/^>>>>>[^>]?.*$/gim, '$&')
-        rich = rich.replace(/^>>>>[^>]?.*$/gim, '$&')
-        rich = rich.replace(/^>>>[^>]?.*$/gim, '$&')
-        rich = rich.replace(/^>>[^>]?.*$/gim, '$&')
-        rich = rich.replace(/^>[^>]?.*$/gim, '$&', 'gim')
+        if text
+            rich = text.replace urls, '$1', 'gim'
+            rich = rich.replace(/^>>>>>[^>]?.*$/gim, '$&')
+            rich = rich.replace(/^>>>>[^>]?.*$/gim, '$&')
+            rich = rich.replace(/^>>>[^>]?.*$/gim, '$&')
+            rich = rich.replace(/^>>[^>]?.*$/gim, '$&')
+            rich = rich.replace(/^>[^>]?.*$/gim, '$&', 'gim')
         # @TODO Do we want to convert text only messages to HTML ?
         # /!\ if messageDisplayHTML is set, this method should always return
         # a value fo html, otherwise the content of the email flashes


Updated Atom settings

Updated Atom settings

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -70,6 +70,7 @@
     ]
   editor:
     fontFamily: "Knack Nerd Font"
+    fontSize: 12
     invisibles: {}
     showInvisibles: true
     softWrapHangingIndent: 4
@@ -98,7 +99,7 @@
     honorStyleSettings: true
   "linter-jshint": {}
   "linter-ui-default":
-    panelHeight: 36
+    panelHeight: 81
   "merge-conflicts": {}
   minimap:
     plugins:


Update .atom

Update .atom

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -11,6 +11,7 @@
     'fontSize': 11
     'showInvisibles': true
     'showIndentGuide': true
+    'invisibles': {}
   'core':
     'disabledPackages': [
       'metrics'


Add additional potential Symfony console paths

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -12,6 +12,8 @@
     console: [
       "app/console"
       "bin/console"
+      "php/app/console"
+      "php/bin/console"
     ]
   "autocomplete-plus":
     backspaceTriggersAutocomplete: true