Refactor, done.

Refactor, done.

--- client/landing/app/MainApp/paymentcontroller.coffee
+++ client/landing/app/MainApp/paymentcontroller.coffee
@@ -257,7 +257,9 @@
     else
       KD.remote.api.JRecurlyPlan.getUserAccount cb

-  createPaymentConfirmationModal: (type, group, plan, content, needBilling, willCharge, cb)->
+  createPaymentConfirmationModal: (type, group, plan, needBilling, balance, amount, cb)->
+    content = @paymentWarning balance, amount
+
     modal           = new KDModalView
       title         : "Confirm VM Creation"
       content       : "
#{content}
"
@@ -282,13 +284,34 @@
           cssClass  : "modal-clean-green hidden"
           callback  : =>
             modal.destroy()
-            @makePayment type, plan, willCharge, ->
+            @makePayment type, plan, amount, ->
               cb()

     if needBilling
       modal.buttons.Billing.show()
     else
       modal.buttons.Yes.show()
+
+  paymentWarning: do->
+
+    formatMoney = (amount)-> (amount / 100).toFixed 2
+
+    (balance, amount)->
+      content = ""
+
+      chargeAmount = Math.max amount - balance, 0
+
+      if amount is 0
+        content += "
You are already subscribed for an extra VM.
"
+      else if balance > 0
+        content += "
You have $#{formatMoney balance} credited to your account.
"
+
+      if chargeAmount > 0
+        content += "
You will be charged for $#{formatMoney chargeAmount}
"
+      else
+        content += "
You won't be charged for this
"
+
+      content

   confirmPayment:(type, plan, callback=->)->
     group = KD.getSingleton("groupsController").getCurrentGroup()
@@ -298,59 +321,21 @@
       planCode : plan.code
     , (err, status)=>
       if not err and status
-        content = """

-                       You already subscribed for an additional
-                       #{type} VM.
-

-

-                       You won't be charged. Do you want to
-                       continue?.
-

-                  """
-        buildModal content, no, no, callback
+        @createPaymentConfirmationModal type, group, plan, no, 0, 0, callback
       else
         @getBillingInfo type, group, (err, account)=>
-          billing = err or not account or not account.cardNumber
+          needBilling = err or not account or not account.cardNumber

           @getBalance type, group, (err, balance)=>
-            if not err and balance > 0
-              charge = (plan.feeMonthly - balance) / 100
-              balance = balance / 100
-
-              content = """

-                             You have $#{balance.toFixed(2)} credited to your
-                             #{type} account.
-

-                        """
-
-              if charge > 0
-                content += """

-                                You will be charged for $#{charge.toFixed(2)}.
-                                Do you want to continue?
-

-                           """
-                @createPaymentConfirmationModal type, group, plan, content, billing, yes, callback
-              else
-                content += """

-                                You won't be charged. Do you
-                                want to continue?
-

-                           """
-                @createPaymentConfirmationModal type, group, plan, content, no, no, callback
-            else
-              charge = plan.feeMonthly / 100
-              content = """

-                             You will be charged for $#{charge.toFixed(2)}. Do
-                             you want to continue?
-

-                        """
-              @createPaymentConfirmationModal type, group, plan, content, billing, yes, callback
-
-  makePayment: (type, plan, willCharge, callback)->
+            if err
+              balance = 0
+            @createPaymentConfirmationModal type, group, plan, needBilling, balance, plan.feeMonthly, callback
+
+  makePayment: (type, plan, amount, callback)->
     vmController = KD.getSingleton('vmController')
     group        = KD.getSingleton("groupsController").getCurrentGroup()

-    unless willCharge
+    if amount is 0
       vmController.createGroupVM type, plan.code
     else
       if type is 'group'


:bug: #630 now can create 1 column persistent selection by `.` repeat

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -130,7 +130,8 @@
     selection.modifySelection =>
       @moveWithSaveJump(cursor)

-    return if not @isMode('visual') and selection.isEmpty() # Failed to move.
+    if not @isMode('visual') and not @is('CurrentSelection') and selection.isEmpty() # Failed to move.
+      return
     return unless @isInclusive() or @isLinewise()

     if @isMode('visual') and cursorIsAtEndOfLineAtNonEmptyRow(cursor)


:art: refactoring

:art: refactoring

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -612,7 +612,9 @@
   moveCursor: (cursor) ->
     ranges = @scan(cursor)
     if ranges.length is 0
+      @destroyMarkers()
       @vimState.flasher.reset()
+      @vimState.hoverSearchCounter.reset()
       atom.beep()
       return

@@ -713,31 +715,33 @@
   @extend()
   constructor: ->
     super
-    @vimState.search.readInput {@backwards}, @getInputHandler()
-
-  getInputHandler: ->
-    handlers =
-      onDidConfirm: (input) =>
-        repeatChar = if @backwards then '?' else '/'
-        if (input is '') or (input is repeatChar)
-          input = @vimState.searchHistory.get('prev')
-          atom.beep() if input is ''
-        @input = input
-        @complete = true
-        @vimState.operationStack.process()
-        @destroyMarkers()
-      onDidCancel: =>
-        unless @vimState.isMode('visual') or @vimState.isMode('insert')
-          @vimState.activate('reset')
-        @destroyMarkers()
-        @vimState.flasher.reset()
-        @vimState.hoverSearchCounter.reset()
-        @vimState.reset()
-    if settings.get('enableIncrementalSearch')
-      handlers.onDidChange = (@input) =>
-        for c in @editor.getCursors()
-          @moveCursor(c)
-    handlers
+    isearchEnabled = settings.get('enableIncrementalSearch')
+    @vimState.search.readInput {@backwards},
+      onDidConfirm: @onDidConfirm.bind(this)
+      onDidCancel:  @onDidCancel.bind(this)
+      onDidChange:  if isearchEnabled then @onDidChange.bind(this) else null
+
+  onDidConfirm: (input) =>
+    repeatChar = if @backwards then '?' else '/'
+    if (input is '') or (input is repeatChar)
+      input = @vimState.searchHistory.get('prev')
+      atom.beep() if input is ''
+    @input = input
+    @complete = true
+    @vimState.operationStack.process()
+    @destroyMarkers()
+
+  onDidCancel: =>
+    unless @vimState.isMode('visual') or @vimState.isMode('insert')
+      @vimState.activate('reset')
+    @destroyMarkers()
+    @vimState.flasher.reset()
+    @vimState.hoverSearchCounter.reset()
+    @vimState.reset()
+
+  onDidChange: (@input) =>
+    for c in @editor.getCursors()
+      @moveCursor(c)

 class SearchBackwards extends Search
   @extend()


Update single deprecated selector

Update single deprecated selector

--- keymaps/toggle-quotes.cson
+++ keymaps/toggle-quotes.cson
@@ -1,2 +1,2 @@
-'.workspace':
+'atom-workspace':
   'ctrl-"': 'toggle-quotes:toggle'


Cleanup

Cleanup

--- src/middleware.coffee
+++ src/middleware.coffee
@@ -138,4 +138,4 @@
   exports.fromHook(
     pkgman.PackageManager.normalizePath path
     config.get "packageConfig:#{path}"
-  )+  )


some more XHR like functionality

some more XHR like functionality

--- src/models/XHR.coffee
+++ src/models/XHR.coffee
@@ -8,10 +8,29 @@
     @url = undefined
     @async = undefined

+    @status = 0
+    @readyState = 0
+    @headers = {}
+
   open: (methodString, urlString, isAsync=true) ->
     @method = methodString
     @url = urlString
     @async = isAsync
+
+
+  send: (data) =>
+    throw "Error: INVALID_STATE_ERR: DOM Exception 11" unless @method and @url
+
+    throw "Method not implemented" unless @method == "GET"
+
+    @fetch
+      url: @url
+      filenameWithPath: "temp"
+      headers: @headers
+
+  setRequestHeader: (name, value) =>
+    @headers[name] = value
+

   fetch: (options={}, callbacks={}) ->

@@ -21,6 +40,7 @@
       method: "downloadFile"
       parameters:
         url: options.url || @url
+        headers: options.headers || @headers
         filenameWithPath: fullPath
       successCallbacks: [callbacks.onSuccess]
       failureCallbacks: [callbacks.onFailure]


trying something.

trying something.

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -60,7 +60,7 @@
           @project.get_all_change_requests_qa(msg)

    setInterval ->
-     console.log robot.room
+     console.log robot
    , 1000

 class Project


Don’t build ::specialTokens unless properties are provided

--- src/tokenized-line.coffee
+++ src/tokenized-line.coffee
@@ -22,10 +22,10 @@

   constructor: (properties) ->
     @id = idCounter++
+
+    return unless properties?
+
     @specialTokens = {}
-
-    return unless properties?
-
     {@parentScopes, @text, @tags, @lineEnding, @ruleStack} = properties
     {@startBufferColumn, @fold, @tabLength, @indentLevel, @invisibles} = properties


Bring back missed `ext` property

Bring back missed `ext` property

--- files.coffee
+++ files.coffee
@@ -436,6 +436,7 @@
     result.name      = fileName
     result.meta      = opts.file.meta
     result.extension = extension
+    result.ext       = extension
     result           = @dataToSchema result
     result._id       = opts.fileId
     result.userId    = userId if userId


:fire: unused local variable

:fire: unused local variable

--- src/region-map.coffee
+++ src/region-map.coffee
@@ -68,8 +68,6 @@

     @seek(@position.traverse(oldExtent))

-    spliceEndIndex = @index + 1
-
     sourceExtent = @sourcePosition.traversalFrom(startSourcePosition)
     newExtent = Point(0, newContent.length)
     newRegions.push({


Update to correctly display warning status and add default case.

--- src/stashboard.coffee
+++ src/stashboard.coffee
@@ -49,10 +49,12 @@
       status = JSON.parse response
       for service in status.services
         serviceMsg = ''
-        switch service['current-event'].status.id
+        status = service['current-event'].status.id
+        switch status
           when 'down' then serviceMsg += @statusDown
           when 'up' then serviceMsg += @statusUp
-          when 'warn' then serviceMsg += @statusWarn
+          when 'warning' then serviceMsg += @statusWarn
+          else serviceMsg += status
         serviceMsg += ' ' + service.name
         cb(null, serviceMsg)


activity/pm: warn :arrow_right: kd.warn

activity/pm: warn :arrow_right: kd.warn

--- client/activity/lib/views/privatemessage/privatemessagepane.coffee
+++ client/activity/lib/views/privatemessage/privatemessagepane.coffee
@@ -361,7 +361,7 @@

     remote.cacheable 'JAccount', participant._id, (err, account) =>

-      return warn err  if err
+      return kd.warn err  if err

       @participantMap[participant._id].destroy()
       delete @participantMap[participant._id]


Expose Waterfall for unit testing.

Expose Waterfall for unit testing.

--- app/js/jasmine-given.coffee
+++ app/js/jasmine-given.coffee
@@ -106,6 +106,37 @@
         return thing[i]  if test(thing[i]) is true
         i++
       return undefined
+
+  class Waterfall
+    constructor: (functions = [], finalCallback) ->
+      @functions = cloneArray(functions)
+      @finalCallback = finalCallback
+
+      @asyncCount = 0
+      for func in @functions
+        @asyncCount += 1 if func.length > 0
+
+    asyncTaskCompleted: =>
+      @asyncCount -= 1
+      @flow()
+
+    invokeFinalCallbackIfNecessary: =>
+      if @asyncCount == 0
+        @finalCallback?()
+        @finalCallback = undefined
+
+    flow: =>
+      return @invokeFinalCallbackIfNecessary() if @functions.length == 0
+
+      func = @functions.shift()
+
+      if func.length > 0
+        func(@asyncTaskCompleted)
+      else
+        func()
+        @flow()
+
+  cloneArray = (a) -> a.slice(0)

   jasmine._given =
     matchers:
@@ -128,6 +159,7 @@
           msg

         result == false
+    __Waterfall__: Waterfall

   stringifyExpectation = (expectation) ->
     matches = expectation.toString().replace(/\n/g,'').match(/function\s?\(.*\)\s?{\s*(return\s+)?(.*?)(;)?\s*}/i)
@@ -187,37 +219,6 @@
       expect(#{left}).toEqual(#{right})
     """

-  class Waterfall
-    constructor: (functions = [], finalCallback) ->
-      @functions = cloneArray(functions)
-      @finalCallback = finalCallback
-
-      @asyncCount = 0
-      for func in @functions
-        @asyncCount += 1 if func.length > 0
-
-    asyncTaskCompleted: =>
-      @asyncCount -= 1
-      @flow()
-
-    invokeFinalCallbackIfNecessary: =>
-      if @asyncCount == 0
-        @finalCallback?()
-        @finalCallback = undefined
-
-    flow: =>
-      return @invokeFinalCallbackIfNecessary() if @functions.length == 0
-
-      func = @functions.shift()
-
-      if func.length > 0
-        func(@asyncTaskCompleted)
-      else
-        func()
-        @flow()
-
-  cloneArray = (a) -> a.slice(0)
-
   beforeEach ->
     if jasmine.addMatchers?
       jasmine.addMatchers(jasmine.matcherWrapper.wrap(jasmine._given.matchers))


Dashboard: Added onboarding menu yo dashboard.

--- client/Dashboard/AppController.coffee
+++ client/Dashboard/AppController.coffee
@@ -82,6 +82,13 @@
         viewOptions  :
           viewClass  : CustomViewsManager
           lazy       : yes
+      ,
+        name         : 'Onboarding'
+        hiddenHandle : @getData().privacy is 'public'
+        kodingOnly   : yes # this is only intended for koding group, we assume koding group is super-group
+        viewOptions  :
+          viewClass  : OnboardingDashboardView
+          lazy       : yes

       # CURRENTLY DISABLED


remove a console.log

remove a console.log

--- client/Framework/core/KDRouter.coffee
+++ client/Framework/core/KDRouter.coffee
@@ -38,7 +38,6 @@
         state             : state

   clear:(route = '/', replaceState = yes)->
-    console.log {arguments}
     delete @userRoute # TODO: i hope deleting the userRoute here doesn't break anything... C.T.
     @handleRoute route, {replaceState}


Fix silly mistake

Fix silly mistake

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -26,7 +26,7 @@
     @submit_button = @$('.submit-button')
     @child_state = @el.data('state')
     @child_type = @el.data('child-type')
-    if @child_type="openended"
+    if @child_type=="openended"
       @reload_button = @$('.reload-button')

     @open_ended_child= @$('.open-ended-child')


Fixed a bug in payments js at backend

Fixed a bug in payments js at backend

fixes #6129

--- backend/app/assets/javascripts/spree/backend/payments/edit.js.coffee
+++ backend/app/assets/javascripts/spree/backend/payments/edit.js.coffee
@@ -3,7 +3,7 @@
   order_id = $('#payments').data('order-id')
   class Payment
     constructor: (id) ->
-      @url  = Spree.url("#{Spree.routes.payments_api(order_id)}/#{id}.json")
+      @url  = Spree.url("#{Spree.routes.payments_api(order_id)}/#{id}.json" + '?token=' + Spree.api_key)
       @json = $.getJSON @url.toString(), (data) =>
         @data = data


Bring back comparsion erroneously removed

--- services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
+++ services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
@@ -39,7 +39,7 @@
 					$scope.autocompleteContacts = data.contacts or []
 					for contact in $scope.autocompleteContacts
 						if contact.type == "user"
-							if contact.last_name and contact.first_name == contact.email.split("@")[0]
+							if contact.last_name == "" and contact.first_name == contact.email.split("@")[0]
 								# User has not set their proper name so use email as canonical display property
 								contact.display = contact.email
 							else


not needed this

not needed this

--- client/app/MainApp/fs/fsfolder.coffee
+++ client/app/MainApp/fs/fsfolder.coffee
@@ -1,8 +1,6 @@
 class FSFolder extends FSFile

   fetchContents:(callback, dontWatch=yes)->
-
-    {nickname} = KD.whoami().profile

     @emit "fs.job.started"
     @vmController.run


Use method instead of instance var per @markijbema's comment

--- core/app/backbone/views/sub_comments/sub_comments_list_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_list_view.coffee
@@ -6,19 +6,21 @@

   template: 'sub_comments/sub_comments_list'

-  addView: SubCommentsAddView
-
   initialize: ->
     @collection.fetch update: true # only fires 'add' and 'remove' events

   onShow: ->
     if Factlink.Global.signed_in
-      @_subCommentsAddView ?= new @addView(addToCollection: @collection)
-      @$('.js-region-sub-comments-form').html @_subCommentsAddView.render().el
+      @$('.js-region-sub-comments-form').html @subCommentsAddView().render().el

   onClose: ->
-    @_subCommentsAddView?.close()
+    @subCommentsAddView().close()
+
+  subCommentsAddView: ->
+    @_subCommentsAddView ?= new SubCommentsAddView(addToCollection: @collection)

 class window.NDPSubCommentsListView extends SubCommentsListView
   itemView: NDPSubCommentView
-  addView: NDPSubCommentsAddView
+
+  subCommentsAddView: ->
+    @_subCommentsAddView ?= new NDPSubCommentsAddView(addToCollection: @collection)


Added a test for poi-view directive.

Added a test for poi-view directive.

--- test/specs/directive-spec.coffee
+++ test/specs/directive-spec.coffee
@@ -55,3 +55,11 @@
             $rootScope.$digest()
             $element.triggerHandler 'click'
             expect($router.reload).not.toHaveBeenCalled()
+
+    describe 'poiView', ->
+        it 'check poi-view will call $router.registerView().', inject ($router) ->
+            spyOn $router, 'registerView'
+            $element = $ '<div poi-view></div>'
+            $compile($element) $scope
+            $rootScope.$digest()
+            expect($router.registerView).toHaveBeenCalled()


Fix misfetching moment locale on English

--- client/shared/services/session.coffee
+++ client/shared/services/session.coffee
@@ -34,7 +34,7 @@
   updateLocale: ->
     locale = (@user().locale || "en").toLowerCase().replace('_','-')
     I18n.useLocale(locale)
-    return if locale == "en"
+    return if momentLocaleFor(locale) == "en"
     Records.momentLocales.fetch(path: "#{momentLocaleFor(locale)}.js").then -> moment.locale(locale)

 setDefaultParams = (params) ->


ComputeController: keep stacks by Id for quick reach

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -80,6 +80,7 @@
     @stacks       = []
     @machines     = []
     @machinesById = {}
+    @stacksById   = {}
     @plans        = null
     @_trials      = {}

@@ -186,6 +187,10 @@
             queue = []
             return

+          @stacksById   = {}
+          for stack in stacks
+            @stacksById[stack._id] = stack
+
           @machinesById = {}

           machines = []
@@ -251,6 +256,12 @@
   findMachineFromMachineId: (machineId)->
     return @machinesById[machineId]

+  findStackFromStackId: (stackId)->
+    return @stacksById[stackId]
+
+  findStackFromMachineId: (machineId)->
+    for stack in @stacks
+      return stack  if machineId in stack.machines

   findMachineFromQueryString: (queryString)->


app/cc: after sharing and unsharing stack template, change visibility

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1027,6 +1027,8 @@

     if stackTemplate
       reactor.dispatch 'UPDATE_PRIVATE_STACK_TEMPLATE_SUCCESS', { stackTemplate }
+      reactor.dispatch 'MAKE_SIDEBAR_ITEM_VISIBLE_SUCCESS', { type: 'draft', id }
+
     stacks = @stacks.filter (stack) -> stack.config?.clonedFrom is id

     stacks.forEach (stack) =>
@@ -1040,6 +1042,7 @@
     { reactor } = kd.singletons

     reactor.dispatch 'UPDATE_TEAM_STACK_TEMPLATE_SUCCESS', { stackTemplate }
+    reactor.dispatch 'MAKE_SIDEBAR_ITEM_VISIBLE_SUCCESS', { type: 'draft', id: stackTemplate._id }

     stacks = @stacks.filter (stack) ->
       stack.config?.clonedFrom is stackTemplate._id


Revert "Test JS failure"

Revert "Test JS failure"

This reverts commit 50d4d0cdad3a1059ba918aa5fdbe9ab923153ec8.

--- test/unit/test_task_lists.coffee
+++ test/unit/test_task_lists.coffee
@@ -32,7 +32,7 @@
   teardown: ->

 asyncTest "triggers a tasklist:change event on task list item changes", ->
-  expect 2
+  expect 1

   @field.on 'tasklist:change', (event, index, checked) ->
     ok true


Fix VM exports when not directly under /.

--- app/modules/vm/index.coffee
+++ app/modules/vm/index.coffee
@@ -587,7 +587,7 @@
       }
       xo.vm.export id
       .then ({$getFrom: url}) ->
-        window.open url
+        window.open '.' + url

     $scope.exportOnlyMetadataVM = (id) ->
       console.log "Export Metadata only for VM #{id}"
@@ -597,7 +597,7 @@
       }
       xo.vm.export id, true, true
       .then ({$getFrom: url}) ->
-        window.open url
+        window.open '.' + url

     $scope.convertVM = (id) ->
       console.log "Convert VM #{id}"


Better permissions checking

Better permissions checking

--- app/pages/edit-project.cjsx
+++ app/pages/edit-project.cjsx
@@ -22,18 +22,20 @@

   renderAuthTest: ->
-    <PromiseRenderer promise={auth.checkCurrent()} then={@renderPermissions}>
+    currentAndOwner = Promise.all [
+      auth.checkCurrent()
+      @props.project.link 'owner'
+    ]
+
+    <PromiseRenderer promise={currentAndOwner} then={@renderPermissions}>

Checking permissions...

     </PromiseRenderer>

-  renderPermissions: (user) ->
-    if user?
-      if @props.project.id in user.links.projects
-        <ChangeListener target={@props.project} eventName="change" handler={@renderProjectEditor} />
-      else
-
You don’t have permission to edit this project.

+  renderPermissions: ([currentUser, projectOwner]) ->
+    if currentUser is projectOwner
+      <ChangeListener target={@props.project} handler={@renderProjectEditor} />
     else
-
You’re not signed in.

+
You don’t have permission to edit this project.

   renderProjectEditor: (project) ->
     {project} = @props
@@ -41,7 +43,6 @@

Workflows

       {for workflowID in project.links.workflows
-        console.log {project}

           <Link to="edit-workflow" params={id: workflowID}>{workflowID}</Link>

}


CollaborativeWorkspace: scroll chat view if it is already scrolled to the bottom when a new message is created

--- client/CollaborativeWorkspace/panes/chatpane.coffee
+++ client/CollaborativeWorkspace/panes/chatpane.coffee
@@ -84,12 +84,17 @@
     params           = { details, ownerNickname, ownerAccount }
     @lastMessageBody = details.body

+    if ownerNickname is KD.nick()
+    then forceScrollToBottom = yes
+    else forceScrollToBottom = @isScrollTopAtBottom()
+
     if @lastChatItemOwner is ownerNickname
       @appendToChatItem params
       @updateDate details.time
-      return  @scrollToTop()
+    else
+      @createNewChatItem params

-    @createNewChatItem params
+    @scrollToBottom()  if forceScrollToBottom

   createNewChatItem: (params) ->
     {details, ownerAccount, ownerNickname} = params
@@ -99,7 +104,6 @@
     @updateDate details.time
     @lastMessage = @lastChatItem.message
     @emit "NewChatItemCreated"
-    @scrollToBottom()

   appendToChatItem: (params) ->
     {details}  = params
@@ -133,6 +137,11 @@
     element = @messages.getElement()
     element.scrollTop = element.scrollHeight

+  isScrollTopAtBottom: ->
+    element = @messages.getElement()
+    height = parseInt window.getComputedStyle(element).height, 10
+    return (element.scrollTop + height) is element.scrollHeight
+
   pistachio: ->
     """
       {{> @dock}}


Fix issue where images overlap after hitting back.

--- app/assets/javascripts/albums.js.coffee
+++ app/assets/javascripts/albums.js.coffee
@@ -28,6 +28,9 @@

   $window.resize()

+# Fixes an issue where masonry images overlap after hitting the back button.
+$(document).on 'page:restore', ->
+  setupMasonry()

 $ ->
   setupMasonry()
@@ -67,4 +70,3 @@
            .toggleClass('icon-chevron-down')
            .toggleClass('icon-chevron-up')
   ).on('shown', -> $(this).toggleClass('collapse'))
-


Removed debug logs

Removed debug logs

--- client/landing/app/MainApp/paymentcontroller.coffee
+++ client/landing/app/MainApp/paymentcontroller.coffee
@@ -242,7 +242,6 @@
       paymentModal = paymentController.createPaymentMethodModal {}, (newData, onError, onSuccess)->
         if type is 'group'
           group.setBillingInfo newData, (err, result)->
-            console.log "Group Billing:", arguments
             if err
               onError err
             else
@@ -250,7 +249,6 @@
               cb yes
         else
           KD.remote.api.JRecurlyPlan.setUserAccount newData, (err, result)->
-            console.log "User Billing:", arguments
             if err
               onError err
             else


Minimal jstree pushstate support

Minimal jstree pushstate support

--- app/assets/javascripts/jstree.js.coffee
+++ app/assets/javascripts/jstree.js.coffee
@@ -5,6 +5,12 @@
     async: false
   }).responseText
   data = JSON.parse(data)
+
+
+  initially_select = []
+  if window.location.hash != ''
+    id = window.location.hash.substr(1)
+    initially_select.push(id)

   if tree.html() == ''
     tree.jstree
@@ -15,7 +21,9 @@
         show_only_matches: true
       themes:
         theme: 'classic'
-
+      ui:
+        select_limit: 1
+        initially_select: initially_select
       types:
         types:
           root:
@@ -30,6 +38,14 @@

       json_data:
         data: data
+
+
+    # On load
+    tree.bind "loaded.jstree", ->
+      # hack... :(
+      setTimeout ->
+        tree.find('a.jstree-clicked').click()
+      , 50

   else
     $.jstree._focused()._get_settings().json_data.data = data
@@ -49,6 +65,14 @@
       e.preventDefault()
       e.stopPropagation()
       tree.jstree('toggle_node', $(this).parent())
+
+    tree.on 'click', "li[rel=server] > a", (e) ->
+      hash = '#' + $(this).parent().attr('id')
+      if history.pushState
+        history.pushState(null, null, hash);
+      else
+        location.hash = hash;
+

     treeGroup.on 'keypress keydown keyup blur', 'input.search', ->
       search = $(this)


Meshes must be added to scene.mesh_passes only one time if any of the ob.passes is enabled.

--- engine/scene.coffee
+++ engine/scene.coffee
@@ -82,8 +82,9 @@
                 ob.parent_bone_index = p._bone_list.indexOf p.bones[parent_bone]

         if ob.type=='MESH'
-            for p in ob.passes
-                @mesh_passes[p].push ob
+            for p in [0..2]  # TODO: not having number of passes hardcoded
+                if p in ob.passes
+                    @mesh_passes[p].push ob
         if ob.type=='LAMP'
             @lamps.push ob
         if ob.type=='ARMATURE'
@@ -154,7 +155,7 @@
         # TODO: Only the objects marked as unordered need to be resolved here!
         #       (make a new list and append to children)
         children = @children
-
+
         reorder = (ob,index)->
             children[index++] = ob
             for c in ob.children


remove some returns

remove some returns

--- app-database.coffee
+++ app-database.coffee
@@ -4,9 +4,7 @@
       console.log "ERROR: cannot connect to mongo at ", mongoUrl, err
     else
       AppDatabaseWithConnection conn, app
-    return

-  return
 AppDatabaseWithConnection = (conn, app) ->
   mongoPost = (content) ->
     data =
@@ -28,7 +26,6 @@
     resultE.onError (err) ->
       console.log "Mongo error", err
       res.send err
-      return

     resultE.onValue (value) ->
       if value
@@ -37,16 +34,13 @@
         res.json value
       else
         res.status(404).send "Not found"
-      return

-    return
   apps = conn.collection("apps")
   app.get "/apps", (req, res) ->
     sendResult mongoFind({},
       author: true
       name: true
     ), res
-    return

   app.get "/apps/:author", (req, res) ->
     sendResult mongoFind(
@@ -54,18 +48,15 @@
     ,
       name: true
     ), res
-    return

   app.get "/apps/:author/:name", (req, res) ->
     sendResult mongoFind(
       author: req.params.author
       name: req.params.name
     , {}).map(".0"), res
-    return

   app.post "/apps", (req, res) ->
     sendResult mongoPost(req.body), res
-    return

   app.post "/apps/:author/:name/rename/:newName", (req, res) ->
     mongoResult = Bacon.fromNodeCallback(apps, "update",
@@ -76,9 +67,7 @@
         name: req.params.newName
     )
     sendResult mongoResult, res
-    return

-  return
 Bacon = require("baconjs")
 randomstring = require("randomstring")
 MongoClient = require("mongodb").MongoClient


Call Splittable#getLength rather than reading the length property

--- src/trix/models/splittable_list.coffee
+++ src/trix/models/splittable_list.coffee
@@ -103,7 +103,7 @@
   findIndexAndOffsetAtPosition: (position) ->
     currentPosition = 0
     for object, index in @objects
-      nextPosition = currentPosition + object.length
+      nextPosition = currentPosition + object.getLength()
       if currentPosition <= position < nextPosition
         return index: index, offset: position - currentPosition
       currentPosition = nextPosition
@@ -111,7 +111,7 @@

   getLength: ->
     length = 0
-    length += object.length for object in @objects
+    length += object.getLength() for object in @objects
     length

   toString: ->


Add improvement to save webhook headers

Add improvement to save webhook headers

--- static/coffee/flows/controllers.coffee
+++ static/coffee/flows/controllers.coffee
@@ -1656,12 +1656,13 @@
         webhook_headers = []

         item_counter = 0
-        for item in formData.webhook_headers
-          item_name = if $scope.webhook_headers_name then $scope.webhook_headers_name[item_counter] else null
-          item_value = if $scope.webhook_headers_value then $scope.webhook_headers_value[item_counter] else null
-          if (item_name and item_value)
-            webhook_headers.push({name: item_name, value: item_value})
-          item_counter++
+        if formData.webhook_headers
+          for item in formData.webhook_headers
+            item_name = if $scope.webhook_headers_name.length > 0 then $scope.webhook_headers_name[item_counter] else null
+            item_value = if $scope.webhook_headers_value.length > 0 then $scope.webhook_headers_value[item_counter] else null
+            if item_name and item_value
+              webhook_headers.push({name: item_name, value: item_value})
+            item_counter++

         ruleset.config =
           webhook: formData.webhook


Tests: Return post and comment after test.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -85,6 +85,7 @@

     browser.assert.containsText '[testpath=ActivityListItemView]:first-child', post # Assertion

+    return post

   postComment: (browser) ->

@@ -99,6 +100,8 @@

     browser.assert.containsText  '[testpath=ActivityListItemView]:first-child .comment-body-container', comment # Assertion

+    return comment
+

   getUrl: ->
     return 'http://lvh.me:8090'


Added field date type

Added field date type

--- app/dl-admin.coffee
+++ app/dl-admin.coffee
@@ -325,7 +325,10 @@
           input.append "
<button type='button' class='btn btn-xs btn-default' data-method='removeImage' data-params='#{inputID}' data-field='#{inputID}'> Remover imagem</button>
"
           input.append "
"
         input.append "<input type='file' id='#{inputID}'>"
-
+
+      if field.type == "date"
+        input.append "<input type='date' class='form-control' id='#{inputID}' value='#{inputValue}'>"
+
         # input.append '
Dica: blablabla
'
       # else
         # input.append "<input type='text' class='form-control' id='#{inputID}' value='#{object[field.name]}'>"
@@ -378,6 +381,9 @@
           input.append "
<button type='button' class='btn btn-xs btn-default' data-method='removeImage' data-params='#{inputID}' data-field='#{inputID}'> Remover imagem</button>
"
           input.append "
"
         input.append "<input type='file' id='#{inputID}'>"
+
+      if field.type == "date"
+        input.append "<input type='date' class='form-control' id='#{inputID}'>"

         # input.append '
Dica: blablabla
'
       # else


bugfix for three-rotation-ios

bugfix for three-rotation-ios

--- src/helix/three/rotation/ios.coffee
+++ src/helix/three/rotation/ios.coffee
@@ -10,9 +10,9 @@
         _native = window.nativeTracking

         if _native?
-            @set('rw', _native.w)
-            @set('rx', _native.x)
-            @set('ry', _native.y)
-            @set('rz', _native.z)
+            @set('rx', _native[0])
+            @set('ry', _native[1])
+            @set('rz', _native[2])
+            @set('rw', _native[3])

 })


Compact and harden media-grid state reset logic.

--- src/js/jquery.hlf.media-grid.coffee
+++ src/js/jquery.hlf.media-grid.coffee
@@ -99,10 +99,10 @@
     toggleItemExpansion: ($item, expanded) ->
       expanded ?= not $item.hasClass @classNames.expanded
       if expanded
+        @toggleItemExpansion @$expandedItem, off if @$expandedItem?
         i = $item.index()
         if @_isRightEdgeItem(i) then @_adjustItemToRightEdge $item
         if @_isBottomEdgeItem(i) then @_adjustItemToBottomEdge $item
-        @$items.removeClass @classNames.expanded

       $item.addClass @classNames.transitioning
       clearTimeout @_expandTimeout if @_expandTimeout?
@@ -123,8 +123,9 @@
       return

     toggleItemFocus: ($item, focused, delay) ->
+      @$items.removeClass @classNames.focused if focused
       $item.toggleClass @classNames.focused, focused
-      clearTimeout @_dimTimeout if @_dimTimeout?
+      clearTimeout @_dimTimeout
       @_dimTimeout = setTimeout =>
         return unless focused is $item.hasClass(@classNames.focused)
         @$el.toggleClass @classNames.dimmed, focused
@@ -169,7 +170,7 @@
       return

     _reLayoutItems: (delay=0) ->
-      clearTimeout @_layoutTimeout if @_layoutTimeout?
+      clearTimeout @_layoutTimeout
       @_layoutTimeout = setTimeout =>
         key = @attr 'original-position'
         @$items.css


Got auto-updating to work.

Got auto-updating to work.

This can draw lots of lines very quickly, so with Rickshaw it's clear that the plotting system won't be the performance bottleneck.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -1,5 +1,5 @@
 class RickshawStripChart
-  tickLength: 1000
+  tickLength: 10
   values: null
   year: 2015
   numCranes: 400
@@ -13,21 +13,13 @@
     @tick()

   buildChart: ->
-    @chart = new Rickshaw.Graph( {
+    @chart = new Rickshaw.Graph({
       element: document.getElementById('chart')
       width: 960
       height: 500
       renderer: 'line'
-      series: [
-          {
-            color: 'steelblue',
-            data: [ { x: 0, y: 23}, { x: 1, y: 15 }, { x: 2, y: 79 } ]
-          }, {
-            color: 'lightblue',
-            data: [ { x: 0, y: 30}, { x: 1, y: 20 }, { x: 2, y: 64 } ]
-          }
-        ]
-      #series: []
+      series: @values
+      min: 'auto'
     })

   drawChart: ->
@@ -38,16 +30,14 @@
     offsets = years.map (x) -> Math.round(20*(Math.random()-0.65))
     start = @numCranes
     newVals = offsets.reduce ((l, r) -> l.concat([l[l.length-1]+r])), [start]
-    #console.log(JSON.stringify(newVals))
-    newData = newVals.map (v, i) => { 'Year': @year+i, 'Number of cranes': v }
-    #console.log(JSON.stringify(newData))
-    @values.push(newData)
-
+    newData = newVals.map (v, i) => { x: @year+i, y: v }
+    @values.push({color: 'red', data: newData})
+
   tick: =>
-    # @extendData()
-    # console.log(JSON.stringify(@values))
+    @extendData()
     @drawChart()
     @runNumber = @runNumber + 1
-    # setTimeout(@tick, @tickLength)
+    console.log("Run number #{@runNumber}")
+    setTimeout(@tick, @tickLength)

-window.RickshawStripChart = RickshawStripChart+window.RickshawStripChart = RickshawStripChart


Stopped it from running an extra run if you clicked start.

If you clicked the Start button it ran an extra run, even if you'd hit the maximum number of runs. This fixes that.

--- src/lib/rickshaw_strip_chart.coffee
+++ src/lib/rickshaw_strip_chart.coffee
@@ -10,6 +10,7 @@
   offset_param: null
   isRunning: false
   hasStarted: false
+  notDone: true

   constructor: ->
     @values = []
@@ -21,8 +22,11 @@
     @isRunning = not @isRunning
     if not @hasStarted
       @start()
-    if @isRunning
+    if @isRunning and @notDone
+      $("#start_button").text("Stop")
       @tick()
+    else
+      $("#start_button").text("Start")

   start: ->
     @numCranes = Number($("#num_cranes").val())
@@ -74,7 +78,8 @@
     @extendData()
     @drawChart()
     @runNumber = @runNumber + 1
+    @notDone = @runNumber < 100
     console.log("Run number #{@runNumber}")
-    setTimeout(@tick, @tickLength) if @isRunning and @runNumber < 100
+    setTimeout(@tick, @tickLength) if @isRunning and @notDone

 window.RickshawStripChart = RickshawStripChart


fixed heights on rebadylize with small height refEl

--- jquery-badyl.coffee
+++ jquery-badyl.coffee
@@ -1,11 +1,11 @@
 ###
 @name jquery-badyl
 @description Meet Badyl – bootstrap affix-like wheel reinvent.
-@version 1.7.54
+@version 1.7.56
 @author Se7enSky studio <info@se7ensky.com>
 ###

-###! jquery-badyl 1.7.54 http://github.com/Se7enSky/jquery-badyl###
+###! jquery-badyl 1.7.56 http://github.com/Se7enSky/jquery-badyl###

 plugin = ($) ->

@@ -95,17 +95,23 @@

 			@badylHeight = @$refEl.height() + @config.offset * 2

+			@$badylInner.css
+				width: "#{@containerInnerWidth}px"
+				padding: "#{@config.offset}px 0"
+			@originalHeight = @$originalElement.height()
+			@badylInnerHeight = @originalHeight + @config.offset * 2
+
+			if @badylInnerHeight > @badylHeight
+				@$refEl.css height: "#{@badylInnerHeight}px"
+				@badylHeight = @$refEl.height() + @config.offset * 2
+
 			@$badylContainer.css
 				position: 'relative'
 				height: "#{@badylHeight}px"
 				margin: "-#{@config.offset}px 0"
 			@$badylInner.css
-				width: "#{@containerInnerWidth}px"
-				padding: "#{@config.offset}px 0"
-			@originalHeight = @$originalElement.height()
-			@badylInnerHeight = @originalHeight + @config.offset * 2
-			@$badylInner.css
 				height: "#{@badylInnerHeight}px"
+
 			@badylOffsetTop = @$badylContainer.offset().top
 			@refreshInnerCss()
 			@badylized = yes


removed the reset of restify

removed the reset of restify

--- app/coffee/SpellingAPIController.coffee
+++ app/coffee/SpellingAPIController.coffee
@@ -1,31 +1,23 @@
 SpellingAPIManager = require './SpellingAPIManager'
-restify = require 'restify'
 logger = require 'logger-sharelatex'
 metrics = require('metrics-sharelatex')

 module.exports = SpellingAPIController =
 	check: (req, res, next) ->
 		metrics.inc "spelling-check", 0.1
-		if req.is("json")
-			logger.log token: req?.params?.user_id, word_count: req?.body?.words?.length, "running check"
-			SpellingAPIManager.runRequest req.params.user_id, req.body, (error, result) ->
-				if err?
-					logger.err err:err, user_id:req?.params?.user_id, word_count: req?.body?.words?.length, "error processing spelling request"
-					return res.send(500)
-				res.send(result)
-		else
-			next(new restify.NotAcceptableError("Please provide a JSON request"))
+		logger.log token: req?.params?.user_id, word_count: req?.body?.words?.length, "running check"
+		SpellingAPIManager.runRequest req.params.user_id, req.body, (error, result) ->
+			if err?
+				logger.err err:err, user_id:req?.params?.user_id, word_count: req?.body?.words?.length, "error processing spelling request"
+				return res.send(500)
+			res.send(result)

 	learn: (req, res, next) ->
 		metrics.inc "spelling-learn", 0.1
-		if req.is("json")
-			logger.log token: req?.params?.user_id, word: req?.body?.word, "learning word"
-			SpellingAPIManager.learnWord req.params.user_id, req.body, (error, result) ->
-				return next(error) if error?
-				res.send(200)
-				next()
-		else
-			next(new restify.NotAcceptableError("Please provide a JSON request"))
+		logger.log token: req?.params?.user_id, word: req?.body?.word, "learning word"
+		SpellingAPIManager.learnWord req.params.user_id, req.body, (error, result) ->
+			return next(error) if error?
+			res.send(200)
+			next()

-


use `_new_state()` in `tex_from_md()`, avoid to validate existence of files when source is string

--- src/tex-writer.coffee
+++ src/tex-writer.coffee
@@ -2190,10 +2190,11 @@
     process.exit 0

 #-----------------------------------------------------------------------------------------------------------
-@_new_state = ( source_route ) ->
-  R =
+@_new_state = ( source_route, settings ) ->
+  validate    = ( settings ? {} ).validate ? true
+  R           =
     options:              @options
-    layout_info:          HELPERS.new_layout_info @options, source_route
+    layout_info:          HELPERS.new_layout_info @options, source_route, validate
     paragraph_nr:         0
     configuration:        {}
   return R
@@ -2252,11 +2253,8 @@
   layout_info         = HELPERS.new_layout_info @options, source_route, false
   #.........................................................................................................
   ### TAINT use method to produce new state ###
-  S =
-    options:              @options
-    layout_info:          layout_info
-    bare:                 settings[ 'bare' ] ? no
-    paragraph_nr:         0
+  S                       = @_new_state source_route, { validate: false, }
+  S.bare                  = settings[ 'bare' ] ? no
   #.........................................................................................................
   XXX_tex_from_md_nr += +1
   md_readstream       = MD_READER.create_md_read_tee S, md_source


add tour region

add tour region

--- tutor/src/components/task-plan/reading.cjsx
+++ tutor/src/components/task-plan/reading.cjsx
@@ -14,6 +14,7 @@
 LoadableItem = require '../loadable-item'
 TaskPlanBuilder = require './builder'
 Fn = require '../../helpers/function'
+{ default: TourRegion } = require '../tours/region'

 ReviewReadingLi = React.createClass
   displayName: 'ReviewReadingLi'
@@ -75,10 +76,16 @@

   renderSelected: ->
     if @props.selected.length
-

+      <TourRegion
+        tag="ul"
+        delay=4000
+        className="selected-reading-list"
+        id={"add-reading-review-sections"}
+        courseId={@props.courseId}
+      >

Currently selected

         {_.map(@props.selected, @renderSection)}
-

+      </TourRegion>
     else

@@ -106,8 +113,11 @@
         disabled={@props.selected?.length is 0}
         onClick={@hide}>Add Readings
       </BS.Button>
-
-

+    <TourRegion
+      className="reading-plan-select-topics"
+      id={"add-reading-choose-sections"}
+      courseId={@props.courseId}
+    >
       <SelectTopics
         primary={primary}
         onSectionChange={Fn.empty}
@@ -118,11 +128,14 @@
         selected={@props.selected}
         cancel={@props.cancel}
         hide={@hide} />
-

+    </TourRegion>

 ReadingPlan = React.createClass
   displayName: 'ReadingPlan'
   mixins: [PlanMixin]
+
+  getInitialState: ->
+    showSectionTopics: false

   render: ->
     {id, courseId} = @props
@@ -149,7 +162,7 @@
     addReadingText = if topics?.length then 'Add More Readings' else 'Add Readings'

-    if (@state?.showSectionTopics)
+    if (@state.showSectionTopics)
       selectReadings = <ChooseReadings
                         hide={@hideSectionTopics}
                         cancel={@cancelSelection}
@@ -178,9 +191,10 @@
       <BS.Panel
         className={formClasses}
         footer={footer}
-        header={header}>
-
-        <BS.Grid fluid>
+        header={header}
+      >
+
+        {<BS.Grid fluid>
           <TaskPlanBuilder courseId={courseId} id={id} {...builderProps}/>

           <BS.Row>
@@ -196,7 +210,7 @@
             </BS.Col>
           </BS.Row>

-        </BS.Grid>
+        </BS.Grid> unless @state.showSectionTopics}
       </BS.Panel>
       {selectReadings}


make sure the toolbar has not got the full width of the editable element when floating is set to true. if floating is set to true, make sure the toolbar width is calculated properly taking padding and borders into account

--- hallo.coffee
+++ hallo.coffee
@@ -310,7 +310,16 @@
         _activated: (event) ->
             widget = event.data
             jQuery(@).addClass 'inEditMode'
-            widget.toolbar.css "width", jQuery(@).width()+26
+            #make sure the toolbar has not got the full width of the editable element when floating is set to true
+            if widget.options.floating is false
+                el = jQuery @
+                widthToAdd = parseFloat el.css('padding-left')
+                widthToAdd += parseFloat el.css('padding-right')
+                widthToAdd += parseFloat el.css('border-left-width')
+                widthToAdd += parseFloat el.css('border-right-width')
+                widget.toolbar.css "width", jQuery(@).width()+widthToAdd
+            else
+                widget.toolbar.css "width", "auto"
             widget._trigger "activated", event

         _deactivated: (event) ->


Birds - Fix bug where under certain conditions, a user could not edit previously entered data.

--- app/controllers/interfaces/Birds.coffee
+++ app/controllers/interfaces/Birds.coffee
@@ -349,13 +349,13 @@
       @selected = true
       @trigger 'select', @

-  save: (data) =>
-    log 'Record: save: data', data
-    for datum in @data when data.id is datum.id
-      datum = data
+  save: (newFieldData) =>
+    log 'Record: save: data', newFieldData, @data
+    for datum, i in @data when newFieldData.id is datum.id
+      @data[i].data = newFieldData.data
       return

-    @data.push data
+    @data.push newFieldData

   load: (id) =>
     log 'loading data id #', id
@@ -495,7 +495,7 @@
     @pageWidget.load record

     record.bind 'select', =>
-      log 'record', record, @
+      # log 'record', record, @
       @pageWidget.load record

     record.trigger 'start'


clean up code

clean up code

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -70,56 +70,61 @@
     $(document).on 'mark-change', => @updateButtons()
     @marksContainer = @el.find('#marks-container')[0]

+    # /// BEGIN TUTORIAL ///
     @tutorial = new Tutorial
       progress: 9
       steps:
         first:
-          progress: 1
+          progress:    1
           header:      translate 'span', 'tutorial.first.header'
           content:     translate 'span', 'tutorial.first.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          next: 'theData1'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          next:        'theData1'

         theData1:
-          progress: 2
+          progress:    2
           header:      translate 'span', 'tutorial.theData1.header'
           content:     translate 'span', 'tutorial.theData1.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          next: 'theData2'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          next:        'theData2'

         theData2:
-          progress: 3
+          progress:    3
           header:      translate 'span', 'tutorial.theData2.header'
           content:     translate 'span', 'tutorial.theData2.content'
-          attachment: [0.5, 0.5, "#slider-container", 0.5, 0.5]
-          arrow: "bottom"
-          next: 'transits'
+          attachment:  [0.5, 0.5, "#slider-container", 0.5, 0.5]
+          arrow:       "bottom"
+          next:        'transits'

         transits:
-          progress: 4
+          progress:    4
           header:      translate 'span', 'tutorial.transits.header'
           content:     translate 'span', 'tutorial.transits.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          next: 'markingTransits'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          next:        'markingTransits'

         markingTransits:
-          progress: 5
+          progress:    5
           header:      translate 'span', 'tutorial.markingTransits.header'
           content:     translate 'span', 'tutorial.markingTransits.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          next: 'spotTransits'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          next:        'spotTransits'

         spotTransits:
-          progress: 6
+          progress:    6
           header:      translate 'span', 'tutorial.spotTransits.header'
           content:     translate 'span', 'tutorial.spotTransits.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          next: 'showTransits'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          next:        'showTransits'

         showTransits:
-          progress: 7
-          demo: ->
-            console.log 'FOO!'
+          progress:    7
+          header:      translate 'span', 'tutorial.showTransits.header'
+          content:     translate 'span', 'tutorial.showTransits.content'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          next:        'zooming'
+
+          demo: -> # TODO: Fix. This doesn't work!
             # modify this to fit the light curve
             window.classifier.canvasGraph.highlightCurve(1,2)
             window.classifier.canvasGraph.highlightCurve(4,5)
@@ -127,28 +132,22 @@
             window.classifier.canvasGraph.highlightCurve(10,11)
             window.classifier.canvasGraph.highlightCurve(12,13)

-          header:      translate 'span', 'tutorial.showTransits.header'
-          content:     translate 'span', 'tutorial.showTransits.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          next: 'zooming'

         zooming:
-          progress: 8
+          progress:    8
           header:      translate 'span', 'tutorial.zooming.header'
           content:     translate 'span', 'tutorial.zooming.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-          arrow: 'right'
-          attachment: [0, 0.5, "#zoom-button", 0, 0.5]
-          next: 'click [id="zoom-button"]': 'goodLuck'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+          arrow:       'right'
+          attachment:  [0, 0.5, "#zoom-button", 0, 0.5]
+          next:        'click [id="zoom-button"]': 'goodLuck'

         goodLuck:
-          progress: 9
+          progress:    9
           header:      translate 'span', 'tutorial.goodLuck.header'
           content:     translate 'span', 'tutorial.goodLuck.content'
-          attachment: [0.5, 0.5, "#graph-container", 0.5, 0.5]
-
-      # steps: tutorialSteps
-      # firstStep: 'welcome'
+          attachment:  [0.5, 0.5, "#graph-container", 0.5, 0.5]
+    # /// END TUTORIAL ///

     # mini course
     @course = new MiniCourse


on zootorial-end call finishSubject() instead of Subject.next()

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -366,7 +366,7 @@
     addEventListener "zootorial-end", =>
       $('.tutorial-annotations.x-axis').removeClass('visible')
       $('.tutorial-annotations.y-axis').removeClass('visible')
-      Subject.next()
+      @finishSubject() # loads next subject, among other stuff

     # load training subject
     @notify('Loading tutorial...')


Extend range of lens detection

Extend range of lens detection

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -295,7 +295,7 @@

     # Reset classification variables
     @reset()
-
+
     # Create new classification
     @classification = new Classification {subject}

@@ -681,7 +681,13 @@

         # Check if any annotation over lens
         for index, annotation of @annotations
-          @isLensMarked = @checkImageMask(annotation.x, annotation.y)
+          if trainingType is 'lensed_galaxy'
+            r2 = (annotation.x - 220)*(annotation.x - 220) + (annotation.y - 220)*(annotation.y - 220)
+            @isLensMarked = if r2 < 400 then true else false
+            console.log r2, @isLensMarked, annotation.x, annotation.y
+          else
+            @isLensMarked = @checkImageMask(annotation.x, annotation.y)
+
           break if @isLensMarked is true

         if @isLensMarked in [true, 255]


Consistent treatment of tutorial and training sims rearding mask value - still seem to be getting mask read err (val=0) though in local app. Not present in beta2 site.

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -430,8 +430,8 @@
       mask = pixel.data[3]
     catch err
       mask = 0
-    console.log "Marker placed: ",x,y,mask
-    return if mask is 254 then true else mask
+    console.log "Marker placed on training sim: ",x,y,mask
+    return if mask is 255 then true else mask

   # Prevent annotations over SVG elements
   stopPropagation: (e) ->
@@ -692,10 +692,10 @@
         # if @isLensMarked in [true, 0]

         # Normal functioning (needs testing!!)
-        if @isLensMarked in [true, 254]
+        if @isLensMarked in [true, 255]
           # Lens was marked
           @tutorial = @createSimulationFoundFeedback(e, trainingType, x, y)
-        else if @isLensMarked in [false, 255]
+        else if @isLensMarked in [false, 254]
           # Lens was missed
           @tutorial = @createSimulationMissedFeedback(e, trainingType, x, y)
         else


fix(drag): Resize bars losing mosueup when over iframes

--- internal_packages/message-list/lib/email-frame.cjsx
+++ internal_packages/message-list/lib/email-frame.cjsx
@@ -138,8 +138,11 @@
     doc.write(EmailFixingStyles)
     doc.write("
#{@_emailContent()}
")
     doc.close()
-    doc.addEventListener "click", @_onClick
-    doc.addEventListener "keydown", @_onKeydown
+    doc.addEventListener "click", @_onIFrameClick
+    doc.addEventListener "keydown", @_onIFrameKeydown
+    doc.addEventListener "mousedown", @_onIFrameMouseEvent
+    doc.addEventListener "mousemove", @_onIFrameMouseEvent
+    doc.addEventListener "mouseup", @_onIFrameMouseEvent

   _setFrameHeight: ->
     _.defer =>
@@ -170,7 +173,11 @@
     else
       Utils.stripQuotedText(email)

-  _onClick: (e) ->
+  # The iFrame captures events that take place over it, which causes some
+  # interesting behaviors. For example, when you drag and release over the
+  # iFrame, the mouseup never fires in the parent window.
+
+  _onIFrameClick: (e) ->
     e.preventDefault()
     e.stopPropagation()
     target = e.target
@@ -183,6 +190,15 @@
       else
         target = target.parentElement

-  _onKeydown: (event) ->
+  _onIFrameMouseEvent: (event) ->
+    nodeRect = @getDOMNode().getBoundingClientRect()
+    @getDOMNode().dispatchEvent(new MouseEvent(event.type, _.extend({}, event, {
+      clientX: event.clientX + nodeRect.left
+      clientY: event.clientY + nodeRect.top
+      pageX: event.pageX + nodeRect.left
+      pageY: event.pageY + nodeRect.top
+    })))
+
+  _onIFrameKeydown: (event) ->
     return if event.metaKey or event.altKey or event.ctrlKey
     @getDOMNode().dispatchEvent(new KeyboardEvent(event.type, event))


:lipstick: Remove spaces

:lipstick: Remove spaces

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -144,5 +144,5 @@
     return false if selection.isEmpty()
     selectedText = selection.getText()
     firstCharacter = selectedText[0]
-    lastCharacter = selectedText[ selectedText.length - 1 ]
+    lastCharacter = selectedText[selectedText.length - 1]
     @pairedCharacters[firstCharacter] is lastCharacter


Fixes equation builder button incompleteness

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -263,9 +263,18 @@
     class_mixer(ExponentiateLast)

     class AppendExponentiation extends ExpressionManipulation
-      perform: (expression_position)->
+      initialize: (opts={})->
+        super
+        @exponent_content = opts.power
+
+      perform: (expression_position)->
+        exp = if @exponent_content
+          [@comps.build_number(value: @exponent_content)]
+        else
+          []
+
         base = @comps.build_expression()
-        power = @comps.build_expression()
+        power = @comps.build_expression(expression: exp)

         result_exp = @M(expression_position.expression()).clone().
           withComponent(expression_position, (component)=>


tweak mode behaviour

tweak mode behaviour

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -108,7 +108,7 @@
     ed.getCursors()[0].getBufferPosition().isEqual [0, 0]

   setMode: (cell, mode) ->
-    ed = ed = cell.querySelector('atom-text-editor').getModel()
+    ed = cell.querySelector('atom-text-editor').getModel()
     if not mode
       delete ed.inkConsoleMode
       if @view.defaultGrammar then ed.setGrammar @view.defaultGrammar
@@ -122,7 +122,7 @@
     @edListener?.dispose()
     ed = cell.querySelector('atom-text-editor').getModel()
     @edListener = ed.onWillInsertText (e) =>
-      if (mode = @modes()[e.text]) and @cursorAtBeginning(ed) and not ed.inkConsoleMode
+      if (mode = @modes()[e.text]) and @cursorAtBeginning(ed) and ed.inkConsoleMode isnt mode
         e.cancel()
         @setMode cell, mode


Fix the axial slider click delta.

Fix the axial slider click delta.

--- app/javascripts/directives.coffee
+++ app/javascripts/directives.coffee
@@ -83,6 +83,11 @@
         # TODO - Add tooltip to show current slider value.
         #
         scope.timeSeries.image.loader.callback = ->
+          # Obtain the range max from the scope.timeSeries.image.volumes.size
+          # property. This requires that the image is loaded. Therefore, the
+          # slider compile must be coordinated with the image display compile.
+          # The slider create must then be deferred as a callback after load
+          # completes.
           noUiSlider.create sliderAxialPlane,
             start: [ scope.slice.sliceNbr ]
             step: 1
@@ -100,37 +105,30 @@
             newVal = Math.floor sliderAxialPlane.noUiSlider.get()
             updateImage(newVal)

-        updateImage = (sliceNbr) ->
-          # Update the image with the new slice number, which corresponds to
-          # new slider position.
-          scope.$apply ->
-            scope.slice.sliceNbr = sliceNbr
-
-        # FIXME - Forward or back click moves the slider two ticks rather
-        #   than one. This is true only for the axial slider, not for the
-        #   time series slider.
-
-        # When the back button is clicked, reduce the slider position by one
-        # and update the image.
-        sliderAxialPlaneBack.addEventListener 'click', ->
-          newVal = scope.slice.sliceNbr - 1
-          # Do the update only if the new value does not fall below 1.
-          if newVal >= 1
-            sliderAxialPlane.noUiSlider.set(newVal)
-            updateImage(newVal)
-
-        # When the forward button is clicked, increase the slider position by
-        # one and update the image.
-        sliderAxialPlaneFwd.addEventListener 'click', ->
-          newVal = scope.slice.sliceNbr + 1
-          # Do the update only if the new value does not go above the number of
-          # slices in the image axial plane.
-          #
-          # TODO - Obtain this value from the image object properties.
-          #
-          if newVal <= scope.timeSeries.image.slices.size
-            sliderAxialPlane.noUiSlider.set(newVal)
-            updateImage(newVal)
+          updateImage = (sliceNbr) ->
+            # Update the image with the new slice number, which corresponds to
+            # new slider position.
+            scope.$apply ->
+              scope.slice.sliceNbr = sliceNbr
+
+          # When the back button is clicked, reduce the slider position by one
+          # and update the image.
+          sliderAxialPlaneBack.addEventListener 'click', ->
+            newVal = scope.slice.sliceNbr - 1
+            # Do the update only if the new value does not fall below 1.
+            if newVal >= 1
+              sliderAxialPlane.noUiSlider.set(newVal)
+              updateImage(newVal)
+
+          # When the forward button is clicked, increase the slider position by
+          # one and update the image.
+          sliderAxialPlaneFwd.addEventListener 'click', ->
+            newVal = scope.slice.sliceNbr + 1
+            # Do the update only if the new value does not go above the number of
+            # slices in the image axial plane.
+            if newVal <= scope.timeSeries.image.slices.size
+              sliderAxialPlane.noUiSlider.set(newVal)
+              updateImage(newVal)

     directives.directive 'qiSliderTimeSeries', ->
@@ -142,11 +140,6 @@
         sliderTimeSeriesBack = document.getElementById('qi-slider-time-series-back')
         sliderTimeSeriesFwd = document.getElementById('qi-slider-time-series-fwd')

-        # The number of volumes in the time series.
-        timeSeriesLength = scope.timeSeries.image.volumes.size
-        # The initial volume number.
-        volumeNbr = scope.volume.volumeNbr
-
         # Configure the slider.
         #
         # FIXME - The time series slider created is called twice,
@@ -158,27 +151,27 @@
         # TODO - Add tooltip to show current slider value.
         #
         noUiSlider.create sliderTimeSeries,
-          start: [ volumeNbr ]
+          start: [ scope.volume.volumeNbr ]
           step: 1
           range:
             'min': 1
-            'max': timeSeriesLength
+            'max': scope.timeSeries.image.volumes.size
           pips:
             mode: 'positions'
             values: [0, 100]
             density: 4
+
+        # When the slider is dragged, get the new slider position and update
+        # the image.
+        sliderTimeSeries.noUiSlider.on 'slide', ->
+          newVal = Math.floor sliderTimeSeries.noUiSlider.get()
+          updateImage(newVal)

         updateImage = (volumeNbr) ->
           # Update the image with the new volume number, which corresponds to
           # new slider position.
           scope.$apply ->
             scope.volume.volumeNbr = volumeNbr
-
-        # # When the slider is dragged, get the new slider position and update
-        # # the image.
-        sliderTimeSeries.noUiSlider.on 'slide', ->
-          newVal = Math.floor sliderTimeSeries.noUiSlider.get()
-          updateImage(newVal)

         # When the back button is clicked, reduce the slider position by one
         # and update the image.
@@ -195,7 +188,7 @@
           newVal = scope.volume.volumeNbr + 1
           # Do the update only if the new value does not go above the number of
           # volumes in the time series.
-          if newVal <= timeSeriesLength
+          if newVal <= scope.timeSeries.image.volumes.size
             sliderTimeSeries.noUiSlider.set(newVal)
             updateImage(newVal)


--- lib/assets/javascripts/helpers/formatting.js.coffee
+++ lib/assets/javascripts/helpers/formatting.js.coffee
@@ -62,6 +62,8 @@
   formatTentMarkdown: (text = '', mentions = []) ->
     inline_mention_urls = _.map mentions, (m) => TentStatus.Helpers.entityProfileUrl(m.entity)

+    preprocessors = []
+
     parsePara = (para, callback) ->
       new_para = for item in para
         if _.isArray(item) && item[0] in ['para', 'strong', 'em', 'del']
@@ -79,9 +81,21 @@
       jsonml[1]['data-view'] = 'ExternalLink'
       jsonml

+    preprocessors.push(externalLinkPreprocessor)
+
+    # Disable hashtag autolinking when search isn't enabled
+    unless TentStatus.config.services.search_api_root
+      disableHashtagAutolinking = (jsonml) ->
+        return jsonml unless jsonml[0] is 'link'
+        return jsonml unless jsonml[1]?.rel is 'hashtag'
+
+        ['span', jsonml[2]]
+
+      preprocessors.push(disableHashtagAutolinking)
+
     markdown.toHTML(text, 'Tent', {
       footnotes: inline_mention_urls
       hashtagURITemplate: @fullPath('/search') + '?q=%23{hashtag}'
-      preprocessors: [externalLinkPreprocessor]
+      preprocessors: preprocessors
     })


Fix UnifiedCollection next params

Fix UnifiedCollection next params

--- lib/assets/javascripts/unified_collection.js.coffee
+++ lib/assets/javascripts/unified_collection.js.coffee
@@ -37,7 +37,7 @@
       do (cid) =>
         _completeFn = options[cid]?.complete
         options[cid] ?= {}
-        options[cid].complete = (models, res, xhr) =>
+        options[cid].complete = (models, res, xhr, _params) =>
           _completeFn?.apply?(null, arguments)

           return unless xhr.status == 200
@@ -52,12 +52,16 @@
               # sometimes not all the results are used
               # in this case we need to create our own `next` query
               _last_model = _.last(models)
-              _before = "#{_last_model.get('received_at') || _last_model.get('published_at')} #{_last_model.get('version.id')}"
+              _before = "before=#{_last_model.get('received_at') || _last_model.get('published_at')} #{_last_model.get('version.id')}"
               _pagination.next = _pagination.next.replace(/before=[^&]+/, _before)
             else
               # in the case that no models are returned,
-              # just use the same next query as last time
-              _pagination.next = @pagination[cid]?.next
+              # just use the same query as last time
+              _pagination.next = @pagination[cid]?.next || Marbles.history.serializeParams(_params)
+          else if models.length != res.posts.length
+            # in the case that no models are returned
+            # and there is only a single, non-empty page
+            _pagination.next = Marbles.history.serializeParams(_params)

           if options.prepend # fetchPrev
             _pagination.next = @pagination[cid]?.next


hub: monitoring cpu usage, in a node 5 and earlier compatible way.

--- src/smc-hub/metrics-recorder.coffee
+++ src/smc-hub/metrics-recorder.coffee
@@ -21,10 +21,11 @@

 # This is a small helper class to record real-time metrics about the hub.
 # It is designed for the hub, such that a local process can easily check its health.
-# optionally, it stores the metrics to a status file -- TODO: also push this to the DB
-# usage: after init, publish key/value pairs which are then going to be reported
+# After an initial version, this has been repurposed to use prometheus.
+# It wraps its client elements and adds some instrumentation to some hub components.

 fs         = require('fs')
+path       = require('path')
 underscore = require('underscore')
 {defaults} = misc = require('smc-util/misc')

@@ -91,7 +92,6 @@

         # the full statistic
         @_data  = {}
-        @_lastCpuUsage = null

         @_collectors = []

@@ -121,24 +121,42 @@
         setup monitoring of some components
         called by the hub *after* setting up the DB, etc.
         ###
-        num_clients_gauge = new_gauge('hub_clients_count', 'Number of connected clients')
+        num_clients_gauge = new_gauge('clients_count', 'Number of connected clients')
         {number_of_clients} = require('./hub_register')
         @register_collector ->
             num_clients_gauge.set(number_of_clients())

-        cpu_seconds_total = new_counter('hub_cpu_seconds_total', 'Total number of CPU seconds used', ['type'])
-        @register_collector =>
-            if not process.cpuUsage?
-                return
-            #cpuUsage = process.cpuUsage(@_lastCpuUsage)
-            #cpu_seconds_total.labels('user').inc(cpuUsage.user)
-            #cpu_seconds_total.labels('system').inc(cpuUsage.system)
-		    #@_lastCpuUsage = cpuUsage
+        # our own CPU metrics monitor, separating user and sys!
+        @_lastCpuUsage =
+            user:      0.0
+            system:    0.0
+            chld_user: 0.0
+            chld_time: 0.0
+        @_cpu_seconds_total = new_counter('process_cpu_seconds_total', 'Total number of CPU seconds used', ['type'])

     _collect: ->
         # called by @_update to evaluate the collector functions
         for c in @_collectors
             c()
+
+        # linux specific: collecting this process and all its children sys+user times
+        fs.readFile path.join('/proc', ''+process.pid, 'stat'), 'utf8', (err, infos) =>
+            if err
+                return
+            index = infos.lastIndexOf(')')
+            infos = infos.substr(index + 2).split(' ')
+            cpu =
+                user:       parseFloat(infos[11])
+                system:     parseFloat(infos[12])
+                chld_user:  parseFloat(infos[13])
+                chld_time:  parseFloat(infos[14])
+            @_cpu_seconds_total.labels('user')       .inc(cpu.user        - @_lastCpuUsage.user)
+            @_cpu_seconds_total.labels('system')     .inc(cpu.system      - @_lastCpuUsage.system)
+            @_cpu_seconds_total.labels('chld_user')  .inc(cpu.chld_user   - @_lastCpuUsage.chld_user)
+            @_cpu_seconds_total.labels('chld_system').inc(cpu.chld_system - @_lastCpuUsage.chld_system)
+
+            @_lastCpuUsage = cpu
+

     # every FREQ_s the _data dict is being updated
     # e.g current value, exp decay, later on also "intelligent" min/max, etc.


Remove trailing whitespaces

Remove trailing whitespaces

--- client/landing/app/CommonViews/Tags/TagViews.coffee
+++ client/landing/app/CommonViews/Tags/TagViews.coffee
@@ -5,7 +5,7 @@
       cssClass      : 'tag-group'
     , options
     super options, data
-
+
   viewAppended:->
     @setTemplate @pistachio()
     @template.update()
@@ -16,7 +16,7 @@
         title     :
           $in     : stringTags
       ,
-        sort      :
+        sort      :
           'title' : 1
       , (err,tags)=>
         unless err and not tags
@@ -27,7 +27,6 @@
     else
       warn "no tag info was given!"

-
 class SkillTagGroup extends TagGroup
   constructor:(options, data)->
     super options, data
@@ -38,7 +37,7 @@
       tagName   : "span"
       cssClass  : "noskilltags"
       partial   : "#{@getData().profile.firstName} hasn't entered any skills yet."
-
+
     controller = new KDListViewController
       view            : new KDListView
         subItemClass  : TagCloudListItemView
@@ -58,12 +57,12 @@
       '{{> @noTags}}'
     else
       '{{> @listViewWrapper}}'
-
+
 class TagCloudListItemView extends KDListItemView
   constructor:(options, data)->
     options = $.extend
       tagName     : "a"
-      attributes  :
+      attributes  :
         href      : "#"
     , options
     super options, data
@@ -75,7 +74,7 @@
   viewAppended:->
     @setTemplate @pistachio()
     @template.update()
-
+
   pistachio:->
     super "{{#(title)}}"


exchange order

exchange order

--- src/plugins/export-portals.coffee
+++ src/plugins/export-portals.coffee
@@ -29,8 +29,8 @@
                 line.push po.title.replace(/,/g, '-').trim() if argv.title or argv.t
                 line.push po.latE6 / 1e6 if argv.latlng or argv.l
                 line.push po.lngE6 / 1e6 if argv.latlng or argv.l
+                line.push po.image if argv.image or argv.I
                 line.push po._id if argv.id or argv.i
-                line.push po.image if argv.image or argv.I

                 fs.writeSync fd, line.join(',') + '\n'


separate function to draw state

separate function to draw state

--- layout.coffee
+++ layout.coffee
@@ -1,14 +1,23 @@
-layout = (g, state) ->
+drawState = (g, state) ->
+    size = 100
+
+    if state.children?
+        size += drawChildren(g, state.children)
+
+    return size
+
+
+drawChildren = (g, children) ->
     childNodes = []
     childLinks = []
     stateMap = {}

-    for child in state.children or []
+    for child in children
         child._idx = childNodes.length
         stateMap[child.name] = child
         childNodes.push({name: child.name})

-    for child in state.children or []
+    for child in children
         for tr in child.transitions or []
             target = stateMap[tr.target]
             childLinks.push({source: child._idx, target: target._idx})
@@ -24,10 +33,9 @@
       .enter().append('g')
         .attr('class', 'cell')

-    size = 1000
-    for child in state.children or []
-        if child.children?
-            size += layout(cell, child) * 5
+    size = 0
+    for child in children
+        size += drawState(cell, child) * 3

     r = Math.sqrt(size)

@@ -75,7 +83,7 @@
       .append('g')
         .attr('transform', "translate(#{width/2}, #{height/2})")

-    layout(svg, {name: "_root", children: tree})
+    drawChildren(svg, tree)

 demo([


fixed Bandango.User#isCurrentUser

fixed Bandango.User#isCurrentUser

--- app/assets/javascripts/mixins/user_mixin.js.coffee
+++ app/assets/javascripts/mixins/user_mixin.js.coffee
@@ -9,5 +9,5 @@
   ).property("nombres")

   isCurrentUser: (->
-    parseInt(@get("id"), 10) == Bandango.currentSession.get("user").get("id") and this != Bandango.currentSession.get("user")
+    this == Bandango.currentSession.get("user")
   ).property()


Fix redirect issues with closing cards.

Fix redirect issues with closing cards.

--- app/assets/javascripts/routes/task_route.js.coffee
+++ app/assets/javascripts/routes/task_route.js.coffee
@@ -19,7 +19,7 @@
     taskController.set('model', model)
     @set('taskController', taskController)

-    if Em.computed.notEmpty(@controllerFor('application').get('overlayRedirect'))
+    if !Em.isEmpty(@controllerFor('application').get('overlayRedirect'))
       taskController.set('onClose', 'redirect')

   renderTemplate: ->
@@ -35,6 +35,8 @@

   actions:
     willTransition: (transition) ->
-      redirectRoute = @controllerFor('application').get('overlayRedirect').popObject()
-      unless transition.targetName == redirectRoute.get('firstObject')
-        @controllerFor('application').set('cachedModel', null)
+      redirectStack = @controllerFor('application').get('overlayRedirect')
+      if !Em.isEmpty(redirectStack)
+        redirectRoute = redirectStack.popObject()
+        unless transition.targetName == redirectRoute.get('firstObject')
+          @controllerFor('application').set('cachedModel', null)


:memo: Add a note on LinterRegistry::deactivate

--- lib/linter-registry.coffee
+++ lib/linter-registry.coffee
@@ -63,6 +63,8 @@

   deactivate: ->
     @emitter.dispose()
+    # Intentionally set it to empty array instead of null 'cause this would
+    # disallow further execution, while still not throwing in current one
     @linters = []

 module.exports = LinterRegistry


sneaks in fix for linking to artist

sneaks in fix for linking to artist

--- components/commercial_filter/queries/artwork.coffee
+++ components/commercial_filter/queries/artwork.coffee
@@ -17,6 +17,7 @@
       id
       name
       href
+      public
     }
     cultural_maker
     medium


end ussd action check for destination on save

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -1297,6 +1297,11 @@

       actionset._lastActionMissingTranslation = null

+      if action.type == "end_ussd"
+        actionset.destination = null
+        Plumb.updateConnection(actionset)
+        @applyActivity(actionset, $rootScope.activity)
+
       found = false
       lastAction = null
       for previous, idx in actionset.actions


:art: rename lastOperation to operation, and separate execution logic as execute method

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -64,13 +64,15 @@
       if @vimState.isMode('normal') and @peekTop().instanceof?('Operator')
         @vimState.activate('operator-pending')
     else
-      operation = @stack.pop()
-      @lastOperation = operation # Used for better error message.
-      @vimState.emitter.emit 'will-execute-operation', operation
-      operation.execute()
-      @vimState.emitter.emit 'did-execute-operation', operation
-      @record(operation) if operation.isRecordable()
-      @finish()
+      @operation = @stack.pop()
+      @execute()
+
+  execute: ->
+    @vimState.emitter.emit 'will-execute-operation', @operation
+    @operation.execute()
+    @vimState.emitter.emit 'did-execute-operation', @operation
+    @record(@operation) if @operation.isRecordable()
+    @finish()

   cancel: ->
     unless @vimState.isMode('visual') or @vimState.isMode('insert')
@@ -82,10 +84,10 @@
     if @vimState.isMode('normal')
       unless @editor.getLastSelection().isEmpty()
         if settings.get('throwErrorOnNonEmptySelectionInNormalMode')
-          operationName = @lastOperation.constructor.name
+          operationName = @operation.constructor.name
           message = "Selection is not empty in normal-mode: #{operationName}"
-          if @lastOperation.target?
-            message += ", target= #{@lastOperation.target.constructor.name}"
+          if @operation.target?
+            message += ", target= #{@operation.target.constructor.name}"
           throw new Error(message)
         else
           @editor.clearSelections()
@@ -93,7 +95,7 @@
       # Ensure Cursor is NOT at EndOfLine position
       for cursor in @editor.getCursors() when cursor.isAtEndOfLine()
         moveCursorLeft(cursor, {preserveGoalColumn: true})
-    @lastOperation = null
+    @operation = null
     @vimState.refreshCursors()
     @vimState.reset()


small cleanups

small cleanups

--- lib/operators/input.coffee
+++ lib/operators/input.coffee
@@ -47,7 +47,7 @@
       @typedText = @typedText.trimLeft()
       return super

-    @vimState.activateInsertMode(transactionStarted = true)
+    @vimState.activateInsertMode()
     @typingCompleted = true

 class InsertBelowWithNewline extends Insert
@@ -62,7 +62,7 @@
       @typedText = @typedText.trimLeft()
       return super

-    @vimState.activateInsertMode(transactionStarted = true)
+    @vimState.activateInsertMode()
     @typingCompleted = true

 #
@@ -94,7 +94,7 @@

     return super if @typingCompleted

-    @vimState.activateInsertMode(transactionStarted = true)
+    @vimState.activateInsertMode()
     @typingCompleted = true

 class Substitute extends Insert
@@ -113,7 +113,7 @@
       @typedText = @typedText.trimLeft()
       return super

-    @vimState.activateInsertMode(transactionStarated = true)
+    @vimState.activateInsertMode()
     @typingCompleted = true

 class SubstituteLine extends Insert
@@ -136,7 +136,7 @@
       @typedText = @typedText.trimLeft()
       return super

-    @vimState.activateInsertMode(transactionStarated = true)
+    @vimState.activateInsertMode()
     @typingCompleted = true

 # Takes a transaction and turns it into a string of what was typed.
@@ -154,8 +154,7 @@
     chars.join("")

   isTypedChar: (patch) ->
-    # Technically speaking, a typed char will be of length 1, but >= 1
-    # happens to let us test with editor.setText, so we'll look the other way.
+    # a typed char will be of length 1, but pasted text can be longer
     return false unless patch.newText?.length >= 1 and patch.oldText?.length == 0
     patch.newText


remove semicolons

remove semicolons

--- lib/pdf-editor-view.coffee
+++ lib/pdf-editor-view.coffee
@@ -29,7 +29,7 @@
     @subscribe @file, 'contents-changed', => @renderPdf()

   renderPdf: ->
-    pdfData = new Uint8Array(fs.readFileSync(@filePath));
+    pdfData = new Uint8Array(fs.readFileSync(@filePath))

     @container.find("canvas").remove()

@@ -39,11 +39,11 @@

         do (canvas) ->
           pdfDocument.getPage(pdfPageNumber).then (pdfPage) ->
-            scale = 1.5;
-            viewport = pdfPage.getViewport(scale);
-            context = canvas.getContext('2d');
-            canvas.height = viewport.height;
-            canvas.width = viewport.width;
+            scale = 1.5
+            viewport = pdfPage.getViewport(scale)
+            context = canvas.getContext('2d')
+            canvas.height = viewport.height
+            canvas.width = viewport.width

             pdfPage.render({canvasContext: context, viewport: viewport})


Lazy load dependencies

Lazy load dependencies

--- lib/profile-manager.coffee
+++ lib/profile-manager.coffee
@@ -1,8 +1,8 @@
-{Emitter} = require 'event-kit'
-path = require 'path'
-settingsPath = 'particle-cli/settings.js'
-settings = require settingsPath
-utilities = require 'particle-cli/lib/utilities.js'
+Emitter = null
+path = null
+settingsPath = null
+settings = null
+utilities = null

 Function::property = (prop, desc) ->
 	Object.defineProperty @prototype, prop, desc
@@ -10,6 +10,12 @@
 module.exports =
 	class ProfileManager
 		constructor: ->
+			{Emitter} = require 'event-kit'
+			path = require 'path'
+			settingsPath = 'particle-cli/settings.js'
+			settings = require settingsPath
+			utilities = require 'particle-cli/lib/utilities.js'
+
 			@emitter = new Emitter

 		destroy: ->


Getting it to work with the new model.

Getting it to work with the new model.

--- lib/project-checker.coffee
+++ lib/project-checker.coffee
@@ -2,10 +2,12 @@
   projects: {}

   constructor: ->
-    console.log(@getId() + ": activing")
+    #console.log(@getId() + ": activing")
+    return

   deactivate: ->
-    console.log(getId() + ": deactivating")
+    #console.log(getId() + ": deactivating")
+    return

   getId: -> "spell-check-project"
   getName: -> "Project Dictionary"
@@ -32,15 +34,35 @@
     # If we don't have language settings, we don't do anything.
     project = @getProject args
     if not project or not project.valid
-      return {}
+      return { }

     # Check the range for this dictionary.
     ranges = []
     checked = project.checker.check text
     for token in checked
-      if token.status == 1
+      if token.status is 1
         ranges.push {start: token.start, end: token.end }
     { correct: ranges }
+
+  checkArray: (args, words) ->
+    project = @getProject args
+    results = []
+    if not project or not project.valid
+      # We don't have a project settings, so everything is unknown.
+      for word in words
+        results.push null
+    else
+      # We have a project, so check each one directly.
+      for word, index in words
+        checked = project.checker.check word
+        if checked[0].status is 1
+          results.push true
+        else
+          results.push null
+
+    # Return the results for the words, either all nulls or verified against the
+    # project file.
+    results

   suggest: (args, word) ->
     # If we don't have language settings, we don't do anything.


Use new command API

Use new command API

--- lib/project-manager.coffee
+++ lib/project-manager.coffee
@@ -40,14 +40,18 @@
         @subscribeToProjectsFile()
         @loadCurrentProject()

-    atom.workspaceView.command 'project-manager:save-project', =>
-      @createProjectManagerAddView(state).toggle(@)
-    atom.workspaceView.command 'project-manager:toggle', =>
-      @createProjectManagerView(state).toggle(@)
-    atom.workspaceView.command 'project-manager:edit-projects', =>
-      atom.workspaceView.open @file()
-    atom.workspaceView.command 'project-manager:reload-project-settings', =>
-      @loadCurrentProject()
+    atom.commands.add 'atom-workspace',
+      'project-manager:toggle': =>
+        @createProjectManagerView(state).toggle(@)
+
+      'project-manager:save-project': =>
+        @createProjectManagerAddView(state).toggle(@)
+
+      'project-manager:edit-projects': =>
+        atom.workspaceView.open @file()
+
+      'project-manager:reload-project-settings': =>
+        @loadCurrentProject()

     atom.config.observe 'project-manager.environmentSpecificProjects',
     (newValue, obj = {}) =>


#501 - A form does not load completely if begin_group or end_group is used

--- dkobo/koboform/static/js/xlform/model_xlform.coffee
+++ dkobo/koboform/static/js/xlform/model_xlform.coffee
@@ -170,7 +170,7 @@
         r._parent = @
         if r.type in XLF.surveyDetailSchema.typeList()
           @surveyDetails.importDetail(r)
-        else if r.type in ["begin group", "begin repeat", "end group", "end repeat"]
+        else if r.type in ["begin group", "begin repeat", "end group", "end repeat", "begin_group", "begin_repeat", "end_group", "end_repeat"]
           #noop, for now.
         else
           @rows.add r, collection: @rows, silent: true


Fix #2889: show incremental gem and xp award for replayable levels, not total

--- app/views/play/level/modal/HeroVictoryModal.coffee
+++ app/views/play/level/modal/HeroVictoryModal.coffee
@@ -292,7 +292,7 @@
       duration = 1000
     ratio = @getEaseRatio (new Date() - @sequentialAnimationStart), duration
     if panel.unit is 'xp'
-      newXP = Math.floor(panel.previousNumber + ratio * (panel.number - panel.previousNumber))
+      newXP = Math.floor(ratio * (panel.number - panel.previousNumber))
       totalXP = @totalXPAnimated + newXP
       if totalXP isnt @lastTotalXP
         panel.textEl.text('+' + newXP)
@@ -304,7 +304,7 @@
         @XPEl.addClass 'five-digits' if totalXP >= 10000 and @lastTotalXP < 10000
         @lastTotalXP = totalXP
     else if panel.unit is 'gem'
-      newGems = Math.floor(panel.previousNumber + ratio * (panel.number - panel.previousNumber))
+      newGems = Math.floor(ratio * (panel.number - panel.previousNumber))
       totalGems = @totalGemsAnimated + newGems
       if totalGems isnt @lastTotalGems
         panel.textEl.text('+' + newGems)
@@ -326,9 +326,9 @@
       panel.rootEl.removeClass('animating').find('.reward-image-container img').removeClass('pulse')
       @sequentialAnimationStart = new Date()
       if panel.unit is 'xp'
-        @totalXPAnimated += panel.number
+        @totalXPAnimated += panel.number - panel.previousNumber
       else if panel.unit is 'gem'
-        @totalGemsAnimated += panel.number
+        @totalGemsAnimated += panel.number - panel.previousNumber
       @sequentialAnimatedPanels.shift()
       return
     panel.rootEl.addClass('animating').find('.reward-image-container').removeClass('pending-reward-image').find('img').addClass('pulse')


Skrollr Stylesheet Support

Skrollr Stylesheet Support

Allow rules with arbitrary prefixes.

--- lib/autoprefixer/rule.coffee
+++ lib/autoprefixer/rule.coffee
@@ -36,12 +36,16 @@
 Declaration.register require('./hacks/flex-direction')
 Declaration.register require('./hacks/justify-content')

+Declaration.prefixes = ['-ms-', '-moz-', '-webkit-', '-o-']
+
 class Rule
   constructor: (@rules, @number, @node, @prefix) ->
     @type         = @node.type
     @declarations = @node.declarations
     if @type == 'rule'
       @selectors  = @node.selectors.join(', ')
+    if Declaration.prefixes.indexOf(@prefix) == -1
+      @prefix = undefined

   # Execute callback on every property: value declarations
   each: (callback) ->