send current line number of cursor in heartbeat

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -48,11 +48,24 @@
             buffer.onDidSave (e) =>
                 file = buffer.file
                 if file? and file
-                    sendHeartbeat(file, true)
+                    lineno = null
+                    if editor.cursors.length > 0
+                        lineno = editor.cursors[0].getCurrentLineBufferRange().end.row + 1
+                    sendHeartbeat(file, lineno, true)
             buffer.onDidChange (e) =>
                 file = buffer.file
                 if file? and file
-                    sendHeartbeat(file)
+                    lineno = null
+                    if editor.cursors.length > 0
+                        lineno = editor.cursors[0].getCurrentLineBufferRange().end.row + 1
+                    sendHeartbeat(file, lineno)
+            editor.onDidChangeCursorPosition (e) =>
+                file = buffer.file
+                if file? and file
+                    lineno = null
+                    if editor.cursors.length > 0
+                        lineno = editor.cursors[0].getCurrentLineBufferRange().end.row + 1
+                    sendHeartbeat(file, lineno)

 isPythonInstalled = (callback) ->
     pythonLocation((result) ->
@@ -182,7 +195,7 @@
     if cleanup
         fs.unlink(file)

-sendHeartbeat = (file, isWrite) ->
+sendHeartbeat = (file, lineno, isWrite) ->
     time = Date.now()
     if isWrite or enoughTimePassed(time) or lastFile isnt file.path
         if not file.path? or file.path is undefined or fileIsIgnored(file.path)
@@ -196,6 +209,9 @@
                 args = [cliLocation(), '--file', file.path, '--key', apikey, '--plugin', 'atom-wakatime/' + VERSION]
                 if isWrite
                     args.push('--write')
+                if lineno?
+                    args.push('--lineno')
+                    args.push(lineno)
                 process.execFile(python, args, (error, stdout, stderr) ->
                     if error?
                         console.warn error


Use the escaped regex as a stopgap solution

--- app/assets/javascripts/backbone/plugins/me_announce.js.coffee
+++ app/assets/javascripts/backbone/plugins/me_announce.js.coffee
@@ -1,10 +1,10 @@
 class Kandan.Plugins.MeAnnounce

-  @regex: /^\/me /
+  @regex: /^/me /

   @init: ()->
     Kandan.Modifiers.register @regex, (message, state)=>
       message.content = message.content.replace @regex, "#{message.user.first_name} "
       return Kandan.Helpers.Activities.build_from_base_template(message)

-# Kandan.Plugins.register "Kandan.Plugins.MeAnnounce"+# Kandan.Plugins.register "Kandan.Plugins.MeAnnounce"


Add Oak, Duo and Bluz platforms

Add Oak, Duo and Bluz platforms

--- lib/profile-manager.coffee
+++ lib/profile-manager.coffee
@@ -108,6 +108,16 @@
 					name: 'P1'
 				10:
 					name: 'Electron'
+				82:
+					name: 'Digistump Oak'
+				88:
+					name: 'Redbear Duo'
+				103:
+					name: 'Bluz'
+				269:
+					name: 'Bluz Gateway'
+				270:
+					name: 'Bluz Beacon'
 			set: ->

 		# Current platform name


Fix indentation

Fix indentation

--- lib/project-manager.coffee
+++ lib/project-manager.coffee
@@ -54,11 +54,11 @@
         @loadCurrentProject()

     atom.config.observe 'project-manager.environmentSpecificProjects',
-    (newValue, obj = {}) =>
-      previous = if obj.previous? then obj.previous else newValue
-      unless newValue is previous
-        @updateFile()
-        @subscribeToProjectsFile()
+      (newValue, obj = {}) =>
+        previous = if obj.previous? then obj.previous else newValue
+        unless newValue is previous
+          @updateFile()
+          @subscribeToProjectsFile()

   file: (update = false) ->
     @filepath = null if update


modified image densities

modified image densities

--- web/src/cs/views/default.coffee
+++ web/src/cs/views/default.coffee
@@ -12,7 +12,7 @@
 xRange = null
 yRange = null
 zRange = null
-zDensity = 50 # defines how much z range we want per image
+zDensity = 150 # defines how much z range we want per image
 numLoadedImages = 0
 overImage = true;
 mouse = {x:0.5, y: 0.5}
@@ -50,7 +50,7 @@

 	sp = new THREE.CSS3DObject(img)
 	sp.scale.setX(-1)
-	range = 1000
+	range = 700
 	xRange = d3.scale.linear().range([-range, range])
 	yRange = d3.scale.linear().range([-range, range])
 	range = zDensity * numLoadedImages


Fixed messages style and copied coffee mode

--- src/js/app/editor.coffee
+++ src/js/app/editor.coffee
@@ -439,7 +439,7 @@
   $("#centerBar").height winSize.h - 20
   # $("#sourceWrap .CodeMirror-scroll").css "max-height", (winSize.h - 175) + "px"
   $("#commandArea").css "height", 17 + "px"
-  $("#sourceArea").css "height", (winSize.h - 175) + "px"
+  $("#sourceArea").css "height", (winSize.h - 135) + "px"
   $("#rightColumn").width winSize.w - $("#leftColumn").width() - 60
   $("#rightColumn").css "max-height", (winSize.h - 25) + "px"
   setMaxPreWidth $("#output pre")
@@ -484,7 +484,7 @@

 sourceArea = ace.edit 'sourceArea'
 sourceArea.setTheme "ace/theme/cobalt"
-sourceArea.getSession().setMode "ace/mode/coffee"
+sourceArea.getSession().setMode "compilers/coffeescript/mode"
 sourceArea.setHighlightActiveLine true
 sourceArea.getSession().setTabSize 2
 sourceArea.setShowPrintMargin false
@@ -499,7 +499,7 @@

 commandArea = ace.edit 'commandArea'
 commandArea.setTheme "ace/theme/cobalt"
-commandArea.getSession().setMode "ace/mode/coffee"
+commandArea.getSession().setMode "compilers/coffeescript/mode"
 commandArea.setHighlightActiveLine false
 commandArea.setShowPrintMargin false
 commandArea.renderer.setShowGutter false


handle error loading pdf

handle error loading pdf

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -58,6 +58,8 @@
 					]
 					# console.log 'resolved q.all, page size is', result
 					$scope.numPages = result.numPages
+				.catch (error) ->
+					$scope.$emit 'pdf:error', 'loading initial document parameters'

 		@setScale = (scale, containerHeight, containerWidth) ->
 			$scope.loaded.then () ->
@@ -359,7 +361,7 @@
 				scope.$on 'pdf:error', (event, error) ->
 					return if error == 'cancelled'
 					# check if too many retries or file is missing
-					if scope.loadCount > 3 || error.match(/^Missing PDF/i)
+					if scope.loadCount > 3 || error.match(/^Missing PDF/i) || error.match(/^loading/i)
 						scope.$emit 'pdf:error:display'
 						return
 					ctrl.load()


only use /^[A-Z1-9]{6}$/ for kassebaum codes

--- collections/patients.coffee
+++ collections/patients.coffee
@@ -17,8 +17,8 @@
         return (JSON.parse(value) for key, value of output)

     generateUniqueShortId = (mrn) ->
-        shortId = Random.id(6) # Generate a Short ID
-        while Patients.findOne(shortId: shortId)
+        shortId = Random.id(6).toUpperCase() # Generate a Short ID
+        while  shortId.match("0") or Patients.findOne(shortId: shortId)
             shortId = Random.id(6) # Generate another Short ID
         shortId


Use document.URL in place of window.location.href for refresh.

--- public/scripts/refresh.coffee
+++ public/scripts/refresh.coffee
@@ -7,7 +7,7 @@
 refresh = ->
   xhr = new XMLHttpRequest()
   xhr.onload = handleRefresh
-  xhr.open("get", window.location.href, true)
+  xhr.open("get", document.URL, true)
   xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
   xhr.send()
   console.log "loading", window.location.href


Remove console.log in routes/users/index.coffee

--- routes/users/index.coffee
+++ routes/users/index.coffee
@@ -4,7 +4,6 @@

   app.get '/users', (req, res)->
     UsersController.getAll (err, users)->
-      console.log err
       res.send users:users

 module.exports = handler


Trigger title change for electron integration.

--- app/assets/javascripts/app/controllers/widget/electron_events.coffee
+++ app/assets/javascripts/app/controllers/widget/electron_events.coffee
@@ -5,6 +5,10 @@
     return if !electron
     remote = electron.remote
     ipc = electron.ipcRenderer
+
+    App.Event.bind('window-title-set', (arg) ->
+      ipc.send('window-title-set', arg)
+    )
     App.Event.bind('online_notification_counter', (e) ->
       setBadge(e)
     )


Skeleton for most recent buys and copying a buy to basket

--- app/assets/javascripts/controllers/buy_latest_list_controller.coffee
+++ app/assets/javascripts/controllers/buy_latest_list_controller.coffee
@@ -2,6 +2,15 @@
   '$scope'
   '$location'
   'BuyService'
-  ($scope, $location, Buy)->
+  'BasketService'
+  ($scope, $location, Buy, Basket)->
     Buy.all(limit: 20).then (buys)-> $scope.buys = buys
+
+    undo = (buy)->
+
+    canBeDeleted = (buy)->
+
+    $scope.setToBasket = Basket.setFromBuy
+    $scope.delete = undo
+    $scope.canBeDeleted = canBeDeleted
 ]


Using assert to check ValidationError argumentName.

--- lib/errors.coffee
+++ lib/errors.coffee
@@ -7,6 +7,7 @@

 class @ValidationError extends Meteor.Error
   constructor: (reason, argumentName) ->
+    assert argumentName
     error = 400
-    details = "Argument: #{ argumentName or '' }" if argumentName
+    details = "Argument: #{ argumentName }"
     super error, reason, details


add one more regtest to the browser suite

--- test/browser/main.iced
+++ test/browser/main.iced
@@ -1,5 +1,6 @@

 mods =
+  userid : require '../files/userid.iced'
   msg_roundtrip : require '../files/msg_roundtrip.iced'
   decrypt_verify_msg : require '../files/decrypt_verify_msg.iced'
   keymanager : require '../files/keymanager.iced'


remove comment

remove comment

--- lib/minlog.coffee
+++ lib/minlog.coffee
@@ -36,13 +36,6 @@
     if @buffer.length > @options.buffLength
       @stream.write( @buffer.join '' )
       @buffer.length = 0
-    #   return
-    #
-    # now = Date.now()
-    # if now - @lastCheckTime > @options.duration
-    #   @stream.write( @buffer.join '' )
-    #   @lastCheckTime = now
-    #   @buffer.length = 0

   info: (str) ->
     @write '[' + new Date + '] ' + 'INFO ' +  str + '\n'


gobgp v1.11 has changed "gobgp -j" format

see: 3235c0618cfc320c0ad751d83d850ec714fa1c49 of gobgp

--- test/gobgp-node.coffee
+++ test/gobgp-node.coffee
@@ -5,22 +5,30 @@
 exec   = require('child_process').execSync

 describe 'gobgp-node', ->
-  global_rib = ->
-    JSON.parse(exec('gobgp -j global rib'))
+  global_rib = (prefix) ->
+    table = JSON.parse(exec('gobgp -j global rib'))
+    if prefix
+      table[prefix]
+    else
+      table

-  flowspec_rib = ->
-    JSON.parse(exec('gobgp -j global rib -a ipv4-flowspec'))
+  flowspec_rib = (prefix) ->
+    table = JSON.parse(exec('gobgp -j global rib -a ipv4-flowspec'))
+    if prefix
+      table[prefix]
+    else
+      table

   communities = (prefix) ->
-    table = global_rib()
-    paths = table.filter (i) -> i['prefix']==prefix
+    paths = global_rib(prefix)
     return if paths.empty?

-    comms = paths[0]['paths'][0]['attrs'].filter (i) -> i['type']==8
+    comms = paths[0]['attrs'].filter (i) -> i['type']==8
     return comms[0]['communities']

   PREFIX = '10.0.0.0/24'
   FLOWSPEC_PREFIX = "match source #{PREFIX} then rate-limit 10000"
+  FLOWSPEC_JSON_PREFIX = "[source:#{PREFIX}]"

   beforeEach ->
     exec "gobgp global rib del #{PREFIX}"
@@ -33,18 +41,16 @@

       gobgp.addPath family: 'ipv4-unicast', PREFIX

-      table = global_rib()
-      expect(table.length).to.equal 1
-      expect(table[0]['prefix']).to.equal PREFIX
+      prefixes = global_rib(PREFIX)
+      expect(prefixes.length).to.equal 1

     it 'originates a route with BGP community string', ->
       expect(global_rib()).to.be.empty

       gobgp.addPath family: 'ipv4-unicast', "#{PREFIX} community no-advertise"

-      table = global_rib()
-      expect(table.length).to.equal 1
-      expect(table[0]['prefix']).to.equal PREFIX
+      prefixes = global_rib(PREFIX)
+      expect(prefixes.length).to.equal 1

       expect(communities(PREFIX)).to.eql [4294967042]

@@ -60,9 +66,8 @@

       gobgp.addPath family: 'ipv4-unicast', path

-      table = global_rib()
-      expect(table.length).to.equal 1
-      expect(table[0]['prefix']).to.equal PREFIX
+      prefixes = global_rib(PREFIX)
+      expect(prefixes.length).to.equal 1

       expect(communities(PREFIX)).to.eql [4294967042]

@@ -92,9 +97,8 @@

       gobgp.addPath family: 'ipv4-flowspec', FLOWSPEC_PREFIX

-      table = flowspec_rib()
-      expect(table.length).to.equal 1
-      expect(table[0].prefix).to.equal "[source:#{PREFIX}]"
+      prefixes = flowspec_rib(FLOWSPEC_JSON_PREFIX)
+      expect(prefixes.length).to.equal 1

     it 'shows the RIB', ->  # TODO: This does actually nothing. Use chai-as-promised
       exec "gobgp global rib -a ipv4-flowspec add #{FLOWSPEC_PREFIX}"
@@ -123,9 +127,8 @@

       gobgp.modPath family: 'ipv4-unicast', PREFIX

-      table = global_rib()
-      expect(table.length).to.equal 1
-      expect(table[0]['prefix']).to.equal PREFIX
+      prefixes = global_rib(PREFIX)
+      expect(prefixes.length).to.equal 1

     it 'withdraws a route', ->
       exec "gobgp global rib add #{PREFIX}"


minor fix

minor fix

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -948,12 +948,12 @@
       method = 'scanInBufferRange'

     points = []
-    indexWantAcess = @getCount(-1)
+    indexWantAccess = @getCount(-1)
     @editor[method] @getRegex(@input), scanRange, ({range, stop}) ->
       points.push(range.start)
-      stop() if points.length > indexWantAcess
-
-    points[@getCount(-1)]?.translate([0, offset])
+      stop() if points.length > indexWantAccess
+
+    points[indexWantAccess]?.translate([0, offset])

   highlightTextInCursorRows: (text, decorationType) ->
     return unless @getConfig("highlightFindChar")


More useful prompt

More useful prompt

--- lib/mozfee.coffee
+++ lib/mozfee.coffee
@@ -62,9 +62,12 @@
             @prompt()

     normalPrompt: ->
-        ['mozfee> ', 8]
+        mode = @opt.mode
+        repl_name = @mozrepl.repl_name
+        ["#{mode}-#{repl_name}> ", 3 + mode.length + repl_name.length]
     continuationPrompt: ->
-        ['......> ', 8]
+        dotLength = 1 + @opt.mode.length + @mozrepl.repl_name.length
+        [('.' for _ in [1..dotLength]).join('') + '> ', dotLength + 2]

     prompt: ->
         switch @mode


newlines... who needs em?

newlines... who needs em?

--- lib/packer.coffee
+++ lib/packer.coffee
@@ -13,7 +13,7 @@
   unix += 'cd $( dirname "$0" )\n'
   unix += './node/node ./' + main+ '\n'

-  winblows = './node/node.exe ./' + main + '\r\n'
+  winblows = './node/node.exe ./' + main

   writeUnix = (call) ->
     fs.writeFile path.join(dirs.temp, 'run'), unix, (err) ->


simplified and shrunk basic functions code

--- lib/stdlib.coffee
+++ lib/stdlib.coffee
@@ -2,30 +2,12 @@
   print: (x, c) ->
     console.log y for y in x
     x
-  at: (x) ->
-    x[1][x[0] - 1]
-  if: (x) ->
-    if x[0]
-      x[1]
-    else
-      x[2]
-  either: (x) ->
-    if x[0]
-      x[1]
-    else
-      x[0]
-  both: (x) ->
-    if x[0]
-      x[1]
-    else
-      x[0]
-  equal: (x) ->
-    if x[0] is x[1]
-      true
-    else
-      false
-  not: (x) ->
-    not x[0]
+  at:     (x) -> x[1][x[0] - 1]
+  if:     (x) -> if x[0] then x[1] else x[2]
+  either: (x) -> if x[0] then x[0] else x[1]
+  both:   (x) -> if x[0] then x[1] else x[0]
+  equal:  (x) -> x[0] is x[1]
+  not:    (x) -> not x[0]

 library =
   lure:   process.argv


remove indentation from fetchPage

remove indentation from fetchPage

--- spec/acceptance.spec.coffee
+++ spec/acceptance.spec.coffee
@@ -9,13 +9,12 @@
 for type, port of servers
   do (type, port) ->

-    fetchPage = (path) ->
-      ->
-        jasmine.asyncSpecWait()
-        @browser = new zombie.Browser()
-        @browser.visit "http://localhost:#{ port }#{ path }", (err, browser) ->
-          throw err if err
-          jasmine.asyncSpecDone()
+    fetchPage = (path) -> ->
+      jasmine.asyncSpecWait()
+      @browser = new zombie.Browser()
+      @browser.visit "http://localhost:#{ port }#{ path }", (err, browser) ->
+        throw err if err
+        jasmine.asyncSpecDone()

     describe "JS assets in #{ type } server", ->


:lipstick:

:lipstick:

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -371,7 +371,6 @@
     describe "single-click", ->
       it "re-positions the cursor to the clicked row / column", ->
         expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
-
         editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 10])
         expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 10)


Commented out for now.

Commented out for now.


fix bug whereby a balance could be put on a non-balanceable move

--- app/assets/javascripts/dances.coffee
+++ app/assets/javascripts/dances.coffee
@@ -126,7 +126,10 @@
     find_buddy_editor($move,".rotations_edit").val(move_parameter($move.val())[1])
     find_buddy_editor($move,".places_edit").toggle   ( move_cares_about_places(    $move.val() ) )
     find_buddy_editor($move,".places_edit").val(move_parameter($move.val())[1])
-    find_buddy_editor($move,".balance_edit").prop("disabled", !move_cares_about_balance($move.val()))
+    be =
+    find_buddy_editor($move,".balance_edit")
+    be.prop("disabled", !move_cares_about_balance($move.val()))
+    be.prop("checked",  be.prop("checked") && move_cares_about_balance($move.val()))

 initialize_figure_editor = ( e, i ) ->


make the Android app banner go away after you close it

test plan:
 - in a console,
   Setting.set 'google-play-app', 'com.instructure.candroid'
   and restart the server
 - view Canvas on an Android device (or use Chrome's mobile device
   emulation (open dev tools and use Cmd+Shift+M and select a
   device)
 - you should see a banner telling you about the Android app
 - close it with the X, click a link to load a new page,
   and the banner should not come back

fixes CNVS-20322

Change-Id: I1904c8168f39e8cf8fc3a2316e1e675a76201b32
Reviewed-on: https://gerrit.instructure.com/53662
Reviewed-by: Benjamin Porter <fdd60f2e3d506934848b3d0fff79c55f544a2d47@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Tested-by: Jenkins

--- app/coffeescripts/smartbanner.coffee
+++ app/coffeescripts/smartbanner.coffee
@@ -15,6 +15,6 @@
         GooglePlayParams: null,
         icon: '/images/android/logo.png',
         button: I18n.t('android_banner_view_button', 'VIEW'),
-        daysHidden: 0,
-        daysReminder: 0,
+        daysHidden: null,
+        daysReminder: null,
         layer: true


Put groups list back into comprehension.

New, better variable names in comprehensions.

--- examples/simple_reverse.coffee
+++ examples/simple_reverse.coffee
@@ -29,12 +29,10 @@
   unreads = slack.getUnreadCount()

   # Get all the channels that bot is a member of
-  channels = ("##{value.name}" for key, value of slack.channels when slack.channels[key].is_member)
+  channels = ("##{channel.name}" for id, channel of slack.channels when channel.is_member)
+
   # Get all groups that are open and not archived
-  groups = (for key, value of slack.groups
-    if slack.groups[key].is_open and not slack.groups[key].is_archived
-      value.name)
-
+  groups = (group.name for id, group of slack.groups when group.is_open and not group.is_archived)

   console.log "Welcome to Slack. You are @#{slack.self.name} of #{slack.team.name}"
   console.log 'You are in: ' + channels.join(', ')


Fixed comment.

Fixed comment.

--- app/assets/javascripts/app/controllers/widget/translation_inline.js.coffee
+++ app/assets/javascripts/app/controllers/widget/translation_inline.js.coffee
@@ -2,7 +2,7 @@
   constructor: ->

     # bind on key down
-    # if ctrl+shift+t is pressed, enable translation_inline and fire ui:rerender
+    # if ctrl+alt+t is pressed, enable translation_inline and fire ui:rerender
     $(document).on('keydown', (e) =>
       if e.altKey && e.ctrlKey && e.keyCode is 84
         if @active


remove userHasVotedFor prop on band model and add users_who_voted_for array field

--- models/band.coffee
+++ models/band.coffee
@@ -15,9 +15,8 @@
     type: Schema.ObjectId
     ref: 'Vote'
   ]
-  userHasVotedFor:
-    type: Boolean
-    default: false
+  users_who_voted_for:
+    type: Array
   vote_count:
     type: Number
     default: 0


delete team contestants on team delete. #124

--- models/team.coffee
+++ models/team.coffee
@@ -121,4 +121,8 @@
       """
     next()

+## delete team members
+TeamSchema.pre 'remove', (next) ->
+  Person.remove role: 'contestant', _id: { $in: @peopleIds }, next
+
 Team = mongoose.model 'Team', TeamSchema


Fix date formatting

Fix date formatting

--- models/user.coffee
+++ models/user.coffee
@@ -6,6 +6,7 @@
 Organization = require './organization'
 configuration = require '../lib/configuration'
 hexDigest = require('../lib/hex_digest')
+moment = require 'moment'
 db = mongoose.createConnection configuration.get('mongodb')

 # This is a duplicate of the Bdge schema. Dirty I know.
@@ -117,7 +118,7 @@
       }

 formatDate = (dateObj)->
-  "#{dateObj.getFullYear()}-#{dateObj.getMonth()+1}-#{dateObj.getDate()}"
+  moment(dateObj).format("YYYY-MM-DD")

 # Method for returning the OBI compliant assertion JSON
 UserSchema.methods.assertion = (slug, callback) ->


Remove test utils

Remove test utils

--- components/test_utils.coffee
+++ components/test_utils.coffee
@@ -1,13 +0,0 @@
-TestUtils = React.addons.TestUtils
-
-module.exports =
-  renderComponent: (Component, props={}, state={}) ->
-    component = TestUtils.renderIntoDocument(<Component {...props}/>)
-    component.setState(state) unless state == {}
-    component
-
-  shallowRenderComponent: (component, props, children...) ->
-    shallowRenderer = TestUtils.createRenderer()
-    shallowRenderer.render(React.createElement(component, props,
-      children.length > 1 ? children : children[0]))
-    shallowRenderer.getRenderOutput()


Rearrange action.coffee

Rearrange action.coffee

--- lib/action.coffee
+++ lib/action.coffee
@@ -29,23 +29,20 @@
       @res.end('Unsupported Media Type');
       return;

-  @meta_lookup('/x')
   actions= @meta._actions
   extLookup= actions[if @req.method is 'HEAD' then 'GET' else @req.method]
   extSilo = extLookup[urlExtension] ? extLookup['*']

   siteindex= 0
   nextSite= ()=>
-    if (root= @site_stack[siteindex++])
-      pathobj= @path
-      pathobj.site_root= root
-      pathobj.full= join(root,@path.relative)
-      pathobj.base= join(root,@path.relative_base)
-
-      traverseActionList= (actionList)=>
-        @next() unless actionList
+    root= @site_stack[siteindex++]
+    unless root
+      @next()
+    else
+      traverseActionList = (actionList)=>
+        nextSite() unless actionList
         i= 0
-        pass= ()=>
+        pass = ()=>
           try
             actor= actionList[i++]
             if (actor)
@@ -56,6 +53,11 @@
             @next(e)
         pass()

+      pathobj= @path
+      pathobj.site_root= root
+      pathobj.full= join(root,@path.relative)
+      pathobj.base= join(root,@path.relative_base)
+
       if extLookup['/']?
         fs.stat @path.full, (err,stats)=>
           if !err && stats.isDirectory() && extLookup['/']?
@@ -64,7 +66,4 @@
             traverseActionList(extSilo)
       else
         traverseActionList(extSilo)
-
-    else
-      @next()
   nextSite()


cs-wizard promise chain revisited, and $scope.$apply is not necessary!

--- src/components/cs-wizard/cs-wizard.js.coffee
+++ src/components/cs-wizard/cs-wizard.js.coffee
@@ -26,15 +26,18 @@

   link = ($scope, element, attrs, controller) ->

+    ValidationError = () ->
+    ValidationError.prototype = Error.prototype;

     #Setting csLog
-    csLog.set($scope, "csWizard", true)
+    csLog.set($scope, "csWizard")

     $scope.loading = true
     $scope.errors = []

     $scope.setOptions = (resource, item, formMode, wizardMaxDepth, parent) ->

+      $scope.log("setOptions")
       panelDescriptor = {
         resource : resource
         item : item
@@ -44,21 +47,14 @@
         directive : directive
       }
       $scope.panelStack = [panelDescriptor]
-      $scope.finish()
+

     $scope.finish = (error)->

-      #console.log("Watchers")
-      #console.log($scope.$$watchers)
-      # Interesting
-      # Without this $scope.$apply the form is loaded really slowly
-      # or not at all.
-      #$scope.$apply( ()->
       $scope.log("finish")
       $scope.loading = false
-      $scope.errors.push(error) if error
+      $scope.errors = [error] if error
       throw new Error(error) if error
-      #)

     if $scope.csWizardOptions

@@ -78,53 +74,60 @@
           directive = override.directive

       $scope.setOptions(resource, item, formMode, wizardMaxDepth, parent, directive)
+      $scope.finish()

     else

       $scope.log("singlePage branch")
       $scope.csWizardOptions = {}
       # TODO Check if all the parameters arrived
-      # { resourceType, id, pageType}
-      resource_type = $scope.resourceType
+      # { resourceType, itemId, pageType}
       formMode = $scope.pageType
       if formMode != 'show' && formMode != 'edit'
-        $scope.finish("'" + formMode + "' is not a valid page type!")
+        $scope.finish("'" + formMode + "' is not a valid page type!")#
+
+      resource_type = $scope.resourceType
       $scope.csWizardOptions['form-mode'] = formMode
       wizardMaxDepth = 5
       csDescriptorService.getPromises()
       .then(()->
-        return ResourceService.get($scope.resourceType)
-      ).catch((error) ->
-        errorMsg = "\"" + $scope.resourceType + "\" is not a registered resource"
-        $scope.finish(errorMsg)
-      ).then((_resource_) ->
-          resource = angular.copy(_resource_)
-          # Getting the resourc data
-          resource.$get($scope.itemId, {include: '*'})
-      ).catch((reason) ->
-          # TODO process the reason
-          errorMsg = "There is no " + resource.descriptor.name + " with the id: " + $scope.itemId
-          $scope.finish(errorMsg)
-      ).then((_item_)->
+        try
+          _resource_ = ResourceService.get($scope.resourceType)
+        catch error
+          errorMsg = "'" + $scope.resourceType + "' is not a registered resource."
+          $scope.finish(error)
+
+        resource = angular.copy(_resource_)
+        # Getting the resource data
+        resource.$get($scope.itemId, {include: '*'})
+        .then((_item_)->
+
           item = _item_
-      ).then( ()->
-        #Setting events
-        wizardCanceled = (() ->
-          csRoute.go("index", {resourceType : resource.descriptor.type})
-        ).bind(resource)
-
-        wizardSubmitted =  (()->
-          switch(formMode)
-            when "create" then csAlertService.success('new_resource_created')
-            when "edit" then csAlertService.success("changes_saved")
-          csRoute.go("index", {resourceType : resource.descriptor.type})
-        ).bind(formMode, resource)
-
-        $scope.csWizardOptions.events = {
-          'wizard-canceled' : wizardCanceled
-          'wizard-submitted' : wizardSubmitted
-        }
-        $scope.setOptions(resource, item, formMode, wizardMaxDepth)
+          #Setting events
+          wizardCanceled = (() ->
+            csRoute.go("index", {resourceType : resource.descriptor.type})
+          ).bind(resource)
+
+          wizardSubmitted =  (()->
+            switch(formMode)
+              when "create" then csAlertService.success('new_resource_created')
+              when "edit" then csAlertService.success("changes_saved")
+            csRoute.go("index", {resourceType : resource.descriptor.type})
+          ).bind(formMode, resource)
+
+          $scope.csWizardOptions.events = {
+            'wizard-canceled' : wizardCanceled
+            'wizard-submitted' : wizardSubmitted
+          }
+          $scope.setOptions(resource, item, formMode, wizardMaxDepth)
+          $scope.finish()
+
+        ).catch((reason) ->
+            # TODO process the reason
+            errorMsg = "There is no " + resource.descriptor.name + " with the id: " + $scope.itemId
+            $scope.finish(errorMsg)
+        )
+
     )

     if $scope.panelNumberCallback
@@ -137,9 +140,12 @@
         $scope.panelStack[0].item = $scope.csWizardOptions['form-item']

     $scope.$watchCollection 'panelStack', (newPanelStack, oldPanelStack) ->
+
       if newPanelStack
         attrs.$set 'numberOfPanels', newPanelStack.length
         $scope.numberOfPanels = newPanelStack.length
+      else
+        $scope.log("NotPanelStack")

     # ===== LIFECYCLE EVENTS ==============================

@@ -242,6 +248,7 @@
     overriden_resource_descriptors = {}

     $scope.resource_descriptor = (panel) ->
+
       if overriden_resource_descriptors[panel.resource.descriptor.type]
         return overriden_resource_descriptors[panel.resource.descriptor.type]
       else


removing start from balancedString.

removing start from balancedString.

--- lexer.coffee
+++ lexer.coffee
@@ -139,7 +139,7 @@
         return 0 unless match = SIMPLESTR.exec @chunk
         @token 'STRING', (string = match[0]).replace MULTILINER, '\\\n'
       when '"'
-        return 0 unless string = @balancedString @chunk, '"', '"'
+        return 0 unless string = @balancedString @chunk, '"'
         if 0 < string.indexOf '#{', 1
           @interpolateString string.slice 1, -1
         else
@@ -387,7 +387,7 @@
   # a series of delimiters, all of which must be nested correctly within the
   # contents of the string. This method allows us to have strings within
   # interpolations within strings, ad infinitum.
-  balancedString: (str, start, end) ->
+  balancedString: (str, end) ->
     stack = [end]
     for i in [1...str.length]
       switch letter = str.charAt i
@@ -428,7 +428,7 @@
         i += 1
         continue
       unless letter is '#' and str.charAt(i+1) is '{' and
-             (expr = @balancedString str.slice(i + 1), '#{', '}')
+             (expr = @balancedString str.slice(i + 1), '}')
         continue
       tokens.push ['NEOSTRING', str.slice(pi, i)] if pi < i
       inner = expr.slice(1, -1)


Fix #2516, #3560: Unicode space handling

It is possible to match only valid JavaScript identifiers with a really long
regex (like coco and CoffeeScriptRedux does), but CoffeeScript uses a much
simpler one, which allows a bit too much.

Quoting jashkenas/coffeescript#1718 #issuecomment-2152464 @jashkenas:

> But it still seems very much across the "worth it" line. You'll get the
> SyntaxError as soon as it hits JS, and performance aside -- even the increase
> in filesize for our browser coffee-script.js lib seems too much, considering
> this is something no one ever does, apart from experimentation.

In short, CoffeeScript treats any non-ASCII character as part of an identifier.
However, unicode spaces should be excluded since having blank characters as part
of a _word_ is very confusing. This commit does so, while still keeping the
regex really simple.

--- lexer.coffee
+++ lexer.coffee
@@ -731,7 +731,8 @@

 # Token matching regexes.
 IDENTIFIER = /// ^
-  ( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
+  (?!\d)
+  ( (?: (?!\s)[$\w\x7f-\uffff] )+ )
   ( [^\n\S]* : (?!:) )?  # Is this a property name?
 ///


Added some random barks because why not

Added some random barks because why not

--- src/scripts/bark.coffee
+++ src/scripts/bark.coffee
@@ -8,13 +8,14 @@

 moment = require 'moment'
 Organization = require('../models/organization').get()
+barkResponses = ['bark', 'bark bark', 'bark bark bark']

 module.exports = (robot) ->

   Logger = require('../helpers/logger')(robot)

   robot.hear /bark/i, (res) ->
-    res.send "bark bark"
+    res.send res.random barkResponses

   robot.respond /tell me a story/i, (res) ->
     res.send 'woof woof woof'


MSM: Also handle reinit event finish to show IDE.

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -160,7 +160,7 @@
         @switchToIDEIfNeeded()

       else if percentage is 100
-        initial = message is 'apply finished'
+        initial = message in [ 'apply finished', 'reinit finished' ]
         @completeCurrentProcess status, initial

       else if task is 'reinit'


Fix selective purge

Fix selective purge

--- modules/admin/index.coffee
+++ modules/admin/index.coffee
@@ -42,7 +42,7 @@
         try
           messages = await msg.channel.fetchMessages { limit }
           if msg.mentions.members.first()?
-            messages = messages.filter (m)-> m.member.id is msg.mentions.members.first()
+            messages = messages.filter (m)-> m.member.id is msg.mentions.members.first().id
           await msg.channel.bulkDelete(messages)
         catch e
           Core.log(e, 1)


Remove proxy import

Remove proxy import

--- lib/config.coffee
+++ lib/config.coffee
@@ -3,7 +3,6 @@
 useStatement = require './services/use-statement.coffee'
 StatusInProgress = require "./services/status-in-progress.coffee"
 StatusErrorAutocomplete = require "./services/status-error-autocomplete.coffee"
-proxy = require './services/php-proxy.coffee'

 module.exports =


Added some better titles and descriptions for unclear options

--- lib/config.coffee
+++ lib/config.coffee
@@ -3,6 +3,7 @@
   format_on_save:
     type: 'boolean'
     default: true
+    description: 'Enabling will allow JSFormat to format all supported files before the file is saved'
   indent_with_tabs:
     type: 'boolean'
     default: false
@@ -15,9 +16,11 @@
   space_in_paren:
     type: 'boolean'
     default: false
+    title: 'Space in parentheses'
   jslint_happy:
     type: 'boolean'
     default: false
+    title: 'JSLint happy'
   brace_style:
     type: 'string'
     default: 'collapse'
@@ -29,14 +32,16 @@
     type: 'boolean'
     default: false
   space_after_anon_function:
-    type: 'boolean',
+    type: 'boolean'
     default: false
+    title: 'Space after anonymous functions'
   space_before_conditional:
     type: 'boolean'
     default: true
   eval_code:
     type: 'boolean'
     default: false
+    title: 'Evaluate code'
   unescape_strings:
     type: 'boolean'
     default: false
@@ -46,3 +51,4 @@
   e4x:
     type: 'boolean'
     default: false
+    title: 'e4x style'


Fix access

Fix access

--- lib/config.coffee
+++ lib/config.coffee
@@ -42,7 +42,6 @@
         cur[p] = value
     # Save to file
     try
-      fs.accessSync configPath, fs.R_OK | fs.W_OK
-      fs.writeJsonSync configPath, config
+      fs.writeFileSync configPath, JSON.stringify(config, null, 2)
     catch e
       warn e


Expressify: use customContext for rateLimiting if provided

--- node_modules_koding/bongo/lib/expressify.coffee
+++ node_modules_koding/bongo/lib/expressify.coffee
@@ -116,7 +116,7 @@

   (req, res, next) ->

-    { sessionToken, userArea, queue } = req.body
+    { sessionToken, userArea, customContext, queue } = req.body

     unless queue?.length
       return res.status(400).end()
@@ -127,6 +127,8 @@
           res.send _payload
       else
         res.send payload
+
+    rateLimitContext = customContext ? sessionToken

     if sessionToken and userArea

@@ -141,6 +143,7 @@

           payload = []
           healthyQueue = yes
+          rateLimitContext = customContext ? client.username

           queue.forEach (_req) ->
             unless _req.callbacks
@@ -162,12 +165,12 @@
             sendResponse payload
             return

-          rateLimit client.username, queue.length, (isRateLimited) ->
+          rateLimit rateLimitContext, queue.length, (isRateLimited) ->
             return res.status(429).send 'Rate limit exceeded'  if isRateLimited
             process req, client, sendResponse

     else
       # use sessionToken as id for rate limiting
-      rateLimit sessionToken, queue.length, (isRateLimited) ->
+      rateLimit rateLimitContext, queue.length, (isRateLimited) ->
         return res.status(429).send 'Rate limit exceeded'  if isRateLimited
         process req, sendResponse


Node modules koding: expressify: send HTTP 429 when rate limit exceeded

--- node_modules_koding/bongo/lib/expressify.coffee
+++ node_modules_koding/bongo/lib/expressify.coffee
@@ -72,13 +72,13 @@
             return res.status(500).send 'An error occcurred'

           rateLimit client.username, (isRateLimited) ->
-            return res.status(403).send 'Rate limit exceeded'  if isRateLimited
+            return res.status(429).send 'Rate limit exceeded'  if isRateLimited
             process req, client, sendResponse

     else
       # use sessionToken as id for rate limiting
       rateLimit sessionToken, (isRateLimited) ->
-        return res.status(403).send 'Rate limit exceeded'  if isRateLimited
+        return res.status(429).send 'Rate limit exceeded'  if isRateLimited
         process req, sendResponse


Wrote code to allow react to determine browser

--- core/app/backbone/views/react_install_extension_button.coffee
+++ core/app/backbone/views/react_install_extension_button.coffee
@@ -1,3 +1,7 @@
+determineBrowser = ->
+  [ 'chrome', 'firefox', 'safari', 'phantom_js', 'unsupported-browser' ]
+  .filter( (browser) -> $('html.'+browser).length
+  )[0]
 window.ReactInstallExtensionButton = React.createClass
   displayName: 'ReactInstallExtensionButton'
   render: ->


URL => Url.

URL => Url.

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -90,7 +90,7 @@

   setupCrashReporter: ->
     crashReporter.setCompanyName 'GitHub'
-    crashReporter.setSubmissionURL 'https://speakeasy.githubapp.com/submit_crash_log'
+    crashReporter.setSubmissionUrl 'https://speakeasy.githubapp.com/submit_crash_log'
     crashReporter.setAutoSubmit true

   listenForArgumentsFromNewProcess: ->


Added suppot for running jQuery with noConflict

--- lib/expose.coffee
+++ lib/expose.coffee
@@ -1,15 +1,20 @@
-if @$
-  @$.fn.runner = (method, options, start) ->
+_$ = @jQuery or @Zepto or @$
+if _$
+  # Expose the runner as jQuery method
+  _$.fn.runner = (method, options, start) ->
     if not method
       method = 'init'

+    # Normalize params
     if typeof method is 'object'
       start = options
       options = method
       method = 'init'

+    # Check if runner is already defined for this element
     id = @data('runner')
     runner = if id then runners[id] else false
+
     switch method
       when 'init' then new Runner(@, options, start)
       when 'info' then return runner.info() if runner
@@ -17,8 +22,11 @@
       when 'lap' then return runner.lap() if runner
       when 'start', 'stop', 'toggle' then return runner[method]() if runner
       when 'version' then return meta.version
-      else $.error('[' + meta.name + '] Method ' +  method + ' does not exist')
+      else _$.error('[' + meta.name + '] Method ' +  method + ' does not exist')
     return @
-  @$.fn.runner.format = formatTime
+
+  # Expose the default format method
+  _$.fn.runner.format = formatTime
 else
+  # Require jQuery
   throw new Error('[' + meta.name + '] jQuery library is required for this plugin to work')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/collision.coffee
+++ test/collision.coffee
@@ -47,7 +47,7 @@
   Collision.collide player, [enemy, enemy2], (p, e) ->
     ok(true)

-test "Ray", ->
+test "Ca", ->

 module()


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -60,7 +60,7 @@

   debugger

-  p1 = Collision.rayRectangle start, bounds
+  p1 = Collision.rayRectangle start, dire bounds

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


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -59,5 +59,5 @@

   p1 = Collision.rayRectangle start, rect.bounds()

-  equals p1.y
+  equals p1.y, 0
 module()


Client: Activity: Updated computeHref method to check channel is exist or not.

--- client/activity/lib/components/publicchannelmessagelink/index.coffee
+++ client/activity/lib/components/publicchannelmessagelink/index.coffee
@@ -17,6 +17,7 @@

     channelId = message.get 'initialChannelId'
     channel = kd.singletons.socialapi.retrieveCachedItemById channelId
+    return  unless channel
     slug = message.get 'slug'

     return "/Channels/#{channel.name.toLowerCase()}/#{slug}"


Use options hash for component arguments instead of function args

--- test/component.coffee
+++ test/component.coffee
@@ -5,8 +5,8 @@
 describe 'Component', ->

   it 'takes arguments', ->
-    caption = component (caption) ->
-      div '.caption', caption
+    caption = component ({text}) ->
+      div '.caption', text

-    template = -> caption "Hello"
-    expect(render template).to.equal '
Hello
'+    template = -> caption text: "Hello"
+    expect(render template).to.equal '
Hello
'


Update atom config

Update atom config

--- .atom/config.cson
+++ .atom/config.cson
@@ -5,7 +5,6 @@
     'softWrapped': true
     'showIndentGuide': true
     'fontSize': 12
-    'invisibles': {}
   'core':
     'disabledPackages': [
       'metrics'
@@ -16,3 +15,4 @@
     'showOnStartup': false
   'atom-beautify':
     '_analyticsUserId': '1d4703af-c0bf-4e61-8894-f8865aaabf2b'
+  'linter': {}


Clean up tree-view context-menu. Fixes #529

--- menus/git-plus.cson
+++ menus/git-plus.cson
@@ -7,14 +7,19 @@
     }
   ]
   '.tree-view.full-menu .file, .tree-view.full-menu .directory': [
-    {
-      label: 'Git add',
-      'command': 'git-plus-context:add'
-    },
-    {
-      label: 'Git difftool',
-      'command': 'git-plus-context:difftool'
-    }
+    { type: 'separator'},
+    'label': 'Git',
+    'submenu': [
+      {
+        label: 'Git add',
+        'command': 'git-plus-context:add'
+      },
+      {
+        label: 'Git difftool',
+        'command': 'git-plus-context:difftool'
+      },
+    ],
+    { type: 'separator'}
   ]


Fixed date selector

Fixed date selector

--- lib/inputs.coffee
+++ lib/inputs.coffee
@@ -103,46 +103,46 @@
   getInitialState: ->
     date = new Date()
     return {
-      date
+      value: @props.value
       currentYear: date.getFullYear()
     }

   onMonthChange: (e) ->
-    month = e.target.value
-    date = @state.date
+    month = parseInt e.target.value
+    date = @state.value
     # Ensure that the day is valid with the selected month.
     # Correct the day if it is invalid.
-    year = @state.date.getFullYear()
-    date.setDate Math.min date.getDate(), daysInMonth month, year
+    year = @state.value.getFullYear()
+    date.setDate Math.min date.getDate(), daysInMonth month + 1, year
     date.setMonth month
     @setDate date

   onDayChange: (e) ->
-    date = @state.date
-    date.setDate e.target.value
+    date = @state.value
+    date.setDate parseInt e.target.value
     @setDate date

   onYearChange: (e) ->
-    date = @state.date
-    date.setFullYear e.target.value
+    date = @state.value
+    date.setFullYear parseInt e.target.value
     @setDate date

   setDate: (date) ->
-    @setState {date}
+    @setState {value: date}
     @props.onChange date

   renderMonthSelector: ->
     months = _.map monthMap, (month) -> option value: month[0], month[1]
     return select
-      value: @state.date.getMonth()
+      value: @state.value.getMonth()
       onChange: @onMonthChange
     , months

   renderDaySelector: ->
-    days = [1..daysInMonth(@state.date.getMonth(), @state.date.getFullYear())]
+    days = [1..daysInMonth(@state.value.getMonth() + 1, @state.value.getFullYear())]
     dayOptions = _.map days, (day) -> option value: day, day
     return select
-      value: @state.date.getDate()
+      value: @state.value.getDate()
       onChange: @onDayChange
     , dayOptions

@@ -150,7 +150,7 @@
     years = _.map [@state.currentYear..1900], (year) ->
       option value: year, year
     return select
-      value: @state.date.getFullYear()
+      value: @state.value.getFullYear()
       onChange: @onYearChange
     , years


:fire: Remove an un-used import

:fire: Remove an un-used import

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -5,8 +5,6 @@
 IndieRegistry = require './indie-registry'
 UIRegistry = require './ui-registry'
 Commands = require './commands'
-
-require './ui/message-element'

 class Linter
   # State is an object by default; never null or undefined


Look at that, so many green lights.

Look at that, so many green lights.

--- test/formatter.coffee
+++ test/formatter.coffee
@@ -3,78 +3,79 @@
 should = require 'should'

 describe 'Handling message formatting', ->
+
   it 'Should do nothing if there are no user links', ->
     foo = @formatter.incoming {text: 'foo'}
     foo.should.equal 'foo'

-#  it 'Should decode entities', ->
-#    foo = @slackbot.removeFormatting 'foo > & < >&<'
-#    foo.should.equal 'foo > & < >&<'
-#
-#  it 'Should change <@U123> links to @name', ->
-#    foo = @slackbot.removeFormatting 'foo <@U123> bar'
-#    foo.should.equal 'foo @name bar'
-#
-#  it 'Should change <@U123|label> links to label', ->
-#    foo = @slackbot.removeFormatting 'foo <@U123|label> bar'
-#    foo.should.equal 'foo label bar'
-#
-#  it 'Should change <#C123> links to #general', ->
-#    foo = @slackbot.removeFormatting 'foo <#C123> bar'
-#    foo.should.equal 'foo #general bar'
-#
-#  it 'Should change <#C123|label> links to label', ->
-#    foo = @slackbot.removeFormatting 'foo <#C123|label> bar'
-#    foo.should.equal 'foo label bar'
-#
-#  it 'Should change <!everyone> links to @everyone', ->
-#    foo = @slackbot.removeFormatting 'foo <!everyone> bar'
-#    foo.should.equal 'foo @everyone bar'
-#
-#  it 'Should change <!channel> links to @channel', ->
-#    foo = @slackbot.removeFormatting 'foo <!channel> bar'
-#    foo.should.equal 'foo @channel bar'
-#
-#  it 'Should change <!group> links to @group', ->
-#    foo = @slackbot.removeFormatting 'foo <!group> bar'
-#    foo.should.equal 'foo @group bar'
-#
-#  it 'Should change <!here> links to @here', ->
-#    foo = @slackbot.removeFormatting 'foo <!here> bar'
-#    foo.should.equal 'foo @here bar'
-#
-#  it 'Should remove formatting around <http> links', ->
-#    foo = @slackbot.removeFormatting 'foo <http://www.example.com> bar'
-#    foo.should.equal 'foo http://www.example.com bar'
-#
-#  it 'Should remove formatting around <https> links', ->
-#    foo = @slackbot.removeFormatting 'foo <https://www.example.com> bar'
-#    foo.should.equal 'foo https://www.example.com bar'
-#
-#  it 'Should remove formatting around <skype> links', ->
-#    foo = @slackbot.removeFormatting 'foo <skype:echo123?call> bar'
-#    foo.should.equal 'foo skype:echo123?call bar'
-#
-#  it 'Should remove formatting around <https> links with a label', ->
-#    foo = @slackbot.removeFormatting 'foo <https://www.example.com|label> bar'
-#    foo.should.equal 'foo label (https://www.example.com) bar'
-#
-#  it 'Should remove formatting around <https> links with a substring label', ->
-#    foo = @slackbot.removeFormatting 'foo <https://www.example.com|example.com> bar'
-#    foo.should.equal 'foo https://www.example.com bar'
-#
-#  it 'Should remove formatting around <https> links with a label containing entities', ->
-#    foo = @slackbot.removeFormatting 'foo <https://www.example.com|label > & <> bar'
-#    foo.should.equal 'foo label > & < (https://www.example.com) bar'
-#
-#  it 'Should remove formatting around <mailto> links', ->
-#    foo = @slackbot.removeFormatting 'foo <mailto:name@example.com> bar'
-#    foo.should.equal 'foo name@example.com bar'
-#
-#  it 'Should remove formatting around <mailto> links with an email label', ->
-#    foo = @slackbot.removeFormatting 'foo <mailto:name@example.com|name@example.com> bar'
-#    foo.should.equal 'foo name@example.com bar'
-#
-#  it 'Should change multiple links at once', ->
-#    foo = @slackbot.removeFormatting 'foo <@U123|label> bar <#C123> <!channel> <https://www.example.com|label>'
-#    foo.should.equal 'foo label bar #general @channel label (https://www.example.com)'+  it 'Should decode entities', ->
+    foo = @formatter.links 'foo > & < >&<'
+    foo.should.equal 'foo > & < >&<'
+
+  it 'Should change <@U123> links to @name', ->
+    foo = @formatter.links 'foo <@U123> bar'
+    foo.should.equal 'foo @name bar'
+
+  it 'Should change <@U123|label> links to label', ->
+    foo = @formatter.links 'foo <@U123|label> bar'
+    foo.should.equal 'foo label bar'
+
+  it 'Should change <#C123> links to #general', ->
+    foo = @formatter.links 'foo <#C123> bar'
+    foo.should.equal 'foo #general bar'
+
+  it 'Should change <#C123|label> links to label', ->
+    foo = @formatter.links 'foo <#C123|label> bar'
+    foo.should.equal 'foo label bar'
+
+  it 'Should change <!everyone> links to @everyone', ->
+    foo = @formatter.links 'foo <!everyone> bar'
+    foo.should.equal 'foo @everyone bar'
+
+  it 'Should change <!channel> links to @channel', ->
+    foo = @formatter.links 'foo <!channel> bar'
+    foo.should.equal 'foo @channel bar'
+
+  it 'Should change <!group> links to @group', ->
+    foo = @formatter.links 'foo <!group> bar'
+    foo.should.equal 'foo @group bar'
+
+  it 'Should change <!here> links to @here', ->
+    foo = @formatter.links 'foo <!here> bar'
+    foo.should.equal 'foo @here bar'
+
+  it 'Should remove formatting around <http> links', ->
+    foo = @formatter.links 'foo <http://www.example.com> bar'
+    foo.should.equal 'foo http://www.example.com bar'
+
+  it 'Should remove formatting around <https> links', ->
+    foo = @formatter.links 'foo <https://www.example.com> bar'
+    foo.should.equal 'foo https://www.example.com bar'
+
+  it 'Should remove formatting around <skype> links', ->
+    foo = @formatter.links 'foo <skype:echo123?call> bar'
+    foo.should.equal 'foo skype:echo123?call bar'
+
+  it 'Should remove formatting around <https> links with a label', ->
+    foo = @formatter.links 'foo <https://www.example.com|label> bar'
+    foo.should.equal 'foo label (https://www.example.com) bar'
+
+  it 'Should remove formatting around <https> links with a substring label', ->
+    foo = @formatter.links 'foo <https://www.example.com|example.com> bar'
+    foo.should.equal 'foo https://www.example.com bar'
+
+  it 'Should remove formatting around <https> links with a label containing entities', ->
+    foo = @formatter.links 'foo <https://www.example.com|label > & <> bar'
+    foo.should.equal 'foo label > & < (https://www.example.com) bar'
+
+  it 'Should remove formatting around <mailto> links', ->
+    foo = @formatter.links 'foo <mailto:name@example.com> bar'
+    foo.should.equal 'foo name@example.com bar'
+
+  it 'Should remove formatting around <mailto> links with an email label', ->
+    foo = @formatter.links 'foo <mailto:name@example.com|name@example.com> bar'
+    foo.should.equal 'foo name@example.com bar'
+
+  it 'Should change multiple links at once', ->
+    foo = @formatter.links 'foo <@U123|label> bar <#C123> <!channel> <https://www.example.com|label>'
+    foo.should.equal 'foo label bar #general @channel label (https://www.example.com)'


Add support for reservation_id

Add support for reservation_id

--- src/queues/ironmq.coffee
+++ src/queues/ironmq.coffee
@@ -9,6 +9,7 @@
 tokencrypto = require '../tokencrypto'

 CONSUME_INTERVAL  = parseInt(process.env.IRONMQ_CONSUME_INTERVAL, 10) or 1000
+RESERVE_TIMEOUT = parseInt(process.env.IRONMQ_RESERVE_TIMEOUT, 10) or 10000
 IRONMQ_LIMIT = parseInt(process.env.IRONMQ_LIMIT, 10) or 65536

 class IronMQQueue
@@ -139,20 +140,23 @@
   emitConsumeTask: (message, queue, ironTask) ->
     try
       taskArgs = JSON.parse message
+      taskArgs.options.reservation_id = ironTask.reservation_id
     catch e
       logger.error 'ironTask is', ironTask
       logger.error "IVY_IRONMQ_ERROR Retrieve tasks that cannot be parsed as JSON, deleting from queue: #{ironTask}", e
-      @getQueue().del ironTask.id, {}, (err, body) =>
-        if err
-          logger.warn "IVY_WARNING Cannot delete task from IronMQ", err
-          @manager.emit 'mqError', err
+      logger.warn "IRONTASK", ironTask
+      if ironTask.id
+        @getQueue().del ironTask.id, { reservation_id: taskArgs.options.reservation_id}, (err, body) =>
+          if err
+            logger.warn "IVY_WARNING Cannot delete task from IronMQ 1", err
+            @manager.emit 'mqError', err

     @manager.emit 'scheduledTaskRetrieved',
-      id:        ironTask.id
-      name:      taskArgs.name
-      args:      taskArgs.args
-      options:   taskArgs.options
-      queue:     queue
+      id:             ironTask.id
+      name:           taskArgs.name
+      args:           taskArgs.args
+      options:        taskArgs.options
+      queue:          queue

   consumeTasks: (queues) ->
     toRetrieve = @options.messageSize or 1
@@ -162,7 +166,7 @@

     queueName = @getQueueName queues?[0]

-    @getQueue(queueName).reserve n: toRetrieve, (err, ironTasks) =>
+    @getQueue(queueName).reserve { n: toRetrieve, timeout: RESERVE_TIMEOUT}, (err, ironTasks) =>
       if err
         logger.warn "IVY_WARNING Cannot retrieve task from IronMQ", err
         @manager.emit 'mqError', err if err
@@ -188,7 +192,7 @@
           @emitConsumeTask message, queueName, ironTask

   taskExecuted: (err, result) ->
-    @getQueue(result.queue).del result.id, {}, (err, body) =>
+    @getQueue(result.queue).del result.id, { reservation_id: result.options.reservation_id }, (err, body) =>
       if err
         logger.warn "IVY_WARNING Cannot delete task #{result.id} from IronMQ", err
         @manager.emit 'mqError', err


changing step to update in the docs

changing step to update in the docs

--- src/engine_save_state.coffee
+++ src/engine_save_state.coffee
@@ -17,7 +17,7 @@
   Save the current game state and returns a JSON object representing that state.

-  engine.bind 'step', ->
+  engine.bind 'update', ->
     if justPressed.s
       engine.saveState()

@@ -34,7 +34,7 @@
   Loads the game state passed in, or the last saved state, if any.

-  engine.bind 'step', ->
+  engine.bind 'update', ->
     if justPressed.l
       # loads the last saved state
       engine.loadState()


PUBDEV-1581 Infer model-builder endpoints from schema version

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -284,30 +284,45 @@
   requestModelBuildersVisibility = (go) ->
     doGet '/3/Configuration/ModelBuilders/visibility', unwrap go, (result) -> result.value

+  __modelBuilders = null
+  __modelBuilderEndpoints = null
+  cacheModelBuilders = (modelBuilders) ->
+    modelBuilderEndpoints = {}
+    for modelBuilder in modelBuilders
+      modelBuilderEndpoints[modelBuilder.algo] = "/#{modelBuilder.__meta.schema_version}/ModelBuilders/#{modelBuilder.algo}"
+    __modelBuilderEndpoints = modelBuilderEndpoints
+    __modelBuilders = modelBuilders
+  getModelBuilders = -> __modelBuilders
+  getModelBuilderEndpoint = (algo) -> __modelBuilderEndpoints[algo]
+
   requestModelBuilders = (go) ->
-    requestModelBuildersVisibility (error, visibility) ->
-      visibility = if error then 'Stable' else visibility
-      doGet "/3/ModelBuilders", unwrap go, (result) ->
-        builders = (builder for algo, builder of result.model_builders)
-        switch visibility
-          when 'Stable'
-            for builder in builders when builder.visibility is visibility
-              builder
-          when 'Beta'
-            for builder in builders when builder.visibility is visibility or builder.visibility is 'Stable'
-              builder
-          else
-            builders
+    if modelBuilders = getModelBuilders()
+      go null, modelBuilders
+    else
+      requestModelBuildersVisibility (error, visibility) ->
+        visibility = if error then 'Stable' else visibility
+        doGet "/3/ModelBuilders", unwrap go, (result) ->
+          builders = (builder for algo, builder of result.model_builders)
+          availableBuilders = switch visibility
+            when 'Stable'
+              for builder in builders when builder.visibility is visibility
+                builder
+            when 'Beta'
+              for builder in builders when builder.visibility is visibility or builder.visibility is 'Stable'
+                builder
+            else
+              builders
+          cacheModelBuilders availableBuilders

   requestModelBuilder = (algo, go) ->
-    doGet "/3/ModelBuilders/#{algo}", go
+    doGet getModelBuilderEndpoint(algo), go

   requestModelInputValidation = (algo, parameters, go) ->
-    doPost "/3/ModelBuilders/#{algo}/parameters", (encodeObjectForPost parameters), go
+    doPost "#{getModelBuilderEndpoint(algo)}/parameters", (encodeObjectForPost parameters), go

   requestModelBuild = (algo, parameters, go) ->
     _.trackEvent 'model', algo
-    doPost "/3/ModelBuilders/#{algo}", (encodeObjectForPost parameters), go
+    doPost getModelBuilderEndpoint(algo), (encodeObjectForPost parameters), go

   requestPredict = (destinationKey, modelKey, frameKey, options, go) ->
     opts = {}


Rename Meter module

Rename Meter module

--- src/game_object_meter.coffee
+++ src/game_object_meter.coffee
@@ -163,7 +163,6 @@

       player = GameObject

-      player.include Metered

       # creates a health meter
       player.meter 'health'


Add hipchat msg for year sub sale

Add hipchat msg for year sub sale

--- server/payments/subscription_handler.coffee
+++ server/payments/subscription_handler.coffee
@@ -7,6 +7,7 @@
 async = require 'async'
 config = require '../../server_config'
 Handler = require '../commons/Handler'
+hipchat = require '../hipchat'
 discountHandler = require './discount_handler'
 Prepaid = require '../prepaids/Prepaid'
 User = require '../users/User'
@@ -159,6 +160,11 @@
             if err
               @logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}")
               return @sendDatabaseError(res, err)
+            try
+              msg = "Year subscription purchased by #{req.user.get('email')} #{req.user.id}"
+              hipchat.sendHipChatMessage msg, ['tower']
+            catch error
+              @logSubscriptionError(req.user, "Year sub sale HipChat tower msg error: #{JSON.stringify(error)}")
             @sendSuccess(res, user)

   subscribeUser: (req, user, done) ->


:art: refactor as preparation for yet another new Motion

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -205,32 +205,37 @@
 class MoveUpToNonBlank extends Motion
   @extend()
   linewise: true
-
-  getNewRow: (rows, column) ->
-    for row in rows
-      (char = @editor.lineTextForBufferRow(row)[column])
-      if char? and /\S/.test(char)
-        return row
-    null
-
-  getScanRows: (cursor) ->
-    startRow = getValidVimRow(@editor, cursor.getBufferRow() - 1)
-    endRow = 0
-    [startRow..endRow]
+  direction: 'up'

   moveCursor: (cursor) ->
     column = cursor.getBufferColumn()
     @countTimes =>
-      scanRows = @getScanRows(cursor)
-      if newRow = @getNewRow(scanRows, column)
+      newRow = _.detect @getScanRows(cursor), (row) => @isMovableColumn(row, column)
+      if newRow?
         cursor.setBufferPosition([newRow, column])

+  getScanRows: (cursor) ->
+    cursorRow = cursor.getBufferRow()
+    [startRow, endRow] = switch @direction
+      when 'up' then [cursorRow - 1, 0]
+      when 'down' then [cursorRow + 1, getVimLastBufferRow(@editor)]
+    [getValidVimRow(@editor, startRow)..endRow]
+
+  isMovableColumn: (row, column) ->
+    @isNonBlankColumn(row, column)
+
+  isBlankColumn: (row, column) ->
+    if (text = @editor.lineTextForBufferRow(row)[column])?
+      /\s/.test(text)
+    else
+      true
+
+  isNonBlankColumn: (row, column) ->
+    not @isBlankColumn(row, column)
+
 class MoveDownToNonBlank extends MoveUpToNonBlank
   @extend()
-  getScanRows: (cursor) ->
-    startRow = getValidVimRow(@editor, cursor.getBufferRow() + 1)
-    endRow = getVimLastBufferRow(@editor)
-    [startRow..endRow]
+  direction: 'down'

 class MoveToPreviousWord extends Motion
   @extend()


fix Docker project startup time (make it 10x faster!)

--- src/smc-hub/proxy.coffee
+++ src/smc-hub/proxy.coffee
@@ -285,7 +285,7 @@
                                         dbg("project.status finished (mark: #{misc.walltime(tm)})")
                                         if err
                                             cb(err)
-                                        else if not status['raw.port']?
+                                        else if not status['raw.port']
                                             cb("raw port not available -- project might not be opened or running")
                                         else
                                             port = status['raw.port']


Don't timestamp strace logs otherwise it runs as anew container each time since the command changes

--- services/clsi/app/coffee/LatexRunner.coffee
+++ services/clsi/app/coffee/LatexRunner.coffee
@@ -28,7 +28,7 @@
 			return callback new Error("unknown compiler: #{compiler}")

 		if Settings.clsi?.strace
-			command = ["strace", "-o", "strace-#{Date.now()}", "-ff"].concat(command)
+			command = ["strace", "-o", "strace", "-ff"].concat(command)

 		CommandRunner.run project_id, command, directory, image, timeout, (error, output) ->
 			return callback(error) if error?


Quote style consistency...

Quote style consistency...

--- lib/object.coffee
+++ lib/object.coffee
@@ -10,7 +10,7 @@
     @convert: (object) ->
         if Array.isArray object
             items = (PDFObject.convert e for e in object).join(' ')
-            "[" + items + "]"
+            '[' + items + ']'

         else if typeof object is 'string'
             '/' + object


[bugfix] Set a default timeout of 5 seconds (instead of infinite).

--- src/support/index.coffee
+++ src/support/index.coffee
@@ -1,5 +1,7 @@
 module.exports = ->
   @logger = require './logger'
+
+  @timeout = 5000

   @visit = (path) =>
     @driver.get "#{@host}#{path}"


Fixed simple typo in UniqueIdentifier definition. RowToken.getValue throws with "Unrecognized Type" otherwise.

--- src/tds-constants.coffee
+++ src/tds-constants.coffee
@@ -1,4 +1,3 @@
-
 ###*
 Class with static TDS info
 ###
@@ -72,7 +71,7 @@
       emptyPossible: true
     0x24:
       name: 'GUIDTYPE'
-      sqlType: 'UniqueIndentifier'
+      sqlType: 'UniqueIdentifier'
     0x25:
       name: 'VARBINARYTYPE'
       sqlType: 'VarBinary'


changed accounting worker path

changed accounting worker path

--- accounting.coffee
+++ accounting.coffee
@@ -1,39 +0,0 @@
-{argv}   = require 'optimist'
-KONFIG = require('koding-config-manager').load("main.#{argv.c}")
-
-amqp = require 'amqp'
-bongo = require './server/lib/server/bongo'
-{JKiteApp} = bongo.models
-
-__start = (config)->
-
-    @connection       = amqp.createConnection config
-    @connection.on 'error', (e)->
-      console.error "An error occured while AMQP connection! #{e.message}"
-    @connection.on 'ready', =>
-      @connection.exchange 'accounting', { type : 'topic', autoDelete : no, durable : yes, exclusive : no }, (accounting) =>
-        @connection.queue '', {exclusive: yes}, (queue)=>
-
-          queue.bind accounting, '#', ''
-          queue.on 'queueBindOk', =>
-            queue.subscribe (message, headers, deliveryInfo)=>
-              try
-                rawMessage = message?.data?.toString() #, headers, deliveryInfo
-                if rawMessage
-                  msg = JSON.parse rawMessage
-                  if msg?.arguments?[0]
-                    info = msg.arguments[0]
-
-                    if info?
-                      JKiteApp.inc { appKey : info.withArgs.key, methodName : info.method }, () =>
-                        console.log 'incremented count\n'
-                      console.log "name    : #{info.kiteName}"
-                      console.log "method  : #{info.method}"
-                      console.log "args    : #{info.withArgs.key}"
-                      deliveryKey = deliveryInfo.routingKey.split '.'
-                      console.log "username: #{deliveryKey[2]}"
-                      console.log "kite    : #{deliveryKey[3]}"
-              catch e
-                console.log e.message
-
-__start KONFIG.mq


Fix textual check

Fix textual check

--- lib/schema.coffee
+++ lib/schema.coffee
@@ -1,20 +1,20 @@
+isSubtypeOf = (type, checkType) ->
+  if typeof type is 'object'
+    type = type.type
+
+  # TODO: Parse from The Grid JSON Schema
+  return true if checkType is 'block'
+  return true if type is checkType
+  if checkType is 'textual'
+    return true if type in ['text', 'code', 'quote']
+    return isSubtypeOf type, 'headline'
+  if checkType is 'media'
+    return type in ['image', 'video', 'audio', 'article', 'location', 'quote']
+  if checkType is 'headline'
+    return type in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
+  if checkType is 'data'
+    return type in ['list', 'table']
+  false

 module.exports =
-
-  isSubtypeOf: (type, checkType) ->
-    if typeof type is 'object'
-      type = type.type
-
-    # TODO: Parse from The Grid JSON Schema
-    return true if checkType is 'block'
-    return true if type is checkType
-    if checkType is 'textual'
-      return true if type in ['text', 'code', 'quote']
-      return @isSubtypeOf type, 'headline'
-    if checkType is 'media'
-      return type in ['image', 'video', 'audio', 'article', 'location', 'quote']
-    if checkType is 'headline'
-      return type in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
-    if checkType is 'data'
-      return type in ['list', 'table']
-    false
+  isSubtypeOf: isSubtypeOf


Removed obvious comment

Removed obvious comment

--- lib/sentry.coffee
+++ lib/sentry.coffee
@@ -18,7 +18,6 @@

 module.exports = class Sentry extends events.EventEmitter

-  # constructor must be provided a credentials string or object
   constructor: (credentials) ->
     @enabled = false
     credentials = parseDSN credentials if _.isString credentials


consistency checks for zmq que

consistency checks for zmq que

--- lib/server.coffee
+++ lib/server.coffee
@@ -121,6 +121,7 @@
 						#console.log output.join(" | "), output.length,".--"
 						#process.stdout.clearLine()
 						output.push "zmq : " + s_wk._zmq.pending
+						output.push "mem : " + (if os.freemem() < config.minMem * 1048576 then "Ok" else "notOk")
 						if canprogress
 							process.stdout.cursorTo(60)
 							process.stdout.write output.join(" | ")
@@ -129,23 +130,39 @@
 							console.log output.join(" | ")
 						jumpon = 0

-					# TODO :. Inconsistency checks for zmq queue
-
-					# if !dirtyqueue and status_c.onqueue? and status_c.onqueue > s_wk._zmq.pending
-					# 	tosend = []
-					# 	for job in workque when job._status is "onqueue"
-					# 		found = false
-					# 		for item in s_wk._outgoing
-					# 			lid = JSON.parse(item[0].toString('utf-8'))._id
-					# 			found = true if job._id is lid
-					# 		tosend.push job._id if !found
-					# 	if tosend.length > 0
-					# 		console.log "Inconsistent ZMQ buffer, fixing:",tosend.length,"que:",status_c.onqueue, "zmw:", s_wk._outgoing.length
-					# 		for job in workque when job._status is "onqueue"
-					# 			if tosend.indexOf(job._id) isnt -1
-					# 				job._status = "tosend"
-					# 				job._lastchange = ~~((new Date).getTime() / 1000)
-
+					# Inconsistency checks for zmq queue
+
+					if !dirtyqueue and status_c.onqueue? and status_c.onqueue isnt s_wk._zmq.pending
+						console.log "Inconsistent Queue", status_c.onqueue, s_wk._zmq.pending
+						zmqids = []
+						for item,ix in s_wk._outgoing
+							lid = JSON.parse(item[0].toString('utf-8'))._id
+							zmqids.push([lid,ix])
+						# On zmq but not on que
+						notonque = []
+						for item in zmqids
+							notonque.push(item[1]) if findinque(item[0])._status isnt "onqueue" # not on que
+						# Duplicates
+						dups = []
+						for item,ix in zmqids
+							for inItem,inIx in zmqids
+								dups.push(item[1]) if ix isnt inIx and item[0] is inItem[1] # duplicates
+						console.log "NotOnQue", notonque.length, "Dups",dups.length
+						todelete = notonque.concat(dups)
+						if todelete.length isnt 0
+							console.log "Removing", todelete.length,"items from zmqQueue", todelete
+							s_wk._outgoing = s_wk._outgoing.filter (obj,ix)->
+								return false if todelete.indexOf(ix) isnt -1
+								return true
+						# on Que but not on zmq
+						for item in workque when item._status = "onqueue"
+							found = false
+							for zItem in zmqids when zItem[0] is item._id
+								found = true
+							if !found
+								console.log "requeueing",item._id
+								item._status = "tosend"
+								item._lastchange = ~~((new Date).getTime() / 1000)
 					setImmediate -> next()
 			,(err)->
 				#it will never stop
@@ -222,7 +239,7 @@
 						else
 							hostJobs[c.node.name].lastseen = ~~((new Date).getTime() / 1000)
 						syncJobs c.node.name, c.jobIds
-						datalog.gauge 'task_processing', Object.keys(hostJobs[c.node.name]).length, [ 'worker:' + c.node.name ]
+						datalog.gauge 'task_processing', c.jobIds.length, [ 'worker:' + c.node.name ] if c.jobIds? and Array.isArray(c.jobIds)
 					if c.id
 						switch c.status
 							when 2 # job taken
@@ -232,9 +249,11 @@
 							when 3 # job finished
 								if c.error
 									# error on the job
-									findinque(c.id)?._status = "errored"
-									findinque(c.id)?._lastchange = ~~((new Date).getTime() / 1000)
-									findinque(c.id)?.error = c.error
+									jobItem = findinque(c.id)
+									if jobItem._status is "working" and jobItem._takenby is c.node.name
+										jobItem._status = "errored"
+										jobItem._lastchange = ~~((new Date).getTime() / 1000)
+										jobItem.error = c.error
 									console.log ':(', (if c.node then c.node.name else '?'), c.id, c.error.message
 									datalog.legauge 'task_error', c.node.name
 									if config.errorlog isnt false #error log


More works

More works

--- src/twitter-spout.coffee
+++ src/twitter-spout.coffee
@@ -19,7 +19,7 @@
     @_stream = @_twitter.stream 'statuses/sample'

     @_stream.on 'tweet', (tweet) =>
-     @emit "event:new", tweet
+      @emit "event:new", tweet

     @_stream.on 'warning', () => console.log "warning"


Started re-onboarding analytics event

Started re-onboarding analytics event

--- apps/personalize/client/index.coffee
+++ apps/personalize/client/index.coffee
@@ -33,6 +33,7 @@
     @listenTo @state, 'done', @done

     track.funnel 'Landed on personalize page', label: "User:#{@user.id}"
+    track.funnel 'Started re-onboarding' if @reonboarding

     _.defer => @next()


Remove dependency on `hubspot.define`

Remove dependency on `hubspot.define`

--- js/vex.coffee
+++ js/vex.coffee
@@ -1,16 +1,16 @@
-hubspot.define 'vex', ['jQuery'], ($) ->
+$ = jQuery

-    # Detect CSS Animation Support
+# Detect CSS Animation Support

-    animationEndSupport = false
+animationEndSupport = false

-    $ ->
-        s = (document.body || document.documentElement).style
-        animationEndSupport = s.animation isnt undefined or s.WebkitAnimation isnt undefined or s.MozAnimation isnt undefined or s.MsAnimation isnt undefined or s.OAnimation isnt undefined
+$ ->
+    s = (document.body || document.documentElement).style
+    animationEndSupport = s.animation isnt undefined or s.WebkitAnimation isnt undefined or s.MozAnimation isnt undefined or s.MsAnimation isnt undefined or s.OAnimation isnt undefined

-    # Vex
+# Vex

-    return {
+vex =

     globalID: 1

@@ -144,4 +144,4 @@
         vex.hideLoading()
         $('body').append('

')

-    }
+window.vex = vex


set smtp auth to null if not set

set smtp auth to null if not set

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -454,9 +454,11 @@
 			host: process.env["SHARELATEX_EMAIL_SMTP_HOST"]
 			port: process.env["SHARELATEX_EMAIL_SMTP_PORT"],
 			secure: process.env["SHARELATEX_EMAIL_SMTP_SECURE"]
-			auth:
-				user: process.env["SHARELATEX_EMAIL_SMTP_USER"]
-				pass: process.env["SHARELATEX_EMAIL_SMTP_PASS"]
+
+if process.env["SHARELATEX_EMAIL_SMTP_USER"] or process.env["SHARELATEX_EMAIL_SMTP_PASS"]
+	settings.email.parameters.auth =
+			user: process.env["SHARELATEX_EMAIL_SMTP_USER"]
+			pass: process.env["SHARELATEX_EMAIL_SMTP_PASS"]

 # Password Settings


fix some error in specs

fix some error in specs

--- spec/pane-resize-handle-element-spec.coffee
+++ spec/pane-resize-handle-element-spec.coffee
@@ -32,11 +32,11 @@

       # test removeChild
       paneAxis.removeChild(models[2])
-      expectResizeElement(i) for i in [1, 3]
+      expectResizeElement(i) for i in [1]

   describe "when user drag the resize element", ->
     [container, containerElement] = []
-    [resizeElementMove, getElementWidth, expectFlexScale] = []
+    [resizeElementMove, getElementWidth, expectPaneScale] = []

     beforeEach ->
       container = new PaneContainer
@@ -60,8 +60,8 @@
         element.getBoundingClientRect().width

       # assert the pane's flex scale. arguments is list of pane-scale pair
-      expectFlexScale = ->
-        args = Array::slice.apply(arguments, 0)
+      expectPaneScale = ->
+        args = Array::slice.call(arguments, 0)
         for paneScale in args
           expect(paneScale[0].getFlexScale()).toBeCloseTo(paneScale[1], 0.1)

@@ -69,14 +69,19 @@
       leftPane = container.getActivePane()
       middlePane = leftPane.splitRight()

-      resizeElements = containerElement.querySelectorAll('atom-pane-resize-handle')
-      paneElements = containerElement.querySelectorAll('atom-pane')
-      expect(resizeElements.length).toBe(2)
+      [resizeElements, paneElements] = []
+      reloadElements = ->
+        resizeElements = containerElement.querySelectorAll('atom-pane-resize-handle')
+        paneElements = containerElement.querySelectorAll('atom-pane')
+      reloadElements()
+      expect(resizeElements.length).toBe(1)

       resizeElementMove(resizeElements[0], getElementWidth(paneElements[0]) / 2)
       expectPaneScale [leftPane, 0.5], [middlePane, 1.5]

+      # add a new pane
       rightPane = middlePane.splitRight()
+      reloadElements()
       clientX = getElementWidth(paneElements[0]) + getElementWidth(paneElements[1]) / 2
       resizeElementMove(resizeElements[1], clientX)
       expectPaneScale [leftPane, 0.5], [middlePane, 0.75], [rightPane, 1.75]


buttonbar: fixing two of them, adding @interact example, and some tiny enhancements

--- salvus/page/buttonbar.coffee
+++ salvus/page/buttonbar.coffee
@@ -711,11 +711,13 @@
                     """
         icosahedron :
             insert : "show(icosahedron(color='green', opacity=.5, mesh=3), spin=1)"
+        tetrahedron:
+            insert : "show(tetrahedron(color='lime', opacity=.5, mesh=3), spin=1)"
         cube :
             insert : """
-                        show(cube(color=['red', 'blue', 'green'], frame_thickness=2,
-                                  frame_color='brown', opacity=0.8), frame=False)
-                    """
+                     show(cube(color=['red', 'blue', 'green'], frame_thickness=2,
+                              frame_color='brown', opacity=0.8), frame=False)
+                     """
         plot_text3d:
             insert : 'text3d("Text in 3D", (1,1, 1), color="darkred", fontsize=20)'
         graphs:
@@ -723,7 +725,7 @@
         petersen:
             insert : "graphs.PetersenGraph()"
         random_graph:
-            insert : "graphs.RandomGNM(15, 20)  # 15 vertices and 20 edges"
+            insert : "g=graphs.RandomGNM(15, 20)  # 15 vertices and 20 edges\ng.incidence_matrix()"
         chromatic_number:
             insert : "graphs.PetersenGraph().chromatic_number()"
         auto_group_graph:
@@ -798,7 +800,15 @@
         ring_ZZxp:
             insert : "R = PolynomialRing(ZZ, ['x%s'%p for p in primes(100)])\nR.inject_variables()"
         ring_QQ_quo:
-            insert : "R.<x,y> = QQ[]; R.<xx, yy> = R.quo([y^2 - x^3 - x])"
+            insert : "R.<x,y> = QQ[]\nR.<xx, yy> = R.quo([y^2 - x^3 - x])"
+        interact_fx:
+            insert : """
+                     @interact
+                     def interactive_function(a = slider(0, 10, .05, default=4),
+                                              b = (-3, 3, .1)):
+                         f(x) = b * x + sin(a * x)
+                         plot(f, (x, -5, 5)).show()
+                     """

     julia:
         comment:
@@ -969,10 +979,14 @@
                     """
         lm:
             insert: """
+                    y  <- c(0,3,2,2,4,5,8,9,7,6,2,0)
                     x1 <- c(1,2,3,4,3,4,5,7,5,7,8,9)
                     x2 <- c(1,1,1,1,2,2,2,3,3,3,4,4)
                     df <- data.frame(x1=x1, x2=x2)
-                    lm(y ~ x1 + x2, data=df)
+                    model <-lm(y ~ x1 + x2, data=df)
+                    model
+                    summary(model)
+                    anova(model)
                     """
         nlm:
             insert: """
@@ -1196,7 +1210,9 @@
                      [["∂ Differentiate", "#differentiate", "Differentiate a function"],
                       ["∫ Numerical Integral",      "#nintegrate",     "Numerically integrate a function"]
                       ["$f(x,y) = \\cdots $ - Symbolic Function",      "#symbolic_function",     "Define a symbolic function"]
-                      ["∫ Symbolic Integral",      "#integrate",     "Integrate a function"]
+                      ["∫ Symbolic Integral",      "#integrate",     "Integrate a function"],
+                      ["Interact Plots"],
+                      ["Interactive f(x)", "#interact_fx"]
                     ]]
     sage_linalg = ["Linear", "Linear Algebra",
                   [


storage server: allow limited number of simultaneous smc_storage scripts to run in parallel.

--- salvus/storage_server.coffee
+++ salvus/storage_server.coffee
@@ -54,7 +54,9 @@
 ## server-side: Storage server code
 ###########################

-SMC_STORAGE_LIMIT = 1   # maximum number of simultaneous smc_storage.py calls to allow at once
+# We limit the maximum number of simultaneous smc_storage.py calls to allow at once, since
+# this allows us to control ZFS contention, deadlocking, etc.
+SMC_STORAGE_LIMIT = 3

 # Execute a command using the smc_storage script.
 _smc_storage_no_queue = (opts) =>
@@ -77,7 +79,7 @@
                 opts.cb()

 _smc_storage_queue = []
-_smc_storage_queue_lock = false
+_smc_storage_queue_running = 0

 smc_storage = (opts) =>
     opts = defaults opts,
@@ -88,14 +90,14 @@
     process_smc_storage_queue()

 process_smc_storage_queue = () ->
-    if _smc_storage_queue_lock
+    if _smc_storage_queue_running >= SMC_STORAGE_LIMIT
         return
     if _smc_storage_queue.length > 0
         opts = _smc_storage_queue.shift()
-        _smc_storage_queue_lock = true
+        _smc_storage_queue_running += 1
         cb = opts.cb
         opts.cb = (err, output) =>
-            _smc_storage_queue_lock = false
+            _smc_storage_queue_running -= 1
             process_smc_storage_queue()
             cb(err, output)
         _smc_storage_no_queue(opts)


add css task

add css task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -95,7 +95,9 @@
   grunt.loadNpmTasks 'grunt-contrib-connect'
   grunt.loadTasks 'tasks'

-  grunt.registerTask "default", ['sass', 'concat:css', 'coffee', 'peg-grammars', 'copy:parser', 'requirejs-optimize-config', 'requirejs', 'copy:dist']
+  grunt.registerTask 'css', ['sass', 'concat:css']
+
+  grunt.registerTask "default", ['css', 'coffee', 'peg-grammars', 'copy:parser', 'requirejs-optimize-config', 'requirejs', 'copy:dist']

   grunt.registerTask 'requirejs-optimize-config', 'Builds the mainConfigFile for r.js', ->
     config_script = grunt.file.read('build/requirejs_config.js')


Deleted Gruntfile

Deleted Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,14 +0,0 @@
-module.exports = (grunt) ->
-  grunt.initConfig
-    pkg: grunt.file.readJSON 'package.json'
-
-    mochaTest:
-      test:
-        options:
-          reporter: 'spec'
-          require: 'coffee-script/register'
-        src: 'specs/**/*'
-
-    grunt.loadNpmTasks 'grunt-mocha-test'
-
-    grunt.registerTask 'test', ['mochaTest']


Create `grunt timestamp` task, closes GH-33

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -39,13 +39,14 @@
     watch:
       coffee:
         files: ["<%= appConfig.app %>/scripts/**/*.coffee"]
-        tasks: ["coffee:dist"]
+        tasks: ["coffee:dist", "timestamp"]

       coffeeTest:
         files: ["<%= appConfig.test %>/**/*.coffee"]
         tasks: [
           "coffee:test"
           "jasminehtml"
+          "timestamp"
         ]

       html:
@@ -53,19 +54,19 @@
           "<%= appConfig.app %>/**/*.html"
           "!<%= appConfig.dev %>/templates/**/*.html"
         ]
-        tasks: ["copy:dev"]
+        tasks: ["copy:dev", "timestamp"]

       templates:
         files: ["<%= appConfig.app %>/templates/**/*.html"]
-        tasks: ["ngtemplates"]
+        tasks: ["ngtemplates", "timestamp"]

       css:
         files: ["<%= appConfig.app %>/styles/**/*.less"]
-        tasks: ["less"]
+        tasks: ["less", "timestamp"]

       livereload:
         files: ["<%= appConfig.dev %>/**/*"]
-        tasks: ["livereload"]
+        tasks: ["livereload", "timestamp"]

     coffee:
       dist:
@@ -246,6 +247,8 @@

   grunt.renameTask "regarde", "watch"

+  grunt.registerTask "timestamp", -> grunt.log.subhead "--- timestamp: #{new Date()}"
+
   grunt.registerTask "build:dev", [
     "clean"
     "bower"


less and coffee are now executed live again.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,10 +12,11 @@
     'img/*'
     'lib/**/*'
     'views/**/*'
-    'backend-mock/**/*.json',
-    'less/**/*',
-    'scripts/**/*.coffee'
+    'backend-mock/**/*.json'
   ]
+
+  lessSources = 'less/**/*'
+  coffeeSources = 'scripts/**/*.coffee'

   grunt.initConfig
     pkg: grunt.file.readJSON 'package.json'
@@ -50,7 +51,7 @@
         ext: '.js'
       dist:
         files:
-          '.tmp/js/main.js': 'app/scripts/**/*.coffee'
+          'dist/js/main.js': 'app/scripts/**/*.coffee'

     karma:
       options:
@@ -78,14 +79,14 @@

     watch:
       less:
-        files: ['.tmp/less/**/*.less']
-        tasks: ['less:dev']
+        files: "app/#{lessSources}"
+        tasks: ['copy:dev_less', 'less:dev']
       coffee:
-        files: ['.tmp/scripts/**/*.coffee']
-        tasks: ['coffee:dev']
-#      copy: TODO: break down into smaller tasks. this takes too long and also triggers coffee + less
-#        files: devCopySources.map (src) -> "app/#{src}"
-#        tasks: ['copy:dev']
+        files: "app/#{coffeeSources}"
+        tasks: ['copy:dev_coffee', 'coffee:dev']
+      copy:
+        files: devCopySources.map (src) -> "app/#{src}"
+        tasks: ['copy:dev']
       htmlbuild:
         files: ['app/app.html']
         tasks: ['htmlbuild:dev']
@@ -119,6 +120,16 @@
         src: devCopySources
         dest: '.tmp'
         rename: (dest, src) -> "#{dest}/#{src.replace(/(backend-mock\/|\.json$)/g, '')}"
+      dev_less:
+        expand: true
+        cwd: 'app'
+        src: lessSources
+        dest: '.tmp'
+      dev_coffee:
+        expand: true
+        cwd: 'app'
+        src: coffeeSources
+        dest: '.tmp'
       dist:
         expand: true
         cwd: 'app'
@@ -154,7 +165,10 @@
   grunt.loadNpmTasks 'grunt-shell'
   grunt.loadNpmTasks 'grunt-contrib-connect'

-  grunt.registerTask 'dev', ['bower', 'copy:dev', 'coffee:dev', 'less:dev', 'htmlbuild:dev', 'connect:dev', 'watch']
+  grunt.registerTask 'copy_dev', ['copy:dev', 'copy:dev_less', 'copy:dev_coffee']
+  grunt.registerTask 'compile_dev', ['coffee:dev', 'less:dev', 'htmlbuild:dev']
+  grunt.registerTask 'compile_dist', ['coffee:dist', 'less:dist', 'uglify', 'htmlbuild:dist']
+  grunt.registerTask 'dev', ['bower', 'copy_dev', 'compile_dev', 'connect:dev', 'watch']
   grunt.registerTask 'test', ['karma:dev']
-  grunt.registerTask 'dist', ['bower', 'karma:dist', 'less:dist', 'coffee:dist', 'uglify:dist', 'htmlbuild:dist', 'copy:dist']
-  grunt.registerTask 'ci', ['bower','karma:ci','less:dist','coffee:dist','uglify:dist','htmlbuild:dist','copy:dist','shell:git_tag','shell:git_push_tag']+  grunt.registerTask 'dist', ['bower', 'compile_dist', 'copy:dist']
+  grunt.registerTask 'ci', ['dist','karma:ci','shell:git_tag','shell:git_push_tag']


chore(dev): Fix release task.

chore(dev): Fix release task.

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

   grunt.initConfig
+    pkg: grunt.file.readJSON 'package.json'
     pkgFile: 'package.json'

     files: [
@@ -66,6 +67,7 @@

     bump:
       options:
+        updateConfigs: ['pkg']
         commitFiles: ['package.json', 'CHANGELOG.md']
         commitMessage: 'chore: release v%VERSION%'
         pushTo: 'upstream'
@@ -111,13 +113,14 @@
   grunt.loadNpmTasks 'grunt-npm'
   grunt.loadNpmTasks 'grunt-bump'
   grunt.loadNpmTasks 'grunt-auto-release'
+  grunt.loadNpmTasks 'grunt-conventional-changelog'

   grunt.registerTask 'test', ['karma:single']
   grunt.registerTask 'default', ['jshint', 'jscs', 'test']
   grunt.registerTask 'release', 'Bump the version and publish to npm.', (type) ->
     grunt.task.run [
+      "bump:#{type||'patch'}"
       'changelog'
       'npm-contributors'
-      "bump:#{type||'patch'}"
       'npm-publish'
     ]


force all the things

force all the things

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -48,6 +48,7 @@
           environment: 'production'
           outputStyle: 'compressed'
           imagesDir: '<%= compass.dev.options.imagesDir %>'
+          force: true
     copy:
       prod:
         files: [
@@ -81,7 +82,7 @@
           cwd: 'assets/img/'
           dest: 'build/<%= imagemin.prod.files[0].cwd %>'
           src: [
-            # '*.{png,gif}'
+            '*.{png,gif}'
             '**/*.jpg'
           ]
         ]


fix(grunt): start up server synchronously when it is in server mode

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -87,6 +87,9 @@
         options:
           async: true
         command: 'node_modules/.bin/brunch watch --server'
+      # Synchronously watch for changes for re-assembly
+      syncWatch:
+        command: 'node_modules/.bin/brunch watch --server'

   ## Build tasks

@@ -105,7 +108,7 @@
   # Just run server and watch files
   grunt.registerTask 'server', [
     'clean'
-    'shell:watch'
+    'shell:syncWatch'
   ]

   # Test mode. This is for continuous integration. To run tests locally, just


add :install keyword

add :install keyword

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -143,17 +143,18 @@
         ]

     bower:
-      options:
-        targetDir: './'
-        install: true
-        verbose: true
-        cleanTargetDir: false
-        cleanBowerDir: false
-        layout: (type, component) ->
-          if type is 'css'
-            return config.app + '/css/lib'
-          else
-            return config.app + '/scripts/lib'
+      install:
+        options:
+          targetDir: './'
+          install: true
+          verbose: true
+          cleanTargetDir: false
+          cleanBowerDir: false
+          layout: (type, component) ->
+            if type is 'css'
+              return config.app + '/css/lib'
+            else
+              return config.app + '/scripts/lib'

     ngmin:
       production:
@@ -223,14 +224,14 @@
   grunt.registerTask 'minify', ['ngmin', 'uglify']

   grunt.registerTask 'dev', [
-    'bower',
+    'bower:install',
     'coffee:develop',
     'jade:develop',
     'stylus:develop']

   grunt.registerTask 'production', [
     'clean',
-    'bower',
+    'bower:install',
     'copy:dist',
     'chromeManifest:dist',
     'coffee:production',


fix! missed to replace concat with rollup in watch task, now done

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -83,7 +83,7 @@
     watch:
       scripts:
         files: ['src/**/*.js']
-        tasks: ['concat','string-replace','uglify','jshint','jscs']
+        tasks: ['rollup','string-replace','uglify','jshint','jscs']
         options:
           interrupt: true


add a grunt help task

add a grunt help task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,5 +10,11 @@
   grunt.loadNpmTasks 'grunt-exec'
   grunt.loadNpmTasks 'grunt-coffeelint'

+  grunt.registerTask 'help', 'Get a list of commands for building Send A Message', () ->
+    grunt.log.writeln '\n===== SEND A MESSAGE ====='
+    grunt.log.writeln '\ncommands:'
+    grunt.log.writeln 'help   --- get a list of grunt commands'
+    grunt.log.writeln 'server --- run the Send A Message server'
+    grunt.log.writeln '\n=========================='
   grunt.registerTask 'default', ['coffeelint']
   grunt.registerTask 'server', ['exec:server']


fix nodemon grunt command

fix nodemon grunt command

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -205,7 +205,7 @@
       browser:
         command: 'chrome http://localhost:8080 &'
       server:
-        command: "./node_modules/nodemon/nodemon.js --delay 2 build/server/src/server.js"
+        command: "./node_modules/.bin/nodemon --delay 2 build/server/src/server.js"

     sshconfig:
       server:


Fixing release.

Fixing release.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -38,11 +38,11 @@
         _release:
             options:
                 bump: false
-                add: false
-                commit: false
-                tag: false
-                push: false
-                pushTags: false
+                add: true
+                commit: true
+                tag: true
+                push: true
+                pushTags: true
                 npm: true

     grunt.loadNpmTasks 'grunt-coffeelint'


chore(Grunt): Fixed whitespaces

chore(Grunt): Fixed whitespaces

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -37,23 +37,23 @@
   ]

   grunt.registerTask "deploy", "Build and copy to lib/", [
-      "coffee"
-      "stylus"
-      "jade"
-      "concatDeploy"
-      "clean"
-      "replace:src"
-      "chalkboard"
-      "marked"
-      "replace:docs"
-      "replace:version"
-      "karma:build"
-      "update:component"
-      "copy:images"
-      "copy:public"
-      "uglify:dist"
-      "tag"
-    ]
+    "coffee"
+    "stylus"
+    "jade"
+    "concatDeploy"
+    "clean"
+    "replace:src"
+    "chalkboard"
+    "marked"
+    "replace:docs"
+    "replace:version"
+    "karma:build"
+    "update:component"
+    "copy:images"
+    "copy:public"
+    "uglify:dist"
+    "tag"
+  ]

   grunt.registerTask "compile", "Compile files", [
     "coffee"


fix(grunt): make heroku task do nothing

fix(grunt): make heroku task do nothing

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

   # Running on Heroku is for staging, so production mode
-  grunt.registerTask 'heroku', 'production'
+  grunt.registerTask 'heroku', []

   # Release -- new version!
   grunt.registerTask 'release', (type) ->


not to run yuidoc in build

not to run yuidoc in build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -87,4 +87,4 @@

     grunt.registerTask 'default', 'mocha-chai-sinon:spec'
     grunt.registerTask 'single', 'mocha-chai-sinon:single'
-    grunt.registerTask 'build', ['coffee:dist', 'bower:dist', 'browserify:dist', 'uglify:dist', 'titaniumify', 'yuidoc']
+    grunt.registerTask 'build', ['coffee:dist', 'bower:dist', 'browserify:dist', 'uglify:dist', 'titaniumify']


:lipstick:

:lipstick:

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -73,5 +73,4 @@
     @loadNpmTasks "grunt-banner"

     # Default task.
-    @registerTask "default", ["clean:start", "coffee", "concat", "uglify",
-      "copy", "cssmin", "usebanner", "clean:end"]
+    @registerTask "default", ["clean:start", "coffee", "concat", "uglify", "copy", "cssmin", "usebanner", "clean:end"]


less > scss file copy for distribution purposes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -126,6 +126,14 @@
 					rename: (dest) ->
 						"#{dest}/<%= OUTPUT_JS %>.less"
 				]
+			scss:
+				files: [
+					expand: true,
+					src: ['src/less/style.less'],
+					dest: 'dist'
+					rename: (dest) ->
+						"#{dest}/<%= OUTPUT_JS %>.scss"
+				]

 		connect:
 			options:
@@ -176,7 +184,7 @@

 	## Internal tasks
 	grunt.registerTask '_compile', ['clean', 'jshint', 'coffeelint', 'browserify']
-	grunt.registerTask '_package', ['less', 'copy:less', 'uglify', 'clean:tmp']
+	grunt.registerTask '_package', ['less', 'copy:less', 'copy:scss', 'uglify', 'clean:tmp']

 	# Partial (dev) tasks
 	grunt.registerTask 'test', ['_compile', 'mocha', 'clean:tmp', 'notify:test']


lint test files + lint as part of testing

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -5,7 +5,8 @@
     'server.coffee',
     'Gruntfile.coffee',
     'app/**/*.coffee',
-    'config/**/*.coffee'
+    'config/**/*.coffee',
+    'test/**/*.coffee'
   ]

   # Grunt Config
@@ -63,4 +64,4 @@
   # Tasks
   grunt.registerTask('build', ['coffeelint'])
   grunt.registerTask('dev', ['build', 'express:dev', 'watch'])
-  grunt.registerTask('test', 'mochaTest')
+  grunt.registerTask('test', ['build', 'mochaTest'])


delete a few debugging lines...

delete a few debugging lines...

--- salvus/page/worksheet1.coffee
+++ salvus/page/worksheet1.coffee
@@ -367,12 +367,9 @@
                 if error
                     worksheet = page.salvus_worksheet()
                 else
-                    console.log(data)
                     obj = misc.from_json(data)
-                    console.log(obj)
                     worksheet = worksheet1.find(".salvus-templates").find(".salvus-worksheet").clone()
                     page.append(worksheet)
-                    console.log(obj)
                     set_worksheet_from_obj(obj)
                     focus_editor_on_first_cell()


Convert line item edit to Backbone.js

Convert line item edit to Backbone.js

--- backend/app/assets/javascripts/spree/backend/line_items.js.coffee
+++ backend/app/assets/javascripts/spree/backend/line_items.js.coffee
@@ -1,26 +1,3 @@
-editing = (e) ->
-  e.preventDefault()
-  $(e.delegateTarget).addClass('editing')
-
-editingDone = (e) ->
-  e.preventDefault()
-  $(e.delegateTarget).removeClass('editing')
-
-onSaveLineItem = (e) ->
-  e.preventDefault()
-  line_item = $(this).closest('.line-item')
-  line_item_id = line_item.data('line-item-id')
-  quantity = parseInt(line_item.find('input.line_item_quantity').val())
-  adjustLineItem(line_item_id, quantity)
-  editingDone(e)
-
-onDeleteLineItem = (e) ->
-  e.preventDefault()
-  return unless confirm(Spree.translations.are_you_sure_delete)
-  line_item = $(this).closest('.line-item')
-  line_item_id = line_item.data('line-item-id')
-  deleteLineItem(line_item_id)
-  editingDone(e)

 lineItemURL = (id) ->
   "#{Spree.routes.line_items_api(order_number)}/#{id}.json"
@@ -47,6 +24,42 @@
       $('.line-items').remove()
     window.Spree.advanceOrder()

+Spree.CartLineItemView = Backbone.View.extend
+  tagName: 'tr'
+
+  initialize: (options) ->
+    console.log(options)
+
+  events:
+    'click .edit-line-item': 'onEdit'
+    'click .cancel-line-item': 'onCancel'
+    'click .save-line-item': 'onSave'
+    'click .delete-line-item': 'onDelete'
+
+  onEdit: (e) ->
+    e.preventDefault()
+    @$el.addClass('editing')
+
+  onCancel: (e) ->
+    e.preventDefault()
+    @$el.removeClass('editing')
+
+  onSave: (e) ->
+    e.preventDefault()
+    line_item = @$el
+    line_item_id = line_item.data('line-item-id')
+    quantity = parseInt(line_item.find('input.line_item_quantity').val())
+    adjustLineItem(line_item_id, quantity)
+    @$el.removeClass('editing')
+
+  onDelete: (e) ->
+    e.preventDefault()
+    return unless confirm(Spree.translations.are_you_sure_delete)
+    line_item = @$el
+    line_item_id = line_item.data('line-item-id')
+    deleteLineItem(line_item_id)
+    @$el.removeClass('editing')
+
 $ ->
   url = Spree.routes.orders_api + "/" + order_number
   Spree.ajax(url: url).done (result) ->
@@ -55,9 +68,8 @@
       html = HandlebarsTemplates['orders/line_item'](line_item: line_item, image: image)
       $("table.line-items > tbody").append(html)

-    $('.line-item')
-      .on('click', '.edit-line-item',   editing)
-      .on('click', '.cancel-line-item', editingDone)
-      .on('click', '.save-line-item',   onSaveLineItem)
-      .on('click', '.delete-line-item', onDeleteLineItem)
+    $('.line-item').each ->
+      view = new Spree.CartLineItemView(
+        el: $(@)
+      )


Replace dummy text with Mark workflow help text

--- app/assets/javascripts/components/tutorial-text/yale-mark-tutorial-text.cjsx
+++ app/assets/javascripts/components/tutorial-text/yale-mark-tutorial-text.cjsx
@@ -1,19 +1,10 @@
 module.exports = {
-  header: 'Help Mark Fields',
+  header: 'Mark the fields to be transcribed',
   pages: [
     {
       pageNumber: 0,
-      text: "One Lorem ipsum dolor sit amet, posuere a mauris, nostra quam nonummy, facilisis amet neque. Ut lacus eros venenatis ipsum, erat ut turpis, aliquam metus vitae volutpat sit sed. Eros maecenas malesuada quam leo laoreet, vel curabitur maecenas neque quam et libero, ut libero, vitae vel eros vestibulum, sagittis etiam nunc. Nonummy risus mattis, sed nisl. Quis non quis quis ultricies non facilisi."
-    },
+      text: "The first step in crowdsourced transcription is to identify and mark the text that should be transcribed.

-    {
-      pageNumber: 1,
-      text: "Two Lorem ipsum dolor sit amet, posuere a mauris, nostra quam nonummy, facilisis amet neque. Ut lacus eros venenatis ipsum, erat ut turpis, aliquam metus vitae volutpat sit sed. Eros maecenas malesuada quam leo laoreet, vel curabitur maecenas neque quam et libero, ut libero, vitae vel eros vestibulum, sagittis etiam nunc. Nonummy risus mattis, sed nisl. Quis non quis quis ultricies non facilisi."
-    },
+The scroll bar on the left displays all of the pages in each program. Start by locating the page in the program that lists the title, playwright, director, etc. (This may be several pages in on longer programs.) Click the button for the field you want to mark—for example, “Playwright”—and then draw a rectangle around the relevant text. Click the DONE box, then select another field to mark.

-    {
-      pageNumber: 2,
-      text: "Three Lorem ipsum dolor sit amet, posuere a mauris, nostra quam nonummy, facilisis amet neque. Ut lacus eros venenatis ipsum, erat ut turpis, aliquam metus vitae volutpat sit sed. Eros maecenas malesuada quam leo laoreet, vel curabitur maecenas neque quam et libero, ut libero, vitae vel eros vestibulum, sagittis etiam nunc. Nonummy risus mattis, sed nisl. Quis non quis quis ultricies non facilisi."
-    }
-  ]
-}+Click the X to delete your selection if you want to change it. Repeat with the next field until you have marked all of them, and then click NEXT to move on to more marking tasks. Remember that some fields may be located on different pages of the program."}