Workspace: Fix for code quality.

Workspace: Fix for code quality.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -27,20 +27,18 @@
         @tabView.showPaneByIndex val.ActiveTabIndex
         return @workspaceRef.child("ActiveTabIndex").remove()

-      {tabs} = val
-      return unless tabs
-
-      for key, data of tabs
-        if data.path and @openedFiles.indexOf(data.path) is -1
-          file = FSHelper.createFileFromPath data.path
-          @createEditorInstance file, null, data.sessionKey
+      if val.tabs?
+        for key, data of val.tabs
+          if data.path and @openedFiles.indexOf(data.path) is -1
+            file = FSHelper.createFileFromPath data.path
+            @createEditorInstance file, null, data.sessionKey

     @workspaceRef.onDisconnect().remove()  if @workspace.amIHost()

   createEditorTabs: ->
     @tabHandleContainer = new ApplicationTabHandleHolder
-      delegate      : @
-      addPlusHandle : no
+      delegate          : @
+      addPlusHandle     : no

     @tabView = new ApplicationTabView
       delegate           : @
@@ -78,6 +76,15 @@
     @tabView.addPane pane
     @activeTabIndex = @tabView.panes.length

+    workspaceRefData =
+      sessionKey : editor.sessionKey
+
+    if file
+      workspaceRefData.path = file.path
+      @openedFiles.push file.path
+
+    @workspaceRef.child("tabs").push workspaceRefData  unless sessionKey
+
     pane.on "KDTabPaneDestroy", =>
       @workspaceRef.once "value", (snapshot) =>
         {tabs} = snapshot.val()
@@ -86,15 +93,6 @@
         @workspaceRef.set { tabs }

       @openedFiles.splice @openedFiles.indexOf(file.path), 1
-
-    workspaceRefData =
-      sessionKey : editor.sessionKey
-
-    if file
-      workspaceRefData.path = file.path
-      @openedFiles.push file.path
-
-    @workspaceRef.child("tabs").push workspaceRefData  unless sessionKey

     return yes # return something instead of workspaceRef.child


:white_check_mark: Add tests for looping references in context

--- spec/color-parser-spec.coffee
+++ spec/color-parser-spec.coffee
@@ -55,6 +55,13 @@
       '@component-line-height': '@text-height'
       '@list-item-height': '@component-line-height'
     }).asUndefined()
+
+  itParses('c').withContext({'c': 'c'}).asUndefined()
+  itParses('c').withContext({
+    'c': 'd'
+    'd': 'e'
+    'e': 'c'
+  }).asUndefined()

   itParses('#7fff7f00').asColor(255, 127, 0, 0.5)
   itParses('#ff7f00').asColor(255, 127, 0)


Remove commented code

Remove commented code

--- spec/hook-manager-spec.coffee
+++ spec/hook-manager-spec.coffee
@@ -16,7 +16,6 @@
     message = jasmine.createSpyObj('message', ['reply'])
     robot = jasmine.createSpyObj('robot', ['http', 'logger'])

-    # robot.logger.andReturn(logger)
     robot.http.andReturn(client)
     client.header.andReturn(client)


:art: Improve listHooks error handling specs

--- spec/hook-manager-spec.coffee
+++ spec/hook-manager-spec.coffee
@@ -127,28 +127,31 @@

       expect(client.header).toHaveBeenCalledWith('User-Agent', 'lee-dohm')

-    it 'replies with an error if the token is not set', ->
-      delete process.env.HUBOT_GITHUB_EVENT_HOOK_TOKEN
-      manager.listHooks(user, repo)
+    describe 'error handling', ->
+      beforeEach ->
+        spyOn(manager, 'handleError')

-      expect(client.header).not.toHaveBeenCalled()
-      expect(message.reply).toHaveBeenCalled()
+      it 'logs an error if the token is not set', ->
+        delete process.env.HUBOT_GITHUB_EVENT_HOOK_TOKEN
+        manager.listHooks(user, repo)

-    it 'replies with an error if an error is returned', ->
-      client.get.and.returnValue (callback) ->
-        callback(error, null, null)
+        expect(manager.handleError).toHaveBeenCalled()

-      manager.listHooks(user, repo)
+      it 'logs an error if an error is returned', ->
+        client.get.and.returnValue (callback) ->
+          callback(error, null, null)

-      expect(robot.logger.error).toHaveBeenCalledWith(util.inspect(error))
+        manager.listHooks(user, repo)

-    it 'replies with an error if an unsuccessful response is returned', ->
-      response =
-        statusCode: 404
+        expect(manager.handleError).toHaveBeenCalled()

-      client.get.and.returnValue (callback) ->
-        callback(null, response, '{}')
+      it 'logs an error if an unsuccessful response is returned', ->
+        response =
+          statusCode: 404

-      manager.listHooks(user, repo)
+        client.get.and.returnValue (callback) ->
+          callback(null, response, '{}')

-      expect(robot.logger.error).toHaveBeenCalledWith(util.inspect(response))
+        manager.listHooks(user, repo)
+
+        expect(manager.handleError).toHaveBeenCalled()


Add splicing to random MarkerIndex spec

Add splicing to random MarkerIndex spec

This test sometimes fails. Need to find remaining bugs in splice

--- spec/marker-index-spec.coffee
+++ spec/marker-index-spec.coffee
@@ -298,14 +298,19 @@
         markerIndex = new MarkerIndex

         for j in [1..50]
-          # 80% insert, 20% delete
-
-          if markers.length is 0 or random(10) > 2
+          # 60% insert, 20% splice, 20% delete
+
+          if markers.length is 0 or random(10) > 4
             id = idCounter++
             [start, end] = getRange()
             # console.log "#{j}: insert(#{id}, #{start}, #{end})"
             markerIndex.insert(id, start, end)
             markers.push({id, start, end})
+          else if random(10) > 2
+            [start, oldExtent, newExtent] = getSplice()
+            # console.log "#{j}: splice(#{start}, #{oldExtent}, #{newExtent})"
+            markerIndex.splice(start, oldExtent, newExtent)
+            spliceMarkers(start, oldExtent, newExtent)
           else
             [{id}] = markers.splice(random(markers.length - 1), 1)
             # console.log "#{j}: delete(#{id})"
@@ -314,8 +319,8 @@
           # console.log markerIndex.rootNode.toString()

           for {id, start, end} in markers
-            expect(markerIndex.getStart(id)).toEqual start, "(Marker #{id}; Seed: #{seed})"
-            expect(markerIndex.getEnd(id)).toEqual end, "(Marker #{id}; Seed: #{seed})"
+            expect(markerIndex.getStart(id)).toEqual start, "(Marker #{id} start; Seed: #{seed})"
+            expect(markerIndex.getEnd(id)).toEqual end, "(Marker #{id} end; Seed: #{seed})"
             return if currentSpecFailed()

           for k in [1..10]
@@ -323,6 +328,63 @@
             # console.log "#{k}: findContaining(#{queryStart}, #{queryEnd})"
             expect(markerIndex.findContaining(queryStart, queryEnd)).toEqualSet(getExpectedContaining(queryStart, queryEnd), "(Seed: #{seed})")
             return if currentSpecFailed()
+
+    getSplice = ->
+      start = Point(random(100), random(100))
+      oldExtent = Point(random(100 - start.row), random(100))
+      newExtent = Point(random(100 - start.row), random(100))
+      [start, oldExtent, newExtent]
+
+    spliceMarkers = (spliceStart, oldExtent, newExtent) ->
+      spliceOldEnd = spliceStart.traverse(oldExtent)
+      spliceNewEnd = spliceStart.traverse(newExtent)
+
+      shiftBySplice = (point) ->
+        spliceNewEnd.traverse(point.traversalFrom(spliceOldEnd))
+
+      for marker in markers
+        if spliceStart.compare(marker.start) < 0
+
+          # replacing text before the marker or inserting at the start of the marker
+          if spliceOldEnd.compare(marker.start) <= 0
+            marker.start = shiftBySplice(marker.start)
+            marker.end = shiftBySplice(marker.end)
+
+          # replacing text that overlaps the start of the marker
+          else if spliceOldEnd.compare(marker.end) < 0
+            marker.start = spliceNewEnd
+            marker.end = shiftBySplice(marker.end)
+
+          # replacing text surrounding the marker
+          else
+            marker.start = spliceNewEnd.copy()
+            marker.end = spliceNewEnd.copy()
+
+        else if spliceStart.isEqual(marker.start) and spliceStart.compare(marker.end) < 0
+
+          # replacing text at the start of the marker, within the marker
+          if spliceOldEnd.compare(marker.end) < 0
+            marker.end = shiftBySplice(marker.end)
+
+          # replacing text at the start of the marker, longer than the marker
+          else
+            marker.end = spliceNewEnd
+
+        else if spliceStart.compare(marker.end) < 0
+
+          # replacing text within the marker
+          if spliceOldEnd.compare(marker.end) <= 0
+            marker.end = shiftBySplice(marker.end)
+
+          # replacing text that overlaps the end of the marker
+          else if spliceOldEnd.compare(marker.end) > 0
+            marker.end = spliceNewEnd
+
+        else if spliceStart.compare(marker.end) is 0
+
+          # inserting text at the end of the marker
+          if spliceOldEnd.isEqual(marker.end)
+            marker.end = spliceNewEnd

     getRange = ->
       start = Point(random(100), random(100))


Fix cart discount method

Fix cart discount method

--- src/features/cart.coffee
+++ src/features/cart.coffee
@@ -60,7 +60,7 @@
       if ( code == null or code == false )
         return @m.Request 'carts/'+@cartId+'/discount', 'DELETE', null, callback, error

-      return @m.Request 'carts/'+@cartId+'/discount', 'POST', {code: code}, callback. error
+      return @m.Request 'carts/'+@cartId+'/discount', 'POST', {code: code}, callback, error

     `// @if TARGET=='nodejs'
     `


fix(icon-type): Star / unread status should work even when we can't compute "forwarded / replied" icons

--- internal_packages/thread-list/lib/thread-list-icon.cjsx
+++ internal_packages/thread-list/lib/thread-list-icon.cjsx
@@ -14,23 +14,28 @@
   _iconType: =>
     myEmail = NamespaceStore.current()?.emailAddress

-    msgs = @props.thread.metadata
-    return '' unless msgs and msgs instanceof Array
-
-    msgs = _.filter msgs, (m) -> m.isSaved() and not m.draft
-    msg = msgs[msgs.length - 1]
-    return '' unless msgs.length > 0
-
     if @props.thread.hasTagId('starred')
       return 'thread-icon-star'
-    else if @props.thread.unread
+
+    if @props.thread.unread
       return 'thread-icon-unread thread-icon-star-on-hover'
-    else if msg.from[0]?.email isnt myEmail or msgs.length is 1
-      return 'thread-icon-star-on-hover'
-    else if Utils.isForwardedMessage(msg)
-      return 'thread-icon-forwarded thread-icon-star-on-hover'
-    else
-      return 'thread-icon-replied thread-icon-star-on-hover'
+
+    msgs = @_nonDraftMessages()
+    last = msgs[msgs.length - 1]
+
+    if msgs.length > 1 and last.from[0]?.email is myEmail
+      if Utils.isForwardedMessage(last)
+        return 'thread-icon-forwarded thread-icon-star-on-hover'
+      else
+        return 'thread-icon-replied thread-icon-star-on-hover'
+
+    return 'thread-icon-star-on-hover'
+
+  _nonDraftMessages: =>
+    msgs = @props.thread.metadata
+    return [] unless msgs and msgs instanceof Array
+    msgs = _.filter msgs, (m) -> m.isSaved() and not m.draft
+    return msgs

   render: =>


Provide more space for labels in sankey chart

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -333,7 +333,7 @@
       return @__horizontal_spacing if @__horizontal_spacing?
       cols = @view.number_of_columns()
       # this should leave enough room for the node labels
-      @__horizontal_spacing = (@view.width - 150) / (cols - 1)
+      @__horizontal_spacing = (@view.width - (50 * cols)) / (cols - 1)
       @__horizontal_spacing

     # vertical position of the top left corner of the node. Adds some margin


Moving one line only for better clearness in header js

--- app/assets/javascripts/header.js.coffee
+++ app/assets/javascripts/header.js.coffee
@@ -1,4 +1,6 @@
 class Header
+
+    initilized = false

     # Initializes the header
     init: ->
@@ -50,6 +52,4 @@
                 $('.mobile-menu .normal-mode').hide()
                 $('.mobile-menu .crisis-mode').show()

-    initilized = false
-
 @Sf72.Header = new Header(@Sf72)


using gon.product_widgets to render product widgets on page load

--- app/assets/javascripts/stores.js.coffee
+++ app/assets/javascripts/stores.js.coffee
@@ -59,7 +59,7 @@

 if gon.page is 'stores_show'
     $ ->
-        fetch_and_render_product_widgets()
+        render_product_widgets(gon.product_widgets)

         ($ '.product-widgets').on 'click', '.edit-product', ->
             data = ($ @).closest('.product-widget').data()


Fixing tasks order in user story detail

Fixing tasks order in user story detail

--- app/coffee/modules/related-tasks.coffee
+++ app/coffee/modules/related-tasks.coffee
@@ -205,7 +205,7 @@
     link = ($scope, $el, $attrs) ->
         loadTasks = ->
             return $rs.tasks.list($scope.projectId, null, $scope.usId).then (tasks) =>
-                $scope.tasks = tasks
+                $scope.tasks = _.sortBy(tasks, 'ref')
                 return tasks

         $scope.$on "related-tasks:add", ->


fix logo javascript for IE. fixes #11231

was previously querying 'background' css prop. on logo, which
IE was returning as 'undefined.' now queries 'background-image'
which works in all browsers.

test plan:
  * in IE8, log into canvas and load any page;
  * verify that no javascript error is thrown and that page
    functions as expected.

Change-Id: I78492bf42229042aa99a2723c90d83521b95cc0d
Reviewed-on: https://gerrit.instructure.com/14365
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Cameron Matheson <1d572acbfa68c7c6e541c7b840d6b622e5c0dc91@instructure.com>

--- app/coffeescripts/bundles/common.coffee
+++ app/coffeescripts/bundles/common.coffee
@@ -50,6 +50,6 @@
   # TODO: remove this code once people have had time to update their logo-related
   # custom css. see related code in app/stylesheets/base/_#header.sass.
   $logo = $('#header-logo')
-  if $logo.length > 0 and $logo.css('background').match(/header_canvas_logo/)
+  if $logo.length > 0 and $logo.css('background-image').match(/\/canvas\/header_canvas_logo\.png/)
     $logo.addClass('original')


添加对卡券消息推送的处理

添加对卡券消息推送的处理


Fix single quote-> coffee style double qute for interpolation

--- src/javascripts/math_buttons.coffee
+++ src/javascripts/math_buttons.coffee
@@ -10,105 +10,105 @@
       do (num)=>
         @button({
           value: "#{num}"
-          class: 'jc--button jc--button-number'
+          class: "jc--button jc--button-number"
         }, opts)

   decimal: (opts)->
     @button({
-      value: '.'
-      class: 'jc--button jc--button-decimal'
+      value: "."
+      class: "jc--button jc--button-decimal"
     }, opts)

   negative: (opts)->
     @button({
-      value: 'negative'
-      label: '(–)'
-      class: 'jc--button jc--button-negative'
+      value: "negative"
+      label: "(–)"
+      class: "jc--button jc--button-negative"
     }, opts)

   # Operations
   addition: (opts)->
     @button({
-      value: '+'
-      class: 'jc--button jc--button-operation jc--button-add'
+      value: "+"
+      class: "jc--button jc--button-operation jc--button-add"
     }, opts)

   subtraction: (opts)->
     @button({
-      value: '-'
-      label: '−'
-      class: 'jc--button jc--button-operation jc--button-subtract'
+      value: "-"
+      label: "−"
+      class: "jc--button jc--button-operation jc--button-subtract"
     }, opts)

   multiplication: (opts)->
     @button({
-      value: '*'
-      label: '×'
-      class: 'jc--button jc--button-operation jc--button-multiply'
+      value: "*"
+      label: "×"
+      class: "jc--button jc--button-operation jc--button-multiply"
     }, opts)

   division: (opts)->
     @button({
-      value: '/'
-      label: '÷'
-      class: 'jc--button jc--button-operation jc--button-divide'
+      value: "/"
+      label: "÷"
+      class: "jc--button jc--button-operation jc--button-divide"
     }, opts)

   equals: (opts)->
     @button({
-      value: '='
-      class: 'jc--button jc--button-operation jc--button-equal'
+      value: "="
+      class: "jc--button jc--button-operation jc--button-equal"
     }, opts)

   # Other functions
   lparen: (opts)->
     @button({
-      value: '('
-      class: 'jc--button jc--button-other jc--button-rParen'
+      value: "("
+      class: "jc--button jc--button-other jc--button-rParen"
     }, opts)

   rparen: (opts)->
     @button({
-      value: ')'
-      class: 'jc--button jc--button-other jc--button-lParen'
+      value: ")"
+      class: "jc--button jc--button-other jc--button-lParen"
     }, opts)

   pi: (opts)->
     @button({
-      value: 'pi'
-      label: 'π'
-      class: 'jc--button jc--button-other jc--button-pi'
+      value: "pi"
+      label: "π"
+      class: "jc--button jc--button-other jc--button-pi"
     }, opts)

   fraction: (opts)->
     @button({
-      value: 'fraction'
+      value: "fraction"
       label: "a/b"
-      class: 'jc--button jc--button-other jc--button-fraction'
+      class: "jc--button jc--button-other jc--button-fraction"
     }, opts)

   caret: (opts)->
     @button({
-      value: '^'
-      class: 'jc--button jc--button-other jc--button-caret'
+      value: "^"
+      class: "jc--button jc--button-other jc--button-caret"
     }, opts)

   exponent: (opts)->
-    base = opts.base || 'x'
-    power = opts.power || 'y'
+    base = opts.base || "x"
+    power = opts.power || "y"
     @button({
-      value: 'exponent'
+      value: "exponent"
       label: "#{base}#{power}"
-      class: 'jc--button jc--button-other jc--button-exponent jc--button-exponent-#{base}to#{power}'
+      class: "jc--button jc--button-other jc--button-exponent jc--button-exponent-#{base}to#{power}"
     }, opts)

   root: (opts)->
     degree = if opts.degree then "#{opts.degree}" else ""
-    radicand = opts.radicand || 'x'
+    radicand = opts.radicand || "x"
     @button({
-      value: 'root'
+      value: "root"
       label: "#{degree}√#{radicand}"
-      class: 'jc--button jc--button-other jc--button-root jc--button-root-#{degree}of#{radicand}'
+      class: "jc--button jc--button-other jc--button-root jc--button-root-#{degree}of#{radicand}"
     }, opts)

   # EQ builder vars
@@ -118,7 +118,7 @@
         do (v)=>
           @button({
             value: "#{v.name}"
-            class: 'jc--button jc--button-other jc--button-variable'
+            class: "jc--button jc--button-other jc--button-variable"
             variable: v
           }, opts)
     variables
@@ -126,14 +126,14 @@
   # Operational
   del: (opts)->
     @button({
-      value: 'del'
-      class: 'jc--button jc--button-del'
+      value: "del"
+      class: "jc--button jc--button-del"
     }, opts)

   clear: (opts)->
     @button({
-      value: 'clear'
-      class: 'jc--button jc--button-clear'
+      value: "clear"
+      class: "jc--button jc--button-clear"
     }, opts)

   button: (type_opts, opts)->


Controller generator action instance methods

--- lib/generators/rails_script/controller/templates/javascript.js.coffee
+++ lib/generators/rails_script/controller/templates/javascript.js.coffee
@@ -6,17 +6,17 @@
     return this

-  index: ->
+  index: =>
     return

-  show: ->
+  show: =>
     return

-  new: ->
+  new: =>
     return

-  edit: ->
-    return+  edit: =>
+    return


WIP: SheetRunner with event loop: simple named values and functions with args working

--- src/main/runtime/SheetRunner.coffee
+++ src/main/runtime/SheetRunner.coffee
@@ -14,6 +14,8 @@
   @STREAM_RETURN = 'streamReturn'
   @AGGREGATE_RETURN = 'aggregateReturn'

+  Invalid = '__INVALID__'
+
   isRxObservable = (func) -> typeof func.subscribe == 'function'
   returnsStream = (func) -> func.kind == SheetRunner.STREAM or func.kind == SheetRunner.TRANSFORM_STREAM or func.returnKind == SheetRunner.STREAM_RETURN or func.returnKind == SheetRunner.SEQUENCE_RETURN
   returnsAggregate = (func) -> func.returnKind == SheetRunner.AGGREGATE_RETURN
@@ -40,12 +42,15 @@
     @inputStreams = {}
     @inputCompleteSubject = new Rx.Subject()
     @bufferedValueChanges = bufferedValueChangeStream @valueChanges, @inputCompleteSubject
+    @slots = {}
+    @inputEvents = []

     @sheet = _.assign {}, @providedFunctions, {
       operations: new Operations("a function", @_inputItem)
       stored: {}
       storedUpToDate: {}
     }
+

 # TODO  rationalise this zoo of add...Functions
@@ -90,16 +95,21 @@

   addUserFunction: (funcDef) ->
     name = funcDef.name
+    replacing = @userFunctions[name]?
+    if replacing then @_invalidateDependents name
     @userFunctions[name] = funcDef
     functionImpl = SheetCodeGenerator.exprFunction funcDef, @_functionInfo()
     @userFunctionImpls[name] = functionImpl
     @sheet[name] = switch
       when _.includes(functionImpl.functionNames, name) then errorFunction name, 'Formula uses itself'
       when _.includes(@functionsUsedBy(name), name) then errorFunction name, 'Formula uses itself through another formula'
+      when funcDef.argDefs.length is 0 then @_cachedValueFunction name, functionImpl.theFunction
       else functionImpl.theFunction

+
     @sheet[n] = unknownNameFunction(n) for n in functionImpl.functionNames when not @sheet[n]?
-    if funcDef.argDefs.length == 0
+    if funcDef.argDefs.length is 0
+      @slots[name] = Invalid
       @sheet.stored[name] = []
       @sheet.storedUpToDate[name] = true
       @sheet['all_' + name] = @_allFunction name
@@ -123,6 +133,7 @@
         if not subj.hasObservers()
           delete @userFunctionSubjects[subjName]
           delete @userFunctionImpls[subjName]
+      delete @slots[functionName]
       @sheet[functionName] = unknownNameFunction(functionName)
       @_recalculate()

@@ -187,16 +198,15 @@
       @valueChanges.onNext [name, value]
     subj

+  _invalidateDependents: (name) -> @slots[n] = Invalid for n in @_functionsUsing name
+
+  _functionsUsing: (name) -> (n for n, f of @userFunctions when _.includes @functionsUsedBy(n), name)
+
   _recalculate: ->
     for name, subj of @userFunctionSubjects
       subj.onNext @_sheetValue name

-  _sheetValue: (name) ->
-    ops = new Operations name  #TODO decide where error checking belongs, whether to throw or return error directly
-    try
-      ops._valueCheck @sheet[name].apply @sheet, []
-    catch e
-      ops._error e
+  _sheetValue: (name) -> @sheet[name].apply @sheet, []

   _allFunction: (name) ->
     runner = this
@@ -208,6 +218,17 @@

       storedValues

+  _cachedValueFunction: (name, calcFunction) ->
+    runner = this
+    ops = new Operations name
+    ->
+      if runner.slots[name] is Invalid
+        runner.slots[name] = try
+            ops._valueCheck calcFunction.apply runner.sheet, []
+          catch e
+            ops._error e
+
+      runner.slots[name]

   _userFunctionSubject: (name) -> @userFunctionSubjects[name]
   _unknownUserFunctionSubject: (name) -> (@userFunctionSubjects[name] = @_newUserFunctionSubject(name, new CalculationError(name, "Unknown name: " + name)))


Use wd40.waitForMatchingURL.

Use wd40.waitForMatchingURL.

--- test/integration/tool_rpc.coffee
+++ test/integration/tool_rpc.coffee
@@ -37,9 +37,8 @@
             wd40.switchToTopFrame done

       it 'redirects the host to the specified URL', (done) ->
-        wd40.trueURL (err, url) ->
-          url.should.equal "#{base_url}/"
-          done()
+        regex = new RegExp "^#{base_url}/$"
+        wd40.waitForMatchingURL regex, done

     context 'when the redirect external button is pressed', ->
       before (done) ->
@@ -50,9 +49,7 @@
                 setTimeout done, 500

       it 'redirects the host to the specified URL', (done) ->
-        wd40.trueURL (err, url) ->
-          url.should.equal 'http://www.google.com/robots.txt'
-          done()
+        wd40.waitForMatchingURL /^http:\/\/www.google.com\/robots.txt$/, done

     context 'when the showURL button is pressed', ->
       before (done) ->


Fix bug where stubs were attached to undefined variables

--- services/web/test/unit/coffee/Subscription/SubscriptionViewModelBuilderTests.coffee
+++ services/web/test/unit/coffee/Subscription/SubscriptionViewModelBuilderTests.coffee
@@ -38,8 +38,8 @@
 		@builder = SandboxedModule.require modulePath, requires:
 			"settings-sharelatex": { apis: { recurly: { subdomain: "example.com" }}}
 			"./RecurlyWrapper": @RecurlyWrapper
-			"./PlansLocator": @PlansLocator
-			"./SubscriptionLocator": @SubscriptionLocator
+			"./PlansLocator": @PlansLocator = {}
+			"./SubscriptionLocator": @SubscriptionLocator = {}
 			"./SubscriptionFormatters": @SubscriptionFormatters
 			"./LimitationsManager": {}
 			"logger-sharelatex":


Formatting.

Formatting.

--- public/src/steam-api/steam-api-caller.coffee
+++ public/src/steam-api/steam-api-caller.coffee
@@ -89,7 +89,7 @@
         url = API_METHODS[methodName].url
         validateCallback = API_METHODS[methodName].validateCallback

-        (options = {}, cache=true) ->
+        (options = {}, cache = true) ->
           queryString = createQueryString(possibleOptions, options)

           if cache


apply change to cash-balance widget

apply change to cash-balance widget

--- src/components/widgets/accounts-cash-balance/accounts-cash-balance.directive.coffee
+++ src/components/widgets/accounts-cash-balance/accounts-cash-balance.directive.coffee
@@ -63,13 +63,19 @@
   $scope.chartId = ->
     "cashBalanceChart-#{w.id}"

+  updateLocked = false
+  zoomMetadata = {}
   onZoom = (event) ->
-    metadataHash = angular.merge w.metadata, {
+    zoomMetadata = angular.merge w.metadata, {
       xAxis:
         max: event.max
         min: event.min
     }
-    ImpacWidgetsSvc.update(w, { metadata: metadataHash }, false)
+    unless updateLocked
+      updateLocked = true
+      $timeout ->
+        ImpacWidgetsSvc.update(w, { metadata: zoomMetadata }, false).finally(-> updateLocked = false)
+      , 1000

   # Called after initContext - draws the chart using HighCharts
   w.format = ->


removed console logs

removed console logs

--- scripts/recent-posts.coffee
+++ scripts/recent-posts.coffee
@@ -38,7 +38,6 @@
     timeElement.appendChild(yearElement)
     liElement.appendChild(timeElement)
     liElement.appendChild(divElement)
-    console.log(liElement)
     return liElement

   writeData = (posts) ->
@@ -51,7 +50,6 @@
     blogListElement.appendChild ulElement

   parseData = (xmlChildNodes) ->
-    console.log xmlChildNodes
     for post in xmlChildNodes
       post =
         datetimeString: post.childNodes[0].innerHTML


reference correct event

reference correct event

--- resources/assets/lib/play-detail-menu.coffee
+++ resources/assets/lib/play-detail-menu.coffee
@@ -39,6 +39,7 @@

   hide: (e) =>
+    event = e.originalEvent
     @setState active: false if event.button == 0 && !(@menu.current in event.composedPath())
     @props.onHide?()


Fix indentation

Fix indentation

--- resources/assets/lib/react-turbolinks.coffee
+++ resources/assets/lib/react-turbolinks.coffee
@@ -29,22 +29,22 @@

   boot: =>
-      for own _name, component of @components
-        for target in component.targets
-          continue if target.dataset.reactTurbolinksLoaded == '1'
-          target.dataset.reactTurbolinksLoaded = '1'
-          @targets.push target
-          ReactDOM.render React.createElement(component.element, component.propsFunction(target)), target
+    for own _name, component of @components
+      for target in component.targets
+        continue if target.dataset.reactTurbolinksLoaded == '1'
+        target.dataset.reactTurbolinksLoaded = '1'
+        @targets.push target
+        ReactDOM.render React.createElement(component.element, component.propsFunction(target)), target

   destroy: =>
-      @navigated = true
+    @navigated = true

-      for own _name, component of @components
-        for target in component.targets
-          continue if target.dataset.reactTurbolinksLoaded != '1'
-          target.dataset.reactTurbolinksLoaded = null
-          ReactDOM.unmountComponentAtNode target if !component.persistent
+    for own _name, component of @components
+      for target in component.targets
+        continue if target.dataset.reactTurbolinksLoaded != '1'
+        target.dataset.reactTurbolinksLoaded = null
+        ReactDOM.unmountComponentAtNode target if !component.persistent

   destroyPersisted: =>


Add price and cache version to add URL

Add price and cache version to add URL

- Add price parameter from sku.bestPrice
- Add cache version (cv) from sku.cacheVersionUsedToCallCheckout

--- src/script/portal-buy-button.coffee
+++ src/script/portal-buy-button.coffee
@@ -10,6 +10,8 @@
 		@quantity = buyData.quantity || 1
 		@seller = buyData.seller || 1
 		@salesChannel = buyData.salesChannel || 1
+		@bestPrice = buyData.bestPrice
+		@cacheVersionUsedToCallCheckout = buyData.cacheVersionUsedToCallCheckout

 		if @options.multipleProductIds
 			@manyProducts = {}
@@ -18,6 +20,8 @@
 					sku: null
 					quantity: 1
 					seller: 1
+					bestPrice: null
+					cacheVersionUsedToCallCheckout: null

 		@accessories = []

@@ -67,6 +71,14 @@
 			if salesChannelMatch and salesChannelMatch[1] and salesChannelMatch[1] != @salesChannel
 				@salesChannel = salesChannelMatch[1]

+			bestPriceMatch = href.match(/price=(.*?)&/)
+			if bestPriceMatch and bestPriceMatch[1] and bestPriceMatch[1] != @bestPrice
+				@bestPrice = bestPriceMatch[1]
+
+			cvMatch = href.match(/cv=(.*?)&/)
+			if cvMatch and cvMatch[1] and cvMatch[1] != @cacheVersionUsedToCallCheckout
+				@cacheVersionUsedToCallCheckout = cvMatch[1]
+
 		@_url = href

 	skuSelected: (evt, productId, sku) =>
@@ -78,6 +90,8 @@
 			@skuData = sku
 			@sku = sku.sku
 			@seller = sku.sellerId
+			@bestPrice = sku.bestPrice
+			@cacheVersionUsedToCallCheckout = sku.cacheVersionUsedToCallCheckout

 		@update()
 		@element.click() if @options.instaBuy and sku.available
@@ -90,6 +104,8 @@
 		else
 			@skuData = {}
 			@sku = null
+			@bestPrice = null
+			@cacheVersionUsedToCallCheckout = null

 		@update()

@@ -119,10 +135,14 @@
 				queryParams.push("sku=#{prod.sku.sku}")
 				queryParams.push("qty=#{prod.quantity}")
 				queryParams.push("seller=#{prod.seller}")
+				queryParams.push("price=#{prod.sku.bestPrice}")
+				queryParams.push("cv=#{prod.sku.cacheVersionUsedToCallCheckout}")
 		else
 			queryParams.push("sku=#{@sku}")
 			queryParams.push("qty=#{@quantity}")
 			queryParams.push("seller=#{@seller}")
+			queryParams.push("price=#{@bestPrice}")
+			queryParams.push("cv=#{@cacheVersionUsedToCallCheckout}")

 		queryParams.push("redirect=#{@options.redirect}")
 		queryParams.push("sc=#{@salesChannel}")
@@ -131,6 +151,8 @@
 			queryParams.push("sku=#{acc.sku}")
 			queryParams.push("qty=#{acc.quantity}")
 			queryParams.push("seller=#{acc.sellerId}")
+			queryParams.push("price=#{acc.bestPrice}")
+			queryParams.push("cv=#{acc.cacheVersionUsedToCallCheckout}")

 		if @options.giftRegistry
 			queryParams.push("gr=#{@options.giftRegistry}")


Comment

Comment

--- services/web/public/coffee/ide/AutoCompileOnboardingController.coffee
+++ services/web/public/coffee/ide/AutoCompileOnboardingController.coffee
@@ -6,6 +6,8 @@
 		popover = angular.element('.onboarding__autocompile')
 		{ top, left } = recompileBtn.offset()

+		# If pdf panel smaller than recompile button + popover, show to left.
+		# Otherwise show to right
 		if $scope.ui.pdfWidth < 475
 			$scope.placement = 'left'
 			popover.offset({


Prevent freezing outgoing

Prevent freezing outgoing

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -79,7 +79,26 @@
 		return

 	try
-		result = script[method](params)
+		sandbox =
+			_: _
+			s: s
+			console: console
+			Store:
+				set: (key, val) ->
+					return store[key] = val
+				get: (key) ->
+					return store[key]
+			HTTP: (method, url, options) ->
+				try
+					return {} =
+					result: HTTP.call method, url, options
+				catch e
+					return {} =
+					error: e
+			script: script
+			method: method
+			params: params
+		result = vm.runInNewContext('script[method](params)', sandbox, { timeout: 3000 })

 		logger.outgoing.debug '[Script method [', method, '] result of Trigger', integration.name, ':]'
 		logger.outgoing.debug result


Implement form handling 🍻

Implement form handling 🍻

--- resources/coffee/main.coffee
+++ resources/coffee/main.coffee
@@ -79,3 +79,50 @@
             _ '.input-dynamic-date'
                 .val _(@).find('.active').data 'value'

+    _ '#main-form'
+        .submit (event) ->
+            event.preventDefault()
+            form = _ event.target
+            swal
+                title: '...'
+                showConfirmButton: no
+            _.post form.attr('action'), form.serialize(), (response) ->
+                switch response.status
+                    when 'error', 'not_found'
+                        titles =
+                            error: 'ცხრილი არ დევს'
+                            not_found: 'ცხრილში ვერ გიპოვე'
+                        texts =
+                            error: 'თარიღი ხომ არ შეგეშალა? შეიძლება ჯერ არ დადებულა ინფორმაცია, მოგვიანებით ნახე.'
+                            not_found: 'სახელი და გვარი გადაამოწმე, ან იქნებ დრო მიუთითე შეცდომით? იმიტომ, რომ ცხრილში ვერ გნახე.'
+                        swal
+                            type: 'error'
+                            title: titles[response.status]
+                            text: texts[response.status] + """

+                                თუ ფიქრობ, რომ რამე შეცდომაა, მაშინ აბა
+                                აქ ნახე, შეიძლება მანდ ეწეროს."""
+                            html: yes
+                    when 'found_one'
+                        student = response.results[0]
+                        swal
+                            type: 'success'
+                            title: "სექტორი #{student.sector}, ადგილი #{student.seat}"
+                            text: "საგანი: #{student.subject}
აბა წარმატებები!"
+                            html: yes
+                    when 'found_many'
+                        text = ''
+                        for i, student of response.results
+                            text += "
#{student.subject}
სექტორი #{student.sector}, ადგილი #{student.seat}"
+                        swal
+                            type: 'success'
+                            title: "სეხნია/მოგვარე გყოლია :)"
+                            text: "ნახე აბა რომელ საგანს წერ შენ:" + text
+                            html: yes
+            .fail () ->
+                swal
+                    type: 'error'
+                    title: 'არ ვიცი რა მოხდა O.o'
+                    text: 'მგონი ინტერნეტის პრობლემა უნდა იყოს. ხელახლა სცადე'
+
+


Remove console logs

Remove console logs

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -622,7 +622,6 @@
             map = store.directory_listings.set(path, if err then misc.to_json(err) else immutable.fromJS(listing.files))
             @setState(directory_listings : map)
             delete @_set_directory_files_lock[_key] # done!
-            opts.cb(err, listing)
         )

     # Increases the selected file index by 1
@@ -1215,12 +1214,7 @@
                         timeout : 30
                         cb      : (err, item) =>
                             if err
-                                console.log "ERR", err
-                                @open_file
-                                    path       : full_path
-                                    foreground : foreground
-                                    foreground_project : foreground
-                                alert_message(type:'error', message:"There was an error related to opening the link: #{err}")
+                                alert_message(type:'error', message:"There was an error related to opening #{full_path}: #{err}")
                                 @open_directory(parent_path)
                             else
                                 item = listing?.find (val) => val.get('name') == last
@@ -1545,7 +1539,6 @@
         method = salvus_client.project_directory_listing
     else
         method = salvus_client.public_project_directory_listing
-        console.log "FETCHING WITH PUBLIC VALS"
     listing = undefined
     f = (cb) ->
         method


fix an exception to use proper javascript

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -467,7 +467,7 @@
         path ?= ''
         if typeof path != 'string'
             window.cpath_args = arguments
-            throw "Current path should be a string. Revieved arguments are available in window.cpath_args"
+            throw Error("Current path should be a string. Revieved arguments are available in window.cpath_args")
         # Set the current path for this project. path is either a string or array of segments.
         @setState
             current_path           : path


Make constructor calls nicer.

Make constructor calls nicer.

--- src/spec/common-updater.spec.coffee
+++ src/spec/common-updater.spec.coffee
@@ -7,19 +7,19 @@
     expect(updater).toBeDefined()

   it 'should create logger', ->
-    updater = new CommonUpdater( { logentries_token: '123' } )
+    updater = new CommonUpdater(logentries_token: '123')
     expect(updater.log).toBeDefined()

 describe '#initProgressBar', ->
   it 'should create progress bar', ->
-    updater = new CommonUpdater( { show_progress: true } )
+    updater = new CommonUpdater(show_progress: true)
     updater.initProgressBar 'foo', 42
     expect(updater.bar).toBeDefined()
     updater.bar.terminate()

 describe '#tickProgress', ->
   it 'should update progress bar', ->
-    updater = new CommonUpdater( { show_progress: true } )
+    updater = new CommonUpdater(show_progress: true)
     updater.initProgressBar 'bar', 2
     expect(updater.bar.curr).toBe 0
     updater.tickProgress()
@@ -29,7 +29,7 @@

 describe '#returnResult', ->
   it 'should terminate progress bar', (done) ->
-    updater = new CommonUpdater( { show_progress: true } )
+    updater = new CommonUpdater(show_progress: true)
     updater.initProgressBar 'blabla', 7
     updater.returnResult true, 'some text', (ret) ->
       done()
@@ -60,7 +60,7 @@
       done()

   it 'should send info log to logentries', (done) ->
-    updater = new CommonUpdater( { logentries_token: 'abc' } )
+    updater = new CommonUpdater(logentries_token: 'abc')
     spyOn(updater.log, 'log')
     updater.returnResult true, 'txt', (ret) ->
       expectedMessage =
@@ -71,7 +71,7 @@
       done()

   it 'should send error log to logentries', (done) ->
-    updater = new CommonUpdater( { logentries_token: 'abc' } )
+    updater = new CommonUpdater(logentries_token: 'abc')
     spyOn(updater.log, 'log')
     updater.returnResult false, [ 'Oops' ], (ret) ->
       expectedMessage =


Wrong variable name

Wrong variable name

--- resources/assets/coffee/react/profile-page/extra.coffee
+++ resources/assets/coffee/react/profile-page/extra.coffee
@@ -53,7 +53,7 @@
     anchorHeight = window.innerHeight * 0.5

     if osu.bottomPage()
-      @setState mode: _.last(elements).getAttribute('id')
+      @setState mode: _.last(pages).getAttribute('id')
       return

     # FIXME: I don't remember why this one scans from bottom while


fixing find_all method

fixing find_all method

--- src/toaster/utils/array-util.coffee
+++ src/toaster/utils/array-util.coffee
@@ -27,19 +27,23 @@

 			by_property = [].concat by_property
 			for v, k in source
+
 				for p in by_property
+					item = null
+
 					if regexp
 						if search.test v[p]
-							if unique && _unique[k]?
+							if unique && !_unique[k]
 								item = {item: v, index: k}
-							else if !unique
+							else if unique is not true
 								item = {item: v, index: k}
 					else
 						if search == v[p]
 							item = {item: v, index: k}

 					if item
-						_output.push _unique[k] = item
+						_unique[k] = true
+						_output.push item

 		return _output


xit out tempremental protractor spec

xit out tempremental protractor spec

--- angular/test/protractor/login_spec.coffee
+++ angular/test/protractor/login_spec.coffee
@@ -181,7 +181,7 @@
       page.expectFlash 'You are now a member of Open Dirty Dancing Shoes'
       page.expectElement '.comment-form__submit-button'

-    it 'can use a shareable link', ->
+    xit 'can use a shareable link', ->
       page.loadPath 'view_closed_group_with_shareable_link'
       page.fillIn '.auth-email-form__email input', 'max_von_sydow@example.com'
       page.click '.auth-email-form__submit'


bugfix: add argument check

bugfix: add argument check

--- src/controllers/spaces.coffee
+++ src/controllers/spaces.coffee
@@ -17,6 +17,9 @@
             }

     add: (req, res)->
+        unless req.body.x && req.body.y
+            res.send 'Invalid parameters. Please check x and y'
+            res.statusCode = 500
         camp = new Camp (
             namespace: req.params.namespace
             x: req.body.x


Move WorkersView and JobsView to Travis namespace

That way those views can be overriden easily

--- assets/scripts/app/views/sidebar.coffee
+++ assets/scripts/app/views/sidebar.coffee
@@ -1,4 +1,20 @@
 @Travis.reopen
+  WorkersView: Em.View.extend
+    templateName: 'workers/list'
+    init: ->
+      @_super.apply this, arguments
+      @set 'controller', @get('controller').container.lookup('controller:workers')
+
+  JobsView: Em.View.extend
+    templateName: 'jobs/running'
+    elementId: 'running-jobs'
+    init: ->
+      @_super.apply this, arguments
+      @set 'controller', @get('controller').container.lookup('controller:runningJobs')
+
+    groupsBinding: 'controller.sortedGroups'
+    jobsBinding: 'controller'
+
   SidebarView: Travis.View.extend
     templateName: 'layouts/sidebar'

@@ -10,7 +26,7 @@
     activate: (name) ->
       return if @get('activeTab') == name
       @set('activeTab', name)
-      @connectOutlet 'pane', @["#{name.capitalize()}View"].create(controller: @get('controller'))
+      @connectOutlet 'pane', Travis["#{name.capitalize()}View"].create(controller: @get('controller'))

     classQueues: (->
       'active' if @get('activeTab') == 'queues'
@@ -36,27 +52,11 @@
         @_super.apply this, arguments
         @set 'controller', @get('controller').container.lookup('controller:links')

-    WorkersView: Em.View.extend
-      templateName: 'workers/list'
-      init: ->
-        @_super.apply this, arguments
-        @set 'controller', @get('controller').container.lookup('controller:workers')
-
     QueuesView: Em.View.extend
       templateName: 'queues/list'
       init: ->
         @_super.apply this, arguments
         @set 'controller', @get('controller').container.lookup('controller:queues')
-
-    JobsView: Em.View.extend
-      templateName: 'jobs/running'
-      elementId: 'running-jobs'
-      init: ->
-        @_super.apply this, arguments
-        @set 'controller', @get('controller').container.lookup('controller:runningJobs')
-
-      groupsBinding: 'controller.sortedGroups'
-      jobsBinding: 'controller'

       GroupView: Em.View.extend
         templateName: 'jobs/running/group'


[#88683] Export Raw should not submit if prompt is canceled

--- app/assets/javascripts/app/reports.coffee
+++ app/assets/javascripts/app/reports.coffee
@@ -93,6 +93,8 @@
       .click (event) => @export_all_email_confirm(event)

   export_all_email_confirm: (event) ->
+    event.preventDefault()
+
     new_to = prompt(
       'Have the report emailed to this address:'
       @$emailToAddressField.val()
@@ -104,7 +106,8 @@
       # Actual sending handled by remote: true
       Flash.info("A report is being prepared and will be emailed to #{new_to}
         when complete")
-    event.preventDefault()
+    else
+      false # prevent handling by remote: true

 $ ->
   window.report = new TabbableReports($('#refresh-form'))


Fixed merge conflicts

Fixed merge conflicts

--- app/assets/javascripts/plugins/tag.coffee
+++ app/assets/javascripts/plugins/tag.coffee
@@ -57,6 +57,7 @@
   sel: '#btag-selector'
   constructor: (@options) ->
     @input = '#tags'
+
     throw 'You must set a model in options in TagSelector class'  unless @options.model?
     @model = @options.model
     throw 'You must set a data in options in TagSelector class'  unless @options.data?


Don't redirect to home when song is restored.

React is panacea. Rerender all FTW. I tried something similar with
este.domMerge, but fortunatelly Facebook solved the Problem for us in a
beautiful way.

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -161,7 +161,8 @@

       onToggleDeleteButtonPointerUp: ->
         userStore.trashSong song, !song.inTrash
-        routes.home.redirect()
+        if song.inTrash
+          routes.home.redirect()

       getLyricsHistory: (song) ->
         userStore.getSongLyricsLocalHistory song


DA - correct grading in gb

DA - correct grading in gb

fixes CNVS-16491

test plan:
 * setup DA
 - navigate to gradebook
 > assignments that are not visible to a student should not affect
   her grades
 - turn on "Treat ungraded as 0"
 > assignments that are not visible to a student should still not
   affect her grades

Change-Id: I1ad58b2984a4306cff89038a3d46d7f3c99c8fce
Reviewed-on: https://gerrit.instructure.com/43528
Reviewed-by: Mike Nomitch <74443f6f110b6b7b934c89126258ed3d843d673f@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Sean Lewis <e0580b9aacf857d9a3792f1965500f217c0fc9c9@instructure.com>
Product-Review: Hilary Scharton <b55b20a171673afabe1f3f421d6770a86afaa815@instructure.com>

--- app/coffeescripts/grade_calculator.coffee
+++ app/coffeescripts/grade_calculator.coffee
@@ -28,7 +28,7 @@
       result.group_sums = _(groups).map (group) =>
         group: group
         current: @create_group_sum(group, submissions, true)
-        'final':   @create_group_sum(group, submissions, false)
+        'final': @create_group_sum(group, submissions, false)
       result.current  = @calculate_total(result.group_sums, true, weighting_scheme)
       result['final'] = @calculate_total(result.group_sums, false, weighting_scheme)
       result
@@ -40,8 +40,13 @@
           obj[e[property]] = e
         obj

+      # remove assignments without visibility from gradeableAssignments
+      hidden_assignment_ids = _.chain(submissions).
+                                filter( (s)-> s.hidden).
+                                map( (s)-> s.assignment_id).value()
+
       gradeableAssignments = _(group.assignments).filter (a) ->
-        not _.isEqual(a.submission_types, ['not_graded'])
+        not _.isEqual(a.submission_types, ['not_graded']) and not _(hidden_assignment_ids).contains(a.id)
       assignments = arrayToObj gradeableAssignments, "id"

       # filter out submissions from other assignment groups


fix(LMS-1771):fixed issue merging code

fix(LMS-1771):fixed issue merging code

--- app/coffeescripts/models/Pseudonym.coffee
+++ app/coffeescripts/models/Pseudonym.coffee
@@ -23,10 +23,12 @@
         repeated:     I18n.t("errors.repeated", "Can't have the same character more than %{max} times in a row", {max: policy.max_repeats})
         confirmation: I18n.t("errors.mismatch", "Doesn't match")
         too_long:     I18n.t("errors.too_long", "Can't exceed %{max} characters", {max: 255})
+      password_confirmation:
+        confirmation: I18n.t("errors.mismatch", "Doesn't match")

     normalizeErrors: (errors, policy) ->
       if errors
-        for type in ['unique_id', 'password'] when errors[type]?.length > 1
+        for type in ['unique_id', 'password', 'password_confirmation'] when errors[type]?.length > 1
           # if there are multiple errors and one is "too_short", just show that
           too_short = null
           too_short = e for e in errors[type] when e.type is 'too_short'


Add a method to get the HTTP Auth token

Add a method to get the HTTP Auth token

--- app/src/restclients/authentication.coffee
+++ app/src/restclients/authentication.coffee
@@ -96,6 +96,14 @@
       @_authToken = data['token']
       deferred.resolve()

+  getAuthToken: (callback = null) ->
+    completion = new CompletionClosure(callback)
+    if @isAuthenticated()
+      completion.success(@_authToken)
+    else
+      @_authenticate().then(-> completion.success(@_authToken)).fail((ex) -> completion.failure(ex))
+    completion.readonly()
+
   @instance: (baseUrl = ledger.config.restClient.baseUrl) -> @_instance ?= new @(baseUrl)

 _.extend ledger.api,


payment: code style

payment: code style

--- client/landing/app/Applications/Dashboard.kdapplication/views/producteditform.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/producteditform.coffee
@@ -97,10 +97,10 @@
       overageEnabled  = i.overageEnabled?.getValue()
       soldAlone       = i.soldAlone?.getValue()
       priceIsVolatile = i.priceIsVolatile?.getValue()
+      tags            = @utils.splitTrim i.tags?.getValue()
       feeAmount       =
         unless priceIsVolatile
         then i.feeAmount.getValue() * 100
-      tags            = @utils.splitTrim i.tags?.getValue()

       { subscriptionType, feeUnit, feeInterval } = @getPlanInfo()


payment: product list item #enable/#disable #activate/#deactivate

--- client/landing/app/Applications/Dashboard.kdapplication/views/productlistitem.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/productlistitem.coffee
@@ -13,6 +13,20 @@
     @controls ?= new KDView
     @controls.addSubView controlsView

+  activate: ->
+    @setClass 'active'
+
+  deactivate: ->
+    @unsetClass 'active'
+
+  disable: ->
+    @setClass 'disabled'
+    view.disable?()  for view in @controls.subViews
+
+  enable: ->
+    @unsetClass 'disabled'
+    view.enable?()  for view in @controls.subViews
+
   pistachio: ->
     """
     <div class="product-item">


Fix spec title.

Fix spec title.

--- spec/javascripts/application/seeds_store_spec.js.coffee
+++ spec/javascripts/application/seeds_store_spec.js.coffee
@@ -1,4 +1,4 @@
-describe 'PodsList', ->
+describe 'SeedsStore', ->
   beforeEach (done) ->
     @subject = new SeedsStore('test')
     expect(@subject.clear()).eventually.notify(done)


Refactoring

Refactoring

--- src/generation/manager.coffee
+++ src/generation/manager.coffee
@@ -23,12 +23,11 @@
 		[path, level] = id.split '-'
 		level = Number level

+		@generateConnections id, path, level
+
 		map = @handleMap id, path, level

 		map.id = id
-
-		FeatureGen.generateFeatures path, level, map
-		@handleCreatures map, path, level

 		map

@@ -40,14 +39,18 @@
 			@addConnection thisMap, name, map, target

 	handleMap: (id, path, level) ->
-		@generateConnections id, path, level
 		connections = @getConnections id

-		if level is 1
-			@generateStart path, 1, connections
+		map =
+			if level is 1
+				@generateStart path, 1, connections

-		else if level > 1
-			@generateCave path, (level - 1), connections
+			else if level > 1
+				@generateCave path, (level - 1), connections
+
+		FeatureGen.generateFeatures path, level, map
+
+		map

 	generateStart: (path, level, connections) ->
 		MapGen.generateBigRoom path, level, connections, 80, 21
@@ -55,11 +58,9 @@
 	generateCave: (path, level, connections) ->
 		map = MapGen.generateCellularAutomata path, level, connections, 100, 50

+		@generateCaveEntities map, path, level
+
 		map
-
-	handleCreatures: (map, path, level) ->
-		if level > 1
-			@generateCaveEntities map, path, (level - 1)

 	generateCaveEntities: (map, path, level) ->
 		## Creatures


Remove deprecated class

Remove deprecated class

--- src/coffee/loggers.coffee
+++ src/coffee/loggers.coffee
@@ -1,32 +0,0 @@
-{Logger} = require 'sphere-node-utils'
-
-class ProductImportLogger extends Logger
-
-  @appName: 'brickfox-product-import-logger'
-  @path: './brickfox-product-import-logger.log'
-  @levelStream: 'debug'
-
-class ProductUpdateImportLogger extends Logger
-
-  @appName: 'brickfox-product-update-import-logger'
-  @path: './brickfox-product-update-import-logger.log'
-  @levelStream: 'debug'
-
-class OrderExportLogger extends Logger
-
-  @appName: 'brickfox-order-export-logger'
-  @path: './brickfox-order-export-logger.log'
-  @levelStream: 'debug'
-
-class OrderStatusImportLogger extends Logger
-
-  @appName: 'brickfox-order-status-import-logger'
-  @path: './brickfox-order-status-import-logger.log'
-  @levelStream: 'debug'
-
-
-module.exports =
-  ProductImportLogger: ProductImportLogger
-  ProductUpdateImportLogger: ProductUpdateImportLogger
-  OrderExportLogger: OrderExportLogger
-  OrderStatusImportLogger: OrderStatusImportLogger


Apply same loading logic on both crop and upload forms

--- BackofficeBundle/Resources/public/coffee/mediatheque/SuperboxView.coffee
+++ BackofficeBundle/Resources/public/coffee/mediatheque/SuperboxView.coffee
@@ -147,6 +147,20 @@
             currentView.addEventOnCropForm()
     return

+  addEventOnOverrideForm: ->
+    currentView = this
+    $(".media-override-format-form form", @$el).on "submit", (e) ->
+      $('.media-override-format-form').hide()
+      $('.media_crop_preview img', @$el).hide()
+      $('#image-loader').show()
+      e.preventDefault() # prevent native submit
+      $(this).ajaxSubmit
+        statusCode:
+          200: (response) ->
+            currentView.refreshImages()
+            currentView.addEventOnOverrideForm()
+    return
+
   addEventOnMetaForm: ->
     currentView = this
     $(".media_meta_form form", @$el).on "submit", (e) ->
@@ -191,18 +205,6 @@
           $('.media-override-format-form').html response
           currentView.addEventOnOverrideForm()

-  addEventOnOverrideForm: ->
-    currentView = this
-    $(".media-override-format-form form", @$el).on "submit", (e) ->
-      displayLoader('.media-override-format-form')
-      e.preventDefault() # prevent native submit
-      $(this).ajaxSubmit
-        statusCode:
-          200: (response) ->
-            currentView.refreshImages()
-            currentView.addEventOnOverrideForm()
-    return
-
   cropImage: ->
     $("#media_crop").submit()
 )


Rewrite random test

Rewrite random test

--- test/models/mixins/deep_zoom.coffee
+++ test/models/mixins/deep_zoom.coffee
@@ -19,10 +19,12 @@
         @image.canDeepZoom().should.be.ok()
         deepZoomAttrs = ['tile_base_url', 'tile_size', 'tile_overlap', 'tile_format', 'max_tiled_height', 'max_tiled_width']

-        # Select an attribute and random and unset it
-        attr = deepZoomAttrs[Math.floor(Math.random() * deepZoomAttrs.length)]
-        @image.unset attr
-        @image.canDeepZoom().should.not.be.ok()
+        # Un-set each attribute and verify image is no longer zoomable
+        _.each(deepZoomAttrs, (attr) =>
+          @image.unset attr
+          @image.canDeepZoom().should.not.be.ok()
+          @image.set attr, 'blah'
+        )

       it 'ensures a trailing slash', ->
         @image.set 'tile_base_url', 'http://static0.artsy.net/additional_images/4e7cb83e1c80dd00010038e2/1/dztiles-512-0'


ide: code refactoring for commit f4757534e5f7a6d816c9dacb199e360819ec5c79

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -89,9 +89,8 @@

   createShortcutsView: ->
     name = 'Shortcuts'
-    for pane, index in @tabView.panes when name is pane.name
-      @tabView.showPaneByIndex index
-      return
+    pane = @tabView.getPaneByName name
+    return @tabView.showPane pane  if pane
     @createPane_ new IDE.ShortcutsView, { name: name }

   createTerminal: (machine, path) ->


IDEView: Added a defensive check.

IDEView: Added a defensive check.

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -154,7 +154,7 @@
     @createPreview vm.hostnameAlias

   closeUntitledFileIfNotChanged: ->
-    for pane in @tabView.panes
+    for pane in @tabView.panes when pane
       if pane.data instanceof FSFile and pane.data.path is @getDummyFilePath()
         if pane.subViews.first.getValue() is ''
           @tabView.removePane pane


remove extra semicolon

remove extra semicolon

--- client/js/services/album_service.coffee
+++ client/js/services/album_service.coffee
@@ -8,7 +8,7 @@

 exports.getAsync = (path) ->
   new Promise (resolve, reject) ->
-    xhr = new XMLHttpRequest;
+    xhr = new XMLHttpRequest
     xhr.addEventListener "error", reject
     xhr.addEventListener "load", resolve
     xhr.open "GET", path


Don't show the mini review panel when the only visible entry is the add comment one.

--- services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
+++ services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
@@ -163,7 +163,10 @@

 		$scope.$watch (() ->
 			entries = $scope.reviewPanel.entries[$scope.editor.open_doc_id] or {}
-			Object.keys(entries).length
+			permEntries = {}
+			for entry, entryData of entries
+				permEntries[entry] = entryData if entry != "add-comment"
+			Object.keys(permEntries).length
 		), (nEntries) ->
 			$scope.reviewPanel.hasEntries = nEntries > 0 and $scope.project.features.trackChangesVisible


Tidy up ReviewPanelController

Tidy up ReviewPanelController

--- services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
+++ services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
@@ -9,33 +9,38 @@
 		scroller = $element.find(".review-panel-scroller")
 		list = $element.find(".review-entry-list")

+		# Use these to avoid unnecessary updates. Scrolling one
+		# panel causes us to scroll the other panel, but there's no
+		# need to trigger the event back to the original panel.
 		ignoreNextPanelEvent = false
 		ignoreNextAceEvent = false

-		$scope.onScroll = (scrollTop, height) ->
+		$scope.scrollEvents = new EventEmitter()
+
+		scrollPanel = (scrollTop, height) ->
 			if ignoreNextAceEvent
-				# console.log "Ignoring ace event"
 				ignoreNextAceEvent = false
 			else
 				ignoreNextPanelEvent = true
 				list.height(height)
 				scroller.scrollTop(scrollTop)
-
-		$scope.scrollEvents = new EventEmitter()

 		scrollAce = (e) ->
-			now = new Date()
 			if ignoreNextPanelEvent
-				# console.log "Ignoring review panel event"
 				ignoreNextPanelEvent = false
 			else
-				# console.log "review panel scrolled", e
 				ignoreNextAceEvent = true
 				$scope.scrollEvents.emit "scroll", e.target.scrollTop
-				lastScroll = now

-		previousScroll = new Date()
 		scroller.on "scroll", scrollAce
+		$scope.onScroll = scrollPanel # Passed into the editor directive for it to call
+
+		# If we listen for scroll events in the review panel natively, then with a Mac trackpad
+		# the scroll is very smooth (natively done I'd guess), but we don't get polled regularly
+		# enough to keep Ace in step, and it noticeably lags. If instead, we borrow the manual
+		# mousewheel/trackpad scrolling behaviour from Ace, and turn mousewheel events into
+		# scroll events ourselves, then it makes the review panel slightly less smooth (barely)
+		# noticeable, but keeps it perfectly in step with Ace.
 		ace.require("ace/lib/event").addMouseWheelListener scroller[0], (e) ->
 			deltaY = e.wheelY
 			# console.log "mousewheel", deltaY


:memo: Mention css

:memo: Mention css

--- src/selector-parser.coffee
+++ src/selector-parser.coffee
@@ -17,7 +17,7 @@
     selectorCache[selector] = false
     false

-# Parse the given selector and throw an error if it is invalid
+# Parse the given CSS selector and throw an error if it is invalid.
 exports.validateSelector = (selector) ->
   return if exports.isSelectorValid(selector)


payment: fix reference error

payment: fix reference error

--- client/Main/payment/stripeformview.coffee
+++ client/Main/payment/stripeformview.coffee
@@ -41,11 +41,11 @@
           cardType   : do =>
             cssClass = null
             return (input, event) =>
-              @form.unsetClass cssClass  if cssClass
+              @unsetClass cssClass  if cssClass
               val = $.trim input.getValue().replace(/-|\s/g,"")
               cssClass = (Stripe.card.cardType val).toLowerCase()
               cssClass = KD.utils.slugify cssClass
-              @form.setClass cssClass
+              @setClass cssClass

         events       :
           cardType   : 'keyup'
@@ -118,7 +118,7 @@
         rules            :
           clear          : clearValidationErrors
           checkMonthYear : (yearInput, event) =>
-            monthInput = @form.inputs.cardMonth
+            monthInput = @inputs.cardMonth
             monthVal   = $.trim monthInput.getValue().replace(/-|\s/g, '')
             yearVal    = $.trim yearInput.getValue().replace(/-|\s/g, '')


Admin: Integrations: Added fetchConfigureData method.

--- client/app/lib/helpers/integration.coffee
+++ client/app/lib/helpers/integration.coffee
@@ -1,5 +1,6 @@
 kd           = require 'kd'
 remote       = require('app/remote').getInstance()
+globals      = require 'globals'
 doXhrRequest = require 'app/util/doXhrRequest'

@@ -140,6 +141,43 @@

   fetch()

+
+fetchConfigureData = (options, callback) ->
+
+  fetchChannels (err, channels) ->
+    return callback err  if err
+
+    fetch options, (err, response) ->
+      return callback err  if err
+
+      { integration, channelIntegration } = response
+
+      { id, token, createdAt, updatedAt, description,
+        integrationId, channelId, isDisabled } = channelIntegration
+
+      description     = description or integration.summary
+      webhookUrl      = "#{globals.config.integration.url}/#{integration.name}/#{token}"
+      integrationType = 'confiured'
+      selectedEvents  = []
+      data            = { channels, id, integration, token, createdAt,
+                          updatedAt, description, integrationId, webhookUrl, isDisabled }
+
+      if channelIntegration.settings
+        data.selectedEvents = try JSON.parse channelIntegration.settings.events catch e then []
+
+      if integration.settings?.events
+        events = try JSON.parse integration.settings.events catch e then null
+        data.settings = { events }
+
+      if integration.name is 'github'
+        fetchGithubRepos (err, repositories) =>
+          return callback err  if err
+          data.repositories = repositories
+          callback null, data
+      else
+        callback null, data
+
+
 module.exports = {
   list
   find
@@ -149,5 +187,6 @@
   fetchChannels
   regenerateToken
   fetchGithubRepos
+  fetchConfigureData
   fetchChannelIntegrations
 }


Register shared machine invitation

Register shared machine invitation

--- client/app/lib/machinesharemanager.coffee
+++ client/app/lib/machinesharemanager.coffee
@@ -41,3 +41,10 @@
   bindEvents: ->

     kd.singletons.notificationController
+
+      .on 'SharedMachineInvitation', @bound 'handleSharedMachineInvitation'
+
+
+  handleSharedMachineInvitation: (data) ->
+
+    @set data.uid, type: 'shared machine'


Encode less entities in setOutcome

Encode less entities in setOutcome

--- server/api/setOutcome.coffee
+++ server/api/setOutcome.coffee
@@ -1,4 +1,4 @@
-Entities = require('html-entities').AllHtmlEntities
+Entities = require('html-entities').XmlEntities
 entities = new Entities()

 import User from '../models/user'


zendesk: do not touch the verified field

--- src/smc-hub/support.coffee
+++ src/smc-hub/support.coffee
@@ -126,7 +126,6 @@
             user:
                 name         : if opts.username?.trim?().length > 0 then opts.username else opts.email_address
                 email        : opts.email_address
-                verified     : false  # if true: an email is sent to verify the email address and informed to get account
                 external_id  : opts.account_id ? null
                 # manage custom_fields here: https://sagemathcloud.zendesk.com/agent/admin/user_fields
                 custom_fields:


add DOCUMENT_UPDATER_HOST as option

add DOCUMENT_UPDATER_HOST as option

--- services/real-time/config/settings.defaults.coffee
+++ services/real-time/config/settings.defaults.coffee
@@ -33,7 +33,7 @@
 			user: "sharelatex"
 			pass: "password"
 		documentupdater:
-			url: "http://#{process.env['DOCUPDATER_HOST'] or "localhost"}:3003"
+			url: "http://#{process.env['DOCUMENT_UPDATER_HOST'] or process.env['DOCUPDATER_HOST'] or "localhost"}:3003"

 	security:
 		sessionSecret: "secret-please-change"


Make all requires of 'fs-utils' assign to fsUtils var instead of fs

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -2,7 +2,7 @@
 _ = require 'underscore'
 RootView = require 'root-view'
 StatusBar = require 'status-bar/lib/status-bar-view'
-fs = require 'fs-utils'
+fsUtils = require 'fs-utils'

 describe "StatusBar", ->
   [editor, statusBar, buffer] = []
@@ -57,7 +57,7 @@
   describe "when the buffer content has changed from the content on disk", ->
     it "disables the buffer modified indicator on save", ->
       path = "/tmp/atom-whitespace.txt"
-      fs.write(path, "")
+      fsUtils.write(path, "")
       rootView.open(path)
       expect(statusBar.bufferModified.text()).toBe ''
       editor.insertText("\n")
@@ -107,12 +107,12 @@

   describe "git branch label", ->
     beforeEach ->
-      fs.remove('/tmp/.git') if fs.isDirectory('/tmp/.git')
+      fsUtils.remove('/tmp/.git') if fsUtils.isDirectory('/tmp/.git')
       rootView.attachToDom()

     it "displays the current branch for files in repositories", ->
       path = require.resolve('fixtures/git/master.git/HEAD')
-      project.setPath(fs.resolveOnLoadPath('fixtures/git/master.git'))
+      project.setPath(fsUtils.resolveOnLoadPath('fixtures/git/master.git'))
       rootView.open(path)
       expect(statusBar.branchArea).toBeVisible()
       expect(statusBar.branchLabel.text()).toBe 'master'
@@ -128,22 +128,22 @@

     beforeEach ->
       path = require.resolve('fixtures/git/working-dir/file.txt')
-      newPath = fs.join(fs.resolveOnLoadPath('fixtures/git/working-dir'), 'new.txt')
-      fs.write(newPath, "I'm new here")
-      ignoredPath = fs.join(fs.resolveOnLoadPath('fixtures/git/working-dir'), 'ignored.txt')
-      fs.write(ignoredPath, 'ignored.txt')
+      newPath = fsUtils.join(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'), 'new.txt')
+      fsUtils.write(newPath, "I'm new here")
+      ignoredPath = fsUtils.join(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'), 'ignored.txt')
+      fsUtils.write(ignoredPath, 'ignored.txt')
       git.getPathStatus(path)
       git.getPathStatus(newPath)
-      originalPathText = fs.read(path)
+      originalPathText = fsUtils.read(path)
       rootView.attachToDom()

     afterEach ->
-      fs.write(path, originalPathText)
-      fs.remove(newPath) if fs.exists(newPath)
-      fs.remove(ignoredPath) if fs.exists(ignoredPath)
+      fsUtils.write(path, originalPathText)
+      fsUtils.remove(newPath) if fsUtils.exists(newPath)
+      fsUtils.remove(ignoredPath) if fsUtils.exists(ignoredPath)

     it "displays the modified icon for a changed file", ->
-      fs.write(path, "i've changed for the worse")
+      fsUtils.write(path, "i've changed for the worse")
       git.getPathStatus(path)
       rootView.open(path)
       expect(statusBar.gitStatusIcon).toHaveClass('modified-status-icon')
@@ -161,16 +161,16 @@
       expect(statusBar.gitStatusIcon).toHaveClass('ignored-status-icon')

     it "updates when a status-changed event occurs", ->
-      fs.write(path, "i've changed for the worse")
+      fsUtils.write(path, "i've changed for the worse")
       git.getPathStatus(path)
       rootView.open(path)
       expect(statusBar.gitStatusIcon).toHaveClass('modified-status-icon')
-      fs.write(path, originalPathText)
+      fsUtils.write(path, originalPathText)
       git.getPathStatus(path)
       expect(statusBar.gitStatusIcon).not.toHaveClass('modified-status-icon')

     it "displays the diff stat for modified files", ->
-      fs.write(path, "i've changed for the worse")
+      fsUtils.write(path, "i've changed for the worse")
       git.getPathStatus(path)
       rootView.open(path)
       expect(statusBar.gitStatusIcon).toHaveText('+1,-1')


quick fix

quick fix

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -181,7 +181,7 @@
             {"width":"15px";"max-height":aglh*2.6,
             "z-index": 20,"position":"relative",
             "top":offset-12-$(".ace_scrollbar").scrollTop()+"px",
-            "left":"32px","display": "block"})
+            "left":aglw-15+"px","display": "block"})
         @poffset = $(".ace_scrollbar").scrollTop()
         return


docs(browser support page): tweak verbage

--- src/client/scripts/constants/browsers-const.coffee
+++ src/client/scripts/constants/browsers-const.coffee
@@ -111,8 +111,8 @@
 		</rb-icon>
 	'
 	notes: '
-		Edge has major issues (no surprise),
-		however some things do work via including the
+		Edge has major issues, however
+		some things do work via including the
 		webcomponentsjs polyfill.
 	'
 	'full support': null


environment/flux: store recently created stack template in privates

--- client/app/lib/flux/environment/stores/privatestacktemplatesstore.coffee
+++ client/app/lib/flux/environment/stores/privatestacktemplatesstore.coffee
@@ -14,6 +14,7 @@

     @on actions.LOAD_PRIVATE_STACK_TEMPLATES_SUCCESS, @load
     @on actions.CHANGE_TEMPLATE_TITLE, @changeTitle
+    @on actions.CREATE_STACK_TEMPLATE_SUCCESS, @loadSingle

   load: (stackTemplates, { templates }) ->
@@ -23,6 +24,11 @@
         _templates.set template._id, toImmutable template

     return stackTemplates
+
+
+  loadSingle: (templates, { stackTemplate }) ->
+
+    templates.set stackTemplate._id, toImmutable stackTemplate

   changeTitle: (stackTemplates, { id, value }) ->


Workspace: Updated demo code.

Workspace: Updated demo code.

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

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


Workspace: Changed client terminal pane class for shared session.

--- client/landing/app/CommonViews/workspace/panel/collaborativepanel.coffee
+++ client/landing/app/CommonViews/workspace/panel/collaborativepanel.coffee
@@ -20,7 +20,12 @@
     PaneClass              = @getPaneClass paneOptions.type
     paneOptions.delegate   = @
     paneOptions.sessionKey = @getOptions().sessionKeys[@panes.length]  if @getOptions().sessionKeys
-    pane                   = new PaneClass paneOptions
+    isJoinedASession       = !!paneOptions.sessionKey
+
+    if isJoinedASession and paneOptions.type is "terminal"
+      PaneClass = CollaborativeClientTerminalPane
+
+    pane = new PaneClass paneOptions

     targetContainer.addSubView pane
     @panes.push pane


teams: join by existing user fix

teams: join by existing user fix

--- client/landing/site.landing/coffee/team/forms/teamjoinbyloginform.coffee
+++ client/landing/site.landing/coffee/team/forms/teamjoinbyloginform.coffee
@@ -1,3 +1,5 @@
+kd              = require 'kd.js'
+utils           = require './../../core/utils'
 TeamJoinTabForm = require './../forms/teamjointabform'
 LoginInputView  = require './../../login/logininputview'

@@ -17,7 +19,7 @@
           messages    : { required: 'Please enter a username.' }

-    teamData                = utils.getTeamData()
+    teamData = utils.getTeamData()
     if teamData.profile
       { firstName, nickname } = teamData.profile
       name     = "#{firstName or '@'+nickname}"


Close the editor before leaving the page

--- src/scripts/modules/media/editbar/modals/publish.coffee
+++ src/scripts/modules/media/editbar/modals/publish.coffee
@@ -48,7 +48,8 @@
         xhrFields:
           withCredentials: true
       .done () ->
-        # TODO: Close editor
+        # Close editor
+        @model.set('editable', false)

         # Redirect to workspace
         router.navigate('workspace', {trigger: true})


increase some sign in and account creation timeouts

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -710,7 +710,7 @@
             password       : required
             agreed_to_terms: required
             token          : undefined       # only required if an admin set the account creation token.
-            timeout        : 15
+            timeout        : 40
             cb             : required

         if not opts.agreed_to_terms
@@ -734,7 +734,7 @@
             password      : required
             remember_me   : false
             cb            : required
-            timeout       : 15
+            timeout       : 40

         @call
             message : message.sign_in


Added get_protocol_type_name helper method

--- dl/src/chain/serializer_validation.coffee
+++ dl/src/chain/serializer_validation.coffee
@@ -89,12 +89,19 @@
             throw new Error "unmatched #{regex} #{field_name}: #{value}"
         value

-    require_match:(regex, value, field_name="")->
+    require_match: require_match=(regex, value, field_name="")->
         return value if is_empty value
         match = value.match regex
         if match is null
             throw new Error "unmatched #{regex} #{field_name}: #{value}"
         match
+
+    require_object_id: require_object_id=(value, field_name)->
+        require_match(
+            /^([0-9]+)\.([0-9]+)\.([0-9]+)$/
+            value
+            field_name
+        )

     # Does not support over 53 bits
     require_range:(min,max,value, field_name="")->
@@ -141,6 +148,17 @@
         return value if is_empty value
         require_object_type 1, type, value, field_name
         to_number value.split('.')[2]
+
+    get_protocol_type: get_protocol_type=(value, field_name)->
+        return value if is_empty value
+        require_object_id value, field_name
+        values = value.split('.')
+        to_number values[1]
+
+    get_protocol_type_name: (value, field_name)->
+        return value if is_empty value
+        type_id = get_protocol_type value, field_name
+        (Object.keys chain_types.object_type)[type_id]

     require_implementation_type: require_implementation_type=(type, value, field_name)->
         require_object_type 2, type, value, field_name


added setters for html() and text()

added setters for html() and text()

--- src/api/manipulation.coffee
+++ src/api/manipulation.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 $ = require '../cheerio'
+parser = require '../parser'

 removeChild = (parent, elem) ->
  $.each parent.children, (i, child) ->
@@ -94,16 +95,21 @@
   this.each ->
     this.children = []

-html = exports.html = (htmlString) ->
-  if htmlString is undefined
-    if this[0] and this[0].type is "tag"
-      return $.html this[0]
-
 dom = exports.dom = (domObject) ->
   if domObject is undefined
     if this[0] and this[0].type is "tag"
       return $.dom this[0]

+html = exports.html = (htmlString) ->
+  if typeof htmlString isnt "object" and htmlString isnt undefined
+    htmlElement = parser.parse htmlString
+    this.each (i) ->
+      if this.children
+        this.children = htmlElement
+      return this
+  else
+    return $.html this[0]
+
 text = exports.text = (textString) ->
   if _.isFunction textString
     this.each (i) ->
@@ -112,8 +118,12 @@

   # Set the text
   if typeof textString isnt "object" and textString isnt undefined
-    console.log 'set text'
-
-  return $.text this
+    textElement = parser.parse textString
+    this.each (i) ->
+      if this.children
+        this.children = textElement
+    return this
+  else
+    return $.text this

 module.exports = $.fn.extend exports


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -1,5 +1,5 @@
 Array::average = ->
-  @sum()
+  @sum()/@leng

 ###*
 Returns a copy of the array without null and undefined values.


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -105,6 +105,11 @@
 ###*
 Randomly select an element from the array.

+

+[1, 2, 3].rand()
+# => 2
+

+
 @name rand
 @methodOf Array#
 @returns {Object} A random element from an array
@@ -117,12 +122,12 @@
 present. The array is modified in place.

-   a = [1, 1, "a", "b"]
-   a.remove(1)
-=> 1
-
-   a
-=> [1, "a", "b"]
+a = [1, 1, "a", "b"]
+a.remove(1)
+# => 1
+
+a
+# => [1, "a", "b"]

 @name remove


Removed log.

Removed log.

--- src/chaplin/composer.coffee
+++ src/chaplin/composer.coffee
@@ -9,27 +9,6 @@
 # --------

 # The sole job of the composer is to allow views to be 'composed'.
-# To compose a view (short form):
-#
-# @publishEvent '!composer:compose', ViewClass, options
-#
-# Or (long form):
-#
-# @publishEvent '!composer:compose',
-#   compose: ->
-#     composition = {}
-#     composition.model = new Model()
-#     composition.model.id = 42
-#
-#     composition.view = new View
-#       model: composition.model
-#
-#     composition.model.fetch()
-#     composition
-#
-#   check: (composition) ->
-#     composition.model.id is 42 and
-#     typeof composition.view is typeof View
 #
 # If the view has already been composed by a previous action then nothing
 # apart from registering the view as in use happens. Else, the view
@@ -78,7 +57,6 @@
     composition.stale = value

     # Sets the stale property for every item in the composition that has it
-    console.log 124, composition
     for name, item of composition when _(item).has 'stale'
       item.stale = value


added tests for Report

added tests for Report

--- spec/Spec/Report.spec.coffee
+++ spec/Spec/Report.spec.coffee
@@ -1,6 +1,50 @@
 Spec.extend Spec.Report

 Spec.describe 'Spec.Report', ->
-  describe 'a new report'
-  describe '#result'
-  describe '#addSubreport'
+  subject 'report', -> new Spec.Report()
+
+  describe 'a new report', ->
+    it 'has status of passed', ->
+      expect(@report.status).to equal Spec.Report.Passed
+
+    it 'has null error', ->
+      expect(@report.error).to be null
+
+    it 'has no subreports', ->
+      expect(@report.subreports).to equal []
+
+    it 'has all counts at 0', ->
+      expect(@report.counts).to equal [0, 0, 0]
+
+  describe '#result', ->
+    given 'status',  -> Spec.Report.Failed
+    given 'message', -> 'too silly'
+
+    before ->
+      @report.result @status, @message
+
+    it 'records result as status', ->
+      expect(@report.status).to equal Spec.Report.Failed
+
+    it 'stores error message', ->
+      @report.error.should equal @message
+
+    it 'increments count for status', ->
+      expect(@report.counts[@status]).to equal 1
+
+  describe '#addSubreport', ->
+    given 'subreport', -> new Spec.Report
+    given 'status',  -> Spec.Report.Pending
+    before ->
+      @subreport.result @status
+      @report.addSubreport @subreport
+
+    it 'adds subreport to list', ->
+      @report.subreports.should equal [@subreport]
+
+    it 'adds subreport counts to report counts', ->
+      expect(@report.counts[@status]).to equal 1
+
+    it 'upgrades status to subreport status', ->
+      expect(@report.status).to equal @status
+


Unfocus spec

Unfocus spec

--- spec/select-next-spec.coffee
+++ spec/select-next-spec.coffee
@@ -2,7 +2,7 @@
 {WorkspaceView} = require 'atom'
 SelectNext = require '../lib/select-next'

-fdescribe "SelectNext", ->
+describe "SelectNext", ->
   [editorView] = []

   beforeEach ->


Fix dropping files in IE

Fix dropping files in IE

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -65,7 +65,8 @@
       @draggedRange = @responder?.getLocationRange()

     dragover: (event) ->
-      event.preventDefault() if @draggedRange
+      if @draggedRange or "Files" in event.dataTransfer?.types
+        event.preventDefault()

     dragend: (event) ->
       delete @draggedRange


Spec!

Spec!

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -5961,7 +5961,7 @@
       expect(editor.getGrammar().name).toBe 'CoffeeScript'

   describe "softWrapAtPreferredLineLength", ->
-    it "soft wraps the editor at the preferred line length unless the editor is narrower", ->
+    it "soft wraps the editor at the preferred line length unless the editor is narrower or the editor is mini", ->
       editor.update({
         editorWidthInChars: 30
         softWrapped: true
@@ -5973,6 +5973,9 @@

       editor.update({editorWidthInChars: 10})
       expect(editor.lineTextForScreenRow(0)).toBe 'var '
+
+      editor.update({mini: true})
+      expect(editor.lineTextForScreenRow(0)).toBe 'var quicksort = function () {'

   describe "softWrapHangingIndentLength", ->
     it "controls how much extra indentation is applied to soft-wrapped lines", ->


style(reporters): remove empty lines

style(reporters): remove empty lines

--- src/console_reporter.coffee
+++ src/console_reporter.coffee
@@ -167,7 +167,6 @@

   formatExampleResult: (example) ->
     state = example.result.state
-
     if @options.documentation
       @lastDepth ||= 0
       @lastAncestorsStack ||= []
@@ -178,8 +177,6 @@
       res = @formatDocumentation example, dif, start, PROGRESS_COLOR_MAP[state]

       @lastAncestorsStack = ancestors
-
-
       res
     else
       @colorize PROGRESS_CHAR_MAP[state], PROGRESS_COLOR_MAP[state]


regression test for #39

regression test for #39

commit e788239648af80ea00cc5433e2b45799fc771865

--- test/scripts/controllers/missionDetailController.coffee
+++ test/scripts/controllers/missionDetailController.coffee
@@ -37,6 +37,7 @@
       expect(@scope.bounds).toEqual {}
       @scope.$apply()
       @httpBackend.flush()
+
       expect(@scope.bounds).not.toEqual {}
       expect(@scope.bounds.southWest.lng).toEqual @geojson.bbox[0]
       expect(@scope.bounds.southWest.lat).toEqual @geojson.bbox[1]
@@ -47,7 +48,8 @@
     it 'gets formats date to present to users', ->
       @scope.$apply()
       @httpBackend.flush()
-      expect(@scope.record.dateString).toEqual (new Date(@mission.createdOn)).toDateString()
+      createdOn = new Date(@mission.createdOn)
+      expect(@scope.record.dateString).toEqual "#{createdOn.toDateString()} - #{createdOn.toLocaleTimeString()}"

     it 'prepares data for social sharing', ->
       @scope.$apply()


reapply correct PreliminaryEstimate selectors

--- test/spec/roadmapplanningboard/PlanningBoardSpec.coffee
+++ test/spec/roadmapplanningboard/PlanningBoardSpec.coffee
@@ -107,7 +107,7 @@
     @createCardboard().then =>
       _.each @cardboard.getColumns(), (column) =>
         _.each column.getCards(), (card) =>
-          expect(card.getEl().down('.rui-card-content .PreliminaryEstimate .rui-field-value').dom.innerHTML).toBe "L"
+          expect(card.getEl().down('.rui-card-right-side .PreliminaryEstimate .rui-field-value').dom.innerHTML).toBe "L"

   it 'should have project on the cards', ->
     @createCardboard().then =>


修正repeat的setIndex报错的BUG

修正repeat的setIndex报错的BUG

--- src/core/dom-feature.coffee
+++ src/core/dom-feature.coffee
@@ -282,10 +282,10 @@
 						itemDomBinding.itemId = itemId
 						domBinding.itemDomBindingMap[itemId] = itemDomBinding
 						itemScope.data.setItemData(item)
+						itemScope.data.setIndex(i + 1)
 					else
 						itemDom = @createNewItem(domBinding, templateDom, scope, item, i + 1)
 						$fly(tailDom).before(itemDom)
-					itemScope?.data.setIndex(i + 1)

 					if item is (items.current or originItems?.current)
 						$fly(itemDom).addClass(cola.constants.COLLECTION_CURRENT_CLASS)


oops, fixed new fast color storing

oops, fixed new fast color storing

--- src/core/fetch/color.coffee
+++ src/core/fetch/color.coffee
@@ -6,7 +6,7 @@
 # Finds an object's color and returns random if it cannot be found
 module.exports = (vars, id, level) ->

-  if validObject(id) and id.d3plus and id.d3plus.color
+  if validObject(id) and id.d3plus and id.d3plus.color and !vars.color.changed
     return id.d3plus.color

   getRandom = (c) ->


Allow the graph type to be specified when using the graph widget

--- templates/project/widgets/graph/graph.coffee
+++ templates/project/widgets/graph/graph.coffee
@@ -15,6 +15,7 @@
       element: @node
       width: width
       height: height
+      renderer: @get("graphtype")
       series: [
         {
         color: "#fff",


more code formatting

more code formatting

--- servers/lib/crawler/staticpages/feed.coffee
+++ servers/lib/crawler/staticpages/feed.coffee
@@ -20,11 +20,7 @@
     if page > 0
       skip = (page - 1) * itemsPerPage

-    fetchOptions =
-      targetId   : targetId
-      limit      : itemsPerPage
-      skip       : skip
-      replyLimit : 25
+    fetchOptions = { targetId, limit: itemsPerPage, skip, replyLimit: 25 }

     SocialChannel.fetchProfileFeed client, fetchOptions, (err, result) ->
       return callback err  if err or not result
@@ -34,7 +30,14 @@
       buildContent models, result, options, (err, content) ->
         return callback err  if err or not content

-        pagination = getPagination page, itemCount, itemsPerPage, "#{account.profile.nickname}"
+        paginationOptions = {
+          currentPage   : page
+          numberOfItems : itemCount
+          itemsPerPage
+          route         : "#{account.profile.nickname}"
+        }
+
+        pagination = getPagination paginationOptions
         if pagination
           content += "<nav class='crawler-pagination clearfix'>#{pagination}</nav>"

@@ -83,7 +86,14 @@
         content = schemaorgTagsOpening + channelTitleContent +
           pageContent + schemaorgTagsClosing

-        pagination = getPagination page, itemCount, itemsPerPage, "Activity/#{route}"
+        paginationOptions = {
+          currentPage   : page
+          numberOfItems : itemCount
+          itemsPerPage
+          route         : "Activity/#{route}"
+        }
+
+        pagination = getPagination paginationOptions
         fullPage = putContentIntoFullPage content, pagination

         callback null, fullPage
@@ -120,39 +130,40 @@
   daisy queue

-getPagination = (currentPage, numberOfItems, itemsPerPage, route="")->
+getPagination = (options)->
   # This is the number of adjacent link around current page.
   # E.g. let current page be 9, then pagination will look like this:
   # First Prev ... 4 5 6 7 8 9 10 11 12 13 14 ... Next Last
   # (Except 3-dots, they are useless for bots.)
   PAGERWINDOW = 4
-
-  numberOfPages = Math.ceil(numberOfItems / itemsPerPage)
+  options.route ?= ''
+
+  numberOfPages = Math.ceil(options.numberOfItems / options.itemsPerPage)
   firstLink = prevLink = nextLink = lastLink = ""

-  if currentPage > 1
-    firstLink = getSinglePageLink 1, "First", route
-    prevLink  = getSinglePageLink (currentPage - 1), "Prev", route
-
-  if currentPage < numberOfPages
-    lastLink  = getSinglePageLink numberOfPages, "Last", route
-    nextLink  = getSinglePageLink (currentPage + 1), "Next", route
+  if options.currentPage > 1
+    firstLink = getSinglePageLink 1, "First", options.route
+    prevLink  = getSinglePageLink (options.currentPage - 1), "Prev", options.route
+
+  if options.currentPage < numberOfPages
+    lastLink  = getSinglePageLink numberOfPages, "Last", options.route
+    nextLink  = getSinglePageLink (options.currentPage + 1), "Next", options.route

   pagination = firstLink + prevLink

   start = 1
   end = numberOfPages

-  start = currentPage - PAGERWINDOW  if currentPage > PAGERWINDOW
-
-  if currentPage + PAGERWINDOW < numberOfPages
-    end   = currentPage + PAGERWINDOW
+  start = options.currentPage - PAGERWINDOW  if options.currentPage > PAGERWINDOW
+
+  if options.currentPage + PAGERWINDOW < numberOfPages
+    end   = options.currentPage + PAGERWINDOW

   if start > 1
     pagination += getNoHrefLink " ... "

   [start..end].map (pageNumber)->
-    pagination += getSinglePageLink pageNumber, null, route
+    pagination += getSinglePageLink pageNumber, null, options.route

   if end < numberOfPages
     pagination += getNoHrefLink " ... "


fix exception when content undefined in TikzManager

--- services/clsi/app/coffee/TikzManager.coffee
+++ services/clsi/app/coffee/TikzManager.coffee
@@ -22,7 +22,7 @@
 			SafeReader.readFile path, 65536, "utf8", (error, content) ->
 				return callback(error) if error?
 				usesTikzExternalize = content?.indexOf("\\tikzexternalize") >= 0
-				usesPsTool = content.indexOf("{pstool}") >= 0
+				usesPsTool = content?.indexOf("{pstool}") >= 0
 				logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, usesPsTool: usesPsTool, "checked for packages needing main file as output.tex"
 				needsMainFile = (usesTikzExternalize || usesPsTool)
 				callback null, needsMainFile


remove parens

remove parens

--- test/fixtures/execution/test_literals.coffee
+++ test/fixtures/execution/test_literals.coffee
@@ -42,7 +42,7 @@
   greet: (salutation) ->
     salutation + " " + @name
   hello: ->
-    @greet("Hello")
+    @greet "Hello"
 }

 puts bob.hello() is "Hello Bob"


Fix indentation mistakes in the plumbings

--- library/nucleus/plumbs.coffee
+++ library/nucleus/plumbs.coffee
@@ -115,7 +115,7 @@
             response.setHeader "Content-Length", 0
             response.writeHead relocated, message
             response.end undefined
-            next() unless request.headersSent
+        next() unless request.headersSent

 # This middleware is a little handy utility that merges the set
 # of parameters, specifically the ones transferred via query or


remove code duplicate

remove code duplicate

--- BackofficeBundle/Resources/public/coffee/areaView.coffee
+++ BackofficeBundle/Resources/public/coffee/areaView.coffee
@@ -5,7 +5,6 @@
     "click i#none": "clickButton"
     "sortupdate ul.ui-model-blocks": "sendBlockData"
     "click i.block-remove": "confirmRemoveBlock"
-    "sortupdate ul.ui-model-blocks": 'sendBlockData'

   initialize: (options) ->
     @area = options.area


Add Refresh Alias

Add Refresh Alias

--- BackofficeBundle/Resources/public/coffee/nodeView.coffee
+++ BackofficeBundle/Resources/public/coffee/nodeView.coffee
@@ -15,7 +15,16 @@
     return
   clickButton: (event) ->
     $('.modal-title').text @node.get('name')
-    view = new adminFormView(url: @node.get('links')._self_form)
+    if @node.attributes.alias is ''
+      refreshAlias = ->
+        $("input#node_alias").val $("input#node_name").val()
+        return
+      stopRefreshAlias = ->
+        $("input#node_alias").unbind()
+        return
+      view = new adminFormView(url: parentNode.data("url"), triggers: [{event: 'keyup input#node_name', name: 'refreshAlias', fct : refreshAlias}, {event: 'blur input#node_alias', name: 'stopRefreshAlias', fct : stopRefreshAlias}])
+    else
+      view = new adminFormView(url: parentNode.data("url"))
   duplicateNode: (event) ->
     event.preventDefault() #
     viewContext = this


Fix envelop typos

Fix envelop typos

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -8,9 +8,9 @@
 class Campfire extends Adapter
   send: (envelope, strings...) ->
     if strings.length > 0
-      @bot.Room(envelop.room).speak strings.shift(), (err, data) =>
+      @bot.Room(envelope.room).speak strings.shift(), (err, data) =>
         @robot.logger.error "Campfire error: #{err}" if err?
-        @send envelop.user, strings...
+        @send envelope.user, strings...

   reply: (envelope, strings...) ->
     @send envelope, strings.map((str) -> "#{envelope.user.name}: #{str}")...


Include user agent here too

Include user agent here too

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -257,6 +257,7 @@
       "Authorization" : @authorization
       "Host"          : @host
       "Content-Type"  : "application/json"
+      "User-Agent"    : "Hubot/#{@robot.version} (#{@robot.name})"

     options =
       "agent"  : false


QUALITY: Add confirmation when deleting a stream

--- src/app/common/services/unit-service.coffee
+++ src/app/common/services/unit-service.coffee
@@ -1,6 +1,6 @@
 angular.module("doubtfire.common.services.units", [])

-.factory("unitService", (Unit, UnitRole, Students, Group, tutorialService, streamService, projectService, groupService, gradeService, taskService, $filter, $rootScope, analyticsService, PortfolioSubmission, alertService, Project, $state, TeachingPeriod) ->
+.factory("unitService", (Unit, UnitRole, Students, Group, tutorialService, streamService, projectService, groupService, gradeService, taskService, $filter, $rootScope, analyticsService, PortfolioSubmission, ConfirmationModal, ProgressModal, alertService, Project, $state, TeachingPeriod) ->
   #
   # The unit service object
   #
@@ -178,7 +178,11 @@
       failureCallback = (response) ->
         # Deal with the failure
         alertService.add("danger", "Failed to delete stream. #{response?.data?.error}", 8000)
-      Unit.tutorialStream.delete({id: unit.id, tutorial_stream_abbr: stream.abbreviation}, successCallback, failureCallback)
+      ConfirmationModal.show "Delete Tutorial Stream #{stream.abbreviation}",
+      'Are you sure you want to delete this tutorial stream? This action is final and will delete all associated tutorials.',
+      ->
+        promise = Unit.tutorialStream.delete({id: unit.id, tutorial_stream_abbr: stream.abbreviation}, successCallback, failureCallback)
+        ProgressModal.show "Deleting Tutorial Stream #{stream.abbreviation}.", promise

     # Get a unit's next stream based on activity abbreviation
     unit.nextStream = (activityTypeAbbreviation) ->


Don't use String.startsWith, use slice instead.

- String.startsWith is not supported in IE

--- services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/labels/LabelsManager.coffee
@@ -26,7 +26,7 @@
 				if (
 					change.action in ['remove', 'insert'] and
 					((_.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/))) or
-					 (commandFragment?.length > 2 and commandFragment.startsWith('\\label{')))
+					 (commandFragment?.length > 2 and commandFragment.slice(0,7) == '\\label{'))
 				)
 					@scheduleLoadLabelsFromOpenDoc()


Fix indentation bug

Fix indentation bug

--- spec/image-editor-spec.coffee
+++ spec/image-editor-spec.coffee
@@ -31,8 +31,8 @@

         atom.workspaceView.open(path.join(__dirname, 'fixtures', 'binary-file.png'))

-        waitsFor ->
-          atom.workspaceView.getActivePaneItem()?
+      waitsFor ->
+        atom.workspaceView.getActivePaneItem()?

-        runs ->
-          expect(atom.workspaceView.getActivePaneItem() instanceof ImageEditor).toBe false
+      runs ->
+        expect(atom.workspaceView.getActivePaneItem() instanceof ImageEditor).toBe false


synctable: mark connection failed if initial query fails; otherwise reconnect is a no-op

--- src/smc-util/synctable.coffee
+++ src/smc-util/synctable.coffee
@@ -208,8 +208,8 @@
             @_reconnecting = false
             dbg("running query returned -- #{err}")
             if not @_connected and @_client.connected
-                dbg("didn't work, but client is connected to server -- try again in a little while")
-                setTimeout( @_reconnect, 120*1000 )
+                dbg("didn't work, but client is connected to server -- try again in a minute.")
+                setTimeout( @_reconnect, 60*1000 )

     _run: (cb) =>
         if @_closed
@@ -229,12 +229,15 @@
                 if first_resp
                     first_resp = false
                     if @_closed
+                        @_connected = false
                         @_unclose('first response output from query')
                         cb?("closed")
                     else if err
+                        @_connected = false
                         console.warn("query #{@_table}: _run: first error ", err)
                         cb?(err)
                     else if not resp?.query?[@_table]?
+                        @_connected = false
                         console.warn("query on #{@_table} returned undefined")
                         cb?("got no data")
                     else


Remove unnecessary gulpfile

Remove unnecessary gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,8 +0,0 @@
-gulp = require 'gulp'
-jade = require 'gulp-jade'
-
-
-gulp.task 'default', ->
-	gulp.src './index.jade'
-	.pipe jade()
-	.pipe gulp.dest '.'


Consolidate CSS

Consolidate CSS

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -38,6 +38,7 @@
       'last 2 Explorer versions'
       'last 2 Safari versions'
     ]
+    .pipe concat 'app.css'
     .pipe minifyCss()
     .pipe sourcemaps.write()
     .pipe gulp.dest "#{DEST}/styles"


concat not used

concat not used

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,7 +1,6 @@
 browserify = require 'gulp-browserify'
 coffee = require 'gulp-coffee'
 coffeelint = require 'gulp-coffeelint'
-concat = require 'gulp-concat'
 declare = require 'gulp-declare'
 gulp = require 'gulp'
 gutil = require 'gulp-util'
@@ -58,4 +57,4 @@
   gulp.watch src.css, ['css']
   gulp.watch src.stylus, ['stylus']

-gulp.task 'default', ['stylus', 'css', 'coffee', 'watch']+gulp.task 'default', ['stylus', 'css', 'coffee', 'watch']


Removed gulp-jasmine

Removed gulp-jasmine

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -5,8 +5,6 @@
 concat  = require 'gulp-concat'
 connect = require 'gulp-connect'
 header  = require 'gulp-header'
-jasmine = require 'gulp-jasmine'
-jasmine = require 'gulp-jasmine'
 karma   = require('karma').server
 uglify  = require 'gulp-uglify'
 gutil   = require 'gulp-util'