fix additional address view bug for person form.

--- app/assets/javascripts/dynamic_nested_fields.js.coffee
+++ app/assets/javascripts/dynamic_nested_fields.js.coffee
@@ -3,7 +3,9 @@
     event.preventDefault()
     time = new Date().getTime()
     regexp = new RegExp($(this).data('id'), 'g')
-    $(this).before($(this).data('fields').replace(regexp, time))
+    html = $(this).data('fields').replace(regexp, time)
+    $(this).closest('.form-inputs').find('fieldset:last').after(html)
+
     style_select_picker()
     update_delete_buttons()


DiaScene: guessJoint: to handle more possibilities between nodes

--- lib/components/dia/diascene.coffee
+++ lib/components/dia/diascene.coffee
@@ -114,12 +114,39 @@

     source = @getDia sourceId
     target = @getDia targetId
+
+    debug 'source - target', { source, target }
+
     target.joint = @guessJoint target, source  unless target.joint
     @connect source, target  if target.joint

-    return "left"  if source.joint is "right" and target.dia.joints.left?
-    return "right" if source.joint is "left"  and target.dia.joints.right?
   guessJoint: (target, source) ->
+
+    joints = Object.keys target.dia.joints
+    return  if joints.length is 0
+    return joints.first  if joints.length is 1
+
+    if source.joint is 'right'
+      return 'left'    if target.dia.joints.left
+      return 'bottom'  if target.dia.joints.bottom
+      return 'top'     if target.dia.joints.top
+
+    if source.joint is 'left'
+      return 'right'   if target.dia.joints.right
+      return 'bottom'  if target.dia.joints.bottom
+      return 'top'     if target.dia.joints.top
+
+    if source.joint is 'bottom'
+      return 'top'     if target.dia.joints.top
+      return 'bottom'  if target.dia.joints.bottom
+      return 'left'    if target.dia.joints.left
+
+    if source.joint is 'top'
+      return 'bottom'  if target.dia.joints.bottom
+      return 'top'     if target.dia.joints.top
+      return 'right'   if target.dia.jointsright
+
+

   getDia:(id)->
     # Find a better way for this


Removed double delete from celltracing

Removed double delete from celltracing

--- app/assets/javascripts/oxalis/model/celltracing.coffee
+++ app/assets/javascripts/oxalis/model/celltracing.coffee
@@ -572,7 +572,7 @@

         @trigger("mergeTree", lastTree.treeId, lastNode, @activeNode)

-        @deleteTree(false, lastTree.treeId, false)
+        #@deleteTree(false, lastTree.treeId, false)

         @setActiveNode(activeNodeID)
       else


Fix overlapping paper creation bug (refs #71052648)

--- app/assets/javascripts/views/paper_edit_view.js.coffee
+++ app/assets/javascripts/views/paper_edit_view.js.coffee
@@ -13,17 +13,16 @@
     $('.control-bar').scrollToFixed()

     $('#tahi-container > main > aside > div').scrollToFixed
-      marginTop: $('.control-bar').outerHeight(true)
+      marginTop: $('.control-bar').outerHeight()
       unfixed: ->
         $(this).css('top', '0px')
   ).on('didInsertElement')

   setupStickyToolbar: ->
     $('.oo-ui-toolbar').scrollToFixed
-      marginTop: $('.control-bar').outerHeight(true)
+      marginTop: $('.control-bar').outerHeight()
       unfixed: ->
         $(this).addClass('not-fixed')
-        $(this).css('marginTop', '-86px')
       preFixed: ->
         $(this).removeClass('not-fixed')
         $(this).css('marginTop', '0')


Fix fullscreen in visualization/embed

Fix fullscreen in visualization/embed

--- app/frontend/javascripts/visualization-embed.js.coffee
+++ app/frontend/javascripts/visualization-embed.js.coffee
@@ -4,5 +4,28 @@

   constructor: (_id) ->
     super _id
+    document.addEventListener 'fullscreenchange', @onFullScreenChange, false
+    document.addEventListener 'webkitfullscreenchange', @onFullScreenChange, false
+    document.addEventListener 'mozfullscreenchange', @onFullScreenChange, false
+
+  onFullscreen: (e) =>
+    # toggle full screen
+    if !document.fullscreenElement and !document.mozFullScreenElement and !document.webkitFullscreenElement
+      if document.documentElement.requestFullscreen
+        document.documentElement.requestFullscreen()
+      else if document.documentElement.mozRequestFullScreen
+        document.documentElement.mozRequestFullScreen()
+      else if document.documentElement.webkitRequestFullscreen
+        document.documentElement.webkitRequestFullscreen Element.ALLOW_KEYBOARD_INPUT
+    else
+      if document.cancelFullScreen
+        document.cancelFullScreen()
+      else if document.mozCancelFullScreen
+        document.mozCancelFullScreen()
+      else if document.webkitCancelFullScreen
+        document.webkitCancelFullScreen()
+
+  onFullScreenChange: ->
+    $('body').toggleClass 'fullscreen'

 module.exports = VisualizationEmbed


:bug: Fix a bad spec

:bug: Fix a bad spec

--- spec/message-registry-spec.coffee
+++ spec/message-registry-spec.coffee
@@ -2,6 +2,8 @@
   MessageRegistry = require('../lib/message-registry')
   EditorLinter = require('../lib/editor-linter')
   LinterRegistry = require('../lib/linter-registry')
+  nextAnimationFrame = ->
+    return new Promise (resolve) -> requestAnimationFrame(resolve)
   getLinterRegistry = ->
     linterRegistry = new LinterRegistry
     editorLinter = new EditorLinter(atom.workspace.getActiveTextEditor())
@@ -53,12 +55,11 @@
         gotMessages = messages
       waitsForPromise ->
         linterRegistry.lint({onChange: false, editorLinter}).then ->
-          setTimeout ->
+          nextAnimationFrame().then() ->
             expect(gotMessages).toBeDefined()
             expect(gotMessages.length).toBe(1)
             linterRegistry.deactivate()
             messageRegistry.deactivate()
-          , 1000

   describe '::deleteEditorMessages', ->
     it 'removes messages for that editor', ->
@@ -74,9 +75,8 @@
         messageRegistry.deleteEditorMessages(editor)
       waitsForPromise ->
         linterRegistry.lint({onChange: false, editorLinter}).then ->
-          setTimeout ->
+          nextAnimationFrame().then ->
             expect(gotMessages).toBeDefined()
             expect(gotMessages.length).toBe(0)
             linterRegistry.deactivate()
             messageRegistry.deactivate()
-          , 1000


Be explicit in the tests that the default is US English.

--- spec/number_formatter_spec.coffee
+++ spec/number_formatter_spec.coffee
@@ -7,7 +7,7 @@
     formatter = new NumberFormatter()

   describe 'by default', ->
-    it 'has English-standard number prefixes and suffixes', ->
+    it 'has US English-standard number prefixes and suffixes', ->
       expect(formatter.positivePrefix()).toEqual("")
       expect(formatter.positiveSuffix()).toEqual("")
       expect(formatter.negativePrefix()).toEqual("-")
@@ -20,7 +20,7 @@
     it 'does not always show the decimal separator', ->
       expect(formatter.alwaysShowsDecimalSeparator()).toBeFalsy()

-    it 'has the English-standard decimal separator', ->
+    it 'has the US English-standard decimal separator', ->
       expect(formatter.decimalSeparator()).toEqual('.')

     it 'has USD currency code', ->
@@ -47,10 +47,10 @@
     it 'does not use a grouping separator', ->
       expect(formatter.usesGroupingSeparator()).toBeFalsy()

-    it 'has English-standard grouping separator', ->
+    it 'has US English-standard grouping separator', ->
       expect(formatter.groupingSeparator()).toEqual(',')

-    it 'has English-standard grouping size', ->
+    it 'has US English-standard grouping size', ->
       expect(formatter.groupingSize()).toEqual(3)

     it 'has no custom zero symbol', ->
@@ -62,19 +62,19 @@
     it 'has NaN as the not a number symbol', ->
       expect(formatter.notANumberSymbol()).toEqual('NaN')

-    it 'has English-standard symbol for positive infinity', ->
+    it 'has US English-standard symbol for positive infinity', ->
       expect(formatter.positiveInfinitySymbol()).toEqual('+∞')

-    it 'has English-standard symbol for negative infinity', ->
+    it 'has US English-standard symbol for negative infinity', ->
       expect(formatter.negativeInfinitySymbol()).toEqual('-∞')

-    it 'has English-standard symbol for percent', ->
+    it 'has US English-standard symbol for percent', ->
       expect(formatter.percentSymbol()).toEqual('%')

     it 'has no number style', ->
       expect(formatter.numberStyle()).toEqual(NumberFormatter.Style.NONE)

-    it 'has English-standard symbol for currency', ->
+    it 'has US English-standard symbol for currency', ->
       expect(formatter.currencySymbol()).toEqual('$')

   describe '#numberFromString', ->
@@ -403,7 +403,7 @@
         it 'formats using the custom symbol', ->
           expect(formatter.format .2).toEqual('20PER')

-      describe 'with a non-English locale', ->
+      describe 'with a non-US English locale', ->
         beforeEach ->
           formatter.setLocale 'fr'


Tweak a few Atom settings

Tweak a few Atom settings

--- atom/config.cson
+++ atom/config.cson
@@ -3,10 +3,14 @@
     userId: "30e6f2d0-42d3-ab77-4984-dde4c431f019"
   welcome:
     showOnStartup: false
-  core: {}
+  core:
+    projectHome: "/Users/randy/src"
+    audioBeep: false
   editor:
     invisibles: {}
     fontFamily: "Hack"
+    preferredLineLength: 100
+    showIndentGuide: true
   autosave:
     enabled: true
   "git-diff":


Update atom project home to latest repos dir

--- atom/config.cson
+++ atom/config.cson
@@ -71,7 +71,7 @@
       "resize-indent"
       "script"
     ]
-    projectHome: "~/Local/Repositories"
+    projectHome: "~/Repositories"
     telemetryConsent: "limited"
     themes: [
       "one-dark-ui"


Remove linter settings

Remove linter settings

--- atom/config.cson
+++ atom/config.cson
@@ -32,11 +32,10 @@
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
-  "linter-rubocop":
-    command: "/Users/jan/.rbenv/shims/rubocop"
   "fuzzy-finder": {}
   "autocomplete-plus": {}
   "one-dark-ui":
     layoutMode: "Compact"
   tabs:
     enableVcsColoring: true
+  react: {}


Settings got re-ordered

Settings got re-ordered

--- atom/config.cson
+++ atom/config.cson
@@ -1,24 +1,25 @@
 "*":
-  "exception-reporting":
-    userId: "30e6f2d0-42d3-ab77-4984-dde4c431f019"
-  welcome:
-    showOnStartup: false
   autosave:
     enabled: true
   core:
-    projectHome: "/Users/randy/src"
     audioBeep: false
     excludeVcsIgnoredPaths: false
     themes: [
       "one-light-ui"
       "one-light-syntax"
     ]
+    projectHome: "/Users/randy/src"
   editor:
-    invisibles: {}
     fontFamily: "Hack"
     fontSize: 19
+    invisibles: {}
     showIndentGuide: true
     tabType: "soft"
+  emmet: {}
+  "exception-reporting":
+    userId: "30e6f2d0-42d3-ab77-4984-dde4c431f019"
+  "expand-region": {}
+  "git-blame": {}
   "git-diff":
     showIconsInEditorGutter: true
   "ruby-test":
@@ -28,15 +29,24 @@
     rspecSingleCommand: "bundle exec rspec --tty {relative_path}:{line_number}"
     specFramework: "rspec"
   "git-blame": {}
+  "git-plus": {}
+  linter:
+    errorPanelHeight: 26
+  "linter-eslint": {}
   "linter-sass-lint":
     noConfigDisable: true
   "markdown-deluxe":
     fontFamily: "Hack"
-  "expand-region": {}
+  "package-sync":
+    forceOverwrite: true
+  "terminal-plus":
+    core: {}
+    style: {}
+  "toggle-quotes": {}
   "vim-mode":
     startInInsertMode: true
-  "package-sync":
-    forceOverwrite: true
+  welcome:
+    showOnStartup: false
 "source.gfm":
   editor:
     softWrap: true


router: popstate binding improvements

router: popstate binding improvements

--- lib/core/router.coffee
+++ lib/core/router.coffee
@@ -20,8 +20,6 @@
   revive =(objRef, callback)->
     unless objRef?.constructorName? and objRef.id? then callback null
     else KD.remote.cacheable objRef.constructorName, objRef.id, callback
-
-  isWindowLoaded = -> document.readyState is 'complete'

   constructor:(routes)->

@@ -46,18 +44,11 @@
     @startListening()

   popState:(event)->
-
-    # Safari fires extra popstate event right after window is loaded
-    # Let's skip popstate handling at that moment
-    return  if isWindowLoaded() and @blockPopstateIfWindowLoaded
-
     revive event.state, (err, state)=>
       return KD.showError err  if err
       @handleRoute "#{location.pathname}#{location.search}",
         shouldPushState   : no
         state             : state
-
-  handleWindowLoad: -> KD.utils.defer => @blockPopstateIfWindowLoaded = no

   clear:(route = '/', replaceState = yes)->
     delete @userRoute # TODO: i hope deleting the userRoute here doesn't break anything... C.T.
@@ -65,20 +56,25 @@

   back:-> if @visitedRoutes.length <= 1 then @clear() else history.back()

-  startListening:->
-    return no  if @isListening # make this action idempotent
-    @isListening = yes
+  startListening: do ->

-    if isWindowLoaded()
-      @blockPopstateIfWindowLoaded = no
-    else
-      @blockPopstateIfWindowLoaded = yes
-      window.addEventListener 'load', @bound 'handleWindowLoad'
+    readyStateBinded = no

-    # we need to add a listener to the window's popstate event:
-    window.addEventListener 'popstate', @bound "popState"
+    ->
+      return no  if @isListening # make this action idempotent

-    return yes
+      # Safari fires extra popstate event right after window is loaded
+      # this is to avoid this inconsistent initial firing
+      if document.readyState isnt 'complete' and not readyStateBinded
+        readyStateBinded = yes
+        return document.addEventListener 'readystatechange', =>
+          KD.utils.defer => @startListening()  if document.readyState is 'complete'
+
+      @isListening = yes
+      window.addEventListener 'popstate', @bound "popState"
+
+      return yes
+

   stopListening:->
     return no  unless @isListening # make this action idempotent


Fix updating project page on push

Fix updating project page on push

--- app/static/coffee/views.coffee
+++ app/static/coffee/views.coffee
@@ -433,36 +433,43 @@
             @initReloads()
             @renderToken()

+            @fetchCollection (collection) =>
+                    @renderTaskLines()
+
+                    @plotData = @getPlotData()
+                    @trigger 'renderPartFinished', 'plotData'
+
+                    @renderCharts()
+                    prettyPrint()
+
+        initProgressBar: ->
+            ### Init progress bar and subscribe to updates ###
+            NProgress.start()
+            NProgress.inc()
+
+            @on 'renderPartFinished', NProgress.inc
+            @on 'renderFinished', NProgress.done
+            @on 'renderReload', NProgress.done
+
+        initReloads: ->
+            ### Init view reloads on push ###
+            @options.push.on 'task', (task) =>
+                if task.project == @options.project
+                    @fetchCollection =>
+                        @renderTaskLines()
+                        @trigger 'renderReload'
+
+        fetchCollection: (callback) ->
+            ### Fetch collection ###
             @options.collection.fetch
                 data:
                     limit: 0
                     project: @options.project
                     with_violations: true
+                reset: true
                 success: (collection) =>
                     @options.collection = collection
-                    @renderTaskLines()
-
-                    @plotData = @getPlotData()
-                    @trigger 'renderPartFinished', 'plotData'
-
-                    @renderCharts()
-                    prettyPrint()
-
-        initProgressBar: ->
-            ### Init progress bar and subscribe to updates ###
-            NProgress.start()
-            NProgress.inc()
-
-            @on 'renderPartFinished', NProgress.inc
-            @on 'renderFinished', NProgress.done
-            @on 'renderReload', NProgress.done
-
-        initReloads: ->
-            ### Init view reloads on push ###
-            @options.push.on 'task', (task) =>
-                if task.project == @options.project
-                    @renderTaskLines()
-                    @trigger 'renderReload'
+                    callback.call @, collection

         renderToken: ->
             ### Render generate token view ###


Add basename to history for subdirectory deploys

--- app/main.cjsx
+++ app/main.cjsx
@@ -1,11 +1,15 @@
 React = require 'react'
 ReactDOM = {render} = require 'react-dom'
 {Router, Route, Link} = require 'react-router'
+{useBasename} = require 'history'
 createBrowserHistory = require 'history/lib/createBrowserHistory'
 routes = require './router'

 routes = require './router'
-history = createBrowserHistory()
+
+basename = if process.env.DEPLOY_SUBDIR? then "/panoptes-front-end/#{process.env.DEPLOY_SUBDIR}/" else ''
+
+history = useBasename(createBrowserHistory)({basename})

 if process.env.NON_ROOT isnt 'true' and window.location? and window.location.hash isnt ""
   window.location.pathname = window.location.hash.slice(1)


add sass managing functions

add sass managing functions

--- app/templates/_Gulpfile.coffee
+++ app/templates/_Gulpfile.coffee
@@ -15,6 +15,7 @@
 pngquant    = require "imagemin-pngquant"

 pkg = require './package.json'
+conf = require 'config.json'
 try
 	pvt = require 'private.json'
 catch err
@@ -28,23 +29,23 @@
 #*------------------------------------*/
 gulp.task 'sass', () ->
 	gulp.task "sass", () ->
-  gulp.src([pkg.path.scss + "/style.scss"])
+  gulp.src([conf.path.scss + "/style.scss"])
     .pipe(plumber())
     .pipe(sass({errLogToConsole: true}))
-    .pipe(gulp.dest(pkg.path.css))
+    .pipe(gulp.dest(conf.path.css))
     .pipe reload({stream: true})
 # 	sass:
 # 		options:
 # 			quiet: false,
-# 			cacheLocation: '<%= pkg.path.scss %>/.sass-cache'
+# 			cacheLocation: '<%= conf.path.scss %>/.sass-cache'
 # 		dist:
 # 			files:
-# 				'<%= pkg.path.css %>/style.css': '<%= pkg.path.scss %>/style.scss'
+# 				'<%= conf.path.css %>/style.css': '<%= conf.path.scss %>/style.scss'
 # 		minify:
 # 			options:
 # 				style: 'compressed'
 # 			files:
-# 				'<%= pkg.path.css %>/style.min.css': '<%= pkg.path.scss %>/style.scss'
+# 				'<%= conf.path.css %>/style.min.css': '<%= conf.path.scss %>/style.scss'

 #*------------------------------------*\
@@ -53,7 +54,7 @@
 #		-https://www.npmjs.com/package/gulp-imagemin
 #*------------------------------------*/
 gulp.task('imagemin', () ->
-	return gulp.src(pkg.path.img+'/*')
+	return gulp.src(conf.path.img+'/*')
 		.pipe imagemin {
 			optimizationLevel: 3,
 			progressive: true,
@@ -65,14 +66,14 @@
 	    ],
 			use: [pngquant()]
 		}
-		.pipe gulp.dest pkg.path.img
+		.pipe gulp.dest conf.path.img
 # 	imageoptim:
 # 		options:
 # 			imageAlpha: true,
 # 			# jpegMini: true,
 # 			quitAfter: true
 # 		files:
-# 			['<%= pkg.path.img %>']
+# 			['<%= conf.path.img %>']
 # 	svgmin:
 # 		options:
 # 			plugins: [{
@@ -81,9 +82,9 @@
 # 		dist:
 # 			files: [{
 # 				expand: true,
-# 				cwd: '<%= pkg.path.img %>',
+# 				cwd: '<%= conf.path.img %>',
 # 				src: ['**/*.svg'],
-# 				dest: '<%= pkg.path.img %>'
+# 				dest: '<%= conf.path.img %>'
 # 			}]

@@ -92,35 +93,37 @@
 #*------------------------------------*/
 gulp.task 'shell', shell.task [
 	"cd styleguide/public",
-	'ln -s ../../' + pkg.path.assets + ' assets'
+	'ln -s ../../' + conf.path.assets + ' assets'
 ].join('&&')
 # 	shell:
 # 		styleSymlinks:
 # 			command: [
 # 				'cd styleguide/public'
-# 				'ln -s ../../<%= pkg.path.assets %> assets'
+# 				'ln -s ../../<%= conf.path.assets %> assets'
 # 			].join('&&'),
 # 			options:
 # 				stdout:true

-
+# READ HERE
+# 1:39:12 PM - Lawrence Botha: […] https://www.npmjs.com/package/gulp-newer
+# 1:39:21 PM - Lawrence Botha: https://github.com/gulpjs/gulp#incremental-builds
 #*------------------------------------*\
 #   $CONTRIB-COFFEE
 #*------------------------------------*/
 gulp.task "coffee", () ->
-	gulp.src([pkg.+"**/*.coffee"])
+	gulp.src([conf.path.coffee+"/**/*.coffee"])
 		.pipe(plumber())
 		.pipe(coffee({bare: true})).on('error', gutil.log)
 		.pipe(concat("built#{ext}.js"))
 		.pipe( if isProd then uglify() else gutil.noop() )
-		.pipe(gulp.dest("./#{themePath}/assets/javascripts"))
+		.pipe(gulp.dest(conf.path.js))
 # 	coffee:
 # 		dist:
 # 			files: [{
 # 				expand: true
-# 				cwd: '<%= pkg.path.coffee %>'
+# 				cwd: '<%= conf.path.coffee %>'
 # 				src: '{,*/}*.coffee'
-# 				dest: '<%= pkg.path.js %>'
+# 				dest: '<%= conf.path.js %>'
 # 				ext: '.js'
 # 			}]

@@ -135,10 +138,10 @@

 # 	watch:
 # 		css:
-# 			files: ['<%= pkg.path.scss %>/**/*.scss'],
+# 			files: ['<%= conf.path.scss %>/**/*.scss'],
 # 			tasks: ['sass:dist']
 # 		coffee:
-# 			files: ['<%= pkg.path.coffee %>/**/*.coffee'],
+# 			files: ['<%= conf.path.coffee %>/**/*.coffee'],
 # 			tasks: ['coffee:dist']

@@ -148,7 +151,7 @@
 # 	uglify:
 # 		target:
 # 			files:
-# 				'<%= pkg.path.js %>/main.min.js': '<%= pkg.path.js %>/main.js'
+# 				'<%= conf.path.js %>/main.min.js': '<%= conf.path.js %>/main.js'

 #*------------------------------------*\
@@ -162,7 +165,7 @@
 # 				user: "root"
 # 				pass: "password"
 # 				host: "localhost"
-# 				backup_to: '<%= pkg.path.db_backup %>/dev/dev_<%= grunt.template.date("yyyymmdd-HHmmss") %>.sql'
+# 				backup_to: '<%= conf.path.db_backup %>/dev/dev_<%= grunt.template.date("yyyymmdd-HHmmss") %>.sql'
 # 		prod:
 # 			options:
 # 				title: "prod db"
@@ -171,7 +174,7 @@
 # 				pass: "<%= pvt.db_prod.pass %>"
 # 				host: "<%= pvt.db_prod.host %>"
 # 				ssh_host: "<%= pvt.username %>@<%= pvt.domain %>"
-# 				backup_to: '<%= pkg.path.db_backup %>/prod/<%= pvt.db_prod.name %>_<%= grunt.template.date("yyyymmdd-HHmmss") %>.sql'
+# 				backup_to: '<%= conf.path.db_backup %>/prod/<%= pvt.db_prod.name %>_<%= grunt.template.date("yyyymmdd-HHmmss") %>.sql'

 #*------------------------------------*\


Tweak

Tweak

--- memory_backbone_sync.coffee
+++ memory_backbone_sync.coffee
@@ -60,9 +60,5 @@
     sync['initialize']()
     sync[method].apply(sync, Array::slice.call(arguments, 1))

-  require('./lib/model_extensions')(model_type, sync_fn)
-
-  if cache
-    return require('./cache_sync')(model_type, sync_fn)
-  else
-    return sync_fn+  require('./lib/model_extensions')(model_type, sync_fn) # mixin extensions
+  return if cache then require('./cache_sync')(model_type, sync_fn) else sync_fn


Test syncing different returnInfo changes (add, set) at once.

--- src/spec/utils/order-utils.spec.coffee
+++ src/spec/utils/order-utils.spec.coffee
@@ -33,7 +33,7 @@
     centAmount: 999
   returnInfo: [{
     returnTrackingId: "bla blubb"
-    returnDate: new Date()
+    returnDate: new Date().toISOString()
     items: [{
       id: uniqueId 'ri'
       quantity: 11
@@ -62,7 +62,7 @@
 describe "OrderUtils.actionsMapStatuses", ->
   beforeEach ->
     @utils = new OrderUtils
-    @order = order
+    @order = JSON.parse(JSON.stringify(order))

   afterEach ->
     @utils = null
@@ -149,3 +149,64 @@
         }
       ]
     expect(update).toEqual expectedUpdate
+
+  it "should return required actions for syncing returnInfo and shipmentState", ->
+
+    orderChanged = JSON.parse(JSON.stringify(@order))
+
+    # add a 2nd returnInfo
+    orderChanged.returnInfo.push
+      returnTrackingId: "bla blubb"
+      returnDate: new Date().toISOString()
+      items: [{
+        id: uniqueId 'ri'
+        quantity: 111
+        lineItemId: 1
+        comment: 'Product doesnt have enough mojo.'
+        shipmentState: 'Advised'
+        paymentState: 'Initial'
+      }
+      {
+        id: uniqueId 'ri'
+        quantity: 222
+        lineItemId: 2
+        comment: 'Product too small.'
+        shipmentState: 'Advised'
+        paymentState: 'Initial'
+      }
+      {
+        id: uniqueId 'ri'
+        quantity: 333
+        lineItemId: 3
+        comment: 'Product too big.'
+        shipmentState: 'Advised'
+        paymentState: 'Initial'
+      }]
+
+    # change shipment status of existing returnInfo
+    orderChanged.returnInfo[0].items[0].shipmentState = "Returned"
+    orderChanged.returnInfo[0].items[1].shipmentState = "Unusable"
+
+    delta = @utils.diff(@order, orderChanged)
+    update = @utils.actionMapReturnInfo(delta, orderChanged)
+
+    addAction = JSON.parse(JSON.stringify(orderChanged.returnInfo[1]))
+    addAction["action"] = "addReturnInfo"
+
+    expectedUpdate =
+      [
+        {
+          action: "setReturnShipmentState"
+          returnItemId: orderChanged.returnInfo[0].items[0].id
+          shipmentState: orderChanged.returnInfo[0].items[0].shipmentState
+        }
+        {
+          action: "setReturnShipmentState"
+          returnItemId: orderChanged.returnInfo[0].items[1].id
+          shipmentState: orderChanged.returnInfo[0].items[1].shipmentState
+        }
+        addAction
+      ]
+
+
+    expect(update).toEqual expectedUpdate


Spec in sample app should use improved stubView.

--- lib/calatrava/templates/kernel/spec/converter/controller.converter.spec.coffee
+++ lib/calatrava/templates/kernel/spec/converter/controller.converter.spec.coffee
@@ -33,5 +33,5 @@
       views.conversionForm.trigger 'convert'

     it 'should render the correctly converted amount', ->
-      expect(views.conversionForm.render).toHaveBeenCalledWith
+      expect(views.conversionForm.lastMessage()).toEqual
         out_amount: 96


Add missing close button

Add missing close button

--- app/collections/manager-icon.cjsx
+++ app/collections/manager-icon.cjsx
@@ -13,15 +13,21 @@
   getInitialState: ->
     open: false

+  open: ->
+    @setState {open: true}
+
+  close: ->
+    @setState {open: false}
+
   render: ->
     <button
       className="collections-manager-icon"
       title="Collect"
-      onClick={@setState.bind this, {open: true}, null}>
+      onClick={@open}>
       <i className="fa fa-list" />

       {if @state.open
-        <Dialog tag="div">
-          <CollectionsManager user={@props.user} project={@props.project} subject={@props.subject} onSuccess={@setState.bind this, {open: false}, null} />
+        <Dialog tag="div" closeButton={true} onCancel={@close}>
+          <CollectionsManager user={@props.user} project={@props.project} subject={@props.subject} onSuccess={@close} />
         </Dialog>}
     </button>


:lipstick: Cleanup.

:lipstick: Cleanup.

- Don't need this.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -1,6 +1,5 @@
 path = require 'path'
 {WorkspaceView} = require 'atom'
-Jumpy = require '../lib/jumpy'

 NUM_ALPHA_TEST_WORDS = 26 * 3
 NUM_ENGLISH_TEXT = 8 - 2 #For a's that are only 1 character.  *'s don't count.


Angle property now working for square

Angle property now working for square

--- bokehjs/src/coffee/renderer/glyph/marker/square.coffee
+++ bokehjs/src/coffee/renderer/glyph/marker/square.coffee
@@ -3,15 +3,21 @@

 class SquareView extends Marker.View

-  _render: (ctx, indices, {sx, sy, size}) ->
+  _render: (ctx, indices, {sx, sy, size, angle}) ->
     for i in indices
       if isNaN(sx[i]+sy[i]+size[i])
         continue

-      ctx.translate(sx[i], sy[i])
+      ctx.beginPath()

-      ctx.beginPath()
-      ctx.rect(-size[i]/2, -size[i]/2, size[i], size[i])
+      if angle[i]
+        ctx.translate(sx[i], sy[i])
+        ctx.rotate(angle[i])
+        ctx.rect(-size[i]/2, -size[i]/2, size[i], size[i])
+        ctx.rotate(-angle[i])
+      else
+        ctx.translate(sx[i], sy[i])
+        ctx.rect(-size[i]/2, -size[i]/2, size[i], size[i])

       if @visuals.fill.do_fill
         @visuals.fill.set_vectorize(ctx, i)
@@ -30,4 +36,4 @@

 module.exports =
   Model: Square
-  View: SquareView+  View: SquareView


Added a first test for getMatches

Added a first test for getMatches

--- spec/unit/build.coffee
+++ spec/unit/build.coffee
@@ -58,4 +58,47 @@

+describe 'build.getMatches', ->

+  it 'should parse input data for filenames, spaces, params and files', () ->
+    res = build.getMatches """
+      <html>
+        <!--apa a b   c
+
+          x.js @ d e   f
+          /y.css @ file.ext i j k
+          /z.foobar @ l m data.something n
+        -->
+        test
+        <!--apa banan
+        -->
+
+      </html>
+    """, '<!--apa', '-->'
+
+    res.should.eql([{
+      match: '  <!--apa a b   c\n\n    x.js @ d e   f\n    /y.css @ file.ext i j k\n    /z.foobar @ l m data.something n\n  -->'
+      filename: undefined
+      spaces: '  '
+      params: ['a', 'b', 'c']
+      files: [{
+        name: 'x.js'
+        params: ['d', 'e', 'f']
+        spaces: '    '
+      }, {
+        name: '/y.css'
+        params: ['file.ext', 'i', 'j', 'k']
+        spaces: '    '
+      }, {
+        name: '/z.foobar'
+        params: ['l', 'm', 'data.something', 'n']
+        spaces: '    '
+      }]
+    }, {
+      match: '  <!--apa banan\n  -->'
+      filename: undefined
+      spaces: '  '
+      params: ['banan']
+      files: []
+    }])
+


fix error : 'Uncaught Error: Can't save buffer with no file path'

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -92,7 +92,7 @@
       @renderHTMLCode()

   renderHTMLCode: (text) ->
-    if not atom.config.get("atom-html-preview.triggerOnSave") then @editor.save()
+    if not atom.config.get("atom-html-preview.triggerOnSave") and @editor.getPath()? then @editor.save()
     iframe = document.createElement("iframe")
     # Fix from @kwaak (https://github.com/webBoxio/atom-html-preview/issues/1/#issuecomment-49639162)
     # Allows for the use of relative resources (scripts, styles)


Guard against no login matches

Guard against no login matches

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -87,7 +87,7 @@
       repo = repository
     else
       repo = repository.url
-    repo.match(loginRegex)[1]
+    repo.match(loginRegex)?[1] ? ''

   loadCachedMetadata: ->
     @client.avatar @ownerFromRepository(@pack.repository), (err, path) =>


Adding a no data logo if the no data is located zooming to the feature maps after loading

--- public/scripts/views/SensorPositionView.coffee
+++ public/scripts/views/SensorPositionView.coffee
@@ -33,7 +33,7 @@
         View Sensor Modules

       """)
       .addTo @markers
-      do @fitBounds
+      @fitBounds @markers

   ###
   Event handler for  clicks in leaflet popups. Open a detailed sensor view


Incrementally compile eco files

Incrementally compile eco files

--- bokehjs/Gruntfile.coffee
+++ bokehjs/Gruntfile.coffee
@@ -162,7 +162,9 @@
           src : ['**/*.eco']
           ext : '.js'
           dest: 'build/js'
+          filter: hasChanged("eco.app.files.0")
         ]
+
   grunt.loadNpmTasks("grunt-contrib-coffee")
   grunt.loadNpmTasks("grunt-contrib-watch")
   grunt.loadNpmTasks("grunt-contrib-less")


And make it rewrites work properly when the source is not path matching

--- lib/load-config.coffee
+++ lib/load-config.coffee
@@ -71,11 +71,14 @@
       else
         match =
           target: host.default
+          _path: "/"
           rewrite: (reqUrl)->
             if @target.nopath
-              "/"
+              @target.pathname
+            else if @target.nostrippath
+              path.join @target.pathname, reqUrl
             else
-              reqUrl
+              path.join @target.pathname, reqUrl.substring(@_path.length)

       match


remove unused import in dpcu16exe.coffee

--- src/dcpu16exec.coffee
+++ src/dcpu16exec.coffee
@@ -1,5 +1,4 @@
 dcpu16 = require("dcpu16")
-fs = require("fs")

 exports.runAssembly = (asm_code) ->
   cpu = new dcpu16.CPU()


Add heightOfString method that uses the line wrapper without actually adding content to the document.

Very useful for layout purposes

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -24,7 +24,7 @@
     @y -= @currentLineHeight(true) * lines + @_lineGap
     return this

-  text: (text, x, y, options) ->
+  _text: (text, x, y, options, lineCallback) ->
     options = @_initOptions(x, y, options)

     # Convert text to a string
@@ -39,7 +39,7 @@
       wrapper = @_wrapper
       unless wrapper
         wrapper = new LineWrapper(this, options)
-        wrapper.on 'line', @_line.bind(this)
+        wrapper.on 'line', lineCallback

       @_wrapper = if options.continued then wrapper else null
       @_textOptions = if options.continued then options else null
@@ -47,9 +47,28 @@

     # render paragraphs as single lines
     else
-      @_line line, options for line in text.split '\n'
-
-    return this
+      lineCallback line, options for line in text.split '\n'
+
+    return this
+
+  text: (text, x, y, options) ->
+    @_text text, x, y, options, @_line.bind(this)
+
+  widthOfString: (string, options = {}) ->
+    @_font.widthOfString(string, @_fontSize) + (options.characterSpacing or 0) * (string.length - 1)
+
+  heightOfString: (text, options) ->
+    {x,y} = this
+    lineGap = options.lineGap or @_lineGap or 0
+
+    @_text text, 0, 0, options, (line, options) =>
+      @y += @currentLineHeight(true) + lineGap
+
+    height = @y # we started at y=0
+    @x = x
+    @y = y
+
+    return height

   list: (list, x, y, options, wrapper) ->
     options = @_initOptions(x, y, options)
@@ -135,10 +154,7 @@
     options.columnGap ?= 18 # 1/4 inch

     return options
-
-  widthOfString: (string, options = {}) ->
-    @_font.widthOfString(string, @_fontSize) + (options.characterSpacing or 0) * (string.length - 1)
-
+
   _line: (text, options = {}, wrapper) ->
     @_fragment text, @x, @y, options
     lineGap = options.lineGap or @_lineGap or 0


RepoView refreshes after branch creation.

--- lib/models/repo.coffee
+++ lib/models/repo.coffee
@@ -130,6 +130,7 @@
       callback: (name) ->
         git.cmd "checkout -b #{name}"
         .catch (error) -> new ErrorView(error)
+        .done -> atom.workspaceView.trigger 'atomatigit:refresh'

   # Public: Initiate a user defined git command.
   initiateGitCommand: =>


:up: atom-package-deps changes

:up: atom-package-deps changes

Specifying the package name is no longer required.

--- lib/init.coffee
+++ lib/init.coffee
@@ -17,7 +17,7 @@
       default: false

   activate: ->
-    require('atom-package-deps').install('linter-elixirc')
+    require('atom-package-deps').install()
     @subscriptions = new CompositeDisposable
     @subscriptions.add atom.config.observe 'linter-elixirc.elixircPath',
       (elixircPath) =>


spread assign list fix

spread assign list fix

--- scripts/pagerv2_commands.coffee
+++ scripts/pagerv2_commands.coffee
@@ -173,13 +173,15 @@
     .then (data) ->
       assigned = data.incident.assignments.map (i) ->
         i.assignee.summary
+      if assigned.length > 0
+        assigned = " (#{assigned.join(', ')})"
       origin = pagerv2.colorer(
         robot.adapterName,
         data.incident.status,
         "[#{data.incident.service.summary}] "
         )
-      res.send "#{origin}#{data.incident.id} #{data.incident.summary} - #{data.incident.status} " +
-               "(#{assigned.join(', ')})"
+      res.send "#{origin}#{data.incident.id} #{data.incident.summary} - #{data.incident.status}" +
+               "#{assigned}"
     .catch (e) ->
       res.send e
     res.finish()


implement pairwise history

implement pairwise history

--- app/scripts/services/robot.coffee
+++ app/scripts/services/robot.coffee
@@ -67,6 +67,9 @@
           ans.push closestValue(time)
         return ans

+      historyPairs = (time = 10000) ->
+        _map(_updateHistory, (event) -> [event.time, event.value])
+
       # update the Peripheral
       update = (channel, update) ->
         # insert the update in sorted order


fixed "msg" undefined error

fixed "msg" undefined error

--- app/templates/src/template.coffee
+++ app/templates/src/template.coffee
@@ -18,5 +18,5 @@
   robot.respond /hello/, (msg) ->
     msg.reply "hello!"

-  robot.hear /orly/, ->
+  robot.hear /orly/, (msg) ->
     msg.send "yarly"


Added get to an options object in the maps constructor

--- lib/main.coffee
+++ lib/main.coffee
@@ -9,12 +9,11 @@
 require('sensor-maps/marker')
 require('sensor-maps/vehicle_marker')

-
-
 SM.Map = SC.Object.extend
   init: ->
     @_super()
-    map = new L.Map @get('divId')
+    options = @get('options') || {}
+    map = new L.Map(@get('divId'), options)
     cloudmadeTiles = new L.TileLayer "http://{s}.tile.cloudmade.com/#{SM.CLOUDMADE_API_KEY}/998/256/{z}/{x}/{y}.png",
       maxZoom: 18
     map.addLayer cloudmadeTiles


fix bug reading pathToJar

fix bug reading pathToJar

--- lib/main.coffee
+++ lib/main.coffee
@@ -45,7 +45,7 @@

       @getPath (stdout) ->
         nslJar  = atom.config.get('language-nsl.pathToJar')
-        if !nslJar?
+        if not nslJar
           atom.notifications.addError("**language-nsl**: no valid `nsL.jar` was specified in your config", dismissable: false)
           return


Renamed password reset token to reset-token to avoid conflicts with the csrf token

--- web/coffee/account.coffee
+++ web/coffee/account.coffee
@@ -7,7 +7,7 @@
 resetPassword = (e) ->
   e.preventDefault()
   form = $("#password-reset-form").serializeObject()
-  form.token = window.location.hash.substring(1)
+  form["reset-token"] = window.location.hash.substring(1)
   apiCall "POST", "/api/user/confirm_password_reset", form
   .done (data) ->
     apiNotify data, "/login"


Removed the eligibility cs.

Removed the eligibility cs.

--- web/coffee/profile.coffee
+++ web/coffee/profile.coffee
@@ -11,8 +11,6 @@
         ga('send', 'event', 'Team', 'LoadFailure', data.message)
       when 1
         $("#team-info").html renderTeamInformation({data: data.data})
-        x = "
" + ("
" + _.escape(j) + "
" for j in data.data.justification).join("") + "
"
-        $('#eligibility-exp').data "content", x

 load_group_info = ->
   apiCall "GET", "/api/group/list"


state change events

state change events

--- src/game_state.coffee
+++ src/game_state.coffee
@@ -57,7 +57,7 @@
     [I.objects, toRemove] = I.objects.partition (object) ->
       object.update()

-    I.objects.invoke "trigger", "afterUpdat"
+    I.objects.invoke "trigger", "afterUpdate"

     toRemove.invoke "trigger", "remove"


Better coffee-script error messages

Better coffee-script error messages

--- assets/js/lib/compilers.coffee
+++ assets/js/lib/compilers.coffee
@@ -40,19 +40,24 @@
             lang: "javascript"
             title: "Compiled coffee-script"
         catch error
-          matches = error.message.match /^(.+) on line ([0-9]+)\: (.+)$/i
+          console.log "Error", error
           lines = code.split(/\r?\n/)
-          err =
-            line: parseInt(matches[2], 10)
-            reason: matches[3]

-          err.evidence = $.trim(lines[err.line - 1])
-          err.character = lines[err.line - 1].indexOf(err.evidence)
-
-          return cb null,
-            type: "html"
-            body: buildErrorReport([err])
-            title: "Coffee errors"
+          if matches = error.message.match /^([^,]+)(?:, (starting))? on line ([0-9]+)(?:\: (.+))?$/i
+            console.log "1", matches
+            err =
+              line: parseInt(matches[3], 10)
+              reason: matches[4] or matches[1]
+
+            err.evidence = $.trim(lines[err.line - 1])
+            err.character = if matches[2] == "starting" then lines[err.line - 1].length else lines[err.line - 1].indexOf(err.evidence)
+
+            return cb null,
+              type: "html"
+              body: buildErrorReport([err])
+              title: "Coffee errors"
+          else
+            return cb error
         return

     markdown: (code, cb) ->


unloadRecord -> unload

unloadRecord -> unload

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -14,9 +14,7 @@
     @set('state', 'signed-out')
     @set('user', undefined)
     if user = Travis.__container__.lookup('controller:currentUser').get('content')
-      if user.get('stateManager.currentPath') == 'rootState.loaded.updated.uncommitted'
-        user.send('rollback')
-      user.unloadRecord()
+      user.unload()
     Travis.__container__.lookup('controller:currentUser').set('content', null)
     if router = Travis.__container__.lookup('router:main')
       router.send('afterSignOut')


Don't parse user if data can't be fetched from storage (closes #124)

--- assets/scripts/app/auth.coffee
+++ assets/scripts/app/auth.coffee
@@ -24,7 +24,8 @@
     @setData(data) if data

   userDataFrom: (storage) ->
-    user  = JSON.parse storage.getItem('travis.user')
+    userJSON = storage.getItem('travis.user')
+    user  = JSON.parse userJSON if userJSON?
     user  = user.user if user?.user
     token = storage.getItem('travis.token')
     if user && token && @validateUser(user)


Fix references

Fix references

--- client/import.coffee
+++ client/import.coffee
@@ -70,9 +70,9 @@
             , (err) ->
               throw err if err

-              _amountOfImportsFinished += 1
+              template._amountOfImportsFinished += 1

-              if _amountOfImports == _amountOfImportsFinished
+              if template._amountOfImports == template._amountOfImportsFinished
                 if template._amountOfImports > 1
                   Meteor.Router.to Meteor.Router.profilePath Meteor.personId()
                 else


Updated version

Updated version

--- server/d2mpclient/socket.coffee
+++ server/d2mpclient/socket.coffee
@@ -1,6 +1,6 @@
 Fiber = Npm.require('fibers')
 ws = Meteor.require('ws').Server
-clientVersion = "0.1.3"
+clientVersion = "0.1.8"

 clientSockets = {}
 @installMod = (client, mod)->


:arrow_up: Upgrade specs to match latest behavior

--- spec/custom-element-spec.coffee
+++ spec/custom-element-spec.coffee
@@ -117,12 +117,10 @@
     expect(typeof element.string).toBe('string')
     expect(typeof element.boolean).toBe('boolean')

-  it 'accepts a proto', ->
+  it 'sets unknown properties right on the element', ->
     new (registerCustomElement({
       name: 'x-spec-accepts-a-proto'
       created: ->
         expect(typeof this.a).toBe('function')
-      proto: {
-        a: ->
-      }
+      a: ->
     }))


From atom v1.17.0 no pane change happen on preview

--- lib/provider/provider-base.coffee
+++ lib/provider/provider-base.coffee
@@ -1,3 +1,5 @@
+WorkspaceOpenAcceptPaneOption = atom.workspace.getCenter?
+
 _ = require 'underscore-plus'
 {Point, CompositeDisposable} = require 'atom'
 {
@@ -219,14 +221,20 @@
       return Promise.resolve(@editor)

     filePath ?= @editor.getPath()
-    originalActivePane = atom.workspace.getActivePane() unless activatePane
-
-    # NOTE: See #107 activate pane where I want to open first is SUPER important
-    pane.activate()
-
-    atom.workspace.open(filePath, pending: true).then (editor) ->
-      # Refocus to originalActivePane if necessary
-      originalActivePane.activate() unless activatePane
+
+    openOptions = {pending: true, activatePane: activatePane}
+    if WorkspaceOpenAcceptPaneOption
+      openOptions.pane = pane
+    else
+      # NOTE: See #107
+      # Whe Atom is old and workspace.open not accept pane options to open URI.
+      # We need to activate that pane first.
+      # Otherwise, when original pane have item for that URL, it open that item on current pane.
+      originalActivePane = atom.workspace.getActivePane() unless activatePane
+      pane.activate()
+
+    atom.workspace.open(filePath, openOptions).then (editor) ->
+      originalActivePane?.activate()
       return editor

   confirmed: (item) ->


Fix for setting activity hours.

Fix for setting activity hours.


Avoid adding an unpublished class when theres no flag present

--- models/search_result.coffee
+++ models/search_result.coffee
@@ -67,10 +67,11 @@
       @set location: "#{fair.href()}/browse#{@get('location')}"

   publishedClass: ->
-    if @get('published')
-      'published-search-result'
-    else
-      'unpublished-search-result'
+    if @has 'published'
+      if @get 'published'
+        'published-search-result'
+      else
+        'unpublished-search-result'

   href: ->
     if @get('published')


Prevent dependencies to be added multiple times to the list

--- assets/js/services/updater.coffee
+++ assets/js/services/updater.coffee
@@ -97,9 +97,10 @@
         unless entry.currentVer == pkgRef.ver.semver then list = self.obsolete
         else list = self.current

-        if options.parent and not (0 > idx = list.indexOf(options.parent))
-          list.splice(Math.max(0, idx - 1), 0, entry)
-        else list.push(entry)
+        if list.indexOf(entry) < 0
+          if options.parent and not (0 > idx = list.indexOf(options.parent))
+            list.splice(Math.max(0, idx - 1), 0, entry)
+          else list.push(entry)

         childPromises = []
         childPromises.push(self.addRequired(dependency, parent: entry)) for dependency in pkgRef.ver.dependencies


Attempt at #139 again

Attempt at #139 again

--- lib/views/branch-list-view.coffee
+++ lib/views/branch-list-view.coffee
@@ -58,7 +58,7 @@
       stderr: (data) =>
         notifier.addSuccess data.toString()
         atom.workspace.observeTextEditors (editor) =>
-          if filepath = editor.getPath()
+          if filepath = editor.getPath()?.toString()
             fs.exists filepath, (exists) =>
               editor.destroy() if not exists
         git.refresh()


var name fix

var name fix

--- client/app/Applications/Environments.kdapplication/views/DomainMapperView.coffee
+++ client/app/Applications/Environments.kdapplication/views/DomainMapperView.coffee
@@ -41,18 +41,16 @@
     options.cssClass = 'domain-vm-item'
     super options, data

-    listViewData = @getDelegate().getData()
-    switchStatus = if @getData().hostnameAlias in listViewData.hostnameAliases then on else off
-    domainIns    = listViewData.domain
-
-    console.log domainIns
+    listViewData   = @getDelegate().getData()
+    switchStatus   = if @getData().hostnameAlias in listViewData.hostnameAliases then on else off
+    domainInstance = listViewData.domain

     @onOff = new KDOnOffSwitch
       size        : 'small'
       labels      : ['CON', "DCON"]
       defaultValue: switchStatus
       callback : (state) =>
-        domainIns.bindVM
+        domainInstance.bindVM
           vmName     : @getData().name
           state      : state
         , (err) =>


Make it disable if described as comingsoon

--- client/landing/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
+++ client/landing/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
@@ -124,6 +124,9 @@

   constructor:(options, data)->

+    if data.type is 'comingsoon'
+      data.disabled = yes
+
     super options, data

     @img.$().attr "src", "/images/#{data.icon}"
@@ -165,7 +168,7 @@
     if type is 'koding-app'
       @showLoader()
       appManager.openApplication path, => @hideLoader()
-    else if type is 'comingsoon'
-      new KDNotificationView
-        title : 'Coming Soon!'
+    # else if type is 'comingsoon'
+    #   new KDNotificationView
+    #     title : 'Coming Soon!'
     return no


fix prop lookup in /current_slide/

fix prop lookup in /current_slide/


test key presence using `of`

test key presence using `of`

--- oridomi.coffee
+++ oridomi.coffee
@@ -25,10 +25,10 @@
 testProp = (prop) ->
   # Loop through the vendor prefix list and return a match is found.
   for prefix in prefixList
-    return full if testEl.style[(full = prefix + capitalize prop)]?
+    return full if (full = prefix + capitalize prop) of testEl.style

   # If the unprefixed property is present, return it.
-  return prop if testEl.style[prop]?
+  return prop if prop of testEl.style
   # If no matches are found, return false to denote that the browser is
   # missing this property.
   false
@@ -442,7 +442,7 @@
     # Fill in passed options with defaults.
     @_config = new ->
       for k, v of defaults
-        if options[k]?
+        if k of options
           @[k] = options[k]
         else
           @[k] = v


Working on favourites

Working on favourites

--- twitch.coffee
+++ twitch.coffee
@@ -8,6 +8,8 @@
 #   None
 #
 # Commands:
+#   hubot ttv fav - Returns information about your five favourite streams (tracking)
+#   hubot ttv fav <add|rm> <name> - <Add> or <rm> stream <name> to/from your list of five favourite streams
 #   hubot ttv featured - Returns the first 5 featured live streams
 #   hubot ttv game <category> - Returns the first 5 live streams in a game <category> (case-sensitive)
 #   hubot ttv search <query> - Returns the first 5 live streams matching the search <query>
@@ -23,6 +25,18 @@

 module.exports = (robot) ->
     maxResults = 5
+
+    twitchData = ->
+        robot.brain.data.twitch or= {}
+
+    robot.respond /ttv fav/i, (msg) ->
+        return
+
+    robot.respond /ttv fav (add|rm) (.+)/i, (msg) ->
+        type = msg.match[1]
+        stream = msg.match[2]
+
+        return

     robot.respond /ttv featured/i, (msg) ->
         twitch_request msg, '/streams/featured', limit: maxResults, (object) ->
@@ -77,7 +91,7 @@
         createURL = (game) ->
             "http://www.twitch.tv/directory/game/#{encodeURIComponent(game)}"

-        twitch_request msg, "/games/top", limit: 5, (object) ->
+        twitch_request msg, "/games/top", limit: maxResults, (object) ->
             for gameObj, i in object.top
                 msg.send "#{i + 1}. #{gameObj.game.name} | Viewers: #{gameObj.viewers} | Channels: #{gameObj.channels} | #{createURL(gameObj.game.name)}"


upgraded the preflight with statics

upgraded the preflight with statics

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -63,6 +63,7 @@
     # included in the `Screenplay` context that is going to be emited
     # and deployed on the client site. Basically, use this to bring
     # in all the remote classes that you need to the remote call site.
+    # Refer to the remote compilation procedures for more information.
     @remote: (subject) ->
         assert previous = @remotes or Array()
         qualify = try subject.remote.compile
@@ -84,14 +85,69 @@
         assert remotes = @constructor.remotes or []
         assert uniques = _.unique remotes or Array()
         @inject context, blob for blob in uniques
-        return next.call this, undefined
+        return do => next.call this, undefined
+
+module.exports.LToolkit = class LToolkit extends RToolkit
+
+    # This is a marker that indicates to some internal subsystems
+    # that this class has to be considered abstract and therefore
+    # can not be treated as a complete class implementation. This
+    # mainly is used to exclude or account for abstract classes.
+    # Once inherited from, the inheritee is not abstract anymore.
+    @abstract yes
+
+    # This server side method is called on the context prior to the
+    # context being compiled and flushed down to the client site. The
+    # method is wired in an asynchronous way for greater functionality.
+    # This is the place where you would be importing the dependencies.
+    # Pay attention that most implementations side effect the context.
+    prelude: (symbol, context, request, next) ->
+        assert jscripts = @constructor.jscripts or []
+        assert stsheets = @constructor.stsheets or []
+        assert jscripts = _.unique jscripts or Array()
+        assert stsheets = _.unique stsheets or Array()
+        assert _.isFunction context.sheets.push or null
+        assert _.isFunction context.scripts.push or null
+        context.scripts.push script for script in jscripts
+        context.sheets.push sheet for sheet in stsheets
+        return do => next.call this, undefined
+
+    # This is a preflight directive that can be used to link any
+    # arbitrary JavaScript file source. Is important do understand
+    # that this directive only compiles the appropriate statement
+    # to be transferred to the server and it is up to you to ensure
+    # the existence of that file and its ability to be downloaded.
+    @javascript: (xoptions, xdirection) ->
+        assert previous = @jscripts or Array()
+        options = _.find arguments, _.isObject
+        direction = _.find arguments, _.isString
+        indirect = "an inalid direction supplied"
+        noPrevious = "invalid previous jscripts"
+        assert _.isArray(previous), noPrevious
+        assert _.isString(direction), indirect
+        @jscripts = previous.concat direction
+
+    # This is a preflight directive that can be used to link any
+    # arbitrary CSS style file source. Is important do understand
+    # that this directive only compiles the appropriate statement
+    # to be transferred to the server and it is up to you to ensure
+    # the existence of that file and its ability to be downloaded.
+    @stylesheet: (xoptions, xdirection) ->
+        assert previous = @stsheets or Array()
+        options = _.find arguments, _.isObject
+        direction = _.find arguments, _.isString
+        indirect = "an inalid direction supplied"
+        noPrevious = "invalid previous stsheets"
+        assert _.isArray(previous), noPrevious
+        assert _.isString(direction), indirect
+        @stsheets = previous.concat direction

 # This abstract base class service is an extension of the Screenplay
 # family that does some further environment initialization and set
 # up. These preparations will be nececessary no matter what sort of
 # Screenplay functionality you are going to implement. Currently the
 # purpose of preflight is drawing in the remoted and Bower packages.
-module.exports.Preflight = class Preflight extends RToolkit
+module.exports.Preflight = class Preflight extends LToolkit

     # This is a marker that indicates to some internal subsystems
     # that this class has to be considered abstract and therefore


Minor import corrections

Minor import corrections

--- library/membrane/primitive.coffee
+++ library/membrane/primitive.coffee
@@ -36,11 +36,15 @@
 util = require "util"
 url = require "url"

+{Primitive} = require "./primitive"
+{Barebones} = require "./skeleton"
+{EventEmitter2} = require "eventemitter2"
+
 # This is the basis for the default validation context. It contains
 # a set of primitive and standard validators that can be used either
 # directly or as a foundation for more complex validators that could
 # be defined in the descendants of the default context implementation.
-module.exports.Primitive = class Primitive extends events.EventEmitter2
+module.exports.Primitive = class Primitive extends EventEmitter2

     # Check if the value is not empty. The validation will succeed
     # if the value is a string that contains anything, and not empty.


Use utils.logAndSetResponse

Use utils.logAndSetResponse

--- src/api/clients.coffee
+++ src/api/clients.coffee
@@ -2,6 +2,7 @@
 Q = require 'q'
 logger = require 'winston'
 authorisation = require './authorisation'
+utils = require '../utils'

 ###
 # Adds a client
@@ -10,9 +11,7 @@

   # Test if the user is authorised
   if not authorisation.inGroup 'admin', this.authenticated
-    logger.info "User #{this.authenticated.email} is not an admin, API access to addClient denied."
-    this.body = "User #{this.authenticated.email} is not an admin, API access to addClient denied."
-    this.status = 'forbidden'
+    utils.logAndSetResponse this, 'forbidden', "User #{this.authenticated.email} is not an admin, API access to addClient denied.", 'info'
     return

   clientData = this.request.body
@@ -21,9 +20,9 @@
     client = new Client clientData
     result = yield Q.ninvoke client, 'save'

+    logger.info "User #{this.authenticated.email} created client with id #{client.id}"
     this.body = 'Client successfully created'
     this.status = 'created'
-    logger.info "User #{this.authenticated.email} created client with id #{client.id}"
   catch e
     logger.error "Could not add a client via the API: #{e.message}"
     this.body = e.message
@@ -42,16 +41,12 @@
         _id: 0
         name: 1
     else
-      logger.info "The property (#{property}) you are trying to retrieve is not found."
-      this.body = "The property (#{property}) you are trying to retrieve is not found."
-      this.status = 'not found'
+      utils.logAndSetResponse this, 'not found', "The property (#{property}) you are trying to retrieve is not found.", 'info'
       return
   else
     # Test if the user is authorised
     if not authorisation.inGroup 'admin', this.authenticated
-      logger.info "User #{this.authenticated.email} is not an admin, API access to findClientById denied."
-      this.body = "User #{this.authenticated.email} is not an admin, API access to findClientById denied."
-      this.status = 'forbidden'
+      utils.logAndSetResponse this, 'forbidden', "User #{this.authenticated.email} is not an admin, API access to findClientById denied.", 'info'
       return

   clientId = unescape clientId
@@ -59,8 +54,7 @@
   try
     result = yield Client.findById(clientId, projectionRestriction).exec()
     if result is null
-      this.body = "Client with id #{clientId} could not be found."
-      this.status = 'not found'
+      utils.logAndSetResponse this, 'not found', "Client with id #{clientId} could not be found.", 'info'
     else
       this.body = result
   catch e
@@ -73,9 +67,7 @@

   # Test if the user is authorised
   if not authorisation.inGroup 'admin', this.authenticated
-    logger.info "User #{this.authenticated.email} is not an admin, API access to findClientByDomain denied."
-    this.body = "User #{this.authenticated.email} is not an admin, API access to findClientByDomain denied."
-    this.status = 'forbidden'
+    utils.logAndSetResponse this, 'forbidden', "User #{this.authenticated.email} is not an admin, API access to findClientByDomain denied.", 'info'
     return

   clientDomain = unescape clientDomain
@@ -83,8 +75,7 @@
   try
     result = yield Client.findOne(clientDomain: clientDomain).exec()
     if result is null
-      this.body = "Could not find client with clientDomain #{clientDomain}"
-      this.status = 'not found'
+      utils.logAndSetResponse this, 'not found', "Could not find client with clientDomain #{clientDomain}", 'info'
     else
       this.body = result
   catch e
@@ -96,9 +87,7 @@

   # Test if the user is authorised
   if not authorisation.inGroup 'admin', this.authenticated
-    logger.info 'User ' +this.authenticated.email+ ' is not an admin, API access to updateClient denied.'
-    this.body = 'User ' +this.authenticated.email+ ' is not an admin, API access to updateClient denied.'
-    this.status = 'forbidden'
+    utils.logAndSetResponse this, 'forbidden', "User #{this.authenticated.email} is not an admin, API access to updateClient denied.", 'info'
     return

   clientId = unescape clientId
@@ -109,8 +98,8 @@

   try
     yield Client.findByIdAndUpdate(clientId, clientData).exec()
+    logger.info "User #{this.authenticated.email} updated client with id #{clientId}"
     this.body = 'Successfully updated client.'
-    logger.info "User #{this.authenticated.email} updated client with id #{clientId}"
   catch e
     logger.error "Could not update client by ID #{clientId} via the API: #{e.message}"
     this.body = e.message
@@ -120,9 +109,7 @@

   # Test if the user is authorised
   if not authorisation.inGroup 'admin', this.authenticated
-    logger.info "User #{this.authenticated.email} is not an admin, API access to removeClient denied."
-    this.body = "User #{this.authenticated.email} is not an admin, API access to removeClient denied."
-    this.status = 'forbidden'
+    utils.logAndSetResponse this, 'forbidden', "User #{this.authenticated.email} is not an admin, API access to removeClient denied.", 'info'
     return

   clientId = unescape clientId
@@ -140,9 +127,7 @@

   # Test if the user is authorised
   if not authorisation.inGroup 'admin', this.authenticated
-    logger.info "User #{this.authenticated.email} is not an admin, API access to getClients denied."
-    this.body = "User #{this.authenticated.email} is not an admin, API access to getClients denied."
-    this.status = 'forbidden'
+    utils.logAndSetResponse this, 'forbidden', "User #{this.authenticated.email} is not an admin, API access to getClients denied.", 'info'
     return

   try


add filter NUMBER option

add filter NUMBER option

--- bin/theta.coffee
+++ bin/theta.coffee
@@ -19,8 +19,14 @@
   ['--delete [Object Handle]', 'delete a picture']
   ['--info [Object Handle]', 'show picture info']
   ['--battery', 'check battery level']
-  ['--volume [NUM]', 'get/set audio volume (0~100)']
+  ['--volume [NUMBER]', 'get/set audio volume (0~100)']
 ]
+
+parser.filter 'NUMBER', (v) ->
+  return v unless v # allow empty argument
+  if /^[1-9][0-9]*(\.\d+)?$/.test v
+    return v - 0
+  throw "invalid number - \"#{v}\""

 parser.on 'help', ->
   package_json = require "#{__dirname}/../package.json"


Tests: Updated parallel.sets to add NIGHTWATCH_OPTIONS.

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -22,69 +22,73 @@
 module.exports = [

   [
-    'register'
-    'login'
-    'logout'
-    'activity likeunlike'
+    { name: 'register'  }
+    { name: 'login'
+    { name: 'logout' }
+    { name: 'activity likeunlike' }
   ]

   [
-    'pricing payment'
-    'activity post'
-    'staticpages loggedout'
+    { name: 'pricing payment' }
+    { name: 'activity post' }
+    { name: 'staticpages loggedout' }
   ]

   [
-    'messages privatemessage'
-    'account accountsettings'
-    'activity postwith'
+    { name: 'messages privatemessage' }
+    { name: 'account accountsettings' }
+    { name: 'activity postwith' }
   ]

   [
-    'activity edit'
-    'activity delete'
+    { name : 'activity edit' }
+    { name : 'activity delete' }
   ]

   [
-    'unittests'
-    'activity others'
-    'account accountpage'
+    { name : 'unittests' }
+    { name : 'activity others' }
+    { name : 'account accountpage' }
   ]

   [
-    'ide file'
-    'ide folder'
+    { name : 'ide file' }
+    { name : 'ide folder' }
   ]

   [
-    'ide search'
-    'ide workspace'
+    { name : 'ide search' }
+    { name : 'ide workspace' }
   ]

   [
-    'ide terminal'
+    { name : 'ide terminal' }
   ]

   [
-    'ide general'
-    'ide layout'
+    { name : 'ide general' }
+    { name : 'ide layout' }
   ]

   [
-    'collaboration collaboration'
+    { name : 'collaboration collaboration' }
   ]

   [
-    'environments vm'
+    { name : 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
   ]

   [
-    'environments paidaccount'
+    { name : 'environments vm' }
   ]

   [
-    'environments domain'
-    'teams teams'
+    { name : 'environments paidaccount' }
+  ]
+
+  [
+    { name : 'environments domain' }
+    { name : 'teams teams' }
   ]

 ]


Tests: Disabled Collaboration tests for algolia failure.

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -35,7 +35,7 @@
   ]

   [
-    { name: 'messages privatemessage' }
+    # { name: 'messages privatemessage' }
     { name: 'account accountsettings' }
     # { name: 'activity postwith' }
   ]
@@ -72,12 +72,12 @@
   ]

   [
-    { name : 'collaboration collaboration' }
+    # { name : 'collaboration collaboration' }
     { name : 'environments snapshot' }
   ]

   [
-    { name : 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
+    # { name : 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
   ]

   [


Add a trailing comma

Add a trailing comma

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -121,7 +121,7 @@

 		webRouter.get  '/user/projects', AuthenticationController.requireLogin(), ProjectController.userProjectsJson
 		webRouter.get  '/project/:Project_id/entities', AuthenticationController.requireLogin(),
-			AuthorizationMiddlewear.ensureUserCanReadProject
+			AuthorizationMiddlewear.ensureUserCanReadProject,
 			ProjectController.projectEntitiesJson

 		webRouter.get  '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage


Switch to using the Promise constructor rather than deferred, it allows for slightly nicer code.  Also add some parens in appropriate places.

--- src/application.coffee
+++ src/application.coffee
@@ -37,18 +37,14 @@
 		console.log("Pulling image:", app.imageId)
 		docker.createImageAsync(fromImage: app.imageId)
 		.then (stream) ->
-			deferred = Promise.defer()
+			return new Promise (resolve, reject) ->
+				if stream.headers['content-type'] is 'application/json'
+					stream.pipe(JSONStream.parse('error'))
+					.pipe(es.mapSync(reject))
+				else
+					stream.pipe(es.wait((error, text) -> reject(text))

-			if stream.headers['content-type'] is 'application/json'
-				stream.pipe(JSONStream.parse('error'))
-				.pipe es.mapSync (error) ->
-					deferred.reject(error)
-			else
-				stream.pipe es.wait (error, text) -> deferred.reject(text)
-
-			stream.on 'end', -> deferred.resolve()
-
-			return deferred.promise
+				stream.on('end', resolve)
 	.then ->
 		console.log("Creating container:", app.imageId)
 		ports = {}


Give esri type to indicators to make them appear on index

--- server/test/integration/theme_index.coffee
+++ server/test/integration/theme_index.coffee
@@ -17,6 +17,7 @@
   theme1.indicators = [{
     title: "I am an indicator of theme 1"
     narrativeRecency: "Out of date"
+    type: 'esri'
   }]
   theme2 = new Theme({
     title: 'Theme 2'
@@ -24,6 +25,7 @@
   theme2.indicators = [{
     title: "theme 2 indicator"
     narrativeRecency: "Out of date"
+    type: 'esri'
   }]
   themes = [theme1, theme2]


log error if state file is truncacted

log error if state file is truncacted

--- services/clsi/app/coffee/SafeReader.coffee
+++ services/clsi/app/coffee/SafeReader.coffee
@@ -1,4 +1,5 @@
 fs = require "fs"
+logger = require "logger-sharelatex"

 module.exports = SafeReader =

@@ -21,4 +22,6 @@
 			fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) ->
 				return callbackWithClose(err) if err?
 				result = buffer.toString(encoding, 0, bytesRead)
+				if bytesRead is size
+					logger.error file:file, size:size, bytesRead:bytesRead, "file truncated"
 				callbackWithClose(null, result)


Match chart colors to bugs colors

Match chart colors to bugs colors

--- src/client/game.coffee
+++ src/client/game.coffee
@@ -1,9 +1,9 @@
-make_chart = (svg_id, bug_stats, max_turns, max_bugs) ->
+make_chart = (svg_id, bug_stats, max_turns, max_bugs, colors) ->
   $(svg_id).empty()

   xScale     = new Plottable.Scale.Linear().domain([0, max_turns])
   yScale     = new Plottable.Scale.Linear().domain([0, max_bugs])
-  colorScale = new Plottable.Scale.Color("10")
+  colorScale = new Plottable.Scale.Color().range(colors)

   xAxis  = new Plottable.Axis.Numeric(xScale, "bottom")
   yAxis  = new Plottable.Axis.Numeric(yScale, "left")
@@ -37,15 +37,24 @@

   fabric.Object.prototype.transparentCorners = false

-  width     = 60 # Number of bugs
-  height    = 40 # Number of bugs
-  bug_size  = 10 # pixels
-  max_turns = 1000
+  width       = 60 # Number of bugs
+  height      = 40 # Number of bugs
+  bug_size    = 10 # pixels
+  max_turns   = 1000
+  color_scale = [
+    {rgb: '#A61313',  name: 'red'   },
+    {rgb: '#EF38FF',  name: 'pink'  },
+    {rgb: '#4E45FF',  name: 'blue'  },
+    {rgb: '#0D851E',  name: 'green' },
+    {rgb: '#BEC168',  name: 'yellow'},
+    {rgb: '#C1BAB0',  name: 'grey'  },
+    {rgb: '#000000',  name: 'black' }
+  ]

   canvas.setHeight height * bug_size
   canvas.setWidth  width  * bug_size

-  bugs  = starting_bugs(bug_size, width, height, max_turns)
+  bugs  = starting_bugs(bug_size, width, height, max_turns, color_scale)
   render_game canvas, bugs

   next_turn bugs, canvas
@@ -55,14 +64,16 @@
   render_game canvas, bugs

   if bugs.turns % 10 == 0
-    stats = bugs.names.map((name) -> bugs.stats[name])
-    make_chart '#jitterbug_progress_chart_svg', stats, bugs.max_turns, bugs.count
+    stats  = bugs.names.map((name) -> bugs.stats[name])
+    colors = bugs.color_scale.map((color) -> color.rgb)
+    make_chart '#jitterbug_progress_chart_svg', stats,
+                bugs.max_turns, bugs.count, colors

   if bugs.turns < bugs.max_turns
     bugs.turns += 1
     setTimeout((-> next_turn bugs, canvas), 50)

-starting_bugs = (bug_size, width, height, max_turns) ->
+starting_bugs = (bug_size, width, height, max_turns, color_scale) ->
   grid = []

   for x in [0..(width - 1)]
@@ -76,7 +87,8 @@
     next_serial: 0
     stats:       {}
     names:       []
-    next_color:  ['black', 'yellow', 'blue', 'grey', 'pink', 'green', 'red']
+    color_scale: color_scale
+    next_color:  color_scale.map((color) -> color.name).reverse()
     turns:       0
     count:       0
     bug_size:    bug_size


Async upload.

Async upload.

--- src/client/node.coffee
+++ src/client/node.coffee
@@ -28,6 +28,11 @@
       headers:            blueprint.headers ? {}
       followAllRedirects: true

+    # Get body from previous step
+    if data.body?
+      opts.body = data.body
+
+    # Not JSON if blueprint.file or blueprint.stream is set
     if blueprint.stream? or blueprint.file?
       delete opts.json
     else
@@ -36,14 +41,23 @@
       else
         opts.json = true

-    if blueprint.file?
-      opts.body = fs.readFileSync blueprint.file data
+    new Promise (resolve, reject) =>
+      # Read file is requested
+      if blueprint.file? and (not data.body?)
+        fs.readFile (blueprint.file data), (err, body) =>
+          return reject err if err?

-    if @debug
-      console.log '--REQUEST--'
-      console.log opts
+          data.body = body

-    new Promise (resolve, reject) =>
+          (@request blueprint, data, key)
+            .then resolve
+            .catch reject
+        return
+
+      if @debug
+        console.log '--REQUEST--'
+        console.log opts
+
       req = request opts, (err, res) =>
         if res?
           if @debug


introduction of evens for showing error and informational messages to the user. handling of errors when removing an application

--- client/main.cjsx
+++ client/main.cjsx
@@ -6,6 +6,7 @@
 { browserHistory }  = require 'react-router'
 { EventEmitter }    = require 'fbemitter'
 routes              = require '../imports/startup/routes.cjsx'
+ErrorMapper         = require '../imports/ErrorMapper.coffee'

 Wrapper = React.createClass
   displayName: 'MeteorDataWrapper'
@@ -31,8 +32,18 @@
   onEvent 'app name selected', storeDataInSession('selectedAppName'), (app) -> browserHistory.push("/apps/#{app.name}/#{app.version}")
   onEvent 'app search entered', storeDataInSession('appSearchValue')
   onEvent 'stop instance', (instanceName) -> ddp.call 'stopInstance', instanceName
-  onEvent 'remove app', (app) -> console.log 'remove app', app
+  onEvent 'remove app', (app) -> ddp.call 'deleteApp', app.name, app.version, (err) ->
+    eventEmitter.emit 'show error message', err if err
+    eventEmitter.emit 'app removed', app unless err
+  onEvent 'app removed', (app) ->
+    eventEmitter.emit 'show info message', "App #{app.name}:#{app.version} is deleted."
+    browserHistory.push '/apps'
   onEvent 'start app', (app) -> console.log 'start app', app
+
+  onEvent 'show error message', (message) -> Session.set 'globalErrorMessage', ErrorMapper message
+  onEvent 'clear error message', -> Session.set 'globalErrorMessage', null
+  onEvent 'show info message', (message) -> Session.set 'globalInfoMessage', message
+  onEvent 'clear info message', -> Session.set 'globalInfoMessage', null

   emit: (evt, data) -> eventEmitter.emit evt, data
   collections:
@@ -48,6 +59,8 @@
   state:
     selectedAppName: -> Session.get 'selectedAppName'
     appSearchValue: -> Session.get 'appSearchValue'
+    globalErrorMessage: -> Session.get 'globalErrorMessage'
+    globalInfoMessage: -> Session.get 'globalInfoMessage'

 , Wrapper


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color_table.coffee
+++ src/color_table.coffee
@@ -2519,3 +2519,8 @@
   ["87b84a", "peridot"]
   ["00000000", "transparent"]
 ]
+
+lookup = {}
+
+names.each (element) ->
+  lookup[normalizeKey(element[1])] = parseHex(element[0])


Clear mini editor base select list cancelled()

Clearing the mini editor when closing is something
all sub-classes were already doing so it makes sense
to pull it up to the base class as the default
cancelled() implementation that can still be overridden
if needed.

--- src/command-palette-view.coffee
+++ src/command-palette-view.coffee
@@ -35,7 +35,6 @@

     @setArray(events)
     @appendTo(@rootView)
-    @miniEditor.setText('')
     @miniEditor.focus()

   itemForElement: ({eventName, eventDescription}) ->
@@ -52,5 +51,3 @@
   confirmed: ({eventName}) ->
     @cancel()
     @previouslyFocusedElement.trigger(eventName)
-
-  cancelled: ->


Updated regex

Updated regex

--- lib/markdown-table-formatter.coffee
+++ lib/markdown-table-formatter.coffee
@@ -1,8 +1,8 @@
 class TableFormatter
     subscriptions: []
     #regex: /((.+?)\|)+?(.+)?\r?\n(([:\-\|]+?)\|)+?([:\-\|]+)?[ ]*(\r?\n((.+?)\|)+?(.+)?)+/mg
-    regex: /((.+?)\|)+?(.+)?\r?\n(([\s:\-\|]+?)\|)+?([\s:\-\|]+)?[ ]*(\r?\n((.+?)\|)+?(.+)?)+/mg
-
+    regex: /((?:(?:[^\n]*?\|[^\n]*) *\r?\n)?)((?:\| *:?-+:? *|\|?(?: *:?-+:? *\|)+)(?: *:?-+:? *)? *\r?\n)((?:(?:[^\n]*?\|[^\n]*) *\r?\n)+)/g
+
     constructor: ->
         atom.workspace.observeTextEditors (editor) =>
             subscription = editor.getBuffer().onWillSave =>
@@ -36,7 +36,7 @@
         # console.log('myIterator')
         myIterator = (obj) =>
             # console.log('iterating')
-            obj.replace(@formatTable(obj.matchText))
+            obj.replace(@formatTable(obj.match))

         for range in selectionsRanges
             # console.log(@regex)
@@ -68,18 +68,22 @@
             else
                 return string

-        lines = text.split('\n')
+        formatline = text[2].trim()
+        headerline = text[1].trim()
+
+        if headerline.length==0
+            formatrow=0
+            lines = text[3].trim().split('\n')
+        else
+            formatrow=1
+            lines = (text[1]+text[3]).trim().split('\n')
         rows = lines.length
-
-        formatline = lines[1]
-        formatrow = 1
-        lines.splice(1,1) # Remove the index 1 of the array
-        # console.log(lines)

         formatline = formatline.trim().replace(/(^\||\|$)/g,"")
         fstrings = formatline.split('|')
         justify = []
         for cell in fstrings
+            cell=cell.trim()
             ends = cell[0] + (cell[cell.length-1] || '')
             if ends == '::'
                 justify.push('::')
@@ -140,7 +144,7 @@

         formatted.splice(formatrow, 0, [formatline]);

-        return formatted.join('\n')
+        return formatted.join('\n')+'\n'

 module.exports =
     config:


Change the default for limitLastColumnPadding because may confuse users

--- lib/markdown-table-formatter.coffee
+++ lib/markdown-table-formatter.coffee
@@ -40,7 +40,7 @@
           type: 'string'
       limitLastColumnPadding:
         type: 'boolean'
-        default: true
+        default: false
         description:
           'Do not pad the last column to more than your editor\'s
           preferredLineLength setting.'


fix typo

fix typo

--- lib/motions/general-motions.coffee
+++ lib/motions/general-motions.coffee
@@ -375,8 +375,8 @@

 class MoveToMiddleOfScreen extends MoveToScreenLine
   getDestinationRow: (count) ->
-    firstScreenRow = @editorElemnt.getFirstVisibleScreenRow()
-    lastScreenRow = @editorElemnt.getLastVisibleScreenRow()
+    firstScreenRow = @editorElement.getFirstVisibleScreenRow()
+    lastScreenRow = @editorElement.getLastVisibleScreenRow()
     height = lastScreenRow - firstScreenRow
     Math.floor(firstScreenRow + (height / 2))


[benchmarks] latency: don't print to console each time a client finishes.

--- benchmarks/latency/latency.coffee
+++ benchmarks/latency/latency.coffee
@@ -72,7 +72,6 @@
             return if finishedClients[id]
             numResults[id] = numResults[id] + 1 || 0
             if numResults[id] == 5
-                console.log("#{id} is finished")
                 finishedClients[id] = true
             if Object.keys(finishedClients).length == numClients
                 client.kill() for client in clients


Auth will check API key works now

Auth will check API key works now

--- picksel.coffee
+++ picksel.coffee
@@ -100,7 +100,7 @@
 # @param [Number] res the resolution code of the image to get
 #
 buildUrl = (apiKey, id, res) ->
-  "https://pixabay.com/api/?key=#{user.apiKey}" \
+  "https://pixabay.com/api/?key=#{apiKey}" \
     + (if res > 1 then '&response_group=high_resolution' else '') \
     + "&id=#{id}"

@@ -143,19 +143,6 @@
 persistUser = (obj, callback) ->
   if obj then user = obj # Replace loaded user.
   persist USER_PATH, user, callback
-
-
-# Returns true if a request to the Pixabay API was successful.
-#
-# @param [Object] error any error returned with the response
-# @param [Object] body the body of the response
-#
-isSuccessful = (error, body) ->
-  !error \
-  && body \
-  && body.hits \
-  && body.hits instanceof Array \
-  && body.hits.length > 0

 # Downloads an image.
@@ -304,7 +291,7 @@
     if err || exists
       callback err, exists # Folder exists already, or error.
     else
-      ask "Doesn't look like the #{dir} directory exists, create it? (y/n)", \
+      ask "Doesn't look like the #{dir} directory exists, create it? (y/n) ", \
         (answer) ->
           if yn answer # Create directory? Yes or no.
             mkdirp dir, (err) ->
@@ -345,6 +332,20 @@
                   + ' in to source control.' # Project setup success!

+# Validates that an API key works with Pixabay.
+#
+# @param [String] key the API key to validate
+# @param [Function] callback the function to call back on
+#
+validateApiKey = (key, callback) ->
+  url = buildUrl key, '195893', 0 # We know this image exists.
+  options =
+    url: url
+    json: true
+  request url, (error, response, body) -> # Request image JSON using API key.
+    callback(!error && response.statusCode == 200)
+
+
 # Walks the user through initializing their user file.
 #
 auth = () ->
@@ -359,15 +360,18 @@
       ask 'What\'s your Pixabay API key? To find it you can log in to the' \
         + ' Pixabay website and visit: https://pixabay.com/api/docs/ ', \
         (answer) ->
-          # TODO: Validate API key?
-          newUser.apiKey = apiKey # Add API key to user.
-          persistUser newUser, (err) -> # Persist new user file.
-            if err
-              log.error 'Error writing your user file to disk!'
+          validateApiKey answer, (success) -> # Check API key works.
+            if success
+              newUser.apiKey = answer # Add API key to user.
+              persistUser newUser, (err) -> # Persist new user file.
+                if err
+                  log.error 'Error writing your user file to disk!'
+                else
+                  log.info "New file created at '#{USER_PATH}' containing" \
+                    + ' your API key. DON\'T CHECK THIS FILE IN TO SOURCE' \
+                    + ' CONTROL BECAUSE IT HAS YOUR SECRET API KEY IN IT.'
             else
-              log.info "New file created at '#{USER_PATH}' containing your" \
-                + ' API key. DON\'T CHECK THIS FILE IN TO SOURCE CONTROL' \
-                + ' BECAUSE IT HAS YOUR SECRET API KEY IN IT.'
+              log.error "That API key didn't work with Pixabay!"

 # Prints usage information for the application.


Start redacting API key from logged URLs

--- picksel.coffee
+++ picksel.coffee
@@ -48,6 +48,9 @@
 ]

+redactApiKey = (url) -> url.replace user.apiKey, '[NOPE]'
+
+
 # Builds a URL for downloading image information.
 #
 # @param [String] apiKey the API key to use for the request
@@ -83,6 +86,19 @@
   jsonfile.writeFileSync './picksel.json', config, options

+# Returns true if a request to the Pixabay API was successful.
+#
+# @param [Object] error any error returned with the response
+# @param [Object] body the body of the response
+#
+isSuccessful = (error, body) ->
+  !error \
+  && body \
+  && body.hits \
+  && body.hits instanceof Array \
+  && body.hits.length > 0
+
+
 # Downloads an image.
 #
 # @param [String] id the Pixabay image ID
@@ -93,24 +109,23 @@
   # Build URL for API request.
   url = buildUrl(user.apiKey, id, resolution)

-  log.info 'Requesting information for image with ID ' \
-    + id \
+  log.info 'Requesting information for image' \
     + ' from ' \
-    + url
-
-  # Call out to Pixabay.
+    + redactApiKey url
+
   options =
     url: url
     json: true
+
+  # Call out to Pixabay.
   request options, (error, response, body) ->
-    if error || !body || !body.hits
-      log.error 'Couldn\'t get information about image with ID \'' \
-        + id \
-        + '\''
-    else
+
+    # On success.
+    if isSuccessful error, body
       url = body.hits[0][resolutions[resolution]]
-      log.info 'Downloading file from ' \
-        + url
+      log.info 'Downloading file from \'' \
+        + url \
+        + '\'...'
       tempDestination = destination + '.pickseltemp'
       file = fs.createWriteStream tempDestination
       req = https.get url, (response) ->
@@ -130,8 +145,12 @@
               log.info 'Finished installing image with ID \'' \
                 + id \
                 + '\''
+    else
+      # Download failed.
+      log.error 'Couldn\'t get information about image with ID \'' \
+        + id \
+        + '\''

-

 # Installs a single image.
 #
@@ -160,28 +179,28 @@
 add = (args) ->
   # Check ID.
   id = args[3]
-  if !isValidId
+  if !isValidId id
     log.error 'That ID \'' + id + '\' isn\'t valid.'
     return false

   # Check resolution.
   res = args[4]
-  if !isValidResolution(res)
+  if !isValidResolution res
     log.error 'That resolution \'' + res + '\' isn\'t valid.'
     return false

   # Add new image to configuration file.
   image =
     id: id
-    resolution: getResolutionCode(res)
+    resolution: getResolutionCode res
     destination: args[5]
   config.images.push image

   persistConfig() # Update configuration file.

-  log.info 'Added image with ID ' \
+  log.info 'Added image with ID \'' \
     + image.id \
-    + ' as asset.'
+    + '\' as asset.'

   install() # Freshly install all images.


Hides modal on blur.

Hides modal on blur.

--- lib/quick-file-actions-view.coffee
+++ lib/quick-file-actions-view.coffee
@@ -1,5 +1,4 @@
-module.exports =
-class QuickFileActionsView
+module.exports = class QuickFileActionsView
   constructor: (disposeAction) ->
     @disposeAction = disposeAction

@@ -29,6 +28,8 @@
     @input.setAttribute('mini', true)
     @input.getModel().setText(path)

+    @input.onblur = => window.setTimeout(@disposeAction, 1)
+
     @element.appendChild(label)
     @element.appendChild(@input)


Test transactions.

Test transactions.

--- js/core/test/LocalStore.coffee
+++ js/core/test/LocalStore.coffee
@@ -11,9 +11,10 @@
 			authinfo = user.set_authinfo(runtime.build('Authinfo', {})).get_authinfo();

 			store = new JEFRi.LocalStore
-			runtime.save_new store .then(->)
-
-			start();
+			runtime.save_new(store).then((transaction)->
+				ok(transaction.entities.length == 2)
+				start();
+			)
 		)
 	)
 )


Set window.Bokeh = Bokeh = ... to allow inline autoload

--- bokehjs/gulp/tasks/scripts.coffee
+++ bokehjs/gulp/tasks/scripts.coffee
@@ -196,7 +196,7 @@
       .pipe change (content) ->
         "(function() { var define = undefined; return #{content} })()"
       .pipe change (content) ->
-        "Bokeh = #{content}"
+        "window.Bokeh = Bokeh = #{content}"
       .pipe(insert.append(license))
       .pipe(sourcemaps.write('./'))
       .pipe(gulp.dest(paths.buildDir.js))


added condition for username

added condition for username

--- coffeeApp.coffee
+++ coffeeApp.coffee
@@ -61,7 +61,10 @@
 twit.stream 'statuses/filter', {track:'fuck'}, (stream) ->
   id = 0
   stream.on 'data', (data) ->
-    text = "@" + data.user.screen_name + " : " + data.text
+    if data.user.screen_name
+      text = "@" + data.user.screen_name + " : " + data.text
+    else
+      text = data.text
     if data.coordinates
       tweets.push {text: text, coordinates: data.coordinates.coordinates, id:id++}
       console.log "Coordinates: " + coordPoint++


improve console logging on errors

improve console logging on errors

--- modules/store.coffee
+++ modules/store.coffee
@@ -343,6 +343,7 @@

 # handle any uncaught errors in our Promise chain
 RSVP.on 'error', (reason) ->
-  console.error(reason.stack ? reason.message) if reason?
+  return unless reason?
+  console.error(reason.stack ? reason.message) if reason.stack or reason.message

 module.exports = Store