Cleanup: rename cfg -> options.

Cleanup: rename cfg -> options.

--- core/app/backbone/factlink/tooltip_jq.coffee
+++ core/app/backbone/factlink/tooltip_jq.coffee
@@ -19,30 +19,30 @@

 #returns an object with a close method which when called
 #removes all open tooltips
-Backbone.Factlink.TooltipJQ = (cfg) ->
+Backbone.Factlink.TooltipJQ = (options) ->
   uid = ttCounter++
   uidStr = 'tooltipUid-' + uid
   instCounter = 0
   instances = {}

-  $el = cfg.$el
-  _.defaults cfg, defaults
+  $el = options.$el
+  _.defaults options, defaults

   openInstance = ($target) ->
     instId = 'tt' + instCounter++
     inTooltip = false
     inTarget = true

-    $tooltip = cfg.makeTooltip $el, $target
+    $tooltip = options.makeTooltip $el, $target
     $tooltip.hoverIntent
-      timeout: cfg.closingtimeout
+      timeout: options.closingtimeout
       over: -> inTooltip = true; check()
       out: -> inTooltip = false; check()

     check = -> remove() if !(inTarget || inTooltip)
     remove = ->
       delete instances[instId]
-      cfg.removeTooltip $el, $target, $tooltip
+      options.removeTooltip $el, $target, $tooltip
       $target.removeData(uidStr)

     instances[instId] =
@@ -60,8 +60,8 @@
       openInstance $target

   $el.hoverIntent
-    timeout: cfg.closingtimeout
-    selector: cfg.selector
+    timeout: options.closingtimeout
+    selector: options.selector
     over: hoverTarget true
     out: hoverTarget false


Use tmp.mkdir

Use tmp.mkdir

--- spec/project-spec.coffee
+++ spec/project-spec.coffee
@@ -270,7 +270,7 @@
             range: [[2, 6], [2, 11]]

       it "works on evil filenames", ->
-        temp = process.env['TEMP'] || process.env['TMPDIR']
+        temp = temp.mkdirSync("atom")
         target = path.join(temp, 'evil-files')

         if fs.exists(target)
@@ -278,7 +278,7 @@
             fs.unlinkSync(path.join(target, f))
           fs.rmdirSync target

-        fs.mkdirSync target
+        fs.mkdirSync(target)

         inputs = []
         if (isWindows)


fix little map problem in areas.js.coffee

--- app/assets/javascripts/areas.js.coffee
+++ app/assets/javascripts/areas.js.coffee
@@ -5,7 +5,7 @@
 $ ->
   if $('#map').length > 0
     map = new Map('map')
-    addDraw(map)
+    addDraw(map.map)

 addDraw = (map) ->
   drawnItems = new L.LayerGroup()
@@ -20,7 +20,6 @@
   map.addLayer(drawnItems)

 editableMap = (map, drawnItems) ->
-  @map = map
   @drawControl = new L.Control.Draw
     polyline: false
     circle: false


Use switch/when statement

Use switch/when statement

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -106,30 +106,33 @@
     $textarea = $(e.target)

     # Edit previous note when UP arrow is hit
-    if $textarea.val() is '' and e.which is 38
-      myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
-      if myLastNote.length
-        myLastNoteEditBtn = myLastNote.find('.js-note-edit')
-        myLastNoteEditBtn.trigger('click', [true, myLastNote])
-
-    # Cancel creating diff note or editing any note when ESCAPE is hit
-    if e.which is 27
-      discussionNoteForm = $textarea.closest('.js-discussion-note-form')
-      if discussionNoteForm.length
-        if $textarea.val() isnt ''
-          return unless confirm('Are you sure you want to cancel creating this comment?')
-
-        @removeDiscussionNoteForm(discussionNoteForm)
-        return
-
-      editNote = $textarea.closest(".note")
-      if editNote.length
-        originalText = $textarea.closest('form').data('original-note')
-        newText = $textarea.val()
-        if originalText isnt newText
-          return unless confirm('Are you sure you want to cancel editing this comment?')
-
-        @removeNoteEditForm(editNote)
+    switch e.which
+      when 38
+        return unless $textarea.val() is ''
+
+        myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
+        if myLastNote.length
+          myLastNoteEditBtn = myLastNote.find('.js-note-edit')
+          myLastNoteEditBtn.trigger('click', [true, myLastNote])
+
+      # Cancel creating diff note or editing any note when ESCAPE is hit
+      when 27
+        discussionNoteForm = $textarea.closest('.js-discussion-note-form')
+        if discussionNoteForm.length
+          if $textarea.val() isnt ''
+            return unless confirm('Are you sure you want to cancel creating this comment?')
+
+          @removeDiscussionNoteForm(discussionNoteForm)
+          return
+
+        editNote = $textarea.closest('.note')
+        if editNote.length
+          originalText = $textarea.closest('form').data('original-note')
+          newText = $textarea.val()
+          if originalText isnt newText
+            return unless confirm('Are you sure you want to cancel editing this comment?')
+
+          @removeNoteEditForm(editNote)

   isMetaKey = (e) ->
@@ -427,7 +430,7 @@
   ###
   cancelEdit: (e) =>
     e.preventDefault()
-    note = $(e.target).closest(".note")
+    note = $(e.target).closest('.note')
     @removeNoteEditForm(note)

   removeNoteEditForm: (note) ->


Adding new route for show item page

Adding new route for show item page

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -4,8 +4,13 @@

 #= require_tree ./pages

-@raCrudDemo.config(['$routeProvider', ($routeProvider) ->
+@raCrudDemo.config(
+  ['$routeProvider', ($routeProvider) ->
     $routeProvider
+      .when('/show/:item_id', {
+        templateUrl: '/assets/show.html',
+        controller: 'ItemViewCtrl'
+      })
       .otherwise({
         templateUrl: '/assets/home.html',
         controller: 'HomeCtrl'


Fix quote selector

Fix quote selector

--- app/assets/javascripts/views.js.coffee
+++ app/assets/javascripts/views.js.coffee
@@ -170,6 +170,7 @@
   class window.PostView extends CommonView
     constructor: (@post, @topic) ->
       @selector = $('#posts')
+      @post_id = "'#p#{@post.pid}'"
       @post.current = @post.creator_n == Blabbr.current_user.nickname
       @post.published = @post.state == 'published'
       @post.deleted = @post.state == 'deleted'
@@ -191,9 +192,10 @@
         $('#post_body').val($('#post_body').val() + "bq..:" + user + " " + content + " \n\np. ")

     events: ->
-      @selector.find('.bubble p, .bubble ul').bind 'mouseenter', @quoter
-      @selector.find('.bubble p, .bubble ul').bind 'mouseleave', @unquoter
-      @selector.find('.bubble p, .bubble ul').bind 'click', @insert_quote
+      @selector.find("#{@post_id} .bubble p, #{@post_id} .bubble ul").bind 'mouseenter', @quoter
+      @selector.find("#{@post_id} .bubble p, #{@post_id} .bubble ul").bind 'mouseleave', @unquoter
+      @selector.find("#{@post_id} .bubble p, #{@post_id} .bubble ul").bind 'click', @insert_quote
+
     yield: ->
       @selector.append @template(@post)


fixed calculated values js

fixed calculated values js

--- app/assets/javascripts/autocomplete.js.coffee
+++ app/assets/javascripts/autocomplete.js.coffee
@@ -75,12 +75,18 @@
   calcular_valores_factura: ->
     sum = 0
     sum_iva = 0
+    sum_12 = 0
+    sum_0 = 0
     $(".total_item").each ->
       sum += parseFloat($(this).text())
     $(".iva").each ->
       sum_iva += parseFloat($(this).text())
-    $(".subtotal_0").text sum.toFixed(2)
-    $(".subtotal_12").text sum.toFixed(2)
+      if $(this).text() != "0.0"
+        sum_12 += parseFloat($(this).closest(".fields").find("td:nth-child(5)").text())
+      else
+        sum_0 += parseFloat($(this).closest(".fields").find("td:nth-child(5)").text())
+    $(".subtotal_0").text sum_0.toFixed(2)
+    $(".subtotal_12").text sum_12.toFixed(2)
     $(".descuento_factura").text 0
     $(".iva_factura").text(sum_iva.toFixed(2));
     $(".total_factura").text((sum+sum_iva).toFixed(2));


Fix select all

Fix select all

--- app/assets/javascripts/championship.js.coffee
+++ app/assets/javascripts/championship.js.coffee
@@ -1,3 +1,3 @@
 $ ->
   $("#check_all").on "click", ->
-    $(this).parent("form").find(":checkbox").prop "checked", @checked
+    $(this).parents("form").find(":checkbox").prop "checked", @checked


タグの追加・削除のスクリプト。生成したURLにidを付けたリクエスト生成。 $.post(path, { label: ui.tagLabel })

--- app/assets/javascripts/staff/tag-it.js.coffee
+++ app/assets/javascripts/staff/tag-it.js.coffee
@@ -1,2 +1,13 @@
 $(document).on 'ready page:load', ->
-  $("#tag-it").tagit()
+  $("#tag-it").tagit(
+    afterTagAdded: (e, ui) ->
+      unless ui.duringInitialization
+        message_id = $("#tag-it").data('message-id')
+        path = window.path_for("tag_staff_message", { id: message_id })
+        $.post(path, { label: ui.tagLabel })
+    afterTagRemoved: (e, ui) ->
+      unless ui.duringInitialization
+        message_id = $("#tag-it").data('message-id')
+        path = window.path_for("tag_staff_message", { id: message_id })
+        $.ajax(type: 'DELETE', url: path, data: { label: ui.tagLabel })
+  )


implementation du deadlock vertical

implementation du deadlock vertical

--- app/assets/jax/models/deadlock_core.js.coffee
+++ app/assets/jax/models/deadlock_core.js.coffee
@@ -57,7 +57,7 @@
       escape = { up: false, down: false, left: false, right: false, goal: false }

       ###
-      # Test horizontal line
+      # Test horizontal lines
       ###

       # while cell is not wall
@@ -84,6 +84,37 @@
           if not @position_in_array(cell_pos, corner_deadlock_positions) and not @position_in_array(cell_pos, line_deadlock_positions)
             line_deadlock_positions.push( { m:cell_pos.m, n:cell_pos.n } )
           cell_pos.n = cell_pos.n + 1
+
+      ###
+      # Test vertical lines
+      ###
+
+      escape.goal = false
+
+      # while cell is not wall
+      cell_pos = { m:corner_pos.m, n:corner_pos.n }
+      cell = level.read_pos(cell_pos.m, cell_pos.n)
+      while cell != '#'
+        # If the cell on the left of the actual cell is not wall
+        if level.read_pos(cell_pos.m, cell_pos.n - 1) != '#'
+          escape.left = true
+        # If the cell on the right of the actual cell is not wall
+        if level.read_pos(cell_pos.m, cell_pos.n + 1) != '#'
+          escape.right = true
+        # If cell is goal
+        if cell == '.' or cell == '*' or cell == '+'
+          escape.goal = true
+
+        cell_pos.m = cell_pos.m + 1
+        cell = level.read_pos(cell_pos.m, cell_pos.n)
+
+      # mark vertical line if left or right is full of walls and has no goal
+      if (escape.left == false or escape.right == false) and escape.goal == false
+        cell_pos = { m:corner_pos.m, n:corner_pos.n }
+        while level.read_pos(cell_pos.m, cell_pos.n) != '#'
+          if not @position_in_array(cell_pos, corner_deadlock_positions) and not @position_in_array(cell_pos, line_deadlock_positions)
+            line_deadlock_positions.push( { m:cell_pos.m, n:cell_pos.n } )
+          cell_pos.m = cell_pos.m + 1

     return line_deadlock_positions.concat(corner_deadlock_positions)


check for assignment before ensuring visibility

fixes CNVS-45643

Change-Id: Ic4b5752975c1d211535f32aa108d89059c17750e
Reviewed-on: https://gerrit.instructure.com/187711
Reviewed-by: Jeremy Neander <20252aad5efdc666f083d82ab63ed0f9f72a043e@instructure.com>
Tested-by: Keith Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
Product-Review: Keith Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
QA-Review: Keith Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>

--- app/coffeescripts/gradezilla/Gradebook.coffee
+++ app/coffeescripts/gradezilla/Gradebook.coffee
@@ -113,7 +113,7 @@
   LatePolicyApplicator, { default: Button }, { default: IconSettingsSolid }, FlashAlert) ->

   ensureAssignmentVisibility = (assignment, submission) =>
-    if assignment.only_visible_to_overrides && !assignment.assignment_visibility.includes(submission.user_id)
+    if assignment?.only_visible_to_overrides && !assignment.assignment_visibility.includes(submission.user_id)
       assignment.assignment_visibility.push(submission.user_id)

   isAdmin = =>


:punch: :poop: :toilet:

:punch: :poop: :toilet:

--- app/coffee/modules/tasks/detail.coffee
+++ app/coffee/modules/tasks/detail.coffee
@@ -310,7 +310,7 @@
         link: link
         restrict: "EA"
         require: "ngModel"
-        template: tempalte
+        template: template
     }

 module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", TaskIsIocaineButtonDirective])


fix mistaken gear field spec

fix mistaken gear field spec

--- bokehjs/src/coffee/renderer/glyph/gear.coffee
+++ bokehjs/src/coffee/renderer/glyph/gear.coffee
@@ -123,9 +123,8 @@
 class Gear extends Glyph.Model
   default_view: GearView
   type: 'Gear'
-  distances: ['module']
-  angles: ['pressure_angle']
-  fields: ['angle', 'internal:bool', 'shaft_size', 'teeth']
+  angles: ['module']
+  fields: ['angle', 'internal:bool', 'pressure_angle', 'shaft_size', 'teeth']

   defaults: ->
     return _.extend {}, super(), {


Fix bug in draw legend method of text glyph

--- bokehjs/src/coffee/renderer/glyph/text.coffee
+++ bokehjs/src/coffee/renderer/glyph/text.coffee
@@ -1,7 +1,11 @@
 _ = require "underscore"
 Glyph = require "./glyph"
+properties = require "../../common/properties"

 class TextView extends Glyph.View
+  initialize: (options) ->
+    super(options)
+    @text_props = new properties.Text({obj:@model, prefix: ''})

   _index_data: () ->
     @_xy_index()
@@ -21,15 +25,10 @@

   draw_legend: (ctx, x1, x2, y1, y2) ->
     ctx.save()
-    reference_point = @get_reference_point()
-    if reference_point?
-      glyph_settings = reference_point
-    else
-      glyph_settings = @props
-    text_props = @visuals.text
-    text_props.set(ctx, glyph_settings)
+    @text_props.set_value(ctx)
     # override some features so we fit inside the legend
-    ctx.font = text_props.font(12)
+    ctx.font = @text_props.font_value()
+    ctx.font = ctx.font.replace(/\b[\d\.\d]+[\w]+\b/, '10pt')
     ctx.textAlign = "right"
     ctx.textBaseline = "middle"
     ctx.fillText("txt", x2, (y1+y2)/2)
@@ -52,4 +51,4 @@

 module.exports =
   Model: Text
-  View: TextView+  View: TextView


activity: deprecate `ActivityLikeLink::state` property

--- client/Social/Activity/views/likelink.coffee
+++ client/Social/Activity/views/likelink.coffee
@@ -3,8 +3,6 @@
   constructor: (options = {}, data) ->

     options.cssClass = KD.utils.curry "action-link like-link", options.cssClass
-
-    @state = data.interactions.like.isInteracted

     super options, data

@@ -12,17 +10,18 @@
   click: ->

     {id}           = data = @getData()
+    {isInteracted} = data.interactions.like
     {like, unlike} = KD.singletons.socialapi.message

-    fn = if @state then unlike else like
-    fn {id}, @bound 'toggleState'
+    fn = if isInteracted then unlike else like
+    fn {id}, (err) ->
+
+      return @showError err  if err
+
+      @update()

-  toggleState: (err) ->
-
-    return @showError err  if err
-
-    @state = not @state
+  update: ->

     @setTemplate @pistachio()


api_host includes protocol

api_host includes protocol

--- client/skr/models/mixins/PrintSupport.coffee
+++ client/skr/models/mixins/PrintSupport.coffee
@@ -4,6 +4,6 @@
         if @printFormIdentifier then @printFormIdentifier() else @modelTypeIdentifier()

     pdfDownloadUrl: ->
-        "//#{Lanes.config.api_host}#{Lanes.config.api_path}/skr/print/#{@_mixinPrintFormIdentifier()}/#{@hash_code}.pdf"
+        "#{Lanes.config.api_host}#{Lanes.config.api_path}/skr/print/#{@_mixinPrintFormIdentifier()}/#{@hash_code}.pdf"

 }


Tests: IDE: Layout: Fixed undoSplitPanes test.

--- client/test/lib/helpers/layouthelpers.coffee
+++ client/test/lib/helpers/layouthelpers.coffee
@@ -1,7 +1,7 @@
 helpers = require './helpers.js'
 assert  = require 'assert'

-paneSelector      = '.pane-wrapper .panel-1 .application-tab-handle-holder'
+paneSelector      = '.panel-1 .application-tab-handle-holder'
 plusSelector      = paneSelector + ' .visible-tab-handle.plus'
 undoSplitSelector = paneSelector + ' .general-handles .close-handle'
 newPaneSelector   = '.kdsplitcomboview .kdsplitview-panel.panel-1 .application-tab-handle-holder'
@@ -13,7 +13,7 @@

     browser
       .pause                   5000
-      .waitForElementVisible   '.panel-1 .panel-0 .application-tab-handle-holder', 20000
+      .waitForElementVisible   paneSelector, 20000
       .moveToElement           plusSelector, 60, 17
       .click                   plusSelector
       .waitForElementVisible   '.context-list-wrapper', 20000
@@ -25,15 +25,13 @@
     browser
       .waitForElementVisible '.panel-1', 20000
       .elements 'css selector', '.panel-1', (result) =>
-        assert.equal result.value.length, 2
+        oldLength = result.value.length

         browser
           .waitForElementVisible    paneSelector, 20000
           .click                    undoSplitSelector
-          .waitForElementNotPresent '.pane-wrapper .kdsplitview-panel.panel-1 .application-tab-handle-holder', 20000
-
-        browser.elements 'css selector', newPaneSelector, (result) =>
-          assert.equal result.value.length, 1
+          .elements 'css selector', newPaneSelector, (result) =>
+            assert.equal result.value.length, oldLength - 1

   split: (browser, direction) ->


Fetching variable value

Fetching variable value

--- lib/views/cloud-variables-and-functions-view.coffee
+++ lib/views/cloud-variables-and-functions-view.coffee
@@ -2,6 +2,7 @@
 $ = null
 $$ = null
 SettingsHelper = null
+ApiClient = null

 module.exports =
 class CloudVariablesAndFunctions extends View
@@ -17,6 +18,10 @@
   initialize: (serializeState) ->
     {$, $$} = require 'atom'
     SettingsHelper = require '../utils/settings-helper'
+    ApiClient = require '../vendor/ApiClient'
+
+    @client = null
+
     # TODO: Hook on changing core/logging out
     @listVariables()
     @listFunctions()
@@ -55,19 +60,36 @@
       for variable in Object.keys(variables)
         row = $$ ->
           @table =>
-            @tr =>
+            @tr 'data-id': variable, =>
               @td variable
               @td variables[variable]
               @td class: 'loading'
               @td =>
                 @button class: 'btn btn-sm icon icon-sync'

+        row.find('button').on 'click', (event) =>
+          @refreshVariable $(event.target).parent().parent().attr('data-id')
+
         table.find('tbody').append row.find('tbody >')

       @variables.append table
+
+      # Get initial values
+      for variable in Object.keys(variables)
+        @refreshVariable variable

   listFunctions: ->
     @functions.empty()
     @functions.append $$ ->
       @ul class: 'background-message', =>
         @li 'No functions registered'
+
+  refreshVariable: (variableName) ->
+    @client ?= new ApiClient SettingsHelper.get('apiUrl'), SettingsHelper.get('access_token')
+
+    cell = @find('#spark-ide-cloud-variables [data-id=' + variableName + '] td:eq(2)')
+    cell.addClass 'loading'
+    promise = @client.getVariable SettingsHelper.get('current_core'), variableName
+    promise.done (e) =>
+      cell.removeClass 'loading'
+      cell.text e.result


Fixed famous undefined reference. Thanks to @gadicc and @PEM--

--- popupmodal.coffee
+++ popupmodal.coffee
@@ -16,18 +16,6 @@
   height: e[a + "Height"]

 class @Popups
-  # You can override the default popup animation here. It is an exact replica
-  # of famous.views.Lightbox options parameter
-  _defaultLightbox:
-    inTransform: famous.core.Transform.translate 0,500,0
-    outTransform: famous.core.Transform.translate 0,500,0
-    inTransition:
-      duration: 1000
-      curve: famous.transitions.Easing.outElastic
-    outTransition:
-      duration: 2000
-      curve: famous.transitions.Easing.inOutQuad
-
   # This can be called anytime and will firstly be called during popup init
   constructor: (opts = {}) ->
     opts.translate = [0,0,999]
@@ -93,6 +81,19 @@
     _popups.set p
     opts.id

+# You can override the default popup animation here. It is an exact replica
+# of famous.views.Lightbox options parameter
+FView.ready ->
+  @Popups::_defaultLightbox =
+    inTransform: famous.core.Transform.translate 0,500,0
+    outTransform: famous.core.Transform.translate 0,500,0
+    inTransition:
+      duration: 1000
+      curve: famous.transitions.Easing.outElastic
+    outTransition:
+      duration: 2000
+      curve: famous.transitions.Easing.inOutQuad
+
 Template.modal_popup.helpers
   backdrop: ->
     if _popups.get().length > 0


fix: ps 中长用户名被省略

fix: ps 中长用户名被省略

--- plugin/linux/monitor.coffee
+++ plugin/linux/monitor.coffee
@@ -19,8 +19,9 @@
   setInterval exports.monitoring, config.plugins.linux.monitor_cycle

 exports.loadPasswd = (callback) ->
-  app.redis.get 'rp:passwd_cache', (err, passwd_cache) ->
-    if passwd_cache
+  app.redis.get 'rp:passwd_cache', (err, result) ->
+    if result
+      passwd_cache = JSON.parse result
       callback()
     else
       fs.readFile '/etc/passwd', (err, content) ->
@@ -43,11 +44,11 @@
       callback JSON.parse plist
     else
       exports.loadPasswd ->
-        child_process.exec "ps awufx", (err, stdout, stderr) ->
+        child_process.exec "ps awufxn", (err, stdout, stderr) ->
           plist = stdout.split('\n')[1...-1]

           plist = _.map plist, (item) ->
-            rx = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/
+            rx = /^\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/
             result = rx.exec item
             return {
               user: do ->


fix dynamic clock state

fix dynamic clock state

--- models/mixins/clock.coffee
+++ models/mixins/clock.coffee
@@ -18,7 +18,7 @@

   updateState: ->
     @set('clockState', (
-      if @get('auction_state') == 'closed'
+      if moment().isAfter(@get 'offsetEndAtMoment') or @get('auction_state') == 'closed'
         'closed'
       else if @get('live_start_at') and moment().isBefore(@get 'offsetLiveStartAtMoment')
         'live'


Unassign machines when deleting a datacenter

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -434,6 +434,30 @@

         on_submit: ->
             super
+            @machines_to_delete = {}
+            for machine in machines.models
+                if machine.get('datacenter_uuid') is @datacenter.id
+                    @machines_to_delete[machine.get('id')] =
+                        datacenter_uuid: universe_datacenter.get('id')
+
+            # We first unassign machines
+            @unassign_machines_in_datacenter()
+
+        unassign_machines_in_datacenter: =>
+            $.ajax
+                url: "/ajax/semilattice/machines"
+                type: 'POST'
+                data: JSON.stringify(@machines_to_delete)
+                contentType: 'application/json'
+                success: @delete_datacenter
+                error: @on_error
+
+        delete_datacenter: =>
+            # The first call to unassign the machines was a success, so we can change their datacenter to universe
+            datacenters.remove(@datacenter.id)
+            for machine_id of @machines_to_delete
+                machines.get(machine_id).set 'datacenter_uuid', universe_datacenter.get('id')
+
             $.ajax
                 url: "/ajax/semilattice/datacenters/#{@datacenter.id}"
                 type: 'DELETE'
@@ -441,7 +465,6 @@
                 success: @on_success
                 error: @on_error

-
         on_success_with_error: =>
             @.$('.error_answer').html @template_remove_error

@@ -459,7 +482,7 @@

             super

-            datacenters.remove(@datacenter.id)
+
             $('#user-alert-space').html @alert_tmpl
                 name: @datacenter.get('name')


add some cats

add some cats

--- src/scripts/controllers/redditelly.coffee
+++ src/scripts/controllers/redditelly.coffee
@@ -38,6 +38,12 @@
             'nba'
         ]
     }, {
+        name: 'cats'
+        list: [
+            'catvideos'
+            'catpranks'
+        ]
+    }, {
         name: 'games'
         list: [
             'gaming'


redefined how shadow: offset: is interpreted

--- src/art/engine/elements/fillable_base.coffee
+++ src/art/engine/elements/fillable_base.coffee
@@ -42,8 +42,8 @@
       if offset = shadow.offset
         {x, y} = offset.layout _currentSize
         merge shadow,
-          offsetX: x - _currentSize.x / 2
-          offsetY: y - _currentSize.y / 2
+          offsetX: x
+          offsetY: y
       else
         merge
           offsetX: 0


Disable the old sign in authentication

Disable the old sign in authentication

--- app/assets/javascripts/lib/core/base.coffee
+++ app/assets/javascripts/lib/core/base.coffee
@@ -3,10 +3,7 @@
   class Base

     constructor: (args={})->
-      if LocalStore.getCookie('lp-new-sign-in')
-        @authenticateUser()
-      else
-        @oldAuthenticateUser()
+      @authenticateUser()

       @showUserBasket()
       @initAds() unless args.secure
@@ -21,7 +18,7 @@
         url: @auth.getNewStatusUrl()
         dataType: "json"
         error: =>
-          @oldAuthenticateUser(@auth)
+          @auth.update()
         success: (user) =>
           # The data returned is defined in community at: app/controllers/users_controller.rb@status
           window.lp.user = user
@@ -34,11 +31,6 @@
           window.referer = "null";

           @auth.update()
-
-    oldAuthenticateUser: (auth) ->
-      @auth = auth || new Authenticator()
-      AssetFetch.get "https://secure.lonelyplanet.com/sign-in/status", () =>
-        @auth.update()

     initAds: ->


Unused variables

Unused variables

--- app/assets/javascripts/ljax_rails.js.coffee
+++ app/assets/javascripts/ljax_rails.js.coffee
@@ -6,9 +6,9 @@
       url: $(container).data('remote-url')
       cache: false
       headers: {'X-LJAX': 'true', 'X-LJAX-Container': container.id, 'X-LJAX-Partial': $(container).data('ljax-partial')}
-      success: (data, status, xhr) ->
+      success: (data) ->
         $(container).replaceWith(data).trigger('ljax:success')
-      error: (data, status, xhr) ->
+      error: ->
         $(container).trigger('ljax:error')
       complete: ->
         $(container).trigger('ljax:complete')


Handle multiple responses properly

Handle multiple responses properly

--- app/assets/javascripts/ljax_rails.js.coffee
+++ app/assets/javascripts/ljax_rails.js.coffee
@@ -1,5 +1,5 @@
 $ ->
-  for container in $('.ljax-container')
+  $('.ljax-container').each (index, container) =>
     $.ajax
       type: 'GET'
       dataType: 'text'


make expanded nav default using js

make expanded nav default using js

--- app/assets/javascripts/ui_options.js.coffee
+++ app/assets/javascripts/ui_options.js.coffee
@@ -7,12 +7,12 @@

   build_from_localstorage: ->
     if @supports_html5_storage()
-      if localStorage["collapseNav"] == "false"
-        $('body').addClass('nav-open')
+      if localStorage["collapseNav"] == "true"
+        $('body').removeClass('nav-open')
       else
-        $('body').removeClass('nav-open')
+        $('body').addClass('nav-open') # default

   change_localstorage: ->
     if @supports_html5_storage()
-      localStorage["collapseNav"] = true # default
-      localStorage["collapseNav"] = false if $('body').hasClass('nav-open')+      localStorage["collapseNav"] = false # default
+      localStorage["collapseNav"] = true if !$('body').hasClass('nav-open')


Spy on Develop::installDependencies

Spy on Develop::installDependencies

--- spec/develop-spec.coffee
+++ spec/develop-spec.coffee
@@ -43,6 +43,8 @@
       spyOn(Develop.prototype, "getRepositoryUrl").andCallFake (packageName, callback) ->
         repoUrl = path.join(__dirname, 'fixtures', 'repo.git')
         callback(null, repoUrl)
+      spyOn(Develop.prototype, "installDependencies").andCallFake (packageDirectory, options) ->
+        @linkPackage(packageDirectory, options)

       callback = jasmine.createSpy('callback')
       apm.run(['develop', "fake-package"], callback)


Update pangolin.coffee

Update pangolin.coffee

--- app/lib/field-guide-content/pangolin.coffee
+++ app/lib/field-guide-content/pangolin.coffee
@@ -3,7 +3,7 @@
     The pangolin is distinguished from other mammals by its protective layer of brown horny scales that overlap like roof shingles on the upper body, tail, and outer limbs. It has a tubular head and short, powerful legs with strong curved claws well-adapted for uncovering ants and termites. Other adaptations that make them experts at eating ants and termites are their acute sense of smell that helps them sniff out most of their prey, thick eyelids that protect their eyes from insect bites, and a long, tapering tongue used to lap up prey.
   '''

-  scientificName: '(Smutsia temminckii)'
+  scientificName: 'Smutsia temminckii'
   mainImage: 'assets/fieldguide-content/mammals/pangolin/pangolin-feature.jpg'

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


What is this, Ruby?

What is this, Ruby?

--- spec/install-spec.coffee
+++ spec/install-spec.coffee
@@ -322,7 +322,7 @@
         runs ->
           expect(callback.mostRecentCall.args[0]).toBeTruthy()

-    describe '#getNormalizedGitUrls', ->
+    describe '::getNormalizedGitUrls', ->
       it 'normalizes https:// urls', ->
         url = "https://github.com/user/repo.git"
         urls = new Install().getNormalizedGitUrls url
@@ -343,7 +343,7 @@
         urls = new Install().getNormalizedGitUrls url
         expect(urls).toEqual ["https://github.com/user/repo.git", "git+ssh://git@github.com/user/repo.git"]

-    describe '#cloneFirstValidGitUrl', ->
+    describe '::cloneFirstValidGitUrl', ->
       describe 'when cloning a URL fails', ->
         install = null
         urls = ["url1", "url2", "url3"]


Specs moved to response

Specs moved to response

--- spec/request-spec.coffee
+++ spec/request-spec.coffee
@@ -83,34 +83,3 @@
       request.send()

       expect(beforeCallback).toHaveBeenCalled()
-
-    it "responds with a JSON object if the response is JSON", ->
-      jasmine.Ajax.stubRequest("/api/v1/users.json").andReturn
-        status: 200
-        responseText: JSON.stringify(user: { id: 1, firstName: "Philip" })
-
-
-      completeCallback = jasmine.createSpy("complete")
-
-      request = new Request
-        url: "/api/v1/users.json"
-        complete: completeCallback
-
-      request.send()
-
-      expect(request.response()).toEqual jasmine.any(Object)
-
-    it "response with the Text if the response isn't parseable as JSON", ->
-      jasmine.Ajax.stubRequest("/api/v1/users.json").andReturn
-        status: 200
-        responseText: "<errors>Amazing</errors>"
-
-      completeCallback = jasmine.createSpy("complete")
-
-      request = new Request
-        url: "/api/v1/users.json"
-        complete: completeCallback
-
-      request.send()
-
-      expect(request.response()).toEqual jasmine.any(String)


MapLinkView InfoWindow opens after one second

--- src/main/webapp/lib/views/MapLinkView.coffee
+++ src/main/webapp/lib/views/MapLinkView.coffee
@@ -101,7 +101,7 @@
       if(not @_isInfoWindowOpen())
         @iWindow.setContent(@getLinkRollOverInfo())
         @iWindow.setPosition(e.latLng)
-        trackMouseoverTimeout = setTimeout((() => @iWindow.open($a.map)), 2000)
+        trackMouseoverTimeout = setTimeout((() => @iWindow.open($a.map)), 1000)
         google.maps.event.addListenerOnce(@link, 'mouseout', (e) =>
           window.clearTimeout(trackMouseoverTimeout);
           @iWindow.close()


use new api to allow getting attr and text directly from jquery

--- src/dynamictemplate-jquery.coffee
+++ src/dynamictemplate-jquery.coffee
@@ -38,6 +38,14 @@
         tpl.jquery = tpl.xml._jquery = $(tpl.xml._children)
         delete tpl.xml._children

+    old_query = tpl.xml.query
+    tpl.xml.query = (type, tag, key) ->
+        return old_query.call(this, type, tag, key) unless tag._jquery?
+        if type is 'attr'
+            tag._jquery.attr(key)
+        else if type is 'text'
+            tag._jquery.text()
+
     return tpl

 # exports


Moved Output to CUI.Output.

Moved Output to CUI.Output.

--- src/elements/Output/Output.coffee
+++ src/elements/Output/Output.coffee
@@ -5,7 +5,7 @@
  * https://github.com/programmfabrik/coffeescript-ui, http://www.coffeescript-ui.org
 ###

-class Output extends DataFieldInput
+class CUI.Output extends CUI.DataFieldInput
 	initOpts: ->
 		super()
 		@addOpts
@@ -96,4 +96,6 @@
 				onClick: =>
 					@setValue(null, no_trigger: false)
 			@append(@__deleteBtn)
-		@+		@
+
+Output = CUI.Output


Write tests for telemetry consent and reorganize test structure

--- spec/welcome-spec.coffee
+++ spec/welcome-spec.coffee
@@ -6,109 +6,128 @@

   beforeEach ->
     spyOn(atom.workspace, 'open').andCallThrough()
-    atom.config.set('core.telemetryConsent', 'no')

-    waitsForPromise ->
-      atom.packages.activatePackage("welcome")
+  describe "when `core.telemetryConsent` is 'undecided'", ->
+    beforeEach ->
+      atom.config.set('core.telemetryConsent', 'undecided')

-    waitsFor ->
-      atom.workspace.open.calls.length is 2
+      waitsForPromise ->
+        atom.packages.activatePackage("welcome")

-  describe "when activated for the first time", ->
-    it "shows the welcome panes", ->
+      waitsFor ->
+        atom.workspace.open.calls.length is 3
+
+    it "opens the telemetry consent pane and the welcome pane", ->
       panes = atom.workspace.getPanes()
       expect(panes).toHaveLength 2
-      expect(panes[0].getItems()[0].getTitle()).toBe 'Welcome'
-      expect(panes[1].getItems()[0].getTitle()).toBe 'Welcome Guide'
+      expect(panes[0].getItems()[0].getTitle()).toBe 'Telemetry Consent'
+      expect(panes[0].getItems()[1].getTitle()).toBe 'Welcome'

-  describe "the welcome:show command", ->
-    workspaceElement = null
+  describe "when `core.telemetryConsent` is not `undecided`", ->
+    beforeEach ->
+      atom.config.set('core.telemetryConsent', 'no')

-    beforeEach ->
-      workspaceElement = atom.views.getView(atom.workspace)
-
-    it "shows the welcome buffer", ->
-      atom.workspace.getPanes().map (pane) -> pane.destroy()
-      expect(atom.workspace.getActivePaneItem()).toBeUndefined()
-      atom.commands.dispatch(workspaceElement, 'welcome:show')
+      waitsForPromise ->
+        atom.packages.activatePackage("welcome")

       waitsFor ->
-        atom.workspace.getActivePaneItem()
+        atom.workspace.open.calls.length is 2

-      runs ->
+    describe "when activated for the first time", ->
+      it "shows the welcome panes", ->
         panes = atom.workspace.getPanes()
         expect(panes).toHaveLength 2
         expect(panes[0].getItems()[0].getTitle()).toBe 'Welcome'
+        expect(panes[1].getItems()[0].getTitle()).toBe 'Welcome Guide'

-  describe "deserializing the pane items", ->
-    [panes, guideView, welcomeView] = []
-    beforeEach ->
-      panes = atom.workspace.getPanes()
-      welcomeView = panes[0].getItems()[0]
-      guideView = panes[1].getItems()[0]
+    describe "the welcome:show command", ->
+      workspaceElement = null

-    describe "when GuideView is deserialized", ->
-      it "deserializes with no state", ->
-        {deserializer, uri} = guideView.serialize()
-        newGuideView = atom.deserializers.deserialize({deserializer, uri})
+      beforeEach ->
+        workspaceElement = atom.views.getView(atom.workspace)

-      it "remembers open sections", ->
-        guideView.find("details[data-section=\"snippets\"]").attr('open', 'open')
-        guideView.find("details[data-section=\"init-script\"]").attr('open', 'open')
-        serialized = guideView.serialize()
+      it "shows the welcome buffer", ->
+        atom.workspace.getPanes().map (pane) -> pane.destroy()
+        expect(atom.workspace.getActivePaneItem()).toBeUndefined()
+        atom.commands.dispatch(workspaceElement, 'welcome:show')

-        expect(serialized.openSections).toEqual ['init-script', 'snippets']
+        waitsFor ->
+          atom.workspace.getActivePaneItem()

-        newGuideView = atom.deserializers.deserialize(serialized)
+        runs ->
+          panes = atom.workspace.getPanes()
+          expect(panes).toHaveLength 2
+          expect(panes[0].getItems()[0].getTitle()).toBe 'Welcome'

-        expect(newGuideView.find("details[data-section=\"packages\"]")).not.toHaveAttr 'open'
-        expect(newGuideView.find("details[data-section=\"snippets\"]")).toHaveAttr 'open'
-        expect(newGuideView.find("details[data-section=\"init-script\"]")).toHaveAttr 'open'
+    describe "deserializing the pane items", ->
+      [panes, guideView, welcomeView] = []
+      beforeEach ->
+        panes = atom.workspace.getPanes()
+        welcomeView = panes[0].getItems()[0]
+        guideView = panes[1].getItems()[0]

-  describe "reporting events", ->
-    [panes, guideView, welcomeView] = []
-    beforeEach ->
-      panes = atom.workspace.getPanes()
-      welcomeView = panes[0].getItems()[0]
-      guideView = panes[1].getItems()[0]
+      describe "when GuideView is deserialized", ->
+        it "deserializes with no state", ->
+          {deserializer, uri} = guideView.serialize()
+          newGuideView = atom.deserializers.deserialize({deserializer, uri})

-      spyOn(Reporter, 'sendEvent')
+        it "remembers open sections", ->
+          guideView.find("details[data-section=\"snippets\"]").attr('open', 'open')
+          guideView.find("details[data-section=\"init-script\"]").attr('open', 'open')
+          serialized = guideView.serialize()

-    describe "GuideView events", ->
-      it "captures expand and collapse events", ->
-        expect(Reporter.sendEvent).not.toHaveBeenCalled()
+          expect(serialized.openSections).toEqual ['init-script', 'snippets']

-        guideView.find("details[data-section=\"packages\"] summary").click()
-        expect(Reporter.sendEvent).toHaveBeenCalledWith('expand-packages-section')
-        expect(Reporter.sendEvent).not.toHaveBeenCalledWith('collapse-packages-section')
+          newGuideView = atom.deserializers.deserialize(serialized)

-        guideView.find("details[data-section=\"packages\"]").attr('open', true)
-        guideView.find("details[data-section=\"packages\"] summary").click()
-        expect(Reporter.sendEvent).toHaveBeenCalledWith('collapse-packages-section')
+          expect(newGuideView.find("details[data-section=\"packages\"]")).not.toHaveAttr 'open'
+          expect(newGuideView.find("details[data-section=\"snippets\"]")).toHaveAttr 'open'
+          expect(newGuideView.find("details[data-section=\"init-script\"]")).toHaveAttr 'open'

-      it "captures button events", ->
-        spyOn(atom.commands, 'dispatch')
-        for detailElement in guideView.find('details')
-          detailElement = $(detailElement)
-          sectionName = detailElement.attr('data-section')
-          eventName = "clicked-#{sectionName}-cta"
-          primaryButton = detailElement.find('.btn-primary')
-          if primaryButton.length
-            expect(Reporter.sendEvent).not.toHaveBeenCalledWith(eventName)
-            primaryButton.click()
-            expect(Reporter.sendEvent).toHaveBeenCalledWith(eventName)
+    describe "reporting events", ->
+      [panes, guideView, welcomeView] = []
+      beforeEach ->
+        panes = atom.workspace.getPanes()
+        welcomeView = panes[0].getItems()[0]
+        guideView = panes[1].getItems()[0]

-  describe "when the reporter changes", ->
-    it "sends all queued events", ->
-      reporter1 = sendEvent: jasmine.createSpy('sendEvent')
-      reporter2 = sendEvent: jasmine.createSpy('sendEvent')
+        spyOn(Reporter, 'sendEvent')

-      Reporter.sendEvent('foo', 'bar', 'baz')
-      Reporter.sendEvent('foo2', 'bar2', 'baz2')
-      Reporter.setReporter(reporter1)
+      describe "GuideView events", ->
+        it "captures expand and collapse events", ->
+          expect(Reporter.sendEvent).not.toHaveBeenCalled()

-      expect(reporter1.sendEvent).toHaveBeenCalledWith 'welcome-v1', 'foo', 'bar', 'baz'
-      expect(reporter1.sendEvent).toHaveBeenCalledWith 'welcome-v1', 'foo2', 'bar2', 'baz2'
+          guideView.find("details[data-section=\"packages\"] summary").click()
+          expect(Reporter.sendEvent).toHaveBeenCalledWith('expand-packages-section')
+          expect(Reporter.sendEvent).not.toHaveBeenCalledWith('collapse-packages-section')

-      Reporter.setReporter(reporter2)
-      expect(reporter2.sendEvent.callCount).toBe 0
+          guideView.find("details[data-section=\"packages\"]").attr('open', true)
+          guideView.find("details[data-section=\"packages\"] summary").click()
+          expect(Reporter.sendEvent).toHaveBeenCalledWith('collapse-packages-section')
+
+        it "captures button events", ->
+          spyOn(atom.commands, 'dispatch')
+          for detailElement in guideView.find('details')
+            detailElement = $(detailElement)
+            sectionName = detailElement.attr('data-section')
+            eventName = "clicked-#{sectionName}-cta"
+            primaryButton = detailElement.find('.btn-primary')
+            if primaryButton.length
+              expect(Reporter.sendEvent).not.toHaveBeenCalledWith(eventName)
+              primaryButton.click()
+              expect(Reporter.sendEvent).toHaveBeenCalledWith(eventName)
+
+    describe "when the reporter changes", ->
+      it "sends all queued events", ->
+        reporter1 = sendEvent: jasmine.createSpy('sendEvent')
+        reporter2 = sendEvent: jasmine.createSpy('sendEvent')
+
+        Reporter.sendEvent('foo', 'bar', 'baz')
+        Reporter.sendEvent('foo2', 'bar2', 'baz2')
+        Reporter.setReporter(reporter1)
+
+        expect(reporter1.sendEvent).toHaveBeenCalledWith 'welcome-v1', 'foo', 'bar', 'baz'
+        expect(reporter1.sendEvent).toHaveBeenCalledWith 'welcome-v1', 'foo2', 'bar2', 'baz2'
+
+        Reporter.setReporter(reporter2)
+        expect(reporter2.sendEvent.callCount).toBe 0


tweak priorities

tweak priorities

--- models/team.coffee
+++ models/team.coffee
@@ -253,9 +253,11 @@
           contestant: scores.contestant_count
           voter: scores.popularity_count

+        judge_count_priority = 100.0/((scores.judge_count + 1) * Math.log(scores.judge_count + Math.E))
+        contestant_count_priority = 100.0/((scores.contestant_count + 1) * Math.log(scores.contestant_count + Math.E))
         _.extend team.votePriorities,
-          judge: (50.0/(scores.judge_count + 1)) + scores.overall + (10 * Math.random())
-          contestant: (50.0/(scores.contestant_count + 1)) + scores.overall + (10 * Math.random())
+          judge: judge_count_priority + scores.overall + (15 * Math.random())
+          contestant: contestant_count_priority + scores.overall + (15 * Math.random())

         scores.random = Math.random()
         scores.team_size = team.peopleIds.length


Predicate for account detection

Predicate for account detection

--- models/user.coffee
+++ models/user.coffee
@@ -51,6 +51,10 @@
   isRecentlyRegistered: ->
     @__isRecentlyRegistered__

+  # Be sure to fetch this before calling
+  isWithAccount: ->
+    @related().account.id?
+
   @instantiate: (attributes = {}) ->
     CurrentUser = require './current_user.coffee'
     LoggedOutUser = require './logged_out_user.coffee'


prevent commands from fading when first loaded

--- app/assets/javascripts/presets.js.coffee
+++ app/assets/javascripts/presets.js.coffee
@@ -141,11 +141,11 @@
       when 'delete_object'
         addRemoveObjectConfiguration(container_selector, c['name'], c['object'], false)
       when 'custom_command_value'
-        addCustomCommandValueConfiguration(container_selector, c['command'], c['value'])
+        addCustomCommandValueConfiguration(container_selector, c['command'], c['value'], false)
       when 'custom_command_age'
-        addCustomCommandAgeConfiguration(container_selector, c['command'], c['age'])
+        addCustomCommandAgeConfiguration(container_selector, c['command'], c['age'], false)
       when 'software_version'
-        addSoftwareVersionConfiguration(container_selector, c['software_version'])
+        addSoftwareVersionConfiguration(container_selector, c['software_version'], false)

   popup = """
      +


Failed reconnection fix - take 2.

Failed reconnection fix - take 2.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -382,7 +382,7 @@
         faye_url = '/live'
         faye_url = 'http://localhost:3001/live' if document.location.hostname == "localhost" or document.location.hostname == "127.0.0.1"
         window.faye = new Faye.Client(faye_url, timeout: 60)
-        window.faye.disable("websocket")
+        window.faye.disable("eventsource")

         window.pings_missed = 0
         window.faye.subscribe("/ping", (message) =>


Account For and Fill in Missing Values on Axis Points

--- app/classifier/world_wide_telescope.cjsx
+++ app/classifier/world_wide_telescope.cjsx
@@ -104,10 +104,25 @@

     makeStarCoord = if @starChart.coordinateSystem() == StarChart.EQUATORIAL then StarCoord.fromRaDec else StarCoord.fromGlatGlon
     xAxisDec = if @starChart.xAxis.unit == Axis.DEC || @starChart.xAxis.unit == Axis.GLAT then true else false
+    @fullValues xRange
+    @fullValues yRange
     @coordCorners = [
       makeStarCoord(xRange[0].value, yRange[0].value, xAxisDec), makeStarCoord(xRange[1].value, yRange[0].value, xAxisDec),
       makeStarCoord(xRange[1].value, yRange[1].value, xAxisDec), makeStarCoord(xRange[0].value, yRange[1].value, xAxisDec)
     ]
+
+  fullValues: (ranges) ->
+    firstValue = ranges[0].value.match(/(-)?\d+(?:\.\d+)?/g)
+    secondValue = ranges[1]?.value.match(/(-)?\d+(?:\.\d+)?/g)
+    difference = Math.abs(firstValue.length - secondValue?.length)
+    if difference > 0 && secondValue
+      longerNumber = if firstValue.length > secondValue.length then firstValue else secondValue
+      shorterNumber = if secondValue.length < firstValue.length then secondValue else firstValue
+      index = 0
+      until longerNumber.length == shorterNumber.length
+        shorterNumber.splice(index, 0, longerNumber[index])
+        index = index + 1
+      [ranges[0].value, ranges[1].value] = [firstValue.join(" "), secondValue.join(" ")]

   scale: ->
     [star1, star2] = [ @coordCorners[0], @coordCorners[2] ]


Fix tasks #1231 and 1232: Add better taskboard and taiga-events integration

--- app/coffee/modules/taskboard/main.coffee
+++ app/coffee/modules/taskboard/main.coffee
@@ -88,6 +88,12 @@
         @events.subscribe @scope, routingKey, (message) =>
             @.loadTaskboard()

+        routingKey1 = "changes.project.#{@scope.projectId}.userstories"
+        @events.subscribe @scope, routingKey1, (message) =>
+            @.refreshTagsColors()
+            @.loadSprintStats()
+            @.loadSprint()
+
     loadProject: ->
         return @rs.projects.get(@scope.projectId).then (project) =>
             @scope.project = project


change icon

change icon

--- index.cjsx
+++ index.cjsx
@@ -24,7 +24,7 @@

 module.exports =
   name: "HenseiNikki"
-  displayName: <FontAwesome key={0} name='pancil-square-o' />{__ "Organization Records"}
+  displayName: <FontAwesome key={0} name='folder-open' />{__ "Organization Records"}
   priority: 7
   author: "Rui"
   link: "https://github.com/ruiii"


Fix syntax error

Fix syntax error

--- index.cjsx
+++ index.cjsx
@@ -54,15 +54,13 @@
             data: JSON.stringify info
       catch e
         console.error e
-    ###
-    when '/kcsapi/api_get_member/questlist'
-      try
-        yield request.postAsync "http://#{SERVER_HOSTNAME}/api/report/quest_list",
-          form:
-            data: JSON.stringify body.api_list
-      catch e
-        console.error e
-    ###
+    # when '/kcsapi/api_get_member/questlist'
+    #  try
+    #    yield request.postAsync "http://#{SERVER_HOSTNAME}/api/report/quest_list",
+    #      form:
+    #        data: JSON.stringify body.api_list
+    #  catch e
+    #    console.error e
 module.exports =
   name: 'Reporter'
   author: [Magica]


Revert "Refresh ship status under certain conditions"

--- index.cjsx
+++ index.cjsx
@@ -596,9 +596,6 @@

         # Return to port
         when '/kcsapi/api_port/port'
-        ,    '/kcsapi/api_req_hensei/change', '/kcsapi/api_req_hensei/preset_select' # Refresh if hensei changes
-        ,    '/kcsapi/api_req_nyukyo/start', '/kcsapi/api_req_nyukyo/speedchange' # Refresh when repairing
-        ,    '/kcsapi/api_req_kousyou/destroyship' # In case if any ship in the fleet is destroyed
           shouldRender = true
           goBack = Object.clone initData
           combinedFlag = body.api_combined_flag


Cosmetic Change to Single Skills / Specialize

--- views/application.coffee
+++ views/application.coffee
@@ -493,6 +493,7 @@
     #knowledge input[type=checkbox]:first,
     #normal_items input:first,
     #bioware input:first,
-    #cyberware input:first')
+    #cyberware input:first,
+    .specialize')
   .change()


Update atom sort keybinding to use natural sort order

https://en.wikipedia.org/wiki/Natural_sort_order

--- .config/atom/keymap.cson
+++ .config/atom/keymap.cson
@@ -18,7 +18,7 @@
   'ctrl-d': 'editor:duplicate-lines'
   'ctrl-r': 'es-identifier-highlight:renameIdentifier'
   'ctrl-shift-d': 'editor:delete-line'
-  'f7': 'sort-lines:sort'
+  'f7': 'sort-lines:natural'
 #
 # 'atom-workspace':
 #   'ctrl-shift-p': 'core:move-up'


Remove unused directives

Remove unused directives

--- src/coffee/directives.coffee
+++ src/coffee/directives.coffee
@@ -2,24 +2,6 @@
 eob_directives = angular.module 'eob.directives', []

 eob_directives
-    .directive 'eob-dropdown', ->
-        (scope, element, attr) ->
-            element.on 'click', (event) ->
-                if element.parent().hasClass('open')
-                    element.parent().removeClass('open')
-                else element.parent().addClass('open')
-
-    .directive 'clickOut', ($window, $parse) ->
-        {
-            restrict: 'A',
-            scope: { method:'&clickOut' }
-            link: (scope, element, attrs) ->
-                clickOutHandler = $parse(attrs.clickOut)
-                angular.element($window).on 'click', (event) ->
-                    if element[0].contains(event.target) then return
-                    clickOutHandler scope.method()
-                    scope.$apply()
-        }

     .directive 'resizable', ['$window', ($window) ->
         [ '$scope', ($scope) ->


Added test for tully comment

Added test for tully comment

--- test/unit/validate-parameters-test.coffee
+++ test/unit/validate-parameters-test.coffee
@@ -49,6 +49,22 @@
       result = validateParameters params
       assert.equal result['errors'].length, 0

+  # based on tully's comment
+  describe 'when type is string and example is "666"', () ->
+    it 'should set descriptive error', () ->
+      params =
+        name:
+          description: 'Machine name'
+          type: 'string'
+          required: true
+          example: '666'
+          default: ''
+          values: []
+
+      result = validateParameters params
+      message = result['errors'][0]
+      assert.include message, 'name'
+      assert.include message, 'string'

   describe 'when type is string and example is a not a parseable float', () ->
     it 'should set no error', () ->


fixed ⍞ input

fixed ⍞ input

--- client/init.coffee
+++ client/init.coffee
@@ -43,7 +43,7 @@
     socket.on 'EchoInput', ({input}) -> session.add input
     socket.on 'AppendSessionOutput', ({result}) -> session.add result
     socket.on 'NotAtInputPrompt', -> session.prompt null
-    socket.on 'AtInputPrompt', (why) -> session.prompt why
+    socket.on 'AtInputPrompt', ({why}) -> session.prompt why
     socket.on 'FocusWindow', ({win}) -> if win == debuggerWin then db.focus() else if win then ed.focus() else session.focus()

     socket.on 'open', (name, text, token, bugger, breakpoints) ->


bugfixes in tags

bugfixes in tags

--- client/tags.coffee
+++ client/tags.coffee
@@ -44,9 +44,13 @@

   tag_dicts: ->
     result = []
-    for i of @tags
+
+    if !@tags?
+      return result
+
+    for name in @tags
       tmp =
-        name: @tags[i]
+        name: name
         parent_id: @_id
         collection: @collection


Make green bar slightly smaller

Make green bar slightly smaller

--- client/tawk.coffee
+++ client/tawk.coffee
@@ -334,7 +334,7 @@
             position: 'absolute'
             bottom: 0
             right: 0
-            height: height * stream.volume / 100
+            height: height * stream.volume / 180
             width: '20px'
             borderLeft: '5px solid #7FFF00'
           AUDIO


billing subscription: add a note that we are implementing automated benefits now

--- salvus/page/billing.cjsx
+++ salvus/page/billing.cjsx
@@ -517,21 +517,32 @@
         @props.actions.create_subscription(plan)

     render_create_subscription_options : ->
-        <Row>
-            <Col sm=4>
-                Select a plan
-            </Col>
-            <Col sm=8>
-                <Input
-                    ref         = 'plan'
-                    type        = 'select'
-                    placeholder = 'Select a plan...'
-                    onChange    = {=>@setState(selected_plan : @refs.plan.getValue())} >
-                    <option value=''>Select a plan...</option>
-                    <option value='standard'>Standard plan - $7 / month</option>
-                </Input>
-            </Col>
-        </Row>
+

+
Sign up for a plan

+            NOTE  (Aug 14, 2015):
+            We are currently implementing
+            automated benefits for signing up
+            for a plan.
+            If you need a specific project upgrade now,
+            please email help@sagemath.com.
+
+

+            <Row>
+                <Col sm=4>
+                    Select a plan
+                </Col>
+                <Col sm=8>
+                    <Input
+                        ref         = 'plan'
+                        type        = 'select'
+                        placeholder = 'Select a plan...'
+                        onChange    = {=>@setState(selected_plan : @refs.plan.getValue())} >
+                        <option value=''>Select a plan...</option>
+                        <option value='standard'>Standard plan - $7 / month</option>
+                    </Input>
+                </Col>
+            </Row>
+

     render_create_subscription_buttons : ->
         <Row>


#302 fix replacing select with shallowSelect

--- modules/sticky-table-headers/main/index.coffee
+++ modules/sticky-table-headers/main/index.coffee
@@ -231,7 +231,7 @@

     if options.stickTableHead
       # Append top
-      topHead = container.select('.hx-sticky-table-header-top')
+      topHead = container.shallowSelect('.hx-sticky-table-header-top')
       if topHead.empty()
         topHead = container.prepend('div').class('hx-sticky-table-header-top')
         # We don't need to do this when the scrollbar size is 0 as there's no empty space shown
@@ -252,7 +252,7 @@

     if options.stickFirstColumn
       # Append left
-      leftHead = container.select('.hx-sticky-table-header-left')
+      leftHead = container.shallowSelect('.hx-sticky-table-header-left')
       if leftHead.empty()
         leftHead = container.prepend('div').class('hx-sticky-table-header-left')

@@ -272,7 +272,7 @@

     if options.stickTableHead and options.stickFirstColumn
       # Append top left
-      topLeftHead = container.select('.hx-sticky-table-header-top-left')
+      topLeftHead = container.shallowSelect('.hx-sticky-table-header-top-left')
       if topLeftHead.empty()
         topLeftHead = container.prepend('div').class('hx-sticky-table-header-top-left')
       topLeftHead.clear()


Add default data to Widget#track

Add default data to Widget#track

--- src/client/widget.coffee
+++ src/client/widget.coffee
@@ -41,7 +41,6 @@
     constructor: (options)->
       @ref          = options.ref
       @api          = @sandbox.data.api
-      @track        = @sandbox.track
       @datasources  = _.extend {}, default_datasources, @datasources, options.datasources

       try
@@ -155,6 +154,14 @@
           _.defer(@afterRender.bind(@, data))
           _.defer((-> @sandbox.start(@$el)).bind(@))

+    track: (name, data) ->
+      data = _.extend data || {},
+        id: @id
+        name: @options.name
+        type: @type
+
+      @sandbox.track(name, data)
+
   (app)->
     Datasource = app.core.datasource
     default_datasources =


adds a static delay between steps when replaying. appears to fix #18, and is a good first step on the way to a solution for #17

--- src/coffee/replay.coffee
+++ src/coffee/replay.coffee
@@ -12,11 +12,28 @@

   TODO: Add graphical clicks to the elements being clicked, and ensure that the element in question is in the users current view.
     ###
+
     @main.storage.record_input = false
-    for stored_element in @main.storage.data
+    delay = 500
+    current_step = 0
+
+    trigger_next_step = =>
+      if current_step >= @main.storage.data.length
+        @main.storage.record_input = true
+        return
+      @util.debug "storage.length (#{@main.storage.data.length}) >= current_step (#{current_step})"
+
+      window.setTimeout replay_step, delay
+
+    replay_step = =>
+      stored_element = @main.storage.data[current_step]
       @util.debug "replaying! Attempting to execute a #{stored_element.event_type} on #{stored_element.id}"
       element = document.getElementById stored_element.id
       event = new Event(stored_element.event_type)
       element.dispatchEvent(event)
+      current_step++
+      @util.debug "current_step is now #{current_step}"

-    @main.storage.record_input = true
+      trigger_next_step()
+    trigger_next_step()
+


Define error reponses for unimplemented endpoints

--- src/coffee/routes.coffee
+++ src/coffee/routes.coffee
@@ -7,6 +7,11 @@
   app.get '/', (req, res, next) ->
     res.json _.omit pkg, 'devDependencies'

+  # generate and render pdf in the browser
+  app.get '/api/pdf/:token', (req, res, next) ->
+    res.send 501,
+      message: 'Endpoint not implemented yet'
+
   # retrieve a link to the generated pdf
   app.post '/api/pdf/url', (req, res, next) ->
     # - generate pdf
@@ -14,10 +19,14 @@

   # generate and render pdf in the browser
   app.post '/api/pdf/render', (req, res, next) ->
+    res.send 401,
+      message: 'Not authorized'
     # - generate pdf
     # - respond with pdf

   # generate and download pdf
   app.post '/api/pdf/download', (req, res, next) ->
+    res.send 401,
+      message: 'Not authorized'
     # - generate pdf
     # - respond with download


:bug: Escape strings for javac (Issue #480)

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -133,7 +133,7 @@
       command: "bash"
       args: (context) ->
         className = context.filename.replace /\.java$/, ""
-        args = ['-c', "javac -d /tmp #{context.filepath} && java -cp /tmp #{className}"]
+        args = ['-c', "javac -d /tmp '#{context.filepath}' && java -cp /tmp #{className}"]
         return args

   JavaScript:


Support LOWER

Support LOWER

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -533,6 +533,11 @@
   LOG10: (args)->
     @checkArgumentSize(args, 1)
     Math.log10 @expectNumber(args[0])
+
+  LOWER: (args)->
+    @checkArgumentSize(args, 1)
+    str = @expectString(args[0])
+    str.toLowerCase()

   MINUTE: (args)->
     @checkArgumentSize(args, 1)


:art: provide getCommand method as hook for easy enhancement.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -294,14 +294,20 @@
       selections = @editor.getSelections()
       numberOfProcess = selections.length
       for selection, index in selections
-        @runExternalCommand {
-          command: @command
-          args: @args
-          stdout: @onStdout(index)
-          exit: @onExit(numberOfProcess)
-          stdin: selection.getText()
-        }
-        restorePoint?(selection)
+        {command, args} = @getCommand(selection) ? {}
+        if command? and args?
+          @runExternalCommand {
+            command: command
+            args: args
+            stdout: @onStdout(index)
+            exit: @onExit(numberOfProcess)
+            stdin: selection.getText()
+          }
+          restorePoint?(selection)
+
+  # For easily extend by vmp plugin.
+  getCommand: (selection) ->
+    {@command, @args}

   onStdout: (index) ->
     (output) =>


Only update if any props have changed

Only update if any props have changed

This cuts drastically cuts down on the number for MathJax calls
from around 20 to 6.  Still not super great but an improvement

--- src/components/html.cjsx
+++ src/components/html.cjsx
@@ -24,6 +24,14 @@
     {html} = @props
     if html
       __html: html
+
+  # rendering uses dangerouslySetInnerHTML and then runs MathJax,
+  # Both of which React can't optimize like it's normal render operations
+  # Accordingly, only update if any of our props have actually changed
+  shouldComponentUpdate: (nextProps, nextState) ->
+    for propName, value of nextProps
+      return true if @props[propName] isnt value
+    return false

   componentDidMount:  -> @updateDOMNode()
   componentDidUpdate: -> @updateDOMNode()


overlay component dropping down from current position

--- @client/drop_menu.coffee
+++ @client/drop_menu.coffee
@@ -49,12 +49,15 @@
     wrapper_style = _.defaults {}, @props.wrapper_style,
       position: 'relative'

-    anchor_style = _.defaults {}, @props.anchor_style,
-      position: 'relative'
-      background: 'transparent'
-      border: 'none'
-      cursor: 'pointer'
-      fontSize: 'inherit'
+    if !@props.anchor_class_name
+      anchor_style = _.defaults {}, @props.anchor_style,
+        position: 'relative'
+        background: 'transparent'
+        border: 'none'
+        cursor: 'pointer'
+        fontSize: 'inherit'
+    else
+      anchor_style = _.defaults {}, @props.anchor_style

     anchor_when_open_style = _.defaults {}, @props.anchor_open_style, anchor_style

@@ -153,7 +156,7 @@
         'aria-haspopup': "true"
         'aria-owns': "dropMenu-#{@local.key}"
         style: if @local.show_menu then anchor_when_open_style else anchor_style
-
+        className: if @props.anchor_class_name then @props.anchor_class_name

         onMouseEnter: if open_menu_on == 'focus' then (e) =>
                 @local.show_menu = true
@@ -239,4 +242,141 @@

-
+# DropOverlay
+# Like a DropMenu, but just render a component in the open area after activation
+
+# structure:
+
+#   DropOverlay
+
+#     Wrapper
+
+#       Anchor (triggers area open)
+
+#       OpenArea (a component)
+
+
+# props:
+
+#   open_area_on (focus | activation)  (common case is hover vs click)
+#   wrapper_style
+#   anchor_style
+#   anchor_when_open_style
+#   render_anchor
+
+# state:
+#   - open
+
+window.DropOverlay = ReactiveComponent
+  displayName: 'DropOverlay'
+
+  on_keydown: (e) ->
+    # cancel on ESC if a cancel button has been defined
+    if e.key == 'Escape' || e.keyCode == 27
+      @onClose()
+
+  on_click: (e) ->
+    if @local.show_area
+      # if the focus isn't still on an element inside of this menu,
+      # then we should close the menu
+      if @refs.area_wrap && $(e.target).closest(@refs.area_wrap?.getDOMNode()).length == 0
+        @onClose()
+
+  onOpen: ->
+    document.addEventListener 'keydown', @on_keydown
+    document.addEventListener 'click', @on_click
+    @local.show_area = true
+    save @local
+
+  onClose: ->
+    document.removeEventListener 'keydown', @on_keydown
+    document.removeEventListener 'click', @on_click
+    document.activeElement.blur()
+    @local.show_area = false
+    save @local
+    @props.close_callback?()
+
+
+  render : ->
+    open_area_on = @props.open_area_on or 'activation' #other option is 'focus'
+
+    wrapper_style = _.defaults {}, @props.wrapper_style,
+      position: 'relative'
+
+    if !@props.anchor_class_name
+      anchor_style = _.defaults {}, @props.anchor_style,
+        position: 'relative'
+        background: 'transparent'
+        border: 'none'
+        cursor: 'pointer'
+        fontSize: 'inherit'
+    else
+      anchor_style = _.defaults {}, @props.anchor_style
+
+    anchor_when_open_style = _.defaults {}, @props.anchor_open_style, anchor_style
+
+    render_anchor = @props.render_anchor
+
+    drop_area_style = _.defaults {}, @props.drop_area_style,
+      position: 'absolute'
+      border: '1px solid #979797'
+      boxShadow: '0px 1px 2px rgba(0,0,0,.2)'
+      borderRadius: 8
+      padding: "12px 24px 24px 24px"
+      zIndex: 999999999
+      backgroundColor: 'white'
+
+    # wrapper
+    DIV
+      ref: 'area_wrap'
+      key: 'droparea-wrapper'
+      style: wrapper_style
+
+      onTouchEnd: (e) =>
+        if @local.show_area
+          @onClose()
+        else
+          @onOpen()
+        e.preventDefault()
+
+      # anchor
+
+      BUTTON
+        tabIndex: 0
+        'aria-haspopup': "true"
+        'aria-owns': "DropOverlay-#{@local.key}"
+        style: if @local.show_area then anchor_when_open_style else anchor_style
+        className: if @props.anchor_class_name then @props.anchor_class_name
+
+        onMouseEnter: if open_area_on == 'focus' then (e) =>
+                @onOpen()
+
+        onClick: if open_area_on == 'activation' then (e) =>
+                    if @local.show_area
+                      @onClose()
+                    else
+                      @onOpen()
+
+        onKeyDown: (e) =>
+          if e.which == 13 || e.which == 32
+            if @local.show_area
+              @onClose()
+            else
+              @onOpen()
+            e.preventDefault()
+            e.stopPropagation()
+
+        render_anchor @local.show_area
+
+      # drop area
+
+      DIV
+        id: "DropOverlay-#{@local.key}"
+        role: "dialog"
+        'aria-hidden': !@local.show_area
+        hidden: !@local.show_area
+        style: drop_area_style
+
+        @props.children
+
+


Use arrow in method

Use arrow in method

--- src/components/view.cjsx
+++ src/components/view.cjsx
@@ -15,7 +15,7 @@
     @state =
       stack: ApplicationStore.getViews()

-  componentDidMount: ->
+  componentDidMount: =>
     @unsubscribe = ApplicationStore.listen(@onStateChange)

   onStateChange: =>


don't log time to COMMIT_EDITMSG files

don't log time to COMMIT_EDITMSG files

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -182,29 +182,36 @@
     if cleanup
         fs.unlink(file)

+endsWith = (str, suffix) ->
+    return str.indexOf(suffix, str.length - suffix.length) !== -1;
+
+shouldIgnore = (file) ->
+    return endsWith(file, 'COMMIT_EDITMSG')
+
 sendHeartbeat = (file, isWrite) ->
     time = Date.now()
     if isWrite or enoughTimePassed(time) or lastFile isnt file.path
-        pythonLocation((python) ->
-            if python?
-                if not file.path? or file.path is undefined or fileIsIgnored(file.path)
-                    return
-                apikey = atom.config.get("wakatime.apikey")
-                unless apikey
-                    return
-
-                args = [cliLocation(), '--file', file.path, '--key', apikey, '--plugin', 'atom-wakatime/' + VERSION]
-                if isWrite
-                    args.push('--write')
-                process.execFile(python, args, (error, stdout, stderr) ->
-                    if error?
-                        console.warn error
-                    # else
-                    #     console.log(args)
-                )
-                lastHeartbeat = time
-                lastFile = file.path
-        )
+        if not shouldIgnore(file.path)
+            pythonLocation((python) ->
+                if python?
+                    if not file.path? or file.path is undefined or fileIsIgnored(file.path)
+                        return
+                    apikey = atom.config.get('wakatime.apikey')
+                    unless apikey
+                        return
+
+                    args = [cliLocation(), '--file', file.path, '--key', apikey, '--plugin', 'atom-wakatime/' + VERSION]
+                    if isWrite
+                        args.push('--write')
+                    process.execFile(python, args, (error, stdout, stderr) ->
+                        if error?
+                            console.warn error
+                        # else
+                        #     console.log(args)
+                    )
+                    lastHeartbeat = time
+                    lastFile = file.path
+            )

 fileIsIgnored = (file) ->
     patterns = atom.config.get("wakatime.ignore")


delete form DOM will also delete from cloudinary server if *delete_token* is available

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -141,6 +141,10 @@
       @files = _files
       @redraw()
       @checkMaximum()
+
+      # do removal from cloudinary server if delete token exist
+      $.cloudinary.delete_by_token(removedFile.delete_token) if removedFile.delete_token?
+
       @$input.trigger 'attachinary:fileremoved', [removedFile]

     checkMaximum: ->


Clear mini editor base select list cancelled()

Clearing the mini editor when closing is something
all sub-classes were already doing so it makes sense
to pull it up to the base class as the default
cancelled() implementation that can still be overridden
if needed.

--- src/fuzzy-finder-view.coffee
+++ src/fuzzy-finder-view.coffee
@@ -71,9 +71,6 @@
     else
       @setError('Selected path does not exist')
       setTimeout((=> @setError()), 2000)
-
-  cancelled: ->
-    @miniEditor.setText('')

   toggleFileFinder: ->
     if @hasParent()


download files;

download files;

--- rime/recipe.coffee
+++ rime/recipe.coffee
@@ -1,21 +1,34 @@
 exports.cook = cook = (recipe, ingredients, callback) ->
   if not callback? and typeof ingredients is 'function'  # (recipe, callback)
     [ingredients, callback] = [null, ingredients]
+  # if no callback is given, throw errors
   callback ?= (err) -> throw err if err
   try
-    unless recipe instanceof Recipe
+    unless recipe instanceof Recipe  # cook {...}, ...
       recipe = new Recipe recipe
-    if recipe.props.files
-      recipe.downloadFiles()
     if recipe.props.params
       recipe.collectParams(ingredients)
-    if recipe.props.setup
-      recipe.props.setup.call recipe
   catch e
     console.error "error cooking recipe: #{e}"
-    callback(e)
+    callback e
     return
-  callback()
+  finish = (err) ->
+    if (err)
+      callback err
+      return
+    if recipe.props.setup
+      try
+        recipe.props.setup.call recipe
+      catch e
+        console.error "error cooking recipe: #{e}"
+        callback e
+        return
+    callback()
+  if recipe.props.files
+    recipe.downloadFiles finish
+  else  # no files needed to download
+    finish()
+

 exports.Recipe = class Recipe

@@ -39,8 +52,9 @@
         throw Error("missing ingredient: #{param.name}")
     # TODO

-  downloadFiles: ->
+  downloadFiles: (callback) ->
     # TODO
+    callback()

   installSchema: (schemaId, options) ->
     # TODO


create download directory;

create download directory;

--- rime/recipe.coffee
+++ rime/recipe.coffee
@@ -61,7 +61,8 @@
       callback()
       return
     # TODO:
-    download_dir = 'download/'
+    download_dir = "#{@props.rimeDirectory}/download/"
+    fs.mkdirSync download_dir unless fs.existsSync download_dir
     total = @props.files.length
     success = failure = 0
     finish = ->


allow to use divs for initializing player

--- src/javascripts/embed.coffee
+++ src/javascripts/embed.coffee
@@ -38,7 +38,7 @@
     return hash.toString(16).substring(1)

   origin: () ->
-    scriptSrc = @elem.src
+    scriptSrc = @elem.src || @elem.getAttribute('src')
     unless window.location.protocol.match(/^https/)
       scriptSrc = scriptSrc.replace(/^https/, 'http')
     scriptSrc.match(/(^.*\/)/)[0].replace(/javascripts\/$/, '').replace(/\/$/, '')
@@ -84,7 +84,7 @@
 class Embed
   constructor: ->
     players = []
-    elems = document.querySelectorAll('script.podigee-podcast-player')
+    elems = document.querySelectorAll('script.podigee-podcast-player, div.podigee-podcast-player')

     return if elems.length == 0


:fire: Remove grim require from KeymapExtensions

--- src/keymap-extensions.coffee
+++ src/keymap-extensions.coffee
@@ -2,7 +2,6 @@
 path = require 'path'
 KeymapManager = require 'atom-keymap'
 CSON = require 'season'
-Grim = require 'grim'

 bundledKeymaps = require('../package.json')?._atomKeymaps


defensive

defensive

--- apps/auction_support/client/bid_form.coffee
+++ apps/auction_support/client/bid_form.coffee
@@ -72,7 +72,7 @@
            @timesPolledForBidPlacement > @maxTimesPolledForBidPlacement
           analyticsHooks.trigger 'confirm:bid:form:success', {
             bidder_position_id: bidderPosition.id
-            bidder_id: bidderPosition.get('bidder').id
+            bidder_id: bidderPosition.get('bidder')?.id
           }
           @showSuccessfulBidMessage()
         else


Add support for GD3 course

Add support for GD3 course

--- app/core/utils.coffee
+++ app/core/utils.coffee
@@ -95,6 +95,7 @@
   GAME_DEVELOPMENT_2: '57b621e7ad86a6efb5737e64'
   WEB_DEVELOPMENT_2: '5789587aad86a6efb5737020'
   COMPUTER_SCIENCE_3: '56462f935afde0c6fd30fc8c'
+  GAME_DEVELOPMENT_3: '5a0df02b8f2391437740f74f'
   COMPUTER_SCIENCE_4: '56462f935afde0c6fd30fc8d'
   COMPUTER_SCIENCE_5: '569ed916efa72b0ced971447'
   COMPUTER_SCIENCE_6: '5817d673e85d1220db624ca4'
@@ -107,6 +108,7 @@
   courseIDs.GAME_DEVELOPMENT_2
   courseIDs.WEB_DEVELOPMENT_2
   courseIDs.COMPUTER_SCIENCE_3
+  courseIDs.GAME_DEVELOPMENT_3
   courseIDs.COMPUTER_SCIENCE_4
   courseIDs.COMPUTER_SCIENCE_5
   courseIDs.COMPUTER_SCIENCE_6
@@ -120,6 +122,7 @@
 courseAcronyms[courseIDs.GAME_DEVELOPMENT_2] = 'GD2'
 courseAcronyms[courseIDs.WEB_DEVELOPMENT_2] = 'WD2'
 courseAcronyms[courseIDs.COMPUTER_SCIENCE_3] = 'CS3'
+courseAcronyms[courseIDs.GAME_DEVELOPMENT_3] = 'GD3'
 courseAcronyms[courseIDs.COMPUTER_SCIENCE_4] = 'CS4'
 courseAcronyms[courseIDs.COMPUTER_SCIENCE_5] = 'CS5'
 courseAcronyms[courseIDs.COMPUTER_SCIENCE_6] = 'CS6'


Change dev API host

Change dev API host

--- app/lib/config.coffee
+++ app/lib/config.coffee
@@ -6,8 +6,7 @@
     apiHost: null

   development:
-    # apiHost: 'http://localhost:3000'
-    apiHost: 'https://api.zooniverse.org'
+    apiHost: 'https://dev.zooniverse.org'

   production:
     apiHost: 'https://api.zooniverse.org'


Add sideloaded records to record arrays

Add sideloaded records to record arrays

--- assets/scripts/lib/travis/adapter.coffee
+++ assets/scripts/lib/travis/adapter.coffee
@@ -63,6 +63,7 @@
       # we need to skip records of type, which is loaded by adapter already
       if (type = Ember.get(Travis, 'mappings')[name]) && type != klass
         for record in records
-          type.findFromCacheOrLoad(record)
+          record = type.findFromCacheOrLoad(record)
+          @addToRecordArrays(record)


help icon open directly the kb in browser

--- app/src/routes.coffee
+++ app/src/routes.coffee
@@ -157,8 +157,7 @@

   # Help
   route '/wallet/help/index', (params) ->
-    dialog = new CommonDialogsHelpDialogViewController()
-    dialog.show()
+    window.open t 'application.support_url'

   ## Firmware Update
   route '/update/index', (params) ->


plinks!

plinks!

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -176,6 +176,7 @@
 BaseElements = {

   updateId: (e) ->
+    e.onIdUpdate() if e.onIdUpdate?
     e.id.name = e.props.name
     e.id.sys = e.props.sys

@@ -231,6 +232,14 @@
         design: dsg
       }
       @links = []
+
+
+    onIdUpdate: ->
+      for x in @links
+        if @id.name != @props.name
+          x.props[@props.name] = x.props[@id.name]
+          delete(x.props[@id.name])
+

     #cyjs generates the json for this object
     cyjs: ->
@@ -356,9 +365,27 @@
     isInternet: ->
       @endpoint[0] instanceof Router and @endpoint[1] instanceof Router

+    isPhysical: ->
+      @endpoint[0] instanceof Model and @endpoint[1] instanceof Model or
+      @endpoint[0] instanceof Model and @endpoint[1] instanceof Sax or
+      @endpoint[1] instanceof Model and @endpoint[0] instanceof Sax
+
     applyWanProps: ->
       @props.capacity = 100
       @props.latency = 7
+
+    applyPhysicalProps: ->
+      @props = {
+        name: @props.name,
+        sys: @props.sys,
+        design: @props.dsg,
+        endpoints: [
+          {name: "link0", sys: "root", design: dsg, ifname: ""},
+          {name: "link0", sys: "root", design: dsg, ifname: ""}
+        ]
+      }
+      @props[@endpoint[0].props.name] = "" if @endpoint[0] instanceof Model
+      @props[@endpoint[1].props.name] = "" if @endpoint[1] instanceof Model

     setEndpointData: ->
       @props.endpoints[0].name = @endpoint[0].props.name
@@ -371,6 +398,9 @@

     ifInternetToWanLink:  ->
       @applyWanProps() if @isInternet()
+
+    ifPhysicalToPlink: ->
+      @applyPhysicalProps() if @isPhysical()

     showProps: (f) ->
       f.add(@props, 'name')
@@ -1217,6 +1247,7 @@

       @mh.ve.surface.updateLink(@mh.placingLink.ln)
       @mh.placingLink.ifInternetToWanLink()
+      @mh.placingLink.ifPhysicalToPlink()
       @mh.placingLink.setEndpointData()

       @mh.ve.addie.update([@mh.placingLink])


refactor: encapsulate endpoint linking in link object

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -242,6 +242,21 @@
     x.props.sshcmd =
       'ssh -A -t '+g.user+'@users.isi.deterlab.net '+
       'ssh -A '+x.props.name+'.'+g.user+'-'+dsg+'.cypress'
+
+  initName: (x, n) =>
+    x.props.name = g.ve.namemanager.getName(n)
+    x.id.name = x.props.name
+
+  addInterface: (x) =>
+    ifname = ""
+    if x.props.interfaces?
+      ifname = "ifx"+Object.keys(x.props.interfaces).length
+      x.props.interfaces[ifname] = {
+        name: ifname,
+        latency: 0,
+        capacity: 1000
+      }
+    ifname

   #The Computer class is a representation of a computer
   Computer: class Computer
@@ -603,6 +618,15 @@
         @ln.material.color = new THREE.Color(0x123456)
         @ln.geom.colorsNeedUpdate = true

+    linkEndpoint: (i, x) ->
+      ifname = BaseElements.addInterface(x)
+      x.links.push(this)
+      x.shp.obj3d.lines.push(@ln)
+      @ln.geom.vertices[i] = x.shp.obj3d.linep
+      @endpoint[i] = x
+      @ep_ifx[i] = ifname
+
+
     setEndpointData: ->
       @props.endpoints[0].name = @endpoint[0].props.name
       @props.endpoints[0].sys = @endpoint[0].props.sys
@@ -637,6 +661,11 @@
       else
         @ln.material.color = new THREE.Color(0x123456)
         @ln.geom.colorsNeedUpdate = true
+
+    typeResolve: ->
+      @ifInternetToWanLink()
+      @ifPhysicalToPlink()
+      @setEndpointData()

     showProps: (f) ->
       f.add(@props, 'name')
@@ -2328,22 +2357,8 @@
     for s in @sel
       l = new BaseElements.Link(@ve.surface.baseRect, s.shp.obj3d.linep, @tgtP,
                                 0, 0, 5)
-      l.props.name = @ve.namemanager.getName("link")
-      l.id.name = l.props.name
-
-      ifname = ""
-      if s.props.interfaces?
-        ifname = "ifx"+Object.keys(s.props.interfaces).length
-        s.props.interfaces[ifname] = {
-          name: ifname,
-          latency: 0,
-          capacity: 1000
-        }
-
-      l.endpoint[0] = s
-      l.endpoint[0].links.push(l)
-      l.ep_ifx[0] = ifname
-      s.shp.obj3d.lines.push(l.ln)
+      BaseElements.initName(l, "link")
+      l.linkEndpoint(0, s)
       @links.push(l)
       true

@@ -2358,26 +2373,12 @@
       @tgtP = ixs[0].object.linep

       for l in @links
-        l.ln.geom.vertices[1] = ixs[0].object.linep
-        ixs[0].object.lines.push(l.ln)
-
-        ifname = ""
-        if ixs[0].object.userData.props.interfaces?
-          ifname = "ifx"+Object.keys(ixs[0].object.userData.props.interfaces).length
-          ixs[0].object.userData.props.interfaces[ifname] = {
-            name: ifname,
-            latency: 0,
-            capacity: 1000
-          }
-        l.endpoint[1] = ixs[0].object.userData
-        l.ep_ifx[1] = ifname
-        l.endpoint[1].links.push(l)
+        l.linkEndpoint(1, ixs[0].object.userData)
+        l.typeResolve()
         @ve.surface.updateLink(l.ln)
-        l.ifInternetToWanLink()
-        l.ifPhysicalToPlink()
-        l.setEndpointData()
         @ve.sview.render()
         @ve.addie.update([l])
+
       @ve.sview.container.style.cursor = "default"
       @ve.sview.container.onmousedown = (eve) => @ve.sview.mouseh.baseDown(eve)
       true


parametric models

parametric models

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -228,6 +228,7 @@
       @parent.obj3d.add(@shp.obj3d)
       @props = {
         name: "model0",
+        params: "",
         equations: ""
       }
       @id = {
@@ -255,7 +256,7 @@
         name: "model0",
         sys: "root",
         model: ""
-        params: "",
+        args: "",
       }
       @id = {
         name: "model0",
@@ -263,11 +264,43 @@
         design: dsg
       }
       @links = []
-
+      @args = []
+
+    addArgs: () ->
+      _args = @model.props.params
+        .replace(" ", "")
+        .split(",")
+        .map((x) -> x.replace(" ", ""))
+        .filter((x) -> x.length != 0)
+
+      for p in _args
+        if !@props[p]?
+          @props[p] = ""
+
+      for p in @args
+        if p not in _args
+          delete @props[p]
+
+      @args = _args
+      true
+
+    loadArgValues: () ->
+      _args = @props.args
+        .split(",")
+        .filter((x) -> x.length != 0)
+        .map((x) -> x.split("=").filter((y) -> y.length != 0))
+
+      for x in _args
+        @props[x[0]] = x[1]
+        @args.push(x[0])

     sync: ->
       if @model?
         @props.model = @model.props.name
+        @addArgs()
+        @props.args = ""
+        for p in @args
+          @props.args += (p+"="+@props[p]+",")

     onIdUpdate: ->
       for x in @links
@@ -1027,6 +1060,7 @@
     m = loadedModels[p.props.model]
     m.instances.push(p)
     p.model = m
+    p.loadArgValues()
     true

   loadSax: (x) =>
@@ -1208,6 +1242,7 @@
     @instance = @mh.makePlacingObject(@model.instantiate(null, 0, 0, 0, 25, 25))
     #@instance.props.model = @model.props.name
     @instance.model = @model
+    @instance.addArgs()
     @model.instances.push(@instance)
     @mh.ve.container.onmousemove = (eve) => @handleMove1(eve)

@@ -1300,6 +1335,7 @@
       for k, v of @cprops
         for e in @elements
           e.props[k] = v if v != "..."
+          e.sync() if e.sync?
       @ve.addie.update(@elements)

   hide: () ->
@@ -1328,6 +1364,7 @@
         continue if k == 'endpoints'
         continue if k == 'interfaces'
         continue if k == 'path'
+        continue if k == 'args'
         continue if k == 'equations'
         continue if k == 'bindings'
         continue if k == 'name' and @elements.length > 1


Change from Yahoo to DuckDuckGo

Change from Yahoo to DuckDuckGo

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -1,6 +1,6 @@
 $ ->
   DIC_URL_PREFIX = 'http://alldic.daum.net/search.do?dic=eng&q='
-  IMAGES_URL_PREFIX = 'http://images.search.yahoo.com/search/images?p='
+  IMAGES_URL_PREFIX = 'https://duckduckgo.com/?%3Fiax=1&iax=1&ia=images&q='
   $input = $('input')
   $button = $('button')
   $dicIframe = $('iframe.dic')


JDomain: Use auto-generated relationship creator, pass group info with it for future use

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -225,23 +225,16 @@

         callback new KodingError "CNAME record for #{domain} is not matching with #{baseDomain}", "CNAMEMISMATCH"

-  createDomain = (domainData, account, callback)->
-
-    model = new JDomain domainData
-    model.save (err)->
+  createDomain = (domainData, account, group, callback)->
+
+    domain = new JDomain domainData
+    domain.save (err)->
       return callback err  if err

-      rel = new Relationship
-        targetId   : model.getId()
-        targetName : 'JDomain'
-        sourceId   : account.getId()
-        sourceName : 'JAccount'
-        as         : 'owner'
-
-      rel.save (err)->
+      options = {data:{group}}
+      account.addDomain domain, options, (err)->
         return callback err  if err
-
-      callback err, model
+        callback err, domain

   @createDomain$: secure (client, {domain, stack}, callback)->

@@ -293,7 +286,7 @@
           resolveDomain domainData, (err)->
             return callback err  if err

-            createDomain domainData, delegate, callback
+            createDomain domainData, delegate, group.slug, callback

   @createDomains = ({account, domains, hostnameAlias, stack, group})->

@@ -306,7 +299,8 @@
         as: "owner"
       , (err, rel)->
         if err or not rel
-          account.addDomain domainObj, (err)->
+          options = {data:{group}}
+          account.addDomain domainObj, options, (err)->
             console.log err  if err?

     domains.forEach (domain) ->


add constraint on http code 200

add constraint on http code 200

--- BackofficeBundle/Resources/public/coffee/underscoreTemplateLoader.coffee
+++ BackofficeBundle/Resources/public/coffee/underscoreTemplateLoader.coffee
@@ -42,10 +42,11 @@
           url: filename
           data: { 'language': language,'templateId': templateName }
           success: (tpl, textStatus, xhr) ->
+            if 200 == xhr.status
               templateLoader.addTemplate templateName, language, tpl
               templateLoader.storeTemplates
-              view.onTemplateLoaded templateName, tpl
-              return
+            view.onTemplateLoaded templateName, tpl
+            return
         }
       else
         view.onTemplateLoaded templateName, @templates[language][templateName]


:lipstick: Add getCurrentWindow() helper

--- src/atom.coffee
+++ src/atom.coffee
@@ -21,11 +21,11 @@
     @themes = new ThemeManager()
     @contextMenu = new ContextMenuManager(@getLoadSettings().devMode)

+  getCurrentWindow: ->
+    remote.getCurrentWindow()
+
   getLoadSettings: ->
     @getCurrentWindow().loadSettings
-
-  getCurrentWindow: ->
-    remote.getCurrentWindow()

   #TODO Remove theses once packages have been migrated
   getPackageState: (args...) -> @packages.getPackageState(args...)
@@ -109,8 +109,7 @@
   close: ->
     @getCurrentWindow().close()

-  exit: (status) ->
-    app.exit(status)
+  exit: (status) -> app.exit(status)

   toggleFullScreen: ->
     @setFullScreen(!@isFullScreen())


Fix default size bug

Fix default size bug

--- src/atom.coffee
+++ src/atom.coffee
@@ -231,13 +231,15 @@
     {windowDimensions} = @getLoadSettings()
     return windowDimensions if windowDimensions?

+    dimensions = null
     try
-      JSON.parse(localStorage.getItem("defaultWindowDimensions"))
+      dimensions = JSON.parse(localStorage.getItem("defaultWindowDimensions"))
     catch error
       console.warn "Error parsing default window dimensions", error
       localStorage.removeItem("defaultWindowDimensions")
-      {width, height} = screen.getPrimaryDisplay().workAreaSize
-      {x: 0, y: 0, width: width, height: Math.min(1024, height)}
+
+    {width, height} = screen.getPrimaryDisplay().workAreaSize
+    dimensions ? {x: 0, y: 0, width: Math.min(1024, width), height: height}

   restoreWindowDimensions: ->
     windowDimensions = @state.windowDimensions ? @getDefaultWindowDimensions()


Options for Exporting Capture Date/Time and Owner

--time -t Include Capture Date/Time
--owner -o Include Portal Owner

--- src/plugins/export-portals.coffee
+++ src/plugins/export-portals.coffee
@@ -26,6 +26,8 @@
             line.push po.lngE6 / 1e6 if argv.latlng or argv.l
             line.push po.image if argv.image or argv.I
             line.push po._id if argv.id or argv.i
+            line.push po.capturedTime if argv.time or argv.T
+            line.push po.owner if argv.owner or argv.o

             lines.push line.join(',')

@@ -36,4 +38,4 @@
         else
             console.log lines.join('\n')

-        callback()+        callback()


Sending msg if not null

Sending msg if not null

--- src/chef.coffee
+++ src/chef.coffee
@@ -28,9 +28,9 @@

 execCommand = (msg, cmd) ->
   exec cmd, (error, stdout, stderr) ->
-    msg.send error
+    msg.send error if error
     msg.send stdout
-    msg.send stderr
+    msg.send stderr if stderr

 checkKnife = "which knife"
 exec checkKnife, (error, stdout, stderr) ->


Teamwork: Added enableChat option again.

--- client/Teamwork/Views/teamworkapp.coffee
+++ client/Teamwork/Views/teamworkapp.coffee
@@ -60,6 +60,7 @@
       firebaseInstance    : options.firebaseInstance    or instanceName
       sessionKey          : options.sessionKey
       delegate            : this
+      enableChat          : yes
       playground          : options.playground          or null
       panels              : options.panels              or [
         hint              : "
This is a collaborative coding environment where you can team up with others and work on the same code.
"


Changed History so its only 1 row containing date played instead of the entire tile set.

--- client/js/Utils/gameUserFunctions.coffee
+++ client/js/Utils/gameUserFunctions.coffee
@@ -3,7 +3,7 @@
     currentGame = GameRooms.findOne(gameId)
     location = currentGame.players.indexOf(this.userId)
     if currentGame.playerCount is 1
-      History.insert({currentGame})
+      History.insert({gamePlayed: new Date()})
       GameRooms.remove(gameId)
     else
       currentGame.players.splice(location, 1)


Fix InStateMenu not positioning correctly.

Don't add to world, add to stage. Note that Group#fixedToCamera doesn't
seem to work by itself.

--- src/in-state-menu.coffee
+++ src/in-state-menu.coffee
@@ -21,7 +21,7 @@
       @_initialize()

     _initialize: ->
-      @group = @add.group()
+      @group = @add.group null, 'in-state-menu', yes

       @overlay = @add.graphics 0, 0, @group
       @overlay.beginFill 0x000000, 0.2


Bugfix: progress PlayerView bounded by start/end

--- coffee/src/shells/videolink.shell.coffee
+++ coffee/src/shells/videolink.shell.coffee
@@ -314,8 +314,8 @@

   _onMediaProgress: (view, elapsed, total) =>
     # keep progress bar in sync
-    @_progress = @model.timeStart() + elapsed
-    @_timeRangeInputView.progress @_progress
+    @_progress = elapsed
+    @_timeRangeInputView.progress @_progress + @model.timeStart()

   _onChangeTimes: (changed) =>
@@ -347,6 +347,7 @@

   _onChangeProgress: (progress) =>
     # if slider progress differs from player progress, seek to new position
+    progress = progress - @model.timeStart()
     unless progress.toFixed(5) == @_progress?.toFixed(5)
       @_progress = progress
       @_playerView.seek progress


contains, count, indexes_of wrapped for JS

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -105,7 +105,7 @@
     skip: ar (index) -> new Skip {}, @, index
     limit: ar (index) -> new Limit {}, @, index
     getField: ar (field) -> new GetField {}, @, field
-    contains: varar(1, null, (fields...) -> new Contains {}, @, fields...)
+    contains: varar(1, null, (fields...) -> new Contains {}, @, fields.map(funcWrap)...)
     insertAt: ar (index, value) -> new InsertAt {}, @, index, value
     spliceAt: ar (index, value) -> new SpliceAt {}, @, index, value
     deleteAt: varar(1, 2, (others...) -> new DeleteAt {}, @, others...)
@@ -126,7 +126,7 @@
     filter: aropt (predicate, opts) -> new Filter opts, @, funcWrap(predicate)
     concatMap: ar (func) -> new ConcatMap {}, @, funcWrap(func)
     distinct: ar () -> new Distinct {}, @
-    count: varar(0, 1, (fun...) -> new Count {}, @, fun...)
+    count: varar(0, 1, (fun...) -> new Count {}, @, fun.map(funcWrap)...)
     union: varar(1, null, (others...) -> new Union {}, @, others...)
     nth: ar (index) -> new Nth {}, @, index
     match: ar (pattern) -> new Match {}, @, pattern


Allow not passing event args to binding

Allow not passing event args to binding

Useful when bound function accepts optional arguments that are not
compatible with event arguments (e.g. Marbles.View.prototype.render)

--- src/marbles/events.js.coffee
+++ src/marbles/events.js.coffee
@@ -5,13 +5,13 @@
 event_splitter = /\s+/

 Marbles.Events = {
-  on: (events, callback, context) ->
+  on: (events, callback, context, options = {}) ->
     events = events?.split(event_splitter) unless _.isArray(events)
     @_events ?= {}
     for name in events
       do (name) =>
         @_events[name] ?= []
-        @_events[name].push(callback: callback, context: context || @)
+        @_events[name].push(callback: callback, context: context || @, options: options)

     # remove binding when listener anounces self-deletion
     context?.on? 'detach', (obj) =>
@@ -19,7 +19,7 @@

     @ # chainable

-  once: (events, callback, context) ->
+  once: (events, callback, context, options = {}) ->
     events = events?.split(event_splitter) unless _.isArray(events)

     for name in events
@@ -27,7 +27,7 @@
         once = =>
           @off(name, once)
           callback.apply(context || @, arguments)
-        @on(name, once, context)
+        @on(name, once, context, options)

     @ # chainable

@@ -57,7 +57,10 @@
     for name in events
       continue unless bindings = @_events?[name]
       for binding in bindings
-        binding.callback?.apply?(binding.context, args)
+        if binding.options.args is false
+          binding.callback?.call?(binding.context)
+        else
+          binding.callback?.apply?(binding.context, args)

     @ # chainable
 }


remove wasteful statements

remove wasteful statements

--- src/egen.coffee
+++ src/egen.coffee
@@ -47,7 +47,4 @@
     $element

   $.fn.egen = $.fn.eGen = (queryTokens, attrs, innerContents) ->
-    if innerContents
-      @append $.egen(queryTokens, attrs, innerContents)
-    else
-      @append $.egen(queryTokens, attrs)
+    @append $.egen(queryTokens, attrs, innerContents)


Add cancel points before heavy leaflet operations.

--- src/map-base-view.coffee
+++ src/map-base-view.coffee
@@ -10,6 +10,7 @@
     widgets          = require 'cs!app/widgets'
     jade             = require 'cs!app/jade'
     MapStateModel    = require 'cs!app/map-state-model'
+    operationQueue   = require 'cs!app/operation-queue'
     {getIeVersion}   = require 'cs!app/base'

     # TODO: remove duplicates
@@ -153,6 +154,8 @@
                     @drawDivisions @divisions

         drawUnits: (units, options) ->
+            if operationQueue.status == 'cancelled'
+                return
             @allMarkers.clearLayers()
             if units.filters?.bbox?
                 if @_skipBboxDrawing
@@ -163,6 +166,8 @@
             unless options?.keepViewport
                 @preAdapt?()
                 @map.adaptToLatLngs latLngs
+            if operationQueue.status == 'cancelled'
+                return
             @allMarkers.addLayers markers

         highlightSelectedUnit: (unit) ->