Quick fix support for YouTube single page app-ness

--- src/popout_for_youtube.coffee
+++ src/popout_for_youtube.coffee
@@ -37,9 +37,8 @@

 class Video extends Node

-  constructor: ->
+  constructor: (@id)->
     super document.querySelector('#player video')
-    @id = document.querySelector('meta[itemprop=videoId]').content

   pause: ->
     @node.pause()
@@ -90,11 +89,26 @@
     @node.style.top  = "#{point.y - @height()}px"
     @node.style.left = "#{point.x}px"

-class YouTubePage
+class YouTubeVideoPage

   constructor: ->
-    video   = new Video
-    button  = new Button video
-    document.body.appendChild button.node

-new YouTubePage
+    @whenVideoChanges =>
+      try @button.remove()
+      @videoId = @getVideoId()
+      @video   = new Video @videoId
+      @button  = new Button @video
+      document.body.appendChild @button.node
+
+  whenVideoChanges: (callback)->
+    setInterval =>
+      callback() if @videoChanged()
+    , 250
+
+  videoChanged: ->
+    @videoId != @getVideoId()
+
+  getVideoId: ->
+    try document.querySelector('meta[itemprop=videoId]').content
+
+new YouTubeVideoPage


fix getActiveIndex

fix getActiveIndex

--- src/chz.coffee
+++ src/chz.coffee
@@ -90,7 +90,7 @@
         scope.hideDropDown()
         scope.focus=true

-      scope.getActiveIndex = ->
+      getActiveIndex = ->
         indexOf(scope.shownItems, scope.activeItem) || 0

       scope.move = (d) ->


not needed

not needed

--- src/xml.coffee
+++ src/xml.coffee
@@ -163,8 +163,6 @@
                     data = "</#{@name}>"
                     @closed = yes
                 @emit 'data', prettify this, data
-
-            unless @closed is 'pending'
                 @emit 'close', this
                 @emit 'end'
         else if @closed is 'removed'


#203 replace row on successful import

#203 replace row on successful import

--- app/assets/javascripts/admin/views/dataset/dataset_list_item_view.coffee
+++ app/assets/javascripts/admin/views/dataset/dataset_list_item_view.coffee
@@ -102,6 +102,6 @@
         if value < 100
           window.setTimeout((=> @updateProgress()), 100)
         else
-          @model.fetch()
+          @model.fetch({url : "/api/datasets/#{@model.get("name")}"})
       )


Allow artisan permissions to also play classroom mode from level editor

--- app/views/editor/level/LevelEditView.coffee
+++ app/views/editor/level/LevelEditView.coffee
@@ -116,7 +116,7 @@
       for levelID, level of campaign.get('levels') when levelID is @level.get('original')
         @courseID = campaignCourseMap[campaign.id]
       break if @courseID
-    if not @courseID and me.isAdmin()
+    if not @courseID and (me.isAdmin() or me.isArtisan())
       # Give it a fake course ID so we can test it in course mode before it's in a course.
       @courseID = '560f1a9f22961295f9427742'
     @getLevelCompletionRate()


activity: video coming soon modal copy change

--- client/activity/lib/components/collaborationcomingsoonmodal/index.coffee
+++ client/activity/lib/components/collaborationcomingsoonmodal/index.coffee
@@ -12,7 +12,7 @@
   render: ->
     <Modal className='CollaborationComingSoonModal' isOpen={@props.isOpen} onClose={@props.onClose}>

-
Collaborate using video chat

+
Start a video chat in any channel

         Coming really soon...


working correct but bad version of terminal

--- client/landing/app/Applications/Shell.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Shell.kdapplication/AppController.coffee
@@ -17,8 +17,9 @@
     resetRegexp = /reset\:.*?/
     @dmp = new diff_match_patch()
     @lastScreen = ""
-    @bufferedKeyStrokes = []
     @sendCount = 0
+    @keyStrokeCount = 0
+    @resetBufferedKeyStrokes()

     shellView.registerListener KDEventTypes:'AdvancedSettingsFunction', listener:@, callback:(pubInst, {functionName})=>
       switch functionName
@@ -68,6 +69,7 @@
         msg = "connection closed"
         if error.msg then msg += ",#{error.msg}"
         @setNotification msg
+
       ping : () =>
         try
           @terminal.ping()
@@ -186,10 +188,11 @@
       else
         window.T = terminal
         @sendCount = 0
+        @keyStrokeCount = 0
         @terminal = terminal
         @welcomeUser terminal.isNew
         callback? terminal.totalSessions
-
+

   loadView:(mainView)->
     @initiateTerminal (totalSessions)=>
@@ -215,14 +218,20 @@

   sendThrottled : _.throttle ->
     @sendCount++
-    @terminal.write @bufferedKeyStrokes,@sendCount
+    baseTime = @bufferedKeyStrokes[0][2]
+    k[2] = k[2] - baseTime  for k in @bufferedKeyStrokes
+    @terminal.write @bufferedKeyStrokes
     console.log "#{@bufferedKeyStrokes.length} @bufferedKeyStrokes sent at - interval 500msec",new Date if @terminal.log
-    @bufferedKeyStrokes = []
-  ,500
-
+    @resetBufferedKeyStrokes()
+  ,250
+
+  resetBufferedKeyStrokes : -> @bufferedKeyStrokes = []
+
   send: (command) ->
     # console.log "sending:"+command
-    @bufferedKeyStrokes.push [command,Date.now()]
+    delay = Date.now() #-@bufferedKeyStrokes[@bufferedKeyStrokes.length-1][1]
+    @bufferedKeyStrokes.push [@keyStrokeCount,@sendCount,delay,command]
+    @keyStrokeCount++
     @sendThrottled()
     # @terminal.write command


getGroupSubscriptionMemberOf gets plan code

--- services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee
+++ services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee
@@ -25,4 +25,4 @@
 		Subscription.findOne {member_ids: user_id, _id:subscription_id}, {_id:1}, callback

 	getGroupSubscriptionMemberOf: (user_id, callback)->
-		Subscription.findOne {member_ids: user_id}, {_id:1}, callback+		Subscription.findOne {member_ids: user_id}, {_id:1, planCode:1}, callback


fix unit test for build parameter

fix unit test for build parameter

--- services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee
+++ services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee
@@ -237,11 +237,10 @@
 					@req.query = {build: 1234}
 					@CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)

-				it "should proxy to the standard url with the build parameter", ()->
-					@request
-						.calledWith(
-							method: @req.method
-							qs: {build: 1234}
+				it "should proxy to the standard url without the build parameter", ()->
+					@request
+						.calledWith(
+							method: @req.method
 							url: "#{@settings.apis.clsi.url}#{@url}",
 							timeout: 60 * 1000
 						)
@@ -296,6 +295,26 @@
 						)
 						.should.equal true

+			describe "user with build parameter via query string", ->
+				beforeEach ->
+					@CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"})
+					@req.query = {build: 1234, pdfng: true}
+					@CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next)
+
+				it "should proxy to the standard url with the build parameter", ()->
+					@request
+						.calledWith(
+							method: @req.method
+							qs: {build: 1234}
+							url: "#{@settings.apis.clsi.url}#{@url}",
+							timeout: 60 * 1000
+							headers: {
+								'Range': '123-456'
+								'If-Range': 'abcdef'
+								'If-Modified-Since': 'Mon, 15 Dec 2014 15:23:56 GMT'
+							}
+						)
+						.should.equal true

 	describe "deleteAuxFiles", ->
 		beforeEach ->


add cjsx back

add cjsx back

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -69,6 +69,7 @@
     conf   : 'nginx'   # should really have a list of different types that end in .conf and autodetect based on heuristics, letting user change.
     csharp : 'text/x-csharp'
     'c#'   : 'text/x-csharp'
+    cjsx   : 'text/cjsx'
     coffee : 'coffeescript'
     css    : 'css'
     diff   : 'text/x-diff'


Allow regexp in hot topics

Allow regexp in hot topics

--- scripts/hot-topics.coffee
+++ scripts/hot-topics.coffee
@@ -9,12 +9,12 @@

   robot.respond /hot topic (.*) = (.*)/i, (msg) ->
     robot.brain.data.hot_topics[msg.match[1].toLowerCase()] = msg.match[2]
-    msg.send "OK I'll remember that and let people know"
+    msg.send "OK, I'll remember that and let people know."

   robot.respond /forget hot topic (.*)/i, (msg) ->
     if robot.brain.data.hot_topics[msg.match[1].toLowerCase()]?
       delete robot.brain.data.hot_topics[msg.match[1].toLowerCase()]
-      msg.send "OK forgot it."
+      msg.send "OK, forgot it."
     else
       msg.send "Didn't know anything about that anyway."

@@ -26,7 +26,13 @@

   robot.hear /(.+)/i, (msg) ->
     text = msg.message.text
+    check = (text, phrase) ->
+      r = phrase.match /^\/(.*)\/$/
+      if r
+        phrase = RegExp(r[1], 'i')
+      !!text.toLowerCase().match(phrase)
+
     unless text.match 'hot topic'
-      memories = for phrase, memory of robot.brain.data.hot_topics when text.toLowerCase().match(phrase)
+      memories = for phrase, memory of robot.brain.data.hot_topics when check(text, phrase)
         memory
       msg.send memories.join("\n")


keep this context at this point

keep this context at this point

--- src/xml.coffee
+++ src/xml.coffee
@@ -127,7 +127,7 @@
         @headers = "<#{@name}#{new_attrs @attrs}" if @headers
         this

-    children: (children, {direct} = {}) ->
+    children: (children, {direct} = {}) =>
         if typeof children is 'function'
             if direct
                 children.call this


Remove smaller class to fix buttons style

--- app/assets/javascripts/components/annual_report_upload.js.coffee
+++ app/assets/javascripts/components/annual_report_upload.js.coffee
@@ -25,13 +25,11 @@
       download_button.addClass('disabled')
     if @state.submitted
       download_button.removeClass('disabled')
-      modal_content.addClass('smaller')
       text = I18n.t('submitted_at_info_box') + " " + @getSubmitter()
       text = text + I18n.t('on') + " #{@state.annualReportUpload.submitted_at}. "
       text = text + I18n.t('download_info_box')
       info_text.html(text)
     else
-      modal_content.removeClass('smaller')
       text = I18n.t('change_sandbox_settings')
       text = text + "#{I18n.t('your_admin_page')}"
       info_text.html(text)


Remove unused event handler

Remove unused event handler

--- app/assets/javascripts/rglossa/states/corpus/result_route.coffee
+++ app/assets/javascripts/rglossa/states/corpus/result_route.coffee
@@ -20,7 +20,3 @@

   renderTemplate: ->
     @renderSearchInterface('result')
-
-
-  events:
-    changeResultPage: (pageNo) -> @transitionTo('corpus.result')


better & fixed thirds generator (independent of app or www)

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -21,7 +21,8 @@
                 'source/www/style/__init.styl'
                 'source/www/style/page.styl'
                 'source/www/style/page.*.styl']
-  thirds    : [ 'bower_components/modernizr/modernizr.js'
+  thirds:
+    js      : [ 'bower_components/modernizr/modernizr.js'
                 'bower_components/jquery/dist/jquery.js']
   assets    : 'assets/'

@@ -40,6 +41,13 @@
                 'source/app/style/organism.*.styl'
                 'source/app/style/app.*.styl']
   yml       : [ 'source/app/organism/*.yml']
+  thirds:
+    js      : [ 'bower_components/hope/hope.js',
+                'bower_components/atoms/atoms.js',
+                'bower_components/atoms/atoms.app.js']
+    css     : [ 'bower_components/atoms/atoms.app.css',
+                'bower_components/atoms-icons/atoms.icons.css',]
+
   third_js  : [ 'bower_components/hope/hope.js',
                 'bower_components/atoms/atoms.js',
                 'bower_components/atoms/atoms.app.js']
@@ -59,13 +67,17 @@

 # -- TASKS ---------------------------------------------------------------------
 gulp.task 'thirds', ->
-  gulp.src(app.third_js)
-    .pipe(concat('atoms.js'))
-    .pipe(gulp.dest(app.assets + '/js'))
+  # WWW
+  for key, value of www.thirds
+    gulp.src(value)
+      .pipe(concat('film-it.thirds.' + key))
+      .pipe(gulp.dest(www.assets + '/' + key))
+  # APP
+  for key, value of app.thirds
+    gulp.src(value)
+      .pipe(concat('atoms.' + key))
+      .pipe(gulp.dest(app.assets + '/' + key))

-  gulp.src(app.third_css)
-    .pipe(concat('atoms.css'))
-    .pipe(gulp.dest(app.assets + '/css'))

 gulp.task 'coffee', ->
   gulp.src(www.coffee)


populated jade build tasks

populated jade build tasks

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -149,7 +149,7 @@
       "!#{source}/**/*.xsl.jade"
       "!#{source}/test/**/*"
       "!#{source}/test" ]
-
+    .pipe jade()
     .pipe gulp.dest build

 gulp.task 'build-jade-xml', ->
@@ -158,7 +158,8 @@
       "#{source}/**/*.xml.jade"
       "!#{source}/test/**/*"
       "!#{source}/test" ]
-
+    .pipe jade()
+    .pipe rename extname: ""
     .pipe gulp.dest build

 gulp.task 'build-jade-xsl', ->
@@ -167,7 +168,8 @@
       "#{source}/**/*.xsl.jade"
       "!#{source}/test/**/*"
       "!#{source}/test" ]
-
+    .pipe jade()
+    .pipe rename extname: ""
     .pipe gulp.dest build

 ### Build Stylus ###


Rebuild when bump version

Rebuild when bump version

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -21,7 +21,7 @@
     lib: './lib'
     dist: './dist'

-gulp.task 'bump', ->
+gulp.task 'bump', ['build'], ->
   gulp.src paths.source.manifest
     .pipe bump { type: argv.bump }
     .pipe gulp.dest(paths.dest.root)


remove redundant 'mocha' gulp instruction

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -117,11 +117,6 @@
   gulp.src ['./build/{browser,test,node}/**/*.{js,map}','./doc/'], { read: false }
     .pipe rimraf()

-gulp.task 'mocha', [], ->
-  gulp.src files.test, { read: true }
-    .pipe mocha {reporter : 'spec'}
-    #.pipe exit()
-
 gulp.task 'test', ['mocha'], ->
   gulp.watch files.all, ['mocha']


fix(appName): pass config option to ng-classify

Closes #17

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -182,6 +182,9 @@
 return if showHelp
 	console.log '\n' + yargs.help()

+ngClassifyOptions =
+	appName: APP_NAME
+
 templateOptions =
 	appName: APP_NAME
 	useBackendless: useBackendless
@@ -386,7 +389,7 @@
 			.pipe template templateOptions
 			.on 'error', onError

-			.pipe ngClassify()
+			.pipe ngClassify ngClassifyOptions
 			.on 'error', onError

 	srcs.push src =
@@ -930,7 +933,7 @@
 			.pipe template templateOptions
 			.on 'error', onError

-			.pipe ngClassify()
+			.pipe ngClassify ngClassifyOptions
 			.on 'error', onError

 			.pipe coffeeScript()


move watchify under watch task

move watchify under watch task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -22,22 +22,15 @@

 gulp.task 'coffee', ->

-  bundle = watchify('./src/coffee/main.coffee')
+  bundle = browserify('./src/coffee/main.coffee')

-  rebundle = ->
+  build = bundle.bundle(debug: not production)
+    .pipe(source('bundle.js'))

-    build = bundle.bundle(debug: not production)
-      .pipe(source('bundle.js'))
+  build.pipe(streamify(uglify())) if production

-    build.pipe(streamify(uglify())) if production
-
-    build
-      .pipe(gulp.dest('./public/js/'))
-      .pipe(livereload(reloadServer))
-
-  bundle.on 'update', rebundle unless production
-
-  rebundle()
+  build
+    .pipe(gulp.dest('./public/js/'))

 gulp.task 'jade', ->
   gulp
@@ -71,12 +64,23 @@
   .listen 9001

 gulp.task "watch", ->
-  reloadServer.listen 35729, (err) ->
-    console.error err if err?
+  reloadServer.listen 35729

-    gulp.watch "src/jade/*.jade", ["jade"]
-    gulp.watch "src/stylus/*.styl", ["stylus"]
-    gulp.watch "src/assets/**/*.*", ["assets"]
+  gulp.watch "src/jade/*.jade", ["jade"]
+  gulp.watch "src/stylus/*.styl", ["stylus"]
+  gulp.watch "src/assets/**/*.*", ["assets"]
+
+  bundle = watchify('./src/coffee/main.coffee')
+
+  bundle.on 'update', ->
+    build = bundle.bundle(debug: not production)
+      .pipe(source('bundle.js'))
+
+    build
+      .pipe(gulp.dest('./public/js/'))
+      .pipe(livereload(reloadServer))
+
+  .emit 'update'

 gulp.task "build", ["coffee", "jade", "stylus", "assets"]
 gulp.task "default", ["build", "watch", "server"]


Gulpfile watch improvement

Gulpfile watch improvement

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -140,7 +140,10 @@
 gulp.task 'watch', ->
   gulp.watch config.paths.src.coffee,    [ 'coffee' ]
   gulp.watch config.paths.src.css,       [ 'css' ]
-  gulp.watch config.paths.src.templates, [ 'templates' ]
+  gulp.watch [
+    config.paths.src.coffee,
+    config.paths.src.templates
+  ], [ 'templates' ]

 # Public tasks
 gulp.task 'clean', (cb) ->


Compile references

Compile references

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -6,6 +6,7 @@
 	ts: './src/**/*.ts'
 	webRes: './src/web/resources/**/**'
 	webViews: './src/web/views/**/*.jade'
+	webDevReferences: './src/reference/**/*.jade'

 gulp.task 'clean', del.bind(null, ['./bin/**'])

@@ -27,6 +28,8 @@
 gulp.task 'build-web-views', ->
 	gulp.src paths.webViews
 		.pipe gulp.dest './bin/web/views'
+	gulp.src paths.webDevReferences
+		.pipe gulp.dest './bin/reference'

 gulp.task 'build', ['build-ts', 'build-web-res', 'build-web-views']


cleanup

cleanup

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -11,23 +11,8 @@
 autoReload = require 'gulp-auto-reload'
 changed    = require 'gulp-changed'

-#outbin = './Yakyak.app'
 outapp = './app'
 outui  = outapp + '/ui'
-
-#copyPrebuilt = ->
-#  # XXX this stinks, but gulp can't deal with
-#  # the symlinks in the binary.
-#  gutil.log 'Copying prebuilt binary to', gutil.colors.magenta(outbin)
-#  execSync 'cp -R node_modules/electron-prebuilt/dist/Electron.app Yakyak.app'
-#  gutil.log 'Change app name to Yakyak'
-#  execSync 'sed -i.bak s/Electron/Yakyak/ Yakyak.app/Contents/Info.plist'
-#  execSync 'sed -i.bak s/com\.github\.electron/com\.github\.yakyak/ Yakyak.app/Contents/Info.plist'
-#  execSync 'mv Yakyak.app/Contents/MacOS/Electron Yakyak.app/Contents/MacOS/Yakyak'
-#  execSync 'cp src/icons/atom.icns Yakyak.app/Contents/Resources/atom.icns'
-
-#gulp.task 'pre', ->
-#  copyPrebuilt() unless fs.existsSync outbin

 paths =
   README:  './README.md'
@@ -116,7 +101,6 @@

 gulp.task 'clean', (cb) ->
-#  rimraf outbin, cb

 gulp.task 'default', ['package', 'coffee', 'html', 'images', 'less', 'fontello']


Dev server falls back

Dev server falls back

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -38,6 +38,7 @@
         livereload: false
         directoryListing: false
         open: false
+        fallback: 'index.html'
       })

 gulp.task 'default', ['bower:concat', 'bower:css', 'coffee:compile'], ->


Adding logging for testing purposes

Adding logging for testing purposes

--- scripts/jira-linkifier.coffee
+++ scripts/jira-linkifier.coffee
@@ -30,10 +30,13 @@
     robot.brain.set 'jira-projects', projects

     robot.hear new Regexp(shortcode + "-([0-9]*)", "i"), (mention) ->
+      console.log("Starting to check on " + shortcode + - mention.match[1])
       robot.http(process.env.HUBOT_JIRA_INSTANCE_URL + "/rest/api/2/issue/" + shortcode + "-" + mention.match[1])
         .get( (err, req) ->
+          console.log("adding username and password to request")
           req.auth(process.env.HUBOT_JIRA_READER_USERNAME, process.env.HUBOT_JIRA_READER_PASSWORD)
         ) (err, res, body) ->
+          console.log("Recieved response from jira: " + body)
           issue = JSON.parse(body)
           mention.send(issue.key + ": " + issue.fields.summary + "(" + issue.fields.customfield_10300 + ")")


Close over the looped variables so Mocha is actually testing the right stuff

--- test/processor_test.coffee
+++ test/processor_test.coffee
@@ -41,6 +41,8 @@
       assert.equal(Processor.process(emblem), hbs)

     for group_name, example_group of examples
-      for example in example_group
-        it "should process the #{group_name} example '#{example}' correctly", ->
-          assert.equal(Processor.process(example[0]), example[1])
+      do (group_name, example_group) ->
+        for example in example_group
+          do (group_name, example) ->
+            it "should process the #{group_name} example '#{example}' correctly", ->
+              assert.equal(Processor.process(example[0]), example[1])


fixed url regex matcher so it does not match html


refined prefix detection

refined prefix detection

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -8,12 +8,18 @@
 className = baseName[0].toUpperCase() + baseName.slice 1
 testEl = document.createElement 'div'
 prefixList = ['Webkit', 'Moz', 'O', 'ms']
-transform = do ->
-  return 'transform' if testEl.style.transform?
+
+prefixProp = (prop) ->
+  return prop.toLowerCase() if testEl.style.transform?
   for prefix in prefixList
-    prefixed = prefix + 'Transform'
+    prefixed = prefix + prop
     return prefixed if testEl.style[prefixed]?
   false
+
+css =
+  transform: prefixProp 'Transform'
+  perspective: prefixProp 'Perspective'
+

 class window.Hexaflip


fixed daylight saving time unit test. now works also with node 0.10.x

--- test/unit/data-type.coffee
+++ test/unit/data-type.coffee
@@ -1,5 +1,6 @@
 TYPES = require('../../src/data-type')
 WritableTrackingBuffer = require('../../src/tracking-buffer/writable-tracking-buffer')
+ReadableTrackingBuffer = require('../../src/tracking-buffer/readable-tracking-buffer')

 exports.noTypeOverridesByAliases = (test) ->
   typesByName = {}
@@ -22,10 +23,10 @@
 # Test date calculation for non utc date during daylight savings period
 exports.smallDateTimeDaylightSaving = (test) ->
   type = TYPES.typeByName['SmallDateTime']
-  for testSet in [[new Date('2015-06-18T23:59:59+0200'),42171],
-                  [new Date('2015-06-19T00:00:00+0200'),42172],
-                  [new Date('2015-06-19T23:59:59+0200'),42172],
-                  [new Date('2015-06-20T00:00:00+0200'),42173]]
+  for testSet in [[new Date('2015-06-18T21:59:59'),42171],
+                  [new Date('2015-06-18T22:00:00'),42172],
+                  [new Date('2015-06-19T21:59:59'),42172],
+                  [new Date('2015-06-19T22:00:00'),42173]]
     buffer = new WritableTrackingBuffer 8
     parameter = { value: testSet[0] }
     expectedNoOfDays = testSet[1]
@@ -35,10 +36,10 @@

 exports.dateTimeDaylightSaving = (test) ->
   type = TYPES.typeByName['DateTime']
-  for testSet in [[new Date('2015-06-18T23:59:59+0200'),42171],
-                  [new Date('2015-06-19T00:00:00+0200'),42172],
-                  [new Date('2015-06-19T23:59:59+0200'),42172],
-                  [new Date('2015-06-20T00:00:00+0200'),42173]]
+  for testSet in [[new Date('2015-06-18T21:59:59'),42171],
+                  [new Date('2015-06-18T22:00:00'),42172],
+                  [new Date('2015-06-19T21:59:59'),42172],
+                  [new Date('2015-06-19T22:00:00'),42173]]
     buffer = new WritableTrackingBuffer 16
     parameter = { value: testSet[0] }
     expectedNoOfDays = testSet[1]
@@ -46,28 +47,33 @@
     test.strictEqual(buffer.buffer.readInt32LE(1), expectedNoOfDays)
   test.done()

-exports.dateTimeDaylightSaving = (test) ->
+exports.dateTime2DaylightSaving = (test) ->
   type = TYPES.typeByName['DateTime2']
-  for testSet in [[new Date('2015-06-18T23:59:59+0200'),735766],
-                  [new Date('2015-06-19T00:00:00+0200'),735767],
-                  [new Date('2015-06-19T23:59:59+0200'),735767],
-                  [new Date('2015-06-20T00:00:00+0200'),735768]]
+  for testSet in [[new Date('2015-06-18T21:59:59'),735766],
+                  [new Date('2015-06-18T22:00:00'),735767],
+                  [new Date('2015-06-19T21:59:59'),735767],
+                  [new Date('2015-06-19T22:00:00'),735768]]
     buffer = new WritableTrackingBuffer 16
     parameter = { value: testSet[0], scale: 0 }
     expectedNoOfDays = testSet[1]
     type.writeParameterData(buffer, parameter, { useUTC: false })
-    test.strictEqual(buffer.buffer.readUIntLE(4,3), expectedNoOfDays)
+    rBuffer = new ReadableTrackingBuffer(buffer.buffer)
+    rBuffer.readUInt8()
+    rBuffer.readUInt24LE()
+    test.strictEqual(rBuffer.readUInt24LE(), expectedNoOfDays)
   test.done()

 exports.dateDaylightSaving = (test) ->
   type = TYPES.typeByName['Date']
-  for testSet in [[new Date('2015-06-18T23:59:59+0200'),735766],
-                  [new Date('2015-06-19T00:00:00+0200'),735767],
-                  [new Date('2015-06-19T23:59:59+0200'),735767],
-                  [new Date('2015-06-20T00:00:00+0200'),735768]]
+  for testSet in [[new Date('2015-06-18T21:59:59'),735766],
+                  [new Date('2015-06-18T22:00:00'),735767],
+                  [new Date('2015-06-19T21:59:59'),735767],
+                  [new Date('2015-06-19T22:00:00'),735768]]
     buffer = new WritableTrackingBuffer 16
     parameter = { value: testSet[0] }
     expectedNoOfDays = testSet[1]
     type.writeParameterData(buffer, parameter, { useUTC: false })
-    test.strictEqual(buffer.buffer.readUIntLE(1,3), expectedNoOfDays)
+    rBuffer = new ReadableTrackingBuffer(buffer.buffer)
+    rBuffer.readUInt8()
+    test.strictEqual(rBuffer.readUInt24LE(), expectedNoOfDays)
   test.done()


OHM-119: Fixed breaking tests

OHM-119: Fixed breaking tests

--- test/unit/tasksTest.coffee
+++ test/unit/tasksTest.coffee
@@ -3,15 +3,13 @@
 server = require "../../lib/server"
 Transaction = require("../../lib/model/transactions").Transaction
 Task = require("../../lib/model/tasks").Task
-worker = require "../../lib/tasks"
+tasks = require "../../lib/tasks"
 testUtils = require "../testUtils"
 auth = require("../testUtils").auth
-ObjectId = require('mongoose').Types.ObjectId;
-
-config = require("../../lib/config/config")
-MongoClient = require("mongodb").MongoClient
-
-describe "API Integration Tests", ->
+ObjectId = require('mongoose').Types.ObjectId
+
+
+describe "Rerun Task Tests", ->

   describe 'Transaction Rerun Worker Api testing', ->

@@ -51,75 +49,35 @@
     after (done) ->
       Transaction.remove {}, ->
         Task.remove {}, ->
-          MongoClient.connect config.mongo.url, (err, db) ->
-            mongoCollection = db?.collection "jobs"
-            mongoCollection.drop()
-            done()
+          done()

     beforeEach ->
       authDetails = auth.getAuthDetails()

-    describe '*rerunGetTaskTransactionsData()', ->
-
-      it 'should run rerunGetTaskTransactionsData() and return Transaction object successfully', (done) ->
-
-        taskID = '53c4dd063b8cb04d2acf0adc'
+    describe '*rerunGetTransaction()', ->
+
+      it 'should run rerunGetTransaction() and return Transaction object successfully', (done) ->
+
         transactionID = '53bfbccc6a2b417f6cd14871'

-        # check task object before function run
-        Task.findOne { _id: taskID }, (err, task) ->
-          task.status.should.equal "Queued"
-          task.remainingTransactions.should.equal 2
-
-        # run the worker function and check results
-        worker.rerunGetTaskTransactionsData taskID, transactionID, (err, transaction) ->
+        # run the tasks function and check results
+        tasks.rerunGetTransaction transactionID, (err, transaction) ->
           transaction.clientID.toString().should.equal "42bbe25485e77d8e5daad4b4"
           transaction.status.should.equal "Completed"
           transaction.request.path.should.equal "/sample/api"
           transaction.request.querystring.should.equal "param=hello"
           transaction.request.method.should.equal "GET"

-          # check task object after function run to see changes made
-          Task.findOne { _id: taskID }, (err, task) ->
-            task.status.should.equal "Processing"
-            task.remainingTransactions.should.equal 2
-            done()
-
-      it 'should run rerunGetTaskTransactionsData() and return Task not found error', (done) ->
-
-        # taskID that does not exist should throw error
-        taskID = 'aaaaaaaaaabbbbbbbbbbcccc'
-        transactionID = '53bfbccc6a2b417f6cd14871'
-
-        # run the worker function and check results
-        worker.rerunGetTaskTransactionsData taskID, transactionID, (err, transaction) ->
-          err.should.equal "Could not find the task for ID #aaaaaaaaaabbbbbbbbbbcccc. The job has failed to process..."
-          done()
-
-
-      it 'should run rerunGetTaskTransactionsData() and return Transaction not found in Task object error', (done) ->
-
-        taskID = '53c4dd063b8cb04d2acf0adc'
-        # transactionID that isnt found in task object should throw error
-        transactionID = 'ccccccccccbbbbbbbbbbaaaa'
-
-        # run the worker function and check results
-        worker.rerunGetTaskTransactionsData taskID, transactionID, (err, transaction) ->
-          err.should.equal "Rerun Transaction #ccccccccccbbbbbbbbbbaaaa - Not found in Task object!"
-          done()
-
-
-      it 'should run rerunGetTaskTransactionsData() and return Transaction not found error', (done) ->
-
-        taskID = '53c4dd063b8cb04d2acf0adc'
-        # transactionID that exists in the task object but is not an actual transaction should throw error
+          done()
+
+      it 'should run rerunGetTaskTransactionsData() and return transaction not found error', (done) ->
+
         transactionID = 'aaaaaaaaaabbbbbbbbbbcccc'

-        # run the worker function and check results
-        worker.rerunGetTaskTransactionsData taskID, transactionID, (err, transaction) ->
-          err.should.equal "Rerun Transaction #aaaaaaaaaabbbbbbbbbbcccc - could not be found!"
-          done()
-
+        # run the tasks function and check results
+        tasks.rerunGetTransaction transactionID, (err, transaction) ->
+          err.should.equal "Transaction aaaaaaaaaabbbbbbbbbbcccc could not be found"
+          done()

     describe '*rerunSetHTTPRequestOptions()', ->
@@ -129,8 +87,8 @@
         taskID = '53c4dd063b8cb04d2acf0adc'
         transactionID = "53bfbccc6a2b417f6cd14871"
         Transaction.findOne { _id: transactionID }, (err, transaction) ->
-          # run the worker function and check results
-          worker.rerunSetHTTPRequestOptions transaction, taskID, (err, options) ->
+          # run the tasks function and check results
+          tasks.rerunSetHTTPRequestOptions transaction, taskID, (err, options) ->
             options.should.have.property "hostname", "localhost"
             options.should.have.property "port", 7786
             options.should.have.property "path", "/sample/api?param=hello"
@@ -144,7 +102,7 @@

         taskID = '53c4dd063b8cb04d2acf0adc'
         transaction = null
-        worker.rerunSetHTTPRequestOptions transaction, taskID, (err, options) ->
+        tasks.rerunSetHTTPRequestOptions transaction, taskID, (err, options) ->
           err.should.equal "An empty Transaction object was supplied. Aborting HTTP options configuration"
           done()

@@ -159,13 +117,13 @@
           transactionID = "53bfbccc6a2b417f6cd14871"
           Transaction.findOne { _id: transactionID }, (err, transaction) ->

-            # run the worker function and check results
-            worker.rerunSetHTTPRequestOptions transaction, taskID, (err, options) ->
+            # run the tasks function and check results
+            tasks.rerunSetHTTPRequestOptions transaction, taskID, (err, options) ->

               # transaction object retrieved from fineOne
               # options generated from 'rerunSetHTTPRequestOptions' function

-              worker.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
+              tasks.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->

                 HTTPResponse.transaction.should.have.property "status", "Completed"
                 HTTPResponse.should.have.property "body", "Mock response for rerun Transaction #53bfbccc6a2b417f6cd14871"
@@ -181,7 +139,7 @@

           options = null

-          worker.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
+          tasks.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
             err.should.equal "An empty 'Options' object was supplied. Aborting HTTP Send Request"
             done()

@@ -195,7 +153,7 @@
         options.method = "GET"

         transaction = null
-        worker.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
+        tasks.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
           err.should.equal "An empty 'Transaction' object was supplied. Aborting HTTP Send Request"
           done()

@@ -213,60 +171,11 @@
               path: "/fakepath",
               method: "GET"  }

-            worker.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
+            tasks.rerunHttpRequestSend options, transaction, (err, HTTPResponse) ->
               HTTPResponse.transaction.should.have.property "status", "Failed"
               HTTPResponse.should.have.property "status", 500
               HTTPResponse.should.have.property "message", "Internal Server Error"
               done()
-
-
-    describe '*rerunUpdateTaskObject()', ->
-
-      it 'should run rerunUpdateTaskObject() and return successfully updated task object', (done) ->
-
-        taskID = '53c4dd063b8cb04d2acf0adc'
-        transactionID = '53bfbcd06a2b417f6cd14872'
-
-        # check task object before function run
-        Task.findOne { _id: taskID }, (err, task) ->
-          task.status.should.equal "Processing"
-          task.remainingTransactions.should.equal 1
-
-          HTTPResponse = transaction: { status: "Completed" }
-
-          worker.rerunUpdateTaskObject taskID, transactionID, HTTPResponse, (err, task) ->
-            task.status.should.equal "Completed"
-            done()
-
-      it 'should run rerunUpdateTaskObject() and return error if no taskID supplied', (done) ->
-
-        taskID = null
-        transactionID = '53bfbcd06a2b417f6cd14872'
-        HTTPResponse = transaction: { status: "Completed" }
-
-        worker.rerunUpdateTaskObject taskID, transactionID, HTTPResponse, (err, task) ->
-          err.should.equal "No taskID supplied. Task cannot be updated"
-          done()
-
-      it 'should run rerunUpdateTaskObject() and return error if no transactionID supplied', (done) ->
-
-        taskID = '53c4dd063b8cb04d2acf0adc'
-        transactionID = null
-        HTTPResponse = transaction: { status: "Completed" }
-
-        worker.rerunUpdateTaskObject taskID, transactionID, HTTPResponse, (err, task) ->
-          err.should.equal "No transactionID supplied. Task cannot be updated"
-          done()
-
-      it 'should run rerunUpdateTaskObject() and return error if no response object supplied', (done) ->
-
-        taskID = '53c4dd063b8cb04d2acf0adc'
-        transactionID = '53bfbcd06a2b417f6cd14872'
-        HTTPResponse = null
-
-        worker.rerunUpdateTaskObject taskID, transactionID, HTTPResponse, (err, task) ->
-          err.should.equal "No response supplied. Task cannot be updated"
-          done()

     describe 'Rerun TCP Transactions', ->

@@ -283,7 +192,7 @@
             }
           }

-          worker.rerunTcpRequestSend channel, transaction, (err, data) ->
+          tasks.rerunTcpRequestSend channel, transaction, (err, data) ->
             data.body.should.be.exactly 'TCP OK'
             done()


Add test module for data mixin.

Add test module for data mixin.

--- tests/js/core.mixin.coffee
+++ tests/js/core.mixin.coffee
@@ -82,4 +82,26 @@
     assertDynamicMixinInstance.call @, assert
     return

+  QUnit.module 'hlf.mixins.data',
+    setup: ->
+      @instance =
+        attr: (name) -> "some-ns-#{name}"
+        $el: $ '<div>'
+      hlf.applyMixin @instance, [], 'data'
+
+  QUnit.test '#data', (assert) ->
+    @instance.$el.data 'some-ns-some-attr', 'some-value'
+    assert.strictEqual @instance.data('some-attr'), 'some-value',
+      'it gets value for single key'
+
+    @instance.data 'some-attr', 'some-value'
+    assert.strictEqual @instance.$el.data('some-ns-some-attr'), 'some-value',
+      'it sets value for single key'
+
+    @instance.data { 'some-attr': 'some-value', 'other-attr': 'other-value' }
+    assert.strictEqual @instance.$el.data('some-ns-some-attr'), 'some-value',
+      'it sets values in pairs object'
+    assert.strictEqual @instance.$el.data('some-ns-other-attr'), 'other-value',
+      'it sets values in pairs object'
+
   yes


Cleanup trailing whitespace

Cleanup trailing whitespace

--- app/assets/service/upload.coffee
+++ app/assets/service/upload.coffee
@@ -12,10 +12,9 @@
       file.store.save()
       return

-
-    # If there's an error in the file-upload,
-    # let's first kill the upload process, then
-    # send back the error message.
+    # If there's an error in the file-upload,
+    # let's first kill the upload process, then
+    # send back the error message.
     fileError: (file, message) ->
       file.store.error message
       return


JMachine: shareWith: fix logic with the curiosity of permanent flag.

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -179,7 +179,9 @@
     newUsers = []

     for u in users
-      if u.permanent isnt permanent or not userId.equals u.id
+      unless userId.equals u.id
+        newUsers.push u
+      else if not permanent? and u.permanent
         newUsers.push u

     return newUsers
@@ -192,7 +194,18 @@
     {user, asOwner, permanent} = options

     newUsers = excludeUser { users, user, permanent }
-    newUsers.push { id: user.getId(), owner: no, approved: no, permanent }
+
+    userId = user.getId()
+    for u in newUsers
+      break  if inList = userId.equals u.id
+
+    unless inList
+      newUsers.push {
+        id       : user.getId()
+        owner    : no
+        approved : no
+        permanent
+      }

     return newUsers

@@ -295,7 +308,6 @@

     asUser    ?= yes
     asOwner   ?= no
-    permanent ?= no

     unless target?
       return callback new KodingError "Target required."


added export

added export

--- src/model/auditRecords.coffee
+++ src/model/auditRecords.coffee
@@ -48,3 +48,5 @@
   "ActiveParticipant":[ activeParticipantSchema ]
   "AuditSourceIdentification": AuditSourceIdentificationSchema
   "ParticipantObjectIdentification": [ ParticipantObjectIdentificationSchema ]
+
+exports.auditRecord = mongoose.model 'auditRecord', auditRecordSchema


BUG-FIX Parsing condition when template is not set

--- src/modules/controller.coffee
+++ src/modules/controller.coffee
@@ -88,7 +88,7 @@

       for item in buffer
         helper = new item.controller(el: el, parent: @, mainAttr: item.value)
-        stop = true if helper.template isnt undefined
+        stop = true if helper.template

     @_parseNode child for child in node.childNodes unless stop or not node
     undefined


/v0/hello endopoint

/v0/hello endopoint

--- server.coffee
+++ server.coffee
@@ -37,6 +37,12 @@
 			classes: data.classes
 		}

+v0.get '/hello', (req, res)->
+	res.send {
+		success: true
+		newsHTML: 'Welcome to SVU Student\'s Helper<br />Use the form below to login.'
+	}
+

 v0.use '/student', studentsRouter


Fix for newer express.js

Fix for newer express.js

--- server.coffee
+++ server.coffee
@@ -16,6 +16,7 @@

 app.configure ->
   app.use(express.bodyParser())
+  app.set('dirname', __dirname)
   app.use(app.router)
   app.use(express.static(__dirname + "/public/"))
   app.use(express.errorHandler({ dumpExceptions: true, showStack: true}))


server: print process pid to help debugging

--- server.coffee
+++ server.coffee
@@ -72,7 +72,7 @@
     @socket = require("socket.io").listen(@server)
     @socket.of("/webso/pty").on "connection", (so) ->
       new PTYServer(so)
-    console.log "Listening to port #{port}"
+    console.log "Process #{process.pid} Listening to port #{port}"

   handle: (verb,path,action) ->
     handler = (req,res) ->


IP is removed from createServer param list

--- server.coffee
+++ server.coffee
@@ -40,4 +40,4 @@
     .sendFile("proxy.liquid", root: '.')

-require('http').createServer(app).listen(process.env.PORT, process.env.IP)
+require('http').createServer(app).listen(process.env.PORT)


extract to method

extract to method

--- script.coffee
+++ script.coffee
@@ -2,28 +2,34 @@
   u1 = '/stats/8145ccd1-7eb6-11e3-8871c2aa1a975e8c.201401.xml' #twp
   u2 = '/stats/d1566d63-84ff-11e3-bcf9cf3dda222cab.201401.xml' #dhf

-  doit = (url, div) ->
+  doajax = (url, callback) ->
     $.ajax
       url: url
       dataType: 'html'
       success: (response) ->
-        xmlDoc = $.parseXML(response)
-        $xml = $(xmlDoc)
-        arr = []
-        $xml.find('S').each ->
-          timestamp = new Date($(this).attr('T'))
-          value = parseFloat($(this).attr('AI1'))
-          arr.push [timestamp, value]
+        callback response

-        new Dygraph($(div)[0], arr,
-          includeZero: true
-          delimiter: ';'
-          rollPeriod: 1
-          showRoller: true
-          digitsAfterDecimal: 3
-          strokeWidth: 1
-          labels: ['Time', 'value']
-        )
+  doit = (url, div) ->
+    doajax url, (res) ->
+      xmlDoc = $.parseXML(res)
+      $xml = $(xmlDoc)
+      arr = []
+      $xml.find('S').each ->
+        timestamp = new Date($(this).attr('T'))
+        value = parseFloat($(this).attr('AI1'))
+        arr.push [timestamp, value]
+
+      new Dygraph($(div)[0], arr,
+        includeZero: true
+        delimiter: ';'
+        rollPeriod: 1
+        showRoller: true
+        digitsAfterDecimal: 3
+        strokeWidth: 1
+        labels: ['Time', 'value']
+      )
+
+

   doit(u1, div1)
   doit(u2, div2)


Add saml emailFieldName option

Add saml emailFieldName option

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -437,6 +437,7 @@
 	settings.externalAuth = true
 	settings.saml =
 		identityServiceName: process.env["SHARELATEX_SAML_IDENTITY_SERVICE_NAME"]
+		emailFieldName: process.env["SHARELATEX_SAML_EMAIL_FIELD_NAME"]
 		server:
 			# strings
 			entryPoint: process.env["SHARELATEX_SAML_ENTRYPOINT"]


added textEncoding for email option

added textEncoding for email option

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -348,7 +348,7 @@
 			secure: parse(process.env["SHARELATEX_EMAIL_SMTP_SECURE"])
 			ignoreTLS: parse(process.env["SHARELATEX_EMAIL_SMTP_IGNORE_TLS"])

-
+		textEncoding:  process.env["SHARELATEX_EMAIL_TEXT_ENCODING"]
 		templates:
 			customFooter: process.env["SHARELATEX_CUSTOM_EMAIL_FOOTER"]


Delete obsolete new question form spec

Delete obsolete new question form spec

--- spec/javascripts/views/new_question_form_spec.js.coffee
+++ spec/javascripts/views/new_question_form_spec.js.coffee
@@ -1,18 +0,0 @@
-describe "Views.NewQuestionForm", ->
-  form = null
-
-  beforeEach ->
-    form = new Views.NewQuestionForm
-
-  describe "when the submit button is clicked", ->
-    it "creates a new question and dismisses the form", ->
-      spyOn(form, 'modal')
-      form.textarea.val("What's your favorite color?")
-      form.find('button').click()
-      expect(Monarch.Remote.Server.creates.length).toBe 1
-      expect(form.modal).not.toHaveBeenCalled()
-
-      Monarch.Remote.Server.lastCreate().succeed()
-
-      expect(form.modal).toHaveBeenCalledWith('hide')
-


minors

minors

--- server.coffee
+++ server.coffee
@@ -254,15 +254,7 @@
             # rc.expire "communities", 200, redis.print
             callback err, repos
     else
-      repos = for name, repo of hash
-        json = null
-        try
-          json = JSON.parse(repo)
-        catch error
-          console.log "cannot parse data for repo", name, repo
-        json
-      communities = _.compact repos
-      callback undefined, communities
+      parseCache hash, callback

 app.get "/api/communities/:community", (req, res) ->
@@ -334,15 +326,18 @@
             # rc.expire "communities", 200, redis.print
             callback undefined, topics
     else
-      topics = for name, topic of hash
-        json = null
-        try
-          json = JSON.parse(topic)
-        catch error
-          console.log "cannot parse data for repo", name, topic
-        json
-      topics = _.compact topics
-      callback undefined, topics
+      parseCache hash, callback
+
+parseCache = (hash, callback) ->
+  items = for name, item of hash
+    json = null
+    try
+      json = JSON.parse(item)
+    catch error
+      console.log "cannot parse data for items", name, item
+    json
+  items = _.compact items
+  callback undefined, items

 getMembers = (community, callback) ->


remove kue from server file

remove kue from server file

--- server.coffee
+++ server.coffee
@@ -8,9 +8,6 @@

 ###

-kue = require 'kue'
-jobs = kue.createQueue()
-
 app = module.exports = require('railway').createServer()

 if not module.parent


Comment out listening event for now

Comment out listening event for now

--- server.coffee
+++ server.coffee
@@ -21,7 +21,7 @@

   start: (port) ->
     server = dgram.createSocket("udp4")
-    server.on("listening", @onSocketListen)
+    # server.on("listening", @onSocketListen)
     server.on("message", @onSocketMessage)
     server.listenServer = this
     server.bind(port)
@@ -31,8 +31,8 @@
   stop: ->
     @server.close()

-  onSocketListen: ->
-    console.log "server listening #{@address().address}:#{@address().port}"
+  # onSocketListen: ->
+  #   console.log "server listening #{@address().address}:#{@address().port}"

   onSocketMessage: (msg, rinfo) ->
     # Handle the message in the scope of ListenServer, not the Socket


Added google analytics to HTML output

Added google analytics to HTML output

--- server.coffee
+++ server.coffee
@@ -17,6 +17,7 @@
         </div>
       </div>
     </div>
+    <script type="text/javascript">!function(a,b,c,d,e,f,g){a.GoogleAnalyticsObject=e,a[e]=a[e]||function(){(a[e].q=a[e].q||[]).push(arguments)},a[e].l=1*new Date,f=b.createElement(c),g=b.getElementsByTagName(c)[0],f.async=1,f.src=d,g.parentNode.insertBefore(f,g)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-42553958-1","foaas.com"),ga("send","pageview");</script>
   </body>
 </html>'


Block requests for things that look like assets

They should be in the publicPath and not being caught by the frontend. This is just to speeds up 404s.

--- server/routes/frontend.coffee
+++ server/routes/frontend.coffee
@@ -22,6 +22,10 @@
 plugins = app.get 'plugins'

 app.all '/:frontend*?', (req, res, next) ->
+
+  # Cheating a bit, but if it's not in their publicPath, they shouldn't be serving it w/Templates
+  return next() if req.path.match /\.(gif|jpg|css|js|ico)$/
+
   # We could use a $where here, but it's basically the same
   # since a basic $where scans all rows (plus this gives us more flexibility)
   Route.find {}, null, sort: 'sort', (err, routes) ->


Add custom data cases to test scripts

Add custom data cases to test scripts

--- test/relationship-test.coffee
+++ test/relationship-test.coffee
@@ -53,5 +53,20 @@
       it "should return a magic error number", ->
         @undertest.evaluate(2,2).should.equal Relationship.errValue

-      it "should indivcate an error", ->
+      it "should indicate an error", ->
         @undertest.hasError.should.be.true
+
+    describe "a custom relationship", ->
+      beforeEach ->
+        @customData = [[1,5],[2,16],[3,11],[4,16]]
+        @arguments = {customData: @customData}
+        @undertest = new Relationship(@arguments)
+
+      it "should retrieve a point via lookup", ->
+        @undertest.evaluate(3,0).should.equal 11
+
+      it "should handle out-of-range lookups", ->
+        @undertest.evaluate(5,0).should.equal 0
+
+      it "should handle non-integer lookups via rounding", ->
+        @undertest.evaluate(2.9,0).should.equal 11


Only spawn 1 worker for tests

Only spawn 1 worker for tests

--- test/test_rack_handler.coffee
+++ test/test_rack_handler.coffee
@@ -9,7 +9,7 @@
 {prepareFixtures, fixturePath, touch, serve} = require "test_helper"

 serveApp = (path, callback) ->
-  configuration = new Configuration root: fixturePath("apps"), logRoot: fixturePath("tmp/logs")
+  configuration = new Configuration root: fixturePath("apps"), logRoot: fixturePath("tmp/logs"), workers: 1
   handler       = new RackHandler configuration, fixturePath(path)
   server        = express.createServer()


Remove integration if trigger response is 410

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -54,6 +54,10 @@
 	HTTP.call 'POST', url, opts, (error, result) ->
 		console.log error, result
 		if not result? or result.statusCode isnt 200
+			if result.statusCode is 410
+				RocketChat.models.Integrations.remove _id: trigger._id
+				return
+
 			if tries <= 6
 				# Try again in 0.1s, 1s, 10s, 1m40s, 16m40s, 2h46m40s and 27h46m40s
 				Meteor.setTimeout ->


Prevent dirty in outgoing webhook records

Closes #4246

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -92,35 +92,30 @@
 		return

+addIntegration = (record) ->
+	if _.isEmpty(record.channel)
+		channels = [ '__any' ]
+	else
+		channels = [].concat(record.channel)
+
+	for channel in channels
+		triggers[channel] ?= {}
+		triggers[channel][record._id] = record
+
+removeIntegration = (record) ->
+	for channel, trigger of triggers
+		delete trigger[record._id]
+
 RocketChat.models.Integrations.find({type: 'webhook-outgoing'}).observe
 	added: (record) ->
-		if _.isEmpty(record.channel)
-			channels = [ '__any' ]
-		else
-			channels = [].concat(record.channel)
-
-		for channel in channels
-			triggers[channel] ?= {}
-			triggers[channel][record._id] = record
+		addIntegration(record)

 	changed: (record) ->
-		if _.isEmpty(record.channel)
-			channels = [ '__any' ]
-		else
-			channels = [].concat(record.channel)
-
-		for channel in channels
-			triggers[channel] ?= {}
-			triggers[channel][record._id] = record
+		removeIntegration(record)
+		addIntegration(record)

 	removed: (record) ->
-		if _.isEmpty(record.channel)
-			channels = [ '__any' ]
-		else
-			channels = [].concat(record.channel)
-
-		for channel in channels
-			delete triggers[channel][record._id]
+		removeIntegration(record)

 ExecuteTriggerUrl = (url, trigger, message, room, tries=0) ->


EditorPane: Send content with change.

EditorPane: Send content with change.

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -104,6 +104,7 @@

     ace.on 'FileContentChanged', =>
       change.type = 'ContentChange'
+      change.context.file.content = @getContent()

       @emit 'ChangeHappened', change


EditorPane: Fixed autosave for untitled files in collaboration.

In an active collaboration Untitled file instances won't be DummyMachine.

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -54,7 +54,7 @@

   handleAutoSave: ->
-    return   if @getFile().machine instanceof DummyMachine
+    return   if @getFile().path.indexOf('localfile:/') > -1
     @save()  if @getAce().isContentChanged()


Workspace: Updated demo code.

Workspace: Updated demo code.

--- client/app/Applications/Demos.kdapplication/AppController.coffee
+++ client/app/Applications/Demos.kdapplication/AppController.coffee
@@ -15,26 +15,27 @@

   loadView:(mainView)->
     options =
-      title              : "Kodelicious"
+      title              : "Teamwork"
       version            : "0.1"
-      joinModalContent   : "
Here is the join modal
"
-      shareModalContent  : "
Here is the share modal
"
+
+      joinModalTitle     : "Join a coding session"
+      joinModalContent   : "
Paste the session key that you received and start coding together.
"
+
+      shareSessionKeyInfo: "
This is your session key, you can share this key with your friends to work together.
"
+
       firebaseInstances  : "teamwork-local"
       # firebaseInstances  : ["kd-prod-1", "kd-prod-2", "kd-prod-3", "kd-prod-4", "kd-prod-5"]
       panels             : [
         {
-          title          : "Collaborative IDE"
-          hint           : "
This is the Collaborative IDE that you ever dreamed. Use it wisely.
"
+          title          : "Teamwork"
+          hint           : "
This is a collaborative coding environment where you can team up with others and work on the same code.
"
           buttons        : [
             {
               title      : "Join"
               cssClass   : "cupid-green join-button"
               callback   : (panel, workspace) => workspace.showJoinModal()
             }
-            {
-              title      : "Share"
-              callback   : (panel, workspace) => workspace.showShareModal()
-            }
+
           ]
           panes: [
             {


remove model cache, add findById

remove model cache, add findById

--- client/lanes/models/query/ArrayResult.coffee
+++ client/lanes/models/query/ArrayResult.coffee
@@ -59,22 +59,12 @@

     modelAt: (index, options) ->
         row = @_rowAt(index)
-        @modelCache ||= {}
-        id = @idForRow(row)
-        if _.isBlank(id)
-            @_rowToModel(row)
-        else
-            @modelCache[id] ||= @_rowToModel(row)
+        @_rowToModel(row)

     saveModelChanges: (model, index) ->
         row = @_rowAt(index)
-        @modelCache[ @idForRow(row)] = model
-        data = model.serialize()
         for field, i in @result.query.fields.models
-            row[field.fetchIndex] = data[field.id]
-
-    idForRow: (row) ->
-        row[@result.query.idIndex]
+            row[field.fetchIndex] = model[field.id]

     addBlankRow: (index) ->
         model = new @result.query.model
@@ -127,7 +117,7 @@
     eachRow: (fn) ->
         for i in [0...@length]
             row = @rowAt(i)
-            fn(row, row[@xDataColumn], i)
+            return i if 'break' is fn(row, row[@xDataColumn], i)

     map: (fn) ->
         rows = []
@@ -135,6 +125,12 @@
             rows.push fn(row, xd, i)
         rows

+    findById: (id) ->
+        @eachRow (row, xd, i) =>
+            return 'break' if id is @idForRow(row)
+
+    idForRow: (row) ->
+        row[@query.idIndex]

     filteredRows: (fn) ->
         found = []
@@ -146,7 +142,10 @@
         @pageForIndex(index).modelAt(index)

     saveModelChanges: (model, index) ->
+        if _.isUndefined(index)
+            index = @findById( model.getId() )
         @pageForIndex(index).saveModelChanges(model, index)
+        @query.changeCount += 1

     removeRow: (index = 0) ->
         @total -= 1


Fix indicator of active room in menu

Fix indicator of active room in menu

--- client/views/app/sideNav/chatRoomItem.coffee
+++ client/views/app/sideNav/chatRoomItem.coffee
@@ -25,7 +25,7 @@
 			when 'p' then return 'icon-lock'

 	active: ->
-		if FlowRouter.getParam('_id')? and FlowRouter.getParam('_id') is this.rid
+		if Session.get('openedRoom') is this.rid
 			return 'active'

 	canLeave: ->


made temporary function global so we can move stuff around to other files

--- core/app/backbone/views/autocompleted_add_to_channel_view.coffee
+++ core/app/backbone/views/autocompleted_add_to_channel_view.coffee
@@ -1,6 +1,7 @@
 #TODO: check if this hide-input class thingy has some importance

-updateWindowHeight = ->  window.updateHeight() if window.updateHeight
+# TODO: remove this, use triggers instead
+window.updateWindowHeight = ->  window.updateHeight() if window.updateHeight

 class window.AutoCompletedAddToChannelView extends Backbone.Marionette.Layout
   tagName: "div"


pricing: fix thank you view reference

pricing: fix thank you view reference

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -32,7 +32,7 @@
       then "Please check your email to complete your registration."
       else "Now it’s time, time to start Koding!"

-    @addSubView thankYou = new KDCustomHTMLView
+    @addSubView @thankYou = new KDCustomHTMLView
       cssClass : "pricing-thank-you"
       partial  :
         """


now without newbie mistakes

now without newbie mistakes

--- client/collection_view.coffee
+++ client/collection_view.coffee
@@ -132,7 +132,6 @@
       doc_iter[final_key] = field.value

       field.value = ''
-    debugger
     Meteor.call "admin_#{Session.get('collection_name')}_insert", new_doc

   'click #admin-load-more': (e) ->


adds unit test for wrap-able module lifecycle hooks

--- tests/unit/module.unit.coffee
+++ tests/unit/module.unit.coffee
@@ -155,3 +155,24 @@
     @module.reset()
     process.env.NODE_ENV = nodeEnvBackup
     expect(@module._runLifeCycleAction).not.to.have.been.called
+
+describe "Space.Module - wrappable lifecycle hooks", ->
+
+  it "allows mixins to hook into the module lifecycle", ->
+    moduleOnInitializeSpy = sinon.spy()
+    mixinOnInitializeSpy = sinon.spy()
+    MyModule = Space.Module.extend {
+      onInitialize: moduleOnInitializeSpy
+    }
+    MyModule.mixin {
+      onDependenciesReady: ->
+        @_wrapLifecycleHook 'onInitialize', (onInitialize) ->
+          onInitialize.call(this)
+          mixinOnInitializeSpy.call(this)
+    }
+    module = new MyModule()
+    module.initialize(module, new Space.Injector())
+
+    expect(moduleOnInitializeSpy).to.have.been.calledOnce
+    expect(mixinOnInitializeSpy).to.have.been.calledOnce
+    expect(moduleOnInitializeSpy).to.have.been.calledBefore(mixinOnInitializeSpy)


Zealous Recruit fix

Zealous Recruit fix

-Fixed a lowercase that caused the pilot to be missing.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -875,11 +875,11 @@
             name: "As de Concord Dawn"
             ship: "Caza Estelar del Protectorado"
         'Concord Dawn Veteran':
-            name: "Veterano de Concord Dawn"
+            name: "Veterano de Concord Dawn"dd
             ship: "Caza Estelar del Protectorado"
         'Zealous Recruit':
             name: "Recluta entusiasta"
-            ship: "Caza estelar del Protectorado"
+            ship: "Caza Estelar del Protectorado"
         'Ketsu Onyo':
             ship: "Nave de persecución clase Lancero"
             text: '''Al comienzo de la fase de Combate, puedes elegir una nave que tengas a alcance 1. Si esa nave está dentro de tus arcos de fuego normal y móvil, asígnale 1 ficha de Campo de tracción.'''


Nginx: listen for 8091 in local

Nginx: listen for 8091 in local

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -187,7 +187,7 @@
     # i have added  to koding-sandbox, koding-load, koding-prod and koding-prod-deployment-sg
     server {
       # just a random port
-      listen 81;
+      listen listen #{if environment is "dev" then 8091 else 81};
       # use generic names, do not hardcode values
       return 301 https://$host$request_uri;
     }


run: rename func

run: rename func

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -203,7 +203,7 @@
     }

     function migrate () {
-      migrations
+      apply_custom_pg_migrations

       params=(create up down version reset redo to goto)
       param=$1
@@ -573,8 +573,8 @@
     elif [ "$1" == "sanitize-email" ]; then
       node $KONFIG_PROJECTROOT/scripts/sanitize-email

-    elif [ "$1" == "migrations" ]; then
-      migrations
+    elif [ "$1" == "apply_custom_pg_migrations" ]; then
+      apply_custom_pg_migrations

     elif [ "$1" == "is_pgready" ]; then
       waitPostgresReady


fixes null set bug

fixes null set bug

--- app/assets/javascripts/angular/directives/GradeSchemeDirectives.js.coffee
+++ app/assets/javascripts/angular/directives/GradeSchemeDirectives.js.coffee
@@ -6,7 +6,7 @@
   }
   controller: ($scope) ->
     this.low_range = (modelValue, viewValue) ->
-      if (modelValue < $scope.element.high_range)
+      if (modelValue < $scope.element.high_range || $scope.element.high_range == '')
         GradeSchemeElementsService.update_scheme($scope.index, modelValue)
         true
       else


add agenda calendar view

add agenda calendar view

--- app/assets/javascripts/angular_files/components/calendar.directive.coffee
+++ app/assets/javascripts/angular_files/components/calendar.directive.coffee
@@ -30,7 +30,7 @@
           header:
             left: 'today prev,next',
             center: 'title',
-            right: 'agendaDay,agendaWeek,month'
+            right: 'agendaDay,agendaWeek,month,basicDay'
           #columnFormat: 'ddd'
           allDaySlot: true
           defaultView: 'agendaWeek'
@@ -81,5 +81,7 @@
           events: (start, end, timezone, callback) ->
             callback scope.needs

+        # some customizations
+        $('.fc-basicDay-button').text('Agenda')
         scope.fetch()
   ])


rm jlabel

rm jlabel

--- app/assets/javascripts/backbone/views/project_flow/project_name.js.coffee
+++ app/assets/javascripts/backbone/views/project_flow/project_name.js.coffee
@@ -77,8 +77,6 @@
 	render: ->
 		$(@el).html(@template( @model.toJSON() ))
 		@addTooltips()
-		wait 0, =>
-			@addJLabel('#project-name')
 		@

 	addTooltips: ->
@@ -90,13 +88,6 @@
 			placement: 'right'
 			title: ""
 		@randomizeIconTooltip()
-
-	addJLabel: (element) ->
-		$(@el).find(element).jLabel({color: "#acacac", opacity: 0.8, yShift: '0'})
-		wait 800, =>
-			$(@el).find(element).css({'position':'absolute'})
-		$(@el).find(element).click ->
-			$(@).parent().find('input').focus()

 	hideTooltips: ->
 		@$('input').tooltip('hide')


[refactor] Extracted `computeRegression` from `renderBurndownChart` (8m)

--- app/assets/javascripts/houston-scheduler/views/show_milestone_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/show_milestone_view.coffee
@@ -54,6 +54,106 @@
         effort: remainingEffort
       sprint = @nextSprint(sprint)

+    reg = @computeRegression(data)
+
+    # Widen the graph so that it includes the X intercept
+    lastSprint = @getEndOfSprint(reg.x2)
+    sprints = (d.sprint for d in data).sort()
+    sprint = _.last(sprints)
+    while sprint < lastSprint
+      sprint = @nextSprint(sprint)
+      sprints.push(sprint)
+
+    margin = {top: 40, right: 80, bottom: 24, left: 50}
+    width = 960 - margin.left - margin.right
+    height = 320 - margin.top - margin.bottom
+    formatDate = d3.time.format('%b %e')
+
+    x = d3.scale.ordinal().rangePoints([0, width], 0.75).domain(sprints)
+    y = d3.scale.linear().range([height, 0]).domain([0, totalEffort])
+    rx = d3.scale.linear()
+      .range([x(firstSprint), x(lastSprint)])
+      .domain([firstSprint, lastSprint])
+
+    xAxis = d3.svg.axis()
+      .scale(x)
+      .orient('bottom')
+      .tickFormat((d)-> formatDate(new Date(d)))
+
+    yAxis = d3.svg.axis()
+      .scale(y)
+      .orient('left')
+
+    line = d3.svg.line()
+      .interpolate('cardinal')
+      .x((d)-> x(d.sprint))
+      .y((d)-> y(d.effort))
+
+    svg = d3.select('#graph').append('svg')
+        .attr('width', width + margin.left + margin.right)
+        .attr('height', height + margin.top + margin.bottom)
+      .append('g')
+        .attr('transform', "translate(#{margin.left},#{margin.top})")
+
+    svg.append('g')
+      .attr('class', 'x axis')
+      .attr('transform', "translate(0,#{height})")
+      .call(xAxis)
+
+    svg.append('g')
+        .attr('class', 'y axis')
+        .call(yAxis)
+      .append('text')
+        .attr('transform', 'rotate(-90)')
+        .attr('y', 6)
+        .attr('dy', '.71em')
+        .attr('class', 'legend')
+        .style('text-anchor', 'end')
+        .text('Points Remaining')
+
+    svg.append('line')
+      .attr('class', 'regression')
+      .attr('x1', rx(reg.x1))
+      .attr('y1', y(reg.y1))
+      .attr('x2', rx(reg.x2))
+      .attr('y2', y(reg.y2))
+
+    svg.append('path')
+      .attr('class', 'line')
+      .attr('d', line(data))
+
+    svg.selectAll('circle')
+      .data(data)
+      .enter()
+      .append('circle')
+        .attr('r', 5)
+        .attr('cx', (d)-> x(d.sprint))
+        .attr('cy', (d)-> y(d.effort))
+
+    svg.selectAll('.label')
+      .data(data)
+      .enter()
+      .append('text')
+        .text((d) -> d.effort)
+        .attr('class', 'label')
+        .attr('transform', (d)-> "translate(#{x(d.sprint) + 4.5}, #{y(d.effort) - 11}) rotate(-90)")
+
+  prevSprint: (timestamp)->
+    1.week().before(new Date(timestamp)).getTime()
+
+  nextSprint: (timestamp)->
+    1.week().after(new Date(timestamp)).getTime()
+
+  getEndOfSprint: (timestamp)->
+    +@getNextFriday(new Date(timestamp))
+
+  getNextFriday: (date)->
+    wday = date.getDay() # 0-6 (0=Sunday)
+    daysUntilFriday = 5 - wday # 5=Friday
+    daysUntilFriday += 7 if daysUntilFriday < 0
+    daysUntilFriday.days().after(date)
+
+  computeRegression: (data)->
     # Compute the linear regression of the points
     # http://trentrichardson.com/2010/04/06/compute-linear-regressions-in-javascript/
     # http://dracoblue.net/dev/linear-least-squares-in-javascript/159/
@@ -71,107 +171,10 @@
     [x0, y0] = [((0 - b) / m), 0]

     # Calculate the regression line
-    lastSprint = @getEndOfSprint(x0)
-    rx1 = firstSprint
-    rx2 = x0
-    ry1 = b + m * rx1
-    ry2 = y0
-
-    # Widen the graph so that it includes the X intercept
-    sprints = (d.sprint for d in data).sort()
-    sprint = _.last(sprints)
-    while sprint < lastSprint
-      sprint = @nextSprint(sprint)
-      sprints.push(sprint)
-
-    margin = {top: 40, right: 80, bottom: 24, left: 50}
-    width = 960 - margin.left - margin.right
-    height = 320 - margin.top - margin.bottom
-    formatDate = d3.time.format('%b %e')
-
-    x = d3.scale.ordinal().rangePoints([0, width], 0.75).domain(sprints)
-    y = d3.scale.linear().range([height, 0]).domain([0, totalEffort])
-    rx = d3.scale.linear()
-      .range([x(firstSprint), x(lastSprint)])
-      .domain([firstSprint, lastSprint])
-
-    xAxis = d3.svg.axis()
-      .scale(x)
-      .orient('bottom')
-      .tickFormat((d)-> formatDate(new Date(d)))
-
-    yAxis = d3.svg.axis()
-      .scale(y)
-      .orient('left')
-
-    line = d3.svg.line()
-      .interpolate('cardinal')
-      .x((d)-> x(d.sprint))
-      .y((d)-> y(d.effort))
-
-    svg = d3.select('#graph').append('svg')
-        .attr('width', width + margin.left + margin.right)
-        .attr('height', height + margin.top + margin.bottom)
-      .append('g')
-        .attr('transform', "translate(#{margin.left},#{margin.top})")
-
-    svg.append('g')
-      .attr('class', 'x axis')
-      .attr('transform', "translate(0,#{height})")
-      .call(xAxis)
-
-    svg.append('g')
-        .attr('class', 'y axis')
-        .call(yAxis)
-      .append('text')
-        .attr('transform', 'rotate(-90)')
-        .attr('y', 6)
-        .attr('dy', '.71em')
-        .attr('class', 'legend')
-        .style('text-anchor', 'end')
-        .text('Points Remaining')
-
-    svg.append('line')
-      .attr('class', 'regression')
-      .attr('x1', rx(rx1))
-      .attr('y1', y(ry1))
-      .attr('x2', rx(rx2))
-      .attr('y2', y(ry2))
-
-    svg.append('path')
-      .attr('class', 'line')
-      .attr('d', line(data))
-
-    svg.selectAll('circle')
-      .data(data)
-      .enter()
-      .append('circle')
-        .attr('r', 5)
-        .attr('cx', (d)-> x(d.sprint))
-        .attr('cy', (d)-> y(d.effort))
-
-    svg.selectAll('.label')
-      .data(data)
-      .enter()
-      .append('text')
-        .text((d) -> d.effort)
-        .attr('class', 'label')
-        .attr('transform', (d)-> "translate(#{x(d.sprint) + 4.5}, #{y(d.effort) - 11}) rotate(-90)")
-
-  prevSprint: (timestamp)->
-    1.week().before(new Date(timestamp)).getTime()
-
-  nextSprint: (timestamp)->
-    1.week().after(new Date(timestamp)).getTime()
-
-  getEndOfSprint: (timestamp)->
-    +@getNextFriday(new Date(timestamp))
-
-  getNextFriday: (date)->
-    wday = date.getDay() # 0-6 (0=Sunday)
-    daysUntilFriday = 5 - wday # 5=Friday
-    daysUntilFriday += 7 if daysUntilFriday < 0
-    daysUntilFriday.days().after(date)
+    x1: data[0].sprint
+    x2: x0
+    y1: b + m * data[0].sprint
+    y2: y0


fix #463, file attachments breaking on models with text fields

--- app/assets/javascripts/locomotive/views/shared/fields/file_view.js.coffee
+++ app/assets/javascripts/locomotive/views/shared/fields/file_view.js.coffee
@@ -29,10 +29,10 @@

     # only in HTML 5
     @$('input[type=file]').bind 'change', (event) =>
-      input = $(event.target)[0]
+      input = event.target

       if input.files?
-        name  = $(input).attr('name')
+        name  = input.name
         hash  = {}
         hash[name.replace("#{@model.paramRoot}[", '').replace(/]$/, '')] = input.files[0]
         @model.set(hash)


Create fake activities

Create fake activities

--- server/lib/fake_it.coffee
+++ server/lib/fake_it.coffee
@@ -1,4 +1,5 @@
 assert    = require("assert")
+Async     = require("async")
 BTree     = require("./names/b_tree")
 name      = require("./names")
 Activity  = require("../models/activity")
@@ -24,8 +25,16 @@
 hourly = hourly_dist.done()

+# Only run so many concurrentl
+queue = Async.queue((activity, done)->
+  Activity.create activity, done
+, 5)
+
+
 # Delete and re-create index
+console.log "Deleting index ..."
 search.teardown ->
+  console.log "Populating ElasticSearch with #{COUNT} activities ..."

   for i in [0...COUNT]
     # Random published day within the past DAYS, hour based on the distribution.
@@ -40,4 +49,7 @@
     verb = VERBS[Math.floor(Math.random() * VERBS.length)]
     assert actor && verb

-    Activity.create actor: { displayName: actor }, verb: verb, published: published
+    queue.push actor: { displayName: actor }, verb: verb, published: published
+
+process.on "exit", ->
+  console.log "Done"


Added openshift db

Added openshift db

--- server/mongo/mongo.coffee
+++ server/mongo/mongo.coffee
@@ -4,7 +4,7 @@
 mongoose.Promise = global.Promise;

 ( () ->
-    await mongoose.connect 'mongodb://localhost/algoprog'
+    await mongoose.connect(process.env.OPENSHIFT_MONGODB_DB_URL || 'mongodb://localhost/algoprog')
 )().catch((error) ->
     logger.error error
     process.exit(1)


Code style.

Code style.

--- server/publication.coffee
+++ server/publication.coffee
@@ -1,7 +1,6 @@
 class @Publication extends @Publication
   checkCache: =>
-    if @cached
-      return
+    return if @cached

     if not Storage.exists @filename()
       console.log "Caching PDF for #{ @_id } from the central server"
@@ -73,8 +72,7 @@
     Publication.PUBLIC_FIELDS()

 Meteor.publish 'publications-by-id', (id) ->
-  if not id
-    return
+  return if not id

   Publications.find
     _id: id
@@ -84,11 +82,11 @@
     Publication.PUBLIC_FIELDS()

 Meteor.publish 'publications-by-ids', (ids) ->
-  if not ids
-    return
+  return if not ids

   Publications.find
-    _id: {$in: ids}
+    _id:
+      $in: ids
     cached: true
     processed: true
   ,


Added warning for missing companion form

--- src/formbuilder.coffee
+++ src/formbuilder.coffee
@@ -57,6 +57,13 @@
       creativeForm = @generateForm urls
       @emit 'progress', 'Saving creative form...'
       creativeFormId = @config.getContext().creativeFormId
+      companionFormId = @config.getContext().companionFormId
+
+      if companionFormId is 0
+        @emit 'progress', "***"
+        @emit 'progress', "Warning: Companion form not found."
+        @emit 'progress', "Please create a companion form to associate with this creative form."
+        @emit 'progress', "***"

       if creativeFormId is 0
         @emit 'progress', "Form #{creativeFormId} does not exist."


PrivateMessage: Add listPreviousReplies method

--- client/Social/Activity/views/privatemessage/privatemessagepane.coffee
+++ client/Social/Activity/views/privatemessage/privatemessagepane.coffee
@@ -105,6 +105,23 @@

   hasSameOwner = (a, b) -> a.getData().account._id is b.getData().account._id

+  listPreviousReplies: (event) ->
+
+    @listController.showLazyLoader()
+
+    {appManager} = KD.singletons
+    first         = @listController.getItemsOrdered().first
+    return  unless first
+
+    from         = first.getData().meta.createdAt.toISOString()
+
+    @fetch {from, limit: 10}, (err, items = []) =>
+      @listController.hideLazyLoader()
+
+      return KD.showError err  if err
+
+      items.forEach @lazyBound 'loadMessage'
+
   privateMessageAdded: (item, index) ->
     @scrollDown()
     {data} = item
@@ -136,4 +153,12 @@
       then nextSibling.setClass 'consequent'
       else nextSibling.unsetClass 'consequent'
     else if nextSibling
-      nextSibling.unsetClass 'consequent'+      nextSibling.unsetClass 'consequent'
+
+  fetch: (options = {}, callback) ->
+    options.limit or= 3
+    super options, (err, data) =>
+      channel = @getData()
+      channel.replies = data
+      @listPreviousLink.updateView data
+      callback err, data


Workspace: Fixed removing file content in client.

--- client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
@@ -29,11 +29,11 @@
         return @save()  if snapshot.val().WaitingSaveRequest is yes

   openFile: (file, content) ->
-    @setData    file
-    @setContent content
-
-  setContent: (content) ->
-    @firepad.setText content  if content
+    @setData file
+    amIHost     = @panel.amIHost @sessionKey
+    isLocalFile = file.path.indexOf("localfile") is 0
+    content     = "" if amIHost and isLocalFile
+    @firepad.setText content  if amIHost

   save: ->
     file        = @getData()


Keep icons position same when they destroyed and re-created

--- client/landing/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/landing/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -204,10 +204,16 @@

   createAppIcon:(app, appData, bulk=no)->

-    @appIcons[app]?.destroy()
-    @appItemContainer.addSubView @appIcons[app] = new StartTabAppThumbView
-      delegate : @
+    oldIcon = @appIcons[app]
+    @appItemContainer.addSubView newIcon = new StartTabAppThumbView
+      delegate : this
     , appData
+
+    if oldIcon
+      newIcon.$().insertAfter oldIcon.$()
+      oldIcon.destroy()
+
+    @appIcons[app] = newIcon

     # To make sure its always the last icon
     @createGetMoreAppsButton()  unless bulk


Move the WirePattern inteface to the top for better docs

--- src/lib/Helpers.coffee
+++ src/lib/Helpers.coffee
@@ -32,213 +32,6 @@
     outProxy = getOutputProxy [config.outPort], output
     func data, groups, outProxy
     output.done()
-
-# Takes WirePattern configuration of a component and sets up
-# Process API to handle it.
-processApiWrapper = (component, config, func) ->
-  # Make param ports control ports
-  setupControlPorts component, config
-  # Set up sendDefaults function
-  setupSendDefaults component
-  # Set up bracket forwarding rules
-  setupBracketForwarding component, config
-  # Create the processing function
-  component.process (input, output, context) ->
-    # Abort unless WirePattern-style preconditions don't match
-    return unless checkWirePatternPreconditions component, config, input, output
-    # Populate component.params from control ports
-    component.params = populateParams config, input
-    # Read input data
-    data = getInputData config, input
-    # Read bracket context of first inport
-    groups = getGroupContext config.inPorts[0], input
-    # Produce proxy object wrapping output in legacy-style port API
-    outProxy = getOutputProxy config.outPorts, output
-
-    debug "WirePattern Process API call with", data, groups, component.params, context.scope
-
-    unless config.async
-      # Set up custom error handlers
-      errorHandler = setupErrorHandler component, config, output, ->
-        return output.done()
-      # Synchronous WirePattern, call done here
-      func.call component, data, groups, outProxy
-      do errorHandler
-      output.done()
-      return
-
-    # Async WirePattern will call the output.done callback itself
-    errorHandler = setupErrorHandler component, config, output, ->
-      output.done()
-    func.call component, data, groups, outProxy, (err) ->
-      do errorHandler
-      output.done err
-
-# Provide deprecation warnings on certain more esoteric WirePattern features
-checkDeprecation = (config, func) ->
-  needsFallback = false
-  # First check the conditions that force us to fall back on legacy WirePattern
-  if config.group
-    platform.deprecated 'noflo.helpers.WirePattern group option is deprecated. Please port to Process API'
-    needsFallback = true
-  if config.field
-    platform.deprecated 'noflo.helpers.WirePattern field option is deprecated. Please port to Process API'
-    needsFallback = true
-  if func.length > 4
-    platform.deprecated 'noflo.helpers.WirePattern postpone and resume are deprecated. Please port to Process API'
-    needsFallback = true
-  # Then add deprecation warnings for other unwanted behaviors
-  unless config.async
-    platform.deprecated 'noflo.helpers.WirePattern synchronous is deprecated. Please port to Process API'
-  return needsFallback
-
-# Updates component port definitions to control prots for WirePattern
-# -style params array
-setupControlPorts = (component, config) ->
-  for param in config.params
-    component.inPorts[param].options.control = true
-
-# Sets up Process API bracket forwarding rules for WirePattern configuration
-setupBracketForwarding = (component, config) ->
-  # Start with empty bracket forwarding config
-  component.forwardBrackets = {}
-  return unless config.forwardGroups
-  # By default we forward from all inports
-  inPorts = config.inPorts
-  if isArray config.forwardGroups
-    # Selective forwarding enabled
-    inPorts = config.forwardGroups
-  for inPort in inPorts
-    component.forwardBrackets[inPort] = []
-    # Forward to all declared outports
-    for outPort in config.outPorts
-      component.forwardBrackets[inPort].push outPort
-    # If component has an error outport, forward there too
-    if component.outPorts.error
-      component.forwardBrackets[inPort].push 'error'
-  return
-
-setupErrorHandler = (component, config, output, failCallback) ->
-  errors = []
-  errorHandler = (e, groups = []) ->
-    platform.deprecated 'noflo.helpers.WirePattern error method is deprecated. Please send error to callback instead'
-    errors.push
-      err: e
-      groups: groups
-    component.hasErrors = true
-  failHandler = (e = null, groups = []) ->
-    platform.deprecated 'noflo.helpers.WirePattern fail method is deprecated. Please send error to callback instead'
-    errorHandler e, groups if e
-    sendErrors()
-    failCallback()
-
-  sendErrors  = ->
-    return unless errors.length
-    output.sendIP 'error', new IP 'openBracket', config.name if config.name
-    errors.forEach (e) ->
-      output.sendIP 'error', new IP 'openBracket', grp for grp in e.groups
-      output.sendIP 'error', new IP 'data', e.err
-      output.sendIP 'error', new IP 'closeBracket', grp for grp in e.groups
-    output.sendIP 'error', new IP 'closeBracket', config.name if config.name
-    component.hasErrors = false
-    errors = []
-
-  component.hasErrors = false
-  component.error = errorHandler
-  component.fail = failHandler
-
-  sendErrors
-
-setupSendDefaults = (component) ->
-  portsWithDefaults = Object.keys(component.inPorts.ports).filter (p) ->
-    return false unless component.inPorts[p].options.control
-    return false unless component.inPorts[p].hasDefault()
-    true
-  component.sendDefaults = ->
-    platform.deprecated 'noflo.helpers.WirePattern sendDefaults method is deprecated. Please start with a Network'
-    portsWithDefaults.forEach (port) ->
-      tempSocket = InternalSocket.createSocket()
-      component.inPorts[port].attach tempSocket
-      tempSocket.send()
-      tempSocket.disconnect()
-      component.inPorts[port].detach tempSocket
-
-
-populateParams = (config, input) ->
-  return unless config.params.length
-  params = {}
-  for paramPort in config.params
-    params[paramPort] = input.getData paramPort
-  return params
-
-getInputData = (config, input) ->
-  data = {}
-  for port in config.inPorts
-    data[port] = input.getData port
-  if config.inPorts.length is 1
-    return data[config.inPorts[0]]
-  return data
-
-getGroupContext = (port, input) ->
-  return [] unless input.result.__bracketContext?[port]?
-  input.result.__bracketContext[port].filter((c) ->
-    c.source is port
-  ).map (c) -> c.ip.data
-
-getOutputProxy = (ports, output) ->
-  outProxy = {}
-  ports.forEach (port) ->
-    outProxy[port] =
-      connect: ->
-      beginGroup: (group) ->
-        output.sendIP port, new IP 'openBracket', group
-      send: (data) ->
-        output.sendIP port, new IP 'data', data
-      endGroup: (group) ->
-        output.sendIP port, new IP 'closeBracket', group
-      disconnect: ->
-  if ports.length is 1
-    return outProxy[ports[0]]
-  return outProxy
-
-checkWirePatternPreconditions = (component, config, input, output) ->
-  # First check for required params
-  paramsMet = true
-  inputsMet = true
-  droppedPackets = false
-  for param in config.params
-    continue unless component.inPorts[param].isRequired()
-    paramsMet = false unless input.hasData param
-  # Then check actual input ports
-  for port in config.inPorts
-    unless input.hasData port
-      inputsMet = false
-      continue
-    # If input port has data but param requirements are not
-    # met, and we're in dropInput mode, read the data and
-    # call done
-    if config.dropInput and not paramsMet
-      input.getData port
-      droppedPackets = true
-
-  output.done() if droppedPackets and not paramsMet
-  return inputsMet and paramsMet
-
-# Wraps OutPort in WirePattern to add transparent scope support
-class OutPortWrapper
-  constructor: (@port, @scope) ->
-  connect: (socketId = null) ->
-    @port.openBracket null, scope: @scope, socketId
-  beginGroup: (group, socketId = null) ->
-    @port.openBracket group, scope: @scope, socketId
-  send: (data, socketId = null) ->
-    @port.sendIP 'data', data, scope: @scope, socketId, false
-  endGroup: (group, socketId = null) ->
-    @port.closeBracket group, scope: @scope, socketId
-  disconnect: (socketId = null) ->
-    @endGroup socketId
-  isConnected: -> @port.isConnected()
-  isAttached: -> @port.isAttached()

 # WirePattern makes your component collect data from several inports
 # and activates a handler `proc` only when a tuple from all of these
@@ -339,6 +132,214 @@
   setup = if checkDeprecation config, proc then legacyWirePattern else processApiWrapper
   return setup component, config, proc

+# Takes WirePattern configuration of a component and sets up
+# Process API to handle it.
+processApiWrapper = (component, config, func) ->
+  # Make param ports control ports
+  setupControlPorts component, config
+  # Set up sendDefaults function
+  setupSendDefaults component
+  # Set up bracket forwarding rules
+  setupBracketForwarding component, config
+  # Create the processing function
+  component.process (input, output, context) ->
+    # Abort unless WirePattern-style preconditions don't match
+    return unless checkWirePatternPreconditions component, config, input, output
+    # Populate component.params from control ports
+    component.params = populateParams config, input
+    # Read input data
+    data = getInputData config, input
+    # Read bracket context of first inport
+    groups = getGroupContext config.inPorts[0], input
+    # Produce proxy object wrapping output in legacy-style port API
+    outProxy = getOutputProxy config.outPorts, output
+
+    debug "WirePattern Process API call with", data, groups, component.params, context.scope
+
+    unless config.async
+      # Set up custom error handlers
+      errorHandler = setupErrorHandler component, config, output, ->
+        return output.done()
+      # Synchronous WirePattern, call done here
+      func.call component, data, groups, outProxy
+      do errorHandler
+      output.done()
+      return
+
+    # Async WirePattern will call the output.done callback itself
+    errorHandler = setupErrorHandler component, config, output, ->
+      output.done()
+    func.call component, data, groups, outProxy, (err) ->
+      do errorHandler
+      output.done err
+
+# Provide deprecation warnings on certain more esoteric WirePattern features
+checkDeprecation = (config, func) ->
+  needsFallback = false
+  # First check the conditions that force us to fall back on legacy WirePattern
+  if config.group
+    platform.deprecated 'noflo.helpers.WirePattern group option is deprecated. Please port to Process API'
+    needsFallback = true
+  if config.field
+    platform.deprecated 'noflo.helpers.WirePattern field option is deprecated. Please port to Process API'
+    needsFallback = true
+  if func.length > 4
+    platform.deprecated 'noflo.helpers.WirePattern postpone and resume are deprecated. Please port to Process API'
+    needsFallback = true
+  # Then add deprecation warnings for other unwanted behaviors
+  unless config.async
+    platform.deprecated 'noflo.helpers.WirePattern synchronous is deprecated. Please port to Process API'
+  return needsFallback
+
+# Updates component port definitions to control prots for WirePattern
+# -style params array
+setupControlPorts = (component, config) ->
+  for param in config.params
+    component.inPorts[param].options.control = true
+
+# Sets up Process API bracket forwarding rules for WirePattern configuration
+setupBracketForwarding = (component, config) ->
+  # Start with empty bracket forwarding config
+  component.forwardBrackets = {}
+  return unless config.forwardGroups
+  # By default we forward from all inports
+  inPorts = config.inPorts
+  if isArray config.forwardGroups
+    # Selective forwarding enabled
+    inPorts = config.forwardGroups
+  for inPort in inPorts
+    component.forwardBrackets[inPort] = []
+    # Forward to all declared outports
+    for outPort in config.outPorts
+      component.forwardBrackets[inPort].push outPort
+    # If component has an error outport, forward there too
+    if component.outPorts.error
+      component.forwardBrackets[inPort].push 'error'
+  return
+
+setupErrorHandler = (component, config, output, failCallback) ->
+  errors = []
+  errorHandler = (e, groups = []) ->
+    platform.deprecated 'noflo.helpers.WirePattern error method is deprecated. Please send error to callback instead'
+    errors.push
+      err: e
+      groups: groups
+    component.hasErrors = true
+  failHandler = (e = null, groups = []) ->
+    platform.deprecated 'noflo.helpers.WirePattern fail method is deprecated. Please send error to callback instead'
+    errorHandler e, groups if e
+    sendErrors()
+    failCallback()
+
+  sendErrors  = ->
+    return unless errors.length
+    output.sendIP 'error', new IP 'openBracket', config.name if config.name
+    errors.forEach (e) ->
+      output.sendIP 'error', new IP 'openBracket', grp for grp in e.groups
+      output.sendIP 'error', new IP 'data', e.err
+      output.sendIP 'error', new IP 'closeBracket', grp for grp in e.groups
+    output.sendIP 'error', new IP 'closeBracket', config.name if config.name
+    component.hasErrors = false
+    errors = []
+
+  component.hasErrors = false
+  component.error = errorHandler
+  component.fail = failHandler
+
+  sendErrors
+
+setupSendDefaults = (component) ->
+  portsWithDefaults = Object.keys(component.inPorts.ports).filter (p) ->
+    return false unless component.inPorts[p].options.control
+    return false unless component.inPorts[p].hasDefault()
+    true
+  component.sendDefaults = ->
+    platform.deprecated 'noflo.helpers.WirePattern sendDefaults method is deprecated. Please start with a Network'
+    portsWithDefaults.forEach (port) ->
+      tempSocket = InternalSocket.createSocket()
+      component.inPorts[port].attach tempSocket
+      tempSocket.send()
+      tempSocket.disconnect()
+      component.inPorts[port].detach tempSocket
+
+populateParams = (config, input) ->
+  return unless config.params.length
+  params = {}
+  for paramPort in config.params
+    params[paramPort] = input.getData paramPort
+  return params
+
+getInputData = (config, input) ->
+  data = {}
+  for port in config.inPorts
+    data[port] = input.getData port
+  if config.inPorts.length is 1
+    return data[config.inPorts[0]]
+  return data
+
+getGroupContext = (port, input) ->
+  return [] unless input.result.__bracketContext?[port]?
+  input.result.__bracketContext[port].filter((c) ->
+    c.source is port
+  ).map (c) -> c.ip.data
+
+getOutputProxy = (ports, output) ->
+  outProxy = {}
+  ports.forEach (port) ->
+    outProxy[port] =
+      connect: ->
+      beginGroup: (group) ->
+        output.sendIP port, new IP 'openBracket', group
+      send: (data) ->
+        output.sendIP port, new IP 'data', data
+      endGroup: (group) ->
+        output.sendIP port, new IP 'closeBracket', group
+      disconnect: ->
+  if ports.length is 1
+    return outProxy[ports[0]]
+  return outProxy
+
+checkWirePatternPreconditions = (component, config, input, output) ->
+  # First check for required params
+  paramsMet = true
+  inputsMet = true
+  droppedPackets = false
+  for param in config.params
+    continue unless component.inPorts[param].isRequired()
+    paramsMet = false unless input.hasData param
+  # Then check actual input ports
+  for port in config.inPorts
+    unless input.hasData port
+      inputsMet = false
+      continue
+    # If input port has data but param requirements are not
+    # met, and we're in dropInput mode, read the data and
+    # call done
+    if config.dropInput and not paramsMet
+      input.getData port
+      droppedPackets = true
+
+  output.done() if droppedPackets and not paramsMet
+  return inputsMet and paramsMet
+
+# Wraps OutPort in WirePattern to add transparent scope support
+class OutPortWrapper
+  constructor: (@port, @scope) ->
+  connect: (socketId = null) ->
+    @port.openBracket null, scope: @scope, socketId
+  beginGroup: (group, socketId = null) ->
+    @port.openBracket group, scope: @scope, socketId
+  send: (data, socketId = null) ->
+    @port.sendIP 'data', data, scope: @scope, socketId, false
+  endGroup: (group, socketId = null) ->
+    @port.closeBracket group, scope: @scope, socketId
+  disconnect: (socketId = null) ->
+    @endGroup socketId
+  isConnected: -> @port.isConnected()
+  isAttached: -> @port.isAttached()
+
+# Legacy WirePattern implementation. We fall back to this with
+# some deprecated parameters.
 legacyWirePattern = (component, config, proc) ->
   # Firing policy for addressable ports
   unless 'arrayPolicy' of config


Fix renameNode modern in/outports compatibility

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -171,9 +171,12 @@
     process.id = newId

     # Inform the ports of the node name
-    for name, port of process.component.inPorts
+    # FIXME: direct process.component.inPorts/outPorts access is only for legacy compat
+    inPorts = process.component.inPorts.ports or process.component.inPorts
+    outPorts = process.component.outPorts.ports or process.component.outPorts
+    for name, port of inPorts
       port.node = newId
-    for name, port of process.component.outPorts
+    for name, port of outPorts
       port.node = newId

     @processes[newId] = process


fix doc addressing

fix doc addressing

--- docs/html.coffee
+++ docs/html.coffee
@@ -11,9 +11,9 @@
       String readFileSync resolve __dirname, "dom.coffee"

     SCRIPT {src} for src in [
-      "/lib/domo.js"
-      "/docs/vendor/prettify.js"
-      "/docs/vendor/coffee.js"
+      "lib/domo.js"
+      "docs/vendor/prettify.js"
+      "docs/vendor/coffee.js"
     ]

     SCRIPT "domo.stats = #{json}"


JProvisioner: make sure slug includes delegate.nickname

--- workers/social/lib/social/models/computeproviders/provisioner.coffee
+++ workers/social/lib/social/models/computeproviders/provisioner.coffee
@@ -260,7 +260,9 @@
         fieldsToUpdate.contentSum = contentSum

       fieldsToUpdate.label = label if label?
-      fieldsToUpdate.slug  = slug  if slug?
+      if slug?
+        slug  = "#{delegate.profile.nickname}/#{slug}"
+        fieldsToUpdate.slug = slug

       @update $set : fieldsToUpdate, (err)->


Animation: Deprecate implicit actions, as they're exported as strips.

--- engine/animation.coffee
+++ engine/animation.coffee
@@ -81,8 +81,8 @@
                 full_exclude_list.push thing
         @strips = []
         @objects = for ob in objects when ob not in exclude
-            # TODO: Shall we do this when there are strips too?
-            if ob.animation_strips.length == 0 and ob.actions.length != 0
+            # DEPRECATED
+            if ob.actions?.length
                 ob.animation_strips.push {
                     action: ob.actions[0]
                     frame_start: 0


Add fillingOptions as its own method

Add fillingOptions as its own method

--- client/views/locations/location_edit.coffee
+++ client/views/locations/location_edit.coffee
@@ -1,12 +1,12 @@
-# fillOptions = () ->
-#   $('#selectLocation').
+fillOptions = (name) ->
+  $('#selectLocation').append '<option>' + name + '</option>'

 Template.locationEdit.created = () ->
   Meteor.call 'getOptions', (error, result) ->
     locationsData = JSON.parse result
     locations = locationsData.locations
     for location in locations
-      $('#selectLocation').append '<option>'+location.name+'</option>'
+      fillOptions location.name

 Template.locationEdit.events


[doodle] nodemailer fix

[doodle] nodemailer fix

--- examples/doodle/doodle.coffee
+++ examples/doodle/doodle.coffee
@@ -51,6 +51,7 @@
 appModel.addParticipant()
 ko.applyBindings(appModel)

+FS = require('fs')
 nodemailer = require('nodemailer')

 smtp = nodemailer.createTransport 'SMTP',
@@ -60,6 +61,7 @@
     pass: FS.readFileSync('emailpass.txt', 'utf8')

 $('#send-mail').click () ->
+  $('#send-mail').attr('disabled', 'disabled')
   for p in appModel.participants()
     addr = p.email()
     return if addr == 'none'
@@ -71,3 +73,5 @@
       subject: "Available Meeting Times"
       text: msg
     smtp.sendMail(mail)
+  $('#send-mail').removeAttr('disabled')
+


defaultUrl

defaultUrl

--- models/user.coffee
+++ models/user.coffee
@@ -67,7 +67,7 @@
     b = badge.toJSON()
     b.issued_on = new Date()
     b.badge_id = badge.id
-    b.image = badge.image.original
+    b.image = badge.image.original.defaultUrl
     b.issued_count = undefined
     @badges.push b
     @save (err, user)->
@@ -80,7 +80,7 @@
           # Using dot accessors here so the custom getters are invoked
           name: badge.name
           description: badge.description
-          image: badge.image
+          image: badge.image.original.defaultUrl
           criteria: badge.criteria
           id: badge.id
       }


use combined data

use combined data

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -22,7 +22,7 @@
     projection.scale(s).translate(t)

   $.when($.ajax('data/counties.json'),
-         $.ajax('data/schools.csv'),
+         $.ajax('data/combined.csv'),
          $.ajax('data/recruits.csv'),
          $.ajax('data/places.csv')).then (r1, r2, r3, r4) ->


Update to latest SDK in lots of easy device commands

--- lib/actions/device.coffee
+++ lib/actions/device.coffee
@@ -125,7 +125,7 @@
 			$ resin devices supported
 	'''
 	action: (params, options, done) ->
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		visuals = require('resin-cli-visuals')

 		resin.models.config.getDeviceTypes().then (deviceTypes) ->
@@ -161,7 +161,7 @@
 	]
 	action: (params, options, done) ->
 		Promise = require('bluebird')
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()

 		Promise.join(
 			resin.models.application.get(params.application)
@@ -196,7 +196,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		patterns = require('../utils/patterns')

 		patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
@@ -218,7 +218,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.identify(params.uuid).nodeify(done)

 exports.reboot =
@@ -235,7 +235,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.reboot(params.uuid, options).nodeify(done)

 exports.shutdown =
@@ -252,7 +252,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.shutdown(params.uuid, options).nodeify(done)

 exports.enableDeviceUrl =
@@ -268,7 +268,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.enableDeviceUrl(params.uuid).nodeify(done)

 exports.disableDeviceUrl =
@@ -284,7 +284,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.disableDeviceUrl(params.uuid).nodeify(done)

 exports.getDeviceUrl =
@@ -300,7 +300,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.getDeviceUrl(params.uuid).then (url) ->
 			console.log(url)
 		.nodeify(done)
@@ -318,7 +318,7 @@
 	permission: 'user'
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		resin.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
 			console.log(hasDeviceUrl)
 		.nodeify(done)
@@ -340,7 +340,7 @@
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
 		Promise = require('bluebird')
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		form = require('resin-cli-form')

 		Promise.try ->
@@ -370,14 +370,14 @@
 	options: [ commandOptions.optionalApplication ]
 	action: (params, options, done) ->
 		normalizeUuidProp(params)
-		resin = require('resin-sdk-preconfigured')
+		resin = require('resin-sdk').fromSharedOptions()
 		patterns = require('../utils/patterns')

-		resin.models.device.get(params.uuid).then (device) ->
+		resin.models.device.get(params.uuid, expandForAppName).then (device) ->
 			return options.application or patterns.selectApplication (application) ->
 				return _.every [
 					application.device_type is device.device_type
-					device.application_name isnt application.app_name
+					device.belongs_to__application[0].app_name isnt application.app_name
 				]
 		.tap (application) ->
 			return resin.models.device.move(params.uuid, application)


Add Unicode-snippet to insert horizontal ellipses

--- snippets.cson
+++ snippets.cson
@@ -38,6 +38,10 @@
 	Greek:
 		prefix: "greek"
 		body: "${1:ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ} ${2:αβγδεζηθικλμνξοπρστυφχψω}"
+
+	…:
+		prefix: "hellip"
+		body: "…"

 	§:
 		prefix: "section"