Enable browser native spellcheck Ctrl+Click to replace

--- app/assets/javascripts/effective_ckeditor/init.js.coffee
+++ app/assets/javascripts/effective_ckeditor/init.js.coffee
@@ -62,6 +62,7 @@
     shiftEnterMode: shiftEnterMode
     startupOutlineBlocks: startupOutlineBlocks
     disableNativeTableHandles: true
+    disableNativeSpellChecker: false
     sharedSpaces:
       top: 'effective-ckeditor-top'
     startupShowBorders: true


OX-237 Bug Fix: add spheres when loading from model

--- app/assets/javascripts/oxalis/geometries/skeleton.coffee
+++ app/assets/javascripts/oxalis/geometries/skeleton.coffee
@@ -270,7 +270,7 @@
       # Assign the ID to the vertex, so we can access it later
       @nodes[index].geometry.nodeIDs.push(id)

-      @trigger "newGeometries", [@pushNewNode(radius, position, id, color)]
+      @pushNewNode(radius, position, id, color)

       @routes[index].geometry.verticesNeedUpdate = true
       @nodes[index].geometry.verticesNeedUpdate = true
@@ -324,7 +324,7 @@
     newNode.visible = @disSpheres
     newNode.doubleSided = true
     @nodesSpheres.push(newNode)
-    return newNode
+    @trigger "newGeometries", [newNode]

   getIndexFromTreeId : (treeId) ->
     unless treeId


Better scale

Better scale

--- widgets/graph/graph.coffee
+++ widgets/graph/graph.coffee
@@ -11,13 +11,17 @@
     # Gross hacks. Let's fix this.
     width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1)
     height = (Dashing.widget_base_dimensions[1] * container.data("sizey"))
+
+    min_value = Math.max [0, @get("minValue") - 100]
+    max_value = Math.min [@get("maxValue") * 2, @get("maxValue") + 100]
+
     @graph = new Rickshaw.Graph(
       element: @node
       width: width
       height: height
       renderer: @get("graphtype")
-      max: @get("maxValue") + 100
-      min: Math.max [0, @get("minValue") - 100]
+      max: max_value
+      min: min_value
       series: [
         {
           color: "#fff",


if os less than 2.1 disable transitions

if os less than 2.1 disable transitions

--- js/flight.coffee
+++ js/flight.coffee
@@ -35,6 +35,9 @@
     @fitHeightToContent()

     @detectUA()
+
+    if @os.android and @os.version <= "2.1"
+      @transitionAnimation = false

   goToPage: (targetPanel,options) ->


removing useless code

removing useless code

--- src/coffee/controllers/player/player-options-ctrl.coffee
+++ src/coffee/controllers/player/player-options-ctrl.coffee
@@ -2,8 +2,6 @@
 .controller 'PlayerOptions', [
   '$scope', '$timeout', '$mdDialog', 'CurrentPlayer', 'OptionsCache', 'API', 'CurrentTheme'
   ($scope, $timeout, $mdDialog, Player, OptionsCache, API, CurrentTheme) ->
-
-    initializing = yes

     $scope.options = OptionsCache.getOpts()

@@ -109,16 +107,10 @@
         $scope.customContentList = res.data.customs

     $scope.initialize = ->
-      initializing = yes
-
       $scope.player = Player.getPlayer()
       $scope.buildStringList()

       $scope.refreshCustomContent() if $scope.player?.isContentModerator and not $scope.customContentList
-
-      $timeout ->
-        initializing = no
-      , 0

     Player.observe().then null, null, ->
       $scope.initialize()


get rid of old code that was loading teacher dashboard endpoint twice for the calendar

--- src/components/task-plan/teacher-task-plans-listing.cjsx
+++ src/components/task-plan/teacher-task-plans-listing.cjsx
@@ -65,18 +65,6 @@

   mixins: [CourseDataMixin]

-  componentDidMount: ->
-    {courseId} = @context.router.getCurrentParams()
-
-    # Bypass loading if plan is already loaded, as is the case in testing.
-    if not TeacherTaskPlanStore.isLoaded(courseId)
-      TeacherTaskPlanActions.load( courseId )
-
-  componentWillMount: -> TeacherTaskPlanStore.addChangeListener(@update)
-  componentWillUnmount: -> TeacherTaskPlanStore.removeChangeListener(@update)
-
-  update: -> @setState({})
-
   statics:
     willTransitionTo: (transition, params, query, callback) ->
       {date, planId} = params


Added a prototype implementation of attribute setting/adding.

--- lib/graft.coffee
+++ lib/graft.coffee
@@ -87,7 +87,20 @@
           generators $base.find(selector)
         # Strings are text for replacing the text of the selector matches.
         when 'string' or 'number'
-          $base.find(selector).text(generators)
+          match = null
+          if match = /(.*)\[([^\]]+)\]$/.exec(selector)
+            [strippedSelector, attribute] = match[1..]
+
+            if attribute[-1] == '+'
+              attribute = attribute[0..-1]
+
+              $base
+                .find(strippedSelector)
+                .each (elt) -> $(elt).attr(attribute, "#{$(elt).attr(attribute)} #{generators}")
+            else
+              $base.find(strippedSelector).attr(attribute, generators)
+          else
+            $base.find(selector).text(generators)
         when 'object'
           # If we get a jQuery object (identified by the selector property), we
           # replace the selector matches' html contents with that object's


get rid of extraneous resolvePath function

--- lib/index.coffee
+++ lib/index.coffee
@@ -42,7 +42,7 @@
   if not fs.existsSync(filepath)
     return cli.emit('err', "File '#{filepath}' not found ".red)

-  adapter = accord.load(name, resolvePath(name))
+  adapter = accord.load(name)

   run = -> render(adapter, filepath, locals, cli, argv)
   promise = run()
@@ -64,18 +64,6 @@
   for name, Adapter of accord.all()
     if _.contains(Adapter::extensions, ext) then return name
   return
-
-###*
- * Given the name of a module, returns it's home folder in node
- * modules. This was taken from accord's source.
- * @param  {String} name - name of a node module
- * @return {String} path to the module
-###
-resolvePath = (name) ->
-  _path = require.resolve(name).split(path.sep).reverse()
-  for p, i in _path
-    if _path[i - 1] is name and p is 'node_modules' then break
-  _.first(_path.reverse(), _path.length - i + 1).join(path.sep)

 ###*
  * Compile and render the file


Adeed currentLine method to Player to avoid returning by reference

--- player.coffee
+++ player.coffee
@@ -10,6 +10,9 @@
     lastPos: ->
       @positions[@positions.length - 1]

+    currentLine: ->
+      @current_line.slice() # Copy array
+
     addToLine: (pos) ->
       @current_line.push(pos)


Remove special cases, as both unsubscribed

--- bin/find-unsubscribed.coffee
+++ bin/find-unsubscribed.coffee
@@ -33,11 +33,6 @@
         # scremium is a test account that needs to be on this plan
         if user.shortName == 'scremium'
           return cb null
-        # special case users - check these time to time
-        # Joel Hacker - on xlarge plan at was promised > 100 datsets back in the day
-        # CSV Soundsystem - paid in advance by PayPal for I think 3 years, review again in mid-2015
-        if user.shortName == 'joelhacker' or user.shortName == 'csv'
-          return cb null

         # something is up
         console.log "user:", user.shortName, user.displayName, user.email, "mongo plan:", user.accountLevel


Calculate dropdown position and width more precisely

--- vendor/assets/javascripts/chosen/chosen.js.coffee
+++ vendor/assets/javascripts/chosen/chosen.js.coffee
@@ -350,12 +350,19 @@
     return unless @opened

     offsets = @$container.offset()
-    height = @$container.innerHeight()
-    width = @$container.innerWidth()
+    rect = @$container[0].getBoundingClientRect()
+    border_width = parseInt(@$container.css("border-bottom-width"))
+
+    if rect.width
+      width = rect.width
+      height = rect.height
+    else
+      width = rect.right - rect.left
+      height = rect.bottom - rect.top

     @$dropdown.css
       left: "#{offsets.left}px",
-      top: "#{offsets.top + height}px"
+      top: "#{offsets.top + height - border_width}px"
       width: "#{width}px"

     return


Add multiple/single/rtl identifying classes to the dropdown also

--- vendor/assets/javascripts/chosen/chosen.js.coffee
+++ vendor/assets/javascripts/chosen/chosen.js.coffee
@@ -53,16 +53,16 @@
     Chosen.pool.splice(index, 1) if index >= 0

   build: ->
-    classes = ["chosen-container"]
-
-    classes.push if @is_multiple then "multiple" else "single"
-    classes.push "rtl" if @is_rtl
-    classes.push @target.className if @inherit_classes and @target.className
+    select_classes = ["chosen-container"]
+
+    select_classes.push if @is_multiple then "multiple" else "single"
+    select_classes.push "rtl" if @is_rtl
+    select_classes.push @target.className if @inherit_classes and @target.className

     width = if @width? then @width else "#{@target.offsetWidth}px"

     container_props =
-      class: classes.join ' '
+      class: select_classes.join ' '
       style: "width: #{width}; min-width: #{width}; max-width: #{width}"

     container_props.title = @target.title if @target.title.length
@@ -75,7 +75,17 @@
     @$container.$search_container = @$container.$choices.find("li.chosen-search-field")
     @$container.$search = @$container.$choices.find("input[type=text]").attr(tabindex: @target.tabindex || "0")

-    @$dropdown = $("
", class: "chosen-dropdown", unselectable: "on", html: "

")
+    dropdown_classes = ["chosen-dropdown"]
+
+    dropdown_classes.push if @is_multiple then "multiple" else "single"
+    dropdown_classes.push "rtl" if @is_rtl
+
+    dropdown_props =
+      class: dropdown_classes.join ' '
+      unselectable: "on"
+      html: "

"
+
+    @$dropdown = $("
", dropdown_props)
     @$dropdown.$list = @$dropdown.find("ul").first()
     @$dropdown.$list.suggestion = null


Use rootView.openSync

Use rootView.openSync

--- spec/bookmarks-view-spec.coffee
+++ spec/bookmarks-view-spec.coffee
@@ -5,7 +5,7 @@

   beforeEach ->
     window.rootView = new RootView
-    rootView.open('sample.js')
+    rootView.openSync('sample.js')
     rootView.enableKeymap()
     atom.activatePackage('bookmarks', immediate: true)
     rootView.attachToDom()


Fix #606

Fix #606

--- app/assets/javascripts/dashboard/models/dashboard_model.coffee
+++ app/assets/javascripts/dashboard/models/dashboard_model.coffee
@@ -23,6 +23,7 @@

   initialize : (options) ->

+    @set("tasks", new Backbone.Collection())
     @listenTo(@, "sync", @transformToCollection)


[fix] Removed tickets unable to estimate from the Planning Poker view [#52] (2m)

--- app/assets/javascripts/houston-scheduler/routers/router.coffee
+++ app/assets/javascripts/houston-scheduler/routers/router.coffee
@@ -67,7 +67,7 @@
   showPlanningPoker: ->
     @updateActiveTab()
     @show new Scheduler.PlanningPoker
-      tickets: @parent.openTickets().withoutEffortEstimate()
+      tickets: @parent.ticketsWaitingForEffortEstimate()
       maintainers: @parent.maintainers


Allow specifying browser quirks in the navigators code.

--- spec/helpers/fake_window.coffee
+++ spec/helpers/fake_window.coffee
@@ -1,17 +1,24 @@
-USER_AGENTS =
-  osx:
-    chrome:
-      latest: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17"
-  windows:
-    chrome:
-      latest: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17"
-  android:
-    chrome:
-      latest: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"
+NAVIGATORS =
+  'osx.chrome.latest':
+    userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17"
+    firesKeyPressWithoutCharCode: no
+    firesKeyPressWhenKeydownPrevented: no
+  'windows.chrome.latest':
+    userAgent: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17"
+    firesKeyPressWithoutCharCode: no
+    firesKeyPressWhenKeydownPrevented: no
+  'android.chrome.latest':
+    userAgent: "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"
+    firesKeyPressWithoutCharCode: no
+    firesKeyPressWhenKeydownPrevented: no
+  'osx.firefox.v24':
+    userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"
+    firesKeyPressWithoutCharCode: yes
+    firesKeyPressWhenKeydownPrevented: yes

 class FakeNavigator
   constructor: ->
-    @userAgent = USER_AGENTS.osx.chrome.latest
+    @userAgent = 'osx.chrome.latest'

   Object.defineProperty this::, 'userAgent',
     get: ->
@@ -19,11 +26,13 @@

     set: (userAgent) ->
       if userAgent.match(/^\w+(\.\w+)*$/)
-        lookup = USER_AGENTS
-        for part in userAgent.split('.')
-          lookup = lookup[part]
-          throw new Error("unable to find user agent alias #{userAgent}") unless lookup
-        userAgent = lookup
+        info = NAVIGATORS[userAgent]
+        throw new Error("unable to find user agent alias #{userAgent}") unless info
+
+        userAgent = info.userAgent
+        for own key, value of info when key isnt 'userAgent'
+          this["FK_#{key}"] = value
+
       @_userAgent = userAgent

 class FakeWindow


warns on attempt to watch protected property

--- spec/notice/capsule_spec.coffee
+++ spec/notice/capsule_spec.coffee
@@ -138,8 +138,6 @@
                 instance.set
                     protected: true
                     watched: (property, change, obj) ->
-                        CHANGES[property] ||= []
-                        CHANGES[property].push change
                     thing: 'one'


cleanups

cleanups

--- src/ui/views/applayout.coffee
+++ src/ui/views/applayout.coffee
@@ -13,7 +13,8 @@
 # conversations

 conversationsListItemView = (conversation) ->
-  div class:'conversation', conversation.name, onclick: (e) ->
+  conversationName = conversation.name || 'Unknown'
+  div class:'conversation', conversationName, onclick: (e) ->
     e.preventDefault()
     ipc.send 'conversation:select', conversation.id

@@ -23,11 +24,6 @@
       (conversations || []).forEach conversationsListItemView

 # messages
-
-messagesUtils =
-  messageGetUsername: (model, event) =>
-    chat_id = (event.sender_id || event.user_id).chat_id
-    return model.identitiesById[chat_id].name

 messageBodyView = (model, event) ->
   # Temporary doing this to get something to print
@@ -58,7 +54,9 @@
   messages = model.messagesByConversationId[model.conversationCurrent] || []
   messages.forEach (message) ->
     div class: 'message', ->
-      div class: 'user', messagesUtils.messageGetUsername(model, message)
+      div class: 'user', ->
+        chat_id = (message.sender_id || message.user_id).chat_id
+        return model.identitiesById[chat_id].name || 'Unknown'
       div class: 'body', ->
         messageBodyView model, message


FIX: Group work indicator in the task editor

--- src/app/units/unit-tasks-editor/unit-tasks-editor.coffee
+++ src/app/units/unit-tasks-editor/unit-tasks-editor.coffee
@@ -7,7 +7,7 @@
   replace: true
   restrict: 'E'
   templateUrl: 'units/unit-tasks-editor/unit-tasks-editor.tpl.html'
-  controller: ($scope, $rootScope, Task, Unit, gradeService, alertService, taskService, CsvResultModal, ConfirmationModal, ProgressModal) ->
+  controller: ($scope, $rootScope, Task, Unit, gradeService, alertService, taskService, groupService, CsvResultModal, ConfirmationModal, ProgressModal) ->
     $scope.grades = gradeService.grades

     # Pagination details
@@ -98,4 +98,5 @@
       if response.success.length > 0
         $scope.unit.refresh()

+    $scope.groupSetName = (id) -> groupService.groupSetName(id, $scope.unit)
 )


Make sure autocomplete works for non accepted taxa

--- app/assets/javascripts/admin/admin_in_place_editor.js.coffee
+++ app/assets/javascripts/admin/admin_in_place_editor.js.coffee
@@ -62,10 +62,7 @@
         }, (data) =>
           labels = []
           $.each(data, (i, item) =>
-            if item.name_status == 'A'
-              labels.push(item.full_name)
-            else
-              '[' + item.name_status + '] ' + item.full_name
+            labels.push(item.full_name + ' (' + item.name_status + ')')
           )
           return process(labels)
         )


Missing preventDefaults

Missing preventDefaults

--- app/assets/javascripts/views/admin/posts/post_view.js.coffee
+++ app/assets/javascripts/views/admin/posts/post_view.js.coffee
@@ -15,10 +15,13 @@
       this

     deletePost: ->
+      e.preventDefault()
       if confirm('Are you sure?')
         this.model.destroy()

-    togglePublish: ->
+    togglePublish: (e) ->
+      e.preventDefault()
+
       if this.model.get('published?')
         this.model.save
           transition: 'draft'


Make esri standard indicator integration test pass


fix student progression pagination

fix student progression pagination

fixes CNVS-13811

test plan:
 - have a large browser window (15" full screen will do)
 - enroll 12 or more students in a course with draft state enabled
 - go to the modules page
 - click "View Progress"
 - there should be no blank space at the bottom of the
   "STUDENT PROGRESS" pane on the right
 - you should be able to scroll down and see the last few students

Change-Id: I79342288e4809d03f7c8bd07c7782310f841e8b6
Reviewed-on: https://gerrit.instructure.com/37089
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/bundles/context_module_progressions.coffee
+++ app/coffeescripts/bundles/context_module_progressions.coffee
@@ -13,8 +13,8 @@
     students.urls = null
   else
     students = new UserCollection null,
-      per_page: 50
       params:
+        per_page: 50
         enrollment_type: 'student'

     students.fetch()


Removed Commented code

Removed Commented code


Add DeliverBroadcastSent to job-registry

--- job-registry.cson
+++ job-registry.cson
@@ -48,6 +48,26 @@
         204: 'deliver-message'
     'deliver-message':
       filter: 'HandleDeliverMessage'
+
+DeliverBroadcastSent:
+  start: 'authenticate'
+  tasks:
+    'authenticate':
+      filter: 'Authenticate'
+      on:
+        204: 'protect-your-as'
+    'protect-your-as':
+      task: 'meshblu-core-task-protect-your-as'
+      on:
+        204: 'check-whitelist-broadcast-as'
+    'check-whitelist-broadcast-as':
+      task: 'meshblu-core-task-check-whitelist-broadcast-as'
+      datastoreCollection: 'devices'
+      on:
+        204: 'enqueue-jobs-for-subscriptions-broadcast-sent'
+    'enqueue-jobs-for-subscriptions-broadcast-sent':
+      task: 'meshblu-core-task-enqueue-jobs-for-subscriptions-broadcast-sent'
+      datastoreCollection: 'subscriptions'

 DeliverConfigMessage:
   start: 'authenticate'


Disable representatives job for now.

Disable representatives job for now.

--- jobs/index.coffee
+++ jobs/index.coffee
@@ -19,7 +19,7 @@
   jobs.process 'poll', (job, done) ->
     # Queue up jobs that should run on each poll.
     jobs.create('scrape sessions').save()
-    jobs.create('scrape representatives').save()
+    #jobs.create('scrape representatives').save()
     #jobs.create('scrape committees').save()
     #jobs.create('scrape legislation').save()
     #jobs.create('scrape votes').save()


Fix css namespaces

Fix css namespaces

--- lib/piler.coffee
+++ lib/piler.coffee
@@ -314,6 +314,9 @@
       res.setHeader "Content-type", @contentType
       asset = assetUrlParse req.url

+      # Wrong asset type. Lets skip to next middleware.
+      if asset.ext isnt @Type::ext
+        return next()

       pile = @piles[asset.name]

@@ -321,21 +324,16 @@
         res.send "Cannot find pile #{ asset.name }", 404
         return

-      # Wrong asset type. Lets skip to next middleware.
-      if asset.ext isnt pile.ext
-        return next()
-
-
+      # TODO: set cache headers to forever
       if asset.min
         res.end pile.rawPile
         return

-      if asset.dev
-        codeOb = pile.findCodeObById asset.dev.uid
-        codeOb.getCode (err, code) ->
-          throw err if err
-          res.end code
-          return
+      codeOb = pile.findCodeObById asset.dev.uid
+      codeOb.getCode (err, code) ->
+        throw err if err
+        res.end code
+        return


Prevent shooting away from the planet on spawn

--- src/public/scripts/models/world.coffee
+++ src/public/scripts/models/world.coffee
@@ -78,6 +78,7 @@
 		#
 		createPlayer: ( id, owner, info, timestamp ) ->
 			if player = @getPlayer(id)
+				info.velocity = [0, 0, 0]
 				player.applyInfo(info, timestamp)
 				if player._dead
 					player.revive()


adjust postion of student widget

adjust postion of student widget

--- rtm_student.widget/main.coffee
+++ rtm_student.widget/main.coffee
@@ -1,10 +1,10 @@
-command: "/Users/yina/.pyenv/shims/python /Users/yina/Dropbox/Backups/geektool/ubersicht_widgets/bin/rtm_cli.py -c -p ls list:students priority:0"
+command: "/Users/yina/.pyenv/shims/python /Users/yina/Dropbox/Backups/geektool/ubersicht_widgets/bin/rtm_cli.py -c -p ls list:.students"

-refreshFrequency: 10000
+refreshFrequency: false

 style: """
-  top: 450px
-  left: 375px
+  top: 15px
+  left: 750px

 	*
 		margin 0
@@ -21,7 +21,7 @@
 		list-style none

 	li
-		padding 10px
+		padding 0px
 		&:not(:last-child)
 			border-bottom solid 1px white

@@ -39,6 +39,9 @@

+

+    STUDENTS
+

        #{output}


Remove error when changing land-parcel-plant selector

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -403,6 +403,9 @@
     if unrollValueElement.val() != ""
       unrollValueElement.val('')

+    harvestInProgressError = $(nestedCultivationBlock).find('.harvest-in-progress-error')
+    $(harvestInProgressError).remove() if $(harvestInProgressError).length > 0
+
     plantLandParcelSelector = new E.PlantLandParcelSelector()
     plantLandParcelSelector.changeUnrollUrl(event, unrollElement)

@@ -410,6 +413,10 @@
   $(document).on 'selector:change', '.nested-parameters .nested-cultivation .intervention_targets_product .selector-search', (event) ->
     landParcelPlantSelectorElement = $(event.target).closest('.nested-cultivation').find('.land-parcel-plant-selector')
     productId = $(event.target).closest('.selector').find('.selector-value').val()
+
+    nestedCultivationBlock = $(event.target).closest('.nested-cultivation')
+    harvestInProgressError = $(nestedCultivationBlock).find('.harvest-in-progress-error')
+    $(harvestInProgressError).remove() if $(harvestInProgressError).length > 0

     E.interventionForm.checkPlantLandParcelSelector(productId, landParcelPlantSelectorElement)
     E.interventionForm.checkHarvestInProgress(event, productId, landParcelPlantSelectorElement)
@@ -468,14 +475,17 @@
         url: "/backend/products/interventions/#{ productId }/has_harvesting",
         data: { intervention_started_at: interventionStartedAt }
         success: (data, status, request) ->
+          nestedCultivationBlock = $(event.target).closest('.nested-cultivation')
+          unrollBlock = $(nestedCultivationBlock).find('.intervention_targets_product .controls')
+          harvestInProgressError = $(unrollBlock).find('.harvest-in-progress-error')
+
           if data.has_harvesting
-           nestedCultivationBlock = $(event.target).closest('.nested-cultivation')
            unrollElement = $(nestedCultivationBlock).find('.intervention_targets_product .selector-search')
-           unrollBlock = $(nestedCultivationBlock).find('.intervention_targets_product .controls')
-
-           error = $("#{ unrollElement.attr('data-harvest-in-progress-error-message') }")
-
+
+           error = $("#{ unrollElement.attr('data-harvest-in-progress-error-message') }")
            $(unrollBlock).append(error)
+          else if $(harvestInProgressError).length > 0
+            $(harvestInProgressError).remove()

   $(document).ready ->


Add scroll listener to position drawer while animating.

--- src/drawer/transition/push.coffee
+++ src/drawer/transition/push.coffee
@@ -28,6 +28,11 @@
       @root.style.top = '0'
       drawerDim = 'width'

+
+  @__drawerScrollListener = =>
+    @root.style.top = "#{window.pageYOffset}px"
+
+
   @getRenderedDimensions().then (d) =>
     if dir is 'in'
       @root.style[s] = 'auto' for s in ['top', 'right', 'bottom', 'left']
@@ -39,7 +44,8 @@
       @fire 'closing'

     if @side in ['left', 'right']
-      @root.style.top = "#{window.pageYOffset}px"
+      @__drawerScrollListener()
+      window.addEventListener 'scroll', @__drawerScrollListener

     @root.style[@side] = "-#{d[drawerDim]}px"

@@ -118,6 +124,7 @@
     if @side in ['left', 'right']
       @root.style.top = '0'
     @root.style[@side] = '0'
+    window.removeEventListener 'scroll', @__drawerScrollListener

   ###*
@@ -126,3 +133,4 @@
   # @function afterIn
   ###
   afterOut: ->
+    window.removeEventListener 'scroll', @__drawerScrollListener


Client: AccountSettings: Updated viewAppended method to switch off send email notifications switch.

--- client/account/lib/views/accountemailnotifications.coffee
+++ client/account/lib/views/accountemailnotifications.coffee
@@ -20,7 +20,18 @@
         height   : 25

     whoami().fetchEmailFrequency (err, frequency) =>
+
+      path = kd.singletons.router.getCurrentPath()
+
+      if path.search("unsubscribe") > -1
+        globalFlag  = no
+        frequency.global = globalFlag
+        notify_ 'You are unsubscribed from all email notifications.'
+      else
+        globalFlag = frequency.global
+
       @putContents frequency or {}
+      @switched 'global', globalFlag
       @handleGlobalState frequency.global
       loader.destroy()


Remove old broken code

Remove old broken code

--- app/assets/javascripts/jquery.infinite-pages.js.coffee
+++ app/assets/javascripts/jquery.infinite-pages.js.coffee
@@ -85,7 +85,6 @@
         @_loading()

         $container = @$container
-        @requestAts.push +new Date # note when this request started
         $.getScript(@$container.find(@options.navSelector).attr('href'))
           .done(=> @_success($container))
           .fail(=> @_error($container))


live() -> correct on()

live() -> correct on()

--- app/assets/javascripts/wafflemix/admin/admin.js.coffee
+++ app/assets/javascripts/wafflemix/admin/admin.js.coffee
@@ -40,7 +40,7 @@
       finalstring = finalstring + type + "[" + index + "]" + "[id]=" + id + "&" + type + "[" + index + "][parent_id]=" + parent_id + "&" + type + "[" + index + "][position]=" + index + "&"
     $.post($(this).data('update-url'), finalstring)

-  $('.add_content_link').on 'click', (event) ->
+  $(document).on "click", ".add_content_link", (event) ->
     if $('.add_content_link').hasClass('disabled')
       return false
     else
@@ -49,7 +49,7 @@
       $('.add_content_link').tab('show');
       return false

-  $('#submit_content_part').on 'click', (event) ->
+  $(document).on "click", "#submit_content_part", (event) ->
     $.ajax
       url: '/admin/content_sections.js'
       type: 'POST'


course editor: recollect --> re-collect, etc.

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -1620,7 +1620,7 @@
         if @state[key]
             v = []
             v.push <Button key="copy_confirm" bsStyle="danger" onClick={=>@setState("#{key}":false);copy()}>
-                <Icon name="share-square-o" rotate={"180" if name=='Collect'}/> Yes, re{name.toLowerCase()}
+                <Icon name="share-square-o" rotate={"180" if name=='Collect'}/> Yes, re-{name.toLowerCase()}
             </Button>
             v.push <Button key="copy_cancel" onClick={=>@setState("#{key}":false);}>
                 <Icon name="share-square-o" rotate={"180" if name=='Collect'}/> Cancel
@@ -1630,7 +1630,7 @@
             <Button key="copy" bsStyle='warning' onClick={=>@setState("#{key}":true)}>
                 <Tip title={name} placement={placement}
                     tip={<span>{copy_tip}</span>}>
-                    <Icon name='share-square-o' rotate={"180" if name=='Collect'}/> Re{name.toLowerCase()}...
+                    <Icon name='share-square-o' rotate={"180" if name=='Collect'}/> Re-{name.toLowerCase()}...
                 </Tip>
             </Button>


Education: video mp4 source added for safari and iDevice support

--- client/Education/AppView.coffee
+++ client/Education/AppView.coffee
@@ -60,9 +60,9 @@

             https://koding.com/Terminal
-            <video autoplay loop
-              src='https://koding-cdn.s3.amazonaws.com/movies/terminal.final.webmsd.webm'
-              preload='auto'>
+            <video autoplay loop preload>
+              <source src='https://koding-cdn.s3.amazonaws.com/movies/terminal.webm'>
+              <source src='https://koding-cdn.s3.amazonaws.com/movies/terminal.mp4'>
             </video>


payment: update text of failed attempt modal

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -252,14 +252,17 @@
       @$('.summary')
     ].forEach (element) -> element.detach()

+    subject = "User: #{KD.nick()} blocked from upgrades due to too many failed attempts"
+    body = "Plan Name: #{@state.planTitle}, Plan Interval: #{@state.planInterval}"
+
     @successMessage.updatePartial "
-      We are sorry that you are having trouble upgrading.
-      Looks like there is an issue with the card you are
-      attempting to use. If you feel the error is on our end,
-      please send us relevant details at
-      support@koding.com
-      (don't forget to include your username and the plan name
-      you were trying to purchase).
+      Your access to upgrades has been locked for 24 hours
+      due to too many failed attempts. Please try again in 24 hours.
+      If you believe this is an error on our end, please send us a note at
+
+      support@koding.com with
+      relevant details (your username,
+      plan you want to purchase, etc.).
     "
     @successMessage.show()


Prevent user to focus on other's profile editing areas

--- client/Social/Members/ContentDisplays/profileview.coffee
+++ client/Social/Members/ContentDisplays/profileview.coffee
@@ -354,24 +354,25 @@
       input.setEditingMode yes
       input.focus()

-    @firstName.on "NextTabStop",     => focus @lastName
-    @firstName.on "PreviousTabStop", => focus @bio
-    @lastName.on "NextTabStop",      => focus @bio
-    @lastName.on "PreviousTabStop",  => focus @firstName
-    @bio.on "NextTabStop",           => focus @firstName
-    @bio.on "PreviousTabStop",       => focus @lastName
-
-    @firstName.on "click", -> @setEditingMode yes
-    @lastName.on "click",  -> @setEditingMode yes
-    @bio.on "click",       -> @setEditingMode yes
-
-    @firstName.on "EnterPressed", save
-    @lastName.on "EnterPressed", save
-    @bio.on "EnterPressed", save
-
-    @firstName.on "BlurHappened", save
-    @lastName.on "BlurHappened", save
-    @bio.on "BlurHappened", save
+    if @memberData is KD.whoami().getId()
+      @firstName.on "NextTabStop",     => focus @lastName
+      @firstName.on "PreviousTabStop", => focus @bio
+      @lastName.on "NextTabStop",      => focus @bio
+      @lastName.on "PreviousTabStop",  => focus @firstName
+      @bio.on "NextTabStop",           => focus @firstName
+      @bio.on "PreviousTabStop",       => focus @lastName
+
+      @firstName.on "click", -> @setEditingMode yes
+      @lastName.on "click",  -> @setEditingMode yes
+      @bio.on "click",       -> @setEditingMode yes
+
+      @firstName.on "EnterPressed", save
+      @lastName.on "EnterPressed", save
+      @bio.on "EnterPressed", save
+
+      @firstName.on "BlurHappened", save
+      @lastName.on "BlurHappened", save
+      @bio.on "BlurHappened", save

     avatarOptions  =
       showStatus      : yes


visual glitch with "New items" link in snapshot view for discussions

--- client/app/CommonViews/opinions/opinionviewheader.coffee
+++ client/app/CommonViews/opinions/opinionviewheader.coffee
@@ -81,6 +81,9 @@
       cssClass  : "new-items"
       click     : (event)=>
         event.preventDefault()
+
+        @newItemsLink.unsetClass "in"
+
         if @parent?.constructor is not DiscussionActivityOpinionView
           list.emit "AllOpinionsLinkWasClicked", @
         else


fix for issue #6

fix for issue #6

--- app/assets/javascripts/app/controllers/chat_widget.js.coffee
+++ app/assets/javascripts/app/controllers/chat_widget.js.coffee
@@ -105,6 +105,7 @@

   show: =>
     @isShown = true
+    @el.find('#chat_toogle').html('×');
     if @newMessage
       @el.find('div.well').addClass('alert-success')
       @delay( =>
@@ -138,6 +139,8 @@
   hide: =>
     @isShown = false
     @el.find('#chat_content').hide(100)
+    @el.find('#chat_content').hide(100)
+    @el.find('#chat_toogle').html('♦');

   focusIn: =>
     @focus = true


Fixed show actions in sidebar.

Fixed show actions in sidebar.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -144,6 +144,8 @@

     # get data
     @ticket = App.Ticket.fullLocal( @ticket_id )
+    @ticket.article = undefined
+    console.log('KLKL',@ticket)

     # render page
     @render(force)
@@ -278,7 +280,7 @@
             ]
             filter:    @form_meta.filter
             params:    defaults
-            bookmarkable: true
+            #bookmarkable: true
           )
           #console.log('Ichanges', modelDiff, task_state, ticket.attributes())
           #@markFormDiff( modelDiff )
@@ -323,25 +325,26 @@
           name: 'ticket'
           icon: 'message'
           callback: editTicket
-          actions: [
-            {
-              name:     'ticket-history'
-              title:    'History'
-              callback: showTicketHistory
-            },
-            {
-              name:     'ticket-merge'
-              title:    'Merge'
-              callback: showTicketMerge
-            },
-            {
-              title:    'Change Customer'
-              name:     'customer-change'
-              callback: changeCustomer
-            },
-          ]
         }
       ]
+      if !@isRole('Customer')
+        items[0]['actions'] = [
+          {
+            name:     'ticket-history'
+            title:    'History'
+            callback: showTicketHistory
+          },
+          {
+            name:     'ticket-merge'
+            title:    'Merge'
+            callback: showTicketMerge
+          },
+          {
+            title:    'Change Customer'
+            name:     'customer-change'
+            callback: changeCustomer
+          },
+        ]
       if !@isRole('Customer')
         editCustomer = (e, el) =>
           new App.ControllerGenericEdit(
@@ -573,7 +576,7 @@
     articleParams = @formParam( @$('.article-add') )
     console.log "submit article", articleParams
     articleAttributes = App.TicketArticle.attributesGet( 'edit' )
-    if articleParams['body'] || ( articleAttributes['body'] && articleAttributes['body']['null'] is false )
+    if articleParams['body'] #&& $( articleParams['body'] ).text()
       articleParams.from      = @Session.get().displayName()
       articleParams.ticket_id = ticket.id
       articleParams.form_id   = @form_id


indentation

indentation

--- app/assets/javascripts/backbone/associations/index.js.coffee
+++ app/assets/javascripts/backbone/associations/index.js.coffee
@@ -1,45 +1,45 @@
 # Connection:

 Werkd.Models.Connection.has().one('user', {
-inverse: 'connections'
-source: 'user'
-model: Werkd.Models.User
+  inverse: 'connections'
+  source: 'user'
+  model: Werkd.Models.User
 })

 # User:

 Werkd.Models.User.has().many('projects', {
-inverse: 'user'
-source: 'projects'
-collection: Werkd.Collections.Projects
+  inverse: 'user'
+  source: 'projects'
+  collection: Werkd.Collections.Projects
 })

 Werkd.Models.User.has().many('connections', {
-inverse: 'connectee'
-source: 'connections'
-collection: Werkd.Collections.Connections
+  inverse: 'connectee'
+  source: 'connections'
+  collection: Werkd.Collections.Connections
 })

 # Project:

 Werkd.Models.Project.has().one('creator', {
-inverse: 'created_projects'
-source: 'creator'
-model: Werkd.Models.User
+  inverse: 'created_projects'
+  source: 'creator'
+  model: Werkd.Models.User
 })

 Werkd.Models.Project.has().many('contributors', {
-inverse: 'contributed_projects'
-source: 'contributors'
-collection: Werkd.Collections.Users
+  inverse: 'contributed_projects'
+  source: 'contributors'
+  collection: Werkd.Collections.Users
 })

 Werkd.Models.Project.has().many('pendingContributors', {
-inverse: 'contributed_projects'
-source: 'pending_contributors'
-collection: Werkd.Collections.Users
+  inverse: 'contributed_projects'
+  source: 'pending_contributors'
+  collection: Werkd.Collections.Users
 })


Fixed blog post show more bug

Fixed blog post show more bug

--- src/scripts/controllers/BlogCtrl.coffee
+++ src/scripts/controllers/BlogCtrl.coffee
@@ -12,7 +12,8 @@
         'background-attachment': 'fixed'

     $scope.revealPost = (id) ->
-        $scope.blogPosts[id].revealed = true
+        for post in $scope.blogPosts
+            if post.id is id then post.revealed = true

     $scope.searchQuery = ''
     $scope.filterPosts = (searchTerm) ->


AppController: Update permission selectbox when RTM value changed.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1021,6 +1021,7 @@
     @rtm.on 'MapValueChanged', (map, event) =>

       if      map is @myWatchMap  then @handleWatchMapChange event
+      else if map is @permissions then @handlePermissionsMapChange event

   handleWatchMapChange: (event) ->
@@ -1033,6 +1034,10 @@
     else unless newValue
       @statusBar.emit 'ParticipantUnwatched', property

+
+  handlePermissionsMapChange: (event) ->
+
+    @chat.settingsPane.defaultPermission.setValue event.newValue

   handleChange: (change) ->


--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -124,6 +124,11 @@
       # https://app.asana.com/0/1177356931469/7014047104322
       return  if actorAccount.type is 'unregistered'
       fetchSubjectObj (err, subjectObj)=>
+
+        # TODO: Cross group notifications is not working, so hide for now. -- fka
+        # https://app.asana.com/0/3716548652471/7601810287306
+        return if err or not subjectObj
+
         actorName = KD.utils.getFullnameFromAccount actorAccount
         options.title = switch actionType
           when "reply", "opinion"


only fetch published positions on the proposals object

Former-commit-id: 8ad07058cd487c7982a43e5c32eab1b275ed267f [formerly 1be83cb237b95d653199cc8ba0bb6abbd73d72a6] [formerly 9e66781b660cc51b1d83adbb402e1381a1405079 [formerly 9e66781b660cc51b1d83adbb402e1381a1405079 [formerly 2c5b4a173cf860ec4ff139e0ae391d1897d2b502]]]
Former-commit-id: f5758c284bc840ab0243cde667fd3299c4cf3c97 [formerly 949d4e4f9546fe55f97e832a253d9de4bb6598a0]
Former-commit-id: ddc9c01fa0670b1bdc69905a925e15e03b3dec9d
Former-commit-id: c14bfcb585757d5971fb497c30221262a9da6040

--- app/assets/javascripts/backbone/entities/position.coffee
+++ app/assets/javascripts/backbone/entities/position.coffee
@@ -128,7 +128,7 @@
         @all_positions.add position, {merge: true}

     getPositionsByProposal : (proposal_id) ->
-      new Entities.Positions @all_positions.where({proposal_id: proposal_id})
+      new Entities.Positions @all_positions.where({proposal_id: proposal_id, published : true})

     getPositionsByUser : (user_id) ->
       new Entities.Positions @all_positions.where({user_id: user_id})


Fixes channel deletion

Fixes channel deletion

Signed-off-by: Akash Manohar J <e4cf979cf717fd295f4e4edc427ee1aee78fa7dc@akash.im>

--- app/assets/javascripts/backbone/views/chatarea.js.coffee
+++ app/assets/javascripts/backbone/views/chatarea.js.coffee
@@ -4,7 +4,7 @@
   # className: 'channels'

   events:
-    "click .ui-icon-close" : "deleteChannel"
+    "click .close_channel" : "deleteChannel"
     "click .create_channel": "createChannel"

   render: ->


me page kinda works again

me page kinda works again

--- app/assets/javascripts/models/user/user_viewmodel.coffee
+++ app/assets/javascripts/models/user/user_viewmodel.coffee
@@ -35,47 +35,53 @@
       return timeEntry.get("issue").project
     )

-    # # Iterate over all issues ...
-    # _.each(projectIssues, (timings, repositoryName) =>
+    projectIssues = _.transform(projectIssues,
+      (result, issues, key) ->
+        result[key] = _.groupBy(issues,
+          (timeEntry) ->
+            return timeEntry.get("issue").number
+        )
+    )

-    #   # ... and group by their users
-    #   userProjects = _.groupBy(timings, (timing) ->
-    #     return timing.get("userId")
-    #   )
+    # Finally add the days of the month to every project...
+    _.each(projectIssues, (project, repositoryName) =>

-      # Finally add the days of the month to every project...
-    issues = _.transform(projectIssues,
-      (result, project, key) =>
-        result[key] = Utils.range(1, @get("date").daysInMonth()).map(
-          (day) -> return Utils.sum(
-            _.filter(project,
-              (project) -> return moment(project.get("timestamp")).date() == day
-            ).map(
-              (projectFilterdByDay) -> return projectFilterdByDay.get("duration")
+      issueGroups = _.map(project,
+        (issues) =>
+          name : "Issue Name"
+          number : issues[0].get("issue").number
+          values : Utils.range(1, @get("date").daysInMonth()).map(
+            (day) -> return Utils.sum(
+              _.filter(issues,
+                (issue) -> return moment(issue.get("timestamp")).date() == day
+              ).map(
+                (projectFilterdByDay) -> return projectFilterdByDay.get("duration")
+              )
             )
           )
         )
+
+      # Sum up the total amount of hours per day for every issue
+      sumDaily = Utils.range(1, @get("date").daysInMonth()).map((i) -> return Utils.sum(issueGroups.map((issue) -> issue.values[i - 1]))) #-1 because days start with 1 and arrays with 0
+      sumTotal = Utils.sum(sumDaily)
+
+      # Add that shit to the collection as a table 'header' for every user
+      @get("rows").add(
+        isHeader : true
+        name : repositoryName
+        sum : sumTotal
+        dailyTimeEntries : sumDaily
       )

-    # Sum up the total amount of hours per day for every issue
-    sumDaily = Utils.range(1, @get("date").daysInMonth()).map((i) -> return Utils.sum(_.values(issues), i - 1)) #-1 because days start with 1 and arrays with 0
-    sumTotal = Utils.sum(sumDaily)
-
-    #Add that shit to the collection as a table 'header' for every user
-    @get("rows").add(
-      isHeader : true
-      name : repositoryName
-      sum : sumTotal
-      dailyTimeEntries : sumDaily
-    )
-
-    # Add the daily individual time logs to the collection
-    _.each(issues, (dailyEntries, userID) =>
-      @get("rows").add(
-        isHeader : false
-        name : @usersCollection.getNameById(userID)
-        sum : Utils.sum(dailyEntries)
-        dailyTimeEntries : dailyEntries
+      # Add the daily individual time logs to the collection
+      _.each(issueGroups, (group) =>
+        {name, number, values} = group
+        @get("rows").add(
+          isHeader : false
+          name : "#{number} #{name}"
+          sum : Utils.sum(values)
+          dailyTimeEntries : values
+        )
       )
     )


Refactor loading of order responses

Refactor loading of order responses

--- app/assets/javascripts/sprangular/services/orders.coffee
+++ app/assets/javascripts/sprangular/services/orders.coffee
@@ -4,11 +4,16 @@
     find: (number) ->
       $http.get("/api/orders/#{number}")
         .then (response) ->
-          Sprangular.extend(response.data, Sprangular.Order)
+          service._loadOrder(response.data)

     all: ->
       $http.get("/api/orders")
         .then (response) ->
-          Sprangular.extend(response.data, Sprangular.Order)
+          _.map response.data, (record) ->
+            service._loadOrder(record)
+
+    _loadOrder: (data) ->
+      order = new Sprangular.Order
+      order.load(data)

   service


add method to uncache derived values

add method to uncache derived values

--- client/lanes/models/Base.coffee
+++ client/lanes/models/Base.coffee
@@ -222,6 +222,8 @@
             _.extend(data, this.associations.dataForSave(this, options))
         data

+    unCacheDerived: (name) ->
+        delete this._cache[name]

     # True if the model has "name" as either a prop or session attribute
     hasAttribute: (name) ->


Simplify parameter name

Simplify parameter name

--- client/ide/lib/views/statusbar/idestatusbaravatarview.coffee
+++ client/ide/lib/views/statusbar/idestatusbaravatarview.coffee
@@ -145,10 +145,10 @@
         delegate    : this

-  setWatchState: (shouldWatch) ->
+  setWatchState: (state) ->

     @toggleClass 'watching'
-    methodName = if shouldWatch then 'watchParticipant' else 'unwatchParticipant'
+    methodName = if state then 'watchParticipant' else 'unwatchParticipant'

     kd.singletons.appManager.tell 'IDE', methodName, @nickname


avatar weirdness

avatar weirdness

--- client/landing/app/CommonViews/avatarviews/avatarview.coffee
+++ client/landing/app/CommonViews/avatarviews/avatarview.coffee
@@ -1,7 +1,7 @@
 # FIXME : render runs on every data change in account object which leads to a flash on avatarview. Sinan 08/2012

 class AvatarView extends LinkView
-
+
   constructor:(options = {},data)->

     options.cssClass or= ""
@@ -31,8 +31,12 @@
     host = "#{location.protocol}//#{location.host}/"
     # @$().attr "title", options.title or "#{Encoder.htmlDecode profile.firstName}'s avatar"

-    # this is a temp fix to avoid avatar flashing on each account change - Sinan 08/2012
-    bgImg = "url(#{location.protocol}//gravatar.com/avatar/#{profile.hash}?size=#{options.size.width}&d=#{encodeURIComponent(host + 'images/defaultavatar/default.avatar.' + options.size.width + '.png')})"
+    # this is a temp fix to avoid avatar flashing on every account change - Sinan 08/2012
+    bgImg = unless profile.hash
+      "url(#{KD.apiUri}/images/defaultavatar/default.avatar.#{options.size.width}.png)"
+    else
+      "url(#{location.protocol}//gravatar.com/avatar/#{profile.hash}?size=#{options.size.width}&d=#{encodeURIComponent(host + 'images/defaultavatar/default.avatar.' + options.size.width + '.png')})"
+
     if @bgImg isnt bgImg
       @$().css "background-image", bgImg
       @bgImg = bgImg


Button: Added default options test.

Button: Added default options test.

--- src/tests/components/test_button.coffee
+++ src/tests/components/test_button.coffee
@@ -24,6 +24,11 @@

     it 'should extend spark.core.View', ->
       expect(button instanceof spark.core.View).toBeTruthy()
+
+
+  it 'should have default options', ->
+    b = new spark.components.Button null, null
+    expect(b.hasClass('view')).toBeTruthy()

   describe 'template', ->


Fixing up command-line args for --stdio and --eval. Now makes more sense with --run by default.

--- command.coffee
+++ command.coffee
@@ -77,16 +77,16 @@
   o: options
   code_opts: compile_options source
   try
-    if      o.tokens            then print_tokens CoffeeScript.tokens code
-    else if o.nodes             then puts CoffeeScript.nodes(code).toString()
-    else if o.run               then CoffeeScript.run code, code_opts
+    if      o.tokens      then print_tokens CoffeeScript.tokens code
+    else if o.nodes       then puts CoffeeScript.nodes(code).toString()
+    else if o.run         then CoffeeScript.run code, code_opts
     else
       js: CoffeeScript.compile code, code_opts
-      if      o.compile         then write_js source, js
-      else if o.lint            then lint js
-      else if o.print or o.eval then print js
+      if o.print          then process.stdio.write js
+      else if o.compile   then write_js source, js
+      else if o.lint      then lint js
   catch err
-    if o.watch                  then puts err.message else throw err
+    if o.watch            then puts err.message else throw err

 # Attach the appropriate listeners to compile scripts incoming over **stdin**,
 # and write them back to **stdout**.
@@ -96,7 +96,7 @@
   process.stdio.addListener 'data', (string) ->
     code += string if string
   process.stdio.addListener 'close', ->
-    process.stdio.write CoffeeScript.compile code, compile_options('stdio')
+    compile_script 'stdio', code

 # Watch a list of source CoffeeScript files using `fs.watchFile`, recompiling
 # them every time the files are updated. May be used in combination with other
@@ -140,7 +140,8 @@
 parse_options: ->
   option_parser: new optparse.OptionParser SWITCHES, BANNER
   o: options:    option_parser.parse(process.argv)
-  options.run:   not (o.compile or o.print or o.lint or o.eval)
+  options.run:   not (o.compile or o.print or o.lint)
+  options.print: !!  (o.print or (o.eval or o.stdio and o.compile))
   sources:       options.arguments[2...options.arguments.length]

 # The compile-time options to pass to the CoffeeScript compiler.


Make autodetected binaries work on Windows

The output of `where` command in the autodetection had an unnecessary
CRLF, not removed, so arguments weren't passed correctly. This patch
fixes it.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -76,15 +76,13 @@
             })

   @findBinary: (pkg) ->
-    if process.platform is "win32"
-      return @runCommandOut("where", [pkg]);
-    else
-      data = @runCommandOut("which", [pkg]);
+    command = if process.platform is "win32" then "where" else "which"
+    data = @runCommandOut(command, [pkg]);

-      if data.status == 0 && data.stdoutData.length >= 0
-        data.stdoutData = data.stdoutData.replace(/^\s+|\s+$/g, "")
+    if data.status == 0 && data.stdoutData.length >= 0
+      data.stdoutData = data.stdoutData.replace(/^\s+|\s+$/g, "")

-      return data
+    return data

   @watchConfig: ->
     atom.config.onDidChange "tokamak.autocompleteBlacklist", ({newValue, oldValue}) ->


fix to keep final `,`  in string( comma at last element in list )

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -786,11 +786,10 @@
   trailingSpaces = text[end...] if end isnt -1
   text = text[start...end]
   {tokens, separators} = splitIntoTokensAndSeparators(text)
-  separators.push('')
   items = fn(tokens)
   result = ''
   for [item, separator] in _.zip(items, separators)
-    result += item + separator
+    result += item + (separator ? '')
   leadingSpaces + result + trailingSpaces

 splitIntoTokensAndSeparators = (text) ->
@@ -838,15 +837,16 @@
         separator = ''

       token += char
-  if token
-    tokens.push(token)
+
+  tokens.push(token) if token
+  separators.push(separator) if separator

   if separators.some((separator) -> ',' in separator)
     # When some separator contains `,` treat white-space separator is just part of token.
     # So we move white-space only sparator into tokens by joining mis-separatoed tokens.
     newTokens = []
     newSeparators = []
-    while separators.length
+    while (separators.length and tokens.length)
       separator = separators.shift()
       token = tokens.shift()
       if ',' in separator
@@ -854,7 +854,7 @@
         newSeparators.push(separator)
       else
         tokens.unshift(token + separator + tokens.shift())
-    newTokens.push(tokens.shift())
+    newTokens.push(tokens.shift()) if tokens.length
     tokens = newTokens
     separators = newSeparators


Remove copyObjectOmittingProperties(); not being used.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -193,12 +193,6 @@
   # locale-sensitive uppercase detection
   hasUpperCase: (s) -> s.toLowerCase() != s

-  # Return a copy of object, but with some of its properties omitted.
-  copyObjectOmittingProperties: (obj, properties...) ->
-    obj = extend {}, obj
-    delete obj[property] for property in properties
-    obj
-
   # Does string match any of these regexps?
   matchesAnyRegexp: (regexps, string) ->
     for re in regexps


Preset for key/value form fields. Close #398

--- core/htdocs_source/src/pods/oxisection-form/component.coffee
+++ core/htdocs_source/src/pods/oxisection-form/component.coffee
@@ -23,6 +23,11 @@
                     fields.insertAt index+i, clone
             if clonable.name not in names
                 names.push clonable.name
+
+        for field in fields
+            if field.value and typeof field.value is "object"
+                field.name = field.value.key
+                field.value = field.value.value

         for name in names
             clones = (f for f in fields when f.name is name)


Social: remove nonfunctioning 'Follow' and 'Start a conversation' buttons from profile page

--- client/Social/Members/ContentDisplays/profileview.coffee
+++ client/Social/Members/ContentDisplays/profileview.coffee
@@ -444,19 +444,6 @@
       pistachio   : userDomain
       click       : (event) =>
         KD.utils.stopDOMEvent event unless @memberData.onlineStatus is "online"
-
-    if KD.whoami().getId() is @memberData.getId()
-      @followButton = new KDCustomHTMLView
-      @conversationButton = new KDCustomHTMLView
-    else
-      @followButton = new MemberFollowToggleButton
-        style : "solid small green"
-      , @memberData
-
-      @conversationButton = new KDButtonView
-        style     : 'thin small gray'
-        title     : 'Start a conversation'
-        cssClass  : 'conversation-btn'

     if KD.checkFlag('super-admin') and @memberData.getId() isnt KD.whoami().getId()
     then @trollButton = new TrollButtonView style : 'thin medium red', data
@@ -682,8 +669,6 @@
         {{> @avatar}}

{{> @firstName}} {{> @lastName}}

         {{> @bio }}
-        {{> @followButton}}
-        {{> @conversationButton}}
         {{> @trollButton}}

           {{> @followers}}


Let save as works.

Let save as works.

--- client/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/app/Applications/Ace.kdapplication/AppView.coffee
@@ -18,8 +18,9 @@
     @saveButton = new KDButtonViewWithMenu
       title         : "Save"
       style         : "editor-button save-menu"
+      type          : "contextmenu"
       delegate      : @
-      menu          : [@getSaveMenu()]
+      menu          : @getSaveMenu.bind @
       callback      : ()=>
         @ace.requestSave()

@@ -121,16 +122,13 @@
       view        : new AceSettingsView
         delegate  : @ace

-
   getSaveMenu:->

-    items : [
-      title : "Save as..."
-      id    : 13
-      parentId : null
-      callback : =>
+    "Save as..." :
+      id         : 13
+      parentId   : null
+      callback   : =>
         @openSaveDialog()
-    ]

   _windowDidResize:->


Undo/redo batching: Replace setTimeout with an action

This ensures that the command batch will only be ended after all queued
up actions have fired, yet still gives us the clean API of calling
`startCommandBatch` and `endCommandBatch` from outside.

[#101973812]

--- src/code/utils/undo-redo.coffee
+++ src/code/utils/undo-redo.coffee
@@ -2,6 +2,8 @@
 CodapConnect = require '../models/codap-connect'

 DEFAULT_CONTEXT_NAME = 'building-models'
+
+internalEndCommandBatchAction = Reflux.createAction()

 class Manager
   constructor: (options = {}) ->
@@ -11,20 +13,21 @@
     @savePosition = -1
     @changeListeners = []
     @currentBatch = null
+    internalEndCommandBatchAction.listen @_finalizeEndComandBatch, @

   startCommandBatch: ->
     @currentBatch = new CommandBatch() unless @currentBatch

   endCommandBatch: ->
-    # The setTimeout here is ugly, but allows us to call this directly after a call
-    # to a store's action, which happens via an async callback, and ensure that
-    # endCommandBatch will be pushed to the back of the execution stack
-    setTimeout =>
-      if @currentBatch
-        @commands.push @currentBatch
-        @stackPosition++
-        @currentBatch = null
-    , 1
+    # calling this via an action pushes it to the end of the
+    # command stack, allowing any other commands to execute first
+    internalEndCommandBatchAction()
+
+  _finalizeEndComandBatch: ->
+    if @currentBatch
+      @commands.push @currentBatch
+      @stackPosition++
+      @currentBatch = null

   createAndExecuteCommand: (name, methods) ->
     result = @execute (new Command name, methods)


Encode feed URLs when client scheme includes query arguments

--- src/coffee/clients_panel.coffee
+++ src/coffee/clients_panel.coffee
@@ -51,13 +51,17 @@

   prepareClients: (pathPrefix) ->
     feed = @chooseFeed() || {}
+    return false unless feed.url
     feedUrlWithOutHttp = feed.url.replace(/^(http|https):\/\//, '')
-    return false unless feed.url

     for client in @clients
       Utils.fixIconPath(client, pathPrefix)

-      standardUrl = "#{client.scheme}#{feedUrlWithOutHttp}"
+      standardUrl = if -1 == client.scheme.indexOf('?')
+        "#{client.scheme}#{feedUrlWithOutHttp}"
+      else
+        "#{client.scheme}#{encodeURIComponent(feedUrlWithOutHttp)}"
+
       client.url = if type = client.customFeedType
         if customUrl = feed["directory-url-#{type}"]
           customUrl
@@ -72,6 +76,11 @@
     for client in @cloudClients
       Utils.fixIconPath(client, pathPrefix)
       cloudFeedUrl = if client.http then feed.url else feedUrlWithOutHttp
+      cloudFeedUrl = if -1 == client.scheme.indexOf('?')
+        cloudFeedUrl
+      else
+        encodeURIComponent(cloudFeedUrl)
+
       if client.post
         client.url = client.scheme
         client.feedUrl = cloudFeedUrl


use the view which is the closest to the original parentclass

--- core/app/backbone/views/channels/editable_channels.coffee
+++ core/app/backbone/views/channels/editable_channels.coffee
@@ -1,6 +1,6 @@
 #= require ../channels_view

-class window.EditableChannelsView extends ChannelsView
+class window.EditableChannelsView extends ChannelListView
   template: "channels/_single_editable_menu_items"

   itemView: EditableChannelItemView
@@ -8,4 +8,4 @@
   className: 'tourChannels'

   showEmptyView: => @$el.hide()
-  closeEmptyView: => @$el.show()+  closeEmptyView: => @$el.show()


Fix pricing plan

Fix pricing plan

--- app/controllers/pricing.coffee
+++ app/controllers/pricing.coffee
@@ -19,6 +19,6 @@
     ENUMS.PRICING.TIME_LIMIT is pricing.get "pricingType"

   specialPricings: Ember.computed.filter 'model', (pricing) ->
-    ENUMS.OFFER.NONE isnt pricing.get "offer"
+    ENUMS.OFFER.CUSTOM isnt pricing.get "offer"

 `export default PricingController`


Fix issue #130

Fix issue #130

--- app/controllers/profile.coffee
+++ app/controllers/profile.coffee
@@ -25,7 +25,7 @@
       return unless User.current
       @pages =
         recents: Math.ceil (User.current.classification_count / @opts.per_page) or 1
-        favorites: Math.ceil (User.current.favorites / @opts.per_page) or 1
+        favorites: Math.ceil (User.current.favorite_count / @opts.per_page) or 1
     User.bind 'sign-in', @refresh
     Quiz.bind 'quiz-user', @render
     Quiz.bind 'quiz-finished', @render


factView -> factRegion

factView -> factRegion

--- core/app/backbone/views/facts/new_discussion_view.coffee
+++ core/app/backbone/views/facts/new_discussion_view.coffee
@@ -4,7 +4,7 @@
   template: 'facts/new_discussion'

   regions:
-    factView: '.fact-region'
+    factRegion: '.fact-region'

   onRender: ->
-    @factView.show new NewFactView(model: @model)
+    @factRegion.show new NewFactView(model: @model)


wait until mount to call validate callback

Otherwise the owner of validate function may call setState in turn which
will occur inside it's render method

--- tutor/src/components/tutor-input.cjsx
+++ tutor/src/components/tutor-input.cjsx
@@ -29,9 +29,12 @@
       return ['required'] unless (inputValue? and inputValue.length > 0)
     type: 'text'

+  componentDidMount: ->
+    errors = @props.validate(@props.default)
+    @setState({errors}) unless _.isEmpty(errors)
+
   getInitialState: ->
-    errors = @props.validate(@props.default)
-    errors: errors or []
+    errors: []

   componentDidUpdate: (prevProps, prevState) ->
     @props.onUpdated?(@state) unless _.isEqual(prevState, @state)


add autofocus prop

add autofocus prop

--- tutor/src/components/tutor-input.cjsx
+++ tutor/src/components/tutor-input.cjsx
@@ -23,6 +23,7 @@
     onChange: React.PropTypes.func
     validate: React.PropTypes.func
     onUpdated: React.PropTypes.func
+    autofocus: React.PropTypes.bool

   getDefaultProps: ->
     validate: (inputValue) ->
@@ -36,6 +37,9 @@
   getInitialState: ->
     errors: []

+  componentDidMount: ->
+    @focus().cursorToEnd() if @props.autofocus
+
   componentDidUpdate: (prevProps, prevState) ->
     @props.onUpdated?(@state) unless _.isEqual(prevState, @state)

@@ -51,10 +55,12 @@

   focus: ->
     ReactDOM.findDOMNode(@refs.input)?.focus()
+    return @ # support chaining

   cursorToEnd: ->
     input = ReactDOM.findDOMNode(@refs.input)
     input.selectionStart = input.selectionEnd = input.value.length
+    return @ # support chaining

   # The label has style "pointer-events: none" set.  Unfortunantly IE 10
   # doesn't support that and refuses to pass the click through the label into the input


Menu item: Capital B

Menu item: Capital B

Instead of 'build', show 'Build'.

--- menus/build.cson
+++ menus/build.cson
@@ -3,7 +3,7 @@
     'label': 'Packages'
     'submenu': [
       {
-        'label': 'build'
+        'label': 'Build'
         'submenu': [
           {
             'label': 'Build project...'


ActivityTicker: filtering conditions are taken into isFiltered method

--- client/Social/Activity/ContentDisplays/ActivityTicker.coffee
+++ client/Social/Activity/ContentDisplays/ActivityTicker.coffee
@@ -84,8 +84,7 @@
   addActivity: (data)->
     {origin, subject} = data

-    unless @filters and "addactivity" in @filters
-      return
+    return if @isFiltered "activity"

     unless @getConstructorName(origin) and @getConstructorName(subject)
       return console.warn "data is not valid"
@@ -102,8 +101,7 @@
   deleteActivity: (data) ->
     {origin, subject} = data

-    unless @filters and "deleteactivity" in @filters
-      return
+    return if @isFiltered "activity"

     unless @getConstructorName(origin) and @getConstructorName(subject)
       return console.warn "data is not valid"
@@ -117,8 +115,7 @@
   addJoin: (data)->
     {member} = data

-    unless @filters and "member" in @filters
-      return
+    return if isFiltered "member"

     return console.warn "member is not defined in new member event"  unless member

@@ -130,9 +127,8 @@

   addFollow: (data)->
     {follower, origin} = data
-
-    unless @filters and "follow" in @filters
-      return
+
+    return if @isFiltered "follower"

     return console.warn "data is not valid"  unless follower and origin

@@ -156,8 +152,7 @@
   addLike: (data)->
     {liker, origin, subject} = data

-    unless @filters and "like" in @filters
-      return
+    return if @isFiltered "like"

     unless liker and origin and subject
       return console.warn "data is not valid"
@@ -180,8 +175,7 @@
   addComment: (data) ->
     {origin, reply, subject, replier} = data

-    unless @filters and "comment" in @filters
-      return
+    return if @isFiltered "comment"

     unless replier and origin and subject and reply
       return console.warn "data is not valid"


ace: Created a method to add key combo to editor then used it everywhere to remove lots of duplicated lines.

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -54,47 +54,17 @@
     @editor.getSession().selection.on 'changeCursor', (cursor)=>
       @emit "ace.change.cursor", @editor.getSession().getSelection().getCursor()

-    @editor.commands.addCommand
-      name    : 'find'
-      bindKey :
-        win   : 'Ctrl-F'
-        mac   : 'Command-F'
-      exec    : => @showFindReplaceView no
-
-    @editor.commands.addCommand
-      name    : 'replace'
-      bindKey :
-        win   : 'Ctrl-Shift-F'
-        mac   : 'Command-Shift-F'
-      exec    : => @showFindReplaceView yes
-
-    @editor.commands.addCommand
-      name    : 'save'
-      bindKey :
-        win   : 'Ctrl-S'
-        mac   : 'Command-S'
-      exec    : => @requestSave()
-
-    @editor.commands.addCommand
-      name    : 'save as'
-      bindKey :
-        win   : 'Ctrl-Shift-S'
-        mac   : 'Command-Shift-S'
-      exec    : => @requestSaveAs()
-
-    @editor.commands.addCommand
-      name    : 'compileAndRun'
-      bindKey :
-        win   : 'Ctrl-Shift-C'
-        mac   : 'Command-Shift-C'
-      exec    : => @getDelegate().compileAndRun()
-
-    @editor.commands.addCommand
-      name    : 'preview'
-      bindKey :
-        win   : 'Ctrl-Shift-P'
-        mac   : 'Command-Shift-P'
-      exec    : => @getDelegate().preview()
+    @addKeyCombo "save", "Ctrl-S", "Command-S", @bound "requestSave"
+
+    @addKeyCombo "saveAs", "Ctrl-Shift-S", "Command-Shift-S", @bound "requestSaveAs"
+
+    @addKeyCombo "find", "Ctrl-F", "Command-F", @bound "showFindReplaceView", no
+
+    @addKeyCombo "replace", "Ctrl-Shift-F", "Command-Shift-F", @bound "showFindReplaceView", yes
+
+    @addKeyCombo "compileAndRun", "Ctrl-Shift-C", "Command-Shift-C", => @getDelegate().compileAndRun()
+
+    @addKeyCombo "preview", "Ctrl-Shift-P", "Command-Shift-P", => @getDelegate().preview()

     @getSingleton('windowController').on "keydown", (e) =>
       {findAndReplaceView} = @getDelegate()
@@ -116,6 +86,14 @@
     findAndReplaceView.setViewHeight openReplaceView
     findAndReplaceView.setTextIntoFindInput selectedText
     findAndReplaceView.on "FindAndReplaceViewClosed", => @focus()
+
+  addKeyCombo: (name, winKey, macKey, callback) ->
+    @editor.commands.addCommand
+      name    : name
+      bindKey :
+        win   : winKey
+        mac   : macKey
+      exec    : => callback?()

   ###
   FS REQUESTS


BaseStackTemplatesView: Add not ready stack template to list without reload / refresh

--- client/stacks/lib/views/stacks/basestacktemplatesview.coffee
+++ client/stacks/lib/views/stacks/basestacktemplatesview.coffee
@@ -132,6 +132,7 @@
   showEditor: (options, stackTemplate) ->

     { inEditMode, showHelpContent } = options
+    { appManager } = kd.singletons

     @initialView.hide()

@@ -139,12 +140,27 @@
     @scrollView.addSubView @defineStackView

     @defineStackView.on 'Reload', ->
-      kd.singletons.appManager.tell 'Stacks', 'reloadStackTemplatesList'
+      appManager.tell 'Stacks', 'reloadStackTemplatesList'

     @defineStackView.on 'NewStackTemplateAdded', ({ stackTemplate, templatesView }) ->
       return  if stackTemplate.event is 'updateInstance'
       templatesView.initialView.stackTemplateList.listController.addItem stackTemplate

+
+    @defineStackView.on 'Cancel', ({ stackTemplate }) =>
+
+      return  unless stackTemplate
+      return  if stackTemplate.config.verified
+
+      stackApp = appManager.appControllers.Stacks.instances.first
+      templatesView = stackApp.getStackTemplatesViewByName 'My Stack Templates'
+
+      return  unless templatesView
+
+      # Add always "not ready" stack template to "My Stack Templates" list.
+      templatesView.initialView.stackTemplateList.listController.addStackTemplateById stackTemplate._id
+
+
     @defineStackView.on [ 'Cancel', 'Completed' ], =>
       @defineStackView.destroy()
       @initialView.show()


white space cleanup

white space cleanup

--- plugins/040-apps/app/assets/javascripts/apps.js.coffee
+++ plugins/040-apps/app/assets/javascripts/apps.js.coffee
@@ -5,9 +5,8 @@
 		$(document).on "ajax:beforeSend", ".install-app-in-background, .uninstall-app-in-background", ->
 				$(".install-button").hide()
 				_this.toggle_spinner this
-				$('.app').each ->
+				$('.app').each ->
 					$(this).find('.install-app-in-background').addClass('inactive')
-

 		$(document).on "ajax:success", ".install-app-in-background, .uninstall-app-in-background", (data, results) ->
 				_this.update_progress results["identifier"], results["content"]
@@ -64,15 +63,12 @@
 			url: _this.app(finder).data("progressPath")
 			success: (data) ->
 				_this.update_progress_message finder, data["content"]
-
 				if data["app_content"]
 					_this.update_installed_app finder, data["app_content"]
-					$('.app').each ->
+					$('.app').each ->
 						$(this).find('.install-app-in-background').removeClass('inactive')
-
 				else if data["uninstalled"]
 					_this.update_uninstalled_app finder
-
 				else
 					setTimeout (-> Apps.trace_progress(finder)), 2000


Auto-expand the discussion thread after it is scrolled to by simulating a click on the show/hide button. Note: the jQuery highlight effect doesnt work here due to a CSS !important setting on .discussion-module background color.

--- common/lib/xmodule/xmodule/js/src/annotatable/display.coffee
+++ common/lib/xmodule/xmodule/js/src/annotatable/display.coffee
@@ -105,8 +105,12 @@
             onAfter: @makeAfterScroll(el)
         })

-    makeAfterScroll: (el, duration = 500) ->
-        @_once -> el.effect 'shake', {}, duration
+    makeAfterScroll: (el, duration = 1500) ->
+        @_once ->
+            btn = $('.discussion-show', el)
+            if !btn.hasClass('shown')
+                btn.click()
+            #el.effect 'highlight', {}, duration

     makeTipContent: (el) ->
         (api) =>


Onboarding: Passed app storage flag in viewController.

--- client/Main/onboarding/onboardingviewcontroller.coffee
+++ client/Main/onboarding/onboardingviewcontroller.coffee
@@ -4,17 +4,18 @@

     super options, data

-    {@app}   = @getOptions()
-    {@items} = @getData()
+    {@app, @slug} = @getOptions()
+    {@items}      = @getData()

-    @show @items.first
+    @show @items.first, yes
     @on "NavigationRequested", (direction, itemData) =>
       @navigate direction, itemData
+
+  show: (item, setStorage) ->
+    delegate = this
+    new OnboardingItemView { delegate, @slug, @app, @items, setStorage }, item

   navigate: (direction, itemData) ->
     index = @items.indexOf itemData
     item  = if direction is "next" then @items[++index] else @items[--index]
     @show item
-
-  show: (item) ->
-    new OnboardingItemView { delegate: this, @app, @items }, item


resurrection.activity: fix app installation user identity check expression

--- client/Social/Activity/views/activitytickeritem.coffee
+++ client/Social/Activity/views/activitytickeritem.coffee
@@ -112,7 +112,7 @@

   pistachio: ->
     {target} = @getData()
-    if actor.getId is KD.whoami().getId()
+    if target.getId() is KD.whoami().getId()
       return "{{> @avatar}} You installed {{> @object}}"

     return "{{> @avatar}} {{> @actor}} installed {{> @object}}"


home/kdcli: use reusable Code Block component

--- client/home/lib/utilities/components/kdcli/view.coffee
+++ client/home/lib/utilities/components/kdcli/view.coffee
@@ -1,6 +1,7 @@
-kd               = require 'kd'
-React            = require 'kd-react'
-List             = require 'app/components/list'
+kd = require 'kd'
+React = require 'kd-react'
+List = require 'app/components/list'
+CodeBlock = require 'app/components/codeblockmodal'

 module.exports = class KDCliView extends React.Component
@@ -11,13 +12,8 @@
         kd  is a command line program that allows you to use your local
           IDE with your VMs. Copy and paste the command below into your terminal.

-
-
-          {@props.cmd}
-
-        {@props.copyKey}
-
-
Once installed, you can use kd list to list your Koding VMs and
+      <CodeBlock cmd={@props.cmd}/>
+
Once installed, you can use kd list to list your Koding VMs and
         kd mount to mount your VM to a local folder in your computer.
         For detailed instructions:

@@ -28,7 +24,7 @@

   render: ->
-
+

       {@renderKDInstallView()}


Much better and cleaner

Much better and cleaner

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -68,22 +68,20 @@
         stack = []

         files.forEach (file)->
-          if /\.kdapp$/.test file.name
+          if /\.kdapp$/.test(file.name) and file.type is 'folder'
             apps.push file

         apps.forEach (app)=>
           stack.push (cb)=>
-            manifestFile = if app.type is "folder" then \
-              FSHelper.createFileFromPath "#{app.path}/.manifest" else app
-            manifestFile.fetchContents (err, response)->
+            manifest = FSHelper.createFileFromPath "#{app.path}/.manifest"
+            manifest.fetchContents (err, response)->
               # shadowing the error is intentional here
-              # to not to break the results of the stack
+              # to not to break the result of the stack
               cb null, response

-        manifests = @constructor.manifests
-        async.parallel stack, (err, results)=>
-          warn err if err
-          results.forEach (rawManifest)->
+        {manifests} = @constructor
+        async.parallel stack, (err, result)=>
+          result.forEach (rawManifest)->
             if rawManifest
               try
                 manifest = JSON.parse rawManifest


Apps: Added refreshApps method to kodingAppsController to maintain backward compatibility.

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -295,6 +295,9 @@
         stack.push (callback) =>
           @updateUserApp app.manifest, callback
       async.series stack
+
+  # added this to keep backward compatibility
+  refreshApps: (callback) -> @syncAppStorageWithFS yes, callback

   updateUserApp:(manifest, callback)->
     appName = manifest.name


Provide profile from KD.whoami when publishing app, so we can force to use profile info in manifest file of app

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -297,6 +297,7 @@
           version     : manifest.version
           appName     : manifest.name
           userAppPath : userAppPath
+          profile     : KD.whoami().profile

       @kiteController.run options, (err, res)=>
         log "app is being published"


Fix path

Fix path

--- app/assets/javascripts/admin/models/integration.js.coffee
+++ app/assets/javascripts/admin/models/integration.js.coffee
@@ -19,7 +19,7 @@

   signup: (parameters,enabled) ->
     $.ajax
-      url: "/integrations/#{@.id}/signup"
+      url: "/stores/#{Augury.store_id}/integrations/#{@.id}/signup"
       type: 'POST'
       data:
         store_id: Augury.store_id


Using currentTarget over target, so it always uses the bound element, not whatever triggered the event

--- app/assets/javascripts/ama_layout/real_date_picker.coffee
+++ app/assets/javascripts/ama_layout/real_date_picker.coffee
@@ -8,10 +8,10 @@

   addEvents: () ->
     $('select.month').on 'change', (event) =>
-      @limitDaysInMonth $(event.target).parent()
+      @limitDaysInMonth $(event.currentTarget).parent()

     $('select.year').on 'change', (event) =>
-      @limitDaysInMonth $(event.target).parent()
+      @limitDaysInMonth $(event.currentTarget).parent()

   limitDaysInMonth: (container) ->
     year = container.find('.year').val()


Fixed that farking thing.

Fixed that farking thing.

--- app/assets/javascripts/collections/clouds/users.js.coffee
+++ app/assets/javascripts/collections/clouds/users.js.coffee
@@ -19,9 +19,9 @@
     session.get('users').findOrInitialize [],
       specific_endpoint: true
       url: @url()
-      success: (_users, status, xhr) =>
-        @add(_users.models)
-        options.success(_users, status, xhr) if options.success
+      success: (collection, resp, xhr) =>
+        $.each resp.result, (i,user) => @findOrInitialize(user)
+        options.success(collection, resp, xhr) if options.success

       error: (resp, xhr,_options) =>
         options.error(resp, xhr,_options) if options.error


fixed state validation (need to transition away)

--- app/assets/javascripts/states/runbooks/runbooks.js.coffee
+++ app/assets/javascripts/states/runbooks/runbooks.js.coffee
@@ -10,4 +10,7 @@
     models: [ B.m.HostList, B.m.CommandList ]

     validate: ->
-      return !@current_user.get("otp_required_for_login") || @password_confirmed == true
+      if !(!@current_user.get("otp_required_for_login") || @password_confirmed == true)
+        @transition "inventory"
+        return false
+      return true


use https for backend if https is used

use https for backend if https is used

--- app/assets/javascripts/typus_maiko/models/asset.js.coffee
+++ app/assets/javascripts/typus_maiko/models/asset.js.coffee
@@ -29,6 +29,7 @@
   setThumbnail: ->
     { url, previewFormat } = @attributes
     thumbnail = Maiko.image url, previewFormat
+    thumbnail = thumbnail.replace('http://cdn.maikoapp.com', 'https://maiko.a.ssl.fastly.net') if window.location.protocol == 'https:'
     @set { thumbnail }

   setCaption: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -119,6 +119,6 @@

   4.circularPoints (p, i) ->
     equals p.x.toFixed(5), points[i].x.toFixed(5), "#{p.x} == #{points[i].x}"
-    equals p.x.toFixed(5), points[i].x.toFixed(5), "#{p.x} == #{points[i].x}"
+    equals p.y.toFixed(5), points[i].y.toFixed(5), "#{p.y} == #{points[i].y}"

 module undefined


removed reference to jQuery

removed reference to jQuery

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -43,7 +43,7 @@

 test "#times", ->
   n = 5
-  equals n.times($.noop), n, "returns n"
+  equals n.times(->), n, "returns n"

 test "#times called correct amount", ->
   n = 5


Expanded for nextmoonphase

Expanded for nextmoonphase

--- moon-phase.widget/index.coffee
+++ moon-phase.widget/index.coffee
@@ -218,7 +218,10 @@
     phen = moonEl.find('.phenomenae')
     phen.empty()
     if count < 3
-      phenName = @returnPhenomenae("#{data.prevmoondata[0].phen}")
+      if data.prevmoondata?
+        phenName = @returnPhenomenae("#{data.prevmoondata[0].phen}")
+      else
+        phenName = @returnPhenomenae("#{data.nextmoondata[0].phen}")
       phen.append "
#{phenName}
"
     for d in data.moondata
       phenName = @returnPhenomenae("#{d.phen}")
@@ -227,11 +230,13 @@
     time = moonEl.find('.phentimes')
     time.empty()
     if count < 3
+      if data.prevmoondata?
+        my_time = data.prevmoondata[0].time
+      else
+        my_time = data.nextmoondata[0].time
       if @option.showAMPM
-        my_time = @returnAMPM(data.prevmoondata[0].time)
-      else
-        my_time = data.prevmoondata[0].time
-      time.append "
#{my.time}
"
+        my_time = @returnAMPM(my_time)
+      time.append "
#{my_time}
"
     for d in data.moondata
       if @option.showAMPM
         my_time = @returnAMPM(d.time)


Allow input also in degrees

Allow input also in degrees

--- components/RotateElement.coffee
+++ components/RotateElement.coffee
@@ -7,15 +7,19 @@
     @inPorts =
       element: new noflo.Port 'object'
       percent: new noflo.Port 'number'
+      degrees: new noflo.Port 'number'

     @inPorts.element.on 'data', (element) =>
       @element = element
     @inPorts.percent.on 'data', (percent) =>
-      if @element
-        @setRotation @element, percent
+      return unless @element
+      degrees = 360 * percent % 360
+      @setRotation @element, degrees
+    @inPorts.degrees.on 'data', (degrees) =>
+      return unless @element
+      @setRotation @element, degrees

-  setRotation: (element, percent) ->
-    degrees = 360 * percent % 360
+  setRotation: (element, degrees) ->
     @setVendor element, "transform", "rotate(#{degrees}deg)"

   setVendor: (element, property, value) ->


LOG - commented out, but handy debug log for memory management

--- source/Art/Engine/Core/Drawing/DrawCacheManager.coffee
+++ source/Art/Engine/Core/Drawing/DrawCacheManager.coffee
@@ -110,6 +110,7 @@

   # called every time a new element drawCache is created
   allocateCacheBitmap: (element, size) ->
+    # console.log "allocateCacheBitmap #{element.inspectedName} #{size}"
     @doneWithCacheBitmap element

     @_recycleUnusedCacheBitmap(element, size) ||


LOG - when caching is too big and must be scaled down; I'm not sure this actually even works right

--- source/Art/Engine/Core/Drawing/ElementDrawMixin.coffee
+++ source/Art/Engine/Core/Drawing/ElementDrawMixin.coffee
@@ -673,9 +673,20 @@
         else
           elementToTargetMatrix.getExactScaler()

+      originalCacheScale = cacheScale
       cacheSpaceDrawArea = elementSpaceDrawArea.mul cacheScale
+      originalCacheSpaceDrawArea = cacheSpaceDrawArea
       while cacheSpaceDrawArea.area >= maxCanvasSize
         cacheSpaceDrawArea = elementSpaceDrawArea.mul cacheScale *= .75
+
+      if originalCacheSpaceDrawArea != cacheSpaceDrawArea
+        log.warn """
+          ArtEngine.Element #{@inspectedName}._updateDrawCache
+            maxCanvasSize:            #{maxCanvasSize}
+            cacheSpaceDrawArea.area:  #{originalCacheSpaceDrawArea.area}
+            cacheSpaceDrawArea:       #{originalCacheSpaceDrawArea}
+            cacheScale changed from #{originalCacheScale} to #{cacheScale}
+            """

       cacheSpaceDrawArea = cacheSpaceDrawArea.roundOut snapTo, colorPrecision
       # don't cache if too big


Fix broken specs

Fix broken specs

--- test/javascripts/integration/super_ad_hoc_test.js.coffee
+++ test/javascripts/integration/super_ad_hoc_test.js.coffee
@@ -51,13 +51,13 @@
     Em.$('.inline-edit-form div[contenteditable]')
     .html("New contenteditable, yahoo!")
     .trigger('keyup')
-    click '.task-body .inline-edit-form .button--green:contains("Save")'
+    click '.task-body .inline-edit-body-part .button--green:contains("Save")'
   andThen ->
     ok Em.$.trim(find('.inline-edit').text()).indexOf('yahoo') isnt -1
-    click '.inline-edit .glyphicon-trash'
+    click '.inline-edit-body-part .glyphicon-trash'
   andThen ->
-    ok Em.$.trim(find('.inline-edit').text()).indexOf('Are you sure?') isnt -1
-    click '.inline-edit .delete-button'
+    ok Em.$.trim(find('.inline-edit-body-part').text()).indexOf('Are you sure?') isnt -1
+    click '.inline-edit-body-part .delete-button'
   andThen ->
     ok Em.$.trim(find('.inline-edit').text()).indexOf('yahoo') is -1
     click '.overlay-close-button:first'
@@ -69,17 +69,20 @@
   click '.adhoc-content-toolbar .glyphicon-plus'
   click '.adhoc-content-toolbar .adhoc-toolbar-item--list'
   andThen ->
-    ok exists find '.inline-edit-form input[type=checkbox]:disabled'
+    ok exists find '.inline-edit-form .item-remove'
     ok exists find '.inline-edit-form label[contenteditable]'
     ok exists find '.button--disabled:contains("Save")'
     Em.$('.inline-edit-form label[contenteditable]')
     .html("Here is a checkbox list item")
     .trigger('keyup')
-    click '.task-body .inline-edit-form .button--green:contains("Save")'
+    click '.task-body .inline-edit-body-part .button--green:contains("Save")'
   andThen ->
     ok Em.$.trim(find('.inline-edit').text()).indexOf('checkbox list item') isnt -1
-    ok exists find '.inline-edit-form input[type=checkbox]'
-    click '.inline-edit .glyphicon-trash'
+    ok exists find '.inline-edit input[type=checkbox]'
+    click '.inline-edit-body-part .glyphicon-trash'
+  andThen ->
+    ok Em.$.trim(find('.inline-edit-body-part').text()).indexOf('Are you sure?') isnt -1
+    click '.inline-edit-body-part .delete-button'
   andThen ->
     ok Em.$.trim(find('.inline-edit').text()).indexOf('checkbox list item') is -1
     click '.overlay-close-button:first'


:bug: coreSpecFailed is not an array

:bug: coreSpecFailed is not an array

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -141,7 +141,7 @@

     method specs, (error, results) ->
       failedPackages = []
-      coreSpecFailed = []
+      coreSpecFailed = null

       if process.env.ATOM_SPECS_TASK is 'packages'
         [failedPackages] = results
@@ -154,7 +154,7 @@
       elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
       grunt.log.ok("Total spec time: #{elapsedTime}s using #{concurrency} cores")
       failures = failedPackages
-      failures.push "atom core" if coreSpecFailed.length > 0
+      failures.push "atom core" if coreSpecFailed

       grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0


Store the version available for update.

Store the version available for update.

--- lib/about.coffee
+++ lib/about.coffee
@@ -2,6 +2,9 @@

 AboutView = null
 StatusBarView = null
+
+# The local storage key for the available update version.
+AvailableUpdateVersion = 'about:version-available'

 aboutURI = 'atom://about'

@@ -24,9 +27,12 @@
     @subscriptions.add atom.commands.add 'atom-workspace', 'about:view-release-notes', ->
       require('shell').openExternal('https://atom.io/releases')

+    availableVersion = localStorage.getItem(AvailableUpdateVersion)
+    localStorage.removeItem(AvailableUpdateVersion) if availableVersion is atom.getVersion()
+
     if atom.isReleasedVersion()
-      @subscriptions.add atom.commands.add 'atom-workspace', 'window:update-available', =>
-        @updateAvailable = true
+      @subscriptions.add atom.onUpdateAvailable ({releaseVersion}) =>
+        localStorage.setItem(AvailableUpdateVersion, releaseVersion)
         @showStatusBarIfNeeded()

   deactivate: ->
@@ -37,8 +43,12 @@
     @statusBar = statusBar
     @showStatusBarIfNeeded()

+  isUpdateAvailable: ->
+    availableVersion = localStorage.getItem(AvailableUpdateVersion)
+    availableVersion? and availableVersion isnt atom.getVersion()
+
   showStatusBarIfNeeded: ->
-    return unless @updateAvailable and @statusBar?
+    return unless @isUpdateAvailable() and @statusBar?

     StatusBarView ?= require './about-status-bar'


make clusterer remove markers

make clusterer remove markers

--- app/assets/javascripts/forge/controllers.js.coffee
+++ app/assets/javascripts/forge/controllers.js.coffee
@@ -88,16 +88,19 @@
       else
         wmsLayerBottom = null

-  $scope.$on 'buildings:updated', (event, buildings) ->
+  mcOptions =
+    imagePath: 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m'
+    minimumClusterSize: 10
+    maxZoom: 16
+  $scope.$on 'buildings:updated', (event, buildings) =>
     $scope.buildings = buildings
 #    $scope.meta = BuildingService.meta
     markers = $scope.buildings.map (building) -> $scope.createMarker(building)
-    NgMap.getMap().then (map) ->
-      mcOptions =
-        imagePath: 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m'
-        minimumClusterSize: 10
-        maxZoom: 16
-      return new MarkerClusterer(map, markers, mcOptions)
+    NgMap.getMap().then (map) =>
+      @clusterer or= new MarkerClusterer(map, [], mcOptions)
+      @clusterer.clearMarkers()
+      @clusterer.addMarkers(markers)
+      #return new MarkerClusterer(map, markers, mcOptions)

   $scope.createMarker = (building) ->
     marker = new google.maps.Marker


Load autocomplete data when loading an issue page

This ensures the dropdown is fully available the moment a user starts
typing out a username, issue ID, etc. While this won't speed up loading
the autocomplete data itself it should at least make it less annoying
for the user.

--- app/assets/javascripts/gfm_auto_complete.js.coffee
+++ app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -34,7 +34,7 @@
       searchKey: 'search'
       callbacks:
         beforeSave: (members) ->
-          $.map members, (m) ->
+          $.map members, (m) ->
             title = m.name
             title += " (#{m.count})" if m.count

@@ -50,7 +50,7 @@
       insertTpl: '${atwho-at}${id}'
       callbacks:
         beforeSave: (issues) ->
-          $.map issues, (i) ->
+          $.map issues, (i) ->
             id:     i.iid
             title:  sanitize(i.title)
             search: "#{i.iid} #{i.title}"
@@ -63,18 +63,17 @@
       insertTpl: '${atwho-at}${id}'
       callbacks:
         beforeSave: (merges) ->
-          $.map merges, (m) ->
+          $.map merges, (m) ->
             id:     m.iid
             title:  sanitize(m.title)
             search: "#{m.iid} #{m.title}"

-    input.one 'focus', =>
-      $.getJSON(@dataSource).done (data) ->
-        # load members
-        input.atwho 'load', '@', data.members
-        # load issues
-        input.atwho 'load', 'issues', data.issues
-        # load merge requests
-        input.atwho 'load', 'mergerequests', data.mergerequests
-        # load emojis
-        input.atwho 'load', ':', data.emojis
+    $.getJSON(@dataSource).done (data) ->
+      # load members
+      input.atwho 'load', '@', data.members
+      # load issues
+      input.atwho 'load', 'issues', data.issues
+      # load merge requests
+      input.atwho 'load', 'mergerequests', data.mergerequests
+      # load emojis
+      input.atwho 'load', ':', data.emojis


handle left and right keys

handle left and right keys

--- app/assets/javascripts/handle_aria_event.js.coffee
+++ app/assets/javascripts/handle_aria_event.js.coffee
@@ -21,8 +21,8 @@
   @main_element = $(element)
   $(element).attr('tab-index', 0)

-  @maxrow = max_row
-  @maxcol =
+  @maxrow = Number(max_row)
+  @maxcol = Number(max_col)
   @keys = keyCodes
   $('#row_0_col_0').attr('tabindex', "0")
   console.log "In Left and right key are in progress"
@@ -32,10 +32,6 @@
   $row_event = @main_element.find('.row[role="row"] .span3')
   $row_event.bind 'keyup', (e)->
     console.log "key up"
-    thisObj.bind_grid_cell($(this), e)
-
-  $row_event.bind 'keypress', (e)->
-    console.log('key press 1')
     thisObj.bind_grid_cell($(this), e)

@@ -53,19 +49,52 @@
       e.stopPropagation
       return false
       break
-    when 37
-      $newCell = $curCell.prev()
-      $newCell.attr("tabindex", "0").focus()
+    when 37 #left key
+      $newCell = $curCell.prev()
+      if $newCell.length
+        $newCell.attr("tabindex", "0").focus()
+      else
+        row_number = $curCell.attr('id').split('_')[1]
+        col_number = $curCell.attr('id').split('_')[3]
+        if row_number > 0
+          new_row_number = Number(row_number) - 1
+          $newCell = $("#row_#{new_row_number}_col_#{@maxcol-1}")
+          $newCell.attr("tabindex", "0").focus()
+        else
+          col_count_last_row = $("#row_index_#{@maxrow - 1} .thumbnails").children('li.span3').size() - 1
+          $newCell = $("#row_#{@maxrow - 1}_col_#{col_count_last_row}")
+          console.log $newCell
+          $newCell.attr("tabindex", "0").focus()
+
+      $curCell.removeAttr("tabindex")
       e.stopPropagation
       return false
       break
-    when 38
+    when 38 #Up key
       break
-    when 39
+    when 39 #right key
       $newCell = $curCell.next()
-      $newCell.attr("tabindex", "0").focus()
+      if $newCell.length
+        $newCell.attr("tabindex", "0").focus()
+      else
+        row_number = $curCell.attr('id').split('_')[1]
+        col_number = $curCell.attr('id').split('_')[3]
+        console.log "max row"
+        console.log @maxrow
+        console.log row_number
+        if row_number < (@maxrow - 1)
+          console.log "inside if "
+          new_row_number = Number(row_number) + 1
+          $newCell = $("#row_#{new_row_number}_col_0")
+          $newCell.attr("tabindex", "0").focus()
+        else
+          $newCell = $('#row_0_col_0')
+          $newCell.attr("tabindex", "0").focus()
+
       $curCell.removeAttr("tabindex")
+      e.stopPropagation
       break
+
 $(document).ready ->
   app = new AriaEvent('#game-levels', $("#game-levels .row[role='row']").size(), 5)
   app.bindhandler()


Make button to reject task work, squash little bugs

--- app/assets/javascripts/vimeo_integration.js.coffee
+++ app/assets/javascripts/vimeo_integration.js.coffee
@@ -85,8 +85,8 @@

"

     translationOptions: (languageChoice)->
-      if languageChoice is "en"
-        template = "What language would you like captions in?
+      template = if languageChoice is "en"
+        "What language would you like captions in?

         <select id=\"verbalizeit-choose-to-language\">" +
           verbalizeIt.templates.languagesAsOptions() +
@@ -202,7 +202,8 @@
       $("#verbalizeit-caption-form").find(".error_message").text("")
       email = $("#verbalizeit-email").val()
       password = $("#verbalizeit-password").val()
-      action(email, password)
+      action(email, password).fail ->
+        $(".verbalizeit-button").removeClass("disabled")

     login: (email, password)->
       $.post("https://stagingapi.verbalizeit.com/api/customers/login",
@@ -328,6 +329,10 @@
         verbalizeIt.languageHandler.handleTaskSubmission(
           fromLanguageCode, toLanguageCode)

+    rejectTask: ->
+      $("#verbalizeit-caption-form").on "click.rejectTask", "#verbalizeit-reject-task", ->
+        verbalizeIt.languageHandler.displayLanguages()
+
     downloadFileLink: ->
       $("#verbalizeit-caption-form").on "click", ".verbalizeit-download-link", (e)->
         e.preventDefault()


Add deleteIfOutOfBounds

Add deleteIfOutOfBounds

--- app/classifier/drawing-tools/column-rectangle.cjsx
+++ app/classifier/drawing-tools/column-rectangle.cjsx
@@ -2,6 +2,7 @@
 DrawingToolRoot = require './root'
 DragHandle = require './drag-handle'
 Draggable = require '../../lib/draggable'
+deleteIfOutOfBounds = require './delete-if-out-of-bounds'
 DeleteButton = require './delete-button'

 DEFAULT_WIDTH = 25
@@ -43,7 +44,7 @@
     {x, width} = @props.mark

     <DrawingToolRoot tool={this}>
-      <Draggable onDrag={@handleMainDrag} disabled={@props.disabled}>
+      <Draggable onDrag={@handleMainDrag} onEnd={deleteIfOutOfBounds.bind null, this} disabled={@props.disabled}>
         <rect ref="rect" x={x} y={0} width={width} height={@props.containerRect.height / @props.scale.vertical} />
       </Draggable>


Quiz icon in Gradebook doesn't break everything

When the quiz icon shows up and is clicked on to edit, it stalls the JS
on the page, making everything uneditable.  It also doesn't allow for
editing quizzes.

Closes CNVS-16332

Test-plan:
  - Confirm quiz icon edit is functional
  - Confirm other edit fields on the page are still functional

Change-Id: If2d9e5322472ba4bd3474371e5c27b726242ba12
Reviewed-on: https://gerrit.instructure.com/43127
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Derek DeVries <ff87a0776152510efd53bb1f4ea089dbbc90e730@instructure.com>
Reviewed-by: Brian Finney <6d3e3f1a68388efca72d0ac9c9ecbc1039d2a9c2@instructure.com>
QA-Review: Trevor deHaan <85df72112e8a9484fec7b3db2e31789c2c2d4e07@instructure.com>
Product-Review: Ryan Taylor <2eafa1f6bf34395b543d6ca7f438461b0774d24f@instructure.com>

--- app/coffeescripts/gradebook2/SubmissionCell.coffee
+++ app/coffeescripts/gradebook2/SubmissionCell.coffee
@@ -76,7 +76,7 @@
       specialClasses = SubmissionCell.classesBasedOnSubmission(opts.submission, opts.assignment)

       opts.classes += ' no_grade_yet ' unless opts.submission.grade && opts.submission.workflow_state != 'pending_review'
-      innerContents = null if opts.submission.workflow_state == 'pending_review'
+      innerContents = null if opts.submission.workflow_state == 'pending_review' && !isNaN(innerContents);
       innerContents ?= if submission_type then SubmissionCell.submissionIcon(submission_type) else '-'

       if turnitin = extractData(opts.submission)


add a finish to avoid the hear to trigger

--- scripts/phabs_templates.coffee
+++ scripts/phabs_templates.coffee
@@ -34,7 +34,8 @@
           msg.send body.error_info
         else
           msg.send "Ok. Template '#{name}' will use T#{taskid}."
-
+    msg.finish()
+
   #   hubot pht show <name>
   robot.respond (/pht show ([-_a-zA-Z0-9]+)$/), (msg) ->
     phab.withPermission msg, msg.envelope.user, 'phuser', ->
@@ -44,6 +45,7 @@
           msg.send body.error_info
         else
           msg.send "Template '#{name}' uses T#{body.task}."
+    msg.finish()

   #   hubot pht search <term>
   robot.respond (/pht search ([-_a-zA-Z0-9]+)$/), (msg) ->
@@ -55,6 +57,7 @@
         else
           for found in body
             msg.send "Template '#{found.name}' uses T#{found.task}."
+    msg.finish()

   #   hubot pht remove <name>
   robot.respond (/pht remove ([-_a-zA-Z0-9]+)$/), (msg) ->
@@ -65,6 +68,7 @@
           msg.send body.error_info
         else
           msg.send "Ok. Template '#{name}' was removed."
+    msg.finish()

   #   hubot pht update <name> T321
   robot.respond (/pht update ([-_a-zA-Z0-9]+) T([0-9]+)$/), (msg) ->
@@ -76,6 +80,7 @@
           msg.send body.error_info
         else
           msg.send "Ok. Template '#{name}' will now use T#{taskid}."
+    msg.finish()

   #   hubot pht rename <name> <newname>
   robot.respond (/pht rename ([-_a-zA-Z0-9]+) ([-_a-zA-Z0-9]+)$/), (msg) ->
@@ -87,3 +92,4 @@
           msg.send body.error_info
         else
           msg.send "Ok. Template '#{name}' will now bew known as '#{newname}'."
+    msg.finish()


Update pygmy_chameleon.coffee

Update pygmy_chameleon.coffee

--- app/lib/field-guide-content/pygmy_chameleon.coffee
+++ app/lib/field-guide-content/pygmy_chameleon.coffee
@@ -4,7 +4,7 @@
     Fully grown female Mount Gorongosa pygmy chameleons are about as long as a person’s pinky finger and the males are even smaller. They lack prehensile tails. They are able to alter their body color and pattern to camouflage themselves with their surroundings.
   '''

-  scientificName: '(Rhampholeon gorongosae)'
+  scientificName: 'Rhampholeon gorongosae'
   mainImage: 'assets/fieldguide-content/reptiles/pygmy_chameleon/pygymy-chameleon-feature.jpg'

   conservationStatus: 'Least Concern' # Options are Least Concern, Near Threatened, Vulnerable, and Endangered; all have their own little icons as well.


too soon

too soon

--- src/atom/project.coffee
+++ src/atom/project.coffee
@@ -68,12 +68,6 @@
   save: ->
     @activeResource?.save()

-  close: ->
-    if @activeResource
-      @activeResource.close()
-    else
-      super
-
   # Determines if a passed URL is a child of @url.
   # Returns a Boolean.
   childURL: (url) ->


Fix spec reference

Fix spec reference

--- core/spec/javascripts/factlink/string_hash_spec.coffee
+++ core/spec/javascripts/factlink/string_hash_spec.coffee
@@ -1,4 +1,4 @@
-#= require lib/string_hash
+#= require utils/string_hash

 describe 'window.string_hash', ->
   tests = [


Ensure browser console.logs appear in test output, to help debugging

Change-Type: patch

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -26,6 +26,11 @@
 		'tests/**/*.spec.coffee'
 	]

+	karmaConfig.browserConsoleLogOptions =
+		level: 'log'
+		format: '%b %T: %m'
+		terminal: true
+
 	karmaConfig.envPreprocessor = [
 		'RESINTEST_API_URL'
 		'RESINTEST_EMAIL'