add formula 33 support

add formula 33 support

--- views/components/ship-parts/topalert.cjsx
+++ views/components/ship-parts/topalert.cjsx
@@ -142,6 +142,70 @@
   teitoku: parseFloat(teitokuSaku.toFixed(2))
   total: parseFloat(totalSaku.toFixed(2))

+  # Saku (33)
+  # 索敵スコア = Sigma(CiSi) + Sigma(sqrt(s)) - Ceil(0.4H) + 2M
+  #     Si(改修): 電探(1.25 * Sqrt(Star)) 水上偵察機(1.2 * Sqrt(Star))
+  #     Ci(装備):
+  #              6 0.6 艦上戦闘機
+  #              7 0.6 艦上爆撃機
+  #              8 0.8 艦上攻撃機
+  #              9 1.0 艦上偵察機
+  #             10 1.2 水上偵察機
+  #             11 1.1 水上爆撃機
+  #             12 0.6 小型電探
+  #             13 0.6 大型電探
+  #             26 0.6 対潜哨戒機
+  #             29 0.6 探照灯
+  #             34 0.6 司令部施設
+  #             35 0.6 航空要員
+  #             39 0.6 水上艦要員
+  #             40 0.6 大型ソナー
+  #             41 0.6 大型飛行艇
+  #             42 0.6 大型探照灯
+	#             45 0.6 水上戦闘機
+  #             93 大型電探(II) null
+  #             94 艦上偵察機(II) null
+  #     S(各艦毎の素索敵)
+  #     H(レベル)
+  #     M(空き数)
+
+getSaku33 = (deck) ->
+  {$ships, $slotitems, _ships, _slotitems} = window
+  totalSaku = shipSaku = itemSaku = teitokuSaku = 0
+  shipCount = 6
+  for shipId in deck.api_ship
+    continue if shipId == -1
+    shipCount -= 1
+    ship = _ships[shipId]
+    shipPureSaku = ship.api_sakuteki[0]
+    for itemId, slotId in ship.api_slot
+      continue unless itemId != -1 && _slotitems[itemId]?
+      item = _slotitems[itemId]
+      shipPureSaku -= item.api_saku
+      switch item.api_type[2]
+        when 8
+          itemSaku += item.api_saku * 0.8
+        when 9
+          itemSaku += item.api_saku * 1.0
+        when 10
+          itemSaku += (item.api_saku + 1.2 * Math.sqrt(item.api_level)) * 1.2
+        when 11
+          itemSaku += item.api_saku * 1.1
+        when 12
+          itemSaku += (item.api_saku + 1.25 * Math.sqrt(item.api_level)) * 0.6
+        when 13
+          itemSaku += (item.api_saku + 1.25 * Math.sqrt(item.api_level)) * 0.6
+        else
+          itemSaku += item.api_saku * 0.6
+    shipSaku += Math.sqrt(shipPureSaku)
+  teitokuSaku = Math.ceil(window._teitokuLv * 0.4)
+  totalSaku = shipSaku + itemSaku - teitokuSaku + 2 * shipCount
+
+  ship: parseFloat(shipSaku.toFixed(4))
+  item: parseFloat(itemSaku.toFixed(4))
+  teitoku: parseFloat(teitokuSaku.toFixed(4))
+  total: parseFloat(totalSaku.toFixed(4))
+
 getDeckMessage = (deck) ->
   {$ships, $slotitems, _ships} = window
   totalLv = totalShip = 0
@@ -155,6 +219,7 @@
   tyku: getTyku(deck)
   saku25: getSaku25(deck)
   saku25a: getSaku25a(deck)
+  saku33: getSaku33(deck)

 TopAlert = React.createClass
@@ -329,6 +394,7 @@
                 <Tooltip id='topalert-recon'>

2-5 {__ 'Autumn'}: {@messages.saku25a.ship} + {@messages.saku25a.item} - {@messages.saku25a.teitoku} = {@messages.saku25a.total}

2-5 {__ 'Old'}: {@messages.saku25.ship} + {@messages.saku25.recon} + {@messages.saku25.radar} = {@messages.saku25.total}

+
33: {@messages.saku33.total}

                 </Tooltip>
               }>
                 {__ 'LOS'}: {@messages.saku25a.total}


Fix bug on hashchange event.

Fix bug on hashchange event.

This handler is undefined at the point that it is installed.  The
definition is at the bottom of the file.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -189,7 +189,7 @@
 # We install these listeners directly (that is, we don't use installListener) because we still need to receive
 # events when Vimium is not enabled.
 window.addEventListener "focus", onFocus
-window.addEventListener "hashchange", checkEnabledAfterURLChange
+window.addEventListener "hashchange", -> checkEnabledAfterURLChange()

 initializeOnDomReady = ->
   # Tell the background page we're in the domReady state.


Refactor mode initialisation for clarity.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -103,6 +103,10 @@
       if area == "local" and changes.normalModeKeyStateMapping?.newValue
         @setKeyMapping changes.normalModeKeyStateMapping.newValue

+    # Initialize components which normal mode depends upon.
+    Scroller.init()
+    FindModeHistory.init()
+
   commandHandler: ({command: registryEntry, count}) ->
     count *= registryEntry.options.count ? 1
     count = 1 if registryEntry.noRepeat
@@ -128,8 +132,6 @@
   normalMode = new NormalMode
   new InsertMode permanent: true
   new GrabBackFocus if isEnabledForUrl
-  Scroller.init()
-  FindModeHistory.init()
   normalMode # Return the normalMode object (for the tests).

 initializeOnEnabledStateKnown = Utils.makeIdempotent ->


Unique Avatar: fix randomness

Unique Avatar: fix randomness

--- app/assets/javascripts/app/models/user.coffee
+++ app/assets/javascripts/app/models/user.coffee
@@ -90,8 +90,8 @@
       height = 226 * size/baseSize

       rng = new Math.seedrandom(@id)
-      x   = 0.5 * (width - size)
-      y   = 0.5 * (height - size)
+      x   = rng * (width - size)
+      y   = rng * (height - size)

       return App.view('avatar_unique')
         data: data


new method for calculate total producto

new method for calculate total producto

--- app/assets/javascripts/autocomplete.js.coffee
+++ app/assets/javascripts/autocomplete.js.coffee
@@ -16,23 +16,21 @@
       $this.closest(".fields").find("td:nth-child(3)").find(".producto_id").val ui.item.id
       $this.closest(".fields").find("td:nth-child(1)").find(".codigo").val ui.item.codigo
       $this.closest(".fields").find("td:nth-child(4)").find(".valor_unitario").val ui.item.precio_a
-      cantidad = $this.closest(".fields").find("td:nth-child(3)").find(".cantidad").val()
-      valor_unitario = $this.closest(".fields").find("td:nth-child(4)").find(".valor_unitario").val()
-      total = $this.closest(".fields").find("td:nth-child(7)").find(".total")
-      total.val(cantidad * valor_unitario)
-      calcular_valores()
+      calcular_total_producto($this)
+      calcular_valores_factura()

   $(".cantidad").on "input", ->
     $this = $(this)
-    cantidad = $this.val()
-    valor_unitario = $this.closest(".fields").find("td:nth-child(4)").find(".valor_unitario").val()
-    total = $this.closest(".fields").find("td:nth-child(7)").find(".total")
-    total.val(cantidad * valor_unitario)
-    calcular_valores()
+    calcular_total_producto($this)
+    calcular_valores_factura()

-calcular_total = ->
+calcular_total_producto = (componente) ->
+  cantidad = componente.closest(".fields").find("td:nth-child(3)").find(".cantidad").val()
+  valor_unitario = componente.closest(".fields").find("td:nth-child(4)").find(".valor_unitario").val()
+  total = componente.closest(".fields").find("td:nth-child(7)").find(".total")
+  total.val(cantidad * valor_unitario)

-calcular_valores = ->
+calcular_valores_factura = ->
   sum = 0
   $(".total").each ->
     sum += parseFloat($(this).val())


Fix computed property that depends on a promise.

--- app/assets/javascripts/models/paper.js.coffee
+++ app/assets/javascripts/models/paper.js.coffee
@@ -34,13 +34,16 @@
   ).property('title', 'shortTitle')

   allMetadataTasksCompleted: (->
-    @get('tasks').then (tasks) ->
-      tasks.filterBy('isMetadataTask').everyProperty('completed', true)
-  ).property('tasks.content.@each.isMetadataTask','tasks.content.@each.completed')
+    taskArray = @get('tasks')
+    if taskArray.get('isPending')
+      false
+    else
+      taskArray.filterBy('isMetadataTask').everyProperty('completed', true)
+  ).property('tasks.content.@each.isMetadataTask','tasks.content.@each.completed', 'tasks.isPending')

   editable: (->
-    !(@get('allTasksCompleted') and @get('submitted'))
-  ).property('allTasksCompleted', 'submitted')
+    !(@get('allMetadataTasksCompleted') and @get('submitted'))
+  ).property('allMetadataTasksCompleted', 'submitted')

   authors: (->
     @get('authorGroups').reduce(


OX-265 changed minimum move value and step size

--- app/assets/javascripts/oxalis/view/gui.coffee
+++ app/assets/javascripts/oxalis/view/gui.coffee
@@ -76,8 +76,8 @@
                           .onChange(@setMouseInversionY)

     fView = @gui.addFolder("Planes")
-    (fView.add @settings, "moveValue", 1, 10)
-                          .step(0.25)
+    (fView.add @settings, "moveValue", 0.1, 10)
+                          .step(0.1)
                           .name("Move Value")
                           .onChange(@setMoveValue)
     scale = @model.scaleInfo.baseVoxel


Fix bounds limiting on page load

Fix bounds limiting on page load

--- client.coffee
+++ client.coffee
@@ -48,7 +48,6 @@
 		mainElement = document.getElementsByTagName("main")[0]
 		mainElement.setAttribute 'id', 'main'
 		redraw.get();
-		limitToBounds()
 		gameState = Db.shared.get('gameState')
 		if gameState is 0 # Setting up game by user that added plugin
 			setupContent()
@@ -953,6 +952,7 @@
 		log "setupMap()"
 		if map?
 			log "map already initialized"
+			limitToBounds()
 		else if not L?
 			log "javascript not yet loaded"
 		else
@@ -961,6 +961,7 @@
 			window.map = L.mapbox.map('OpenStreetMap', 'nlthijs48.4153ad9d', {center: [52.249822176849, 6.8396973609924], zoom: 13, zoomControl:false, updateWhenIdle:false, detectRetina:true})
 			layer = L.mapbox.tileLayer('nlthijs48.4153ad9d', {reuseTiles: true})
 			log "Initialized MapBox map"
+			limitToBounds()

 # Setup map bounds
 limitToBounds = ->
@@ -974,6 +975,10 @@
 			if bounds? and loc1? and loc2?
 				map._layersMinZoom = map.getBoundsZoom(bounds.pad(0.05))
 				map.setMaxBounds(bounds.pad(0.05))
+				limit = () ->
+					map._layersMinZoom = map.getBoundsZoom(bounds.pad(0.05))
+					map.setMaxBounds(bounds.pad(0.05))
+				window.setTimeout(limit, 100)
 				#map.fitBounds(bounds); # Causes problems, because it zooms to max all the time
 			else
 				log "Bounds not existing"


html tags in allocations display correctly

--- app/assets/javascripts/pirates/codegui.coffee
+++ app/assets/javascripts/pirates/codegui.coffee
@@ -235,7 +235,7 @@
     if $row.length < 1 # new row
       $row = $ "
#{variable}	#{allocation}
"
       @_$watchlistDebuggerTbody.append $row
-
+    allocation = allocation.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
     $row.children('td:last').html allocation
     $row.children().addClass('highlight').delay(250).removeClass 'highlight', 1000, 'linear'


Satisfy Brush specs

Satisfy Brush specs

--- app/assets/javascripts/player/brush.js.coffee
+++ app/assets/javascripts/player/brush.js.coffee
@@ -8,7 +8,7 @@
     @_default ||= @create()

   @hash: (brush) ->
-    "#{brush.fg}_#{brush.bg}_#{brush.bright}_#{brush.underline}_#{brush.italic}_#{brush.blink}"
+    "#{brush.fg}_#{brush.bg}_#{brush.blink}_#{brush.bright}_#{brush.italic}_#{brush.underline}"

   @create: (options = {}) ->
     key = @hash options
@@ -21,12 +21,15 @@
     brush

   constructor: (options = {}) ->
-    @fg        = options.fg
-    @bg        = options.bg
-    @blink     = options.blink
-    @bright    = options.bright
-    @italic    = options.italic
-    @underline = options.underline
+    @fg        = undefined
+    @bg        = undefined
+    @blink     = false
+    @bright    = false
+    @italic    = false
+    @underline = false
+
+    for name, value of options
+      this[name] = value

   hash: ->
     AsciiIo.Brush.hash this


Fixed formatting of Help response

Fixed formatting of Help response

--- src/helpers/constants.coffee
+++ src/helpers/constants.coffee
@@ -92,12 +92,12 @@
                           `@ibizan [mode] [time] [date] [project] [notes]`\n
                           \n
                           Examples:\n
-                          @ibizan in\n
-                          @ibizan out\n
-                          @ibizan in at 9:15\n
-                          @ibizan out 4:30p yesterday\n
-                          @ibizan in #project-name\n
-                          @ibizan 3.5 hours today\n
+                          `@ibizan in`\n
+                          `@ibizan out`\n
+                          `@ibizan in at 9:15`\n
+                          `@ibizan out 4:30p yesterday`\n
+                          `@ibizan in #project-name`\n
+                          `@ibizan 3.5 hours today`\n
                           \n
                           Punches can be `in`, `out`, `vacation`, `sick` or `unpaid`
                           punches. You can also clock in independent blocks of time.


keep projects consistent with staffplans, link to user's staffplan

--- app/assets/javascripts/views/projects/user_view.js.coffee
+++ app/assets/javascripts/views/projects/user_view.js.coffee
@@ -28,7 +28,7 @@
     user: '''

-      {{user.first_name}} {{user.last_name}}
+      {{user.first_name}} {{user.last_name}}

     '''


correction of tva calculation ok

correction of tva calculation ok

--- app/assets/javascripts/backend/purchase_invoices.js.coffee
+++ app/assets/javascripts/backend/purchase_invoices.js.coffee
@@ -12,10 +12,9 @@
         vatRate = $(item).find('.total-column label.vat-rate').text().split("%")[0]
         amountIncludingTaxes = $(item).find('.total-column label.amount-including-taxes').text()

-        console.log(item)
         totalAmountExcludingTaxes += parseFloat(amountExcludingTaxes)
         totalAmountIncludingTaxes += parseInt(amountIncludingTaxes)
-        totalVatRate += parseFloat(parseFloat(totalAmountIncludingTaxes - totalAmountExcludingTaxes).toFixed(2))
+        totalVatRate += parseFloat(parseFloat(amountExcludingTaxes * vatRate / 100).toFixed(2))
         $('.nested-item-form').each (index, item) ->

       $('.invoice-totals .total-except-tax .total-value').text(totalAmountExcludingTaxes)


Add decimals to total headers ttc amount

--- app/assets/javascripts/backend/purchase_invoices.js.coffee
+++ app/assets/javascripts/backend/purchase_invoices.js.coffee
@@ -12,7 +12,7 @@
         vatRate = $(item).find('.total-column label.vat-rate').text().split("%")[0]

         totalAmountExcludingTaxes += parseFloat(amountExcludingTaxes)
-        totalAmountIncludingTaxes += parseInt(amountExcludingTaxes * (1 + (parseFloat(vatRate) / 100)))
+        totalAmountIncludingTaxes += parseFloat(amountExcludingTaxes * (1 + (parseFloat(vatRate) / 100)))
         totalVatRate += parseFloat(parseFloat(totalAmountIncludingTaxes - totalAmountExcludingTaxes).toFixed(2))
         $('.nested-item-form').each (index, item) ->


fix greater than and less than comparison by adding parseFloat for true numeric comparison

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -201,10 +201,10 @@
         if value.length > 0 then hide_questions = false
       when "is greater than"
         if $.isNumeric(value)
-          if value > answer then hide_questions = false
+          if parseFloat(value) > parseFloat(answer) then hide_questions = false
       when "is less than"
         if $.isNumeric(value)
-          if value < answer then hide_questions = false
+          if parseFloat(value) < parseFloat(answer) then hide_questions = false
       when "starts with"
         if value and value.slice(0, answer.length) == answer then hide_questions = false
       when "contains"


find checked radio button and get that value, instead of grabbing value of first radio button in list

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -142,8 +142,9 @@
   # get value of triggering question
   getValue: ($conditionElement) ->
     if $conditionElement.is(":radio")
-      if $conditionElement.is(":checked")
-        return $conditionElement.val()
+      selected = $("input[type='radio'][name='" + $conditionElement.attr('name') + "']:checked")
+      if selected.length > 0
+        return selected.val()
       else
         return
     if $conditionElement.is(":checkbox")


Refactored catalog service to use interceptor/model loading

--- app/assets/javascripts/sprangular/resources/catalog.coffee
+++ app/assets/javascripts/sprangular/resources/catalog.coffee
@@ -1,47 +1,23 @@
-Sprangular.service 'Catalog', ($http, $q, _, Product) ->
-
-  fetchDefer = $q.defer()
+Sprangular.service 'Catalog', ($http, $q, _) ->

   service =
-    products: []
-    variants: []
+    products: (page=1) ->
+      @getPaged('/products', page)

-    init: ->
-      catalog = this
-      $http.get '/products.json'
-        .success (data) ->
-          catalog.products = (catalog._populateSingle(product) for product in data.products)
-          catalog.variants = []
-          for product in service.products
-            catalog.variants = catalog.variants.concat product.variants
-          fetchDefer.resolve catalog
-        .error (data) ->
-          fetchDefer.resolve service
+    taxon: (path,page=1) ->
+      @getPaged("/products/?category=#{path}", page)

-    fetch: ->
-      return fetchDefer.promise
+    find: (id) ->
+      @get("/products/#{id}")

-    # query = {id: ...} or {slug: ...}
-    findProduct: (query) ->
-      _.where(@products, query)[0]
+    get: (path) ->
+      $http.get(path, class: Sprangular.Product)

-    findVariant: (query) ->
-      _.where(@variants, query)[0]
+    getPaged: (path, page=1) ->
+      $http.get(path, params: {per_page: 40, page: page})
+           .then (response) ->
+             list = Sprangular.extend(response.data?.products || [], Sprangular.Product)
+             list.isLastPage = list.length < 40
+             list

-    _populateSingle: (data) ->
-      Product.load data
-
-    # query = {id: ...} or {slug: ...}
-    findSibling: (delta, query) ->
-      currentEl = @findProduct(query)
-      currentPos = _.indexOf(@products, currentEl)
-      productsLgt = @products.length - 1
-      deltaPos = currentPos + delta
-      siblingPos = switch
-        when deltaPos > productsLgt then 0
-        when deltaPos < 0 then productsLgt
-        else deltaPos
-      url = @products[siblingPos]
-
-  service.init()
   service


Replaces variant name with product name

Replaces variant name with product name

--- app/assets/javascripts/sprangular/services/checkout.coffee
+++ app/assets/javascripts/sprangular/services/checkout.coffee
@@ -100,7 +100,7 @@
       for item in order.items
         ga "ecommerce:addItem",
           id:       order.number
-          name:     item.variant.name
+          name:     item.variant.product.name
           sku:      item.variant.sku
           price:    item.price
           quantity: item.quantity


Removed trailing whitespace in message view.

--- app/assets/javascripts/views/clouds/chat/message.js.coffee
+++ app/assets/javascripts/views/clouds/chat/message.js.coffee
@@ -1,42 +1,42 @@
 class Cloudsdale.Views.CloudsChatMessage extends Backbone.View
-
+
   template: JST['clouds/chat/message']
-
+
   model: 'message'
   tagName: 'div'
   className: 'chat-message'
-
+
   events:
     'click a[data-action="showUser"]' : 'openUserDialog'
-
+
   initialize: ->
     @render()
     @bindEvents()
-
+
   render: ->
     $(@el).html(@template(model: @model))
     .addClass("role-#{@model.user().get('role')}")
     .addClass("device-#{@model.get('device')}")
-
+
     if @model.get('author_id') == @model.topic().get('owner_id')
       $(@el).addClass("role-owner")
-
+
     if _.include(@model.topic().get('moderator_ids'), @model.get('author_id')) and @model.get('author_id') != @model.topic().get('owner_id')
       $(@el).addClass("role-mod")
-
+
     @.$('.chat-message-avatar > a, .chat-message-head > a').attr('data-entity-id',@model.id).attr('data-action',"showUser")
-
+
     @appendContent(@model)
     @appendDrops(@model)
-
+
     $(@el).addClass('self-reference') if @model.selfReference()
-
+
     this
-
+
   bindEvents: ->
     @model.user().on 'change', (event,user) =>
       @refreshGfx()
-
+
   refreshGfx: ->
     @.$('.chat-message-avatar a img').attr('src',@model.user().get('avatar').thumb)
     @.$('.chat-message-head a').text(@model.user().get('name'))
@@ -45,38 +45,38 @@

   # Appends content to the message
   appendContent: (message) ->
-
+
     content = message.get('content')
     content = escapeHTML(content).autoLink({ target: "_blank", rel: 'safe' })
-
+
     content = content + "\\n"

     # Tab fix
     content = content.replace(/\\t/ig,"    ")
-
+
     # Greentext
     content = content.replace(/((^>|\\n>)[\w\d\s\!\'\"\.\,\-\_\/\?\:\&\^\<\>\=\#\%\+\*\~\\√\◊\(\)]*\\n)/ig,"$1")
-
+
     # Zee text.
     # if @model.get('user').get('role') == 'founder'
     #   content = content.replace(/^\[ZEE\](.*)/ig,"$1")
-
+
     content = content.replace(/\\n$/ig,"").replace(/\\n/ig,"
")
-
+
     elem = $("

")
     if message.selfReference()
       elem.html(message.get('content').replace(/^\/me/i,message.user().get('name')))
     else
       elem.html(content)
-
+
     @.$('.chat-message-content').append(elem)
     @.$('.chat-message-meta').text(message.timestamp().toString('HH:mm:ss'))
-
+
   appendDrops: (message) ->
     message.drops().each (drop) =>
       view = new Cloudsdale.Views.CloudsDropsListItem(model: drop)
       @.$('ul.chat-message-drops').append(view.el)
-
+
   openUserDialog: (event) ->
     $.event.trigger "#{@model.topic().type}s:#{@model.topic().id}:users:show", @model.user()
-    false+    false


Add source header

Add source header

--- src/jquery.turbolinks.coffee
+++ src/jquery.turbolinks.coffee
@@ -1,4 +1,10 @@
-# jQuery.Turbolinks ~ https://github.com/kossnocorp/jquery.turbolinks
+###
+jQuery.Turbolinks ~ https://github.com/kossnocorp/jquery.turbolinks
+jQuery plugin for drop-in fix binded events problem caused by Turbolinks
+
+The MIT License
+Copyright (c) 2012-2013 Sasha Koss & Rico Sta. Cruz
+###

 $ = window.jQuery or require?('jquery')
 $document = $(document)


Fix expanding first truncated sidebar item

--- assets/javascripts/views/sidebar/sidebar_hover.coffee
+++ assets/javascripts/views/sidebar/sidebar_hover.coffee
@@ -40,7 +40,7 @@
   position: =>
     if @cursor
       top = $.rect(@cursor).top
-      if top > @offsetTop
+      if top >= @offsetTop
         @clone.style.top = top + 'px'
       else
         @hide()


Allow tip instances to be mixin-compatible with _setTip.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -138,8 +138,10 @@
       _.bindAll @, '_onTriggerMouseMove', '_setBounds'

     init: ->
-      # The element represented by this API is `$tip`. We build it.
-      @$tip = $ '<div>'
+      # The element represented by this API is `$tip`. We build it. Alias it to
+      # `$el` for any mixins to consume.
+      @_setTip = ($tip) => @$tip = @$el = $tip
+      @_setTip $ '<div>'
       # Infer `doStem` and `doFollow` flags from respective `classNames` entries.
       @doStem = @classNames.stem isnt ''
       @doFollow = @classNames.follow isnt ''
@@ -235,7 +237,8 @@
       return no if @$tip.html().length
       html = @htmlOnRender()
       if not (html? and html.length) then html = @_defaultHtml()
-      @$tip = $(html).addClass @classNames.follow
+      $tip = $(html).addClass @classNames.follow
+      @_setTip $tip
       @$tip.prependTo @$context

     # `_inflateByTrigger` will reset and update `$tip` for the given trigger, so


Slightly simplify some code in the canvas view model.

--- app/scripts/viewmodels/drawings/canvas.coffee
+++ app/scripts/viewmodels/drawings/canvas.coffee
@@ -53,12 +53,12 @@
       "#{element.type}-#{element.id}"

     addNode: (parameters) =>
-      node = @run(new CreateNode(@drawing, parameters))
+      @run(new CreateNode(@drawing, parameters))

     addLink: (parameters) =>
-      link = @run(new CreateLink(@drawing, parameters))
+      @run(new CreateLink(@drawing, parameters))

-    run: (command) ->
+    run: (command) =>
       @commander.run(command)

     elementAt: (point) =>


Fix #3598: Make unary + and - generate _refs

Before commit c056c93e `Op::isComplex()` used to return true always. As far as I
understand, that commit attempts to exclude code such as `+1` and `-2` from
being marked as complex (and thus getting cached into `_ref` variables
sometimes). CoffeeScript is supposed to generate readable output so that choice
is understandable. However, it also excludes code such as `+a` (by mistake I
believe), which can cause `a` to be coerced multiple times. This commit fixes
this by only excluding unary + and - ops followed by a number.

--- nodes.coffee
+++ nodes.coffee
@@ -1639,7 +1639,8 @@
     not @second

   isComplex: ->
-    not (@isUnary() and @operator in ['+', '-']) or @first.isComplex()
+    not (@isUnary() and @operator in ['+', '-'] and
+         @first instanceof Value and @first.isSimpleNumber())

   # Am I capable of
   # [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin)?


CoffeeScript lints cleanly again.

CoffeeScript lints cleanly again.

--- nodes.coffee
+++ nodes.coffee
@@ -317,6 +317,7 @@
     @base       = base
     @properties = props or []
     @[tag]      = true if tag
+    return this

   children: ['base', 'properties']

@@ -1173,6 +1174,7 @@
     @first    = first
     @second   = second
     @flip     = !!flip
+    return this

   # The map of conversions from CoffeeScript to JavaScript symbols.
   CONVERSIONS =
@@ -1660,7 +1662,7 @@
       function ctor() { this.constructor = child; }
       ctor.prototype = parent.prototype;
       child.prototype = new ctor;
-      for (var key in parent) if (__hasProp.call(parent, key)) child[key] = parent[key];
+      for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
       child.__super__ = parent.prototype;
       return child;
     }


satyr 4e39e2e -- efficient closures in loops -- pulling them out.

--- nodes.coffee
+++ nodes.coffee
@@ -1335,6 +1335,7 @@
         when -1 then '--'
         else (if pvar < 0 then ' -= ' + pvar.slice 1 else ' += ' + pvar)
     varPart = idt + namePart + ';\n' if namePart
+    defPart += @pluckDirectCall o, body, name, index unless @pattern
     code = guardPart + varPart
     unless body.isEmpty()
       if o.level > LEVEL_TOP or @returns
@@ -1347,6 +1348,30 @@
       code    += body.compile o, LEVEL_TOP
     code = '\n' + code + '\n' + @tab if code
     defPart + @tab + "for (#{forPart}) {#{code}}" + retPart
+
+  pluckDirectCall: (o, body, name, index) ->
+    defs = ''
+    for expr, idx in body.expressions
+      expr = expr.unwrapAll()
+      continue unless expr instanceof Call
+      val = expr.variable.unwrapAll()
+      continue unless val instanceof Code and not expr.args.length or
+        val instanceof Value and val.base instanceof Code and
+        val.properties.length is 1 and
+        val.properties[0].name?.value is 'call'
+      fn    = val.base or val
+      ref   = new Literal o.scope.freeVariable 'fn'
+      base  = new Value ref
+      args  = compact [name, index]
+      args.reverse() if @object
+      for arg, i in args
+        fn.params.push new Param args[i] = new Literal arg
+      if val.base
+        [val.base, base] = [base, val]
+        args.unshift new Literal 'this'
+      body.expressions[idx] = new Call base, args
+      defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'
+    defs

 #### Switch


Fix #3502: Define param variables when expansion

--- nodes.coffee
+++ nodes.coffee
@@ -1360,8 +1360,8 @@
     for param in @params when param not instanceof Expansion
       o.scope.parameter param.asReference o
     for param in @params when param.splat or param instanceof Expansion
-      for {name: p} in @params when param not instanceof Expansion
-        if p.value then o.scope.add p.value, 'var', yes
+      for p in @params when p not instanceof Expansion and p.name.value
+        o.scope.add p.name.value, 'var', yes
       splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
                           new Value new Literal 'arguments'
       break


fixing single evaluation of functions used in chained comparisons wrapped in parentheses.

--- nodes.coffee
+++ nodes.coffee
@@ -97,6 +97,10 @@
       return true if block(node)
       return true if node.contains and node.contains block
     false
+
+  # Is this node of a certain type, or does it contain the type?
+  contains_type: (type) ->
+    this instanceof type or @contains (n) -> n instanceof type

   # Convenience for the most common use of contains. Does the node contain
   # a pure statement?
@@ -928,7 +932,7 @@
   #     true
   compile_chain: (o) ->
     shared: @first.unwrap().second
-    [@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
+    [@first.second, shared]: shared.compile_reference(o) if shared.contains_type CallNode
     [first, second, shared]: [@first.compile(o), @second.compile(o), shared.compile(o)]
     "($first) && ($shared $@operator $second)"


:sparkles: fix test

:sparkles: fix test

--- build/task/cover.coffee
+++ build/task/cover.coffee
@@ -59,8 +59,7 @@
           .pipe mocha()
           .pipe istanbul.writeReports()
       when 'ci:cover'
-        gulp.src ['coverage/**/*.*'],
-          base:'coverage'
+        gulp.src ['coverage/**/*.*']
           .pipe gulp.dest 'ci/cover/'+config.branch

 module.exports = CoverTask


better change detection for hosts

better change detection for hosts

--- src/main.iced
+++ src/main.iced
@@ -51,9 +51,10 @@
       @e.poke()

     $('#input-host').keyup =>
-      v = $('#input-host').val()
-      $('#input-saved-host').val('') unless v is @e.get 'host'
-      @e.set "host", v
+      before = @e.get 'host'
+      @e.set "host", $('#input-host').val()
+      after = @e.get 'host'
+      $('#input-saved-host').val('') unless before is after
       @update_save_button()
       @e.poke()


Add separate handlers for field and herbarium page events

--- overrides.coffee
+++ overrides.coffee
@@ -38,26 +38,34 @@
 herbarium_page = currentProject.classifyPages[0]
 field_page = currentProject.classifyPages[1]

-{decisionTree, subjectViewer} = herbarium_page
-
-ms = subjectViewer.markingSurface
+for page in currentProject.classifyPages
+  do (page) ->
+    page.ms = page.subjectViewer.markingSurface
+    # set the image scale if not already set
+    page.ms.on 'marking-surface:add-tool', (tool) ->
+      @rescale() if @scaleX is 0

 subject_metadata = new SubjectMetadata
 herbarium_page.el.find('.decision-tree').prepend subject_metadata.el

-# set the image scale if not already set
-ms.on 'marking-surface:add-tool', (tool) ->
-  @rescale() if @scaleX is 0

 herbarium_page.on herbarium_page.LOAD_SUBJECT, (e, subject)->
+  {ms, subjectViewer, decisionTree} = herbarium_page
   ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight
-
-herbarium_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->
-  {metadata} = subjectViewer.subject
-  task.reset if task.key is 'verify'
+  {metadata} = subject
+  decisionTree.tasks.verify.setDefaults
     date: metadata.date
     locality: metadata.locality
     vc: metadata.vc
+
+field_page.on field_page.LOAD_SUBJECT, (e, subject)->
+  {ms, subjectViewer, decisionTree} = field_page
+  ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight
+  {metadata} = subject
+  decisionTree.tasks.species.setDefaults
+    species: metadata.species
+
+# herbarium_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task, index}})->

 zoom_timeout = null


notify: presence detection restored

notify: presence detection restored

--- src/scripts/notify.coffee
+++ src/scripts/notify.coffee
@@ -99,7 +99,6 @@
               body

   checkIfOffline = (user, callback) ->
-    ###
     if process.env.HUBOT_HIPCHAT_TOKEN
       options =
         host: "api.hipchat.com"
@@ -119,8 +118,7 @@
           callback err
       req.end()
     else
-    ###
-    callback null, true
+      callback null, true

   robot.hear /@(\w+)/i, (msg) ->
     sender   = msg.message.user.name.toLowerCase()


security: close a vector for a reflected XSS in domain creation form

This closes a security hole discovered by our user Nathan Malcolm

--- client/Environments/views/domains/domaincreateform.coffee
+++ client/Environments/views/domains/domaincreateform.coffee
@@ -54,8 +54,10 @@
     {message}             = @newDomainEntryForm
     {createButton}        = @newDomainEntryForm.buttons
     {domains, domainName} = @newDomainEntryForm.inputs
-    domainName            = "#{domainName.getValue()}.#{domains.getValue()}"
     {getDomainInfo, getDomainSuggestions} = KD.remote.api.JDomain
+
+    domainName =
+      Encoder.XSSEncode "#{domainName.getValue()}.#{domains.getValue()}"

     @newDomainEntryForm.domainListView?.unsetClass 'in'

@@ -93,14 +95,17 @@
   createSubDomain: ->
     {domains, domainName} = @subDomainEntryForm.inputs
     {createButton}        = @subDomainEntryForm.buttons
-    domainName            = domainName.getValue()
+
+    domainName = domainName.getValue()

     # Check given subdomain
     unless subDomainPattern.test domainName
       createButton.hideLoader()
       return notifyUser "#{domainName} is an invalid subdomain."

-    domainName = "#{domainName}.#{domains.getValue()}"
+    domainName =
+      Encoder.XSSEncode "#{domainName}.#{domains.getValue()}"
+
     domainType = 'subdomain'
     regYears   = 0

@@ -131,8 +136,9 @@
     , callback

   showSuccess:(domain) ->
+    domainName =
+      Encoder.XSSEncode @subDomainEntryForm.inputs.domainName.getValue()

-    {domainName} = @subDomainEntryForm.inputs
     @emit 'DomainSaved', domain
     @successNote?.destroy()

@@ -140,7 +146,7 @@
       tagName  : 'p'
       cssClass : 'success'
       partial  : """
-        Your subdomain #{domainName.getValue()} has been added.
+        Your subdomain #{domainName} has been added.
         You can dismiss this panel and point your new domain to one of your VMs
         on the right.
       """


styleguide: Use a constant in place of option

--- client/app/lib/providers/environmentsprogressmodal.coffee
+++ client/app/lib/providers/environmentsprogressmodal.coffee
@@ -3,6 +3,8 @@
 HelpSupportModal      = require '../commonviews/helpsupportmodal'
 JView                 = require '../jview'

+
+DEFAULT_PERCENTAGE = 10

 ###*
@@ -86,7 +88,7 @@
   ###*
    * Create the progress bar.
   ###
-  createProgressBar: (initial = 10) ->
+  createProgressBar: (initial = DEFAULT_PERCENTAGE) ->

     @container.addSubView @progressBar = new kd.ProgressBarView { initial }

@@ -115,7 +117,7 @@
    *
    * @param {Number} percentage - The percentage to set the bar to.
   ###
-  updatePercentage: (percentage = 10) ->
+  updatePercentage: (percentage = DEFAULT_PERCENTAGE) ->

     @progressBar.updateBar percentage


use contentModal for modal appeared when user close the unsaved file

--- client/ide/lib/views/tabview/ideapplicationtabview.coffee
+++ client/ide/lib/views/tabview/ideapplicationtabview.coffee
@@ -4,7 +4,7 @@
 IDETabHandleView    = require './idetabhandleview'
 ApplicationTabView  = require 'app/commonviews/applicationview/applicationtabview'
 SplitRegionView     = require './region/splitregionview'
-
+contentModal = require 'app/components/contentModal'

 module.exports = class IDEApplicationTabView extends ApplicationTabView

@@ -73,15 +73,20 @@
     file    = ace.getData()

     @askForSaveModal?.destroy()
-    @askForSaveModal = new KDModalView
+    @askForSaveModal = new contentModal
       width         : 620
-      cssClass      : 'modal-with-text'
-      title         : 'Do you want to save your changes?'
-      content       : "
#{content}
"
+      cssClass      : 'modal-with-text content-modal'
+      title         : 'Do You Want to Save Your Changes?'
+      content       : "
#{content}
"
       overlay       : yes
       buttons       :
+        'Cancel'    :
+          cssClass  : 'solid cancel medium'
+          title     : 'Cancel'
+          callback  : =>
+            @askForSaveModal.destroy()
         'SaveClose' :
-          cssClass  : 'solid green medium'
+          cssClass  : 'solid medium'
           title     : 'Save and Close'
           callback  : =>
             if file.isDummyFile()
@@ -96,18 +101,13 @@

             @askForSaveModal.destroy()
         'DontSave'  :
-          cssClass  : 'solid red medium'
+          cssClass  : 'solid medium'
           title     : "Don't Save"
           callback  : =>
             @removePane_ pane
             @parent.handleCloseSplitView pane
             @askForSaveModal.destroy()
             file.emit 'FileContentsNeedsToBeRefreshed'
-        'Cancel'    :
-          cssClass  : 'solid light-gray medium'
-          title     : 'Cancel'
-          callback  : =>
-            @askForSaveModal.destroy()

   dragEnter: (event) ->


using 'yield' automatically turns functions into generators

--- nodes.coffee
+++ nodes.coffee
@@ -455,7 +455,6 @@
       answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
     answer.push @makeCode ";"
     return answer
-

 #### Value

@@ -1302,6 +1301,9 @@
     @params  = params or []
     @body    = body or new Block
     @bound   = tag is 'boundfunc'
+    @isGenerator = false
+    @body.traverseChildren false, (child) =>
+      @isGenerator = true if child.operator is 'yield'

   children: ['params', 'body']

@@ -1369,6 +1371,7 @@
       uniqs.push name
     @body.makeReturn() unless wasEmpty or @noReturn
     code  = 'function'
+    code += '*' if @isGenerator
     code  += ' ' + @name if @ctor
     code  += '('
     answer = [@makeCode(code)]


webgl: revert line glyph for now

webgl: revert line glyph for now

--- bokehjs/src/coffee/renderer/glyph/line.coffee
+++ bokehjs/src/coffee/renderer/glyph/line.coffee
@@ -9,7 +9,7 @@

   _render: (ctx, indices, {sx, sy}) ->
     drawing = false
-    @visuals.line.set_value(ctx)  # Set before gl, because it uses it too
+    @visuals.line.set_value(ctx)

     for i in indices
       if !isFinite(sx[i]+sy[i]) and drawing
@@ -104,7 +104,6 @@
   type: 'Line'
   visuals: ['line']

-
 module.exports =
   Model: Line
   View: LineView


fixing return node squashing the possiblity of a ternary, when there should be one. Issue #475

--- nodes.coffee
+++ nodes.coffee
@@ -1290,10 +1290,10 @@
   constructor: (condition, body, tags) ->
     @condition: condition
     @body:      body
-    @elseBody: null
+    @elseBody:  null
     @tags:      tags or {}
     @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert
-    @isChain:  false
+    @isChain:   false

   bodyNode: -> @body?.unwrap()
   elseBodyNode: -> @elseBody?.unwrap()
@@ -1345,9 +1345,12 @@
     if @isStatement() then @compileStatement(o) else @compileTernary(o)

   makeReturn: ->
-    @body:      and @ensureExpressions(@body.makeReturn())
-    @elseBody:  and @ensureExpressions(@elseBody.makeReturn())
-    this
+    if @isStatement()
+      @body:      and @ensureExpressions(@body.makeReturn())
+      @elseBody:  and @ensureExpressions(@elseBody.makeReturn())
+      this
+    else
+      new ReturnNode this

   ensureExpressions: (node) ->
     if node instanceof Expressions then node else new Expressions [node]


null values should be preserved as a property

--- bokehjs/src/coffee/renderer/properties.coffee
+++ bokehjs/src/coffee/renderer/properties.coffee
@@ -46,7 +46,7 @@

       value = styleprovider.mget(attrname)
       if not value?
-        null
+        @[attrname].value = null
       else if _.isString(value)
         @[attrname].value = value
       else if _.isObject(value)
@@ -60,7 +60,7 @@

       value = styleprovider.mget(attrname)
       if not value?
-        null
+        @[attrname].value = null
       else if _.isBoolean(value)
         @[attrname].value = value
       else if _.isString(value)
@@ -79,7 +79,7 @@

       value = styleprovider.mget(attrname)
       if not value?
-        null
+        @[attrname].value = null
       else if _.isNumber(value)
         @[attrname].value = value
       else if _.isString(value)
@@ -95,7 +95,7 @@

       value = styleprovider.mget(attrname)
       if not value?
-        null
+        @[attrname].value = null
       else if _.isString(value)
         if svg_colors[value]? or value.substring(0, 1) == "#"
           @[attrname].value = value
@@ -115,7 +115,7 @@

       value = styleprovider.mget(attrname)
       if not value?
-        null
+        @[attrname].value = null
       else if _.isString(value)
         @[attrname].field = value
       else if _.isArray(value)
@@ -132,7 +132,7 @@
       levels = vals.split(" ")
       value = styleprovider.mget(attrname)
       if not value?
-        null
+        @[attrname].value = null
       else if _.isString(value)
         if value in levels
           @[attrname].value = value


satyr 1612b04 ... enabled break/continue in comprehensions.

--- nodes.coffee
+++ nodes.coffee
@@ -85,11 +85,12 @@
   # Recursively traverses down the *children* of the nodes, yielding to a block
   # and returning true when the block finds a match. `contains` does not cross
   # scope boundaries.
-  contains: (block, arg) ->
+  contains: (pred) ->
     contains = no
-    @traverseChildren false, (node, arg) ->
-      if (rearg = block node, arg) is true then not contains = true else if arg? then rearg
-    , arg
+    @traverseChildren no, (node) ->
+      if pred node
+        contains = yes
+        return no
     contains

   # Is this node of a certain type, or does it contain the type?
@@ -99,11 +100,7 @@
   # Convenience for the most common use of contains. Does the node contain
   # a pure statement?
   containsPureStatement: ->
-    @isPureStatement() or @contains (node, func) ->
-      func(node) or if node instanceof While or node instanceof For
-        (node) -> node instanceof Return
-      else func
-    , (node) -> node.isPureStatement()
+    @isPureStatement() or @contains (node) -> node.isPureStatement()

   # `toString` representation of the node, for inspecting the parse tree.
   # This is what `coffee --nodes` prints out.
@@ -125,10 +122,10 @@
     @eachChild (node) -> nodes.push node
     nodes

-  traverseChildren: (crossScope, func, arg) ->
+  traverseChildren: (crossScope, func) ->
     @eachChild (child) ->
-      return false if (arg = func child, arg) is false
-      child.traverseChildren crossScope, func, arg
+      return false if func(child) is false
+      child.traverseChildren crossScope, func

   invert: ->
     new Op '!', this
@@ -996,13 +993,20 @@
     @condition = if options?.invert then condition.invert() else condition
     @guard     = options?.guard

-  addBody: (body) ->
-    @body = body
+  addBody: (@body) ->
     this

   makeReturn: ->
     @returns = true
     this
+
+  containsPureStatement: ->
+    {expressions} = @body
+    i = expressions.length
+    return true if expressions[--i]?.containsPureStatement()
+    ret = (node) -> node instanceof Return
+    return true while i-- when expressions[i].contains ret
+    false

   # The main difference from a JavaScript *while* is that the CoffeeScript
   # *while* can be used as a part of a larger expression -- while loops may
@@ -1259,10 +1263,11 @@

   isStatement: YES

-  constructor: (@body, head) ->
+  constructor: (body, head) ->
     if head.index instanceof Value
       throw SyntaxError 'index cannot be a pattern matching expression'
     extend this, head
+    @body    = Expressions.wrap [body]
     @step  or= new Literal 1 unless @object
     @pattern = @name instanceof Value
     @returns = false
@@ -1270,6 +1275,8 @@
   makeReturn: ->
     @returns = true
     this
+
+  containsPureStatement: While::containsPureStatement

   compileReturnValue: (val, o) ->
     return '\n' + new Return(new Literal val).compile o if @returns
@@ -1282,11 +1289,11 @@
   # some cannot.
   compileNode: (o) ->
     {scope} = o
+    {body}  = this
     name    = not @pattern and @name?.compile o
     index   = @index?.compile o
     ivar    = if not index then scope.freeVariable 'i' else index
     varPart = guardPart = defPart = retPart = ''
-    body    = Expressions.wrap [@body]
     idt     = @idt 1
     scope.find(name,  immediate: yes) if name
     scope.find(index, immediate: yes) if index


fix(aliases): Show from field whenever aliases are present

--- internal_packages/composer/lib/composer-view.cjsx
+++ internal_packages/composer/lib/composer-view.cjsx
@@ -574,10 +574,11 @@
     account = AccountStore.itemWithId @_proxy?.draft().accountId
     @setState {enabledFields, account}

-  _shouldShowFromField: (draft) ->
+  _shouldShowFromField: (draft) =>
     return false unless draft
-    return AccountStore.items().length > 1 and
-           draft.files.length is 0
+    account = AccountStore.itemWithId(draft.accountId)
+    return false unless account
+    return account.aliases.length > 1

   _shouldEnableSubject: =>
     return false unless @_proxy


Fix nicknames in comments issues

Fix nicknames in comments issues

 - Fix expandUsers regexp to ignore @bar in foo@bar
 - Use same font-size for nickname links too

--- core/utils.coffee
+++ core/utils.coffee
@@ -1,14 +1,14 @@
 # utils singleton
 # -------------------------
-#
+#
 # -------------------------
 __utils =

   idCounter : 0
-
+
   formatPlural:(count, noun)->
     "#{count or 0} #{if count is 1 then noun else Inflector.pluralize noun}"
-
+
   selectText:(element)->
     doc   = document
     if doc.body.createTextRange
@@ -21,27 +21,27 @@
       range.selectNodeContents element
       selection.removeAllRanges()
       selection.addRange range
-
+
   getCallerChain:(args, depth)->
     {callee:{caller}} = args
     chain = [caller]
     while depth-- and caller = caller?.caller
       chain.push caller
     chain
-
+
   getUniqueId:->
     "#{__utils.getRandomNumber(100000)}_#{Date.now()}"

   getRandomNumber :(range)->
-    range = range or 1000000
+    range = range or 1000000
     Math.floor Math.random()*range+1
-
+
   uniqueId : (prefix)->
     id = __utils.idCounter++
     if prefix? then "#{prefix}#{id}" else id

-  getRandomRGB :()->
-    "rgb(#{@getRandomNumber(255)},#{@getRandomNumber(255)},#{@getRandomNumber(255)})"
+  getRandomRGB :()->
+    "rgb(#{@getRandomNumber(255)},#{@getRandomNumber(255)},#{@getRandomNumber(255)})"

   getRandomHex : ->
     # hex = (Math.random()*0xFFFFFF<<0).toString(16)
@@ -58,7 +58,7 @@

     a = /\+/g  # Regex for replacing addition symbol with a space
     r = /([^&;=]+)=?([^&;]*)/g
-    d = (s)->
+    d = (s)->
       decodeURIComponent s.replace a, " "
     q = tag.substring 1

@@ -66,24 +66,24 @@
       hashParams[d e[1] ] = d e[2]

     hashParams
-
+
   capAndRemovePeriods:(path)->
     newPath = for arg in path.split "."
       arg.capitalize()
     newPath.join ""
-
+
   slugify:(title = "")->
     url = title
       .toLowerCase()                # change everything to lowercase
-      .replace(/^\s+|\s+$/g, "")    # trim leading and trailing spaces
+      .replace(/^\s+|\s+$/g, "")    # trim leading and trailing spaces
       .replace(/[_|\s]+/g, "-")     # change all spaces and underscores to a hyphen
       .replace(/[^a-z0-9-]+/g, "")  # remove all non-alphanumeric characters except the hyphen
       .replace(/[-]+/g, "-")        # replace multiple instances of the hyphen with a single instance
-      .replace(/^-+|-+$/g, "")      # trim leading and trailing hyphens
+      .replace(/^-+|-+$/g, "")      # trim leading and trailing hyphens

   stripTags:(value)->
     value.replace /<(?:.|\n)*?>/gm, ''
-
+
   applyTextExpansions: (text)->
     return null unless text
     # @expandWwwDotDomains @expandUrls @expandUsernames @expandTags text
@@ -96,7 +96,7 @@

   expandUsernames: (text) ->
     return null unless text
-    text.replace /[@]+[A-Za-z0-9-_]+/g, (u) ->
+    text.replace /\ [@]+[A-Za-z0-9-_]+/g, (u) ->
       username = u.replace "@", ""
       u.link "#!/member/#{username}"

@@ -128,7 +128,7 @@
       # prefer to end the teaser at the end of a sentence (a period).
       # failing that prefer to end the teaser at the end of a word (a space).
       candidate = tryToShorten(longText, '.') or tryToShorten longText, ' ', '...'
-
+
       if candidate?.length > minLength
         candidate
       else
@@ -139,12 +139,12 @@

   getYearOptions  : (min = 1900,max = Date::getFullYear())->
     ({ title : "#{i}", value : i} for i in [min..max])
-
+
   getFileExtension: (path) ->
     fileName = path or ''
     [name, extension...]  = fileName.split '.'
     extension = if extension.length is 0 then '' else extension[extension.length-1]
-
+
   getFileType: (extension)->

     fileType = "unknown"
@@ -174,7 +174,7 @@
       ]
       sound   : ["aac","au","gsm","mid","midi","snd","wav","3g2","mp3","asx","asf"]
       app     : ["kdapp"]
-
+

     for own type,set of _extension_sets
       for ext in set
@@ -193,20 +193,20 @@
       'r-x': 5
       'rw-': 6
       'rwx': 7
-
+
   symbolsPermissionToOctal: (permissions) ->
     permissions = permissions.substr(1)
-
+
     user    = permissions.substr 0, 3
     group   = permissions.substr 3, 3
     other   = permissions.substr 6, 3
     octal   = '' + @_permissionMap()[user] + @_permissionMap()[group] + @_permissionMap()[other]
-
+
   getNameFromFullname :(fullname)->
     fullname.split(' ')[0]

   getParentPath :(path)->
-
+
     path = path.substr(0, path.length-1) if path.substr(-1) is "/"
     parentPath = path.split('/')
     parentPath.pop()
@@ -217,7 +217,7 @@
       fn = duration
       duration = 0
     setTimeout fn, duration
-
+
   killWait:(id)-> clearTimeout id

   getCancellableCallback:(callback)->


Add versionCompare

Add versionCompare

--- core/utils.coffee
+++ core/utils.coffee
@@ -479,6 +479,71 @@

       return __utils.generatePassword length, memorable, pattern, "" + prefix + chr

+  # Version Compare
+  # https://github.com/balupton/bal-util/blob/master/src/lib/compare.coffee
+  # http://phpjs.org/functions/version_compare
+  # MIT Licensed http://phpjs.org/pages/license
+  versionCompare: (v1,operator,v2) ->
+    i  = x = compare = 0
+    vm =
+      dev   : -6
+      alpha : -5
+      a     : -5
+      beta  : -4
+      b     : -4
+      RC    : -3
+      rc    : -3
+      '#'   : -2
+      p     : -1
+      pl    : -1
+
+    prepVersion = (v) ->
+      v = ('' + v).replace(/[_\-+]/g, '.')
+      v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.')
+      if !v.length then [-8]
+      else v.split('.')
+
+    numVersion = (v) ->
+      if !v then 0
+      else
+        if isNaN(v) then vm[v] or -7
+        else parseInt(v, 10)
+
+    v1 = prepVersion(v1)
+    v2 = prepVersion(v2)
+    x  = Math.max(v1.length, v2.length)
+
+    for i in [0..x]
+      if (v1[i] == v2[i])
+        continue
+
+      v1[i] = numVersion(v1[i])
+      v2[i] = numVersion(v2[i])
+
+      if (v1[i] < v2[i])
+        compare = -1
+        break
+      else if v1[i] > v2[i]
+        compare = 1
+        break
+
+    return compare unless operator
+    return switch operator
+      when '>', 'gt'
+        compare > 0
+      when '>=', 'ge'
+        compare >= 0
+      when '<=', 'le'
+        compare <= 0
+      when '==', '=', 'eq', 'is'
+        compare == 0
+      when '<>', '!=', 'ne', 'isnt'
+        compare != 0
+      when '', '<', 'lt'
+        compare < 0
+      else
+        null
+
   registerDummyUser:->

     return if location.hostname isnt "localhost"


groupcontroller: add `GroupJoined` to group notifications list

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -97,6 +97,7 @@
           'GroupStackTemplateRemoved'
           'InstanceChanged'
           'GroupDestroyed'
+          'GroupJoined'
           'StackAdminMessageCreated'
         ]


added register link to sidebar

added register link to sidebar

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -39,9 +39,10 @@

     navAdditions = [
       { type  : 'separator',      order : 65 }
-      { title : 'Invite Friends', order : 66,  type : 'account', role : 'member' }
-      { title : 'Logout',         order : 100, path : '/Logout', type : 'account', loggedIn : yes }
-      { title : 'Login',          order : 101, path : '/Login',  type : 'account', loggedIn : no  }
+      { title : 'Invite Friends', order : 66,  type : 'account',   role : 'member' }
+      { title : 'Logout',         order : 100, path : '/Logout',   type : 'account', loggedIn : yes }
+      { title : 'Login',          order : 101, path : '/Login',    type : 'account', loggedIn : no  }
+      { title : 'Register',       order : 102, path : '/Register', type : 'account', loggedIn : no  }
     ]

     KD.registerNavItem navItem for navItem in navAdditions


removed else condition and moved block to an upper level

--- client/app/CommonViews/opinions/opinionlistitemview.coffee
+++ client/app/CommonViews/opinions/opinionlistitemview.coffee
@@ -121,43 +121,42 @@
     # activity = @getDelegate().getData()

     loggedInId = KD.whoami().getId()
-    if loggedInId is data.originId or       # if comment owner
-       # loggedInId is activity.originId or     # activity owner can remove opinion
-       KD.checkFlag "super-admin", KD.whoami()  # if super-admin
+    # comment owner and super-admin can remove opinion
+    if loggedInId is data.originId or KD.checkFlag "super-admin", KD.whoami()

       @editLink.on "click", =>

-          if @editForm?
-            @editForm?.destroy()
-            delete @editForm
-            @$("p.opinion-body").show()
-            @$(".opinion-size-links").show() if @needsToResize
-
-          else
-            @editForm = new OpinionFormView
-              submitButtonTitle : "Save your changes"
-              title             : "edit-opinion"
-              cssClass          : "edit-opinion-form opinion-container"
-              callback          : (data)=>
-                @getData().modify data, (err, opinion) =>
-                  @$("p.opinion-body").show()
-                  callback? err, opinion
-                  @editForm.reset()
-                  @editForm.submitOpinionBtn.hideLoader()
-                  if err
-                    new KDNotificationView title : "Your changes weren't saved.", type :"mini"
-                  else
-                    @bodyView.render yes
-                    @getDelegate().emit "RefreshTeaser", ->
-                    @emit "OwnOpinionWasAdded", opinion
-                    @editForm.setClass "hidden"
-                    @$("p.opinion-body").show()
-                    @$(".opinion-size-links").show() if @needsToResize
-            , data
-
-            @addSubView @editForm, "p.opinion-body-edit", yes
-            @$("p.opinion-body").hide()
-            @$(".opinion-size-links").hide() if @needsToResize
+        if @editForm?
+          @editForm?.destroy()
+          delete @editForm
+          @$("p.opinion-body").show()
+          @$(".opinion-size-links").show() if @needsToResize
+
+
+        @editForm = new OpinionFormView
+          submitButtonTitle : "Save your changes"
+          title             : "edit-opinion"
+          cssClass          : "edit-opinion-form opinion-container"
+          callback          : (data)=>
+            @getData().modify data, (err, opinion) =>
+              @$("p.opinion-body").show()
+              callback? err, opinion
+              @editForm.reset()
+
+              @editForm.submitOpinionBtn.hideLoader()
+              if err
+                new KDNotificationView title : "Your changes weren't saved.", type :"mini"
+              else
+                @getDelegate().emit "RefreshTeaser", ->
+                @emit "OwnOpinionWasAdded", opinion
+                @editForm.setClass "hidden"
+                @$("p.opinion-body").show()
+                @$(".opinion-size-links").show() if @needsToResize
+          , data
+
+        @addSubView @editForm, "p.opinion-body-edit", yes
+        @$("p.opinion-body").hide()
+        @$(".opinion-size-links").hide() if @needsToResize

       @deleteLink.on "click", =>
         @confirmDeleteOpinion data


managed: Log error

managed: Log error

The error was not being logged.

--- client/app/lib/providers/managed/managedkitechecker.coffee
+++ client/app/lib/providers/managed/managedkitechecker.coffee
@@ -126,6 +126,7 @@
       # can popup a Provider specific modal
       klient = machine.getBaseKite()
       klient.klientInfo().nodeify (err, payload) =>
+        return kd.error err if err
         listener payload, machine
         @emit 'NewKite', kite, payload, machine


Fix resetVMData

Fix resetVMData

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -186,7 +186,8 @@
         callback null, @vmDomains[vmName]

   resetVMData:->
-    @vms = @groupVms = []
+    @vms      = []
+    @groupVms = []
     @defaultVmName = null
     @vmDomains = {}


features: show bottom features according to pane

--- client/landing/site.landing/coffee/features/AppView.coffee
+++ client/landing/site.landing/coffee/features/AppView.coffee
@@ -91,41 +91,89 @@
       "

   BOTTOMFEATURES =
-    'Awesome Interface'       :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'Handsome Interface'      :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'Beautiful Interface'     :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'Crazy Interface'         :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'Fokking Interface'       :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'User Interface'          :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'Physical Interface'      :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'My Interface'            :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
-
-    'Your Interface'          :
-      text                    : "It's time to play the music, it's time to light the lights. "
-      iconClass               : "window"
+    'VMs'                  :
+      'VM Specs'           :
+        text               : "1 GB RAM, 3GB Storeage, 1Core CPU (burstable)"
+        iconClass          : "pink vms-specs"
+      'SSH Access'         :
+        text               : "Full ssh access to your box. Use your localhost to connect to your Koding VM."
+        iconClass          : "sand vms-access"
+      'sudo Access'        :
+        text               : "Unrestricted sudo access. Install anything, delete anything. You are in control."
+        iconClass          : "green vms-access"
+      'Install anything'   :
+        text               : "No restrictions on what you can and can not install. Try anything."
+        iconClass          : "light-blue vms-install"
+      'Public IPs'         :
+        text               : "VMs come with publicly accessible IPs making it super easy to connect to then."
+        iconClass          : "blue vms-publicip"
+      'Multiple VMs'       :
+        text               : "Want more than one VM, easy!"
+        iconClass          : "purple vms-multiple"
+      'Always On'          :
+        text               : "If you want to, you can now have an always on VM to run your dev server, blog, database or anything!"
+        iconClass          : "red vms-alwayson"
+      'Great for:'         :
+        text               : "Development environtments, code repositories, experiments and small databases."
+        iconClass          : "yellow vms-install"
+      'Custom domains and nicknames' :
+        text               : "It's time to play the music, it's time to light the lights. "
+        iconClass          : "tinted-yellow vms-specs"
+
+    'IDE'                  :
+      'Workspaces'         :
+        text               : "Use workspaces to organize your project work, just like on Sublime."
+        iconClass          : "pink ide-workspaces"
+      'Previews'           :
+        text               : "Easily preview your work using the built-in browser."
+        iconClass          : "sand ide-preview"
+      'Collapsable Panes'  :
+        text               : "Easily collapse our IDE's main frame to get more screen real estate for the IDE."
+        iconClass          : "green ide-collapse"
+      'Open Source'        :
+        text               : "Don't like something? Change it and make the IDE better for everyone!"
+        iconClass          : "light-blue ide-opensource"
+      'Common editor settings' :
+        text               : "Autocomplete, soft tabs, line numbers, word wrap, tab size, auto-indenting..are all there."
+        iconClass          : "blue ide-settings"
+      'Resize and Split panes' :
+        text               : "Open many IDE windows and resize them the way you want."
+        iconClass          : "purple terminal-split"
+      'Graphical permissions' :
+        text               : "Don't like setting permissions on the command line, do it via a handy GUI."
+        iconClass          : "red ide-permissions"
+      'multi-cursor support' :
+        text               : "Easily change code/text across multiple lines."
+        iconClass          : "yellow ide-multiplecursor"
+      'Code folding'       :
+        text               : "Easily fot away fragments of code taht you don't need to look at."
+        iconClass          : "tinted-yellow ide-fold"
+
+    'Terminal'             :
+      'Fonts'              :
+        text               : "A nice range of mono spaced fonts to chose from to make it your way."
+        iconClass          : "pink terminal-fonts"
+      'Split views'        :
+        text               : "Split terminals into other Terminals...so many Terminals!"
+        iconClass          : "sand terminal-split"
+      'Resizeable'         :
+        text               : "Resize the Terminal frame to make it perfect for your tasks."
+        iconClass          : "green terminal-resize"
+      'Chromebook friendly':
+        text               : "Now you can have a Terminal on your Chromebook!"
+        iconClass          : "light-blue terminal-chromebook"
+
+    'Community'            :
+      'Diverse global community':
+        text               : "Our developer community is comprised of users from all over the globe so no matter what language you speak, you can make new friends and learn together."
+        iconClass          : "pink com-diverse"
+      'Channels for everything':
+        text               : "Follow favorite topics or contribute to the collective learning for the same topic. Get help and help others."
+        iconClass          : "sand ide-workspaces"
+      'Chat'               :
+        text               : "What to make something private? You can easily have private chats on Koding."
+        iconClass          : "green ide-autocomplete"
+

   constructor:(options = {}, data)->

@@ -178,6 +226,10 @@
       attributes    :
         'src'       : "#{IMAGEPATH}/#{image}"

+    tabView.addSubView new KDCustomHTMLView cssClass: 'clearfix'
+
+    tabView.addSubView @createBottomFeatures name
+
     if @currentTab
       leaveFn = =>
         @currentTab.destroy()
@@ -194,17 +246,21 @@
       @addSubView @currentTab = tabView, '.tab-contents'

-  createBottomFeatures: ->
-
-    for title, content of BOTTOMFEATURES
+  createBottomFeatures: (tabName) ->
+    bottomFeatures = new KDCustomHTMLView
+      cssClass     : 'bottom-features clearfix'
+
+    for title, content of BOTTOMFEATURES[tabName]
       do (title, content) =>
         view = new KDCustomHTMLView
           cssClass    : 'feature-item'
           partial     : "
-
#{title}

+
#{title}

#{content.text}

           "
-        @addSubView view, '.bottom-features'
+        bottomFeatures.addSubView view
+
+    return bottomFeatures

   partial: ->
@@ -214,7 +270,6 @@

-

     """


Remove `?`

Remove `?`

--- build/tasks/compile-packages-slug-task.coffee
+++ build/tasks/compile-packages-slug-task.coffee
@@ -47,7 +47,7 @@
         grunt.log.error("#{metadata.name}: #{msg}")
       normalizePackageData metadata, reportPackageError, true
       if metadata.repository.type is 'git'
-        metadata.repository.url ?= metadata.repository.url?.replace(/^git\+/, '')
+        metadata.repository.url = metadata.repository.url?.replace(/^git\+/, '')

       moduleCache = metadata._atomModuleCache ? {}


ParseTerraformOutput: add support for vagrant provider

--- client/stacks/lib/views/stacks/parseterraformoutput.coffee
+++ client/stacks/lib/views/stacks/parseterraformoutput.coffee
@@ -19,19 +19,27 @@

   out = machines: []

-  {machines} = response
+  { machines } = response

   for machine, index in machines

-    {label, provider, region} = machine
-    {instance_type, ami} = machine.attributes
+    { label, provider, region, hostQueryString } = machine

-    out.machines.push {
-      label, provider, region
-      source_ami   : ami
-      instanceType : instance_type
-      provisioners : [] # TODO what are we going to do with provisioners? ~ GG
-    }
+    if provider is 'vagrant'
+      out.machines.push {
+        label, provider
+        hostQueryString
+        provisioners : []
+      }
+    else
+      { instance_type, ami } = machine.attributes
+
+      out.machines.push {
+        label, provider, region
+        source_ami   : ami
+        instanceType : instance_type
+        provisioners : [] # TODO what are we going to do with provisioners? ~ GG
+      }

   console.info "[parseTerraformOutput]", out.machines


feat(ctrl): Update to signed url + promise of onBeforeUpload (#13)

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -1,5 +1,5 @@
 angular.module('cg.fileupload')
-.factory 'cgFileUploadCtrl', ($timeout, $q) ->
+.factory 'cgFileUploadCtrl', ($timeout, $q, $http) ->

     class cgFileUploadCtrl

@@ -52,7 +52,14 @@
             @_disabled = false
             @_createInput()

-        _uploadS3: ({ file, filename, destFolder }) ->
+
+        _uploadS3: ({ file, filename, destFolder, signedUrl, uploadMethod, uploadUrl }) ->
+            return $http.put(
+                uploadUrl
+                file
+                headers: 'Content-Type': file.type
+            ) if signedUrl
+
             defer = $q.defer()
             awsS3 = @awscredentials

@@ -97,7 +104,7 @@

             return defer.promise

-        _uploadWorker: ({ file, filename, uploadUrl, uploadMethod }) ->
+        _uploadWorker: ({ file, filename, uploadUrl, uploadMethod, destFolder }) ->
             defer = $q.defer()
             script = document.querySelectorAll('[src*="cg-file-upload.js"]')[0]
             workerUrl = new URL script.src.replace 'file-upload.js', 'file-upload-worker.js'
@@ -108,6 +115,8 @@
                     when 'progress' then defer.notify(e.data.body)

             worker.onerror = defer.reject
+
+            filename = "#{ destFolder }/#{ filename }" if destFolder

             data =
                 file: file
@@ -123,17 +132,18 @@
             name = name.replace /[^a-zA-Z-_.0-9]/g, '_'
             _prefixRand = "#{ Math.floor(Math.random() * 10000) }-#{ Date.now() }"
             return "#{ _prefixRand }-#{ name }"
-
-        _upload: ({ file, filename, destFolder, uploadUrl, uploadMethod }) ->
+
+        _upload: ({ file, filename, destFolder, uploadUrl, uploadMethod, signedUrl }) ->
             @_disabled = true

-            func = if @awscredentials then '_uploadS3' else '_uploadWorker'
+            func = if @awscredentials or signedUrl then '_uploadS3' else '_uploadWorker'
             this[func]({
                 file
                 filename
                 destFolder
                 uploadUrl
                 uploadMethod
+                signedUrl
             }).then(
                 (data) => @_loadHandler(data) # success
                 (err) => @_errorHandler(err) # error
@@ -155,10 +165,13 @@
             _ctrl =
                 filename: _filename
                 originalFilename: _originalFilename
+                file: file
+                signedUrl: false
                 setDestFolder: (destFolder) -> _ctrl.destFolder = destFolder
                 setFileName: (filename) -> _ctrl.filename = filename
                 setUploadUrl: (uploadUrl) -> _ctrl.uploadUrl = uploadUrl
                 setUploadMethod: (uploadMethod) -> _ctrl.uploadMethod = uploadMethod
+                isSignedUrl: (signedUrl) -> _ctrl.signedUrl = signedUrl

             _doUpload = =>
                 @_upload(
@@ -167,11 +180,12 @@
                     destFolder: _ctrl.destFolder
                     uploadUrl: _ctrl.uploadUrl or @uploadUrl
                     uploadMethod: _ctrl.uploadMethod or @uploadMethod
+                    signedUrl: _ctrl.signedUrl
                 )

             if @onBeforeUpload
                 promise = @onBeforeUpload(_ctrl)
-                if promise.then?
+                if promise?.then?
                     promise.then(_doUpload)
                 else _doUpload()
             else _doUpload()


now in scss

now in scss

--- app/assets/javascripts/cmsino/cmsino_internal.js.coffee
+++ app/assets/javascripts/cmsino/cmsino_internal.js.coffee
@@ -1,6 +1,5 @@
 //= require jquery
 //= require jquery_ujs
-//= require cmsino/bootstrap
 //= require wysihtml5/wysihtml5-0.3.0.min
 //= require wysihtml5/bootstrap-wysihtml5


Update dataset selection logic to avoid having selected datasets in the result list.

--- app/assets/javascripts/models/ui/project_list.js.coffee
+++ app/assets/javascripts/models/ui/project_list.js.coffee
@@ -8,9 +8,18 @@
       @datasetsToDownload = ko.computed(@_computeDatasetsToDownload, this, deferEvaluation: true)

     showProject: =>
+      if @_selectedDataset?
+        @project.selectedDatasetId(@_selectedDataset)
+      else
+        @selectFirstDataset()
+      @_selectedDataset = null
+      console.log 'oh'
       @visible(true)

     hideProject: =>
+      @_selectedDataset = @project.selectedDatasetId()
+      @project.selectedDatasetId(null)
+      console.log 'ok'
       @visible(false)

     loginAndDownloadDataset: (dataset) =>
@@ -28,17 +37,18 @@

       $('#data-access').submit()

+    selectFirstDataset: ->
+      datasets = @project.datasets()
+      selection = null
+      selection = datasets[0].id() if datasets.length > 0
+      @project.selectedDatasetId(selection)
+
     toggleDataset: (dataset) =>
       project = @project
       if project.hasDataset(dataset)
         project.removeDataset(dataset)
-        if @visible()
-          @datasetResults.removeVisibleDataset(dataset)
-        if @isSelected(dataset)
-          datasets = project.datasets()
-          selection = null
-          selection = datasets[0] if datasets.length > 0
-          project.selectedDatasetId(selection)
+        @datasetResults.removeVisibleDataset(dataset) if @visible()
+        @selectFirstDataset() if @isSelected(dataset)
       else
         project.addDataset(dataset)
         project.selectedDatasetId(dataset.id()) unless project.selectedDatasetId()?


fix searching in new files

fix searching in new files

test plan:
in new files,
type something in the search box
hit enter
it should work

Change-Id: Ib47fd0fbfb415d242585f1a08c41985b04b8c363
Reviewed-on: https://gerrit.instructure.com/40289
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Jason Madsen <bcb5fff571a20f07c35d17ba9c364855c776dce7@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/react_files/components/Toolbar.coffee
+++ app/coffeescripts/react_files/components/Toolbar.coffee
@@ -11,7 +11,7 @@
     propTypes:
       currentFolder: React.PropTypes.object # not required as we don't have it on the first render

-    onSubmit: (event) ->
+    onSubmitSearch: (event) ->
       event.preventDefault()
       query = {search_term: @refs.searchTerm.getDOMNode().value}
       Router.transitionTo 'search', @props.params, query


switched up order to no longer race, ember must go first

--- src/tasks/selenium.coffee
+++ src/tasks/selenium.coffee
@@ -45,9 +45,9 @@
     commandOptions = assign serverOpts, browserOptions,
       baseURL: @project.config(browserOptions.environment).baseURL || '/'

-    RSVP.hash
-      browser: start.run(browserOptions)
-      server: serve.run(commandOptions)
-    .then ({browser}) ->
+    serve.run(commandOptions)
+    .then ->
+      start.run(browserOptions)
+    .then (browser) ->
       browser.buildPages()
       .then -> browser.quit()


Always pass a hash to TextBuffer.scanInRange iterators

This makes it easy to only assign variables for the information you
need in the iterator. Before, we always forced you to take a match and
a range as the first two arguments even if you weren't using them.

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -102,7 +102,7 @@
     regex = new RegExp("[#{_.escapeRegExp(startPair + endPair)}]", 'g')
     endPairPosition = null
     unpairedCount = 0
-    buffer.scanInRange regex, scanRange, (match, range, {stop}) =>
+    buffer.scanInRange regex, scanRange, ({match, range, stop}) =>
       if match[0] is startPair
         unpairedCount++
       else if match[0] is endPair
@@ -116,7 +116,7 @@
     regex = new RegExp("[#{_.escapeRegExp(startPair + endPair)}]", 'g')
     startPairPosition = null
     unpairedCount = 0
-    buffer.backwardsScanInRange regex, scanRange, (match, range, {stop}) =>
+    buffer.backwardsScanInRange regex, scanRange, ({match, range, stop}) =>
       if match[0] is endPair
         unpairedCount++
       else if match[0] is startPair


Guard update of containingTags on presence of decoration iterator

--- src/token-iterator.coffee
+++ src/token-iterator.coffee
@@ -51,9 +51,10 @@
     @containingTags

   moveToSuccessor: ->
-    for tag in @closeTags
-      @containingTags.splice(@containingTags.lastIndexOf(tag), 1)
-    @containingTags.push(@openTags...)
+    if @decorationIterator?
+      for tag in @closeTags
+        @containingTags.splice(@containingTags.lastIndexOf(tag), 1)
+      @containingTags.push(@openTags...)

     @startScreenPosition = @endScreenPosition
     @startBufferPosition = @endBufferPosition


remove github checker

remove github checker

--- js/main.coffee
+++ js/main.coffee
@@ -1,16 +1,4 @@
 window.onload = ->
-  $.ajax
-    url: 'https://api.github.com/users/sargeant45'
-    type: 'GET'
-    dataType: 'JSONP'
-    success: (userdata) ->
-      document.getElementById('repos').innerHTML = userdata.data['public_repos']
-      document.getElementById('gists').innerHTML = userdata.data['public_gists']
-      $('#profile').css 'background-image', 'url(' + userdata.data['avatar_url'] + ')'
-      return
-    error: (e) ->
-      console?.error e
-      return
   # email obfuscator
   obfuscated = [5, 20, 8, 1, 14, 1, 18, 20, 5, 18, 2, 5, 18, 18, 25, "@", 7, 13, 1, 9, 12, ".", 3, 15, 13]
   numberletters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]


Remove ngTapOutside which is buggy and not commonly used

--- src/lc-touch.coffee
+++ src/lc-touch.coffee
@@ -165,58 +165,6 @@
                     angular.element(elem).triggerHandler 'dbltap', event
                     scope.$apply ->
                         dbltapHandler(scope, { $event: event })
-    }
-]
-
-
-###
-    ngTapOutside
-
-    Description: A directive that listens when a user clicks or taps
-    outside the area.
-
-    Usage:
-    <button type="button" ng-tap-outside="closeDropdown()">Show Dropdown</button>
-###
-
-lcTouch.directive 'ngTapOutside', ['$timeout', '$parse', ($timeout, $parse)->
-    return {
-        restrict: 'A'
-        link: (scope, elem, attrs)->
-            stopEvent = false
-
-            if angular.isDefined attrs.when
-                scope.$watch attrs.when, (newValue, oldValue)->
-                    if newValue is true
-                        $timeout ()->
-                            elem.bind 'touchstart mousedown', onElementTouchStart
-                            angular.element(document.documentElement).bind 'touchend mouseup', onTouchEnd
-                    else
-                        elem.unbind 'touchstart mousedown', onElementTouchStart
-                        angular.element(document.documentElement).unbind 'touchend mouseup', onTouchEnd
-            else
-                elem.bind 'touchstart mousedown', onElementTouchStart
-                angular.element(document.documentElement).bind 'touchend mouseup', onTouchEnd
-
-
-            # JS Functions
-            onTouchEnd = (event)->
-                unless stopEvent
-                    $timeout ()->
-                        scope.$apply ->
-                            $parse(attrs.ngTapOutside)(scope, { $event: event })
-                    , 10
-                else
-                    stopEvent = false
-
-            onElementTouchStart = (event)->
-                event.stopPropagation()
-                stopEvent = true
-
-
-            # Event Listeners
-            scope.$on 'event:stopTapOutside', ()->
-                stopEvent = true
     }
 ]


fix the global leak error

fix the global leak error

--- proxy.coffee
+++ proxy.coffee
@@ -76,7 +76,7 @@
         @instanceManager = new InstanceManager()
         configurePassport @userManager

-        @userManager.all (err, users) ->
+        @userManager.all (err, users) =>
             if users.length > 0
                 @username = helpers.hideEmail(users[0].value.email)


and tweak them again

and tweak them again

--- src/lib/config.iced
+++ src/lib/config.iced
@@ -3,7 +3,7 @@
   input :
     defaults :
       algo_version : 1
-      security_bits : 8
+      security_bits : 7
       generation : 1
       length : 12
       num_symbols : 0
@@ -23,7 +23,7 @@
     host : "oneshallpass.com"
     generation : 1
     algo_version : 2
-    security_bits : 8
+    security_bits : 7
     length : 32
     num_symbols : 0


Fix bug, add new yacc

Fix bug, add new yacc

--- big_handler.coffee
+++ big_handler.coffee
@@ -1,4 +1,4 @@
-# vim: set nu ai et ts=4 sw=4:
+# vim: set nu ai et ts=4 sw=4 cc=80:
 #

 ssubstring = (str, start, end)->
@@ -195,7 +195,7 @@
             node: 'distinct'
             value: value

-    create_table: (temp, ifnotexists, fullname)>
+    create_table: (temp, ifnotexists, fullname)->
         $$ =
             node: 'create_table'
             temp: temp
@@ -217,6 +217,10 @@
             nm: nm
             idxlist: idxlist
             select: select
+    uniqueflag: (value)->
+        $$ =
+            node: 'uniqueflag'
+            value: value

     ########################################
     # tcons, Table Creation Options


Fixed deprecated API call

Fixed deprecated API call

--- lib/emmet.coffee
+++ lib/emmet.coffee
@@ -67,7 +67,7 @@
     # 2. there’s a selection (user wants to indent it)
     # 3. has expanded snippet (e.g. has tabstops)
     activeEditor = editorProxy.editor;
-    if not isValidTabContext() or not activeEditor.getSelection().isEmpty()
+    if not isValidTabContext() or not activeEditor.getLastSelection().isEmpty()
       return evt.abortKeyBinding()
     if activeEditor.snippetExpansion
       # in case of snippet expansion: expand abbreviation if we currently on last


added todo for headers

added todo for headers

--- src/localize.coffee
+++ src/localize.coffee
@@ -32,6 +32,25 @@
   # add custom middleware to your route,
   # accepts an array of middleware
   @middleware = []
+
+  ###
+
+  @todo - add option for custom headers because `req.headers`
+  was not correctly mapping to `github` and giving errors.
+
+  something like this will be coming:
+
+  this.getKnownHeaders = function (req, res, fn) {
+
+    var headers = _.extend req.headers, this.headers;
+
+  };
+
+  this.headers = {
+    'User-Agent' : 'some-user-agent-idk'
+  };
+
+  ###

   # add a custom route handler, item will have
   # access to `req` & `res` @update - this now accepts


fix indentation and the missing label

fix indentation and the missing label

--- def/zh-cn/menu_darwin.cson
+++ def/zh-cn/menu_darwin.cson
@@ -77,13 +77,14 @@
       Lines:
         value: "行操作"
         submenu:
+          Indent:
             value: "缩进"
           Outdent:
             value: "退格"
           "Auto Indent":
             value: "自动缩进"
           "Move Line Up":
-          value: "上移当前行"
+            value: "上移当前行"
           "Move Line Down":
             value: "下移当前行"
           "Duplicate Lines":


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/array_extensions.coffee
+++ test/array_extensions.coffee
@@ -1,7 +1,7 @@
 module "Array"

 test "#average", ->
-  equals [1, 3, 5, 7].average()
+  equals [1, 3, 5, 7].average(), 6

 test "#compact", ->
   a = [0, 1, undefined, 2, null, 3, '', 4]


Client test uses sinon-chai

Client test uses sinon-chai

--- test/unit/client_test.coffee
+++ test/unit/client_test.coffee
@@ -1,6 +1,10 @@
+chai       = require("chai")
+sinon      = require("sinon")
+sinon_chai = require("sinon-chai")
+expect     = chai.expect
+chai.use(sinon_chai)
+
 Client = require("../../src/client")
-expect = require("chai").expect
-sinon = require("sinon")

 describe "Client", ->
   describe "environment", ->
@@ -120,7 +124,7 @@
       client = new Client(getProcessor, getReporter)
       client.captureException(exception)

-      expect(processor.process.called).to.be.true
+      expect(processor.process).to.have.been.called

     it "ignores errors thrown by processor", ->
       processor = { process: -> throw(new Error("Processor Error")) }


Update www_methods test for Home renaming

--- test/www_methods.spec.coffee
+++ test/www_methods.spec.coffee
@@ -46,6 +46,7 @@
       www_methods.get_dash_events @session, (err, events) =>
         expect(events.length).to.be(1)
         evt = events[0]
+        console.log evt
         delete evt._id
         delete evt.__v
         evt.user = evt.user.toString()
@@ -54,7 +55,7 @@
           id: evt.id,
           absolute_url: "http://localhost:#{config.port}/groups/hey"
           verbed: 'tested'
-          title: "Groups"
+          title: "Home"
           grammar: null
         }, event_params)
         expect(evt).to.eql(result_params)


remove log line

remove log line

--- lib/dev-live-reload.coffee
+++ lib/dev-live-reload.coffee
@@ -4,8 +4,6 @@
 module.exports =
   activate: (state) ->
     return unless atom.getLoadSettings().devMode
-
-    console.log 'Enabling live reloader...'

     uiWatcher = null


HIRE-165:fix

--- src/main/java/app/app.coffee
+++ src/main/java/app/app.coffee
@@ -18,7 +18,7 @@
   tagsInputConfigProvider.setDefaults 'tagsInput',
   	minLength: 1
   return
-  tagsInputConfigProovider.setDefaults('autoComplete',
+  tagsInputConfigProovider.setDefaults 'autoComplete',
     minLength: 1
 .controller 'NavigationCtrl', ($scope, $location, Restangular) ->
   gitProperties = Restangular.one('gitProperties')


New clients supported

New clients supported

Added Podcat (iOS), RSSRadio (iOS), Podkicker Pro (Android) and Podcast
Addict (Android)

--- src/coffee/clients.coffee
+++ src/coffee/clients.coffee
@@ -53,24 +53,24 @@
       icon: 'android/playerfm@2x.png'
       install: 'https://play.google.com/store/apps/details?id=fm.player'
     },
-    #{
-      #title: 'Podcast Addict'
-      #scheme: 'podcastaddict://'
-      #icon: 'android/podcastaddict@2x.png'
-      #install: 'https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict'
-    #},
+    {
+      title: 'Podcast Addict'
+      scheme: 'podcastaddict://'
+      icon: 'android/podcastaddict@2x.png'
+      install: 'https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict'
+    },
     #{
       #title: 'PocketCasts'
-      #scheme: 'pcast://'
+      #scheme: 'pktc://'
       #icon: 'android/pocketcasts-128@2x.png'
       #install: 'https://play.google.com/store/apps/details?id=au.com.shiftyjelly.pocketcasts'
     #},
-    #{
-      #title: 'Podkicker'
-      #scheme: 'pcast://'
-      #icon: 'android/podkicker@2x.png'
-      #install: 'https://play.google.com/store/apps/details?id=ait.podka'
-    #},
+    {
+      title: 'Podkicker Pro'
+      scheme: 'podkicker://subscribe/'
+      icon: 'android/podkicker@2x.png'
+      install: 'https://play.google.com/store/apps/details?id=com.podkicker'
+    },
     {
       title: 'uPod'
       scheme: 'upod://'
@@ -81,52 +81,59 @@

   ios: [
     {
+      title: 'Castro'
+      scheme: 'castro://subscribe/'
+      icon: 'ios/castro@2x.png'
+      install: 'https://itunes.apple.com/de/app/castro-high-fidelity-podcasts/id723142770'
+    },
+    {
+      title: 'Downcast'
+      scheme: 'downcast://'
+      icon: 'ios/downcast@2x.png'
+      install: 'https://itunes.apple.com/de/app/downcast/id393858566'
+    },
+    {
+      title: 'iCatcher'
+      scheme: 'icatcher://'
+      icon: 'ios/icatcher@2x.png'
+      install: 'https://itunes.apple.com/de/app/icatcher!-podcast-app/id414419105'
+    },
+    {
+      title: 'Instacast'
+      scheme: 'instacast://'
+      icon: 'ios/instacast@2x.png'
+      install: 'https://itunes.apple.com/de/app/instacast-4-podcast-client/id577056377'
+    },
+    {
+      title: 'Overcast'
+      scheme: 'overcast://x-callback-url/add?url='
+      icon: 'ios/overcast@2x.png'
+      install: 'https://itunes.apple.com/de/app/overcast-podcast-player/id888422857'
+    },
+    {
+      title: 'PocketCasts'
+      scheme: 'pktc://subscribe/'
+      icon: 'ios/pocketcasts@2x.png'
+      install: 'https://itunes.apple.com/de/app/pocket-casts/id414834813'
+    },
+    {
       title: 'Podcasts'
       scheme: 'pcast://'
       icon: 'ios/podcasts@2x.png'
       install: 'https://itunes.apple.com/de/app/podcasts/id525463029'
     },
     {
-      title: 'Castro'
-      scheme: 'castro://subscribe/'
-      icon: 'ios/castro@2x.png'
-      install: 'https://itunes.apple.com/de/app/castro-high-fidelity-podcasts/id723142770'
-    },
-    {
-      title: 'Downcast'
-      scheme: 'downcast://'
-      icon: 'ios/downcast@2x.png'
-      install: 'https://itunes.apple.com/de/app/downcast/id393858566'
-    },
-    {
-      title: 'iCatcher'
-      scheme: 'icatcher://'
-      icon: 'ios/icatcher@2x.png'
-      install: 'https://itunes.apple.com/de/app/icatcher!-podcast-app/id414419105'
-    },
-    {
-      title: 'Instacast'
-      scheme: 'instacast://'
-      icon: 'ios/instacast@2x.png'
-      install: 'https://itunes.apple.com/de/app/instacast-4-podcast-client/id577056377'
-    },
-    {
-      title: 'Overcast'
-      scheme: 'overcast://x-callback-url/add?url='
-      icon: 'ios/overcast@2x.png'
-      install: 'https://itunes.apple.com/de/app/overcast-podcast-player/id888422857'
-    },
-    {
-      title: 'PocketCasts'
-      scheme: 'pktc://subscribe/'
-      icon: 'ios/pocketcasts@2x.png'
-      install: 'https://itunes.apple.com/de/app/pocket-casts/id414834813'
-    },
-    #{
-      #title: 'Podcat'
-      #scheme: 'podcat://'
-      #icon: 'ios/podcat@2x.png'
-    #},
+      title: 'Podcat'
+      scheme: 'podcat://'
+      icon: 'ios/podcat@2x.png'
+      install: 'https://itunes.apple.com/app/podcat/id845960230'
+    },
+    {
+      title: 'RSSRadio'
+      scheme: 'rssradio://'
+      icon: 'ios/rssradio@2x.png'
+      install: 'https://itunes.apple.com/app/rssradio-premium-podcast-downloader/id679025359'
+    }
   ]
   linux: [
     {


Move output file (including SyncTeX file) to source dir

By default we now move the output file after a successful build, including
the SyncTeX file if it exists. The files are move to the source directory, i.e.
the directory containing the processed root TeX file.
The behavior is configurable via `latex.moveResultToSourceDirectory`.

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -10,6 +10,9 @@
     enableShellEscape:
       type: 'boolean'
       default: false
+    moveResultToSourceDirectory:
+      type: 'boolean'
+      default: true
     openResultAfterBuild:
       type: 'boolean'
       default: true
@@ -52,7 +55,9 @@
       @destroyProgressIndicator()
       result = builder.parseLogFile(rootFilePath)
       switch statusCode
-        when 0 then @showResult(result)
+        when 0
+          @moveResult(result, rootFilePath) if @shouldMoveResult()
+          @showResult(result)
         when 127 then @showError \
           """
           TeXification failed! Builder executable not found.
@@ -87,9 +92,22 @@
     return new OpenerImpl() if OpenerImpl?
     console.info 'Opening PDF files is not yet supported on your platform.' unless atom.inSpecMode()

+  moveResult: (result, filePath) ->
+    sourceDir = path.dirname(filePath)
+    outputFilePath = result.outputFilePath
+    result.outputFilePath = path.join(sourceDir, path.basename(outputFilePath))
+    fs.moveSync(outputFilePath, result.outputFilePath)
+
+    syncFilePath = outputFilePath.replace(/.pdf$/, '.synctex.gz')
+    if fs.existsSync(syncFilePath)
+      fs.moveSync(syncFilePath, path.join(sourceDir, path.basename(syncFilePath)))
+
   resolveRootFilePath: (path) ->
     finder = new MasterTexFinder(path)
     finder.getMasterTexPath()
+
+  shouldMoveResult: ->
+    atom.config.get('latex.moveResultToSourceDirectory')

   shouldOpenResult: ->
     atom.config.get('latex.openResultAfterBuild')


Added comment.

Added comment.

--- lib/formatselection.coffee
+++ lib/formatselection.coffee
@@ -1,3 +1,5 @@
+# Selection of format
+

 async = require 'async'
 typer = require 'media-typer'


Update titanium and titanium-backbone dependencies of generated app

--- lib/generator/index.coffee
+++ lib/generator/index.coffee
@@ -23,8 +23,9 @@
   private: true
   description: 'Mobile app'
   dependencies:
-    "titanium-backbone": "0.1.x"
-    "titanium-backbone-ks": "0.0.x"
+    "titanium": "3.0.x"
+    "titanium-backbone": "0.5.x"
+    "titanium-backbone-ks": "0.1.x"
   stitch:
     identifier: 'stitchRequire'
     output:


add showProjects button

add showProjects button

--- imports/ui/components/allUsers/allUsers.coffee
+++ imports/ui/components/allUsers/allUsers.coffee
@@ -26,9 +26,9 @@
 		rows.push
 			id: index + 1
 			username: user.username
-			action: 'Impersonate...'
 			name: user.profile.firstname + ' ' + user.profile.lastname
 			email: user.profile.email
+			action: 'Impersonate... | Show projects...'
 			projects: projects.join(';')

 	$('#userTable').html('').footable
@@ -54,3 +54,16 @@
 			Accounts.callLoginMethod methodArguments: [ impToken: token ]

 			FlowRouter.go 'home', language: userLang
+
+	'click .showProjects': (e) ->
+		userId = $(e.target).attr('data-id')
+		projectIds = []
+
+		for role in Permissions.member
+			roleProjectIds = Roles.getGroupsForUser(userId, role).join(',')
+
+			if roleProjectIds then projectIds.push roleProjectIds
+
+		$('#projectTable .footable-filtering button').click()
+		$('#projectTable .footable-filtering input[type="text"]').val projectIds.join(',')
+		$('#projectTable .footable-filtering button').click()


Allow changing out the store on FinderWindow.  This is usefull as the window can be shared between views

--- lib/generators/nas_extjs/templates/public/app/ux/FinderWindow.coffee
+++ lib/generators/nas_extjs/templates/public/app/ux/FinderWindow.coffee
@@ -76,6 +76,9 @@

     setDestinationElement: ( @dest_element)->

+    setStore: (@store)->
+        @grid.reconfigure( @store )
+        this

     listeners:
         show: ->


Create programs for turtle:

Create programs for turtle:

--- src/config/turtles.coffee
+++ src/config/turtles.coffee
@@ -42,14 +42,9 @@
     setup: (world, args) ->
       return if world.label().length > 2
       console.log "Call setup for #{world}",world
+      world.put 'programs', world.make_world(programs)
       world.send 'create', world
       world.send 'inspect', world
-    programs: (world, args) ->
-      local = world.get('_local_programs')
-      return local if local?
-      local = world.make_world(programs)
-      world.put '_local_programs', local
-      local
     next_signal: (world, args) ->
       local = world.get('programs')
       assert signal = local.call('next'), "No next signal"


Every Pokemon updates after a move.

Every Pokemon updates after a move.

It's not just the move-maker, but all Pokemon run their update hook
after a move.

--- server/battle.coffee
+++ server/battle.coffee
@@ -278,7 +278,8 @@

       # Update Pokemon itself.
       # TODO: Is this the right place?
-      pokemon.update(this)
+      for active in @getActiveAlivePokemon()
+        active.update(this)

       # If a move adds a request to the queue, the request must be resolved
       # before the battle can continue.


Correct formula

Correct formula

--- index.cjsx
+++ index.cjsx
@@ -203,7 +203,7 @@
         when '/kcsapi/api_req_member/get_practice_enemyinfo'
           enemyShips = body.api_deck.api_ships
           baseExp = exp[enemyShips[0].api_level] / 100 + exp[enemyShips[1].api_level ? 0] / 300
-          baseExp = if baseExp <= 500 then baseExp else 500 + Math.sqrt baseExp - 500
+          baseExp = if baseExp <= 500 then baseExp else 500 + Math.floor Math.sqrt baseExp - 500
           fleetShips = window._decks[0].api_ship
           flagshipFlag = false
           trainingCount = 0


Removed logging message

Removed logging message

--- src/cuffs/bindings.coffee
+++ src/cuffs/bindings.coffee
@@ -202,7 +202,6 @@
             super node
             @values = (att.split('==') for att in @attr.split(','))

-
         activate: (value)->
             if value in (v[1] for v in @values)
                 $(@node).addClass 'active'
@@ -213,10 +212,7 @@
             for v in @values
                 context.watch v[0], (value)=> @activate value

-            console.log @node, @attr, @values
-
             $(@node).bind 'click', =>
-                debugger
                 context.set @values[0][0], @values[0][1]

             @activate context.get @values[0][0]


Let's have it in short format.

Let's have it in short format.

--- server/routes.coffee
+++ server/routes.coffee
@@ -20,7 +20,7 @@
       url: '/'
       changefreq: 'weekly'
       priority: 1
-      lastmodISO: dateFormat(now, "yyyy-MM-dd HH:mm:ss")
+      lastmodISO: dateFormat(now, "yyyy-MM-dd")
     }
   ]
 });


Set targetLeft to Infinity when past last row

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -662,7 +662,7 @@
     targetLeft = pixelPosition.left
     defaultCharWidth = @defaultCharWidth
     row = Math.floor(targetTop / @getLineHeightInPixels())
-    pastLastRow = row > @getLastRow()
+    targetLeft = Infinity if row > @getLastRow()
     row = Math.min(row, @getLastRow())
     row = Math.max(0, row)

@@ -672,7 +672,7 @@
       charWidths = @getScopedCharWidths(token.scopeDescriptor)
       for char in token.value
         charWidth = charWidths[char] ? defaultCharWidth
-        break if not pastLastRow and targetLeft <= left + (charWidth / 2)
+        break if targetLeft <= left + (charWidth / 2)
         left += charWidth
         column++


Add reverse cache.

Add reverse cache.

--- server/server.coffee
+++ server/server.coffee
@@ -82,6 +82,55 @@
   version #{version}
   """

+# A simple cache. Entries used within two expiry periods are retained, otherwise they are discarded.
+# At most 2 * @entries entries are retained.
+class SimpleCache
+  # expiry: expiry time in milliseconds (default, one hour)
+  # entries: maximum number of entries in @cache (there may be up to this many entries in @previous, too)
+  constructor: (@expiry = 60 * 60 * 1000, @entries = 1000) ->
+    @cache = {}
+    @previous = {}
+    @lastRotation = new Date()
+
+  has: (key) ->
+    @rotate()
+    (key of @cache) or key of @previous
+
+  # Set value, and return that value.  If value is null, then delete key.
+  set: (key, value = null) ->
+    @rotate()
+    delete @previous[key]
+    if value?
+      @cache[key] = value
+    else
+      delete @cache[key]
+      null
+
+  get: (key) ->
+    @rotate()
+    if key of @cache
+      @cache[key]
+    else if key of @previous
+      @cache[key] = @previous[key]
+      delete @previous[key]
+      @cache[key]
+    else
+      null
+
+  rotate: (force = false) ->
+    if force or @entries < Object.keys(@cache).length or @expiry < new Date() - @lastRotation
+      console.log "rotating reverse cache"
+      @lastRotation = new Date()
+      @previous = @cache
+      @cache = {}
+
+  clear: ->
+    @rotate true
+    @rotate true
+
+reverseCache = new SimpleCache 1000 * 60 * 60, 50
+getCacheKey = (data) -> data.trim().split(/\s+/).join " "
+
 WSS  = ws.Server
 wss  = new WSS port: args.port, host: config.host
 wss.on "connection", (ws) -> ws.on "message", handler ws
@@ -116,6 +165,11 @@
   console.log "edit:", filename
   onExit.push -> console.log "  done:", filename

+  cacheKey = getCacheKey request.text
+  if reverseCache.has cacheKey
+    request.text = reverseCache.get cacheKey
+  cacheKey = null
+
   fs.writeFile filename, request.text, (error) ->
     return exit() if error
     onExit.push -> fs.unlink filename
@@ -124,8 +178,14 @@
       fs.readFile filename, "utf8", (error, data) ->
         return exit() if error
         console.log "  send:", filename
-        data = formatParagraphs data if request.isContentEditable
-        request.text = data
+        request.text =
+          if request.isContentEditable
+            formattedData = formatParagraphs data
+            formattedKey = getCacheKey formattedData
+            reverseCache.set getCacheKey(formattedData), data
+            formattedData
+          else
+            data
         ws.send JSON.stringify request
         continuation?()


don't try to reuse an empty cell

don't try to reuse an empty cell

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -111,9 +111,9 @@
   add_input_cell = (notebook, opts={}) ->
     if opts.reuse
       if opts.after?
-        cell = seek opts.after, forwards, is_input
+        cell = seek opts.after, forwards, (cell) -> is_input(cell) and visible(cell)
       else if opts.before?
-        cell = seek opts.before, backwards, is_input
+        cell = seek opts.before, backwards, (cell) -> is_input(cell) and visible(cell)
     unless cell? and is_clean cell
       cell = create_input_cell notebook
       insert_cell cell, opts


fixed draft4 changes to optional/format

fixed draft4 changes to optional/format


no wildcard

no wildcard

--- services/http.coffee
+++ services/http.coffee
@@ -34,7 +34,6 @@
 tuneResponses = (app) ->
     app.use (req, res, next) ->
         # returns a sequelize table to include
-        res.header 'Access-Control-Allow-Origin', '*'
         # model associations in REST
         req.getAssocSections = (availableSections) ->
             return if not req.query.sections? # if no section is specified