Prevent default when dragging over the placeholder

--- src/sortable.coffee
+++ src/sortable.coffee
@@ -180,6 +180,9 @@
     unless @placeholder
       @placeholder = @options.placeholder(e, @_elements.length)
       $(@placeholder).on("drop", $.proxy(this, "_handleDrop"))
+      $(@placeholder).on "dragover", (e) ->
+        e.preventDefault()
+
     $(@placeholder).detach()
     $(e.currentTarget)[keyword](@placeholder)


added pointerMoveIn/pointerMoveOut/pointerUpInside/pointerUpOutside events

--- src/art/engine/events/pointer_event_manager.coffee
+++ src/art/engine/events/pointer_event_manager.coffee
@@ -12,6 +12,19 @@
   focus
   blur

+  pointerClick
+  pointerUpInside
+  pointerUpOutside
+  pointerMoveIn
+  pointerMoveOut
+
+TODO:
+  Rename these events:
+
+    mouseMove => cursorMove
+    mouseIn   => cursorMoveIn
+    mouseOut  => cursorMoveOut
+
 DESIGN NOTES
 Purpose:
   Support Touch and Mouse events.
@@ -20,7 +33,18 @@

   Synthesize events:
     pointerClick
-      triggerd if active-locations went from 0 to non-0 and back to 0 without moving outside the pointerDeadZone
+      triggered if active-locations went from 0 to non-0 and back to 0 without moving outside the pointerDeadZone
+    pointerUpInside / pointerUpOutside
+      just before the pointerUp event is sent:
+        if pointer is "pointInside" for the target element
+          send pointerUpInside
+        else
+          send pointerUpOutside
+    pointerMoveIn / pointerMoveOut
+      pointer's "pointInside" changed from false>>true for the target element
+        send pointerMovedIn
+      else
+        send pointerMovedOut

 Differences between Touch events and Mouse events
   Touch Events
@@ -144,13 +168,18 @@

     queuePointerEventForElements: (elements, type, pointer, timeStampInPerformanceSeconds) ->
       elements = prioritySortElements elements.slice()
-      @queuePointerEventForElement element, type, pointer, timeStampInPerformanceSeconds for element in elements
+      for element in elements
+        @queuePointerEventForElement element, type, pointer, timeStampInPerformanceSeconds

     queuePointerEvents: (type, pointer, timeStampInPerformanceSeconds) ->
+      @forEachReceivingElement (e) =>
+        @queuePointerEventForElement e, type, pointer, timeStampInPerformanceSeconds
+
+    forEachReceivingElement: (f) ->
       if e = @capturingElement
-        @queuePointerEventForElement e, type, pointer, timeStampInPerformanceSeconds
+        f e
       else
-        @queuePointerEventForElements @currentFocusedPath, type, pointer, timeStampInPerformanceSeconds
+        f e for e in @currentFocusedPath

     queueMouseEvents: (type, pointer, timeStampInPerformanceSeconds) ->
       @queuePointerEventForElements @currentMousePath, type, pointer, timeStampInPerformanceSeconds
@@ -230,6 +259,28 @@

       @queuePointerEvents "pointerDown", pointer, timeStampInPerformanceSeconds

+    queuePointerUpInAndOutsideEvents: (pointer, timeStampInPerformanceSeconds) ->
+      @forEachReceivingElement (element) =>
+        locationInParentSpace = pointer.locationIn element.parent
+        type = if element.pointInside locationInParentSpace then  "pointerUpInside" else "pointerUpOutside"
+        @queuePointerEventForElement element, type, pointer, timeStampInPerformanceSeconds
+
+    queuePointerMoveInAndOutEvents: (pointer, timeStampInPerformanceSeconds) ->
+      isInsideParent = true
+      wasInsideParent = true
+      @forEachReceivingElement (element) =>
+        lastLocationInParentSpace = pointer.lastLocationIn element.parent
+        locationInParentSpace = pointer.locationIn element.parent
+        wasInside = wasInsideParent && element.pointInside lastLocationInParentSpace
+        isInside = isInsideParent && element.pointInside locationInParentSpace
+
+        if isInside != wasInside
+          type = if isInside then "pointerMoveIn" else "pointerMoveOut"
+          @queuePointerEventForElement element, type, pointer, timeStampInPerformanceSeconds
+
+        isInsideParent = isInside
+        wasInsideParent = wasInside
+
     # pointerUp - user activity cased this
     pointerUp: (id, timeStampInPerformanceSeconds) ->
       eventEpoch.logEvent "pointerUp", id
@@ -239,7 +290,9 @@
       @_numActivePointers--
       delete @activePointers[id]

+      @queuePointerUpInAndOutsideEvents pointer, timeStampInPerformanceSeconds
       @queuePointerEvents "pointerUp", pointer, timeStampInPerformanceSeconds
+
       if pointer.stayedWithinDeadzone
         # If you want to open a file dialog, for security reasons, the browser REQUIRES this happens within the mouse-up event.
         # So, flush the eventEpoch immediatly.
@@ -270,6 +323,7 @@
       return unless !pointer.location.eq location

       @activePointers[id] = pointer = pointer.moved location
+      @queuePointerMoveInAndOutEvents pointer, timeStampInPerformanceSeconds
       @queuePointerEvents "pointerMove", pointer, timeStampInPerformanceSeconds

     mouseDown: (location, timeStampInPerformanceSeconds) -> @pointerDown "mousePointer", location, timeStampInPerformanceSeconds


--- client/app/Applications/Apps.kdapplication/Views/appview.coffee
+++ client/app/Applications/Apps.kdapplication/Views/appview.coffee
@@ -134,8 +134,23 @@
         diameter: 30
         color   : "#ffffff"
       callback  : ->
-        appsController.installApp app, app.manifest.version, (err)=>
+
+        _installApp = =>
+          @showLoader()
+          appsController.installApp app, app.manifest.version, (err)=>
+            @hideLoader()
+
+        vmController = KD.getSingleton("vmController")
+        unless vmController.vms.length
           @hideLoader()
+          vmController.askToTurnOn app.title, -> _installApp()
+        else
+          vmController.info (err, vm, info)=>
+            if info?.state is "RUNNING"
+              _installApp()
+            else
+              @hideLoader()
+              vmController.askToTurnOn app.title, -> _installApp()

     @runButton = new KDButtonView
       title     : "Run"


Minor method renaming and code comment.

Minor method renaming and code comment.

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -85,7 +85,7 @@
       cssClass : 'solid medium red'
       title    : title
       loader   : yes
-      callback : @bound 'denyShare'
+      callback : @bound 'deny'

     unless @isApproved

@@ -93,7 +93,7 @@
         cssClass : 'solid green medium'
         title    : 'ACCEPT'
         loader   : yes
-        callback : @bound 'approveShare'
+        callback : @bound 'approve'

   createAvatarView: (nickname) ->
@@ -122,7 +122,7 @@
       "

-  approveShare: ->
+  approve: ->

     { jMachine } = @getData()

@@ -155,7 +155,7 @@
           doNavigation()

-  denyShare: ->
+  deny: ->

     @denyButton.showLoader()
     @getData().jMachine.deny (err) =>
@@ -166,6 +166,7 @@
         kd.singletons.mainView.activitySidebar.redrawMachineList()

         if @isApproved
+          # quit IDE if exists
           envDataProvider.getIDEFromUId(@getData().uid)?.quit()

         @destroy()


Better values logging

Better values logging


Error cleanup

Error cleanup

* Error messages no longer start with "svgtiler: ".
* Instead, error name is properly set to "SVGTilerError".
* API change: renamed `SVGTilerException` to `SVGTilerError`.

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -142,10 +142,10 @@

 extensionOf = (filename) -> path.extname(filename).toLowerCase()

-class SVGTilerException
-  constructor: (@message) ->
-  toString: ->
-    "svgtiler: #{@message}"
+class SVGTilerError extends Error
+  constructor: (message) ->
+    super message
+    @name = 'SVGTilerError'

 parseBox = (box) ->
   return null unless box
@@ -304,7 +304,7 @@
 class Symbol extends HasSettings
   @parse: (key, data, settings) ->
     unless data?
-      throw new SVGTilerException "Attempt to create symbol '#{key}' without data"
+      throw new SVGTilerError "Attempt to create symbol '#{key}' without data"
     else if typeof data == 'function'
       new DynamicSymbol key, data, settings
     else if data.function?
@@ -339,7 +339,7 @@
                   svg: fs.readFileSync filename,
                          encoding: getSetting settings, 'svgEncoding'
               else
-                throw new SVGTilerException "Unrecognized extension in filename '#{data}' for symbol '#{key}'"
+                throw new SVGTilerError "Unrecognized extension in filename '#{data}' for symbol '#{key}'"
         else
           data = svg: data
       new StaticSymbol key, {...data, settings}
@@ -560,7 +560,7 @@
   use: (context) ->
     result = @func.call context
     unless result?
-      throw new Error "Function for symbol #{@key} returned #{result}"
+      throw new SVGTilerError "Function for symbol #{@key} returned #{result}"
     ## Render Preact virtual dom elements (e.g. from JSX notation) to SVG now,
     ## for better duplicate detection.
     result = renderPreact result
@@ -603,7 +603,7 @@
     if extension of extensionMap
       extensionMap[extension].parseFile filename, filedata, settings
     else
-      throw new SVGTilerException "Unrecognized extension in filename #{filename}"
+      throw new SVGTilerError "Unrecognized extension in filename #{filename}"

 class Style extends Input
   load: (@css) ->
@@ -736,7 +736,7 @@
     else if Array.isArray data
       new Mappings data
     else
-      throw new SVGTilerException "Could not convert into Mapping(s): #{data}"
+      throw new SVGTilerError "Could not convert into Mapping(s): #{data}"
   push: (map) ->
     @maps.push map
   lookup: (key) ->
@@ -1504,7 +1504,7 @@
   Drawings, XLSXDrawings,
   Style, CSSStyle, StylusStyle,
   extensionMap, Input, Mappings, Context,
-  SVGTilerException, SVGNS, XLINKNS, escapeId,
+  SVGTilerError, SVGNS, XLINKNS, escapeId,
   main, convertSVG, renderDOM, defaultSettings,
   version: metadata.version
 }


Fix Babel path resolution (MODULE_NOT_FOUND error)

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -360,7 +360,7 @@
   parse: (data) ->
     {code} = require('@babel/core').transform data,
       filename: @filename
-      plugins: [['@babel/plugin-transform-react-jsx',
+      plugins: [[require.resolve('@babel/plugin-transform-react-jsx'),
         useBuiltIns: true
         pragma: 'preact.h'
         pragmaFrag: 'preact.Fragment'


Update the keys action to use the multicontainer SDK

Change-Type: patch


Add support for loading fonts from Uint8Array or ArrayBuffer

--- lib/font.coffee
+++ lib/font.coffee
@@ -11,6 +11,12 @@

     else if Buffer.isBuffer(src)
       font = new TTFFont src, family
+
+    else if src instanceof Uint8Array
+      font = new TTFFont new Buffer(src), family
+
+    else if src instanceof ArrayBuffer
+      font = new TTFFont new Buffer(new Uint8Array(src)), family

     if not font?
       throw new Error 'Not a supported font format or standard PDF font.'


spinner: complete callback fix

spinner: complete callback fix

--- app/_src/helpers/spinner.coffee
+++ app/_src/helpers/spinner.coffee
@@ -55,17 +55,17 @@
 		, BEFORE_HIDE_DURATION_MS * 1.2

 	hide: (callback, isToBeRemoved) =>
-		return @animateOut(callback, isToBeRemoved) unless @hasDeffered
+		return @animateOut callback, isToBeRemoved unless @hasDeffered
 		$.when( @deffered.promise() ).done =>
-			@animateOut isToBeRemoved
+			@animateOut callback, isToBeRemoved

 	animateOut: (callback, isToBeRemoved) =>
 		TweenLite.to @$spinner, .3, {
 			opacity: 0,
 			ease: Power4.EaseOut,
-			onComplete: =>
+			onComplete:  =>
 				@hasDeffered = false
 				@$spinner.addClass CLASS_INACTIVE
-				@callback() if typeof callback is 'function'
+				callback() if typeof callback is 'function'
 				@removeEl() if isToBeRemoved is true
 		}


Fixed multi-floor truncation.

Fixed multi-floor truncation.

--- app/assets/javascripts/calendar.js.coffee
+++ app/assets/javascripts/calendar.js.coffee
@@ -20,20 +20,21 @@
     current_time = new Date()
     current_hour = current_time.getHours()
     bar_length = current_hour*60*60/180
-    start_offset = $(".room-data-bar").first().offset().top
     hour_elements = $(".tab-content div").filter( -> $(this).data("hour") < current_hour)
     hour_elements.show()
     $("div").filter( -> $(this).data("old-height")?).each (key, item) ->
       $(this).height($(this).data("old-height"))
     return unless [current_time.getFullYear(), current_time.getMonth()+1, current_time.getDate()].toString() == @date_selected.toString()
+    $(".tab-pane").show()
     $(".room-data").each (key, item) =>
       $(item).data("old-height", $(item).height())
       $(item).height($(item).height() - bar_length)
     $(".bar").each (key, item) =>
       item = $(item)
+      start_offset = item.parent().offset().top
       start_at = item.offset().top - start_offset
       end_at = item.height()+start_at
-      console.log "Start At: #{start_at} End At: #{end_at} Target Bar Location: #{bar_length} #{item.attr("class")}"
+      console.log "Start offset: #{start_offset} Start At: #{start_at} End At: #{end_at} Target Bar Location: #{bar_length} #{item.attr("class")}"
       if start_at < bar_length
         if end_at > bar_length
           console.log "Making smaller"
@@ -45,6 +46,7 @@
     hour_elements.hide()
     $("#dayviewTable").data("old-height", $("#dayviewTable").height())
     $("#dayviewTable").height($("#dayviewTable").height() - bar_length)
+    $(".tab-pane").attr("style",null)
     return
   selected_date: (dateText, inst) =>
     date = @datepicker.datepicker("getDate")


change config back for heroku

change config back for heroku

--- app/assets/javascripts/facebook.js.coffee
+++ app/assets/javascripts/facebook.js.coffee
@@ -8,7 +8,7 @@

 window.fbAsyncInit = ->
-  FB.init(appId: 1649192258640996, cookie: true)
+  FB.init(appId: 626838637425914, cookie: true)

   $('#sign_in').click (e) ->
     e.preventDefault()


Made sure we treat dialog refresh as a dialog:show

--- app/assets/javascripts/selector.js.coffee
+++ app/assets/javascripts/selector.js.coffee
@@ -269,6 +269,7 @@
                 frame.dialog "close"
               invalid: (frame, data, status, request) ->
                 frame.html request.responseText
+                frame.trigger('dialog:show')
         else
           console.log "Don't known how to manage this option"
           console.log selected


bubble up raw event

bubble up raw event

--- src/template.coffee
+++ src/template.coffee
@@ -1,6 +1,10 @@
 { EventEmitter } = require 'events'
 { Builder:DefaultBuilder } = require 'asyncxml'
-EVENTS = ['new','add','attr','attr:remove','text','remove','data','close','end']
+EVENTS = [
+    'new','add'
+    'attr','attr:remove','text', 'raw'
+    'remove','data','close','end'
+]

 schema =
     'xml'  : -> "" # allready includes tag


Pass the 'view' as the first argument

Pass the 'view' as the first argument

--- src/template.coffee
+++ src/template.coffee
@@ -28,7 +28,7 @@

 class Template extends EventEmitter
-    constructor: (opts = {}, template) ->
+    constructor: (view, opts = {}, template) ->
         # options
         [template, opts] = [opts, {}] if typeof opts is 'function'
         # defaults
@@ -53,6 +53,7 @@
         # instantiate
         @xml = new ExtendedBuilder opts
         @xml.template = this
+        @xml.view     = view
         # override query
         old_query = @xml.query
         @xml.query = (type, tag, key) ->


feat(): include `loopfunc` with the default relaxing `jshint` options.

Fixes #25

Please _do_ note that this will now also hide legitimate reports.

--- lib/hint.coffee
+++ lib/hint.coffee
@@ -25,18 +25,25 @@

 defaultOptions  = [

-    'undef'
-
-    ##  options to relax for cs
-    ##
-    'eqnull'
-    'expr'
-    'shadow'
-    'sub'
-    'multistr'
+    ##  http://jshint.com/docs/options/#enforcing-options
+    ##
+    'undef'     ##  http://jshint.com/docs/options/#undef
+
+    ##  Options to relax for cs.
+    ##
+    ##  http://jshint.com/docs/options/#relaxing-options
+    ##
+    'eqnull'    ##  http://jshint.com/docs/options/#eqnull
+    'expr'      ##  http://jshint.com/docs/options/#expr
+    'loopfunc'  ##  http://jshint.com/docs/options/#loopfunc
+    'multistr'  ##  http://jshint.com/docs/options/#multistr
+    'shadow'    ##  http://jshint.com/docs/options/#shadow
+    'sub'       ##  http://jshint.com/docs/options/#sub
 ]

+##  Warnings / errors for which no relaxing options exist will be filtered away.
+##
 errorsToSkip                = {}
 errorsToSkip[ error.code ]  = error for error in [


Add display_name back to user-search label

--- app/components/user-search.cjsx
+++ app/components/user-search.cjsx
@@ -14,7 +14,7 @@
       apiClient.type('users').get display_name: "#{value}*", page_size: 10
         .then (users) =>
           opts = for user in users
-            { value: user.id, label: "@#{ user.login }" }
+            { value: user.id, label: "@#{ user.login }: #{ user.display_name }" }
           callback null, {
             options: opts
           }


Check the right user tutorial_done flag

Check the right user tutorial_done flag

--- app/controllers/classify.coffee
+++ app/controllers/classify.coffee
@@ -126,7 +126,7 @@
     @signInButton.toggle not user?
     @favoriteButton.toggle user?

-    if user?.finished_tutorial_or_whatever
+    if user?.project?.tutorial_done
       Subject.next() if @surface.marks.length is 0
     else
       selectTutorialSubject()


Settings close now

Settings close now

--- app/controllers/settings.coffee
+++ app/controllers/settings.coffee
@@ -23,11 +23,17 @@
     return unless @state is off
     @state = on
     @el.show(0).addClass("show")
+    setTimeout ( =>
+    @el.on "click.modal", (event) =>
+      if event.target.className.indexOf("modal") >= 0 then @hide()
+    ), 500

   hide: ->
     return unless @state is on
     @state = off
     @el.removeClass("show")
+    setTimeout ( => @el.hide(0)), 350
+    @el.off("click.modal")

 settings = null # Temp


update exclude mark

update exclude mark

--- js/umobi.offlinecache.coffee
+++ js/umobi.offlinecache.coffee
@@ -1,6 +1,10 @@
-`//>>excludeStart("umobiBuildExclude", pragmas.umobiBuildExclude)`
+###
+//>>excludeStart("umobiBuildExclude", pragmas.umobiBuildExclude)
+###
 define ['jquery','cs!umobi.core'], () ->
-  `//>>excludeEnd("umobiBuildExclude")`
+  ###
+  //>>excludeEnd("umobiBuildExclude")
+  ###
   c = window.applicationCache
   switch c.status
     # UNCACHED == 0
@@ -18,6 +22,10 @@
       return 'OBSOLETE'
     else
       return 'UKNOWN CACHE STATUS'
-  `//>>excludeStart("umobiBuildExclude", pragmas.umobiBuildExclude)`
+  ###
+  //>>excludeStart("umobiBuildExclude", pragmas.umobiBuildExclude)
+  ###
   return c
-`//>>excludeEnd("umobiBuildExclude")`
+###
+//>>excludeEnd("umobiBuildExclude")
+###


Fixed res.send for express v3

Fixed res.send for express v3

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -2705,14 +2705,14 @@
 					return next(err)
 				else
 					if opts.statusCode?
-						return res.send(content, opts.statusCode)
+						return res.send(opts.statusCode, content)
 					else
 						return res.send(content)
 		else
 			content = document.get('contentRendered') or document.get('content') or document.getData()
 			if content
 				if opts.statusCode?
-					return res.send(content, opts.statusCode)
+					return res.send(opts.statusCode, content)
 				else
 					return res.send(content)
 			else


Control rendering of trailing newlines and spaces to eliminate the invisible space hack and fix end-of-document cursor issue.

--- src/trix/dom.coffee
+++ src/trix/dom.coffee
@@ -19,14 +19,19 @@
   createContainersForText: (text) ->
     containers = []
     length = 0
+    lastString = ""

     text.eachRun (string, attributes, position) ->
       container = createContainer(string, attributes, position)
       containers.push(container)
       length += string.length
+      lastString = string

-    container = createContainer("\uFEFF", {}, length)
-    containers.push(container)
+    # Add an extra newline if the text ends with one. Without it, the cursor won't move down.
+    if lastString.match(/\n$/)
+      container = createContainer("\n", {}, length)
+      containers.push(container)
+
     containers

   getSelectedRange: ->
@@ -101,6 +106,8 @@
       .replace(/\s{2}/g, " \u00a0")
       # Replace leading space with a non-breaking space
       .replace(/^\s{1}/, "\u00a0")
+      # Replace trailing space with a non-breaking space
+      .replace(/\s{1}$/, "\u00a0")

   isWithin = (ancestor, element) ->
     while element


update

update

--- lib/middleman_ember_scaffold/template/source/app/main.coffee
+++ lib/middleman_ember_scaffold/template/source/app/main.coffee
@@ -4,7 +4,6 @@
 #= require_tree ./views
 #= require_tree ./templates
 #= require      ./routes
-#= require ./templates


fixes #120

fixes #120

--- lib/atom-ternjs-client.coffee
+++ lib/atom-ternjs-client.coffee
@@ -121,8 +121,8 @@
         end: end
     ).then (data) =>
       if data?.start
-        @manager.helper.setMarkerCheckpoint()
-        @manager.helper.openFileAndGoTo(data.start, data.file)
+        @manager.helper?.setMarkerCheckpoint()
+        @manager.helper?.openFileAndGoTo(data.start, data.file)
     , (err) ->
       console.log err


Queue failed classification saves

Queue failed classification saves

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -11,6 +11,8 @@
 SignInPrompt = require '../../partials/sign-in-prompt'
 seenThisSession = require '../../lib/seen-this-session'

+FAILED_CLASSIFICATION_QUEUE_NAME = 'failed-classifications'
+
 PROMPT_TO_SIGN_IN_AFTER = [5, 10, 25, 50, 100, 250, 500]

 SKIP_CELLECT = location?.search.match(/\Wcellect=0(?:\W|$)/)?
@@ -60,6 +62,7 @@
   getDefaultProps: ->
     query: null
     project: null
+    simulateSaveFailure: (location?.search ? '').indexOf('simulate-classification-save-failure') isnt -1

   getInitialState: ->
     workflow: null
@@ -230,22 +233,58 @@
     console?.info 'Completed classification', @state.classification
     savingClassification = if @state.demoMode
       Promise.resolve @state.classification
+    else if @props.simulateSaveFailure
+      Promise.reject new Error 'Simulated failure of classification save'
     else
       @state.classification.save()

-    savingClassification.then (classification) =>
-      if @state.demoMode
-        console?.log 'Demo mode: Did NOT save classification'
-      else
-        console?.log 'Saved classification', classification.id
-        Promise.all([
-          classification.get 'workflow'
-          classification.get 'subjects'
-        ]).then ([workflow, subjects]) ->
-          seenThisSession.add workflow, subjects
-          classification.destroy()
+    savingClassification
+      .then (classification) =>
+        if @state.demoMode
+          console?.log 'Demo mode: Did NOT save classification'
+        else
+          console?.log 'Saved classification', classification.id
+          Promise.all([
+            classification.get 'workflow'
+            classification.get 'subjects'
+          ]).then ([workflow, subjects]) ->
+            seenThisSession.add workflow, subjects
+            classification.destroy()
+        @saveAllQueuedClassifications()
+      .catch (error) =>
+        console?.warn 'Failed to save classification:', error
+        @queueClassification @state.classification
+
       classificationsThisSession += 1
       @maybePromptToSignIn()
+
+  queueClassification: (classification) ->
+    queue = JSON.parse localStorage.getItem FAILED_CLASSIFICATION_QUEUE_NAME
+    queue ?= []
+    queue.push classification
+    try
+      localStorage.setItem FAILED_CLASSIFICATION_QUEUE_NAME, JSON.stringify queue
+      console?.info 'Queued classifications:', queue.length
+    catch error
+      console?.error 'Failed to queue classification:', error
+
+  saveAllQueuedClassifications: ->
+    queue = JSON.parse localStorage.getItem FAILED_CLASSIFICATION_QUEUE_NAME
+    if queue? and queue.length isnt 0
+      console?.log 'Saving queued classifications:', queue.length
+      for classificationData in queue then do (classificationData) =>
+        apiClient.type('classifications').create(classificationData).save()
+          .then (actualClassification) =>
+            actualClassification.destroy()
+            indexInQueue = queue.indexOf classificationData
+            queue.splice indexInQueue, 1
+            try
+              localStorage.setItem FAILED_CLASSIFICATION_QUEUE_NAME, JSON.stringify queue
+              console?.info 'Saved a queued classification, remaining:', queue.length
+            catch error
+              console?.error 'Failed to update classification queue:', error
+          .catch (error) =>
+            console?.error 'Failed to save a queued classification:', error

   maybePromptToSignIn: ->
     if classificationsThisSession in PROMPT_TO_SIGN_IN_AFTER and not @props.user?


get api for login

get api for login

--- src/user/login.cjsx
+++ src/user/login.cjsx
@@ -3,6 +3,7 @@

 AccountsIframe = require './accounts-iframe-mixin'
 User  = require './model'
+api = require '../api'

 UserLogin = React.createClass

@@ -17,7 +18,7 @@

   # called by iframe when it's content is loaded and it's ready for requests
   onIframeReady: ->
-    @setState(isLoading: true)
+    # @setState(isLoading: true)
     @sendCommand('displayLogin', User.endpoints.iframe_login)

   render: ->


Drop some promise warnings with careful error catching in log subcriptions

--- lib/logs.coffee
+++ lib/logs.coffee
@@ -52,17 +52,23 @@
 			baseUrl: opts.apiUrl
 			signal: controller.signal
 		.then (stream) ->
+			# Forward request errors to the parser
+			stream.on 'error', (e) ->
+				parser.emit('error', e)
+
 			parser.on 'data', (log) ->
-				# Manually silence events after abort, as
-				# abort is not reliable in all environments
 				if not controller.signal.aborted
 					emitter.emit('line', log)
+
 			parser.on 'error', (err) ->
-				# Manually silence events after abort, as
-				# abort is not reliable in all environments
 				if not controller.signal.aborted
 					emitter.emit('error', err)
+
 			stream.pipe(parser)
+		.catch (e) ->
+			# Forward request setup errors
+			if not controller.signal.aborted
+				emitter.emit('error', err)

 		emitter.unsubscribe = ->
 			controller.abort()


:bomb: update command name

:bomb: update command name

--- lib/main.coffee
+++ lib/main.coffee
@@ -84,6 +84,7 @@
       return text

     @subscriptions.add atom.commands.add 'atom-text-editor.vim-mode-plus-search',
-      'vim-mode-plus-user:narrow-lines-from-search': => @narrow('lines', uiInput: confirmSearch())
-      'vim-mode-plus-user:narrow-search': => @narrow('search', search: confirmSearch())
-      'vim-mode-plus-user:narrow-search-current-project': =>  @narrow('search', search: confirmSearch(), currentProject: true)
+      'vim-mode-plus-user:narrow:lines': => @narrow('lines', uiInput: confirmSearch())
+      'vim-mode-plus-user:narrow:search': => @narrow('search', search: confirmSearch())
+      'vim-mode-plus-user:narrow:atom-scan': => @narrow('atom-scan', search: confirmSearch())
+      'vim-mode-plus-user:narrow:search-current-project': =>  @narrow('search', search: confirmSearch(), currentProject: true)


closes #20

closes #20

--- lib/main.coffee
+++ lib/main.coffee
@@ -47,9 +47,11 @@
             changeColor: @changeColor
           })
         @cbHandler = @colorChangeCb? app.colorChangeCb
-        tv = atom.packages.getActivePackage("tree-view")?.mainModule
-          .treeView.element
-        app.$before tv.firstChild if tv?
+        tv = atom.packages.getActivePackage("tree-view")?.mainModule?.treeView?.element
+        if tv?
+          app.$before tv.firstChild
+        else
+          throw new Error "tree-view not found, make sure the tree-view package is enabled. CTRL+ALT+R to reload"
         #if @openedFiles?.comps?.app?
           #@openedFiles.comps.app.colorPicker = @colorPicker
           #@openedFiles.comps.app.changeColor = @changeColor


Update cubic-bezier-curve.coffee

Update cubic-bezier-curve.coffee

--- lib/cubic-bezier-curve.coffee
+++ lib/cubic-bezier-curve.coffee
@@ -1,4 +1,3 @@
-atomJquery = require('atom').$
 $ = require("jquery")
 require('jquery-ui/draggable');

@@ -201,8 +200,6 @@

 	drawPoints : () ->
 		points = @points
-		console.log("Draw Points: " +points)
-		console.dir points
 		throw "Invalid points: " + points  if not points or points.length isnt 4

 		$("#P0").css "top", points[1]
@@ -256,6 +253,7 @@
 		editor = atom.workspace.getActiveEditor()
 		editor.replaceSelectedText null, =>  "cubic-bezier(" + @points.join() + ")"
 		editor.clearSelections()
+		@points = {}
 		editor.addSelectionForBufferRange
 			start:
 				column: @matcher.start


Properly show paper doll equipment when inventory modal first loads.

--- app/views/play/menu/InventoryModal.coffee
+++ app/views/play/menu/InventoryModal.coffee
@@ -140,6 +140,7 @@
     @requireLevelEquipment()
     @$el.find('.nano').nanoScroller({alwaysVisible: true})
     @onSelectionChanged()
+    @onEquipmentChanged()

   afterInsert: ->
     super()
@@ -147,7 +148,6 @@
     @canvasHeight = @$el.find('canvas').innerHeight()
     @inserted = true
     @requireLevelEquipment()
-    @onEquipmentChanged()

   #- Draggable logic


Add indent check to close bracket

Add indent check to close bracket

--- tasks/sass-format.coffee
+++ tasks/sass-format.coffee
@@ -242,6 +242,9 @@

                 indent--

+                if options.indent
+                  checkIndent(indent,text[i])
+
                 if options.blankLine.close
                   checkBlankLine(indent,text[i+1])


Log errors even without ?LR-verbose

Log errors even without ?LR-verbose

--- src/livereload.coffee
+++ src/livereload.coffee
@@ -11,11 +11,16 @@
     @pluginIdentifiers = {}

     # i can haz console?
-    @console = if @window.location.href.match(/LR-verbose/) && @window.console && @window.console.log && @window.console.error
-      @window.console
-    else
-      log:   ->
-      error: ->
+    @console =
+      if @window.console && @window.console.log && @window.console.error
+        if @window.location.href.match(/LR-verbose/)
+          @window.console
+        else
+          log:   ->
+          error: @window.console.error.bind(@window.console)
+      else
+        log:   ->
+        error: ->

     # i can haz sockets?
     unless @WebSocket = @window.WebSocket || @window.MozWebSocket
@@ -74,6 +79,8 @@
           when 'reload' then @performReload(message)
           when 'alert'  then @performAlert(message)

+    @initialized = yes
+
   on: (eventName, handler) ->
     @listeners[eventName] = handler

@@ -93,6 +100,7 @@
     alert message.message

   shutDown: ->
+    return unless @initialized
     @connector.disconnect()
     @log "LiveReload disconnected."
     @listeners.shutdown?()
@@ -100,6 +108,8 @@
   hasPlugin: (identifier) -> !!@pluginIdentifiers[identifier]

   addPlugin: (pluginClass) ->
+    return unless @initialized
+
     return if @hasPlugin(pluginClass.identifier)
     @pluginIdentifiers[pluginClass.identifier] = yes

@@ -140,6 +150,7 @@
     return

   analyze: ->
+    return unless @initialized
     return unless @connector.protocol >= 7

     pluginsData = {}


update task

update task

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -82,17 +82,20 @@
             done()
             return

-  grunt.registerTask 'build', [
-    'clean'
+  grunt.registerTask 'default', [
     'sass'
     'autoprefixer'
     'requirejs'
   ]
-  grunt.registerTask 'default', ['build']
   grunt.registerTask 'server', [
     'default'
     'browserSync:dev'
     'watch'
   ]

+  grunt.registerTask 'build', [
+    'clean'
+    'default'
+  ]
+
   return


Fix alignment

Fix alignment

--- app/views/home-page-view.coffee
+++ app/views/home-page-view.coffee
@@ -87,7 +87,7 @@
     @canvas.height = window.innerHeight

     # get attributes
-    tileSize = @gMap.get 'tileSize'
+    tileSize  = @gMap.get 'tileSize'
     numXTiles = @gMap.get 'numXTiles'
     numYTiles = @gMap.get 'numYTiles'


Add eventing to table page changes

Add eventing to table page changes

--- app/views/settings/table.coffee
+++ app/views/settings/table.coffee
@@ -19,7 +19,7 @@
     @model.tool.trigger 'next-page'

   prevPage: =>
-    @model.too.trigger 'prev-page'
+    @model.tool.trigger 'prev-page'

 module.exports = TablePageSettings


Fixed list.coffee so repo cell to be entirely clickable, all the credit goes to @bastilian

--- assets/scripts/app/views/repo/list.coffee
+++ assets/scripts/app/views/repo/list.coffee
@@ -1,14 +1,14 @@
 @Travis.reopen
   ReposView: Travis.View.extend
     templateName: 'repos/list'
-
+
   ReposListView: Em.CollectionView.extend
     elementId: 'repos'
     tagName: 'ul'
-
+
     emptyView: Ember.View.extend
       template: Ember.Handlebars.compile('
Loading
')
-
+
     itemViewClass: Travis.View.extend
       repoBinding: 'content'
       classNames: ['repo']
@@ -16,31 +16,34 @@
       selected: (->
         @get('content') == @get('controller.selectedRepo')
       ).property('controller.selectedRepo')
-
+
       color: (->
         Travis.Helpers.colorForState(@get('repo.lastBuildState'))
       ).property('repo.lastBuildState')
-
+
+      click: ->
+        @get('controller').transitionToRoute('/' + @get('repo.slug'))
+
   ReposListTabsView: Travis.View.extend
     templateName: 'repos/list/tabs'
     tabBinding: 'controller.tab'
     currentUserBinding: 'controller.currentUser.id'
-
+
     activate: (name) ->
       @get('controller').transitionToRoot()
       @get('controller').activate(name)
-
+
     classRecent: (->
       'active' if @get('tab') == 'recent'
     ).property('tab')
-
+
     classOwned: (->
       classes = []
       classes.push('active')  if @get('tab') == 'owned'
       classes.push('display-inline') if @get('currentUser')
       classes.join(' ')
     ).property('tab', 'currentUser')
-
+
     classSearch: (->
       'active' if @get('tab') == 'search'
-    ).property('tab')
+    ).property('tab')


Use _.string's `truncate` here instead

Use _.string's `truncate` here instead

--- SingularityUI/app/utils.coffee
+++ SingularityUI/app/utils.coffee
@@ -8,8 +8,7 @@
         if split.length > 1
             return "#{ split[0] }..."
         else
-            if taskId.length > 20
-                return "#{ taskId.substr(0, 20) }..."
+            return _.truncate(taskId, 20)
         return taskId

     @stringJSON: (object) ->


:lipstick:

:lipstick:

--- lib/tabs.coffee
+++ lib/tabs.coffee
@@ -4,7 +4,7 @@
 module.exports =
   configDefaults:
     showIcons: true
-    tabScrolling: if process.platform == 'linux' then true else false
+    tabScrolling: process.platform is 'linux'
     tabScrollingThreshold: 120

   activate: ->


- slight update to logging system

- slight update to logging system

--- src/xhrStore.coffee
+++ src/xhrStore.coffee
@@ -3,17 +3,20 @@
 Q = require "q"
 path = require "path"

+if detectEnv.isModule
+  Minilog=require("minilog")
+  Minilog.pipe(Minilog.backends.console.formatClean).pipe(Minilog.backends.console)
+  logger = Minilog('xhr-store')
+
 if detectEnv.isNode
   XMLHttpRequest = require("xhr2").XMLHttpRequest
+  Minilog.pipe(Minilog.backends.console.formatColor).pipe(Minilog.backends.console)

 if detectEnv.isBrowser
   XMLHttpRequest = window.XMLHttpRequest
-  #logger = Minilog('xhr-store')
-  #Minilog.enable();
+  Minilog.pipe(Minilog.backends.console.formatClean).pipe(Minilog.backends.console)
+  logger = Minilog('xhr-store')

-if detectEnv.isModule
-  logger = require('minilog')('xhr-store')
-  require('minilog').enable()

 class XHRStore
   constructor:(options)->


Make sure user.mm is set. Fixes #46

Make sure user.mm is set. Fixes #46

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -121,6 +121,7 @@

         # Otherwise, create a new DM channel ID and message it.
         @client.getUserDirectMessageChannel user.id, (channel) =>
+            user.mm ?= {}
             user.mm.dm_channel_id = channel.id
             @client.postMessage(str, channel.id) for str in strings

@@ -158,6 +159,7 @@

         # Otherwise, create a new DM channel ID and message it.
         @client.getUserDirectMessageChannel user.id, (channel) =>
+            user.mm ?= {}
             user.mm.dm_channel_id = channel.id
             postData.channel_id = channel.id
             @client.customMessage(postData, postData.channel_id)
@@ -180,6 +182,7 @@
         if msg.data.channel_type == 'D'
           if !///^@?#{@robot.name} ///i.test(text) # Direct message
             text = "#{@robot.name} #{text}"
+          user.mm ?= {}
           user.mm.dm_channel_id = mmPost.channel_id
         @robot.logger.debug 'Text: ' + text


* closure_compile_with_sandbox_cache - fix browser mode problems

  - app.coffee tests pass
  - consolidate.js tests pass

--- src/view.coffee
+++ src/view.coffee
@@ -28,8 +28,9 @@
   ###
   __ = "  "
   """
-#{__}if not toffee?            then toffee = {}
-#{__}if not toffee.templates   then toffee.templates = {}
+
+#{__}if not toffee? then toffee = {}
+#{__}if not toffee.templates then toffee.templates = {}
 #{__}
 #{__}toffee.states = #{JSON.stringify states}
 #{__}
@@ -80,6 +81,7 @@
   ###
   __ = "  "
   """
+# keep this, for proper indenting
 #{__}toffee.__print = (locals, o) ->
 #{__}  if locals.__toffee.state is toffee.states.COFFEE
 #{__}    locals.__toffee.out.push o
@@ -268,7 +270,10 @@
       if not @error
         d = Date.now()
         try
-          @javaScript = coffee.compile c, {bare: true}
+          opts = { bare: true }
+          if @browserMode
+            opts.bare = false
+          @javaScript = coffee.compile c, opts
         catch e
           @error = new toffeeError @, errorTypes.COFFEE_COMPILE, e
         if @minimize and not @error
@@ -486,8 +491,7 @@
     ___  = @_tabAsSpaces()
     __ = "  "
     """
-_TMPL_ = (__toffee_run_input) ->
-#{if @browserMode then '' else getCommonHeaders false, @autoEscape }
+#{if @browserMode then '' else ('_TMPL_ = (__toffee_run_input) ->' + (getCommonHeaders false, @autoEscape)) }
 #{__}tmpl = toffee.templates["#{@bundlePath}"]  =
 #{__}  bundlePath: "#{@bundlePath}"
 #{__}tmpl.render = tmpl.pub = (__locals) ->


Grid autoWidth/Height bug fixed

Grid autoWidth/Height bug fixed

--- impl/base/utils/grid.coffee
+++ impl/base/utils/grid.coffee
@@ -235,12 +235,12 @@

 onWidthChange = (oldVal) ->
 	if not @_impl.updatePending
-		@_impl.autoWidth = @_width is 0 and oldVal isnt -1
+		@_impl.autoWidth = @_effectItem._width is 0 and oldVal isnt -1
 	updateSize.call @

 onHeightChange = (oldVal) ->
 	if not @_impl.updatePending
-		@_impl.autoHeight = @_height is 0 and oldVal isnt -1
+		@_impl.autoHeight = @_effectItem._height is 0 and oldVal isnt -1
 	updateSize.call @

 enableChild = (child) ->


(- new cli) Support arbitrary expressions at the command-line

--- Paws/Executables/paws.js.coffee
+++ Paws/Executables/paws.js.coffee
@@ -42,6 +42,11 @@
 {{#flag}}   --help{{/flag}}:        Show usage information
 {{#flag}}   --version{{/flag}}:     Show version information

+{{#flag}}   -e "EXPR", --expression="EXPR"{{/flag}}:
+               For {{#b}}parse{{/b}} and {{#b}}start{{/b}}, allows you to provide a cPaws expression at the
+               command-line, to substitute for a file. (If at least one {{#c}}--expression{{/c}} is
+               included, the required filname for those operations may be omitted.)
+

          Paws.js also accepts several environment variables, in the form:
 {{#pre}}   > VAR=value paws.js ...{{/pre}}
@@ -106,6 +111,10 @@
    process.on 'exit', exit
    process.on 'SIGINT', -> process.exit 0

+   # TODO: More robust file resolution
+   readFilesAsync = (files)->
+      bluebird.map files, (file)->
+         fs.readFileAsync(file, 'utf8').then (source)-> { from: file, code: source }

 # ---- --- ---- --- ----

@@ -118,25 +127,36 @@
    if (argf.version)
       return version()

-   if not argv[1]? then argv.unshift 'start'
-   switch argv[0]
-
-      when 'parse'
-         # TODO: More robust file resolution
-         return fs.readFileAsync(argv[1], 'utf8').then (source)->
-            expr = Paws.parser.parse(source, {root: true})
-            out.write expr.serialize() + "\n"
-
-      when 'start'
-         return fs.readFileAsync(argv[1], 'utf8').then (source)->
-            expr = Paws.parser.parse(source, {root: true})
-
-            here = new Paws.reactor.Unit
-            here.start() unless argf.start == false
-            here.stage new Execution expr
-
-   # If we get nothing we understand, we just print the usage data and terminate.
-   help()
+   sources = _([argf.e, argf.expr, argf.expression])
+      .flatten().compact().map (expression)-> { from: expression, code: expression }
+      .value()
+
+   help() if _.isEmpty argv[0]
+   choose = -> switch operation = argv.shift()
+
+      when 'pa', 'parse'
+         readFilesAsync(argv).then (files)->
+            sources.push files...
+            _.forEach sources, (source)->
+               Paws.info "-- Parse-tree for '#{T.bold source.from}':"
+               expr = Paws.parser.parse source.code, root: true
+               out.write expr.serialize() + "\n"
+
+      when 'st', 'start'
+         readFilesAsync(argv).then (files)->
+            sources.push files...
+            _.forEach sources, (source)->
+               Paws.info "-- Staging '#{T.bold source.from}' from the command-line ..."
+               expr = Paws.parser.parse source.code, root: true
+
+               here = new Paws.reactor.Unit
+               here.stage new Execution expr
+
+               here.start() unless argf.start == false
+
+      else argv.unshift('start', operation) and choose()
+
+   choose()

 prettify.skipNodeFiles()
 bluebird.onPossiblyUnhandledRejection (error)->


Document public MarkerLayer methods

Document public MarkerLayer methods

--- src/marker-layer.coffee
+++ src/marker-layer.coffee
@@ -28,6 +28,10 @@
       result[id].range = Range.deserialize(markerSnapshot.range)
     result

+  ###
+  Section: Lifecycle
+  ###
+
   constructor: (@delegate, @id) ->
     @index = new MarkerIndex
     @markersById = {}
@@ -35,6 +39,7 @@
     @nextMarkerId = 0
     @destroyed = false

+  # Public: Remove the {MarkerLayer} from the {TextBuffer}
   destroy: ->
     @destroyed = true
     @delegate.markerLayerDestroyed(this)
@@ -46,18 +51,28 @@
     @destroyed

   ###
-  Section: TextBuffer API
-  ###
-
+  Section: Public interface
+  ###
+
+  # Public: Get an existing marker by its id.
   getMarker: (id) ->
     @markersById[id]

+  # Public: Get all existing markers on the buffer.
+  #
+  # Returns an {Array} of {Marker}s.
   getMarkers: ->
     marker for id, marker of @markersById

+  # Public: Get the number of markers in the buffer.
+  #
+  # Returns a {Number}.
   getMarkerCount: ->
     Object.keys(@markersById).length

+  # Public: Find markers conforming to the given parameters.
+  #
+  # See the documentation for {TextBuffer::findMarkers}.
   findMarkers: (params) ->
     markerIds = null

@@ -99,13 +114,23 @@
       result.push(marker) if marker.matchesParams(params)
     result.sort (a, b) -> a.compare(b)

+  # Public: Create a marker with the given range.
+  #
+  # See the documentation for {TextBuffer::markRange}
   markRange: (range, options={}) ->
     @createMarker(@delegate.clipRange(range), Marker.extractParams(options))

+  # Public: Create a marker with the given position and no tail.
+  #
+  # See the documentation for {TextBuffer::markPosition}
   markPosition: (position, options={}) ->
     options.tailed ?= false
     position = @delegate.clipPosition(position)
     @markRange(new Range(position, position), options)
+
+  ###
+  Section: Private - TextBuffer interface
+  ###

   splice: (start, oldExtent, newExtent) ->
     end = start.traverse(oldExtent)
@@ -189,7 +214,7 @@
     return

   ###
-  Section: Marker interface
+  Section: Private - Marker interface
   ###

   markerUpdated: ->
@@ -228,7 +253,7 @@
     marker

   ###
-  Section: Private
+  Section: Internal
   ###

   addMarker: (id, range, params) ->


* [bug] updatePermsByRefs can not work properly.

--- src/mixins/roles.coffee
+++ src/mixins/roles.coffee
@@ -273,7 +273,7 @@
               return unless aItem
               aItem.getPerms()
               .then (aPerms)->
-                aItem.updateAttribute permsFieldName, aPerms if aPerms and aPerms.length
+                aItem.updateAttribute permsFieldName, aPerms if isObject(aPerms) and Object.keys(aPerms).length
       else
         Promise.resolve()


*BREAKING*: Return collaborator apps with models.applications.getAll

Change-type: major
Connects-to: #476
Signed-off-by: Cameron Diver <1d572acbfa68c7c6e541c7b840d6b622e5c0dc91@resin.io>
Signed-off-by: Tim Perry <5ee0edb9e2229c0838f1959779f1949031de0123@resin.io>

--- lib/models/application.coffee
+++ lib/models/application.coffee
@@ -103,7 +103,11 @@
 					mergePineOptions
 						$orderby: 'app_name asc'
 						$filter:
-							user: userId
+							$or:
+								user: userId
+								includes__user: $any:
+									$alias: 'x'
+									$expr: x: user: userId
 					, options

 		.map (application) ->


Cleanup nsync helper a bit

Cleanup nsync helper a bit

--- lib/nsync/nsync-helper.coffee
+++ lib/nsync/nsync-helper.coffee
@@ -99,69 +99,73 @@
     nsync.save(path, content)

 module.exports = helper = (activationState) ->
-  disposables = new CompositeDisposable
+  composite = new CompositeDisposable

-  disposables.add atomHelper.addCommands
-    'learn-ide:save': onSave
-    'learn-ide:save-as': unimplemented
-    'learn-ide:save-all': unimplemented
-    'learn-ide:import': onImport
-    'learn-ide:file-open': unimplemented
-    'learn-ide:add-project': unimplemented
+  disposables = [
+    atomHelper.addCommands
+      'learn-ide:save': onSave
+      'learn-ide:save-as': unimplemented
+      'learn-ide:save-all': unimplemented
+      'learn-ide:import': onImport
+      'learn-ide:file-open': unimplemented
+      'learn-ide:add-project': unimplemented

-  disposables.add nsync.onDidConfigure ->
-    atomHelper.addOpener (uri) ->
-      fs.stat uri, (err, stats) ->
-        if err? and nsync.hasPath(uri)
-          nsync.open(uri)
+    nsync.onDidConfigure ->
+      atomHelper.addOpener (uri) ->
+        fs.stat uri, (err, stats) ->
+          if err? and nsync.hasPath(uri)
+            nsync.open(uri)

-  disposables.add nsync.onDidSetPrimary ({localPath, expansionState}) ->
-    atomHelper.updateProject(localPath, expansionState)
+    nsync.onDidSetPrimary ({localPath, expansionState}) ->
+      atomHelper.updateProject(localPath, expansionState)

-  disposables.add nsync.onWillLoad ->
-    secondsTillNotifying = 2
+    nsync.onWillLoad ->
+      secondsTillNotifying = 2

-    setTimeout ->
-      unless nsync.hasPrimaryNode()
-        atomHelper.loading()
-    , secondsTillNotifying * 1000
+      setTimeout ->
+        unless nsync.hasPrimaryNode()
+          atomHelper.loading()
+      , secondsTillNotifying * 1000

-  disposables.add nsync.onDidDisconnect (detail) ->
-    if detail?
-      atomHelper.error 'Learn IDE: you are not connected!', {detail}
-    else
-      atomHelper.disconnected()
-      atomHelper.emit('learn-ide-tree:connection', {isConnected: false})
+    nsync.onDidDisconnect (detail) ->
+      if detail?
+        atomHelper.error 'Learn IDE: you are not connected!', {detail}
+      else
+        atomHelper.disconnected()
+        atomHelper.emit('learn-ide-tree:connection', {isConnected: false})

-  disposables.add nsync.onWillConnect ->
-    atomHelper.connecting()
+    nsync.onWillConnect ->
+      atomHelper.connecting()

-  disposables.add nsync.onDidConnect ->
-    atomHelper.emit('learn-ide-tree:connection', {isConnected: true})
-    atomHelper.connected()
+    nsync.onDidConnect ->
+      atomHelper.emit('learn-ide-tree:connection', {isConnected: true})
+      atomHelper.connected()

-  disposables.add nsync.onDidReceiveCustomCommand (payload) ->
-    executeCustomCommand(payload)
+    nsync.onDidReceiveCustomCommand (payload) ->
+      executeCustomCommand(payload)

-  disposables.add nsync.onDidChange (path) ->
-    parent = _path.dirname(path)
-    atomHelper.reloadTreeView(parent, path)
-    atomHelper.updateTitle()
+    nsync.onDidChange (path) ->
+      parent = _path.dirname(path)
+      atomHelper.reloadTreeView(parent, path)
+      atomHelper.updateTitle()

-  disposables.add nsync.onDidUpdate (path) ->
-    atomHelper.saveEditor(path)
+    nsync.onDidUpdate (path) ->
+      atomHelper.saveEditor(path)

-  disposables.add atomHelper.observeTextEditors (editor) ->
-    disposables.add editor.onDidSave (e) ->
-      onEditorSave(e)
+    atomHelper.observeTextEditors (editor) ->
+      composite.add editor.onDidSave (e) ->
+        onEditorSave(e)

-  disposables.add atomHelper.onDidActivatePackage (pkg) ->
-    if pkg.name is 'find-and-replace'
-      projectFindView = pkg.mainModule.projectFindView
-      resultModel = projectFindView.model
+    atomHelper.onDidActivatePackage (pkg) ->
+      if pkg.name is 'find-and-replace'
+        projectFindView = pkg.mainModule.projectFindView
+        resultModel = projectFindView.model

-      disposables.add resultModel.onDidReplacePath ({filePath}) ->
-        onFindAndReplace(filePath)
+        composite.add resultModel.onDidReplacePath ({filePath}) ->
+          onFindAndReplace(filePath)
+  ]
+
+  disposables.forEach (disposable) -> composite.add(disposable)

   atomHelper.getToken().then (token) ->
     nsync.configure
@@ -173,5 +177,5 @@
         opts:
           spawn: atomHelper.spawn

-  return disposables
+  return composite


Grab one subject at a time from the queue

--- app/classifier.cjsx
+++ app/classifier.cjsx
@@ -32,21 +32,6 @@
         @subjects.flush()
         @subjects.fetch()
           .then @nextSubject
-
-  componentDidUpdate: ->
-    return unless @subjects.current?
-    container = @refs.scrollContainer?.getDOMNode()
-    subject_node = @refs["subject#{@subjects.current.id}"]?.getDOMNode()
-    return unless container? && subject_node?
-
-    container.scrollTop -= subject_node.scrollHeight
-    distance = subject_node.offsetTop / 20
-
-    move_subject = =>
-      container.scrollTop = container.scrollTop + distance
-      setTimeout move_subject, 50 unless container.scrollTop > subject_node.offsetTop - 50
-
-    setTimeout move_subject, 50

   render: ->
     <ClassificationTask onChange={@onChangeAnnotation} onFinish={@onFinishPage}>
@@ -77,13 +62,7 @@
     @nextSubject()

   nextSubject: ->
-    currentSubjects = @state.currentSubjects
-    if currentSubjects.length is 0
-      currentSubjects.push @subjects.next(), @subjects.queue[0]
-    else
-      @subjects.next()
-      currentSubjects.push @subjects.queue[0]
-      currentSubjects.shift() if currentSubjects.length > 3
+    currentSubjects = [@subjects.next()]
     # create a new classification here
     @classifications.create [@subjects.current]
     # remove undefined or null subjects from currentSubjects


handling the exception when the text() or html() call with a non-exist element

--- src/quo.output.coffee
+++ src/quo.output.coffee
@@ -4,12 +4,12 @@
         if value or $$.toType(value) is "number"
           @each -> @textContent = value
         else
-            @[0].textContent
+            (if @length > 0 then @[0].textContent else '')

     $$.fn.html = (value) ->
         type = $$.toType(value)
         if type is "undefined"
-            @[0].innerHTML
+            (if @length > 0 then @[0].innerHTML else)
         else
             @each ->
                 if type is "string"


修复图片链接解析多出问号

修复图片链接解析多出问号

--- tpl/utils/parser.coffee
+++ tpl/utils/parser.coffee
@@ -8,19 +8,20 @@
     "#{$1}#{href}#{$3}#{$4}"
   )

-imgParser = (content, chapter, prefix, v) ->
-  content.replace(//g, ($0, $1, $2) ->
+imgParser = (content, chapter, prefix, version) ->
+  content.replace(/]+)(.*?)>/g, ($0, $1, $2) ->
     img = '<img class=img-responsive src='
-    v = "?#{v}"
+    v = "?#{version}"
     if $1.indexOf('//') < 0
-      "#{img}#{prefix}#{chapter}#{$1}#{v}#{$2}>"
+      img += "#{prefix}#{chapter}#{$1}#{v}#{$2}>"
     else if $1.indexOf('../') >= 0
-      "#{img}#{$1.substr(3)}#{v}#{$2}>"
+      img += "#{$1.replace('../', '')}#{v}#{$2}>"
     else
-      "#{img}#{$1}#{$2}>"
+      img += "#{$1}#{$2}>"
+    img
   )

-module.exports = (content, section, prefix, v) ->
+module.exports = (content, section, prefix, version) ->
   chapter = section.split('/')
   chapter = if chapter.length > 1 then chapter[0] + '/' else ''
-  linkParser(imgParser(content, chapter, prefix, v), chapter)
+  linkParser(imgParser(content, chapter, prefix, version), chapter)


:fire: Remove check for autocomplete-plus

--- lib/autocomplete-hack.coffee
+++ lib/autocomplete-hack.coffee
@@ -3,9 +3,7 @@
 module.exports =
   Hack: null
   activate:->
-    if typeof atom.packages.getLoadedPackage("autocomplete-plus") is 'undefined'
-      return atom.notifications.addError "[Hack] autocomplete-plus Package not found, but is required for autocomplete.", dismissable: true
-    else if typeof atom.packages.getLoadedPackages("atom-hack") is 'undefined'
+    if typeof atom.packages.getLoadedPackages("atom-hack") is 'undefined'
       return atom.notifications.addError "[Hack] atom-hack Package not found, but is required for autocomplete", dismissable: true
   consumeHack: (Hack)->
     @Hack = Hack
@@ -91,4 +89,4 @@
             ToReturn.sort (a,b)=>
               b.score - a.score
             Resolve(ToReturn)
-    return Provider+    return Provider


Avoid username check after registration is started

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -346,7 +346,7 @@
         defaultValue : familyName
         label        : 'Last Name'

-
+    USERNAME_VALID = no
     @signupModal = new KDModalViewWithForms
       cssClass                        : 'extra-info password'
       width                           : 360
@@ -397,16 +397,19 @@

   usernameCheck:(input, event, delay=800)->

-    return if event?.which is 9
-    return if input.getValue().length < 4
+    return  if event?.which is 9
+    return  if input.getValue().length < 4

     KD.utils.killWait usernameCheckTimer
     usernameCheckTimer = null
+
     input.setValidationResult "usernameCheck", null
     username = input.getValue()

     if input.valid
       usernameCheckTimer = KD.utils.wait delay, =>
+        return  unless @signupModal?
+
         $.ajax
           url         : "/Validate/Username/#{username}"
           type        : 'POST'
@@ -470,8 +473,8 @@
       formData.firstName       = firstName?.getValue()
       formData.lastName        = lastName?.getValue()

+      @signupModal.destroy()
       @doRegister formData, @registerForm
-      @signupModal.destroy()
     else if usernameCheckTimer?
       @signupModal.setOption 'pendingSubmit', yes


return error from update if there is one, do not send config activity if update fails

--- lib/updateIfAuthorized.coffee
+++ lib/updateIfAuthorized.coffee
@@ -14,5 +14,7 @@

       clearCache toDevice.uuid, =>
         newDevice = new Device(uuid: toDevice.uuid)
-        newDevice.update params, callback
-        sendConfigActivity query.uuid
+        newDevice.update params, (error) =>
+          return callback error if error?
+          sendConfigActivity query.uuid
+          callback()


Hide setup WiFi from menu

Hide setup WiFi from menu

--- lib/utils/menu-manager.coffee
+++ lib/utils/menu-manager.coffee
@@ -52,9 +52,9 @@
         label: 'Identify device...',
         command: "#{packageName()}:identify-device"
       },{
-        label: "Setup device's WiFi...",
-        command: "#{packageName()}:setup-wifi"
-      },{
+      #   label: "Setup device's WiFi...",
+      #   command: "#{packageName()}:setup-wifi"
+      # },{
       #   label: 'Flash device via USB',
       #   command: "#{packageName()}:try-flash-usb"
       # },{


Fixed the jQuery dep.

Fixed the jQuery dep.

--- src/rivescript.coffee
+++ src/rivescript.coffee
@@ -393,32 +393,33 @@
   # Load a file using ajax. DO NOT CALL THIS DIRECTLY.
   _ajaxLoadFile: (loadCount, file, onSuccess, onError) ->

+    self = @
     xhr = new XMLHttpRequest();
     xhr.open "GET", file, true
     xhr.onreadystatechange  = () ->
-      console.log(xhr.readyState);

       if xhr.readyState is 4
-        if xhr.status is 200
-            console.log('we hebben status 200');
-
-            @say "Loading file #{file} complete."
-
-            # Parse it!
-            @parse file, data, onError
-
-            # Log that we've received this file.
-            delete @_pending[loadCount][file]
-
-            # All gone?
-            if Object.keys(@_pending[loadCount]).length is 0
-              if typeof(onSuccess) is "function"
-                onSuccess.call undefined, loadCount
-
-          else
-            @say "Ajax error! #{textStatus}; #{errorThrown}"
-            if typeof(onError) is "function"
-              onError.call undefined, textStatus, loadCount
+        if xhr.status in [0, 200]
+
+          self.say "Loading file #{file} complete."
+
+          # Parse it!
+          self.parse file, xhr.responseText, onError
+
+          # Log that we've received this file.
+          delete self._pending[loadCount][file]
+
+          # All gone?
+          if Object.keys(self._pending[loadCount]).length is 0
+            if typeof(onSuccess) is "function"
+              onSuccess.call undefined, loadCount
+
+        else
+          self.say "Ajax error! #{xhr.statusText}; #{xhr.status}"
+          if typeof(onError) is "function"
+            onError.call undefined, xhr.statusText, loadCount
+
+    xhr.send null

   # Load a file using node. DO NOT CALL THIS DIRECTLY.


Fixed typo

Fixed typo

--- lib/zenproxy.fileserve.coffee
+++ lib/zenproxy.fileserve.coffee
@@ -162,3 +162,7 @@
   xwd   : "image/x-xwindowdump"
   xyz   : "chemical/x-pdb"
   zip   : "application/zip"
+
+# -- Private methods -----------------------------------------------------------
+__time = (value) -> (new Date(value)).getTime()
+


simplified font class

simplified font class

--- src/scene/font.coffee
+++ src/scene/font.coffee
@@ -27,18 +27,17 @@

     @invalid_char = @chars[String.fromCharCode(end)]

-
+    @linesize   = @size << 1
+    @tabsize    = @size << 2
+
   # Public: Centers text inside the given rect.
   #
   # ro - {Fz2D.Rect}, {Fz2D.Object} or {Fz2D.Group}
   # text - text to measure
-  # size - text size (default: font size)
-  # line_spacing - desired line spacing (default: 2 * size)
-  # word_spacing - desired word spacing (default: size)
   #
   # Returns a {Fz2D.Rect}.
-  centerText: (ro, text, size=@size, line_spacing=size+size, word_spacing=size) ->
-    m = @measureText(text, size, line_spacing, word_spacing)
+  centerText: (ro, text) ->
+    m = @measureText(text)

     new Fz2D.Rect(ro.x + ((ro.w - m.w) >> 1),
                   ro.y + ((ro.h - m.h) >> 1)
@@ -48,12 +47,9 @@
   # Public: Measures text.
   #
   # text - text to measure
-  # size - text size (default: font size)
-  # line_spacing - desired line spacing (default: 2 * size)
-  # word_spacing - desired word spacing (default: size)
   #
   # Returns a {Fz2D.Rect}.
-  measureText: (text, size=@size, line_spacing=size+size, word_spacing=size) ->
+  measureText: (text) ->
     w = 0
     h = 0
     maxWidth = 0
@@ -61,24 +57,26 @@
     for c in text
       switch c
         when ' '
-          w += word_spacing
+          w += @size
           continue
         when '\n'
           maxWidth = w if w > maxWidth
           w = 0
-          h += line_spacing
+          h += @linesize
           continue
         when '\t'
-          w += 3 * word_spacing
+          w += @tabsize
           continue

-      w += size
+      char = @chars[c] || @invalid_char
+
+      w += char.w

     if w > 0 and maxWidth == 0
       maxWidth = w

     if maxWidth > 0 and h == 0
-      h = size
+      h = @size

     new Fz2D.Rect(0, 0, maxWidth, h)

@@ -88,32 +86,29 @@
   # text - text to measure
   # x - position on the X axis
   # y - position on the Y axis
-  # size - text size (default: font size)
-  # line_spacing - desired line spacing (default: 2 * size)
-  # word_spacing - desired word spacing (default: size)
   #
   # Returns a last position.
-  drawText: (ctx, text, x, y, size=@size, line_spacing=size+size, word_spacing=size) ->
+  drawText: (ctx, text, x, y) ->
     xx = x
     yy = y

     for c in text
       switch c
         when ' '
-          xx += word_spacing
+          xx += @size
           continue
         when '\n'
           xx = x
-          yy += line_spacing
+          yy += @linesize
           continue
         when '\t'
-          xx += 3 * word_spacing
+          xx += @tabsize
           continue

       char = @chars[c] || @invalid_char

-      ctx.draw(@texture, char.x, char.y, char.w, char.h, xx, yy, size, size)
+      ctx.draw(@texture, char.x, char.y, char.w, char.h, xx, yy, char.w, char.h)

-      xx += size
+      xx += char.w

     xx


Provider Google:[backend] create and fetchAvailable implementations

--- workers/social/lib/social/models/computeproviders/google.coffee
+++ workers/social/lib/social/models/computeproviders/google.coffee
@@ -3,4 +3,88 @@
 module.exports = class Google extends ProviderInterface

   @ping = (client, callback)->
-    callback null, "Google. #{ client.connection.delegate.profile.nickname }!"+    callback null, "Google. #{ client.connection.delegate.profile.nickname }!"
+
+
+  @create = (client, options, callback)->
+
+    { credential, name } = options
+
+    credential.fetchData (err, credData)->
+
+      if err?
+        return new KodingError "Failed to fetch credential"
+
+      callback null,
+        {
+          "type": "googlecompute",
+          "bucket_name": "my-project-packer-images",
+          "client_secrets_file": credData.meta.clientSecretsContent,
+          "private_key_file": credData.meta.privateKeyContent,
+          "project_id": credData.meta.projectId,
+          "source_image": "debian-7-wheezy-v20131014",
+          "zone": "us-central1-a"
+        }
+
+
+  @fetchAvailable = (client, options, callback)->
+
+    callback null, [
+      {
+        name  : "n1-standard-1"
+        title : "N1 Standard 1"
+        spec  : {
+          cpu : 1, ram: 3.75, storage: "n/a"
+        }
+        price : "$0.070 per Hour"
+      }
+      {
+        name  : "n1-standard-2"
+        title : "N1 Standard 2"
+        spec  : {
+          cpu : 2, ram: 7.5, storage: "n/a"
+        }
+        price : "$0.140 per Hour"
+      }
+      {
+        name  : "n1-standard-4"
+        title : "N1 Standard 4"
+        spec  : {
+          cpu : 4, ram: 15, storage: "n/a"
+        }
+        price : "$0.280 per Hour"
+      }
+      {
+        name  : "n1-standard-8"
+        title : "N1 Standard 8"
+        spec  : {
+          cpu : 8, ram: 30, storage: "n/a"
+        }
+        price : "$0.560 per Hour"
+      }
+      {
+        name  : "n1-highmem-2"
+        title : "N1 Highmem 2"
+        spec  : {
+          cpu : 2, ram: 13, storage: "n/a"
+        }
+        price : "$0.164 per Hour"
+      }
+      {
+        name  : "n1-highmem-4"
+        title : "N1 Highmem 4"
+        spec  : {
+          cpu : 4, ram: 26, storage: "n/a"
+        }
+        price : "$0.328 per Hour"
+      }
+      {
+        name  : "n1-highmem-8"
+        title : "N1 Highmem 8"
+        spec  : {
+          cpu : 8, ram: 52, storage: "n/a"
+        }
+        price : "$0.656 per Hour"
+      }
+
+    ]


minor reorders in the visual core

minor reorders in the visual core

--- library/membrane/bower.coffee
+++ library/membrane/bower.coffee
@@ -61,43 +61,6 @@
     # takes place. See the `Archetype::composition` hook definition
     # for more information. Keys are names, values can be anything.
     @COMPOSITION_EXPORTS = bowerings: yes
-
-    # Install the specified packages via Bower into the specific
-    # location within the system that is figured out automatically.
-    # All packages installed via Bower will be served as the static
-    # assets, by using the `pub` env dir. The package installation
-    # is per service and automatically will be included in `prelude`.
-    @bower: (target, entry, options={}) ->
-        ent = "an entrypoint has to be a valid string"
-        noTarget = "target must be a Bower package spec"
-        noOptions = "options must be a plain JS object"
-        assert previous = this.bowerings or new Array()
-        assert previous = try _.unique(previous) or null
-        assert _.isString(entry or null), ent if entry
-        assert _.isObject(options or null), noOptions
-        assert _.isString(target or null), noTarget
-        return this.bowerings = previous.concat
-            options: options or Object()
-            entry: entry or undefined
-            target: target.toString()
-
-    # Either get or set the bower sink directory name. If no args
-    # supplied the method will return the automatically deduced the
-    # bower sink. If you supply an argument the method will set it
-    # as a bower sink and later will return it, unless overriden by
-    # the global configuration. See the implementation for the info.
-    @bowerSink: (sink) ->
-        assert hash = try crypto.createHash("md5") or 0
-        identity = try this.identify().underline or null
-        assert id = hash.update(@identify()).digest "hex"
-        automatic = => try global or this.$bowerSink or id
-        notify = "Bower sink directory for %s set to %s"
-        global = nconf.get "bower:globalSinkDirectory"
-        return automatic() if arguments.length is 0
-        assert _.isString(sink), "has to be a string"
-        assert not _.isEmpty(sink), "got empty sink"
-        logger.debug notify.cyan, identity, sink
-        return @$bowerSink = try sink.toString()

     # A hook that will be called prior to registering the service
     # implementation. Please refer to this prototype signature for
@@ -203,3 +166,40 @@
             ext = (fxt) -> path.extname(file) is fxt
             context.scripts.push file if ext ".js"
             context.sheets.push file if ext ".css"
+
+    # Either get or set the bower sink directory name. If no args
+    # supplied the method will return the automatically deduced the
+    # bower sink. If you supply an argument the method will set it
+    # as a bower sink and later will return it, unless overriden by
+    # the global configuration. See the implementation for the info.
+    this.bowerSink = this.bowerDirectory = (sink) ->
+        assert hash = try crypto.createHash("md5") or 0
+        identity = try this.identify().underline or null
+        assert id = hash.update(@identify()).digest "hex"
+        automatic = => try global or this.$bowerSink or id
+        notify = "Bower sink directory for %s set to %s"
+        global = nconf.get "bower:globalSinkDirectory"
+        return automatic() if arguments.length is 0
+        assert _.isString(sink), "has to be a string"
+        assert not _.isEmpty(sink), "got empty sink"
+        logger.debug notify.cyan, identity, sink
+        return @$bowerSink = try sink.toString()
+
+    # Install the specified packages via Bower into the specific
+    # location within the system that is figured out automatically.
+    # All packages installed via Bower will be served as the static
+    # assets, by using the `pub` env dir. The package installation
+    # is per service and automatically will be included in `prelude`.
+    @bower: (target, entry, options={}) ->
+        ent = "an entrypoint has to be a valid string"
+        noTarget = "target must be a Bower package spec"
+        noOptions = "options must be a plain JS object"
+        assert previous = this.bowerings or new Array()
+        assert previous = try _.unique(previous) or null
+        assert _.isString(entry or null), ent if entry
+        assert _.isObject(options or null), noOptions
+        assert _.isString(target or null), noTarget
+        return this.bowerings = previous.concat
+            options: options or Object()
+            entry: entry or undefined
+            target: target.toString()


better naming, simpler lock-down flow

better naming, simpler lock-down flow

--- static/index.coffee
+++ static/index.coffee
@@ -1,5 +1,5 @@
 $ () ->
-  currentContent = ''
+  contentSrc = ''
   $window = $ window
   $body = $ window.document.body
   $theme = $ '#theme'
@@ -20,10 +20,10 @@

   setDomContent = (content) ->
     if 'innerText' of $editorCode[0]
-      $editorCode[0].innerText = content
+      contentSrc = $editorCode[0].innerText = content
     else
       content = content.replace
-      $editorCode[0].innerHTML = he.encode content
+      contentSrc = $editorCode[0].innerHTML = he.encode content

   wantsToEdit = (evt) ->
     metaKey = evt.ctrlKey
@@ -48,7 +48,7 @@

   cleanupPaste = () ->
     # erase any styling
-    setDomContent getDomContent()
+    setContent getDomContent()

   scheduleCleanupPaste = (evt) ->
     setTimeout cleanupPaste, 100
@@ -86,10 +86,10 @@
       method = 'POST'
       url = 'tastes/'

-    currentContent = getDomContent().trim().replace(/\s+\n/g, '\n')
-    currentContent += '\n'  if currentContent.length
-    setDomContent currentContent  if currentContent isnt getDomContent()
-    return true  unless currentContent.length
+    contentSrc = getDomContent().trim().replace(/\s+\n/g, '\n')
+    contentSrc += '\n'  if contentSrc.length
+    setContent contentSrc  if contentSrc isnt getDomContent()
+    return true  unless contentSrc.length

     always = () ->
       edit()
@@ -102,30 +102,27 @@

     fail = () ->
       $linenosCode.html "Failed to save".replace /(.)/g, '$1
'
+      edit()

     lock null, 'Saving...'
     $.ajax({
       method
       url
       contentType: 'application/octet-stream'
-      data: currentContent
+      data: currentSrc
     }).always(always).done(done).fail(fail)
     false

-  edit = (content) ->
-    content ?= getDomContent()
-    $list.hide()
-    $linenosCode.html "Esc - #{metaKeyName}+s - Shift+#{metaKeyName}+s".replace /(.)/g, '$1
'
-    $editorCode.html content
-    $editorCode.attr('contentEditable', 'true').focus()
-    $editorCode.on 'blur', keepFocus
+  edit = (content = contentSrc) ->
+    $content.html content
+    $content.attr('contentEditable', 'true').focus()
+    $content.on 'blur', keepFocus
     $body.addClass 'editing'
     $body.off 'keydown', disableSave
     $body.on 'keydown', maybeSave
     $body.on 'keydown', maybeCancelEditing

-  lock = (content, lines = []) ->
-    content ?= getDomContent()
+  lock = (content = contentSrc, lines = [], language) ->
     if Array.isArray lines
       if lines.length is 0
         linenosCount = content.split('\n').length
@@ -185,19 +182,16 @@
       filename = filename.join '.'

     done = (body, status, xhr) ->
-      currentContent = body
+      setContent body
       if language?
         try
-          high = hljs.highlight language, currentContent
+          high = hljs.highlight language, body
         catch e
-          high = {value: currentContent}
+          high = {value: body}
       else
-        high = hljs.highlightAuto currentContent
+        high = hljs.highlightAuto body
         if high.language?
           history.replaceState undefined, undefined, "\##{filename}.#{high.language}"
-          # window.location.hash = "#{filename}.#{high.language}"
-          # return
-
       lock high.value

     fail = () ->


update api paths in spec

update api paths in spec

--- shared/test/model/notifications.spec.coffee
+++ shared/test/model/notifications.spec.coffee
@@ -18,11 +18,11 @@
     URLs.reset()

   it 'polls when URL is set', ->
-    URLs.update(accounts_api_url: 'http://localhost:2999')
+    URLs.update(accounts_api_url: 'http://localhost:2999/api')
     Notifications.startPolling(@windowImpl)
     expect(Poller::poll).to.have.callCount(1)
     expect(Poller::setUrl.lastCall.args).to.deep.equal(['http://localhost:2999/api/user'])
-    URLs.update(tutor_api_url: 'http://localhost:3001/api/notifications')
+    URLs.update(tutor_api_url: 'http://localhost:3001/api')
     Notifications.startPolling(@windowImpl)
     expect(Poller::poll).to.have.callCount(2)


Fix moveElement arguments

Fix moveElement arguments


Add the option to pass a path into the urlFor helper method

--- apps/helpers.coffee
+++ apps/helpers.coffee
@@ -8,8 +8,10 @@
   # static helpers take any parametes and usually format data
   app.helpers
     # Object is a mongoose model object
-    urlFor: (object) ->
-      if object.collection
+    urlFor: (object, path) ->
+      if path
+        prefix = path + '/'
+      else if object.collection
         prefix = object.collection.name + '/'
       else
         prefix = ''


Increased wait timer.

Increased wait timer.

--- client/app/Applications/Ace.kdapplication/aceappview.coffee
+++ client/app/Applications/Ace.kdapplication/aceappview.coffee
@@ -52,7 +52,7 @@

   viewAppended:->
     super
-    @utils.wait 30, => @addNewTab() if @tabView.panes.length is 0
+    @utils.wait 100, => @addNewTab() if @tabView.panes.length is 0

   addNewTab: (file) ->
     file = file or FSHelper.createFileFromPath 'localfile:/Untitled.txt'


sitewide: commoninnernavigation selectitembyname method implemented

--- client/landing/app/CommonViews/CommonInnerNavigation.coffee
+++ client/landing/app/CommonViews/CommonInnerNavigation.coffee
@@ -47,6 +47,13 @@
     mainView.addSubView list
     @instantiateListItems(@getData().items or [])

+  selectItemByName:(name)->
+    item = null
+    for navItem in @itemsOrdered when navItem.getData()?.title is name
+      @selectItem item = navItem
+      break
+    return item
+
 class CommonInnerNavigationList extends KDListView
   constructor : (options = {},data)->
     options.tagName or= "ul"


A metric may have no measurements at all. Be prepared.

--- client/templates/subject_metrics/subject_metric_item.coffee
+++ client/templates/subject_metrics/subject_metric_item.coffee
@@ -9,10 +9,12 @@

   bg_class: ->
     last_measurement = Measurements.findOne {subject_metric_id: this._id}, {sort: {updated: -1}}
+    return '' if not last_measurement
     return 'bg-info' if last_measurement.error_message
     if last_measurement.target_met then 'bg-success' else 'bg-danger'

   description: ->
     last_measurement = Measurements.findOne {subject_metric_id: this._id}, {sort: {updated: -1}}
+    return 'No measurements available.' if not last_measurement
     return last_measurement.error_message if last_measurement.error_message
     return last_measurement.value + last_measurement.unit + '. Target is ' + last_measurement.target + last_measurement.unit


Moderation: Auto Complete input is added for setting parent topics

--- client/Social/Topics/AppController.coffee
+++ client/Social/Topics/AppController.coffee
@@ -166,14 +166,39 @@
             fields                :
               Synonym             :
                 label             : "Synonym"
-                itemClass         : KDInputView
-                name              : "synonym"
+                type              : "hidden"
             callback              : (formData) =>
-              topic.createSynonym formData.synonym.trim(), (err) ->
+              showStatus = (status) =>
+                modal.modalTabs.forms.synonym.buttons.Confirm.hideLoader()
                 new KDNotificationView
-                  title : if err then err.message else "Synonym is set successfully"
-                # modal.modalTabs.forms.synonym.buttons.Confirm.hideLoader()
+                  title : status
+              unless formData.synonyms?.length
+                return showStatus "You must choose parent topic first"
+
+              [synonym] = formData.synonyms
+              title = if synonym.$suggest then synonym.$suggest.trim() else synonym.title
+              topic.createSynonym title, (err) ->
+                status = if err then err.message else "Parent Topic is set successfully"
+                showStatus status
                 modal.destroy()
+
+    {fields, inputs} = modal.modalTabs.forms["synonym"]
+    @synonymController = new TagAutoCompleteController
+      form              : modal.modalTabs.forms["synonym"]
+      width             : 300
+      name              : "synonym"
+      itemClass         : TagAutoCompleteItemView
+      itemDataPath      : "title"
+      selectedItemClass : TagAutoCompletedItemView
+      submitValueAsText : yes
+      selectedItemsLimit: 1
+      dataSource : (args, callback) =>
+        {inputValue} = args
+        KD.singleton("appManager").tell "Topics", "fetchTopics", {inputValue}, (tags = [], deletedTags = []) =>
+          if not tags then @synonymController.showNoDataFound()
+          else callback tags
+
+    fields.Synonym.addSubView userRequestLineEdit = @synonymController.getView()

   updateTopic:(topicItem)->
     topic = topicItem.data


Client:Login: add a separate handler for Reset route

--- client/landing/Login/AppController.coffee
+++ client/landing/Login/AppController.coffee
@@ -1,6 +1,14 @@
 class LoginAppsController extends AppController

   handler = (callback)->-> KD.singleton('appManager').open 'Login', callback
+
+  handleResetRoute = ({params:{token}})->
+    do handler (app)=>
+      if KD.isLoggedIn()
+        KD.getSingleton('router').handleRoute "/Account/Profile?focus=password&token=#{token}"
+      else
+        app.getView().setCustomDataToForm('reset', {recoveryToken:token})
+        app.getView().animateToForm('reset')

   KD.registerAppClass this,
     name                        : "Login"
@@ -9,11 +17,7 @@
       '/:name?/Redeem'          : handler (app)-> app.getView().animateToForm 'redeem'
       '/:name?/Register'        : handler (app)-> app.getView().animateToForm 'register'
       '/:name?/Recover'         : handler (app)-> app.getView().animateToForm 'recover'
-      '/:name?/Reset/:token'    : ({params:{token}})->
-        do handler (app)=>
-          app.getView().setCustomDataToForm('reset', {recoveryToken:token})
-          app.getView().animateToForm('reset')
-
+      '/:name?/Reset/:token'    : handleResetRoute
       '/:name?/ResendToken'     : handler (app)-> app.getView().animateToForm 'resendEmail'
     hiddenHandle                : yes
     behavior                    : 'application'


updating server

updating server

--- cli/theoricus/commands/server.coffee
+++ cli/theoricus/commands/server.coffee
@@ -10,8 +10,9 @@
 		@port = "11235"
 		@root = "#{@the.pwd}/public"

-		@compiler = new theoricus.commands.Compiler @the, options, =>
-			@start_server()
+		# console.log  "Server is born()"
+		@compiler = new theoricus.commands.Compiler @the, options
+		@start_server()

 	start_server:()->

@@ -54,13 +55,18 @@
 						response.write err + "\n"
 						response.end()
 						return
-					response.writeHead 200, {"Content-Type": "text/javascript"}
+
+					if filename.match /.js$/m
+						response.writeHead 200, {"Content-Type": "text/javascript"}
+					else if filename.match /.css$/m
+						response.writeHead 200, {"Content-Type": "text/css"}
+
 					response.write file
 					response.end()

 		http.createServer( server ).listen @port

-		console.log(
+		console.log (
 			"#{'Server running at'.bold} http://localhost:#{@port}".grey
 			# "Hit CTRL+C to quit".grey
 		)


Do not save twice the same message

Do not save twice the same message

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -256,6 +256,8 @@
         LayoutActionCreator.displayModal params

     componentWillUnmount: ->
+        return if @props.lastUpdate is @state.date
+
         doSave = =>
             LayoutActionCreator.hideModal()
             MessageActionCreator.send @state, (error, message) ->
@@ -357,6 +359,7 @@
         message = _.clone @state

         @props.isSaving = true
+        @props.lastUpdate = message.date
         MessageActionCreator.send message, (error, message) =>
             if error? or not message?
                 if @state.isDraft


Remove InitCompose : Useless method

Remove InitCompose : Useless method

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -206,7 +206,8 @@
             @finalRedirect()

-    _initCompose: ->
+    componentDidMount: ->
+        setTimeout @_autosave, 30000

         # scroll compose window into view
         @getDOMNode().scrollIntoView()
@@ -218,12 +219,6 @@
             , 10
         else if @props.inReplyTo?
             document.getElementById('compose-editor')?.focus()
-
-
-    componentDidMount: ->
-        @_initCompose()
-
-        setTimeout @_autosave, 30000

     componentDidUpdate: ->


analytics: send user agent to segment

analytics: send user agent to segment

--- client/app/lib/setupanalytics.coffee
+++ client/app/lib/setupanalytics.coffee
@@ -57,6 +57,7 @@
             global       : emailFrequency?.global
           foreignAuth    : providers      if Object.keys(providers).length > 0
           sshKeysCount   : sshKeys.length if sshKeys?.length > 0
+          userAgent      : navigator.userAgent

         analytics?.identify nickname, args


minor: reference to deleted file removed

--- client/teams/lib/teamsappview.coffee
+++ client/teams/lib/teamsappview.coffee
@@ -2,8 +2,6 @@
 JView           = require 'app/jview'
 FooterView      = require 'app/commonviews/footerview'
 remote          = require('app/remote').getInstance()
-TeamsLaunchForm = require './teamslaunchform'
-

 module.exports = class TeamsAppView extends kd.CustomHTMLView


Fix test selection manipulation in FF

Fix test selection manipulation in FF

--- test/src/test_helpers.coffee
+++ test/src/test_helpers.coffee
@@ -150,7 +150,7 @@

   do expand = -> defer ->
     if triggerEvent(document.activeElement, "keydown", keyCode: keyCodes[direction], shiftKey: true)
-      editor.composition.expandSelectionInDirection(if direction is "left" then "backward" else "forward")
+      editor.composition.expandLocationRangeInDirection(if direction is "left" then "backward" else "forward")

     if --times is 0
       callback()


added modal to team view

added modal to team view

--- app/assets/javascripts/admin/views/team/team_list_view.coffee
+++ app/assets/javascripts/admin/views/team/team_list_view.coffee
@@ -23,7 +23,6 @@

-

@@ -32,17 +31,40 @@

     </form>
-

+

+

+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+
Add a New Team

+

+

+        <form class="form-horizontal">
+

+

+              Name
+

+                <input type="text" id="inputName" placeholder="Name" required>
+

+

+

+        </form>
+

+

+        Add
+        Close
+

+

   """)
   className : "team-administration container wide"
   itemView : TeamListItemView
   itemViewContainer : "tbody"

   ui :
-    "modalWrapper" : "#modal-wrapper"
+    "modal" : ".modal"
+    "inputName" : "#inputName"

   events :
-    "click #new-team" : "addNewTeam"
+    "click #new-team" : "showModal"
+    "click .modal .btn-primary" : "addNewTeam"

   initialize : ->

@@ -52,17 +74,18 @@
         isEditable: true
     )

-    #fetch the user's name
+    #fetch the logged-in user's name
     @user = $.ajax(
       url: "/api/user"
     )

   addNewTeam : ->

+
     @user.then(
       (userData) =>
         team =
-          name : "test"
+          name : @ui.inputName.val()
           owner : "#{userData.firstName} #{userData.lastName}"
           roles : [{ name : "admin" }, { name : "user" }]
           isEditable : true
@@ -75,12 +98,5 @@

   showModal : (modalView) ->

-    if @$el.find("input[type=checkbox]:checked").length > 0
-      view = new modalView({userCollection : @collection})
-      view.render()
-      @ui.modalWrapper.html(view.el)
-
-      view.$el.modal("show")
-
-    else
-      Toast.error("No user is selected.")
+    @ui.inputName.val("")
+    @ui.modal.modal("show")


Only call github api if a handle is provided

--- app/assets/javascripts/application/views/users/edit.js.coffee
+++ app/assets/javascripts/application/views/users/edit.js.coffee
@@ -10,7 +10,8 @@
     form = @$el.find "form"
     newAttrs = @updateModelFromForm(form)
     success = =>
-      window.getGitHubData(@model.get('github_handle'))
+      gh_handle = @model.get('github_handle')
+      window.getGitHubData(gh_handle) if gh_handle
       App.router.navigate("/#{@model.get('username')}", {trigger: true});
     error = -> $('.message').html("There was an issue saving your updates")
     @model.save(newAttrs, success: success, error: error)


3036: Refactored upload view

3036: Refactored upload view

--- app/assets/javascripts/backbone/views/file_upload_view.coffee
+++ app/assets/javascripts/backbone/views/file_upload_view.coffee
@@ -21,13 +21,15 @@
       thumbnailWidth: 150
       init: ->
         dz = this
-        dz.on 'addedfile', ->
-          dz.removeFile(dz.files[0]) if dz.files[1] # Replace existing dragged file if present
-          view.dzForm.addClass('existing-deleted') if view.dzForm.is('.has-existing')
-          view.setMainPhotoDestroyFlag(false)
+        dz.on 'addedfile', (file) -> view.fileAdded.apply(view, [file, dz])

   events:
     'click a.delete': 'delete'
+
+  fileAdded: (file, dz) ->
+    dz.removeFile(dz.files[0]) if dz.files[1] # Replace existing dragged file if present
+    @dzForm.addClass('existing-deleted') if @dzForm.is('.has-existing')
+    @setMainPhotoDestroyFlag(false)

   delete: (e) ->
     e.preventDefault()


#588 Call isArbitraryMode() -> fix plane user settings not shown

--- app/assets/javascripts/oxalis/view/tracing_layout_view.coffee
+++ app/assets/javascripts/oxalis/view/tracing_layout_view.coffee
@@ -85,7 +85,7 @@
   renderSettings : ->

     if @isSkeletonMode()
-      settingsTabClass = if @isArbitraryMode then SkeletonArbitraryTabView else SkeletonPlaneTabView
+      settingsTabClass = if @isArbitraryMode() then SkeletonArbitraryTabView else SkeletonPlaneTabView
       settingsTabView = new settingsTabClass(@options)
     else if @isVolumeMode()
       settingsTabView = new VolumeTabView(@options)


Properly clear form after create new topic

--- app/assets/javascripts/yy/controllers/TopicFormCtrl.js.coffee
+++ app/assets/javascripts/yy/controllers/TopicFormCtrl.js.coffee
@@ -18,6 +18,11 @@
       ).$save()
       topic.then (response) ->
         $scope.newTopicForm.show = false
+        $scope.topicForm.$setPristine()
+        $scope.topic =
+          title: ''
+          content: ''
+          category_id: ''
         $scope.topics.push response
-        $location.path('/')
+        $location.path("/topics/#{response.slug}")


fix collection.map is undefined error on spotlight view

--- app/assets/javascripts/admin/models/pagination_collection.coffee
+++ app/assets/javascripts/admin/models/pagination_collection.coffee
@@ -148,6 +148,9 @@
     clonedCollection.setPageSize(@state.pageSize)
     return clonedCollection

+  map : (args...) ->
+    return _.map(args...)
+

   _lastPageIndex : ->
     return Math.ceil(@currentModels.length / @state.pageSize) - 1


Fixed issue #1165 - User can not be removed from organization.

--- app/assets/javascripts/app/lib/app_post/searchable_select.coffee
+++ app/assets/javascripts/app/lib/app_post/searchable_select.coffee
@@ -12,6 +12,7 @@
     'mouseenter .js-back':   'highlightItem'
     'shown.bs.dropdown':     'onDropdownShown'
     'hidden.bs.dropdown':    'onDropdownHidden'
+    'keyup .js-input':       'onKeyUp'

   elements:
     '.js-dropdown': 'dropdown'
@@ -119,6 +120,10 @@
     @unhighlightCurrentItem()
     $(document).off 'keydown.searchable_select'
     @isOpen = false
+
+  onKeyUp: =>
+    return if @input.val().trim() isnt ''
+    @shadowInput.val('')

   toggle: =>
     @currentItem = null


Hide legend if there's no different node sizes

--- app/frontend/javascripts/views/visualization-legend.js.coffee
+++ app/frontend/javascripts/views/visualization-legend.js.coffee
@@ -9,7 +9,7 @@

   render: ( scale_size, scale_color ) ->

-    if !scale_size and !scale_color
+    if (!scale_color or scale_color.domain().length <= 1 ) and (!scale_size or scale_size.domain()[0] == scale_size.domain()[1])
       @$el.hide()
       return

@@ -18,8 +18,8 @@
     @$el.show()

     # Setup size legend
-    if scale_size
-
+    if scale_size and scale_size.domain()[0] != scale_size.domain()[1]
+
       # create legend size group
       legend_size = @$el.find('.visualization-graph-legend-size').show()


class GroupDescription added.

class GroupDescription added.

--- client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
+++ client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
@@ -75,3 +75,26 @@
     , data

     KD.remote.api.ActiveItems.fetchTopics {group}, @bound 'renderItems'
+
+class GroupDescription extends KDView
+
+  constructor:(options={}, data)->
+    super options, data
+
+    group = KD.singletons.groupsController.getCurrentGroup()
+
+    @titleView = new KDCustomHTMLView
+      partial  : "#{group.title} Group"
+      cssClass : "group-title"
+
+    @bodyView = new KDCustomHTMLView
+      tagName  : "p"
+      partial  : group.body or ""
+      cssClass : "group-body"
+
+    @addSubView @titleView
+    @addSubView @bodyView
+
+  viewAppended:JView::viewAppended
+
+


CommentView: add permission checking for comment settings menu

--- client/Social/Activity/views/comments/commentlistitemview.coffee
+++ client/Social/Activity/views/comments/commentlistitemview.coffee
@@ -43,13 +43,27 @@
     activity = @getDelegate().getData()
     loggedInId = KD.whoami().getId()

-    @settings = if true  # if super-admin
-      @getSettings(data)
-    else if loggedInId is data.originId or # if comment/review owner
-            loggedInId is activity.originId # if activity/app owner
-      @getDeleteButton(data)
+    isCommentMine = loggedInId is data.originId
+    isActivityMine = loggedInId is activity.originId
+
+    settingsOptions = {}
+    # if i am the owner of the comment or activity
+    # i can delete it
+    if isCommentMine or isActivityMine
+      settingsOptions.delete = true
+      showSettingsMenu = yes
+
+    # if i can edit comments(have permission)
+    if isCommentMine and "edit own comments" in KD.config.permissions or \
+      "edit comments" in KD.config.permissions
+        settingsOptions.edit = true
+        showSettingsMenu     =  yes
+
+    # if settings menu should be visible
+    if showSettingsMenu
+      @settings = @getSettings data, settingsOptions
     else
-      new KDCustomHTMLView tagName : 'span', cssClass : 'hidden'
+      @settings = new KDView

     @likeView = new LikeViewClean { tooltipPosition : 'sw', checkIfLikedBefore: yes }, data


opinion loader style adjusted

opinion loader style adjusted

--- client/landing/app/CommonViews/opinions/opinionviewheader.coffee
+++ client/landing/app/CommonViews/opinions/opinionviewheader.coffee
@@ -53,7 +53,7 @@
     @loader = new KDLoaderView
       cssClass      : "opinion-loader hidden"
       size          :
-        width       : 18
+        width       : 20
       loaderOptions :
         color       : "#FD7E09"
         shape       : "spiral"


Add old tests to commons tests

Add old tests to commons tests

--- test/autoprefixer.coffee
+++ test/autoprefixer.coffee
@@ -54,7 +54,8 @@
 commons = ['transition', 'values', 'keyframes', 'gradient', 'flex-rewrite',
            'flexbox', 'filter', 'border-image', 'border-radius', 'notes',
            'selectors', 'placeholder', 'fullscreen', 'intrinsic', 'mistakes',
-           'custom-prefix', 'cascade', 'double', 'multicolumn']
+           'custom-prefix', 'cascade', 'double', 'multicolumn', 'ie-transform',
+           'background-size']

 describe 'autoprefixer()', ->


Grunt: Disable coffeelint for now

Grunt: Disable coffeelint for now

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -105,7 +105,7 @@
       @task.run 'copy'

   @registerTask 'test', 'Build MicroFlo and run automated tests', (target = 'all') =>
-    @task.run 'coffeelint'
+    # @task.run 'coffeelint'
     @task.run 'build'
     if target is 'all' or target is 'nodejs'
       @task.run 'mochaTest'


nitrogen6x.coffee: Specify Yocto Morty instead of Jethro

We are now using Yocto Morty

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- nitrogen6x.coffee
+++ nitrogen6x.coffee
@@ -20,7 +20,7 @@
 		machine: 'nitrogen6x'
 		image: 'resin-image'
 		fstype: 'resinos-img'
-		version: 'yocto-jethro'
+		version: 'yocto-morty'
 		deployArtifact: 'resin-image-nitrogen6x.resinos-img'
 		compressed: true


fix Context in test scope

fix Context in test scope

--- test/context.test.coffee
+++ test/context.test.coffee
@@ -134,7 +134,7 @@

     it 'can output in nested items', ->
       context_a = Context.create_run_context [ctx]
-      context_a.imported_vars.Context = Context
+      context_a.scope.Context = Context
       Context.eval_in_context context_a, ->
         text 'a'
         Context.nested_item @, ->
@@ -157,7 +157,7 @@

     it 'allows output in an async block after render', (done) ->
       context_a = Context.create_run_context [ctx, {set_test_result}]
-      context_a.imported_vars.Context = Context
+      context_a.scope.Context = Context
       Context.eval_in_context context_a, ->
         Context.nested_item @, ->
           setTimeout @keeping_context ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

   player.include(Controllable)

-
+  # Mock the k
   window.keydown = {}
   window.keydown.left = true


simplify terms after collecting by exponent

--- coffeequate/src/operators/Mul.coffee
+++ coffeequate/src/operators/Mul.coffee
@@ -326,7 +326,7 @@
 						break

 				newLikeTerms.push(term0) unless found
-			liketerms = newLikeTerms
+			liketerms = ([base.expandAndSimplify(), power] for [base, power] in newLikeTerms)

 			newMul = null
 			for liketerm in liketerms


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_flash.coffee
+++ test/engine_flash.coffee
@@ -20,8 +20,18 @@
   engine.update()

   ok engine.I.flashColor.a < 1
-  equal engine.I.flashDuration, 12
   equal engine.I.flashCooldown, 11
-  equal engine.I.flashTargetAlpha, 0
+
+  engine.flash(Color(255, 255, 255, 0), 30, 1)
+
+  ok engine.I.flashColor.equal(Color(255, 255, 255, 0))
+  equal engine.I.flashDuration, 30
+  equal engine.I.flashCooldown, 30
+  equal engine.I.flashTargetAlpha, 1
+
+  engine.update()
+
+  ok engine.I.flashColor.a > 0
+  equal engine.I.flashCooldown, 29

 module()


:art:

:art:

--- lib/fuzzy-finder-view.coffee
+++ lib/fuzzy-finder-view.coffee
@@ -56,6 +56,7 @@
       highlighter = (path, matches, offsetIndex) =>
         lastIndex = 0
         matchedChars = [] # Build up a set of matched chars to be more semantic
+
         for matchIndex in matches
           matchIndex -= offsetIndex
           continue if matchIndex < 0 # If marking up the basename, omit path matches


Fix tests for refresh option.

Fix tests for refresh option.

--- test/shared/cache.coffee
+++ test/shared/cache.coffee
@@ -95,11 +95,11 @@

 test.global = (CacheClass, options = {}) ->
   impromptu = new Impromptu()
-  background = new Impromptu
-    background: true
+  refreshable = new Impromptu
+    refresh: true

-  it 'should update when background is set', (done) ->
-    cached = new CacheClass background, test.name(),
+  it 'should update when refresh is set', (done) ->
+    cached = new CacheClass refreshable, test.name(),
       update: (fn) ->
         done()
         fn null, 'value'
@@ -108,7 +108,7 @@

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


dataType isn't used

dataType isn't used

--- tutor/src/components/scores/assignment-header.cjsx
+++ tutor/src/components/scores/assignment-header.cjsx
@@ -88,7 +88,7 @@

 AssignmentHeader = (props) ->
-  {isConceptCoach, periodIndex, period_id, courseId, sort, onSort, dataType, columnIndex, width} = props
+  {isConceptCoach, periodIndex, period_id, courseId, sort, onSort, columnIndex, width} = props
   heading = props.headings[columnIndex]

   cellWidth = getCellWidth({isConceptCoach, heading})


referral: JReferrableEmail.invite function

--- workers/social/lib/social/models/referrableemail.coffee
+++ workers/social/lib/social/models/referrableemail.coffee
@@ -22,7 +22,7 @@
         type      : Date
         get       : -> new Date
     sharedMethods :
-      static      : ["getUninvitedEmails", "deleteEmailsForAccount"]
+      static      : ["invite", "getUninvitedEmails", "deleteEmailsForAccount"]
       instance    : ["invite"]

   @create: (clientId, {email, title}, callback)->
@@ -68,3 +68,10 @@
     email.save (err)=>
       return callback err  if err
       @update $set: invited: true, callback
+
+  @invite: secure (client, email, callback) ->
+    {connection: {delegate: {profile: {nickname}}}} = client
+    r = new JReferrableEmail {email, username: nickname}
+    r.save (err) ->
+      return  callback err if err
+      r.invite client, callback