Account: added upgrade and support links

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -122,11 +122,35 @@
     #   click      : handleSubMenu
     #   mousemove  : KD.utils.stopDOMEvent

-    @avatarPopupContent.addSubView new KDCustomHTMLView
-      tagName    : 'a'
-      attributes : href : '/Account', testpath : "AccountSettingsLink"
+    @avatarPopupContent.addSubView new CustomLinkView
+      title      : 'Upgrade plan'
+      href       : '/Pricing'
       cssClass   : 'bottom-separator'
-      partial    : 'Account Settings'
+      click      : (event)=>
+        KD.utils.stopDOMEvent event
+        router.handleRoute '/Pricing'
+        @hide()
+
+    @avatarPopupContent.addSubView new CustomLinkView
+      title      : 'Koding University'
+      href       : 'http://learn.koding.com'
+      target     : '_blank'
+
+    @avatarPopupContent.addSubView new CustomLinkView
+      title      : 'Contact support'
+      cssClass   : 'bottom-separator support'
+      click      : (event)=>
+        KD.utils.stopDOMEvent event
+        new HelpSupportModal
+        @hide()
+
+
+    @avatarPopupContent.addSubView new CustomLinkView
+      title      : 'Account Settings'
+      href       : '/Account'
+      attributes :
+        testpath : 'AccountSettingsLink'
+      cssClass   : 'bottom-separator'
       click      : (event)=>
         KD.utils.stopDOMEvent event
         router.handleRoute '/Account'
@@ -157,11 +181,6 @@
     #     new TroubleshootModal
     #     @hide()

-    @avatarPopupContent.addSubView new CustomLinkView
-      tagName    : 'a'
-      title      : 'Koding University'
-      href       : 'http://learn.koding.com'
-      target     : '_blank'

     @avatarPopupContent.addSubView dashboardLink = new KDCustomHTMLView
       tagName  : "a"


Client: Activity: Updated handleLoadMessageSuccess method to not set reply as message to ChannelThreadStore

--- client/activity/lib/flux/stores/channelthreadsstore.coffee
+++ client/activity/lib/flux/stores/channelthreadsstore.coffee
@@ -62,6 +62,8 @@
   ###
   handleLoadMessageSuccess: (threads, { channelId, message }) ->

+    return threads  if message.typeConstant is 'reply'
+
     return addMessage threads, channelId, message.id


reversed changes

reversed changes

--- client/app/Applications/Demos.kdapplication/AppView.coffee
+++ client/app/Applications/Demos.kdapplication/AppView.coffee
@@ -1,74 +1,3 @@
-class FlippingCounterView extends KDCustomHTMLView
-
-  constructor:(options = {}, data)->
-
-    options.title        or= ''
-    options.digitOptions or= {}
-    options.minDigits     ?= 0
-    options.cssClass     or= "counter"
-    options.tagName        = "figure"
-
-    super options, data
-
-  viewAppended:->
-
-    @addSubView @title = new KDCustomHTMLView
-      tagName  : 'h6'
-      cssClass : 'title'
-      partial  : @getOption('title')+':'
-
-    @addSubView @digitWrapper = new KDCustomHTMLView
-      tagName  : 'div'
-
-    @createDigits()
-    @emit "ready"
-
-    # to update the counter just call update with the new count
-    # uncomment this test case below for a demo
-    # @utils.repeat 50, =>
-    #   @update @getData().count + @utils.getRandomNumber(10)
-
-  createDigits:->
-    {count} = @getData()
-    {digitOptions, minDigits} = @getOptions()
-    @prevCount = count + ''
-
-    # create leading zeros
-    # if minDigits option is passed
-    if @prevCount.length < minDigits
-      for i in [@prevCount.length...minDigits]
-        @prevCount = ' ' + @prevCount
-    # create digit subViews if they are not created already
-    # and add the subView
-    for value, i in @prevCount when not @["digit#{i}"]
-      @["digit#{i}"] = new CounterDigitView digitOptions, {value}
-      @digitWrapper.addSubView @["digit#{i}"]
-
-  increment:(val=0)->
-    {count} = @getData()
-    @update count + val
-
-  decrement:(val=0)->
-    {count} = @getData()
-    @update count - val
-
-  update:(count)->
-    @getData().count = count
-    @createDigits()
-    {count}     = @getData()
-    newCountStr = count+''
-    for value, i in newCountStr
-      # digitDiff is to determine which subview is for which digitView after
-      # we update the count. In case of minDigits option is set
-      # or the newCount has more digits than it had previously.
-      digitDiff = if newCountStr.length >= @prevCount.length
-      then newCountStr.length - @prevCount.length
-      else @prevCount.length - newCountStr.length
-
-      @["digit#{i+digitDiff}"].update value
-
-
 class DemosMainView extends KDScrollView

   viewAppended:->
-    @addSubView new FlippingCounterView {title: 'ornek'}, count:550


Add fixme lines to taskboard lightboxes.

--- app/coffee/modules/taskboard/lightboxes.coffee
+++ app/coffee/modules/taskboard/lightboxes.coffee
@@ -36,6 +36,7 @@
                 assigned_to: null
             }
             isNew = true
+
             # Update texts for creation
             $el.find(".button-green span").html("Create") #TODO: i18n
             $el.find(".title").html("New task  ") #TODO: i18n
@@ -44,6 +45,7 @@
         $scope.$on "taskform:edit", (ctx, task) ->
             $scope.task = task
             isNew = false
+
             # Update texts for edition
             $el.find(".button-green span").html("Save") #TODO: i18n
             $el.find(".title").html("Edit task  ") #TODO: i18n
@@ -53,11 +55,9 @@
             if task.is_blocked
                 $el.find(".blocked-note").show()
                 $el.find("label.blocked").addClass("selected")
+
             if task.is_iocaine
                 $el.find("label.iocaine").addClass("selected")
-
-        $scope.$on "$destroy", ->
-            $el.off()

         # Dom Event Handlers

@@ -79,6 +79,7 @@
                 promise = $repo.save($scope.task)
                 broadcastEvent = "taskform:edit:success"

+            # FIXME: error handling?
             promise.then (data) ->
                 $el.addClass("hidden")
                 $rootScope.$broadcast(broadcastEvent, data)
@@ -99,6 +100,7 @@
             $el.off()

     return {link: link}
+

 CreateBulkTasksDirective = ($repo, $rs, $rootscope) ->
     link = ($scope, $el, attrs) ->
@@ -123,6 +125,7 @@
             projectId = $scope.projectId
             usId = $scope.form.usId

+            # FIXME: error handling?
             $rs.tasks.bulkCreate(projectId, usId, data).then (result) ->
                 $rootscope.$broadcast("taskform:bulk:success", result)
                 $el.addClass("hidden")
@@ -134,7 +137,18 @@

 module = angular.module("taigaTaskboard")
-module.directive("tgLbCreateEditTask", ["$tgRepo", "$tgModel", "$tgResources", "$rootScope",
-                                        CreateEditTaskDirective])
-module.directive("tgLbCreateBulkTasks", ["$tgRepo", "$tgResources", "$rootScope",
-                                               CreateBulkTasksDirective])
+
+module.directive("tgLbCreateEditTask", [
+    "$tgRepo",
+    "$tgModel",
+    "$tgResources",
+    "$rootScope",
+    CreateEditTaskDirective
+])
+
+module.directive("tgLbCreateBulkTasks", [
+    "$tgRepo",
+    "$tgResources",
+    "$rootScope",
+    CreateBulkTasksDirective
+])


Add explaination beside sourcemaps.

Add explaination beside sourcemaps.

--- config.coffee
+++ config.coffee
@@ -1,6 +1,6 @@
 exports.config =
   # See http://brunch.io/#documentation for docs.
-  sourceMaps: false
+  sourceMaps: false # Source maps are usefull in order to see the dev structure into the dev tools, and they are not use outside of the devtools, there is no perturbation of the performance in production.
   files:
     javascripts:
       joinTo:


trigger input event

trigger input event

--- public/javascripts/inkdrop.coffee
+++ public/javascripts/inkdrop.coffee
@@ -56,8 +56,10 @@
 			start = $(this).get(0).selectionStart
 			end = $(this).get(0).selectionEnd
 			text = $(this).val()
-
+
 			$(this).val(text.substring(0, start) + '\t' + text.substring(end))
 			$(this).get(0).selectionEnd = start + 1
+
+			$(this).trigger('input')
 	)
 )


Systematic transfer of props from HS to sock

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -161,7 +161,8 @@
             execute = (a...) => g => method.apply this, i(a)
             respond = (a...) => g => s => callback.apply this, o(a)
             respond.socket = socket; respond.context = context
-            respond.session = session; socket.session = session
+            _.extend respond, socket.handshake or new Object
+            _.extend socket, socket.handshake or new Object
             return execute parameters..., respond

     # This server side method is called on the context prior to the


syntax conv changes

syntax conv changes

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -98,15 +98,15 @@
     # to the implementation and the class for more information on it.
     inject: (context, subject, symbol) ->
         caching = context.caching ?= new Object
-        scripts = -> context.scripts.push subject
-        sources = -> context.sources.push compile()
+        scripts = -> assert context.scripts.push subject
+        sources = -> assert context.sources.push compile()
         compile = -> subject.remote.compile caching, symbol
         invalid = "not a remote object and not a link"
         assert _.isObject(context), "got invalid context"
         compilable = _.isFunction subject.remote?.compile
-        return scripts() if _.isString subject
-        return sources() if compilable
-        throw new Error invalid
+        return scripts.call this if _.isString subject
+        return sources.call this if compilable
+        throw new Error invalid.toString()

     # This is an internal routine that performs the task of compiling
     # a screenplay context into a valid HTML document to be rendered


Add qualified too

Add qualified too

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -82,6 +82,7 @@
         context.uuid = request: request.uuid
         context.headers = request.headers
         context.params = request.params
+        context.qualified = @qualified()
         context.location = @location()
         context.uuid.service = @uuid
         context.url = request.url


Only notify new chats if chat is active.

--- app/assets/javascripts/app/controllers/chat.coffee
+++ app/assets/javascripts/app/controllers/chat.coffee
@@ -175,12 +175,13 @@
     if @lastWaitingChatCount isnt counter

       # do not play sound on initial load
-      if counter > 0 && @lastWaitingChatCount isnt undefined
-        @sounds.chat_new.play()
-        @notifyDesktop(
-          title: "#{counter} #{App.i18n.translateInline('Waiting Customers')}",
-          url: '#customer_chat'
-        )
+      if @switch()
+        if counter > 0 && @lastWaitingChatCount isnt undefined
+          @sounds.chat_new.play()
+          @notifyDesktop(
+            title: "#{counter} #{App.i18n.translateInline('Waiting Customers')}",
+            url: '#customer_chat'
+          )
       @lastWaitingChatCount = counter

     # collect chat window messages


highly descriptive main specs

highly descriptive main specs

--- spec/notice_spec.coffee
+++ spec/notice_spec.coffee
@@ -72,6 +72,7 @@
         instance = Messenger.create 'origin name'
         instance.use.should.be.an.instanceof Function
         instance.messageTypeName( 'messageTypeValue' ).then (m) ->
+            m._type.should.equal 'messageTypeName'
             m.should.eql
                 messageTypeName: 'messageTypeValue'
                 sequence: 0


tests: Prepare support for MQTT, uncomment to enable

--- spec/participant.coffee
+++ spec/participant.coffee
@@ -6,11 +6,12 @@
 participants =
   'C++Repeat': [path.join __dirname, '..', 'build', './repeat-cpp']

-describe 'Participants', ->
-  address = 'amqp://localhost'
-  g =
-    broker: null
-    commands: participants
+# Note: most require running an external broker service
+transports =
+  #'MQTT': 'mqtt://localhost'
+  'AMQP': 'amqp://localhost'
+
+transportTests = (g, address) ->

   beforeEach (done) ->
     g.broker = msgflo.transport.getBroker address
@@ -23,4 +24,12 @@
     heterogenous.testParticipant g, name

+describe 'Participants', ->
+  g =
+    broker: null
+    commands: participants

+  Object.keys(transports).forEach (type) =>
+    describe "#{type}", () ->
+      address = transports[type]
+      transportTests g, address


virtualization controller: WIP vm purchases

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -273,7 +273,6 @@
     KD.remote.api.JVM.fetchDefaultVm callback

   createDefaultVM: (callback)->
-
     @hasDefaultVM (err, state)->
       return warn 'Default VM already exists.'  if state

@@ -342,18 +341,18 @@

     productForm = new VmProductForm

-    paymentController = KD.getSingleton 'paymentController'
-
-    KD.whoami().fetchPlansAndSubscriptions ['vm'], (err, plansAndSubscriptions) =>
+    pay = KD.getSingleton 'paymentController'
+
+    KD.whoami().fetchPlansAndSubscriptions ['vm'], (err, plansAndSubs) =>
       return  if KD.showError err

-      { subscriptions } = paymentController
-                            .groupPlansBySubscription plansAndSubscriptions
+      { subscriptions } = pay.groupPlansBySubscription plansAndSubs

       productForm.setCurrentSubscriptions subscriptions

     productForm.on 'PackOfferingRequested', (subscription) ->
       options = targetOptions: selector: tags: 'vm'
+
       KD.getGroup().fetchProducts 'pack', options, (err, packs) ->
         return  if KD.showError err

@@ -370,28 +369,31 @@
       width   : 500
       overlay : yes

-    workflow.on 'DataCollected', @bound 'provisionVm'
-
-    workflow.on 'Cancel', -> modal.destroy()
-
-    workflow.enter()
+    workflow
+      .on('DataCollected', @bound 'provisionVm')
+      .on('Cancel', modal.bound 'destroy')
+      .enter()

   provisionVm: ({ subscription, paymentMethod, productData })->
+
+    { JVM } = KD.remote.api
+
     { plan, pack } = productData

     if paymentMethod and not subscription

-      plan.subscribe paymentMethod.paymentMethodId, (err, subscription) =>
+      return plan.subscribe paymentMethod.paymentMethodId, (err, subscription) =>
         return  if KD.showError err

         @provisionVm { subscription, productData }

-    subscription.debit pack, (err, blah) ->
-      debugger
-
+    subscription.debit pack, (err, nonce) ->
       return  if KD.showError err

-      debugger
+      JVM.createVmByNonce nonce, (err, vm) ->
+        return  if KD.showError err
+
+        debugger

   askForApprove:(command, callback)->


Testing impressions and errors

Testing impressions and errors

--- spec/server.spec.coffee
+++ spec/server.spec.coffee
@@ -20,13 +20,29 @@

 describe 'the server is able to handle basic requests', ->

-  it 'can handle a basic feedback request', ->
+  it 'handles basic feedback requests', ->
     runs ->
       requestMaker.post(createTestMessage 'feedback' )
     waitsFor (-> requestMaker.completed()), 'no feedback', TIMEOUT_TIME
     runs ->
       responseObject = requestMaker.jsonResponse()
-      expect(responseObject.passphrase).toEqual 'feedback test'
+      expect(responseObject.passphrase).toMatch 'feedback test'
+
+  it 'handles basic impression requests', ->
+    runs ->
+      requestMaker.post(createTestMessage 'impression' )
+    waitsFor (-> requestMaker.completed()), 'no impression', TIMEOUT_TIME
+    runs ->
+      responseObject = requestMaker.jsonResponse()
+      expect(responseObject.passphrase).toMatch 'impression test'
+
+  it 'handles basic error requests', ->
+    runs ->
+      requestMaker.post(createTestMessage 'error' )
+    waitsFor (-> requestMaker.completed()), 'no answer for error', TIMEOUT_TIME
+    runs ->
+      responseObject = requestMaker.jsonResponse()
+      expect(responseObject.passphrase).toMatch 'error'

 runs ->
   server.stop()


New spec helper.

New spec helper.

--- spec/spec_helper.coffee
+++ spec/spec_helper.coffee
@@ -1,4 +1,4 @@
-app = require('../config/app').app
+exports.app    = app = require('../config/app').app

 exports.vows   = require 'vows'
 exports.assert = require 'assert'
@@ -8,8 +8,7 @@
   debug:      false
   runScripts: false

-exports.tobi         = require 'tobi'
-exports.tobi.browser = exports.tobi.createBrowser app
+exports.tobi                = require 'tobi'

 exports.headers             = {}
 exports.headers.jsonHeaders = { 'Content-Type': 'application/json' }


Added logging for subscribing to authenticated collections.

--- src/AccountsUtil.coffee
+++ src/AccountsUtil.coffee
@@ -11,6 +11,9 @@
         return unless userId
         user = Meteor.users.findOne(userId)
         username = user.username
+        Logger.info('User subscribed to collection', userId, username, name)
+        @onStop ->
+          Logger.info('User unsubscribed from collection', userId, username, name)
         if username == 'admin'
           # Admin can see all docs.
           collection.find()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -21,7 +21,8 @@
       width: 30
       height: 20
       activeBounds: Rectangle
-        x:
+        x: 30
+

 ActiveBounds module
 @name ActiveBounds


Don't misuse a's

Don't misuse a's

--- core/app/backbone/views/evidence/evidence_vote_view.coffee
+++ core/app/backbone/views/evidence/evidence_vote_view.coffee
@@ -27,11 +27,19 @@
     R.div className: 'evidence-vote',
       R.span className:"evidence-vote-relevance spec-evidence-relevance",
         format_as_short_number(@model().relevance())
-      R.a
-        className: up_classes
-        href: "javascript:" if Factlink.Global.signed_in
-        onClick: @_on_up_vote
-      R.a
-        className: down_classes
-        href: "javascript:" if Factlink.Global.signed_in
-        onClick: @_on_down_vote
+      if Factlink.Global.signed_in
+        [
+          R.a
+            className: up_classes
+            href: "javascript:" if Factlink.Global.signed_in
+            onClick: @_on_up_vote
+          R.a
+            className: down_classes
+            href: "javascript:" if Factlink.Global.signed_in
+            onClick: @_on_down_vote
+        ]
+      else
+        [
+          R.span(className: up_classes)
+          R.span(className: down_classes)
+        ]


fix(drafts): fix illegal ID set

fix(drafts): fix illegal ID set

Fixes T3468

--- internal_packages/inbox-contact-elements/lib/Participants.cjsx
+++ internal_packages/inbox-contact-elements/lib/Participants.cjsx
@@ -30,7 +30,7 @@
       list = _.reject list, (contact) -> contact.isMe()

     list.forEach (p) ->
-      p.id = p.name+p.email
+      p.serverId ?= p.name+p.email

     list


Add defaults when `json` is null or empty

This ensures the change events fire for each property when the
working and upstream trees are updated.

Fix #168, fix #170

--- src/coffee/cilantro/models/context/manager.coffee
+++ src/coffee/cilantro/models/context/manager.coffee
@@ -11,7 +11,7 @@
     # Light wrapper for fixing attrs for the top-level branch node. If `attrs`
     # has ID fields, it is added as a child to the branch. This is primarily
     # to fix legacy data that did not conform to this structure.
-    _fix = (node, attrs={}, options) ->
+    _fix = (node, attrs, options) ->
         if attrs.concept? or attrs.field?
             node.children.add(attrs, options)
         else
@@ -102,6 +102,13 @@
         # the working tree since this would remove local state that controls
         # may be relying on.
         update: (attrs) ->
+            # Ensure the active and working trees are not empty, otherwise
+            # change events do not fire.
+            attrs = c._.extend
+                children: []
+                type: 'and'
+            , attrs
+
             # Update `upstream` tree with server response
             _fix @upstream, attrs,
                 manager: @


style(): unify error messages tags

style(): unify error messages tags

--- src/madlib-locale.coffee
+++ src/madlib-locale.coffee
@@ -31,6 +31,10 @@
 )( ( Q, console, settings, objectUtils, XHR, Polyglot, Moment, accounting, _, capitalize ) ->

+
+    TAG = '[LocaleManager]'
+
+
     ###*
     #   This module is used to handle translations, formatting and locale settings
     #
@@ -51,7 +55,7 @@

             if @initialized

-                error   = '[LocaleManager] Already initialized'
+                error       = "#{ TAG } Already initialized."

                 console.error( error )
                 return Q.reject( error )
@@ -110,7 +114,7 @@

             unless @initialized

-                error   = '[LocaleManager] Tried to set locale before initializion.'
+                error   = "#{ TAG } Tried to set locale before initialization."

                 console.error( error )
                 return Q.reject( error )
@@ -139,7 +143,7 @@

             loaded.catch( () ->

-                console.error( "[i18n] Failed to load locale #{locale}")
+                console.error( "#{ TAG } Failed to load locale #{ locale }")

                 return
             )


Use shared machine remote API

Use shared machine remote API

--- client/app/lib/providers/machinesettingsvmsharingview.coffee
+++ client/app/lib/providers/machinesettingsvmsharingview.coffee
@@ -1,5 +1,6 @@
 kd                               = require 'kd'
 nick                             = require 'app/util/nick'
+remote                           = require('app/remote').getInstance()
 KDView                           = kd.View
 Machine                          = require 'app/providers/machine'
 UserItem                         = require 'app/useritem'
@@ -82,12 +83,7 @@

     {profile: {nickname} } = user

-    @machine.jMachine.shareWith
-      target    : [nickname]
-      permanent : yes
-      asUser    : task is 'add'
-
-    , (err) =>
+    remote.api.SharedMachine[task] @machine.uid, [nickname], (err) =>

       return @showNotification err  if err


Working ESRI service

Working ESRI service

--- indicatorators/esri.coffee
+++ indicatorators/esri.coffee
@@ -6,8 +6,8 @@
 indicatorDefinitions = JSON.parse(fs.readFileSync('./definitions/esri_indicator_definitions.json', 'UTF8'))

 #ESRI_URL = "http://196.218.36.14/ka/rest/services" # K&A Egypt
-#ESRI_URL = "http://nrtstest.ead.ae/ka/rest/services" # EAD Server
-ESRI_URL = "http://10.20.1.73/ka/rest/services/" # EAD Server (Possibly)
+ESRI_URL = "http://nrtstest.ead.ae/ka/rest/services" # EAD Server
+#ESRI_URL = "http://10.20.1.73/ka/rest/services/" # EAD Server (Possibly)
 ESRI_QUERY_SUFFIX =
   'where': 'objectid > 0'
   'objectIds': ''


use test router for context

use test router for context

--- tutor/specs/components/helpers/enzyme-context.coffee
+++ tutor/specs/components/helpers/enzyme-context.coffee
@@ -1,6 +1,7 @@
 React       = require 'react'
 extend      = require 'lodash/extend'
 TestBackend = require 'react-dnd-test-backend'
+TestRouter  = require './test-router'
 {DragDropManager} = require 'dnd-core'

@@ -10,7 +11,7 @@
     extend({
       context:
         dragDropManager: new DragDropManager(TestBackend)
-        router: {}
+        router: new TestRouter()
       childContextTypes:
         dragDropManager: React.PropTypes.object
         router: React.PropTypes.object


Fix primary post page (/posts/:post_id)

Fix primary post page (/posts/:post_id)

--- assets/javascripts/views/posts/conversation.js.coffee
+++ assets/javascripts/views/posts/conversation.js.coffee
@@ -13,7 +13,11 @@
     @on 'ready', @initPostViews

     @on 'change:post', @render
-    new HTTP 'GET', "#{TentStatus.config.current_tent_api_root}/posts/#{encodeURIComponent @entity}/#{@post_id}", null, (post, xhr) =>
+    url = if TentStatus.config.domain_entity.assertEqual(@entity)
+      "#{TentStatus.config.current_tent_api_root}/posts/#{@post_id}"
+    else
+      "#{TentStatus.config.current_tent_api_root}/posts/#{encodeURIComponent @entity}/#{@post_id}"
+    new HTTP 'GET', url, null, (post, xhr) =>
       return unless xhr.status == 200
       post = new TentStatus.Models.Post post
       post.on 'change:profile', => @render()


CHG: fix a bug of remote table panel

CHG: fix a bug of remote table panel

--- lib/assets/javascripts/tao/ui/table/expandable.coffee
+++ lib/assets/javascripts/tao/ui/table/expandable.coffee
@@ -30,7 +30,7 @@
     $row.addClass 'expanded expanding'
     $panel.addClass 'expanded'
     $panelWrapper.css 'height', $panelWrapper[0].scrollHeight
-    @_loadRemotePanel $panel
+    @_loadRemotePanel $panel.find('> [data-url]')
     @

   collapseRow: (row) ->


In prod mode, add leading underscore check

--- configs/build-mode.coffee
+++ configs/build-mode.coffee
@@ -1,3 +1,5 @@
+{basename} = require 'path'
+
 module.exports = (mode, config) ->
   switch mode
     when 'dev' then setDevMode config
@@ -21,5 +23,7 @@

 # Modify given config so that test files are ignored
 setProdMode = (config) ->
-  config.conventions = ignored: (file) -> /^test/.test file
+  config.conventions ?= {}
+  config.conventions.ignored = (file) ->
+    basename(file).indexOf('_') is 0 or /^test/.test(file)
   config


Adding sisterly feuding

Adding sisterly feuding

--- scripts/love.coffee
+++ scripts/love.coffee
@@ -8,12 +8,15 @@
   robot.respond /\<3/i, (msg) ->
     msg.reply "<3"

+  robot.hear /┬─┬/, (msg) ->
+    msg.send "They flipped that for a reason, Rommie!" if msg.message.user.name is "Rommie"
+
   robot.respond /you\'re the best/i, (msg) ->
     msg.reply "Awe, you're the best!"

   robot.hear /(dammit|damnit|damn it|fucking|fuck you),? Liona/i, (msg) ->
     msg.reply "Oh come on! What do you want from me?  I run on node!  Ugh, rude."

-  reg = new RegExp(", ?#{process.env.HUBOT_IRC_NICK || 'Liona'}\?", 'i')
+  reg = new RegExp(", ?#{process.env.HUBOT_IRC_NICK || 'Liona'}\\?", 'i')
   robot.hear reg, (msg) ->
     msg.reply "I have no idea why you're asking me, I'm a bot."


Finish support for history listing in /mine

--- scripts/mine.coffee
+++ scripts/mine.coffee
@@ -25,23 +25,113 @@
     first = page * itemsPerPage
     last = page * itemsPerPage + itemsPerPage
     last = reversedHistory.length if last > reversedHistory.length
-    itemsToLoad = reversedHistory[first...last]
+    itemsToLoad = reversedHistory[first...last].reverse()

     itemsToFetch = (i for i in itemsToLoad when not detailedHistory[i]?)
     if itemsToFetch.length > 0
         UserHistory.loadDetailedHistory(itemsToFetch, (result) ->
             detailedHistory[k] = v for k, v of result
+            itemsToLoad = (detailedHistory[k] or k for k in itemsToLoad)
             loadPage(itemsToLoad)
+            document.getElementById('progress').classList.add('hidden')
         )
     else
+        itemsToLoad = (detailedHistory[k] for k in itemsToLoad)
         loadPage(itemsToLoad)
+        document.getElementById('progress').classList.add('hidden')

 loadPage = (items) ->
     container = document.getElementById('items')
     container.appendChild(createView(item)) for item in items

-createView = (item) ->
-    # todo
+createView = (item, noLink = false) ->
+    container = document.createElement('div')
+    if not item.hash
+        container.id = item
+        container.className = 'missing-item'
+        text = document.createElement('div')
+        text.textContent = 'This item no longer exists.'
+        container.appendChild(text)
+        forget = document.createElement('a')
+        forget.textContent = 'Remove from history'
+        forget.href = '/forget/' + item
+        forget.addEventListener('click', (e) ->
+            e.preventDefault()
+            confirm((a) ->
+                return if not a
+                UserHistory.remove(item.hash)
+                container.parentElement.removeChild(container)
+                # todo: don't reload the entire page for this
+                loadCurrentPage()
+            )
+        )
+        container.appendChild(forget)
+        return container
+    else
+        preview = null
+        if item.type == 'image/gif' or item.type.indexOf('video/') == 0
+            preview = document.createElement('video')
+            preview.loop = true
+            for file in item.files
+                continue if file.type == 'image/gif'
+                source = document.createElement('source')
+                source.src = file.file
+                source.type = file.type
+                preview.appendChild(source)
+            preview.volume = 0
+            preview.play()
+        else if item.type.indexOf('image/') == 0
+            preview = document.createElement('img')
+            preview.src = item.files[0].file
+        else if item.type.indexOf('audio/') == 0
+            preview = document.createElement('img')
+            preview.src = '/static/audio-player.png'
+            preview.style.marginTop = '23px'
+        else if item.type == 'application/album'
+            preview = document.createElement('div')
+            preview.className = 'album-preview'
+            for file in item.files[..3]
+                preview.appendChild(createView(file, true))
+        preview.classList.add('item')
+        if not noLink
+            outerContainer = document.createElement('div')
+            bar = document.createElement('div')
+            bar.className = 'bar'
+
+            forgetLink = document.createElement('a')
+            forgetLink.textContent = 'Forget'
+            forgetLink.className = 'left'
+            forgetLink.href = '/forget/' + item.hash
+            forgetLink.addEventListener('click', (e) ->
+                e.preventDefault()
+                # toto
+            )
+            forgetLink.title = 'Remove this item from your history'
+            bar.appendChild(forgetLink)
+
+            deleteLink = document.createElement('a')
+            deleteLink.textContent = 'Delete'
+            deleteLink.className = 'right'
+            deleteLink.href = '/delete/' + item.hash
+            deleteLink.addEventListener('click', (e) ->
+                e.preventDefault()
+                # toto
+            )
+            deleteLink.title = 'Delete this item from the site'
+            bar.appendChild(deleteLink)
+
+            a = document.createElement('a')
+            a.href = '/' + item.hash
+            a.target = '_blank'
+            a.appendChild(preview)
+            container.appendChild(a)
+            container.appendChild(bar)
+            outerContainer.appendChild(container)
+            outerContainer.className = 'item-wrapper'
+            return outerContainer
+        else
+            container.appendChild(preview)
+            return container

 createPagination = ->
     history = UserHistory.getHistory()


Rearranged to put user settings at the top.

--- reminders.widget/reminders.coffee
+++ reminders.widget/reminders.coffee
@@ -1,19 +1,33 @@
-# Externalize styles for more flexibility
-# Current options are:
-# - reminders.widget/styles/default.css (original style)
-# - reminders.widget/styles/sidebar.css (sidebar style)
+#############################
+# Widget Settings
+# File containing widget style
+# See: reminders.widget/styles/README.md
+styleFilename = 'default.css'
+# Number of tasks to show per list, 0 for all
+tasksPerList = 0
+# Whether notes get shown. Either true or false
+showNotes = true
+# Lists to hide, or leave empty as [] to show all
+listsToNotShow = []
+# For American's and such. Either true or false
+monthBeforeDay = false
+# Set the refresh frequency (milliseconds).
+refreshFrequency: '1m'
+#############################
+
+#############################
+# Do not alter below here.
+#############################
+
+#############################
+# CSS styling for the widget
 style: """
-    @import url(reminders.widget/styles/default.css);
+    @import url(reminders.widget/styles/""" + styleFilename + """);
 """

+#############################
+# Widget update behavior
 update: (output, domEl) ->
-    # Widget Settings
-    tasksPerList = 0    # Number of tasks to show per list, 0 for all
-    showNotes = true   # Whether notes get shown. Either true or false
-    listsToNotShow = [] # Or leave empty as [] to show all
-    monthBeforeDay = false # For American's and such. Either true or false
-
-    # Do not alter below here
     str = '
'
     listNameTpl = ''
     reminders = JSON.parse(output)
@@ -116,12 +130,14 @@
     str += '
'
     @content.html(str)

-# Set the refresh frequency (milliseconds).
-refreshFrequency: '1m'
-
+#############################
 # Command
 command: 'reminders.widget/pending.sh'

+#############################
+# Transforms kludgy JSON from pending.sh
+# into something more reasonable.
+# TODO: Fix the shell script
 tasksByList: (output) ->
     reminders = JSON.parse(output)
     listTasks = {}
@@ -130,15 +146,20 @@
         listTasks[t.list].push(t)
     return listTasks

-# Most of the following was cribbed from
+#############################
+# Shows errors if they occur
 showError: (err) ->
 	if @content
 		@content.html '
' + err + '
'

+#############################
+# Wrapper div
 render: (output) -> """

 """

+#############################
+# Launch Reminders on click
 afterRender: (domEl)->
   $(domEl).on 'click', => @run "open /Applications/Reminders.app"


Create free VM if it's missing.

Create free VM if it's missing.

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -129,6 +129,14 @@
       createDefaultVM()

   createNewVM:->
+    vmController = @getSingleton('vmController')
+    vmController.hasDefaultVM (state)->
+      unless state
+        vmController.createDefaultVM()
+      else
+        vmController.createPaidVM()
+
+  createPaidVM:->
     return  if @dialogIsOpen

     vmController        = @getSingleton('vmController')


machinelist: UnknownProvider support

machinelist: UnknownProvider support

The valid "UnknownProvider" value was not added to the UI in the
machine list modal. This commit adds that, and removes the image link
when there is no valid link.

--- client/app/lib/environment/machineslistitem.coffee
+++ client/app/lib/environment/machineslistitem.coffee
@@ -78,14 +78,19 @@
     logo  = pData.logo or provider.toLowerCase()
     url   = pData.url

+    logoImg = """
+
+    """
+
+    # If the url has text, make the logoImg into a link
+    logoImg = "#{logoImg}"  if url
+
     """

         {{> @labelLink}}

-
-
-
+        #{logoImg}

         VM{{> @settingsLink}}
@@ -101,15 +106,16 @@
     """

   PROVIDERS      =
-    Azure        : url: 'https://azure.microsoft.com/en-us'
-    HPCloud      : url: 'http://www.hpcloud.com'
-    Joyent       : url: 'https://www.joyent.com/'
-    SoftLayer    : url: 'http://www.softlayer.com'
-    Rackspace    : url: 'http://www.rackspace.com'
-    GoogleCloud  : url: 'https://cloud.google.com'
-    DigitalOcean : url: 'https://www.digitalocean.com'
-    AWS          : url: 'http://aws.amazon.com'
+    Azure           : url: 'https://azure.microsoft.com/en-us'
+    HPCloud         : url: 'http://www.hpcloud.com'
+    Joyent          : url: 'https://www.joyent.com/'
+    SoftLayer       : url: 'http://www.softlayer.com'
+    Rackspace       : url: 'http://www.rackspace.com'
+    GoogleCloud     : url: 'https://cloud.google.com'
+    DigitalOcean    : url: 'https://www.digitalocean.com'
+    AWS             : url: 'http://aws.amazon.com'
+    UnknownProvider : {}

     # handle `jMachine.provider` field.
-    aws          : url: 'http://aws.amazon.com'
-    koding       : url: 'http://aws.amazon.com', logo: 'aws'
+    aws             : url: 'http://aws.amazon.com'
+    koding          : url: 'http://aws.amazon.com', logo: 'aws'


compute/resource: get unread count from stack

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/util/calculateOwnedResources.coffee
+++ client/app/lib/util/calculateOwnedResources.coffee
@@ -14,7 +14,8 @@
     unless stacks.length
       return [{ stack: null, template, unreadCount: 0 }]

-    return stacks.map (stack) -> { stack, template, unreadCount: 0 }
+    return stacks.map (stack) ->
+      return { stack, template, unreadCount: stack.getUnreadCount() }

   debug 'resources are calculated before flatten', resources


fix image to use glyphspec too

fix image to use glyphspec too

--- bokehjs/src/coffee/renderer/glyph/image.coffee
+++ bokehjs/src/coffee/renderer/glyph/image.coffee
@@ -20,9 +20,9 @@
       # provided number of rows/cols, otherwise treat as a "list of lists".
       spec = @mget('glyphspec')
       if spec.rows?
-        @_fields = ['image:array', 'rows', 'cols', 'x', 'y', 'dw', 'dh', 'palette:string', 'distance_dilate']
+        @_fields = ['image:array', 'rows', 'cols', 'x', 'y', 'dw', 'dh', 'palette:string']
       else
-        @_fields = ['image:array', 'x', 'y', 'dw', 'dh', 'palette:string', 'distance_dilate']
+        @_fields = ['image:array', 'x', 'y', 'dw', 'dh', 'palette:string']
       super(options)

     _set_data: (@data) ->
@@ -65,8 +65,8 @@

     _map_data: () ->
       [@sx, @sy] = @plot_view.map_to_screen(@x, @glyph_props.x.units, @y, @glyph_props.y.units)
-      @sw = @distance_vector('x', 'dw', 'edge',  @mget('distance_dilate'))
-      @sh = @distance_vector('y', 'dh', 'edge',  @mget('distance_dilate'))
+      @sw = @distance_vector('x', 'dw', 'edge',  @mget('glyphspec')['distance_dilate'])
+      @sh = @distance_vector('y', 'dh', 'edge',  @mget('glyphspec')['distance_dilate'])

     _render: (ctx, indices, glyph_props) ->
       old_smoothing = ctx.getImageSmoothingEnabled()


PaymentController: subscriptions: to return `koding` plan by default on default env.

--- client/app/lib/payment/paymentcontroller.coffee
+++ client/app/lib/payment/paymentcontroller.coffee
@@ -1,5 +1,6 @@
-remote = require('../remote').getInstance()
-kd     = require 'kd'
+kd      = require 'kd'
+remote  = require('../remote').getInstance()
+globals = require 'globals'

 module.exports = class PaymentController extends kd.Controller
@@ -26,8 +27,17 @@
       callback err, result

-  subscriptions : (callback) -> @api().subscriptions {}, callback
-  invoices      : (callback) -> @api().invoices {}, callback
+  subscriptions: (callback) ->
+
+    # return plan as 'koding' on default environment
+    if globals.config.environment is 'default'
+      # checkout servers/models/computeproviders/plans.coffee
+      return callback null, { planTitle: 'koding' }
+
+    @api().subscriptions {}, callback
+
+
+  invoices: (callback) -> @api().invoices {}, callback

   creditCard: (callback) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -2,7 +2,7 @@

 test "should clamp the x-axis", ->
   player = GameObject
-
+    includedMo
   player.include(ClampBounds)

   # Mock App.width, App.height


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -25,7 +25,7 @@

   equals player.I.y, player.I.height / 2

-  player.I.x = 800
+  player.I.y = 800
   player.update()

   equals player.I.x, App.width - player.I.width / 2


Add test for specialIndent bug

Add test for specialIndent bug

--- test/coffee/tests.coffee
+++ test/coffee/tests.coffee
@@ -164,6 +164,22 @@
       console.log hello)
     ''', 'Move for into socket (req. paren wrap)'

+  test 'specialIndent bug', ->
+    document = coffee.parse '''
+    for i in [1..10]
+      ``
+    for i in [1..10]
+      fd 10
+    '''
+
+    document.getBlockOnLine(2).moveTo document.getBlockOnLine(1).end.prev.container.start, coffee
+
+    strictEqual document.stringify(coffee.empty), '''
+    for i in [1..10]
+      for i in [1..10]
+        fd 10
+    '''
+
   test 'Paren wrap', ->
     document = coffee.parse '''
     Math.sqrt 2


skelton for parsing reflog message in data-driven manner

--- content/gitoe-repo.coffee
+++ content/gitoe-repo.coffee
@@ -50,6 +50,18 @@
     return sorted

 class GitoeHistorian
+  @message_patterns = {
+    head: {
+      patterns: {
+        clone: /^clone: from (.*)/
+      }
+      actions : {
+        clone: (matched,change)->
+      }
+    }
+    branch: {
+    }
+  }
   constructor: ()->
     @cb               = {} # { name: fun }

@@ -118,10 +130,51 @@
         }
     reflog_branches.sort (a,b)->( a.time - b.time )
     for change in reflog_branches
+      change.head = []
       while reflog_head.length > 0 and reflog_head[0].time <= change.time
-        change.head ?= []
         change.head.push reflog_head.shift()
-    reflog_branches
+    @convert_repo reflog_branches
+
+  convert_repo: (reflog_branches)->
+    # convert reflogs to DSL
+    self = @
+    converted_repo = []
+    for change in reflog_branches
+      converted_change = @convert_change change
+      converted_change.head = change.head.map @convert_change
+      converted_repo.push converted_change
+    return converted_repo
+
+  convert_change: (change)=>
+    message = change.message
+    if change.branch is "HEAD"
+      @parse_message( message, change, GitoeHistorian.message_patterns.head )
+    else
+      @parse_message( message, change, GitoeHistorian.message_patterns.branch )
+
+  parse_message: ( message, change, rule )->
+    matched = @match_message message, rule.patterns
+    if matched.num isnt 1
+      [
+        "not recognized: "
+        { message: message }
+      ]
+    else
+      type =  matched.last
+      match = matched[type]
+      rule.actions[type]( match, change )
+
+  match_message : (message, patterns)-> # { case => matched }
+    matched = {
+      num  : 0
+      last : null
+    }
+    for type, regex of patterns
+      if match = regex.exec message
+        matched.last = type
+        matched[type] = match
+        matched.num++
+    matched

 class GitoeRepo
   constructor: ()->


That goes double! :beers:

That goes double! :beers:

--- scripts/vote.coffee
+++ scripts/vote.coffee
@@ -50,7 +50,7 @@
     # We're not done unless we get three votes
     return if (@votes.yes.length + @votes.no.length) < 3
     # We're not done if there's no difference between the votes
--    return if (Math.abs(@votes.yes.length - @votes.no.length) < 0)
+    return if (Math.abs(@votes.yes.length - @votes.no.length) < 0)
     # Is this still being contested?
     return if (Date.now() - @votes.prev) < 10000
     @finish()


comment

comment

--- client/components/autocomplete_select/index.coffee
+++ client/components/autocomplete_select/index.coffee
@@ -12,7 +12,9 @@
     loading: true, value: null, id: null

   clear: ->
-    @setState { value: null }, => _.defer => $(@refs.input.getDOMNode()).focus()
+    @setState { value: null }, =>
+      # Deferring to focus after render happens
+      _.defer => $(@refs.input.getDOMNode()).focus()
     @props.cleared?()

   componentDidUpdate: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -1,6 +1,6 @@
 module "Controllable"

 test "should move player left while key is pressed", ->
-  player
+  player = GameObject

 module()


Tests: Environments: Paidaccount: Fixed createSnapshotForNonPaidUser test.

--- client/test/lib/environments/paidaccount.coffee
+++ client/test/lib/environments/paidaccount.coffee
@@ -56,7 +56,7 @@
     helpers.waitForVMRunning(browser)
     environmentHelpers.attemptCreateSnapshot(browser)

-    browser.element 'css selector', '.snapshots .snapshot', (result) =>
+    browser.element 'css selector', '.snapshots .add-view', (result) =>
       if result.status is 0
         browser.end()
       else


re-enable standby timeout - disabling it didn't help

--- src/smc-webapp/client_browser.coffee
+++ src/smc-webapp/client_browser.coffee
@@ -90,8 +90,8 @@
         # The following two lines disable the idle timeout functionality.
         # This is disabled since it may be causing a DOS attack
         # by users... not 100% sure yet.
-        @set_standby_timeout_m = ->   # make this a no-op
-        #setTimeout(@_init_idle, 15 * 1000)  # don't setup.
+        #@set_standby_timeout_m = ->   # make this a no-op
+        setTimeout(@_init_idle, 15 * 1000)

     _setup_window_smc: () =>
         # if we are in DEBUG mode, inject the client into the global window object


For the project tab tooltip... use the title for the title :-)

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -1369,10 +1369,11 @@
                 # Ensure that at some point we'll have the title if possible (e.g., if public)
                 @props.redux?.getActions('projects').fetch_public_project_title(@props.project_id)
                 return <Loading />
+        desc = misc.trunc(@props.project_map?.getIn([@props.project_id, 'description']) ? '', 128)
         project_state = @props.project_map?.getIn([@props.project_id, 'state', 'state'])
         icon = require('smc-util/schema').COMPUTE_STATES[project_state]?.icon ? 'bullhorn'

-            <Tip title={'ID: ' + @props.project_id[...8] + '...'} tip={title} placement='bottom' size='small'>
+            <Tip title={misc.trunc(title,32)} tip={desc} placement='bottom' size='small'>
                 <Icon name={icon} style={fontSize:'20px'} />
                 {misc.trunc(title, 32)}
             </Tip>


[Group Notifications] add case for jgroup, do nothing when user clicks jgroup item

--- client/landing/app/CommonViews/messagesList.coffee
+++ client/landing/app/CommonViews/messagesList.coffee
@@ -181,11 +181,15 @@
           title : "This post has been deleted!"
           duration : 1000

-    if @snapshot.anchor.constructorName is "JPrivateMessage"
-      appManager = KD.getSingleton "appManager"
-      appManager.open "Inbox"
-      appManager.tell 'Inbox', "goToMessages"
-    else if @snapshot.anchor.constructorName in ["JComment", "JReview", "JOpinion"]
-      KD.remote.api[@snapshot.anchor.constructorName].fetchRelated @snapshot.anchor.id, showPost
-    else unless @snapshot.anchor.constructorName is "JAccount"
-      KD.remote.api[@snapshot.anchor.constructorName].one _id : @snapshot.anchor.id, showPost
+    switch @snapshot.anchor.constructorName
+      when  "JPrivateMessage"
+        appManager = KD.getSingleton "appManager"
+        appManager.open "Inbox"
+        appManager.tell 'Inbox', "goToMessages"
+      when "JComment", "JReview", "JOpinion"
+        KD.remote.api[@snapshot.anchor.constructorName].fetchRelated @snapshot.anchor.id, showPost
+      when "JGroup"
+        # do nothing
+      else
+        unless @snapshot.anchor.constructorName is "JAccount"
+          KD.remote.api[@snapshot.anchor.constructorName].one _id : @snapshot.anchor.id, showPost


Fix message

Fix message

--- client/landing/app/CommonViews/messagesList.coffee
+++ client/landing/app/CommonViews/messagesList.coffee
@@ -150,7 +150,7 @@
           appManager.tell "Activity", "createContentDisplay", post
         else
           new KDNotificationView
-            title : "Oww this post deleted!"
+            title : "This post has been deleted!"
             duration : 1000

     # {sourceName,sourceId} = @getData()[0]


SocialWorker: Tests: add migration method test cases

--- workers/social/lib/social/models/account.test.coffee
+++ workers/social/lib/social/models/account.test.coffee
@@ -7,10 +7,11 @@
   generateRandomString
   checkBongoConnectivity } = require '../../../testhelper'

-JUser    = require './user'
-JGroup   = require './group'
-JAccount = require './account'
-JSession = require './session'
+JUser            = require './user'
+JGroup           = require './group'
+JAccount         = require './account'
+JAppStorage      = require './appstorage'
+{ Relationship } = require 'jraphical'

 # making sure we have db connection before tests
@@ -256,9 +257,9 @@
           account.fetchOrCreateAppStorage options, (err, appStorage) ->
             expect(err).to.not.exist
             expect(appStorage).to.be.an 'object'
+            expect(appStorage.bongo_.constructorName).to.be.equal 'JCombinedAppStorage'
             expect(appStorage.accountId).to.be.deep.equal account._id
             expect(appStorage.storage[appId]).to.be.an 'object'
-            expect(appStorage.storage[appId]['version']).to.be.a 'string'
             expect(appStorage.storage[appId]['data']).to.be.an 'object'
             expect(appStorage.storage[appId]['data']).to.be.empty
             done()
@@ -282,9 +283,9 @@
                 expect(err).to.not.exist
                 appStorage = storage
                 expect(appStorage).to.be.an 'object'
+                expect(appStorage.bongo_.constructorName).to.be.equal 'JCombinedAppStorage'
                 expect(appStorage.accountId).to.be.deep.equal account._id
                 expect(appStorage.storage[appId]).to.be.an 'object'
-                expect(appStorage.storage[appId]['version']).to.be.a 'string'
                 expect(appStorage.storage[appId]['data']).to.be.an 'object'
                 expect(appStorage.storage[appId]['data']).to.be.empty
                 queue.next()
@@ -293,6 +294,7 @@
               # expecting previously created app storage to be fetched
               account.fetchOrCreateAppStorage options, (err, storage) ->
                 expect(err).to.not.exist
+                expect(storage.bongo_.constructorName).to.be.equal 'JCombinedAppStorage'
                 expect(storage._id.toString()).to.be.equal appStorage._id.toString()
                 expect(storage.accountId).to.be.deep.equal appStorage.accountId
                 expect(storage.storage[appId]).to.be.deep.equal appStorage.storage[appId]
@@ -304,6 +306,106 @@

           daisy queue

+
+  describe 'migrateOldAppStorageIfExists()', ->
+
+    createOldAppStorageDocument = (data, callback) ->
+      { account, appId, version, bucket } = data
+      bucket ?= {}
+
+      storage = new JAppStorage { appId, version, bucket }
+      storage._shouldPrune = no
+      storage.save (err) ->
+        return callback err  if err
+
+        relationshipOptions =
+          targetId    : storage.getId()
+          targetName  : 'JAppStorage'
+          sourceId    : account.getId()
+          sourceName  : 'JAccount'
+          as          : 'appStorage'
+          data        : { appId, version }
+
+        rel = new Relationship relationshipOptions
+        rel.save (err) ->
+          callback err, { storage, relationshipOptions }
+
+
+    it 'should return null if storage doesnt exist', (done) ->
+
+      withConvertedUser ({ account }) ->
+
+        account.migrateOldAppStorageIfExists {}, (err, storage) ->
+          expect(err).to.not.exist
+          expect(storage).to.not.exist
+          done()
+
+
+    it 'should migrate old storage if there is one', (done) ->
+
+      withConvertedUser ({ account }) ->
+
+        version             = generateRandomString()
+        appId               = generateRandomString()
+        bucket              = {}
+        oldStorage          = {}
+        relationshipOptions = {}
+
+        queue = [
+
+          ->
+            # creating an old app storage document
+            bucket =
+              someString  : generateRandomString()
+              someData    :
+                moreData  : { data : {} }
+              anotherData :
+                someArray : [1,2,3]
+
+            options = { account, appId, version, bucket }
+            createOldAppStorageDocument options, (err, data) ->
+              expect(err).to.not.exist
+              { storage : oldStorage, relationshipOptions } = data
+              expect(oldStorage.bongo_.constructorName).to.be.equal 'JAppStorage'
+              queue.next()
+
+          ->
+            # expecting old app storage document to be migrated
+            options = { appId, version }
+            account.migrateOldAppStorageIfExists options, (err, newStorage) ->
+              expect(err).to.not.exist
+              console.log newStorage
+              expect(newStorage).to.be.an 'object'
+              expect(newStorage.bongo_.constructorName).to.be.equal 'JCombinedAppStorage'
+              expect(newStorage.accountId).to.be.deep.equal account._id
+              expect(newStorage.storage[appId].data).to.be.deep.equal bucket
+              expect(newStorage.storage.bucket).to.not.exist
+              expect(newStorage.storage.version).to.not.exist
+              queue.next()
+
+          ->
+            # expecting old storage document to be deleted
+            options = { appId, version }
+            JAppStorage.one options, (err, oldStorage) ->
+              expect(err).to.not.exist
+              expect(oldStorage).to.not.exist
+              queue.next()
+
+          ->
+            # expecting relationship to be deleted
+            expectRelation.toNotExist relationshipOptions, (err, data) ->
+              expect(err).to.not.exist
+              expect(data).to.not.exist
+              queue.next()
+
+          -> done()
+
+        ]
+
+        daisy queue
+
+
+
 beforeTests()

 runTests()


simplify the notice card

simplify the notice card

--- modules/card/main/index.coffee
+++ modules/card/main/index.coffee
@@ -1,6 +1,7 @@
 div = (clasz) -> hx.detached('div').class(clasz)
 span = (clasz) -> hx.detached('span').class(clasz)

+#XXX: use the palette context functions instead
 classContext = (selection, context) -> if context then selection.classed('hx-' + context, true) else selection
 textContext = (selection, context) -> if context then selection.classed('hx-text-' + context, true) else selection
 backgroundContext = (selection, context) -> if context then selection.classed('hx-background-' + context, true) else selection
@@ -181,9 +182,7 @@
 # Complete cards

 hx.card.notice = (options = {}) ->
-  card = hx.card().classed('hx-card-small', true)
+  hx.card().classed('hx-card-small', true)
     .add(hx.card.header.section({sectionContext: options.context or 'info'})
-      .add(hx.card.title({titleText: 'Note'})))
+      .add(hx.card.title({titleText: options.title or 'Note'})))
     .add(hx.card.section().add(hx.card.text({text: options.message})))
-    .add(hx.card.action.section({text: 'Ok. Got it.', icon: 'fa fa-check'}).on('click', -> card.remove() ))
-  card


make navbarBrand template more generic

make navbarBrand template more generic

--- client/views/common/navbarBrand/navbarBrand.coffee
+++ client/views/common/navbarBrand/navbarBrand.coffee
@@ -3,19 +3,22 @@
 	name: -> TAPi18n.__('navigation.' + FlowRouter.getRouteName())

 	target: ->
-		if Session.get('parent')
-			if Session.get('target')
-				Session.get('target')
-			else if Session.get('parent') == 'settings' && FlowRouter.getParam('projectId')
-				FlowRouter.path 'settings',
-					projectId: FlowRouter.getParam('projectId')
-					language: TAPi18n.getLanguage()
-			else if Session.get('parent') == 'admin' && FlowRouter.getParam('projectId')
-				FlowRouter.path 'admin',
-					projectId: FlowRouter.getParam('projectId')
-					language: TAPi18n.getLanguage()
-			else if Session.get('parent') == 'home'
-				FlowRouter.path 'home', language: TAPi18n.getLanguage()
+		parent = Session.get('parent')
+
+		if parent
+			target = Session.get('target')
+
+			if target
+				target
+			else
+				projectId = FlowRouter.getParam('projectId')
+
+				if parent != 'home' && projectId
+					FlowRouter.path parent,
+						projectId: projectId
+						language: TAPi18n.getLanguage()
+				else
+					FlowRouter.path 'home', language: TAPi18n.getLanguage()

 Template.navbarBrand.events


fix: activeRepl not recognized the first time

--- lib/controller.coffee
+++ lib/controller.coffee
@@ -31,6 +31,7 @@
         delete @repls[uri]

       repl = new Repl(uri, @projectRoot, onClose)
+      @activeRepl = repl
       @repls[uri] = repl
       repl.startSCLang()
       # and must return the window


moved the html fetching into here

moved the html fetching into here

--- src/extras/testing/controller_test_case.coffee
+++ src/extras/testing/controller_test_case.coffee
@@ -27,6 +27,38 @@
       @assert params.params[namedArg], "named argument: #{namedArg} doesn't exist in parameters"

     params.preAction?()
-    controller.dispatch actionRoute.action, params.params
+    try
+      controller.dispatch actionRoute.action, params.params
+      currentView = controller.get('currentView')
+      $('body').append('<div id="batman_fixture">')
+      currentView.get('node')
+      currentView.addToParentNode( $('#batman_fixture')[0] )
+      currentView.initializeBindings()
+      @assert currentView.get('html'), "No HTML for view"
+    catch e
+      @assert false, "Caught exception in view bindings: #{e.toString()}"
+    finally
+      $('#batman_fixture').remove()
+
     params.postAction?()
     null
+
+  @fetchHTML: (basePath, path, callback) ->
+    $.ajax( {
+      url: "#{basePath}/#{path}.js.htm"
+      method: 'GET'
+      success: (data) ->
+        callback(data, path)
+      error: =>
+        callback(undefined)
+      } )
+
+  @populateHTML: (basePath, callback) ->
+    routes = Batman.currentApp.get('routes.routeMap.childrenByOrder')
+    numRoutes = routes.length
+    for route in routes
+      @fetchHTML basePath, "#{route.controller}/#{route.action}", (data, path) ->
+        Batman.View.store.set(Batman.Navigator.normalizePath(path), data)
+        numRoutes -= 1
+        if numRoutes == 0
+          callback()


fix issue where the codeglance would flicker sometimes

--- lib/codeglance-view.coffee
+++ lib/codeglance-view.coffee
@@ -32,8 +32,8 @@
     @editor.setText @minimap.getTextEditor().getText()

   show: (parentTextEditor) ->
-    # hide the border until content is visible
-    @style.borderColor = if @clientHeight is 0 then 'transparent' else ''
+    # hide until content is visible
+    @style.opacity = if @clientHeight is 0 then 0 else ''
     @style.display = ''

   hide: ->


swap order of on_tile_load render vs. cache tile

--- bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
@@ -68,8 +68,8 @@
     tile_data = e.target.tile_data
     tile_data.img = e.target
     tile_data.current = true
+    @mget('tile_source').tiles[tile_data.cache_key] = tile_data
     @_render_tile(tile_data.cache_key)
-    @mget('tile_source').tiles[tile_data.cache_key] = tile_data

   _on_tile_cache_load: (e) =>
     tile_data = e.target.tile_data


服务器状态页面需要登录

服务器状态页面需要登录

--- core/router/public.coffee
+++ core/router/public.coffee
@@ -3,7 +3,7 @@
 fs = require 'fs'

 plugin = require '../plugin'
-{renderAccount} = require './middleware'
+{renderAccount, requireAuthenticate} = require './middleware'

 monitor = require '../../plugin/linux/monitor'

@@ -20,7 +20,7 @@
       plans: _.values(config.plans)
       services: result

-exports.get '/monitor', renderAccount, (req, res) ->
+exports.get '/monitor', renderAccount, requireAuthenticate, (req, res) ->
   async.parallel
     resources_usage: (callback) ->
       monitor.monitoringStorage ->


Use Marbles.DOM.setInnerText()

Use Marbles.DOM.setInnerText()

--- lib/assets/javascripts/views/subscription_toggle.js.coffee
+++ lib/assets/javascripts/views/subscription_toggle.js.coffee
@@ -85,7 +85,7 @@

   createSubscriptions: =>
     Marbles.DOM.addClass(@el, 'disabled')
-    @el.innerText = '...'
+    Marbles.DOM.setInnerText(@el, '...')

     TentStatus.Models.Following.create @entity,
       failure: (res, xhr) =>


update tests

update tests

--- src/tests/interim-mkts-table-patterns.coffee
+++ src/tests/interim-mkts-table-patterns.coffee
@@ -104,41 +104,31 @@
   MKTS_TABLE.unitheight   R, '1mm'
   MKTS_TABLE.columnwidth  R, '20'
   MKTS_TABLE.rowheight    R, '10'
-  MKTS_TABLE.fieldcells   R, 'A1..A4:@japanese-text'
-  MKTS_TABLE.fieldcells   R, 'A1..A4:@japanese-text'
-  MKTS_TABLE.fieldcells   R, 'B1..D1'
-  MKTS_TABLE.fieldcells   R, 'B2..D2'
-  MKTS_TABLE.fieldcells   R, 'B3..B4'
-  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-topright'
-  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-bottomright'
-  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-topleft'
-  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-bottomleft'
+  MKTS_TABLE.fieldcells   R, 'A1..A4:@japanese-text'        # fieldnr 1
+  MKTS_TABLE.fieldcells   R, 'B1..D1'                       # fieldnr 2
+  MKTS_TABLE.fieldcells   R, 'B2..D2'                       # fieldnr 3
+  MKTS_TABLE.fieldcells   R, 'B3..B4'                       # fieldnr 4
+  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-topright'     # fieldnr 5
+  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-bottomright'  # fieldnr 6
+  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-topleft'      # fieldnr 7
+  MKTS_TABLE.fieldcells   R, 'C3..D4:@overlap-bottomleft'   # fieldnr 8
   return R

 #-----------------------------------------------------------------------------------------------------------
 @[ "MKTS_TABLE._resolve_aliases" ] = ( T, done ) ->
+  table = @_get_sample_table_1()
+  #.........................................................................................................
   probes_and_matchers = [
-    ["\"japanese-text\"",null]
-    ["@japanese-text,@headings,@foo",["@japanese-text","@headings","@foo"]]
-    [" @japanese-text,  @headings , @foo  ",["@japanese-text","@headings","@foo"]]
-    ["@some-name",["@some-name"]]
-    ["@some-name,",["@some-name"]]
-    ["@some-name,,@other-name",null]
-    ["@some-name,,",null]
-    ["@some-name,@other-name,",["@some-name","@other-name"]]
-    ["",[]]
-    [null,[]]
+    ["@japanese-text",[1]]
+    ["@overlap-bottomleft,C3",[8,"C3"]]
+    ["@overlap-bottomleft,C3..D4",[8,'C3..D4']]
+    ["A1..C3",["A1..C3"]]
+    ["A1..C3,@japanese-text",["A1..C3",1]]
     ]
   #.........................................................................................................
-  debug '93033', me = @_get_sample_table_1()
-  urge MKTS_TABLE._resolve_aliases me, '@japanese-text'
-  urge MKTS_TABLE._resolve_aliases me, '@overlap-bottomleft,C3'
-  urge MKTS_TABLE._resolve_aliases me, 'A1..C3'
-  urge MKTS_TABLE._resolve_aliases me, 'A1..C3,@japanese-text'
-  done(); return
   for [ probe, matcher, ] in probes_and_matchers
     try
-      result = MKTS_TABLE._parse_aliases null, probe
+      result = MKTS_TABLE._resolve_aliases table, probe
     catch error
       if ( matcher is null ) and ( error.message.match /aliases must be prefixed with '@'/ )?
         urge '36633', ( jr [ probe, matcher, ] )


Add a render method to menu for re-rendering the menu when open and the items change

--- modules/menu/main/index.coffee
+++ modules/menu/main/index.coffee
@@ -234,27 +234,29 @@

     @options.dropdownOptions.ddClass = 'hx-menu ' + if colorClass? then 'hx-' + colorClass else @options.dropdownOptions.ddClass

-    dropdownContent = () ->
-      container = div('hx-menu-items')
-
-      setup = (items) ->
-        if self._.itemsChanged # We don't want to keep making lots of new menu items if the items haven't changed
-          self._.itemsChanged = false
-          setupInner(self, items, self)
-        populateNode(container.node(), self._.menuItems)
-
-      # Items as set by the user.
-      rawItems = self._.items
-
-      if isFunction(rawItems)
-        self._.itemsChanged = true # Items have always changed when being returned from a function
-        rawItems((items) -> setup(items))
-      else
-        setup(rawItems)
-
-      return container
-
-    @dropdown = new Dropdown(@selector, dropdownContent, @options.dropdownOptions)
+    dropdownContainer = div('hx-menu-items')
+
+    if options.extraContent
+      dropdownContainer.add(options.extraContent)
+
+    # Items as set by the user.
+    rawItems = self._.items
+
+    setup = (items) ->
+      if self._.itemsChanged # We don't want to keep making lots of new menu items if the items haven't changed
+        self._.itemsChanged = false
+        setupInner(self, items, self)
+      populateNode(dropdownContainer.node(), self._.menuItems)
+
+    self._.setup = setup
+
+    if isFunction(rawItems)
+      self._.itemsChanged = true # Items have always changed when being returned from a function
+      rawItems((items) -> setup(items))
+    else
+      setup(rawItems)
+
+    @dropdown = new Dropdown(@selector, dropdownContainer, @options.dropdownOptions)

     @dropdown.on 'showend', =>
       if @dropdown._.dropdown?
@@ -333,6 +335,9 @@

     if @options.disabled then @disabled(@options.disabled)

+  render: () ->
+    this._.setup(this.items())
+
   renderer: (f) ->
     if arguments.length > 0
       @options.renderer = f


added test cases for ontrac and lasership

--- test/guessCarrier.coffee
+++ test/guessCarrier.coffee
@@ -101,3 +101,30 @@

     it 'detects another mail innovation tracking number', ->
       expect(guessCarrier '92748901377803583000610270').to.include 'usps'
+
+
+  describe 'for lasership', ->
+
+    it 'detects a legitimate lasership tracking number', ->
+      expect(guessCarrier 'LM25904730').to.include 'lasership'
+
+    it 'detects a another lasership tracking number', ->
+      expect(guessCarrier 'LE17119906').to.include 'lasership'
+
+    it 'ignores a malformed lasership tracking number', ->
+      expect(guessCarrier 'LE1711990').to.not.include 'lasership'
+
+    it 'detects a lasership tracking number with lower case prefix', ->
+      expect(guessCarrier 'le17119906').to.include 'lasership'
+
+
+  describe 'for ontrac', ->
+
+    it 'detects a legitimate ontrac tracking number', ->
+      expect(guessCarrier 'C10999814714549').to.include 'ontrac'
+
+    it 'ignores a malformed ontract tracking number', ->
+      expect(guessCarrier 'C1099981471459').to.not.include 'ontrac'
+
+    it 'detects a ontrac tracking number with lower case prefix', ->
+      expect(guessCarrier 'c10999814714549').to.include 'lasership'


improve AusPost test descriptions

improve AusPost test descriptions

--- test/guessCarrier.coffee
+++ test/guessCarrier.coffee
@@ -152,9 +152,9 @@
     it 'detects a 94748... number', ->
       expect(guessCarrier '9474812901015476250258').to.include 'dhlgm'

-  describe 'for auspost', ->
+  describe 'for AusPost', ->

-    it 'detects a AP Standard Barcode', ->
+    it 'detects an AP Standard Barcode', ->
       expect(guessCarrier '12345678ABB12345678901200').to.include 'auspost'

     it 'detects a GS1 Standard Barcode', ->


Fix simplified conditional

Fix simplified conditional

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -96,7 +96,7 @@
   mutationIsSignificant: (mutationSummary) ->
     textChanged = Object.keys(mutationSummary).length > 0
     composedEmptyString = @compositionInput?.getEndData() is ""
-    textChanged and not composedEmptyString
+    textChanged or not composedEmptyString

   unlessMutationOccurs: (callback) ->
     mutationCount = @mutationCount


shift+return always inputs a newline

shift+return always inputs a newline

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -34,6 +34,7 @@
         context = switch
           when event.ctrlKey then @keys.control
           when event.altKey then @keys.alt
+          when event.shiftKey then @keys.shift
           else @keys

         context[keyName]?.call(this, event)
@@ -139,3 +140,9 @@
         @delegate?.inputControllerWillPerformTyping()
         @responder?.deleteWordBackward()
         event.preventDefault()
+
+    shift:
+      return: (event) ->
+        @delegate?.inputControllerWillPerformTyping()
+        @responder?.insertString("\n")
+        event.preventDefault()


Optimization: Skip setup work for internal elements

--- src/trix/elements/trix_editor_element.coffee
+++ src/trix/elements/trix_editor_element.coffee
@@ -182,9 +182,10 @@
   # Element lifecycle

   initialize: ->
-    makeEditable(this)
-    addAccessibilityRole(this)
-    ensureAriaLabel(this)
+    unless @hasAttribute("data-trix-internal")
+      makeEditable(this)
+      addAccessibilityRole(this)
+      ensureAriaLabel(this)

   connect: ->
     unless @hasAttribute("data-trix-internal")


Add markup for better JAWS and NVDA screenreader performance (#492)

Fixes #195

--- src/trix/elements/trix_editor_element.coffee
+++ src/trix/elements/trix_editor_element.coffee
@@ -19,6 +19,10 @@
     return if element.hasAttribute("contenteditable")
     element.setAttribute("contenteditable", "")
     handleEventOnce("focus", onElement: element, withCallback: -> configureContentEditable(element))
+
+  addAccessibilityRole = (element) ->
+    return if element.hasAttribute("role")
+    element.setAttribute("role", "textbox")

   configureContentEditable = (element) ->
     disableObjectResizing(element)
@@ -154,6 +158,7 @@

   createdCallback: ->
     makeEditable(this)
+    addAccessibilityRole(this)

   attachedCallback: ->
     unless @hasAttribute("data-trix-internal")


Do not autoformat link attr when appending to a link.

Currently, if you append to hyperlinked text, Scribe doe not continue the
hyperlink automatically. I think this is a bug, and so once we acknowledge
that/make the fix, I'll take out this hack. But right now it's useful so that we
don't hit that error case all the time.

--- tests/scripts/fuzzer.coffee
+++ tests/scripts/fuzzer.coffee
@@ -59,6 +59,7 @@
           precedingAttrs = followingAttrs = {}
           if appendingToLine(index)
             precedingAttrs = getAttrsAt(index - 1)
+            delete precedingAttrs['link'] if precedingAttrs['link']?
           else if prependingToLine(index)
             precedingAttrs = followingAttrs = getAttrsAt(index)
           else


Avoid id clash when compiling multiple modules

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -5612,7 +5612,8 @@
   classes = subtractMaps ctx._scope().classes, what._scope().classes
   macros = subtractMaps ctx._scope().macros, what._scope().macros
   savedScopes = ctx.savedScopes
-  {definitions, typeNames, classes, macros, savedScopes}
+  nameIndex = ctx.nameIndex
+  {definitions, typeNames, classes, macros, savedScopes, nameIndex}

 injectedContext = (modulesToInject) ->
   ctx = new Context
@@ -5634,6 +5635,7 @@
   topScope.typeNames = concatMaps topScope.typeNames, typeNames
   topScope.classes = concatMaps topScope.classes, classes
   ctx.scopeIndex += compiledModule.savedScopes.length
+  ctx.nameIndex += compiledModule.nameIndex
   ctx

 collectRequiresFor = (name) ->


THETA image resize

THETA image resize

--- action/GenerateMode_action.cjsx
+++ action/GenerateMode_action.cjsx
@@ -90,14 +90,34 @@
           tmp = []
           data.value.forEach ( file )->
             tmp.push "asset/" + file.name
-            reader = new FileReader();
-            reader.onload = (e)->
-              buf = new Buffer(e.target.result.byteLength);
-              source = new Uint8Array(e.target.result);
-              for i in [0..e.target.result.byteLength]
-                buf[i] = source[i];
-              fs.writeFile "./public/" + templateId + "/preview/asset/" + file.name, buf
-            reader.readAsArrayBuffer file
+            if /JPG$/.test file.name ##THETA resize
+              img = new Image
+              img.onload = ( e )->
+                target = e.target;
+                canvas = document.createElement "canvas"
+                canvas.width = target.width / 2
+                canvas.height = target.height / 2
+
+                ctx = canvas.getContext "2d"
+                ctx.drawImage e.target, 0, 0, canvas.width, canvas.height
+
+                bin = atob(canvas.toDataURL( "image/jpeg" ).replace(/^.*,/, ''));
+                buf = new Buffer bin.length
+                for i in [0..bin.length]
+                  buf[i] = bin.charCodeAt i
+                fs.writeFile "./public/" + templateId + "/preview/asset/" + file.name, buf
+
+                URL.revokeObjectURL e.target.src
+              img.src = URL.createObjectURL file
+            else
+              reader = new FileReader();
+              reader.onload = (e)->
+                buf = new Buffer(e.target.result.byteLength);
+                source = new Uint8Array(e.target.result);
+                for i in [0..e.target.result.byteLength]
+                  buf[i] = source[i];
+                fs.writeFile "./public/" + templateId + "/preview/asset/" + file.name, buf
+              reader.readAsArrayBuffer file

           if data.type == "file"
             data.value = tmp[ 0 ]


Plugged the compiler back into the mode, selection by clicking works only, wip

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -16,9 +16,10 @@
       throw new Error "Could not recognize a token starting with `#{input[0..10]}`"
     [symbol] = match
     input = input[symbol.length...]
-    pos = currentPos
+    start = currentPos
     currentPos += symbol.length
-    constantLabeling {symbol, pos}
+    end = currentPos
+    constantLabeling {symbol, start, end}

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

@@ -31,12 +32,15 @@
   stack = [[]]
   for token in tokens
     if token.symbol in leftDelims
-      stack.push [token]
+      form = [token]
+      form.start = token.start
+      stack.push form
     else if token.symbol in rightDelims
       closed = stack.pop()
       if token.symbol isnt delims[closed[0].symbol]
         throw "Wrong closing delimiter #{token.symbol} for opening delimiter #{closed[0].symbol}"
       closed.push token
+      closed.end = token.end
       if not stack[stack.length - 1]
         throw "Missing opening delimeter matching #{token.symbol}"
       stack[stack.length - 1].push closed
@@ -340,7 +344,7 @@

 expressionCompile = (ctx, expression) ->
-  throw "invalid expressionCompile args" unless ctx instanceof Context and expression
+  throw new Error "invalid expressionCompile args" unless ctx instanceof Context and expression
   (if isAtom expression
     atomCompile
   else if isTuple expression
@@ -350,7 +354,7 @@
   else if isCall expression
     callCompile
   else
-    log "Not handled", expression
+    # log "Not handled", expression
     throw "Not handled expression in expressionCompile"
   ) ctx, expression

@@ -407,7 +411,7 @@

   paramNames = ctx.arity operator.symbol
   if not paramNames
-    log "deferring in known call #{operator.symbol}"
+    # log "deferring in known call #{operator.symbol}"
     ctx.setDefer operator.symbol
     return 'deferred'
   positionalParams = filter ((param) -> not (lookupInMap labeledArgs, param)), paramNames
@@ -438,7 +442,7 @@
         malformed call, "function patterns not supported"
     else
       if nonLabeledArgs.length < positionalParams.length
-        log "currying known call"
+        # log "currying known call"
         lambda = (fn_ extraParams, sortedCall)
         compiled = macroCompile ctx, lambda
         retrieve call, lambda
@@ -468,7 +472,7 @@

   # Typing operator like inside a known call
   precsForData = operatorCompile ctx, call
-  log 'op in pattern', precsForData
+  # log 'op in pattern', precsForData

   # Gets the general type as if the extra arguments were supplied
   callTyping ctx, call
@@ -486,7 +490,7 @@
   args = _arguments call

   compiledOperator = operatorCompile ctx, call
-  log "compiled saturated", operator, call
+  # log "compiled saturated", operator, call

   compiledArgs = termsCompile ctx, args

@@ -658,14 +662,14 @@
 #     (call_ (token_ 'cons-array'), [x, (arrayToConses xs)])

 assignCompile = (ctx, expression, translatedExpression) ->
-  if not translatedExpression
-    log expression
+  # if not translatedExpression # TODO: throw here?
+    #log expression
   if to = ctx._nameExpression()

     ctx.setGroupTranslation()
     {precs, assigns} = patternCompile ctx, to, expression, translatedExpression

-    log "ASSIGN #{ctx._name()}", ctx.shouldDefer()
+    #log "ASSIGN #{ctx._name()}", ctx.shouldDefer()
     if ctx.shouldDefer()
       ctx.addDeferred ctx.shouldDefer(), to, expression
       return 'deferred'
@@ -691,7 +695,7 @@
     for {name} in definedNames
       if not ctx.arity name
         ctx.addToScope name
-    log "exiting pattern early", pattern, "for", ctx.shouldDefer()
+    #log "exiting pattern early", pattern, "for", ctx.shouldDefer()
     return {}

   # Check the types
@@ -706,19 +710,19 @@
     # or use the substitution instead of type below:
     mapMap (-> {name, type}), findFree substitute ctx.substitution, type)

-  log "pattern compiel", definedNames
+  #log "pattern compiel", definedNames
   for {name, type} in definedNames
     currentType = substitute ctx.substitution, type
     deps = concat mapToArray intersectRight (findFree currentType), tempVars
     if deps.length > 0
       depsNames =  deps
-      log "adding top level lhs to deferred #{name}"
+      #log "adding top level lhs to deferred #{name}"
       ctx.addToDeferred {name, type, deps: (map (({name}) -> name), deps)}
       for dep in deps
         ctx.addToDeferred {name: dep.name, type: dep.type, deps: [name]}
       ctx.addType name, new TempType type
     else
-      log "adding type for lhs #{name}", currentType
+      #log "adding type for lhs #{name}", currentType
       ctx.addArity name, [] unless ctx.arity name
       ctx.addType name, if ctx._nameExpression()
         quantifyAll currentType
@@ -738,12 +742,16 @@

 definitionList = (ctx, pairs) ->
   compiledPairs = filter _is, (for [lhs, rhs] in pairs
-    definitionPairCompile ctx, lhs, rhs)
+    if rhs
+      definitionPairCompile ctx, lhs, rhs
+    else
+      malformed lhs, 'missing value in definition'
+      undefined)

   compiledPairs = join compiledPairs, compileDeferred ctx
   resolveDeferredTypes ctx

-  log "yay"
+  #log "yay"

   listOfLines compiledPairs

@@ -755,7 +763,7 @@
     for name, types of values names
       canonicalType = ctx.freshTypeVariable star
       for type in types
-        log type.constructor
+        #log type.constructor
         # log "unifying", canonicalType, type
         unify ctx, canonicalType, type
         # log "done unifying one"
@@ -823,11 +831,11 @@
       # log "adding types", (map _symbol, params), paramTypes
       ctx.addTypes (map _symbol, params), paramTypes

-      log "compiling wheres", pairs wheres
+      #log "compiling wheres", pairs wheres
       compiledWheres = definitionList ctx, pairs wheres

       # log "types added"
-      log "compiling", body
+      #log "compiling", body
       compiledBody = termCompile ctx, body
       # log "compiled", body.tea
       ctx.closeScope()
@@ -842,7 +850,7 @@

       # Arity - before deferring instead go to assignCompile, because this makes the naming of functions special
       if ctx._name()
-        log "adding arity for #{ctx._name()}", paramNames
+        #log "adding arity for #{ctx._name()}", paramNames
         ctx.addArity ctx._name(), paramNames

       assignCompile ctx, call,
@@ -851,7 +859,7 @@
         else
           # Typing
           if not body.tea
-            log body
+            #log body
             throw "Body not typed"
           call.tea = typeFn (map _type, paramTypes)..., body.tea

@@ -885,7 +893,7 @@
       else
         ctx._name()
       # TODO support polymorphic data
-      log "Adding constructor #{constr.symbol}"
+      #log "Adding constructor #{constr.symbol}"
       ctx.addType constr.symbol, toForAll constrType
       constr.tea = constrType
     # TODO: support polymorphic data
@@ -977,7 +985,7 @@
       if ctx.shouldDefer()
         continue

-      log "unifying in match", resultType, result.tea
+      #log "unifying in match", resultType, result.tea
       unify ctx, resultType, result.tea
       varNames.push (findDeclarables precs)...

@@ -1110,7 +1118,7 @@
         type: freshInstance ctx, ctx.type symbol
         pattern: constPattern ctx, symbol
       else
-        log "deferring in pattern for #{symbol}"
+        #log "deferring in pattern for #{symbol}"
         ctx.setDefer symbol
         pattern: []
     else
@@ -1138,7 +1146,7 @@
         translation: nameTranslate ctx, atom, symbol
       }
     else
-      log "deferring for #{symbol}"
+      #log "deferring for #{symbol}"
       ctx.setDefer symbol
       translation: 'deferred'

@@ -1366,39 +1374,6 @@
     else
       []

-join = (seq1, seq2) ->
-  seq1.concat seq2
-
-concatMap = (fn, list) ->
-  concat map fn, list
-
-concat = (lists) ->
-  [].concat lists...
-
-id = (x) -> x
-
-map = (fn, list) ->
-  if list then list.map fn else (list) -> map fn, list
-
-allMap = (fn, list) ->
-  all (map fn, list)
-
-all = (list) ->
-  (filter _is, list).length is list.length
-
-filter = (fn, list) ->
-  list.filter fn
-
-partition = (fn, list) ->
-  [(filter fn, list), (filter ((x) -> not (fn x)), list)]
-
-_notEmpty = (x) -> x.length > 0
-
-_is = (x) -> !!x
-
-__ = (fna, fnb) ->
-  (x) -> fna fnb x
-
 theme =
   keyword: 'red'
   numerical: '#FEDF6B'
@@ -1508,13 +1483,6 @@
     crawl node, (node) ->
       collapsed += node
   collapsed
-
-# TODO need?
-# Correct offset by 1 in positions, when we wrap the source in an S-exp
-shiftPos = (ast) ->
-  crawl ast, (word) ->
-    word.pos = word.pos - 1
-    word

 # TODO: need?
 parentize = (highlighted) ->
@@ -2055,15 +2023,15 @@
 concatSets =
 concatMaps = (maps...) ->
   concated = newMap()
-  for map in maps
-    for k, v of map.values
+  for m in maps
+    for k, v of m.values
       addToMap concated, k, v
   concated

 concatConcatMaps = (maps) ->
   concated = newMap()
-  for map in maps
-    for k, v of map.values
+  for m in maps
+    for k, v of m.values
       if list = lookupInMap concated, k
         list.push v
       else
@@ -2437,17 +2405,70 @@
 ;
 """

-
-
-exports.compileTopLevel = (ast) ->
+# API
+
+compileTopLevel = (ast) ->
   compiled = topLevel (ctx = new Context), ast
-    types: values mapMap (__ highlightType, _type), ctx._scope()
-    js: library + compiled
-
-exports.compileExpression = (ast) ->
+  types: values mapMap (__ highlightType, _type), ctx._scope()
+  js: library + compiled
+
+compileExpression = (ast) ->
   compiled = expressionCompile (ctx = new Context), ast
-    types: values mapMap (__ highlightType, _type), ctx._scope()
-    js: compiled
+  types: values mapMap (__ highlightType, _type), ctx._scope()
+  js: compiled
+
+astizeList = (source) ->
+  astize tokenize "(#{source})", -1
+
+astizeExpression = (source) ->
+  astize tokenize source
+
+
+# end of API
+
+# Utils
+
+join = (seq1, seq2) ->
+  seq1.concat seq2
+
+concatMap = (fn, list) ->
+  concat map fn, list
+
+concat = (lists) ->
+  [].concat lists...
+
+id = (x) -> x
+
+map = (fn, list) ->
+  if list then list.map fn else (list) -> map fn, list
+
+allMap = (fn, list) ->
+  all (map fn, list)
+
+all = (list) ->
+  (filter _is, list).length is list.length
+
+filter = (fn, list) ->
+  list.filter fn
+
+partition = (fn, list) ->
+  [(filter fn, list), (filter ((x) -> not (fn x)), list)]
+
+_notEmpty = (x) -> x.length > 0
+
+_is = (x) -> !!x
+
+__ = (fna, fnb) ->
+  (x) -> fna fnb x
+
+# end of Utils
+
+
+
+exports.compileTopLevel = compileTopLevel
+exports.compileExpression = compileExpression
+exports.astizeList = astizeList
+exports.astizeExpression = astizeExpression

 # exports.compileModule = (source) ->
 #   """
@@ -2456,12 +2477,21 @@
 #   #{compileDefinitionsInModule source}
 #   exports"""

-exports.astizeList = (source) ->
-  astize tokenize "(#{source})", -1
-
-exports.astize = (source) ->
-  astize tokenize source
-
 exports.library = library

-exports.walk = walk+exports.walk = walk
+exports.isForm = isForm
+
+
+exports.join = join
+exports.concatMap = concatMap
+exports.concat = concat
+exports.id = id
+exports.map = map
+exports.allMap = allMap
+exports.all = all
+exports.filter = filter
+exports.partition = partition
+exports._notEmpty = _notEmpty
+exports._is = _is
+exports.__ = __


Req needs at least one arg

Req needs at least one arg

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2028,9 +2028,9 @@

 ms.req = ms_req = (ctx, call) ->
   reqTuple = ctx.definitionPattern()
-  if not isTuple reqTuple
+  reqs = _validTerms reqTuple
+  if (not isTuple reqTuple) or _empty reqs
     return malformed ctx, reqTuple, 'req requires a tuple of names to be required'
-  reqs = _validTerms reqTuple
   map (syntaxNewName ctx, 'definition name to be imported required'), reqs
   [moduleName] = _validArguments call
   if not moduleName or not isName moduleName


Troubleshoot: Hide errors related to recovering items

--- client/Main/troubleshoot/troubleshooterrorview.coffee
+++ client/Main/troubleshoot/troubleshooterrorview.coffee
@@ -26,9 +26,9 @@
                  supports offline working will continue to work, but you cannot \
                  send/receive updates, reach your VMs nor interact with terminal."
     liveUpdate :
-      slow     : "You will not receive live updates, and you wont be able to \
+      slow     : "You will experience slowness with live updates"
+      fail     : "You will not receive live updates, and you wont be able to \
                   connect to your vms"
-      fail     : "You will experience slowness with live updates"
     version    :
       fail     : "You are currently running an old version of Koding. Please refresh \
                   your page."
@@ -41,6 +41,8 @@
     super options, data
     @hide()
     @initStatusListener()
+    @errorViews = {}
+    @errorCount = 0

   initStatusListener: ->
@@ -51,8 +53,28 @@
           {status} = item
           if status in ["fail", "slow"] and errorMessages[name]?[status]
             @show()
-            @addSubView new KDCustomHTMLView
-              tagName: "div"
-              cssClass: "status-message #{status}"
-              partial: "* #{errorMessages[name][status]}"
+            @errorCount += 1
+            @addSubView @errorViews[name] = @createErrorView name, item

+        item.on "recoveryStarted", =>
+          @errorCount -= 1
+          @hide()  unless @errorCount
+          @errorViews[name].destroy()
+          delete @errorViews[name]
+
+
+        item.on "recoveryCompleted", =>
+          {status} = item
+          if status in ["fail", "slow"] and errorMessages[name]?[status]
+            @show()
+            @errorCount += 1
+            @addSubView @errorViews[name] = @createErrorView name, item
+
+
+  createErrorView: (name, item) ->
+    {status} = item
+    new KDCustomHTMLView
+      tagName: "div"
+      cssClass: "status-message #{status}"
+      partial: "* #{errorMessages[name][status]}"
+


sidebar: map channels with workspace items

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -43,6 +43,8 @@
     @itemsBySlug  = {}
     @itemsByName  = {}
     @selectedItem = null
+
+    @workspaceItemChannelMap = {}

     # @appsList = new DockController

@@ -426,6 +428,7 @@
           machineLabel : machine.slug or machine.label

       KD.userWorkspaces.forEach (workspace) ->
+
         if workspace.machineUId is machine.uid
           ideRoute = "/IDE/#{machine.slug or machine.label}/#{workspace.slug}"
           title    = "#{workspace.name}"
@@ -445,7 +448,20 @@
             id           : workspace._id
             parentId     : id

-    @machineTree.addNode data for data in treeData
+    for data in treeData
+
+      node = @machineTree.addNode data
+
+      @mapWorkspaceWithChannel data, node  if data.type is 'workspace'
+
+
+  mapWorkspaceWithChannel: (data, node) ->
+
+    return  unless data.data?.channelId?
+
+    { channelId } = data.data
+
+    @workspaceItemChannelMap[channelId] = node

   selectWorkspace: (data) ->


Sidebar: has workspace logic made per machine

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -485,7 +485,7 @@
       machineOwner = machine.credential
       isMyMachine  = machineOwner is KD.nick()
       ideRoute     = "#{ideRoute}/#{machineOwner}"  unless isMyMachine
-      hasWorkspace = (KD.userWorkspaces.filter ({name}) -> return name is 'My Workspace').length > 0
+      hasWorkspace = (KD.userWorkspaces.filter ({name, machineUId}) -> return name is 'My Workspace' and machineUId is machine.uid).length > 0

       unless hasWorkspace
         treeData.push


security: fix an xss injection hazard in filetree

--- client/app/MainApp/filetree/itemviews/fileitem.coffee
+++ client/app/MainApp/filetree/itemviews/fileitem.coffee
@@ -77,13 +77,13 @@

   pistachio:->
-
-    path = FSHelper.plainPath @getData().path
-
+    data = @getData()
+    path = FSHelper.plainPath data.path
+    name = Encoder.XSSEncode data.name
     """
       {{> @arrow}}
       {{> @icon}}
       {{> @loader}}
-      {span.title[title="#{path}"]{ #(name)}}
+      #{name}

     """


SocialWorker: JAccount: replace daisy calls with async.series

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -24,12 +24,13 @@
   @getFlagRole            = 'content'
   @lastUserCountFetchTime = 0

-  { ObjectId, Register, secure, race, dash, daisy, signature } = require 'bongo'
+  { ObjectId, secure, signature } = require 'bongo'
   { Relationship } = jraphical
   { permit } = require './group/permissionset'
   Validators = require './group/validators'
-  Protected = require '../traits/protected'
+  Protected  = require '../traits/protected'
   { extend } = require 'underscore'
+  async      = require 'async'

   validateFullName = (value) -> not /<|>/.test value

@@ -321,13 +322,13 @@
       return callback err   if err
       return callback null  if not groups

-      queue = groups.map (group) -> ->
+      queue = groups.map (group) -> (fin) ->
         if group.slug in ['koding', 'guests'] # just skip koding & guests
-          queue.fin()
+          fin()
         else
-          group.leave client, queue.fin
-
-      dash queue, callback
+          group.leave client, fin
+
+      async.parallel queue, callback

   fetchAllParticipatedGroups: secure (client, callback) ->

@@ -874,9 +875,9 @@

   unstoreAll: (callback) ->
     @fetchStorages [], (err, storages) ->
-      daisy queue = storages.map (storage) ->
-        -> storage.remove -> queue.next()
-      queue.push -> callback null
+      queue = storages.map (storage) ->
+        (next) -> storage.remove -> next()
+      async.series queue, -> callback null

   _store: ({ name, content }, callback) ->
     @fetchStorage { 'data.name' : name }, (err, storage) =>


Removed unnecessary preventScrollPropagations

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -66,6 +66,3 @@

 # initialize the page, so we are ready to render new pages fast
 loadUrl '/facts/new?layout=client'
-
-$("iframe").preventScrollPropagation()
-$("iframe").on 'click', (e) -> e.stopPropagation()


Use blank client page when first loading the intermediate

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -63,4 +63,4 @@
     showFrame.src = url

 # initialize the page, so we are ready to render new pages fast
-loadUrl '/facts/new?layout=client'
+loadUrl '/client/blank'


GroupsController: currentGroupHasStack helper

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -1,13 +1,15 @@
-globals = require 'globals'
-getGroup = require '../util/getGroup'
-remote = require('../remote').getInstance()
-trackEvent = require '../util/trackEvent'
-whoami = require '../util/whoami'
-showError = require '../util/showError'
-kd = require 'kd'
-KDController = kd.Controller
+kd                 = require 'kd'
+KDController       = kd.Controller
 KDNotificationView = kd.NotificationView
-GroupData = require './groupdata'
+
+whoami             = require '../util/whoami'
+getGroup           = require '../util/getGroup'
+showError          = require '../util/showError'
+trackEvent         = require '../util/trackEvent'
+
+remote             = require('../remote').getInstance()
+globals            = require 'globals'
+GroupData          = require './groupdata'

 module.exports = class GroupsController extends KDController
@@ -32,6 +34,11 @@
   getCurrentGroup:->
     throw 'FIXME: array should never be passed'  if Array.isArray @currentGroupData.data
     return @currentGroupData.data
+
+  currentGroupHasStack: ->
+    {stackTemplates} = @getCurrentGroup()
+    return stackTemplates?.length > 0
+

   filterXssAndForwardEvents: (target, events) ->
     events.forEach (event) =>
@@ -122,4 +129,3 @@
         policy.emit 'MembershipPolicyChangeSaved'
         new KDNotificationView {title:"Membership policy has been updated."}
       showError err
-


Removed useless line

Removed useless line

--- core/app/backbone/factlink/react_popover.coffee
+++ core/app/backbone/factlink/react_popover.coffee
@@ -1,5 +1,3 @@
-Backbone.Factlink ||= {}
-
 window.ReactPopover = React.createBackboneClass
   displayName: 'ReactPopover'


Follow symlinks loading paths

Follow symlinks loading paths

--- src/packages/fuzzy-finder/lib/load-paths-task.coffee
+++ src/packages/fuzzy-finder/lib/load-paths-task.coffee
@@ -16,6 +16,7 @@
     args = ['-l', rootPath]
     args.unshift('--addVCSIgnores') if config.get('nak.addVCSIgnores')
     args.unshift('-d', ignoredNames.join(',')) if ignoredNames.length > 0
+    args.unshift('--follow')

     paths = []
     deferred = $.Deferred()


Invoke callback with empty array on non-zero exit code

--- src/packages/fuzzy-finder/lib/load-paths-task.coffee
+++ src/packages/fuzzy-finder/lib/load-paths-task.coffee
@@ -17,8 +17,11 @@
     args.unshift('--follow')

     paths = []
-    exit = =>
-      @callback(paths)
+    exit = (code) =>
+      if code is 0
+        @callback(paths)
+      else
+        @callback([])
     stdout = (data) ->
       paths.push(_.compact(data.split('\n'))...)


:lipstick:

:lipstick:

--- src/packages/settings-view/lib/settings-panel.coffee
+++ src/packages/settings-view/lib/settings-panel.coffee
@@ -3,12 +3,11 @@
 _ = require 'underscore'
 async = require 'async'
 AtomPackage = require 'atom-package'
+Editor = require 'editor'

 ###
 # Internal #
 ###
-
-Editor = require 'editor'

 module.exports =
 class SettingsPanel extends View


Fixes #587: fix for the fix - thanks travis

--- src/api/channels.coffee
+++ src/api/channels.coffee
@@ -1,5 +1,6 @@
 Channel = require('../model/channels').Channel
 Transaction = require('../model/transactions').Transaction
+ObjectId = require('mongoose').Types.ObjectId
 Q = require 'q'
 logger = require 'winston'
 authorisation = require './authorisation'
@@ -60,6 +61,7 @@
     this.status = 201
     logger.info 'User %s created channel with id %s', this.authenticated.email, channel.id

+    channelData._id = channel._id
     processPostAddTriggers channelData
   catch err
     # Error! So inform the user
@@ -145,6 +147,7 @@
     this.body = 'The channel was successfully updated'
     logger.info 'User %s updated channel with id %s', this.authenticated.email, id

+    channelData._id = ObjectId id
     processPostUpdateTriggers channelData
   catch err
     # Error! So inform the user


Remove unused require

Remove unused require

--- src/packages/archive-view/lib/archive-view.coffee
+++ src/packages/archive-view/lib/archive-view.coffee
@@ -1,6 +1,5 @@
 ScrollView = require 'scroll-view'
 archive = require 'ls-archive'
-{$$} = require 'space-pen'
 FileView = require './file-view'
 DirectoryView = require './directory-view'


Make slideUp work

Make slideUp work

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -174,6 +174,7 @@
         @translate(currentPage, "Y", screenHeight)
       , 0
     else
+      targetPage.style.zIndex = "999"
       @translate(targetPage, "Y", screenHeight,"0ms")
       setTimeout =>
         @translate(targetPage, "Y", "0%")


Provide callback as parameter.

Provide callback as parameter.

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -38,7 +38,7 @@
       .fail (msg) =>
         @returnResult false, msg, callback

-  publishOnly: (publish = true) ->
+  publishOnly: (publish = true, callback) ->
     @publishProducts = true
     action = if publish then 'publish' else 'unpublish'
     @productService.getAllExistingProducts(@rest, publish).then (existingProducts) =>
@@ -176,7 +176,7 @@
             humanReadable = JSON.stringify body, null, '  '
             deferred.reject "Problem on #{action}ing product:\n" + humanReadable
         else
-          deferred.reject "Problem on #{action}ing product: " + body
+          deferred.reject "Problem on #{action}ing product (code #{response.statusCode}: " + body

     deferred.promise


barebone fallback to processProductsBasesOnSkus

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -75,6 +75,11 @@

       if @validator.updateVariantsOnly
         console.warn "Mapping variants only. Falling back to map by sku option"
+        p = (p) => @processProductsBasesOnSkus(p)
+        return Promise.map(_.batchList(productsRows, 20), p, { concurrency: 20 })
+        .then((results) => results.reduce((agg, r) ->
+          agg.concat(r)
+        , []))
       else
         @validator.validateOnline()
         .then (rawProducts) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -1,7 +1,7 @@
 asyncTest "Animation should set proper frame", ->
   animation = Animation.fromPixieId 45

-  milliseconds = 600
+  milliseconds = 300

   setTimeout ->
     equals animation.currentSprite(), animation.frames()[0], "Animation should default to initial sprite"


temporarily remove SR on capa alerts to fix problem submissions (LMS-2474)

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -366,7 +366,6 @@
     alert_elem = "
" + msg + "
"
     @el.find('.action').after(alert_elem)
     @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700)
-    window.SR.readElts(msg)

   save: =>
     if not @check_save_waitfor(@save_internal)


Delete Define.coffee

Delete Define.coffee

--- Define.coffee
+++ Define.coffee
@@ -1,7 +0,0 @@
-class Define
-
-
-
-Define::process = (obj)->
-  for i in obj
-    window.eval i


Safer id labeling of editors.

Safer id labeling of editors.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -56,8 +56,9 @@
   findLocation: ->
       label = "#{@firstChar}#{@secondChar}"
       for editor in atom.workspaceView.getEditorViews()
-          if label of @allPositions[editor.id]
-              return @allPositions[editor.id][label]
+          currentId = editor.find('.jumpy.labels').attr('jumpyid')
+          if label of @allPositions[currentId]
+              return @allPositions[currentId][label]

       return null

@@ -78,7 +79,7 @@
     atom.workspaceView.eachEditorView (e) ->
         return if !e.active
         e.addClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
-        e.find('.scroll-view .overlayer').append('

')
+        e.find('.scroll-view .overlayer').append("

")
         positions = {}
         that.allPositions[e.id] = positions