Apps: Moved putAppResources to create method. With this change we are adding app resources on app physically created.

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -88,20 +88,10 @@
       # we assume it should be a 3rd party app
       # that's why it should be run via kodingappscontroller

-      # FIXME: SY
-      # There is a joint recursion here
-      # we call kodingappscontroller.runApp which calls
-      # appManager.open back, this method should be divided
-      # to some logical parts, and runApp should call the
-      # appropriate method rather than ::open.
       if not appOptions? and not options.avoidRecursion?
         return @fetchManifests name, =>
-          options.avoidRecursion = yes
-          @on "AppCreated", (appInstance)->
-            kodingAppsController.putAppResources appInstance
           @open name, options, callback
-          delete options.avoidRecursion
-
+
       appParams = options.params or {}

       if appOptions?.multiple
@@ -194,6 +184,7 @@
     @register appInstance = new AppClass appOptions  if AppClass
     @utils.defer =>
       @emit "AppCreated", appInstance
+      KD.getSingleton("kodingAppsController").putAppResources appInstance  if appOptions.thirdParty
       callback? appInstance

   show:(appOptions, callback)->


use contentModal for modal that appear when user role change

--- client/app/lib/notificationcontroller.coffee
+++ client/app/lib/notificationcontroller.coffee
@@ -7,6 +7,7 @@
 KDModalView        = kd.ModalView
 KDNotificationView = kd.NotificationView
 KDObject           = kd.Object
+ContentModal = require 'app/components/contentModal'

 remote_extensions  = require 'app/remote-extensions'

@@ -177,20 +178,22 @@
       # check if the notification is sent for current group
       return  unless group is getGroup().slug

-      modal = new KDModalView
-        title         : 'Your team role has been changed!'
-        overlay       : yes
-        width         : 500
-        content       :
-          """
-

+      modal = new ContentModal
+        title : 'Your team role has been changed!'
+        overlay : yes
+        width : 500
+        cssClass : 'content-modal'
+        content :
+          """
+

             @#{adminNick} made you #{articlize role} #{role}, please refresh your browser for changes to take effect.
-

-          """
-        buttons       :
-          'Reload page'        :
-            style     : 'solid green medium'
-            callback  : (event) ->
+

+          """
+        buttons :
+          'Reload page' :
+            title : 'Reload Page'
+            style : 'solid medium'
+            callback : (event) ->
               modal.destroy()
               global.location.reload yes


Sort field dropdowns on visualization dialog. PP-141

--- client/src/scripts/scoring-sheet-view.coffee
+++ client/src/scripts/scoring-sheet-view.coffee
@@ -580,15 +580,19 @@
   _metricTypeFilter = null
   _metricCriteriaFilter = null
   _allFilters = null
+
+  getSortedFloatVariables = (variables) ->
+    sortBy (filter variables, (variable) -> variable.type is 'float'), (variable) -> variable.caption
+
   _metricsVisualizationType =
     type: 'scoring'
     caption: 'Scoring'
-    variables: filter metricVariables, (variable) -> variable.type is 'float'
+    variables: getSortedFloatVariables metricVariables

   _thresholdVisualizationType =
     type: 'threshold'
     caption: 'Threshold'
-    variables: filter thresholdVariables, (variable) -> variable.type is 'float'
+    variables: getSortedFloatVariables thresholdVariables

   _visualizationTypes = [ _metricsVisualizationType, _thresholdVisualizationType ]


add .pros-modal selector to add-depot modal

--- lib/views/add-depot.coffee
+++ lib/views/add-depot.coffee
@@ -7,7 +7,7 @@
 module.exports =
   class AddDepotModal extends ScrollView
     @content: ->
-      @div class: 'pros-add-depot', tabindex: -1, =>
+      @div class: 'pros-modal pros-add-depot', tabindex: -1, =>
         @h1 'Add a new PROS Depot'
         @h4 'Choose a registrar:'
         @div class: 'registrar-picker select-list', =>


Update _tracklist.coffee

Update _tracklist.coffee

--- coffee/_tracklist.coffee
+++ coffee/_tracklist.coffee
@@ -158,7 +158,7 @@
     coverLargURL = options.coverLargURL

     return new gui.MenuItem(
-      label: 'Add to ' + playlistName,
+      label: l10n.get('context_menu_add_to_playlist') + ' ' + playlistName,
       click: ->
         Playlists.addTrack(
           artist,
@@ -180,7 +180,7 @@
     title = options.title

     return new gui.MenuItem(
-      label: 'Remove from ' + playlistName,
+      label: l10n.get('context_menu_remove_from_plalist') + playlistName,
       click: ->
         Playlists.removeTrack(
           artist,
@@ -200,7 +200,7 @@
     title = options.title

     return new gui.MenuItem(
-      label: 'Remove from History',
+      label: l10n.get('context_menu_remove_from_history'),
       click: ->
         History.removeTrack(
           artist,


Player: trigger show:splash on init

Player: trigger show:splash on init

--- coffee/src/player.coffee
+++ coffee/src/player.coffee
@@ -16,10 +16,13 @@
   initialize: =>
     @acornModel = @options.acornModel # TODO initialize from id or data
     @shellModel = acorn.shellWithAcorn @acornModel
+    @eventhub = @

     @view = new acorn.player.PlayerView
       model:
         shellid: 'acorn.Shell',
         acornModel: @acornModel,
         shellModel: @shellModel
-      eventhub: @
+      eventhub: @eventhub
+
+    @eventhub.trigger 'show:splash'


Code clean up

Code clean up

--- coffeescript/site.coffee
+++ coffeescript/site.coffee
@@ -5,21 +5,9 @@
 angularApplication = angular.module('crossFilterApp', ['ngRoute','ngAnimate'])

 angularApplication.factory('DataService' , ($q) ->
-	generateData = ->
-		crossfilter([
-			{ name: 'Rusty',  type: 'human', legs: 2 },
-			{ name: 'Alex',   type: 'human', legs: 2 },
-			{ name: 'Lassie', type: 'dog',   legs: 4 },
-			{ name: 'Spot',   type: 'dog',   legs: 4 },
-			{ name: 'Polly',  type: 'bird',  legs: 2 },
-			{ name: 'Fiona',  type: 'plant', legs: 0 }
-		])
-
 	generateGffData = () ->
 		deferred = $q.defer()
 		sourceFile = 'data/genome.gff'
-		barChart = dc.barChart('#dc-bar-chart')
-		pieChart = dc.pieChart('#dc-pie-chart')

 		d3.gff(sourceFile, (rows) =>
 			rows.forEach( (row) ->
@@ -51,6 +39,7 @@
 			seqNamesDomain = _.map(seqNamesGroup.reduceCount().top(25), (seqNameCount)->
 				seqNameCount.key
 			)
+			barChart = dc.barChart('#dc-bar-chart')
 			barChart
 				.width(700)
 				.height(200)
@@ -63,6 +52,7 @@
 #				.x(d3.scale.ordinal().domain(seqNamesDomain))
 				.x(d3.scale.linear().domain(startDomain))
 				.elasticY(true)
+			pieChart = dc.pieChart('#dc-pie-chart')
 			pieChart
 				.width(200)
 				.height(200)
@@ -75,7 +65,6 @@
 		deferred.promise

 	return {
-		testData: generateData()
 		generateGffData: generateGffData
 		resetAllFilters: ->
 			dc.filterAll()
@@ -84,7 +73,6 @@
 )

 angularApplication.controller('HomeController' , ($scope, $location, DataService) ->
-	$scope.result = DataService.testData.groupAll().reduceCount().value()
 	DataService.generateGffData().then((gffData) ->
 		$scope.gffData = gffData
 		$scope.gffResults = gffData.groupAll().reduceCount().value()


fix similar issue with feed

fix similar issue with feed

--- commands/use/food.coffee
+++ commands/use/food.coffee
@@ -13,6 +13,28 @@
   else
     increase

+alter_target_hpmax_and_hunger = (character, amount_hpmax, amount_hunger, cb) ->
+  async.parallel [
+    (cb) ->
+      query =
+        _id: character._id
+      update =
+        $set:
+          hp_max: character.hp_max + amount_hpmax
+          hunger: character.hunger + amount_hunger
+      db.characters.update query, update, cb
+    (cb) ->
+      query =
+        x: character.x
+        y: character.y
+        z: character.z
+        'people._id': character._id
+      update =
+        $set:
+          'people.$.hp_max': character.hp_max + amount_hpmax
+      db.tiles.update query, update, cb
+  ], cb
+
 module.exports = (user, target, item, tile, cb) ->
   return cb('Your target is already full.') unless target.hunger < MAX_HUNGER

@@ -23,13 +45,7 @@
     (cb) ->
       remove_item user, item, 1, cb
     (cb) ->
-      query =
-        _id: target._id
-      update =
-        $inc:
-          hunger: hunger_gain
-          hp_max: hpmax_gain
-      db.characters.update query, update, cb
+      alter_target_hpmax_and_hunger character, hpmax_gain, hunger_gain, cb
     (cb) ->
       send_message 'feed', user, user,
         item: item.id


Add edit tracking

Add edit tracking

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -51,6 +51,7 @@
     ).then ->
       if file == 'javascript'
         run()
+        startEditTracking()

       if _.include(['gemfile', 'routes', 'controller'], file)
         editor.setReadOnly(true)
@@ -82,6 +83,17 @@
     $(document).on 'click', 'button', (button) ->
       analytics.track 'Clicked button',
         text: $(button.target).text()
+        js_code: editors().javascript.getValue()
+
+  startEditTracking = ->
+    editors().javascript.getSession().on 'change', _.debounce(logEdit, 5000,
+      leading: false
+      trailing: true
+    )
+
+  logEdit = (e) ->
+    analytics.track 'Edited Javascript',
+      js_code: editors().javascript.getValue()

   return {
     init: init


emberjs specific javascipt is not loaded on rails views

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -2,7 +2,6 @@
 #= require jquery_ujs
 #= require turbolinks
 #= require bootstrap
-#= require_tree .

 #turbolinks spinner
 $(document).on 'page:fetch', ->


Handle thumbsup and thumbsdown mutuality.

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -24,6 +24,7 @@
   handleClick: (e) =>

     e.preventDefault()
+
     $emojiBtn = $(e.currentTarget)
     $addAwardBtn = $('.js-add-award.is-active')
     $votesBlock = $($addAwardBtn.closest('.js-award-holder').data('target'))
@@ -36,6 +37,13 @@
     $votesBlock.addClass 'js-awards-block'
     awardUrl = $votesBlock.data 'award-url'
     emoji = $emojiBtn.find('.icon').data('emoji')
+
+    if emoji in [ 'thumbsup', 'thumbsdown' ]
+      mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
+
+      isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
+      @addAward awardUrl, mutualVote if isAlreadyVoted
+
     @addAward awardUrl, emoji


Correctly scrolls the dropdown up & down with arrow keys

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -436,15 +436,22 @@

     # Dropdown content scroll area
     $dropdownContent = $listItem.closest('.dropdown-content')
-    dropdownContentBottom = $dropdownContent.prop('offsetTop') + $dropdownContent.prop('offsetHeight')
+    dropdownScrollTop = $dropdownContent.prop('scrollTop')
+    dropdownContentHeight = $dropdownContent.prop('offsetHeight')
+    dropdownContentTop = $dropdownContent.prop('offsetTop')
+    dropdownContentBottom = dropdownContentTop + dropdownContentHeight

     # Get the offset bottom of the list item
-    listItemBottom = $listItem.prop('offsetTop') + $listItem.prop('offsetHeight')
-    console.log listItemBottom, dropdownContentBottom
-
-    if listItemBottom > dropdownContentBottom
+    listItemHeight = $listItem.prop('offsetHeight')
+    listItemTop = $listItem.prop('offsetTop')
+    listItemBottom = listItemTop + listItemHeight
+
+    if listItemBottom > dropdownContentBottom + dropdownScrollTop
       # Scroll the dropdown content down
       $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
+    else if listItemTop < dropdownContentTop + dropdownScrollTop
+      # Scroll the dropdown content up
+      $dropdownContent.scrollTop(listItemTop - dropdownContentTop)

 $.fn.glDropdown = (opts) ->
   return @.each ->


Fixes [#71161400] – "Check Answer" Button no longer works.

This was Javascript NameSpace collision.  See AnswerChecker.js (!)

https://www.pivotaltracker.com/story/show/71161400

--- app/assets/javascripts/save-on-change.coffee
+++ app/assets/javascripts/save-on-change.coffee
@@ -2,7 +2,7 @@
   Array::filter = (callback) ->
     element for element in this when callback(element)

-class @AnswerChecker
+class @CompleteChecker
   @FieldNames = [
     "embeddable_open_response_answer[answer_text]"
     "embeddable_multiple_choice_answer[answers]"
@@ -18,7 +18,7 @@
   is_answered: () ->
     @data = @$form.serializeArray()
     @data = @data.filter (obj) ->
-      obj.name in AnswerChecker.FieldNames
+      obj.name in CompleteChecker.FieldNames
     @data = @data.filter (obj) ->
       !!obj.value

@@ -37,7 +37,7 @@
     # don't send events until the doc
     # is ready
     $(document).ready () =>
-      @checker = new AnswerChecker(@$form)
+      @checker = new CompleteChecker(@$form)
       @check_for_answer()


Remove 'jQuery' from settlements.js.coffee.

--- app/assets/javascripts/settlements.js.coffee
+++ app/assets/javascripts/settlements.js.coffee
@@ -37,8 +37,7 @@
       result = ',' + result
   result

-jQuery(document).ready ($)->
-
+$ ->
   refreshTargets = ->
     $('#target_deals').load($('#target_deals_form').data('url'), $('#target_deals_form').serialize())


Fix #2019 Sprint creation name reminder not working on firefox

--- app/coffee/modules/backlog/lightboxes.coffee
+++ app/coffee/modules/backlog/lightboxes.coffee
@@ -133,6 +133,7 @@
             $el.find(".button-green").text("Create") #TODO i18n
             lightboxService.open($el)
             $el.find(".sprint-name").focus()
+            $el.find(".last-sprint-name").removeClass("disappear")

         $scope.$on "sprintform:edit", (ctx, sprint) ->
             createSprint = false


Add Enter and ESC handling and focus on edit

--- app/coffee/modules/common/attachments.coffee
+++ app/coffee/modules/common/attachments.coffee
@@ -334,16 +334,24 @@
             if attachment.is_deprecated
                 $el.addClass("deprecated")

+        saveAttachment = ->
+            attachment.description = $el.find("input[name='description']").val()
+            attachment.is_deprecated = $el.find("input[name='is-deprecated']").prop("checked")
+
+            $scope.$apply ->
+                $ctrl.updateAttachment(attachment).then ->
+                    render(attachment, false)
+
         ## Actions (on edit mode)
         $el.on "click", "a.editable-settings.icon-floppy", (event) ->
             event.preventDefault()
-
-            attachment.description = $el.find("input[name='description']").val()
-            attachment.is_deprecated = $el.find("input[name='is-deprecated']").prop("checked")
-
-            $scope.$apply ->
-                $ctrl.updateAttachment(attachment).then ->
-                    render(attachment)
+            saveAttachment()
+
+        $el.on "keyup", "input[name=description]", (event) ->
+            if event.keyCode == 13
+                saveAttachment()
+            else if event.keyCode == 27
+                render(attachment, false)

         $el.on "click", "a.editable-settings.icon-delete", (event) ->
             event.preventDefault()
@@ -353,6 +361,7 @@
         $el.on "click", "a.settings.icon-edit", (event) ->
             event.preventDefault()
             render(attachment, true)
+            $el.find("input[name='description']").focus().select()

         $el.on "click", "a.settings.icon-delete", (event) ->
             event.preventDefault()
@@ -365,6 +374,8 @@
         # Bootstrap
         attachment = $scope.$eval($attrs.tgAttachment)
         render(attachment, attachment.isCreatedRightNow)
+        if attachment.isCreatedRightNow
+            $el.find("input[name='description']").focus().select()

     return {
         link: link


fix issue 4251 - autoscroller in taskboard

--- app/coffee/modules/taskboard/sortable.coffee
+++ app/coffee/modules/taskboard/sortable.coffee
@@ -87,7 +87,7 @@
                 $scope.$apply ->
                     $rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex)

-            scroll = autoScroll(containers, {
+            scroll = autoScroll([$('.taskboard-table-body')[0]], {
                 margin: 20,
                 pixels: 30,
                 scrollWhenOutside: true,


Catch server errors in edit US form

Catch server errors in edit US form

--- app/coffee/modules/userstories/detail.coffee
+++ app/coffee/modules/userstories/detail.coffee
@@ -151,14 +151,25 @@
             if not form.validate()
                 return

-            $tgrepo.save($scope.us).then ->
+            onSuccess = ->
                 $confirm.notify("success")
                 $location.path("/project/#{$scope.project.slug}/us/#{$scope.us.ref}")

+            onError = ->
+                $confirm.notify("error")
+
+            $tgrepo.save($scope.us).then(onSuccess, onError)
+
         $el.on "click", ".add-comment a.button-green", (event) ->
             event.preventDefault()
-            $tgrepo.save($scope.us).then ->
+
+            onSuccess = ->
                 $ctrl.loadHistory()
+
+            onError = ->
+                $confirm.notify("error")
+
+            $tgrepo.save($scope.us).then(onSuccess, onError)

         $el.on "click", ".us-activity-tabs li a", (event) ->
             $el.find(".us-activity-tabs li a").toggleClass("active")


refactor load-stuff

refactor load-stuff

--- tasks/load-stuff.coffee
+++ tasks/load-stuff.coffee
@@ -1,29 +1,30 @@
 fs = require('fs')
+
+linemanNpmTasks = [
+  "grunt-contrib-clean"
+  "grunt-contrib-coffee"
+  "grunt-contrib-concat"
+  "grunt-contrib-copy"
+  "grunt-contrib-handlebars"
+  "grunt-contrib-jshint"
+  "grunt-contrib-jst"
+  "grunt-contrib-less"
+  "grunt-contrib-cssmin"
+  "grunt-contrib-uglify"
+  "grunt-watch-nospawn"
+]

 module.exports = (grunt) ->
   config = require("#{process.cwd()}/config/application")
-  linemanNpmTasks = [
-    "grunt-contrib-clean"
-    "grunt-contrib-coffee"
-    "grunt-contrib-concat"
-    "grunt-contrib-copy"
-    "grunt-contrib-handlebars"
-    "grunt-contrib-jshint"
-    "grunt-contrib-jst"
-    "grunt-contrib-less"
-    "grunt-contrib-cssmin"
-    "grunt-contrib-uglify"
-    "grunt-watch-nospawn"
-  ]

-  grunt.util._(linemanNpmTasks).
-    chain().
-    union(if config.enableSass then "grunt-contrib-sass" else []).
-    union(config.loadNpmTasks || []).
-    each((module) ->
-      if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
-        grunt.loadNpmTasks(module)
-      else
-        grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")).
-    tap ->
-      grunt.task.renameTask("copy", "images")
+  loadTask = (module) ->
+    if fs.existsSync("#{process.cwd()}/node_modules/#{module}")
+      grunt.loadNpmTasks(module)
+    else
+      grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks")
+
+  loadTask module for module in grunt.util._.chain(linemanNpmTasks).
+    union("grunt-contrib-sass" if config.enableSass).
+    union(config.loadNpmTasks).compact().value()
+
+  grunt.task.renameTask("copy", "images")


Disable file upload controls when uploading.

--- app/assets/javascripts/file_assets.js.coffee
+++ app/assets/javascripts/file_assets.js.coffee
@@ -36,6 +36,14 @@

   xhr.open(form.getAttribute('method'), form.getAttribute('action'));
   xhr.send(fd);
+
+  submitButton = $('#submit')
+  inputElm = $('#file_asset_resource')
+  inputBtn = $('.fileinput-button')
+
+  submitButton.attr("disabled", "disabled")
+  inputElm.attr("disabled", "disabled")
+  inputBtn.attr("disabled", "disabled")

 uploadComplete = (event) =>
   document.getElementById('upload_progress').style.display = 'none';


allow cart plugin to handle 'add to cart' forms loaded after page ready

--- app/assets/javascripts/glysellin/cart.coffee
+++ app/assets/javascripts/glysellin/cart.coffee
@@ -4,24 +4,23 @@

   constructor: (@container, options) ->
     @options = $.extend({}, defaults, options)
-    @add_to_cart_forms = $('[data-add-to-cart-form]')
     @bindAll()

   bindAll: ->
-    @add_to_cart_forms.on 'ajax:success', (e, resp) =>
+    $('body').on 'ajax:success', '[data-add-to-cart-form]', (e, resp) =>
       @update(resp)
       @showError false

-    @add_to_cart_forms.on 'ajax:error', (e, resp) =>
+    $('body').on 'ajax:error', '[data-add-to-cart-form]', (e, resp) =>
       if resp.responseJSON.error is 'choose_variant'
         @showError true

   showError: (state) =>
     if state
-      @add_to_cart_forms.find('.submit-container').addClass('has-error')
+      $('[data-add-to-cart-form]').find('.submit-container').addClass('has-error')
       $('[data-choose-variant-text]').removeClass('hidden')
     else
-      @add_to_cart_forms.find('.submit-container').removeClass('has-error')
+      $('[data-add-to-cart-form]').find('.submit-container').removeClass('has-error')
       $('[data-choose-variant-text]').addClass('hidden')

   update: (markup) ->


cleaned website.coffee

cleaned website.coffee

--- app/assets/javascripts/pages/websites.coffee
+++ app/assets/javascripts/pages/websites.coffee
@@ -13,32 +13,29 @@
   console.log gon.action
   switch gon.action
     when "new"
-      addNewWebsite('#formWebsite', 'new')
+      $('#formWebsite').submit (e) ->
+        e.preventDefault()
+        form = $('#formWebsite')
+        $.ajax
+          url: Routes.api_v1_websites_url()
+          type: 'post'
+          dataType: 'json'
+          data: { website: { domain: form.find('#domain').val(), title: form.find('#title').val() } }
+          success: (data) ->
+            EpicLogger.setMemberDetails(data.id)
+            swal("Success", "Website added!", "success")
+            setTimeout (->
+              location.href = '/installations'
+              return
+            ), 2000
+          error: (error) ->
+            sweetAlert("Error", "Website exists!", "error") if error.status == 401
+        return
+      return
     when "index"
       $.getJSON Routes.api_v1_websites_url(), {member_id: $.auth.user.id}, (data) ->
         $('#websites-container').render data, directive
         console.log 'data loaded'

 )
-addNewWebsite = (form, action) ->
-  $(form).submit (e) ->
-    e.preventDefault()
-    $.ajax
-      url: Routes.api_v1_websites_url()
-      type: 'post'
-      dataType: 'json'
-      data: { website: { domain: $(form).find('#domain').val(), title: $(form).find('#title').val() } }
-      success: (data) ->
-        EpicLogger.setMemberDetails(data.id)
-        if action == 'index'
-          manipulateWizard(2)
-          $('.tab1').addClass('disabled')
-          $('.tab2').removeClass('disabled')
-        else
-          alert 'Website added!'
-          location.href = '/installations'
-      error: (error) ->
-        alert "Website exists!" if error.status == 401
-    return
-  return


fix ie9 media viewing

fix ie9 media viewing

There were two problems:
  * IE9 cannot do $("<source type='blah'>").attr('type') for some
    reason, so now we're collecting the types separately.
  * IE9 has trouble with canPlayType(), so the HTML5 detection stuff
    in mejs was failing. This forces IE9 to just use the flash player.

Test plan:
 * Test that video and audio still works in all browsers.

Change-Id: Ia9f35e211f601424b244220edebf5d11a8c0bed2
Reviewed-on: https://gerrit.instructure.com/18034
QA-Review: Bryan Madsen <0a3ba1af216659500dd8f41e29f3979f42452870@instructure.com>
Reviewed-by: Bracken Mosbacker <48b846001bca4166dc3fb7711563afc17525921e@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/jquery/mediaComment.coffee
+++ app/coffeescripts/jquery/mediaComment.coffee
@@ -6,6 +6,13 @@
   'vendor/mediaelement-and-player'
   'jquery'
 ], (I18n, _, pubsub, mejs, $) ->
+
+  isIE9 = () -> $("body").hasClass("ie9")
+  isMobileDevice = () ->
+    agent = navigator.userAgent.toLowerCase()
+    agent.match(/ip(hone|od|ad)/i) or agent.match(/android/i)
+
+  browserSupportsVideoInAudioTag = () -> isMobileDevice()

   VIDEO_WIDTH = 550
   VIDEO_HEIGHT = 448
@@ -22,16 +29,12 @@
   $.extend mejs.MepDefaults,
     # prefer flash player, as it works more consistently
     # for now, but allow fallback to html5 (like on mobile)
-    mode: 'auto_plugin'
+    # Force IE9 to always use the plugin, because it has
+    # problems with the canPlayType call.
+    mode: if isIE9() then 'shim' else 'auto_plugin'
     success: (mediaElement, domObject) ->
       if(mediaElement.pluginType == 'flash')
         mediaElement.play()
-
-  isMobileDevice = () ->
-    agent = navigator.userAgent.toLowerCase()
-    agent.match(/ip(hone|od|ad)/i) or agent.match(/android/i)
-
-  browserSupportsVideoInAudioTag = () -> isMobileDevice()

   # track events in google analytics
   mejs.MepDefaults.features.push('googleanalytics')
@@ -43,7 +46,8 @@
       tracks = _.map data.media_tracks, (track) ->
           languageName = mejs.language.codes[track.locale] || track.locale
           "<track kind='#{track.kind}' label='#{languageName}' src='#{track.url}' srclang='#{track.locale}' />"
-      dfd.resolve {sources, tracks, can_add_captions: data.can_add_captions}
+      types = _.map data.media_sources, (source) -> source.content_type
+      dfd.resolve {sources, tracks, types, can_add_captions: data.can_add_captions}
     dfd

   # After clicking an image to play the video, load the sources and tracks
@@ -55,12 +59,11 @@
     {sourcesAndTracks, mediaType, height, width} = options
     tag_type = if mediaType is 'video' then 'video' else 'audio'

-    sourceTypes = _.map sourcesAndTracks.sources, (source) -> $(source).attr('type')
     # A lot of our recorded audio is actually served up via video/mp4 or video/flv.
     # We need to trick the flash player into playing the video, but looking like
     # an audio player. (Not necessary on iOS/Android - they seem fine playing
     # an mp4 inside an audio tag.)
-    if mediaType is 'audio' and sourceTypes[0].match(/^video\//) and !browserSupportsVideoInAudioTag()
+    if mediaType is 'audio' and sourcesAndTracks.types[0].match(/^video\//) and !browserSupportsVideoInAudioTag()
       tag_type = 'video'
       options.mediaPlayerOptions.isVideo = false
       options.mediaPlayerOptions.videoHeight = 30


require envify/custom so we can provide options to it

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -1,5 +1,7 @@
 Browserify = Npm.require 'browserify'
-envify = Npm.require 'envify'
+
+# use custom envify so we can specify the env based on the meteor command used
+envify = Npm.require 'envify/custom'

 # get 'stream' to use PassThrough to provide a Buffer as a Readable stream
 stream     = Npm.require 'stream'


remove deprecated comment

remove deprecated comment

--- src/components/widgets-common/chart-threshold/chart-threshold.component.coffee
+++ src/components/widgets-common/chart-threshold/chart-threshold.component.coffee
@@ -159,8 +159,6 @@
     # Disable threshold when selected time period is strictly in the past
     validateHistParameters = ->
       widgetHistParams = ctrl.widget.metadata && ctrl.widget.metadata.hist_parameters
-      # Widget histParams are YTD by default (when undefined on metadata),
-      # therefore in the past by default
       ctrl.disabled = widgetHistParams? && moment(widgetHistParams.to) <= moment.utc().startOf('day')
       return


:bug: Fix broken quick settings command

:bug: Fix broken quick settings command

Fixes #186

--- lib/minimap.coffee
+++ lib/minimap.coffee
@@ -99,9 +99,9 @@

     if atom.config.get('minimap.displayPluginsControls')
       @subscriptions.add atom.commands.add 'atom-workspace',
-        'minimap:open-quick-settings': ->
+        'minimap:open-quick-settings': =>
           editor = atom.workspace.getActiveEditor()
-          @minimapForEditor(editor).openQuickSettings.mousedown()
+          @minimapForEditor(editor)?.openQuickSettings.mousedown() if editor?

     @subscriptions.add atom.config.observe 'minimap.displayMinimapOnLeft', (value) ->
       workspaceElement.classList.toggle 'minimap-on-left', value


Log uid err

Log uid err

--- server/code/model/box.coffee
+++ server/code/model/box.coffee
@@ -89,6 +89,7 @@
     unless @uid?
       @uid = Box.generateUid()
     super (err) =>
+      console.log "err: ", @uid, err?.code, err
       if err? and err.code is 11000
         if @duplicateErrorCount <3
           @uid = Box.generateUid()


Add spacing.

Add spacing.

--- test/config-test.coffee
+++ test/config-test.coffee
@@ -131,4 +131,5 @@

           'returns false': (config) ->
             assert.equal config, false
+
   .export(module)


Core#send

Core#send

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -76,6 +76,6 @@
 test "#send", ->
   o = Core()

-  o.send("attrReader")
+  o.send("attrReader", "test")

 module undefined


Groups: group creation default type made project

--- client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -45,19 +45,20 @@
   viewAppended:->

     @addSubView @typeSelector = new KDFormViewWithFields
-      cssClass      : "type-selector"
-      fields        :
-        label       :
-          itemClass : KDCustomHTMLView
-          tagName   : 'h2'
-          cssClass  : 'heading'
-          partial   : "1 What will be this group for?"
-        selector    :
-          name      : "type"
-          itemClass : GroupCreationSelector
-          cssClass  : "group-type"
-          radios    : GROUP_TYPES
-          change    : =>
+      cssClass         : "type-selector"
+      fields           :
+        label          :
+          itemClass    : KDCustomHTMLView
+          tagName      : 'h2'
+          cssClass     : 'heading'
+          partial      : "1 What will be this group for?"
+        selector       :
+          name         : "type"
+          itemClass    : GroupCreationSelector
+          cssClass     : "group-type"
+          defaultValue : "project"
+          radios       : GROUP_TYPES
+          change       : =>
             @ready =>
               typeSelector = @typeSelector.inputs.selector
               # hostSelector = @hostSelector.inputs.selector


took away fails on plugins directory being missing

--- lib/plugins.coffee
+++ lib/plugins.coffee
@@ -4,12 +4,13 @@

 get_with_base = (base, filename, moduleName) ->
   plugins = []
-  for file in fs.readdirSync base
-    try
-      pluginModule = require path.join(base, file, filename)
-      plugins.push pluginModule[moduleName] if pluginModule[moduleName]?
-    catch e
-      # console.error e.stack
+  if path.existsSync base
+    for file in fs.readdirSync base
+      try
+        pluginModule = require path.join(base, file, filename)
+        plugins.push pluginModule[moduleName] if pluginModule[moduleName]?
+      catch e
+        # console.error e.stack
   plugins

 exports.get = (filename, moduleName) ->


Added missing test case to tests

Added missing test case to tests

--- test/projections.coffee
+++ test/projections.coffee
@@ -107,6 +107,7 @@
       it 'allows editing in-property fields', ->
         @op {}, [p:['x', 'y'], na:1], []
         @op {x:true}, [p:['x', 'y'], na:1]
+        @op {x:true}, [p:['x'], na:1]
         @op {y:true}, [p:['x', 'y'], na:1], []

     describe 'create', ->


fix: ignore more whitespace in resource URLs

--- lib/routing.coffee
+++ lib/routing.coffee
@@ -8,7 +8,7 @@
 literal = {}
 templated = []  # order matters here
 resource = (paths, description, responses_obj) ->
-    for path in paths.split ' '
+    for path in paths.split /\s+/
         path = mkPath path
         if (path.indexOf '{') == -1  # it is a literal URL
             literal[path] = { description, responses_obj }


Add spec for uninstalling a package

Add spec for uninstalling a package

--- spec/app/package-config-panel-spec.coffee
+++ spec/app/package-config-panel-spec.coffee
@@ -22,9 +22,15 @@
         version: '5.8.5'
       }
     ]
+
     spyOn(packageManager, 'getAvailable').andCallFake (callback) ->
       callback(null, packages)
+    spyOn(packageManager, 'uninstall').andCallFake (pack, callback) ->
+      callback()
+
     spyOn(atom, 'getAvailablePackageMetadata').andReturn(packages)
+    spyOn(atom, 'resolvePackagePath').andCallFake (name) ->
+      "/tmp/atom-packages/#{name}"

     configObserver = jasmine.createSpy("configObserver")
     observeSubscription = config.observe('core.disabledPackages', configObserver)
@@ -75,6 +81,14 @@
         p3View.enableToggle.find('a').click()
         expect(configObserver).toHaveBeenCalledWith(['p1'])

+    describe "when Uninstall is clicked", ->
+      it "removes the package from the tab", ->
+        expect(panel.installed.find("[name='p1']")).toExist()
+        p1View = panel.installed.find("[name='p1']").view()
+        expect(p1View.defaultAction.text()).toBe 'Uninstall'
+        p1View.defaultAction.click()
+        expect(panel.installed.find("[name='p1']")).not.toExist()
+
   describe 'Available tab', ->
     it 'lists all available packages', ->
       panel.availableLink.click()


'you call me' functionality

'you call me' functionality

--- lib/client/webrtc_signaller.coffee
+++ lib/client/webrtc_signaller.coffee
@@ -53,6 +53,9 @@
   createOffer: ->
     @_createLocalStream(@_createOffer)

+  requestCall: ->
+    @_sendMessage(callMe: true)
+
   sendData: (data) ->
     throw 'No data channel created' unless @_dataChannel?
     @_dataChannel.send(data)
@@ -60,7 +63,8 @@
   stop: ->
     if @_dataChannel?
       @_dataChannel.close()
-    @_rtcPeerConnection.close()
+    if @_rtcPeerConnection?
+      @_rtcPeerConnection.close()
     @_rtcPeerConnection = null
     @_started = false
     @_startedDep.changed()
@@ -70,7 +74,11 @@
     WebRTCSignallingStream.emit(@_channelName, message)

   _handleMessage: (message) =>
-    if message.sdp?
+    if message.callMe
+      @stop()
+      @start()
+      @createOffer()
+    else if message.sdp?
       @_handleSDP(JSON.parse(message.sdp))
     else if message.candidate?
       @_handleIceCandidate(JSON.parse(message.candidate))


Add licence and usage guide

Add licence and usage guide

--- resources/assets/coffee/sticky-header.coffee
+++ resources/assets/coffee/sticky-header.coffee
@@ -1,3 +1,29 @@
+###
+# Copyright 2015 ppy Pty. Ltd.
+#
+# This file is part of osu!web. osu!web is distributed with the hope of
+# attracting more community contributions to the core ecosystem of osu!.
+#
+# osu!web is free software: you can redistribute it and/or modify
+# it under the terms of the Affero GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
+###
+
+# How to use:
+# 1. create a marker on when it should be fixed, with class including
+#    'js-sticky-header' and data attribute 'data-sticky-header-target'
+# 2. subscribe to 'stickyHeader' event
+# 3. in the function, check if second parameter (first one is unused event
+#    object) is the correct target
+# 4. stick if matches, unstick otherwise
 class @StickyHeader
   stickMarker: document.getElementsByClassName('js-sticky-header')


refactored event order firing, this corrects a potential case where a dom click event could sneak in after the fact

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -1,7 +1,7 @@
 pageCache    = []
 currentState = null
 initialized  = false
-clickstate = false
+
 visit = (url) ->
   if browserSupportsPushState
     cacheCurrentPage()
@@ -114,22 +114,25 @@
   target.dispatchEvent event

 handleClick = (event) ->
-  unless clickstate or event.defaultPrevented
-    document.addEventListener 'click', handleLinkClick
-    clickstate = true
-    stopEverything(event)
-    refireClickEvent(event.target)
-
-handleLinkClick = (event) ->
   unless event.defaultPrevented
     link = extractLink event
     if link.nodeName is 'A' and !ignoreClick(event, link)
+      document.removeEventListener 'click', handleAfterClick
+      document.removeEventListener 'click', handleClick, true
+      document.addEventListener 'click', handleBeforeClick,true
+      document.addEventListener 'click', handleAfterClick
+      stopEverything(event)
+      refireClickEvent(event.target)
+
+handleAfterClick = (event) ->
+  unless event.defaultPrevented
+      link = extractLink event
       visit link.href
       stopEverything(event)

-resetClickEvent = (event) ->
-  document.removeEventListener 'click', handleLinkClick
-  clickstate = false
+handleBeforeClick = (event) ->
+  document.removeEventListener 'click', handleBeforeClick, true
+  document.addEventListener 'click', handleClick, true

 extractLink = (event) ->
   link = event.target
@@ -167,7 +170,7 @@
   window.addEventListener 'popstate', (event) ->
     fetchHistory event.state if event.state?.turbolinks

-  document.addEventListener 'click', handleClick
-  document.addEventListener 'page:change', resetClickEvent
+  document.addEventListener 'click', handleClick,true
+
 # Call Turbolinks.visit(url) from client code
 @Turbolinks = visit: visit


:lipstick:

:lipstick:

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -25,7 +25,10 @@
       while pos.column >= 0
         if line[pos.column] == @char
           if pos.column == 0 or line[pos.column - 1] != '\\'
-            return if @isStartQuote(pos) then pos else @lookForwardOnLine(start)
+            if @isStartQuote(pos)
+              return pos
+            else
+              return @lookForwardOnLine(start)
         -- pos.column
       pos.column = -1
       -- pos.row
@@ -33,16 +36,15 @@

   isStartQuote: (end) ->
     line = @editor.lineForBufferRow(end.row)
-    numQuotes = line.substring(0,end.column + 1).replace('\''+@char,'').split(@char).length-1
-    return numQuotes % 2
-
+    numQuotes = line.substring(0, end.column + 1).replace( "'#{@char}", '').split(@char).length - 1
+    numQuotes % 2

   lookForwardOnLine: (pos) ->
     line = @editor.lineForBufferRow(pos.row)

-    idx = line.substring(pos.column).indexOf @char
-    if idx >= 0
-      pos.column += idx
+    index = line.substring(pos.column).indexOf(@char)
+    if index >= 0
+      pos.column += index
       return pos
     null


Changes to simplify the select method.

Changes to simplify the select method.

See #229.

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -25,12 +25,7 @@
       pos.column = -1
       -- pos.row

-  select: ->
-    start = @findOpeningQuote(@editor.getCursorBufferPosition())
-    return [false] unless start?
-
-    ++ start.column  # skip the opening quote
-
+  findClosingQuote: (start) ->
     end = start.copy()
     escaping = false

@@ -43,12 +38,21 @@
           @editor.expandSelectionsForward (selection) =>
             selection.cursor.setBufferPosition start
             selection.selectToBufferPosition end
-          return [true]
+          return {select:[true], end:end}
         ++ end.column
       end.column = 0
       ++ end.row

-    [false]
+    {select:[false], end:end}
+
+  select: ->
+    start = @findOpeningQuote(@editor.getCursorBufferPosition())
+    return [false] unless start?
+
+    ++ start.column  # skip the opening quote
+
+    {select,end} = @findClosingQuote(start)
+    select

 class SelectInsideBrackets extends TextObject
   constructor: (@editor, @beginChar, @endChar) ->
@@ -68,15 +72,9 @@
       pos.column = -1
       -- pos.row

-  select: ->
-    start = @findOpeningBracket(@editor.getCursorBufferPosition())
-    return [false] unless start?
-
-    ++ start.column  # skip the opening bracket
-
+  findClosingBracket: (start) ->
     end = start.copy()
     depth = 0
-
     while end.row < @editor.getLineCount()
       endLine = @editor.lineForBufferRow(end.row)
       while end.column < endLine.length
@@ -87,11 +85,18 @@
               @editor.expandSelectionsForward (selection) =>
                 selection.cursor.setBufferPosition start
                 selection.selectToBufferPosition end
-              return [true]
+              return {select:[true], end:end}
         ++ end.column
       end.column = 0
       ++ end.row

-    [false]
+    {select:[false], end:end}
+
+  select: ->
+    start = @findOpeningBracket(@editor.getCursorBufferPosition())
+    return [false] unless start?
+    ++ start.column  # skip the opening bracket
+    {select,end} = @findClosingBracket(start)
+    select

 module.exports = {TextObject, SelectInsideWord, SelectInsideQuotes, SelectInsideBrackets}


Add test for no disclosure arrows

Add test for no disclosure arrows

--- lib/ui-demo-view.coffee
+++ lib/ui-demo-view.coffee
@@ -325,7 +325,36 @@

       @exampleSection 'list-tree', 'List Tree', ->
         @p => @raw 'A .list-tree is a special case of .list-group.'
-        @p => @raw '.list-tree.has-collapsable-children gives the children with nested items disclosure arrows.'
+        @exampleCode '''
+          @ul class: 'list-tree', =>
+            @li class: 'list-nested-item', =>
+              @div class: 'list-item', =>
+                @span class: 'icon icon-file-directory', 'A Directory'
+              @ul class: 'list-tree', =>
+                @li class: 'list-nested-item', =>
+                  @div class: 'list-item', =>
+                    @span class: 'icon icon-file-directory', 'Nested Directory'
+                  @ul class: 'list-tree', =>
+                    @li class: 'list-item', =>
+                      @span class: 'icon icon-file-text', 'File one'
+                @li class: 'list-nested-item collapsed', =>
+                  @div class: 'list-item', =>
+                    @span class: 'icon icon-file-directory', 'Collpased Nested Directory'
+                  @ul class: 'list-tree', =>
+                    @li class: 'list-item', =>
+                      @span class: 'icon icon-file-text', 'File one'
+                @li class: 'list-item', =>
+                  @span class: 'icon icon-file-text', 'File one'
+                @li class: 'list-item selected', =>
+                  @span class: 'icon icon-file-text', 'File three .selected!'
+            @li class: 'list-item', =>
+              @span class: 'icon icon-file-text', '.icon-file-text'
+            @li class: 'list-item', =>
+              @span class: 'icon icon-file-symlink-file', '.icon-file-symlink-file'
+        '''
+
+        @h2 'With disclosure arrows'
+        @p => @raw 'Add the class .has-collapsable-children to give the children with nested items disclosure arrows.'
         @exampleCode '''
           @ul class: 'list-tree has-collapsable-children', =>
             @li class: 'list-nested-item', =>


fix for long summaries in exercises table

--- src/components/task-plan/homework/exercises.cjsx
+++ src/components/task-plan/homework/exercises.cjsx
@@ -1,6 +1,7 @@
 React = require 'react'
 _ = require 'underscore'
 BS = require 'react-bootstrap'
+$ = require 'jquery'

 ArbitraryHtmlAndMath = require '../../html'
 ExerciseCard = require '../../exercise-card'
@@ -183,6 +184,9 @@
   renderExerciseRow: (exerciseId, index, hasTeks) ->
     {section, lo, tagString} = ExerciseStore.getTagStrings(exerciseId)
     content = ExerciseStore.getContent(exerciseId)
+
+    if $("<div>#{content}</div>").children().length > 1
+      content = $(content).first().html()

     if (hasTeks)
       teksString = ExerciseStore.getTeksString(exerciseId)


Add Sass function check into checkOrder

Add Sass function check into checkOrder

--- tasks/sass-format.coffee
+++ tasks/sass-format.coffee
@@ -174,7 +174,12 @@
               okMsg.push('one property.')
             if options.lang is 'ja'
               okMsg.push('1行1プロパティ')
-          if /^(([^,{]+|[^,]+{[^}]+}[^,]+),){1,}([^,{]+|[^,]+{[^}]+}[^,]+){$/.test(text)
+          else if /^[^@]@[^(]+([^,]+,[^)]+)[^{]+[{,]$/.test(text)
+            if options.lang is 'en'
+              okMsg.push('Sass function.')
+            if options.lang is 'ja'
+              okMsg.push('Sass関数')
+          else if /^(([^,{]+|[^,]+{[^}]+}[^,]+),){1,}([^,{]+|[^,]+{[^}]+}[^,]+){$/.test(text)
             if options.lang is 'en'
               errMsg.push('Many properties!')
             if options.lang is 'ja'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

   window.keydown.left = false

-  equals player,
+  equals player.I.x,

 test "should move player right while key is pressed", ->
   window.keydown.right = true


Lint correction

Lint correction

--- server/collection.coffee
+++ server/collection.coffee
@@ -25,11 +25,11 @@

   # A subset of public fields used for catalog results
   @PUBLISH_CATALOG_FIELDS: ->
-      authorPerson: 1
-      authorGroup: 1
-      name: 1
-      slug: 1
-      publicationsCount: 1
+    authorPerson: 1
+    authorGroup: 1
+    name: 1
+    slug: 1
+    publicationsCount: 1

 registerForAccess Collection


start more test coverage

start more test coverage

--- test/pairbot_test.coffee
+++ test/pairbot_test.coffee
@@ -13,7 +13,7 @@
         require('../src/pairbot')(@robot)

     it 'registers a hear listener', ->
-        #expect(@robot.hear).to.have.been.calledWith(/spw ey/)
+        expect(@robot.hear).to.have.been.calledWith(/(.*)/i)
         expect(true)

     it 'registers a listener to list', ->
@@ -22,5 +22,13 @@
     it 'registers a listener to clear', ->
         expect(@robot.respond).to.have.been.calledWith(/(clear pairs|clear pairing)$/i)

+    it 'doesnt mistake stunning for stu', ->
+        expect true

+
+
+
+
+
+


Remove the migrations, to save up on loggin when it starts...

--- server/migrations.coffee
+++ server/migrations.coffee
@@ -10,16 +10,16 @@
   #     {$set: {'profile.role': 'administrator'}})
   #   log.info("Updated: #{update}"))

-  Meteor.Migrations.add('unedit all sessions', ((log) ->
-    update = Proposal._collection.update({'editing': true},
-      {$set: {'editing': false}},
-      {multi: true})
-    log.info("Updated: #{update}")))
+  # Meteor.Migrations.add('unedit all sessions', ((log) ->
+  #   update = Proposal._collection.update({'editing': true},
+  #     {$set: {'editing': false}},
+  #     {multi: true})
+  #   log.info("Updated: #{update}")))

-  Meteor.Migrations.add('multiple speakers', ((log) ->
-    log.info('Migrating Proposals')
-    Proposal._collection.find().forEach((p) ->
-      Proposal._collection.update({_id: p._id}, {$set: {speaker_ids: [p.user_id]}})
-      log.info("#{p._id}: #{p.user_id}")
-    )
-  ), force: false)
+  # Meteor.Migrations.add('multiple speakers', ((log) ->
+  #   log.info('Migrating Proposals')
+  #   Proposal._collection.find().forEach((p) ->
+  #     Proposal._collection.update({_id: p._id}, {$set: {speaker_ids: [p.user_id]}})
+  #     log.info("#{p._id}: #{p.user_id}")
+  #   )
+  # ), force: false)


Beginnings of more elabortate ai.

Beginnings of more elabortate ai.

--- ai.coffee
+++ ai.coffee
@@ -83,5 +83,12 @@

 # The focused ai will play the best move for the current active subboard.
 # That is, it 'focuses' on winning subboards.
+ais["focused"] = {}
+ais["focused"].move = () ->
+	if subboard_next.length < 1
+		return [4,4]
+	else
+		return ais["greedy"].move()
+

 reset()


improve tests style

improve tests style

--- tests/index_tests.coffee
+++ tests/index_tests.coffee
@@ -1,7 +1,6 @@
 should = require('chai').Should()
 async = require 'async'
 helpers = require './helpers'
-indexer = require '../server/lib/indexer'

 # connection to DB for "hand work"
 db = require("#{helpers.prefix}server/helpers/db_connect_helper").db_connect()
@@ -48,8 +47,8 @@
     before helpers.clearDB db
     before helpers.startApp

-    before (cb) -> indexer.initialize cb
-    before (cb) -> indexer.cleanup cb
+    before indexer.initialize
+    before indexer.cleanup

     after helpers.stopApp

@@ -181,8 +180,9 @@

     describe "Reindexing", ->

-        it "When I stop the DS", helpers.stopApp
-        it "and erase the index", (done) ->
+        before helpers.stopApp
+
+        it "When, DS stoped, I erase the index", (done) ->
             require('../server/lib/indexer').cleanup done

         it "And start the DS", helpers.startApp


Be more explicit in GificiencyAPI.init

Be more explicit in GificiencyAPI.init

--- assets/javascripts/src/gificiency_api.coffee
+++ assets/javascripts/src/gificiency_api.coffee
@@ -32,7 +32,7 @@
   init = ->
     getGifs()
     _initialized = true
-    @
+    return @

   # -------------------------------------
   #   Trim Category


Second half of bc5e5dca868401f71836572d048ec3746300b344 #97

--- SingularityUI/app/views/request.coffee
+++ SingularityUI/app/views/request.coffee
@@ -200,7 +200,7 @@
                     vex.dialog.buttons.NO
                 ]
                 callback: (confirmedOrPromptData) =>
-                    return unless confirmedOrPromptData
+                    return if confirmedOrPromptData is false

                     requestModel.run(confirmedOrPromptData).done =>
                         setTimeout =>


add support for using backspace at number positions with commas, seems to be working well

--- app/assets/javascripts/angular/directives/smartNumber.js.coffee
+++ app/assets/javascripts/angular/directives/smartNumber.js.coffee
@@ -80,7 +80,7 @@

     # helper methods for finding the new cursor position based on input type
     findNewCharacterCursorPosition = (elem, initialPosition) ->
-      if elem.val().length == 3 || elem.val().length == 7 # a new comma is added
+      if elem.val().length == 3 || elem.val().length == 7 # a new comma will be added
         initialPosition + 2
       else if elem[0].selectionStart < elem.val().length # cursor is adding numbers, but is not at the end of the input field
         initialPosition + 1
@@ -88,7 +88,7 @@
         initialPosition + 1

     findNewDeleteCursorPosition = (elem, initialPosition) ->
-      if elem.val().length == 5 || elem.val().length == 9 # a comma has been removed
+      if elem.val().length == 5 || elem.val().length == 9 # a comma will be removed
         if commaAfterCursor(elem)
           return initialPosition
         else
@@ -97,26 +97,21 @@
         return initialPosition

     findNewBackspaceCursorPosition = (elem, initialPosition) ->
-      if elem.val().length == 5 || elem.val().length == 9 # a comma has been removed
+      if elem.val().length == 5 || elem.val().length == 9 # a comma will be removed
         return initialPosition - 2
       else # cursor is adding numbers at the end of the input field
-        return initialPosition - 1
-
-    backspaceAtComma = (elem, event) ->
-      cursorPos = elem[0].selectionStart
-      elem.val().charAt(elem[0].selectionStart - 1) == "," and event.which == 8
-
+        if commaBeforeCursor(elem)
+          return initialPosition - 2
+        else
+          return initialPosition - 1
+
     commaAfterCursor = (elem) ->
       cursorPos = elem[0].selectionStart
       elem.val().charAt(cursorPos) == ","

-    moveCursorLeft = (elem) ->
-      newCursorPos = elem[0].selectionStart - 1
-      elem[0].setSelectionRange(newCursorPos, newCursorPos)
-
-    moveCursorRight = (elem) ->
-      newCursorPos = elem[0].selectionStart + 1
-      elem[0].setSelectionRange(newCursorPos, newCursorPos)
+    commaBeforeCursor = (elem) ->
+      cursorPos = elem[0].selectionStart - 1
+      elem.val().charAt(cursorPos) == ","

     # logic for handling insertion of number characters
     insertCharacter = (elem, event) ->
@@ -130,7 +125,10 @@
     backspacePressed = (elem, event) ->
       originalValue = elem.val() # the original value of the field before keypress
       position = elem[0].selectionStart # cursor position
-      newValue = [originalValue.slice(0, position - 1), originalValue.slice(position)].join('') # insert the new number into the field
+      if commaBeforeCursor(elem)
+        newValue = [originalValue.slice(0, position - 2), originalValue.slice(position)].join('') # insert the new number into the field
+      else
+        newValue = [originalValue.slice(0, position - 1), originalValue.slice(position)].join('') # insert the new number into the field
       elem.val(newValue) # replace the original value with the new one

     # handle delete keypresses


fix calendar weeks

fix calendar weeks

--- app/assets/javascripts/timers_calendar/controllers/month.coffee
+++ app/assets/javascripts/timers_calendar/controllers/month.coffee
@@ -52,9 +52,10 @@
         $scope.currentTimersLoaded = true

     $scope.setWeeks = ->
-      endDate = moment(@date).endOf('month')
-      weekCount = moment(endDate - @date).weeks()
-      date = moment(@date)
+      startDate = moment(@date).startOf('month').startOf('week')
+      endDate = moment(@date).endOf('month').endOf('week')
+      weekCount = moment(endDate - startDate).weeks() - 1
+      date = moment(startDate)
       for weekNumber in [1..weekCount]
         date = date.add(1, 'weeks') if weekNumber > 1
         itr = moment(date.isoWeekday(1)).twix(date.isoWeekday(7)).iterate("days")


Allow more whitespace in the remember regex

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -258,7 +258,7 @@
     res.send 'You rang?'

 module.exports = (robot) ->
-    robot.respond /Remember (?:that )?(.+) said "([^"]+)"/i, rememberPerson
+    robot.respond /Remember +(?:that )?(.+) +said +"([^"]+)"/i, rememberPerson

     robot.respond /Remind me of (.*)/i, msgRespond
     robot.respond /Quote (.*)/i, msgRespond


Added debug code

Added debug code

--- tests/integrated.coffee
+++ tests/integrated.coffee
@@ -117,6 +117,12 @@

     it "The files should be compiled properly", (done) ->
       defer = q.defer()
+      console.log (
+        path.join(
+          "./tests/data/multiple",
+          gutil.replaceExtension(file, ".scss")
+        ) for file in targetFiles
+      )
       g.src((
         path.join(
           "./tests/data/multiple",
@@ -272,9 +278,7 @@
     it "The code should be compiled properly", (done) ->
       defer = q.defer()
       g.src(
-        "./tests/data/imports/main.scss", (
-          "encoding": "utf8"
-        )
+        "./tests/data/imports/main.scss"
       ).pipe(
         sourcemaps.init()
       ).pipe(


Thor is here

Thor is here

--- scripts/starcraft.coffee
+++ scripts/starcraft.coffee
@@ -7,3 +7,11 @@

   robot.hear /.*(single point of failure|spof|artosis pylon).*/i, (msg) ->
     msg.send "http://i.imgur.com/JeSct.jpg"
+
+  robot.head /.*(thor).*/i, (msg) ->
+    msg.send msg.random([
+      "http://i1022.photobucket.com/albums/af346/ThizzyMarley_Starcraft/Terran/Animation19thor.gif",
+      "http://www.pcgameshardware.com/screenshots/original/2008/08/Thor_Wreckage_Animated.gif",
+      "http://images4.wikia.nocookie.net/__cb20090510213650/starcraft/images/e/ed/Thor_SC2_Anim1.gif",
+      "http://wiki.teamliquid.net/starcraft/images2/6/6b/Thor.gif"
+    ])


fix matplotlib show bug

fix matplotlib show bug

--- lib/code-chunk.coffee
+++ lib/code-chunk.coffee
@@ -75,17 +75,22 @@
         catch e
           callback?(null, e.toString(), options)

+
   if cmd.match(/python/) and options.matplotlib
     content = """
-import matplotlib
-matplotlib.use('Svg') # use Svg backend
+# modify default matplotlib pyplot show function
+try:
+  import matplotlib
+  matplotlib.use('Svg') # use Svg backend
+  import matplotlib.pyplot as plt
+  import sys
+  def new_plt_show():
+    plt.savefig(sys.stdout)
+  plt.show = new_plt_show # override old one
+except Exception:
+  pass

-""" + content + """
-
-import matplotlib.pyplot as plt
-import sys
-plt.savefig(sys.stdout)
-"""
+""" + content
     options.output = 'html' # change to html so that svg can be rendered


Workspace: Removed deleting itemClass in header buttons.

--- client/app/CommonViews/workspace/panel/panel.coffee
+++ client/app/CommonViews/workspace/panel/panel.coffee
@@ -41,7 +41,6 @@
     @getOptions().buttons.forEach (buttonOptions) =>
       if buttonOptions.itemClass
         Klass = buttonOptions.itemClass
-        delete buttonOptions.itemClass
         buttonOptions.callback = buttonOptions.callback?.bind this, this, @getDelegate()

         buttonView = new Klass buttonOptions


Context reduction for explicit types and declaring instance before typing its definition so it can be used

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1107,12 +1107,27 @@
   if ctx.isActuallyTyped name
     # TODO: check class constraints
     if not includesJsType currentType.type
+      # Check the declared type
       declaredType = ctx.type name
       explicitType = freshInstance ctx, declaredType
       unify ctx, currentType.type, explicitType.type
-      inferredType = (quantifyUnbound ctx, (substitute ctx.substitution, explicitType))
+      unifiedType = substitute ctx.substitution, explicitType
+      inferredType = quantifyUnbound ctx, unifiedType
       if not typeEq inferredType, declaredType
         ctx.extendSubstitution substituionFail "#{name}'s declared type is too general, inferred #{plainPrettyPrint inferredType}"
+      # Context reduction
+      isDeclaredConstraint = (c) ->
+        entail ctx, unifiedType.constraints, c
+      ps = filter (__ _not, isDeclaredConstraint), (substituteList ctx.substitution, constraints)
+      try
+        [deferredConstraints, retainedConstraints] = deferConstraints ctx,
+          ctx.allBoundTypeVariables(),
+          (findFree unifiedType),
+          (substituteList ctx.substitution, constraints)
+      catch e
+        throw new Error "Error when deferring #{name} #{e.message}"
+      if _notEmpty retainedConstraints
+        ctx.extendSubstitution substituionFail "#{name}'s context is too weak, missing #{map plainPrettyPrint, retainedConstraints}"
   else
     [deferredConstraints, retainedConstraints] = deferConstraints ctx,
       ctx.allBoundTypeVariables(),
@@ -1633,21 +1648,10 @@
       return malformed ctx, call, "An instance requires a name"
     instanceName = ctx.definitionName()

-    ctx.newScope()
-    freshInstanceType = assignMethodTypes ctx, instanceConstraint, instanceName,
-      classDefinition, instanceType, constraints
-    definitions = pairs wheres
-    methodsDeclarations = definitionList ctx,
-      (prefixWithInstanceName ctx, definitions, instanceName)
-    ctx.closeScope()
-    if ctx.shouldDefer()
-      return deferCurrentDefinition ctx, call
-
-    methods = map (({rhs}) -> rhs), methodsDeclarations
-    # log "methods", methods
-    methodTypes = (rhs.tea for [lhs, rhs] in definitions)
-    if not all methodTypes
-      return (jsMalformed "missing type of a method")
+    # First we must freshen the instance type, to avoid name clashes of type vars
+    freshInstanceType = freshInstance ctx,
+      (quantifyUnbound ctx,
+        (new Constrained constraints, instanceType.types))

     # TODO: defer for class declaration if not defined
     ## if not ctx.isClassDefined className    ...
@@ -1657,11 +1661,29 @@
       freshConstraints = freshInstanceType.constraints
       instance = quantifyAll (new Constrained freshConstraints,
         (new ClassConstraint instanceType.className, freshInstanceType.type))
+
       ## if overlaps ctx, instance
       ##   malformed ctx, 'instance overlaps with another', instance
       ## else
       ctx.addInstance instanceName, instance

+      ctx.newScope()
+      assignMethodTypes ctx, instanceConstraint, freshInstanceType, instanceName,
+        classDefinition, instanceType, constraints
+      definitions = pairs wheres
+      methodsDeclarations = definitionList ctx,
+        (prefixWithInstanceName ctx, definitions, instanceName)
+      ctx.closeScope()
+      if ctx.shouldDefer()
+        return deferCurrentDefinition ctx, call
+
+      methods = map (({rhs}) -> rhs), methodsDeclarations
+      # log "methods", methods
+      methodTypes = (rhs.tea for [lhs, rhs] in definitions)
+      if not all methodTypes
+        malformed ctx, call, "missing type of a method"
+        return jsNoop()
+
       # """var #{instanceName} = new #{className}(#{listOf methods});"""
       (jsVarDeclaration (validIdentifier instanceName),
         (irDefinition (new Constrained freshConstraints, (tupleOfTypes methodTypes).type),
@@ -1669,12 +1691,7 @@

 # Makes sure methods are typed explicitly and returns the instance constraint
 # with renamed type variables to avoid clashes
-assignMethodTypes = (ctx, typeExpression, instanceName, classDeclaration, instanceType, instanceConstraints) ->
-  # First we must freshen the instance type, to avoid name clashes of type vars
-  freshInstanceType = freshInstance ctx,
-    (quantifyUnbound ctx,
-      (new Constrained instanceConstraints, instanceType.types))
-
+assignMethodTypes = (ctx, typeExpression, freshInstanceType, instanceName, classDeclaration) ->
   # log "mguing", classDeclaration.constraint.types, freshInstanceType.type
   sub = mostGeneralUnifier classDeclaration.constraint.types, freshInstanceType.type
   if isFailed sub
@@ -4688,11 +4705,13 @@
   list.filter fn

 partition = (fn, list) ->
-  [(filter fn, list), (filter ((x) -> not (fn x)), list)]
+  [(filter fn, list), (filter (__ _not, fn), list)]

 _notEmpty = (x) -> x.length > 0

 _empty = (x) -> x.length is 0
+
+_not = (x) -> !x

 _is = (x) -> !!x


Preserve type origin through substitution

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -3986,15 +3986,16 @@
   else if type.QuantifiedVar
     substitution[type.var] or type
   else if type.TypeApp
-    new TypeApp (substitute substitution, type.op),
-      (substitute substitution, type.arg)
+    markOrigin (new TypeApp (substitute substitution, type.op),
+      (substitute substitution, type.arg)), type.origin
   else if type.ForAll
     new ForAll type.kinds, (substitute substitution, type.type)
   else if type.Constrained
     new Constrained (substituteList substitution, type.constraints),
       (substitute substitution, type.type)
   else if type.ClassConstraint
-    new ClassConstraint type.className, substitute substitution, type.types
+    markOrigin (new ClassConstraint type.className, substitute substitution, type.types),
+      type.origin
   else if type.Types
     new Types substituteList substitution, type.types
   else


Add modifiers to regex literals

Add modifiers to regex literals

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -7,7 +7,7 @@
         \x20 # space
       | \n # newline
       | [#{controls}] # delims
-      | /([^\s]|\\/)([^/]|\\/)*?/ # regex
+      | /([^\s]|\\/)([^/]|\\/)*?/[gmi]? # regex
       | "(?:[^"\\]|\\.)*" # strings
       | \\[^\s][^\s#{controls}]* # char
       | [^#{controls}\\"'\s]+ # normal tokens
@@ -86,7 +86,7 @@
     ['label', -> isLabel atom]
     ['string', -> /^"/.test symbol]
     ['char', -> /^\\/.test symbol]
-    ['regex', -> /^\/[^\s\/]/.test symbol]
+    ['regex', -> /^\/.*\/[gmi]?$/.test symbol]
     ['const', -> /^[A-Z]([^\s\.-]|-(?=[A-Z]))*$/.test symbol] # TODO: instead label based on context
     ['paren', -> symbol in ['(', ')']]
     ['bracket', -> symbol in ['[', ']']]


Collaboration: Return early if editor is not ready

--- client/ide/lib/workspace/panes/ideeditorpane.coffee
+++ client/ide/lib/workspace/panes/ideeditorpane.coffee
@@ -212,8 +212,10 @@

   setLineWidgets: (row, col, username) ->

+    return  unless editor = @getEditor()
+
     oldWidget      = @lineWidgets[username]
-    {renderer}     = @getEditor()
+    {renderer}     = editor
     widgetManager  = @getAce().lineWidgetManager
     lineHeight     = renderer.lineHeight
     charWidth      = renderer.characterWidth


Error for token in pm, type keyword, JS ffi

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1,7 +1,8 @@
 colorize = (color, string) ->
   "#{string}"

-keywords = 'def defn data class fn proc match if type :: \\'.split ' '
+keywords = 'def defn data class fn proc match if type :: \\
+            access call new'.split ' '

 controls = '\\(\\)\\[\\]'

@@ -14,10 +15,10 @@
       (
         \s+ # pure whitespace
       | [#{controls}] # brackets
-      | [^#{controls}"'\s]+ # normal tokens
+      | /([^\ /]|\\/)([^/]|\\/)*?/ # regex
       | "[^"]*?" # strings
       | '\\?[^']' # char
-      | /[^\ /][^/]*?/ # regex
+      | [^#{controls}"'\s]+ # normal tokens
       )///
     if not match
       throw new Error "Could not recognize a token starting with `#{input[0..10]}`"
@@ -223,6 +224,11 @@
     node.type = 'data'
     node

+typeTypes = (ast) ->
+  macro 'type', ast, (node) ->
+    node.type = 'type'
+    node
+
 typeComments = (ast) ->
   macro '#', ast, (node) ->
     node.type = 'comment'
@@ -248,7 +254,7 @@
   result

 typifyMost = (ast) ->
-  apply ast, typeComments, typeDatas, labelSimple, labelMatches, labelFns
+  apply ast, typeComments, typeDatas, typeTypes, labelSimple, labelMatches, labelFns

 typify = (ast) ->
   apply ast, typifyMost, labelComments
@@ -324,6 +330,7 @@
   switch node.type
     when 'comment' then 'null'
     when 'data' then 'null'
+    when 'type' then 'null'
     when 'function' then compileFn node
     else
       if Array.isArray node
@@ -381,6 +388,7 @@
         .replace(/\?/g, 'p_')
         .replace(/^const$/, 'const_')
         .replace(/^default$/, 'default_')
+        .replace(/^with$/, 'with_')

 compileFn = (node) ->
   {params, body, wheres} = fnDefinition node
@@ -442,6 +450,8 @@
         [tail, "#{exp}.tail"]
       ]
   (pattern) ->
+    if not Array.isArray pattern
+      throw new Error "pattern match expected pattern but saw token #{pattern.token}"
     elems = inside pattern
     trigger: not isMap pattern
     cache: true
@@ -518,9 +528,15 @@
     } else {
       return #{elz};
     }}())"""
+  'access': (field, obj) ->
+    # TODO: use dot notation if method is valid field name
+    "(#{obj})[#{field}]"
   'call': (method, obj, args...) ->
-    # TODO: use dot notation if method is valid method name
-    """((#{obj})[#{method}](#{args.join ', '}))"""
+    "(#{macros.access method, obj}(#{args.join ', '}))"
+  'new': (clazz, args...) ->
+    "(new #{clazz}(#{args.join ', '}))"
+  ':': (elems...) ->
+    "[#{elems.join ', '}]"

 trueMacros =
   'match': (onwhat, cases...) ->
@@ -706,6 +722,16 @@
   };
   return _curry();
 };
+
+var fromminus_nullable = function (jsValue) {
+  if (typeof jsValue === "undefined" || jsValue === null) {
+    return {'None:': []};
+  } else {
+    return {'Just:': [jsValue]};
+  }
+};
+
+;
 """

 exports.compile = (source) ->


We don't directly output js anymore.

We don't directly output js anymore.


:art: Cleanup.

:art: Cleanup.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -113,8 +113,7 @@

     getFilteredJumpyKeys: ->
         atom.keymaps.keyBindings.filter (keymap) ->
-            keymap.command
-                .indexOf('jumpy') > -1 if typeof keymap.command is 'string'
+            keymap.command.includes 'jumpy' if typeof keymap.command is 'string'

     turnOffSlowKeys: ->
         atom.keymaps.keyBindings = @getFilteredJumpyKeys()


Fix some deprecations

Fix some deprecations

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -197,7 +197,7 @@

                 console.timeEnd 'drawLabels'

-            [firstVisibleRow, lastVisibleRow] = editor.getVisibleRowRange()
+            [firstVisibleRow, lastVisibleRow] = editorView.getVisibleRowRange()
             for lineNumber in [firstVisibleRow...lastVisibleRow]
                 lineContents = editor.lineTextForScreenRow(lineNumber)
                 if editor.isFoldedAtScreenRow(lineNumber)
@@ -206,15 +206,15 @@
                     while ((word = wordsPattern.exec(lineContents)) != null)
                         drawLabels lineNumber, word.index

-            @initializeClearEvents(editor, editorView)
+            @initializeClearEvents(editorView)

     clearJumpModeHandler: (e) =>
         @clearJumpMode()

-    initializeClearEvents: (editor, editorView) ->
-        @disposables.add editor.onDidChangeScrollTop =>
+    initializeClearEvents: (editorView) ->
+        @disposables.add editorView.onDidChangeScrollTop =>
             @clearJumpModeHandler()
-        @disposables.add editor.onDidChangeScrollLeft =>
+        @disposables.add editorView.onDidChangeScrollLeft =>
             @clearJumpModeHandler()

         for e in ['blur', 'click']


Fix isSameOrigin check in IE

Fix isSameOrigin check in IE

--- lib/rails/csrf.coffee
+++ lib/rails/csrf.coffee
@@ -61,4 +61,5 @@
 isSameOrigin = (url) ->
   a = document.createElement 'a'
   a.href = url
-  location.protocol is a.protocol and location.host is a.host
+  origin = a.href.split('/', 3).join "/"
+  location.href.indexOf(origin) is 0


Add robots.txt

Add robots.txt

--- lib/www_routes.coffee
+++ lib/www_routes.coffee
@@ -375,6 +375,13 @@
       res.redirect(doc.absolute_long_url)

   #
+  # Static and well-known
+  #
+  app.get "/robots.txt", (req, res) ->
+    res.setHeader('Content-Type', 'text/plain; charset=utf-8')
+    res.send("User-agent: *\nDisallow:")
+
+  #
   # Testing
   #
   if process.env.NODE_ENV != "production"


UPnP requires Content-Length for descriptions

Restructure http request handler to be able to get content-length

--- lib/xml-server.coffee
+++ lib/xml-server.coffee
@@ -9,23 +9,35 @@
 createServer = exports.createServer = (deviceType, deviceName) ->
     http.createServer (req, res) ->
         debug "#{req.url} served to #{req.client.remoteAddress}"
+
+        serve = (body) ->
+            res.writeHead 200,
+                'Content-Type': 'text/xml'
+                'Content-Length': Buffer.byteLength(body)
+            res.write body
+            res.end()
+
+        error = (code) ->
+            res.writeHead code,
+                'Content-Type': 'text/plain'
+            res.write "404 Not Found"
+            res.end()
+
         if isServiceReq(req) or isDeviceReq(req)
-            res.writeHead 200, 'Content-Type': 'text/xml'
             if isServiceReq(req) and getReqAction(req) is 'description'
                 # service descriptions are static XML files
                 fs.readFile makeServicePath(getReqType(req)), (err, file) ->
                     throw err if err
-                    res.write file
-                    res.end()
+                    serve file.toString('utf8', 0, file.length)
+            else if isDeviceReq(req)
+                body = '<?xml version="1.0" encoding="utf-8"?>\n'
+                body += device.buildDescription deviceType, deviceName
+                serve(body)
             else
-                res.write '<?xml version="1.0" encoding="utf-8"?>\n'
-                if isDeviceReq(req)
-                    res.write device.buildDescription deviceType, deviceName
-                res.end()
+                error(404)
         else
-            res.writeHead 404, 'Content-Type': 'text/plain'
-            res.write '404 Not found'
-            res.end()
+            error(404)
+

 # find a suitable IP/port and start listening on server
 listen = exports.listen = (server, callback) ->


More error handling in addDataset

More error handling in addDataset

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -734,7 +734,10 @@
           console.log "TOOL dataset.tool #{dataset.tool} body.tool #{body.tool}"

           Dataset.findOneById dataset.box, req.user.effective.shortName, (err, dataset) ->
-            console.warn err if err?
+            if err?
+              console.warn err
+              return resp.send 500, error: "Error saving dataset: #{err}"
+
             resp.send 200, dataset
             _addView user, dataset,
               tool: 'datatables-view-tool'


shouldn't need to hold the reference.

shouldn't need to hold the reference.

As long as portal is removed in `turbolinks:before-cache` before the next page loads

--- resources/assets/lib/play-detail-menu.coffee
+++ resources/assets/lib/play-detail-menu.coffee
@@ -28,8 +28,6 @@

     @uuid = osu.uuid()
     @menu = createRef()
-
-    @body = document.body

     @state =
       active: false
@@ -96,11 +94,11 @@

   addPortal: =>
-    @body.appendChild @portal if !@portal.parentElement?
+    document.body.appendChild @portal if !@portal.parentElement?

   removePortal: =>
-    @body.removeChild @portal if @portal.parentElement?
+    document.body.removeChild @portal if @portal.parentElement?

   render: =>


download market via a file

download market via a file

--- server/lib/market.coffee
+++ server/lib/market.coffee
@@ -1,10 +1,12 @@
 request = require 'request-json'
+Client = request.JsonClient
 log = require('printit')
     prefix: 'market'
 exec = require('child_process').exec
 fs = require 'fs'
-del = require('del')
-apps = []
+del = require 'del'
+url = require 'url'
+apps = {}
 isDownloading = false

 # sort the applications list by official/community status, then by name
@@ -22,66 +24,45 @@
     else
         return 0

-readApps = (cb) ->
-    fs.readdir './market/apps', (err, files) ->
-        log.error "[Error] Read market: #{err}" if err?
-        return cb(err) if err?
-        apps = []
-        for file in files
-            try
-                # Node js
-                apps.push require "../../../market/apps/#{file}"
-            catch
-                # Coffeescript
-                apps.push require "../../market/apps/#{file}"
-        apps.sort comparator
-        cb null, apps
+download = module.exports.download = (callback) ->

-
-download = module.exports.download = (callback) ->
     isDownloading = true

     # Retrieve market path
     if process.env.MARKET?
         # Use a specific market
-        url = "https://gitlab.cozycloud.cc/zoe/cozy-registry.git"
-        branch = process.env.MARKET
+        path = process.env.MARKET
     else
         # Use default market
-        url = "https://github.com/cozy/cozy-registry.git"
-        branch = "master"
+        path = "https://registry.cozycloud.cc/cozy-registry.json"

-    # Clone market (cannot use github API due to rate limit)
-    command =  "git clone #{url} markettmp && " + \
-          "cd markettmp && " + \
-          "git checkout #{branch}"
-    # Clone new market in temporary folder
-    exec command, {}, (err, stdout, stderr) ->
-
-        if err?
-            # If clone doesn't work, keep old market
-            log.error "[Error] Clone market: #{err}" if err?
-            del './markettmp', (error) ->
-                callback(err) if err?
-
+    version = 0
+    if fs.existsSync './market.json'
+        data = fs.readFileSync './market.json', 'utf8'
+        oldMarket = JSON.parse(data)
+        version = oldMarket.version
+    path = path + "?version=#{version}"
+    path = url.parse path
+    client = new Client "#{path.protocol}//#{path.host}"
+    client.headers['user-agent'] = 'cozy'
+    client.get path.pathname, (err, res, body) ->
+        if not err and body.apps_list?
+            apps = body.apps_list
+            fs.writeFileSync './market.json', JSON.stringify(body)
+        else if oldMarket?
+            apps = oldMarket.apps_list
         else
-            # Remove old market
-            del './market', (err) ->
-                log.error "[Error] delete market : #{err}" if err?
-                # Replace ol;d market by new one
-                exec "mv markettmp/ market", (err, stdout, stderr) ->
-                    log.error "[Error] Copie market: #{err}" if err?
-                    # Read all files (each app is declared in a file)
-                    readApps (err, apps) ->
-                        isDownloading = false
-                        callback err, apps
+            apps = null
+        callback err, apps

 getApps = module.exports.getApps = (cb) ->
-    if apps.length > 0
+    if Object.keys(apps).length > 0
         cb null, apps
-    else if fs.existsSync './market/apps'
-        readApps cb
+    else if fs.existsSync './market.json'
+        data = fs.readFileSync './market.json', 'utf8'
+        market = JSON.parse(data)
+        cb null, market.apps_list
     else
         if isDownloading
             setTimeout () ->
@@ -91,10 +72,7 @@
             download cb

 module.exports.getApp = (app) ->
-    try
-        return [null, require "../../../market/apps/#{app}"]
-    catch
-        try
-            return [null, require "../../market/apps/#{app}"]
-        catch
-            return ['not found', null]
+    if apps.app?
+        return [null, apps[app]]
+    else
+        return ['not found', null]


minor

minor

--- dvl.coffee
+++ dvl.coffee
@@ -834,7 +834,7 @@
 ##
 ##  Displays the object value with a message whenever the object changes.
 ##
-dvl.debug = () ->
+dvl.debug = ->
   if arguments.length is 1
     obj = dvl.wrapConstIfNeeded(arguments[0])
     note = obj.name() + ':'


Update HumanReadableLogsRules.coffee

Update HumanReadableLogsRules.coffee

--- services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee
+++ services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee
@@ -8,31 +8,31 @@
 		regexToMatch: /Extra alignment tab has been changed to \\cr/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:Extra_alignment_tab_has_been_changed_to_%5Ccr"
 		humanReadableHint: """
-			You have written too many alignment tabs in a table, causing one of them to be turned one of them into a line break. Make sure you have specified the correct number of columns in your [table](https://www.sharelatex.com/learn/Tables).
+			You have written too many alignment tabs in a table, causing one of them to be turned into a line break. Make sure you have specified the correct number of columns in your [table](https://www.sharelatex.com/learn/Tables).
 		"""
 	,
 		regexToMatch: /Display math should end with \$\$/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:Display_math_should_end_with_$$."
 		humanReadableHint: """
-			You have forgotten a $ sign at the end of 'display math' mode. When writing in \u2018display math' mode, you must always math write inside $$ \u2026 $$. Check that the number of $s match around maths.
+			You have forgotten a $ sign at the end of 'display math' mode. When writing in display math mode, you must always math write inside $$ \u2026 $$. Check that the number of $s match around each math expression.
 		"""
 	,
 		regexToMatch: /Missing [{$] inserted./
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:Missing_$_inserted"
 		humanReadableHint: """
-			Check that your $s match around maths. If they do, then you've probably used a symbol in normal text that needs to be in math mode. Sybols such as subscripts ( _ ), integrals ( \\int ), Greek letters ( \\alpha, \\beta, \\delta ), and modifiers (\\vec{x}, \\tilde{x} ) must be written in math mode. See the full list [here](https://www.sharelatex.com/learn/Errors:Missing_$_inserted). If you intended to use mathematics mode, then use $ \u2026 $ or \\begin{math} \u2026 \\end{math} or use the \u2018quick math mode\u2019: \\ensuremath{...}.
+			Check that your $'s match around math expressions. If they do, then you've probably used a symbol in normal text that needs to be in math mode. Symbols such as subscripts ( _ ), integrals ( \\int ), Greek letters ( \\alpha, \\beta, \\delta ), and modifiers (\\vec{x}, \\tilde{x} ) must be written in math mode. See the full list [here](https://www.sharelatex.com/learn/Errors:Missing_$_inserted).
 		"""
 	,
 		regexToMatch: /(undefined )?[rR]eference(s)?.+(undefined)?/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:There_were_undefined_references."
 		humanReadableHint: """
-			You have referenced something which has not yet been labeled. If you have labeled it already, make sure that what is written inside \\ref{...} is the same as what is written inside \\label{...}.
+			You have referenced something which has not yet been labelled. If you have labelled it already, make sure that what is written inside \\ref{...} is the same as what is written inside \\label{...}.
 		"""
 	,
 		regexToMatch: /Citation .+ on page .+ undefined on input line .+/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:Citation_XXX_on_page_XXX_undefined_on_input_line_XXX."
 		humanReadableHint: """
-			You have cited something which has not been included in the bibliography. Make sure that the citation (\\cite{...}) has a corresponding label in your bibliography, and that both are spelled the same way.
+			You have cited something which is not included in your bibliography. Make sure that the citation (\\cite{...}) has a corresponding key in your bibliography, and that both are spelled the same way.
 		"""
 	,
 		regexToMatch: /(Label .+)? multiply[ -]defined( labels)?/
@@ -44,13 +44,13 @@
 		regexToMatch: /`!h' float specifier changed to `!ht'/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:%60!h%27_float_specifier_changed_to_%60!ht%27."
 		humanReadableHint: """
-			The float specifier '!h' is too strict of a demand for LaTeX to place our float in a nice way here. Try relaxing it by using 'ht', or even 'htbp' if necessary. If you want to try to keep the float here anyway, check out the [float package](https://www.sharelatex.com/learn/Errors:%60!h%27_float_specifier_changed_to_%60!ht%27.).
+			The float specifier '!h' is too strict of a demand for LaTeX to place your float in a nice way here. Try relaxing it by using 'ht', or even 'htbp' if necessary. If you want to try to keep the float here anyway, check out the [float package](https://www.sharelatex.com/learn/Errors:%60!h%27_float_specifier_changed_to_%60!ht%27.).
 		"""
 	,
 		regexToMatch: /`h' float specifier changed to `ht'/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:%60!h%27_float_specifier_changed_to_%60!ht%27."
 		humanReadableHint: """
-			The float specifier 'h' is too strict of a demand for LaTeX to place our float in a nice way here. Try relaxing it by using 'ht', or even 'htbp' if necessary. If you want to try keep the float here anyway, check out the [float package](https://www.sharelatex.com/learn/Positioning_of_Figures).
+			The float specifier 'h' is too strict of a demand for LaTeX to place your float in a nice way here. Try relaxing it by using 'ht', or even 'htbp' if necessary. If you want to try keep the float here anyway, check out the [float package](https://www.sharelatex.com/learn/Positioning_of_Figures).
 		"""
 	,
 		regexToMatch: /No positions in optional float specifier/
@@ -74,19 +74,13 @@
 		regexToMatch: /LaTeX Error: Unknown graphics extension: \..+/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_graphics_extension:_.gif."
 		humanReadableHint: """
-			The compiler does not recognise the file type of one of your images. Make sure you are using a [supported image format](https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_graphics_extension:_.gif.) for your choice of compiler, and check that there are no full stops in the name of your image.
+			The compiler does not recognise the file type of one of your images. Make sure you are using a [supported image format](https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_graphics_extension:_.gif.) for your choice of compiler, and check that there are no periods (.) in the name of your image.
 		"""
 	,
 		regexToMatch: /LaTeX Error: Unknown float option `H'/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_float_option_%60H%27."
 		humanReadableHint: """
 			The compiler isn't recognizing the float option 'H'. Include \\usepackage{float} in your preamble to fix this.
-		"""
-	,
-		regexToMatch: /LaTeX Error: Unknown float option `P'/
-		extraInfoURL: "https://www.sharelatex.com/learn/Errors:LaTeX_Error:_Unknown_float_option_%60H%27."
-		humanReadableHint: """
-			You have used a float specifier which the compiler does not understand. The only extra float option provided by the 'float' package is 'H'. You can learn more about the different float options available for placing figures in our [documentation](https://www.sharelatex.com/learn/Positioning_of_Figures).
 		"""
 	,
 		regexToMatch: /LaTeX Error: Unknown float option `.+'/
@@ -98,6 +92,6 @@
 		regexToMatch: /LaTeX Error: \\math.+ allowed only in math mode/
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors:LaTeX_Error:_%5Cmathrm_allowed_only_in_math_mode."
 		humanReadableHint: """
-			You have used a font which is only available in math mode. To use this font, you must write it in mathematics mode by using $ \u2026 $ or \\begin{math} \u2026 \\end{math}, or using the \u2018quick math mode\u2019: \\ensuremath{...}.
+			You have used a font command which is only available in math mode. To use this command, you must be in maths mode (E.g. $ \u2026 $ or \\begin{math} \u2026 \\end{math}). If you want to use it outside of math mode, use the text version instead: \\textrm, \\textit, etc.
 		"""
 ]


improve text decoration

improve text decoration

--- lib/psd/layer_info/typetool.coffee
+++ lib/psd/layer_info/typetool.coffee
@@ -86,7 +86,7 @@
       if f.Underline
         decoration = 'underline'
       else
-        decoration = 'normal'
+        decoration = 'none'
       return decoration

   leading: ->


:art:

:art:

--- spec/body-parser-spec.coffee
+++ spec/body-parser-spec.coffee
@@ -45,12 +45,12 @@
     expect(bodyTree).toEqual [
       "snippet ",
       {
-        "index": 1,
-        "content": []
+        index: 1,
+        content: []
       },
       " escaped $2 \\",
       {
-        "index": 3,
-        "content": []
+        index: 3,
+        content: []
       }
     ]


:art: refactor for readability

:art: refactor for readability

--- spec/insert-mode-spec.coffee
+++ spec/insert-mode-spec.coffee
@@ -5,10 +5,13 @@
   [set, ensure, keystroke, editor, editorElement, vimState] = []

   beforeEach ->
-    getVimState (vimState, vim) ->
-      {editor, editorElement} = vimState
-      vimState.activate('reset')
+    getVimState (_vimState, vim) ->
+      vimState = _vimState
+      {editor, editorElement} = _vimState
       {set, ensure, keystroke} = vim
+
+  afterEach ->
+    vimState.activate('reset')

   describe "Copy from line above/below", ->
     beforeEach ->
@@ -24,14 +27,14 @@

     describe "the ctrl-y command", ->
       it "copies from the line above", ->
-        ensure [ctrl: 'y'], text: """
+        ensure {ctrl: 'y'}, text: """
           12345
           1
           abcd
           aefghi
           """
         editor.insertText ' '
-        ensure [ctrl: 'y'], text: """
+        ensure {ctrl: 'y'}, text: """
           12345
           1 3
           abcd
@@ -40,15 +43,39 @@

       it "does nothing if there's nothing above the cursor", ->
         editor.insertText 'fill'
-        ensure [ctrl: 'y'], text: '12345\nfill5\nabcd\nfillefghi'
-        ensure [ctrl: 'y'], text: '12345\nfill5\nabcd\nfillefghi'
+        ensure {ctrl: 'y'},
+          text: """
+            12345
+            fill5
+            abcd
+            fillefghi
+            """
+        ensure {ctrl: 'y'},
+          text: """
+            12345
+            fill5
+            abcd
+            fillefghi
+            """

       it "does nothing on the first line", ->
         set
           cursorBuffer: [[0, 2], [3, 2]]
         editor.insertText 'a'
-        ensure text: '12a345\n\nabcd\nefaghi'
-        ensure [ctrl: 'y'], text: '12a345\n\nabcd\nefadghi'
+        ensure
+          text: """
+            12a345
+
+            abcd
+            efaghi
+            """
+        ensure {ctrl: 'y'},
+          text: """
+            12a345
+
+            abcd
+            efadghi
+            """

     describe "the ctrl-e command", ->
       beforeEach ->
@@ -57,11 +84,35 @@
             'ctrl-e': 'vim-mode-plus:copy-from-line-below'

       it "copies from the line below", ->
-        ensure [ctrl: 'e'], text: '12345\na\nabcd\nefghi'
+        ensure {ctrl: 'e'},
+          text: """
+            12345
+            a
+            abcd
+            efghi
+            """
         editor.insertText ' '
-        ensure [ctrl: 'e'], text: '12345\na c\nabcd\n efghi'
+        ensure {ctrl: 'e'},
+          text: """
+            12345
+            a c
+            abcd
+             efghi
+            """

       it "does nothing if there's nothing below the cursor", ->
         editor.insertText 'foo'
-        ensure [ctrl: 'e'], text: '12345\nfood\nabcd\nfooefghi'
-        ensure [ctrl: 'e'], text: '12345\nfood\nabcd\nfooefghi'
+        ensure {ctrl: 'e'},
+          text: """
+            12345
+            food
+            abcd
+            fooefghi
+            """
+        ensure {ctrl: 'e'},
+          text: """
+            12345
+            food
+            abcd
+            fooefghi
+            """


Add more commentary on the anonymous path

--- services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee
+++ services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee
@@ -34,17 +34,24 @@
 			AuthorizationManager.getPublicAccessLevel project_id, (err, publicAccessLevel) ->
 				return callback(err) if err?
 				if publicAccessLevel == PublicAccessLevels.TOKEN_BASED
+					# Anonymous users can have read-only access to token-based projects,
+					# while read-write access must be logged in
 					TokenAccessHandler.requestHasReadOnlyTokenAccess req, project_id, (err, allowed) ->
 						return callback(err) if err?
 						if allowed
+							# Grant anonymous user read-only access
 							callback null, PrivilegeLevels.READ_ONLY, false
 						else
+							# Deny anonymous user access
 							callback null, PrivilegeLevels.NONE, false
 				else if publicAccessLevel == PublicAccessLevels.READ_ONLY
+					# Legacy public read-only access for anonymous user
 					callback null, PrivilegeLevels.READ_ONLY, true
 				else if publicAccessLevel == PublicAccessLevels.READ_AND_WRITE
+					# Legacy public read-write access for anonymous user
 					callback null, PrivilegeLevels.READ_AND_WRITE, true
 				else
+					# Deny anonymous user access
 					callback null, PrivilegeLevels.NONE, false
 		else
 			# User is present, get their privilege level from database


:art: Remove unused context menus

:art: Remove unused context menus

--- menus/atom-notational.cson
+++ menus/atom-notational.cson
@@ -1,22 +1,22 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
-'context-menu':
-  'atom-text-editor': [
-    {
-      'label': 'Toggle atom-notational'
-      'command': 'atom-notational:toggle'
-    }
-  ]
-'menu': [
-  {
-    'label': 'Packages'
-    'submenu': [
-      'label': 'atom-notational'
-      'submenu': [
-        {
-          'label': 'Toggle'
-          'command': 'atom-notational:toggle'
-        }
-      ]
-    ]
-  }
-]
+# 'context-menu':
+#   'atom-text-editor': [
+#     {
+#       'label': 'Toggle atom-notational'
+#       'command': 'atom-notational:toggle'
+#     }
+#   ]
+# 'menu': [
+#   {
+#     'label': 'Packages'
+#     'submenu': [
+#       'label': 'atom-notational'
+#       'submenu': [
+#         {
+#           'label': 'Toggle'
+#           'command': 'atom-notational:toggle'
+#         }
+#       ]
+#     ]
+#   }
+# ]


Follow mixin

Follow mixin


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


WIP counter drag-and-drop: move stacks of counters

--- ui.coffee
+++ ui.coffee
@@ -31,7 +31,10 @@

 class ui.ComponentStack
-  constructor: ({@type, @container, @stack, @count}) ->
+  constructor: ({@uiType, @container, @stack, @count}) ->
+    @type = @uiType.name
+    check @uiType, ui.ComponentType
+    check @count, Number

 class ui.ComponentType
@@ -115,7 +118,7 @@
           cursor = container.find selector
           count = cursor.count()
           if cursor.count() > @stackAfter
-            Blaze.With (new ui.ComponentStack type: @, stack: value, count: count), =>
+            Blaze.With (new ui.ComponentStack uiType: @, container: container, stack: value, count: count), =>
               @getCounterTemplate count, container.rules.name
           else
             Blaze.With (type: @, stack: value, container: container), =>
@@ -127,7 +130,7 @@
         cursor = container.find type: @name
         count = cursor.count()
         if cursor.count() > @stackAfter
-          Blaze.With (new ui.ComponentStack type: @, count: count), =>
+          Blaze.With (new ui.ComponentStack uiType: @, container: container, count: count), =>
             @getCounterTemplate count, container.rules.name
         else
           Blaze.With (=> type: @, container: container), =>
@@ -264,14 +267,15 @@
     view = Blaze.getView @element
     while view and view isnt Blaze.currentView
       data = Blaze.getData view
-      if data instanceof CBGA.Component and not @component?
+      if (data instanceof CBGA.Component or data instanceof ui.ComponentStack) \
+          and not @component?
         @component = data
       if data.controller? and data.owner? and not @sourceController?
         @sourceController = data.controller
         @sourceOwner = data.owner
       break if @component? and @sourceController?
       view = view.parentView
-    check @component, CBGA.Component
+    check @component, Match.OneOf CBGA.Component, ui.ComponentStack
     check @sourceController, ui.Controller
     event.originalEvent.dataTransfer.setData 'application/vnd-cbga-dnd', @_id
     event.originalEvent.dataTransfer.setData "application/vnd-cbga:#{@_id}", 'dnd'


this is how it should look...

this is how it should look...

--- ng-modules/travelScreen/travelScreen.coffee
+++ ng-modules/travelScreen/travelScreen.coffee
@@ -3,6 +3,7 @@
 Tile = require('./Tile.coffee')
 Sprite = require('./Sprite.coffee')
 Nodule = require('nodule')
+Location = require('./../Location.coffee')

 Randy = require('./ng-randy/ng-randy.coffee')

@@ -230,6 +231,8 @@
     $scope.$on('encounter', function(args){
         // on random encounter
         console.log('adding encounter:', args);
+        // TODO: wrap this in data.addLocation method which checks that no locations are too near each other
+        vm.data.locations.push(new Location("test. TODO: randy.getName", 7000, "TODO args.name?"))
         // TODO: place marker off screen & add to locations?
     });
 }]);


Don't submit beatmap_id for generalAll

Don't submit beatmap_id for generalAll

--- resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
@@ -156,7 +156,7 @@
       beatmap_discussion:
         message_type: e.currentTarget.dataset.type
         timestamp: @state.timestamp
-        beatmap_id: @props.currentBeatmap.id
+        beatmap_id: @props.currentBeatmap.id unless @props.mode == 'generalAll'
       beatmap_discussion_post:
         message: @state.message


Use hashes on configuration keys.

Use hashes on configuration keys.

--- tests-src/domain/services/RequestSigner.coffee
+++ tests-src/domain/services/RequestSigner.coffee
@@ -21,7 +21,7 @@
     fs.readFile TestHelper.path('configs/test-credentials.json'), encoding: 'utf8', (err, config) ->
       return done(err) if err
       c = JSON.parse config
-      credential = new Credential c.accessKey, c.secretKey, c.associateTag
+      credential = new Credential c['accessKey'], c['secretKey'], c['associateTag']
       done()

   it 'should sign requests properly', ->


sort healthchecks and taskUpdates

sort healthchecks and taskUpdates

--- SingularityUI/app/models/TaskHistory.coffee
+++ SingularityUI/app/models/TaskHistory.coffee
@@ -15,7 +15,8 @@
     initialize: ({ @taskId }) ->

     parse: (taskHistory) ->
-        _.sortBy taskHistory.taskUpdates, (t) -> t.timestamp
+        taskHistory.taskUpdates = _.sortBy taskHistory.taskUpdates, (t) -> t.timestamp
+        taskHistory.healthcheckResults = (_.sortBy taskHistory.healthcheckResults, (t) -> t.timestamp).reverse()

         taskHistory.task?.mesosTask?.executor?.command?.environment?.variables = _.sortBy taskHistory.task.mesosTask.executor.command.environment.variables, "name"


sitewide: content-panel transitions improved

--- client/app/MainApp/maincontroller/mainviews/contentpanel.coffee
+++ client/app/MainApp/maincontroller/mainviews/contentpanel.coffee
@@ -67,10 +67,8 @@
     @state = 'develop'

   adjustForSocial:->
-    @once 'transitionend', =>
-      offset = parseInt(@$().css("left"),10)
-      @setWidth @windowController.winWidth - offset - @chatMargin
-
+    offset = if @windowController.winWidth < 768 then 50 else 160
+    @setWidth @windowController.winWidth - offset - @chatMargin
     @setClass 'social'
     @state = 'social'


sitewide: profilelinkview viewappended removed

--- client/landing/app/CommonViews/linkviews/profilelinkview.coffee
+++ client/landing/app/CommonViews/linkviews/profilelinkview.coffee
@@ -33,11 +33,6 @@
     @$().attr "href", "/#{nickname}"  if nickname
     super

-  viewAppended: ->
-
-    super
-    @render()
-
   pistachio:->

     super "{{#(profile.firstName)+' '+#(profile.lastName)}}"


Update context_menu.js (thx Redmine)

Update context_menu.js (thx Redmine)

--- lib/assets/javascripts/datatables/factory.js.coffee
+++ lib/assets/javascripts/datatables/factory.js.coffee
@@ -169,6 +169,7 @@
 contextMenuShow = (event) ->
   mouse_x       = event.pageX
   mouse_y       = event.pageY
+  mouse_y_c     = event.clientY
   render_x      = mouse_x
   render_y      = mouse_y
   dims          = null
@@ -191,7 +192,7 @@
       menu_width = $('#context-menu').width()
       menu_height = $('#context-menu').height()
       max_width = mouse_x + 2 * menu_width
-      max_height = mouse_y + menu_height
+      max_height = mouse_y_c + menu_height

       ws = window_size()
       window_width = ws.width
@@ -207,7 +208,13 @@
       if max_height > window_height
         render_y -= menu_height
         $('#context-menu').addClass('reverse-y')
+        # adding class for submenu
+        if mouse_y_c < 325
+          $('#context-menu .folder').addClass('down')
       else
+        # adding class for submenu
+        if window_height - mouse_y_c < 345
+          $('#context-menu .folder').addClass('up')
         $('#context-menu').removeClass('reverse-y')

       render_x = 1 if render_x <= 0


Ensure re-render on sign-in

Ensure re-render on sign-in

--- core/app/backbone/views/facts/react_fact_opinionate_area.coffee
+++ core/app/backbone/views/facts/react_fact_opinionate_area.coffee
@@ -46,6 +46,8 @@
   displayName: 'FactOpinionateButton'
   changeOptions: 'add remove reset sort' + ' change'

+  mixins: [ UpdateOnSignInOrOutMixin ]
+
   render: ->
     model = @model()
     is_interested = model.is_current_user_interested()