Increased timeouts in tests.

Increased timeouts in tests.

--- tests.coffee
+++ tests.coffee
@@ -471,7 +471,7 @@
     # (triggered by person inserts, but pending) run afterwards, then they can patch things which
     # should in fact be done by source observers (on post), like setting usernames in post's
     # references to persons
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person1 = Persons.findOne @person1Id
@@ -525,7 +525,7 @@
         @postId = postId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -601,7 +601,7 @@
     # Sleep so that observers have time to update documents
     # so that persons updates are not merged together to better
     # test the code for multiple updates
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     Persons.update @person3Id,
@@ -628,7 +628,7 @@
     test.equal @person3.displayName, 'Person 3'

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -688,7 +688,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -737,7 +737,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -769,7 +769,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -828,7 +828,7 @@
         @circularSecondId = circularSecondId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     if Meteor.isServer
@@ -869,7 +869,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -898,7 +898,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -928,7 +928,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -958,7 +958,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -984,7 +984,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -1020,7 +1020,7 @@
         @circularFirstId = circularFirstId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     if Meteor.isServer
@@ -1054,7 +1054,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -1080,7 +1080,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update document
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @circularFirst = CircularFirsts.findOne @circularFirstId,
@@ -1118,7 +1118,7 @@
         @recursive2Id = recursive2Id

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive1 = Recursives.findOne @recursive1Id,
@@ -1145,7 +1145,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive1 = Recursives.findOne @recursive1Id,
@@ -1174,7 +1174,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive1 = Recursives.findOne @recursive1Id,
@@ -1204,7 +1204,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive1 = Recursives.findOne @recursive1Id,
@@ -1234,7 +1234,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive1 = Recursives.findOne @recursive1Id,
@@ -1260,7 +1260,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive1 = Recursives.findOne @recursive1Id,
@@ -1288,7 +1288,7 @@
         @recursiveId = recursiveId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive = Recursives.findOne @recursiveId,
@@ -1309,7 +1309,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive = Recursives.findOne @recursiveId,
@@ -1331,7 +1331,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive = Recursives.findOne @recursiveId,
@@ -1349,7 +1349,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @recursive = Recursives.findOne @recursiveId,
@@ -1367,7 +1367,7 @@
         _id: 'nonexistent'

     # Sleep so that observers have time to update documents
-    Meteor._sleepForMs(500)
+    Meteor._sleepForMs(750)

     intercepted = Log._intercepted()

@@ -1384,7 +1384,7 @@
       subscribers: 'foobar'

     # Sleep so that observers have time to update documents
-    Meteor._sleepForMs(500)
+    Meteor._sleepForMs(750)

     intercepted = Log._intercepted()

@@ -1401,7 +1401,7 @@
       author: null

     # Sleep so that observers have time to update documents
-    Meteor._sleepForMs(500)
+    Meteor._sleepForMs(750)

     intercepted = Log._intercepted()

@@ -1418,7 +1418,7 @@
       user: null

     # Sleep so that observers have time to update documents
-    Meteor._sleepForMs(500)
+    Meteor._sleepForMs(750)

     intercepted = Log._intercepted()

@@ -1537,7 +1537,7 @@
         @postId = postId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -1600,7 +1600,7 @@
         @postLinkId = postLinkId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @postLink = PostLinks.findOne @postLinkId,
@@ -1631,7 +1631,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person2 = Persons.findOne @person2Id
@@ -1664,7 +1664,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @postLink = PostLinks.findOne @postLinkId,
@@ -1686,7 +1686,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @postLink = PostLinks.findOne @postLinkId,
@@ -1776,7 +1776,7 @@
         @postId = postId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -1840,7 +1840,7 @@
     # Sleep so that observers have time to update documents
     # so that persons updates are not merged together to better
     # test the code for multiple updates
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -1906,7 +1906,7 @@
     # Sleep so that observers have time to update documents
     # so that persons updates are not merged together to better
     # test the code for multiple updates
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
    (test, expect) ->
     @post = Posts.findOne @postId,
@@ -1972,7 +1972,7 @@
     # Sleep so that observers have time to update documents
     # so that persons updates are not merged together to better
     # test the code for multiple updates
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
    (test, expect) ->
     @post = Posts.findOne @postId,
@@ -2036,7 +2036,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -2095,7 +2095,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -2708,7 +2708,7 @@
         @postId = postId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -2841,7 +2841,7 @@
     # Sleep so that observers have time to update documents
     # so that persons updates are not merged together to better
     # test the code for multiple updates
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     Persons.update @person3Id,
@@ -2868,7 +2868,7 @@
     test.equal @person3.displayName, 'Person 3'

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -2991,7 +2991,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person1 = Persons.findOne @person1Id
@@ -3119,7 +3119,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person2 = Persons.findOne @person2Id
@@ -3237,7 +3237,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person3 = Persons.findOne @person3Id
@@ -3345,7 +3345,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person1 = Persons.findOne @person1Id
@@ -3454,7 +3454,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person2 = Persons.findOne @person2Id
@@ -3573,7 +3573,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @person3 = Persons.findOne @person3Id
@@ -3702,7 +3702,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -3825,7 +3825,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -3948,7 +3948,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4071,7 +4071,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4193,7 +4193,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4312,7 +4312,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4436,7 +4436,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4561,7 +4561,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4627,7 +4627,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4657,7 +4657,7 @@
         test.isFalse error, error?.toString?() or error

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     @post = Posts.findOne @postId,
@@ -4679,7 +4679,7 @@
         @identityGeneratorId = identityGeneratorId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     if Meteor.isServer
@@ -4715,7 +4715,7 @@
         test.isTrue res

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     if Meteor.isServer
@@ -4755,7 +4755,7 @@
         @identityGeneratorId = identityGeneratorId

     # Sleep so that observers have time to update documents
-    Meteor.setTimeout expect(), 500
+    Meteor.setTimeout expect(), 750
 ,
   (test, expect) ->
     if Meteor.isServer


Remove left over debug logging

Remove left over debug logging


Switched to moustache templates.

Switched to moustache templates.

--- js/binding/src/binding.coffee
+++ js/binding/src/binding.coffee
@@ -24,6 +24,13 @@
 ).call(this, jQuery))

 (((_, $, JEFRi)->
+	# Need to make some different assumptions about Templating
+	_.templateSettings = {
+		escape: /{{-([\s\S]+?)}}/g,
+		evaluate: /{{([\s\S]+?)}}/g,
+		interpolate: /{{=([\s\S]+?)}}/g
+	}
+
 	# Global Binding settings.
 	settings = {
 		paths : {


Fix OS-dependent library paths again

Fix OS-dependent library paths again

--- src/steps/path.coffee
+++ src/steps/path.coffee
@@ -18,9 +18,9 @@
         when 'ia32'
           "C:\\Program Files\\Steam"
     when 'darwin'
-      "~/Library/Application Support/Steam"
+      "#{process.env.HOME}/Library/Application Support/Steam"
     when 'linux'
-      "~/.local/share/Steam"
+      "#{process.env.HOME}/.local/share/Steam"
     else
       throw Error "Unsupported OS"


Put commands in the same order as the `commands` array

--- extension/packages/commands.coffee
+++ extension/packages/commands.coffee
@@ -12,11 +12,6 @@
 # “Selecting an element” means “focusing and selecting the text, if any, of an
 # element”.

-# Open and select the Developer Toolbar
-command_dev = (vim) ->
-  vim.rootWindow.DeveloperToolbar.show(true)
-  vim.rootWindow.DeveloperToolbar.focus()
-
 # Select the Address Bar
 command_focus = (vim) ->
   # This function works even if the Address Bar has been removed.
@@ -44,10 +39,6 @@
 command_paste_tab = (vim) ->
   { url, postData } = helper_paste(vim)
   vim.rootWindow.gBrowser.selectedTab = vim.rootWindow.gBrowser.addTab(url, null, null, postData, null, false)
-
-# Open a new tab and select the Address Bar
-command_open_tab = (vim) ->
-  vim.rootWindow.BrowserOpenTab()

 # Copy the URL or text of a marker element to the system clipboard
 command_marker_yank = (vim) ->
@@ -138,6 +129,10 @@
 command_scroll_page_up = (vim) ->
   utils.simulateWheel(vim.window, 0, -1, utils.WHEEL_MODE_PAGE)

+# Open a new tab and select the Address Bar
+command_open_tab = (vim) ->
+  vim.rootWindow.BrowserOpenTab()
+
 # Switch to the previous tab
 command_tab_prev = (vim) ->
   vim.rootWindow.gBrowser.tabContainer.advanceSelectedTab(-1, true) # `true` to allow wrapping
@@ -146,6 +141,22 @@
 command_tab_next = (vim) ->
   vim.rootWindow.gBrowser.tabContainer.advanceSelectedTab(1, true) # `true` to allow wrapping

+# Move the current tab backward
+command_tab_move_left = (vim) ->
+  { gBrowser } = vim.rootWindow
+  lastIndex = gBrowser.tabContainer.selectedIndex
+  gBrowser.moveTabBackward()
+  if gBrowser.tabContainer.selectedIndex == lastIndex
+    gBrowser.moveTabToEnd()
+
+# Move the current tab forward
+command_tab_move_right = (vim) ->
+  { gBrowser } = vim.rootWindow
+  lastIndex = gBrowser.tabContainer.selectedIndex
+  gBrowser.moveTabForward()
+  if gBrowser.tabContainer.selectedIndex == lastIndex
+    gBrowser.moveTabToStart()
+
 # Load the home page
 command_home = (vim) ->
   vim.rootWindow.BrowserHome()
@@ -157,14 +168,6 @@
 # Switch to the last tab
 command_tab_last = (vim) ->
   vim.rootWindow.gBrowser.selectTabAtIndex(-1)
-
-# Go back in history
-command_back = (vim) ->
-  vim.rootWindow.BrowserBack()
-
-# Go forward in history
-command_forward = (vim) ->
-  vim.rootWindow.BrowserForward()

 # Close current tab
 command_close_tab = (vim) ->
@@ -221,25 +224,13 @@
 command_go_to_root = (vim) ->
   vim.window.location.href = vim.window.location.origin

-# Move the current tab backward
-command_tab_move_left = (vim) ->
-  { gBrowser } = vim.rootWindow
-  lastIndex = gBrowser.tabContainer.selectedIndex
-  gBrowser.moveTabBackward()
-  if gBrowser.tabContainer.selectedIndex == lastIndex
-    gBrowser.moveTabToEnd()
-
-# Move the current tab forward
-command_tab_move_right = (vim) ->
-  { gBrowser } = vim.rootWindow
-  lastIndex = gBrowser.tabContainer.selectedIndex
-  gBrowser.moveTabForward()
-  if gBrowser.tabContainer.selectedIndex == lastIndex
-    gBrowser.moveTabToStart()
-
-# Display the Help Dialog
-command_help = (vim) ->
-  help.injectHelp(vim.window.document, commands)
+# Go back in history
+command_back = (vim) ->
+  vim.rootWindow.BrowserBack()
+
+# Go forward in history
+command_forward = (vim) ->
+  vim.rootWindow.BrowserForward()

 findStorage = { lastSearchString: '' }

@@ -268,6 +259,15 @@
 # Enter insert mode
 command_insert_mode = (vim) ->
   vim.enterMode('insert')
+
+# Display the Help Dialog
+command_help = (vim) ->
+  help.injectHelp(vim.window.document, commands)
+
+# Open and select the Developer Toolbar
+command_dev = (vim) ->
+  vim.rootWindow.DeveloperToolbar.show(true)
+  vim.rootWindow.DeveloperToolbar.focus()

 command_Esc = (vim, event) ->
   utils.blurActiveElement(vim.window)


Remove classes from buttons

Remove classes from buttons

The collapse class was causing the button to be very small since it
is defined in bootstrap with height 0.

--- src/packages/command-panel/lib/command-panel-view.coffee
+++ src/packages/command-panel/lib/command-panel-view.coffee
@@ -14,8 +14,8 @@
       @div class: 'loading is-loading', outlet: 'loadingMessage', 'Searching...'
       @div class: 'header', outlet: 'previewHeader', =>
         @ul outlet: 'expandCollapse', class: 'expand-collapse', =>
-          @li class: 'expand', 'Expand All'
-          @li class: 'collapse', 'Collapse All'
+          @li 'Expand All'
+          @li 'Collapse All'
         @span outlet: 'previewCount', class: 'preview-count'

       @subview 'previewList', new PreviewList(rootView)


Request the subscription count for change warning each time, don't cache

--- app/assets/javascripts/admin/order_cycles/directives/change-warning.js.coffee
+++ app/assets/javascripts/admin/order_cycles/directives/change-warning.js.coffee
@@ -4,16 +4,16 @@
     orderCycle: '=changeWarning'
   link: (scope, element, attrs) ->
     acknowledged = false
-    count = scope.orderCycle.subscriptions_count
     cancel = 'admin.order_cycles.date_warning.cancel'
     proceed = 'admin.order_cycles.date_warning.proceed'
-    msg = t('admin.order_cycles.date_warning.msg', n: count)
+    msg = 'admin.order_cycles.date_warning.msg'
     options = { cancel: t(cancel), confirm: t(proceed) }

     element.focus ->
+      count = scope.orderCycle.subscriptions_count
       return if acknowledged
       return if moment(scope.orderCycle.orders_close_at).isBefore()
       return if count < 1
-      ConfirmDialog.open('info', msg, options).then ->
+      ConfirmDialog.open('info', t(msg, n: count), options).then ->
         acknowledged = true
         element.siblings('img').trigger('click')


fix issue #374

fix issue #374

--- app/assets/javascripts/locomotive/views/editable_elements/file_view.js.coffee
+++ app/assets/javascripts/locomotive/views/editable_elements/file_view.js.coffee
@@ -38,7 +38,8 @@
       on_change: =>
         @$('a:first').hide() & @$('input[type=file]').show() & @$('a.delete').hide()
       on_cancel: =>
-        @$('a:first').show() & @$('input[type=file]').hide() & @$('a.delete').show()
+        @model.set(source: null)
+        @$('a:first').show() & @$('input[type=file]').val('').hide() & @$('a.delete').show()

   toggle_delete: (event) ->
     @_toggle event, 'delete',


fixed navbar search

fixed navbar search

--- manager/app/assets/javascripts/inventory/inventory.js.coffee
+++ manager/app/assets/javascripts/inventory/inventory.js.coffee
@@ -50,6 +50,11 @@

         @app.trigger("search:set_query", @query)

-        return super(data)
+        needed = super(data)
+        _.eachR @, needed, (n) ->
+          if n instanceof Bixby.model.HostList
+            n.query = @query
+
+        return needed

   )


Workspace: CM default mode is htmlmixed.

--- client/landing/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
@@ -61,6 +61,7 @@
   createEditor: ->
     @codeMirrorEditor = CodeMirror @container.getDomElement()[0],
       lineNumbers     : yes
+      mode            : "htmlmixed"
       extraKeys       :
         "Cmd-S"       : @bound "save"
         "Ctrl-S"      : @bound "save"


move status reporting into user model

move status reporting into user model

--- src/user/model.coffee
+++ src/user/model.coffee
@@ -28,8 +28,14 @@
     course = _.findWhere @_course_data, ecosystem_book_uuid: collectionUUID
     course and _.detect(course.roles, (role) -> role.type is 'teacher')

-  get: ->
-    @
+  status: (collectionUUID) ->
+    isLoggedIn: User.isLoggedIn()
+    isLoaded: User.isLoaded
+    isRegistrationPending: User._pendingRegistration?
+    isRegistered: @getCourse(collectionUUID)?.isRegistered()
+
+  getPendingRegistration: ->
+    @_pendingRegistration

   getCourse: (collectionUUID) ->
     _.findWhere( @courses, ecosystem_book_uuid: collectionUUID )


change name of board to fix case problem

--- scripts/pivotal-listener.coffee
+++ scripts/pivotal-listener.coffee
@@ -44,7 +44,7 @@
         "Salesforce": "SFDC-",
         "JAM": "JAM-",
         "Business As Usual": "BAU-",
-        "State Of The States 2019": "SOTS19-"
+        "State of the States 2019": "SOTS19-"

       project =
         projectId: reqBody["project"]["id"]


Remove uneeded initialization after ajax refresh.

--- app/assets/javascripts/followable.js.coffee
+++ app/assets/javascripts/followable.js.coffee
@@ -2,5 +2,3 @@

   update: (followable_id, button) ->
     $("#" + followable_id + " .js-follow").html(button)
-    # Temporary line. Waiting for issue resolution: https://github.com/consul/consul/issues/1736
-    initialize_modules()


fix js for icon...

fix js for icon...

broke due to styling/html modifications

--- app/assets/javascripts/pv_queries.js.coffee
+++ app/assets/javascripts/pv_queries.js.coffee
@@ -6,23 +6,22 @@
   #animate panel icon
   #TODO validate and prevent dud values from activating animation
   $('.bearing_input').change ->
-    #alert 'change'
     bearing = this.value - 45
-    centres_compass = -22
+    centres_compass = '-18px'
     #use compass div not panel div to apply each rotation in order to separate divs
-    icon = $(this).parent().find('.icon')
-    $(this).parent().find('.compass').css({
-      '-moz-transform':     'translateX(' + centres_compass + 'px)
-                            translateY(' + centres_compass + 'px)
+    icon = $(this).parents('.nested_fields').find('.icon')
+    icon.find('.compass').css({
+      '-moz-transform':     'translateX(' + centres_compass + ')
+                            translateY(' + centres_compass + ')
                             rotateZ(' + bearing + 'deg)',
-      '-webkit-transform':  'translateX(' + centres_compass + 'px)
-                            translateY(' + centres_compass + 'px)
+      '-webkit-transform':  'translateX(' + centres_compass + ')
+                            translateY(' + centres_compass + ')
                             rotateZ(' + bearing + 'deg)',
-      '-o-transform':       'translateX(' + centres_compass + 'px)
-                            translateY(' + centres_compass + 'px)
+      '-o-transform':       'translateX(' + centres_compass + ')
+                            translateY(' + centres_compass + ')
                             rotateZ(' + bearing + 'deg)',
-      'transform':          'translateX(' + centres_compass + 'px)
-                            translateY(' + centres_compass + 'px)
+      'transform':          'translateX(' + centres_compass + ')
+                            translateY(' + centres_compass + ')
                             rotateZ(' + bearing + 'deg)',
       'transition':         'transform 1s'
     })
@@ -33,7 +32,7 @@
   $('.tilt_input').change ->
     tilt = this.value
     input = this.id
-    icon = $('#' + input).parent().find('.icon')
+    icon = $('#' + input).parents().find('.icon')
     icon.find('.compass').children('.solar-panels').css({
       'transform-origin':   '0 top 0',
       '-moz-transform':     'rotateX(' + tilt + 'deg)',


Add documentation header, fix regex

Add documentation header, fix regex

The old regex `(went|go(ing|es))?` won't match on `go for it` and would match on ` for it`. This fixes that.

Also added a documentation header.

--- src/scripts/go-for-it.coffee
+++ src/scripts/go-for-it.coffee
@@ -1,3 +1,21 @@
+# Description:
+#   Display a random gopher
+#
+# Dependencies:
+#   None
+#
+# Configuration:
+#   HUBOT_SHIP_EXTRA_GOPHERS # Optional
+#
+# Commands:
+#   go for it
+#   went for it
+#   going for it
+#   goes for it
+#
+# Author:
+#   dylanegan
+
 gophers = [
   'http://25.media.tumblr.com/tumblr_m6k6iluYFU1qa4vxjo1_500.jpg'
   'http://www.sixprizes.com/wp-content/uploads/gopher_caddyshack.jpg'
@@ -12,9 +30,9 @@

   # Enable a looser regex if environment variable is set
   if process.env.HUBOT_SHIP_EXTRA_GOPHERS
-    regex = /(went|go(ing|es))? for it/i
+    regex = /(went|go(ing|es)?) for it/i
   else
     regex = /go for it/i

   robot.hear regex, (msg) ->
-    msg.send msg.random gophers+    msg.send msg.random gophers


Add missing HUBOT_PAGERDUTY_ENDPOINT to pagerduty

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -56,7 +56,7 @@
 pagerDutyScheduleId    = process.env.HUBOT_PAGERDUTY_SCHEDULE_ID
 pagerRoom              = process.env.HUBOT_PAGERDUTY_ROOM
 # Webhook listener endpoint. Set it to whatever URL you want, and make sure it matches your pagerduty service settings
-pagerEndpoint          = "/hook"
+pagerEndpoint          = process.env.HUBOT_PAGERDUTY_ENDPOINT || "/hook"

 module.exports = (robot) ->
   robot.respond /pager( me)?$/i, (msg) ->


bind estimationProcess to a dom element

bind estimationProcess to a dom element

--- app/coffee/modules/common/estimation.coffee
+++ app/coffee/modules/common/estimation.coffee
@@ -224,7 +224,12 @@
             @$el.find(".pop-points-open").show()

     create = ($el, us, project) ->
-        estimationProcess = new EstimationProcess($el, us, project)
+        estimationProcess = $el.data("estimationProcess")
+
+        if !estimationProcess
+            estimationProcess = new EstimationProcess($el, us, project)
+            $el.data("estimationProcess", estimationProcess)
+
         if estimationProcess.isEditable
             estimationProcess.bindClickEvents()
         else


use queue window for requests

use queue window for requests

--- src/virustotal.coffee
+++ src/virustotal.coffee
@@ -20,7 +20,7 @@
 virustotal = require 'virustotal.js'

 maxRequestsPerInterval = 4
-requestWindow = 1000
+requestWindowMs = 60 * 1000
 defaultwaitms = 1000

 # send queue
@@ -28,15 +28,25 @@

 queueUrlReport = (robot, msg, url) ->
   ms = Date.now()
+  if sq.length >= maxRequestsPerInterval
+    msgout = "Max requests per interval (#{maxRequestsPerInterval}) reached. Your request will be serviced as soon as possible.  Window + Queue: #{sq.length}."
+    robot.send {room: msg.envelope.user.name}, msgout
   sq.push { whenqueued: ms, sent: false, robot: robot, msg: msg, url: url }
+  # robot.logger.debug "sq: { whenqueued: #{ms}, url: #{url} }"

 servicequeue = ->
   ms = Date.now()
-  cutoffms = ms - (requestWindow * maxRequestsPerInterval)
+  cutoffms = ms - requestWindowMs

   # remove stale
-  q = sq
-  for o in q when o.whenqueued < cutoffms and o.sent
+  removequeue = []
+  for o in sq when o.whenqueued < cutoffms and o.sent
+    # o.robot.logger.debug "servicequeue: queueing stale object for removal: { whenqueued: #{o.whenqueued}, url: #{o.url}, sent: #{o.sent} }"
+    removequeue.push o
+
+  while removequeue.length > 0
+    o = removequeue.shift()
+    # o.robot.logger.debug "servicequeue: purging stale object: { whenqueued: #{o.whenqueued}, url: #{o.url}, sent: #{o.sent} }"
     sq.splice(sq.indexOf(o), 1)

   # count already sent objects, within the request window
@@ -45,6 +55,7 @@

   # send as many as we have credit for
   for o in sq when o.sent isnt true
+    # o.robot.logger.debug "servicequeue: credit check #{credit} before sending: { whenqueued: #{o.whenqueued}, url: #{o.url}, sent: #{o.sent} }"
     break if credit < 1
     getUrlReport o.robot, o.msg, o.url
     o.sent = true
@@ -54,6 +65,7 @@
   setTimeout servicequeue, defaultwaitms

 getUrlReport = (robot, msg, url) ->
+  # robot.logger.info "sq: sending virustotal request for #{url}"
   virustotal.getUrlReport url, (err, res) ->
     if err
       msgout = "Virus total: error: `#{err.json.verbose_msg}`."
@@ -77,6 +89,8 @@
   else
     virustotal.setKey(process.env.HUBOT_VIRUSTOTAL_API)

+  setTimeout servicequeue, defaultwaitms
+
   robot.respond /virustotal help$/, (msg) ->
     cmds = []
     arr = [
@@ -94,19 +108,4 @@

     robot.logger.info "Virus total: url report request: #{url} [#{msg.envelope.user.name}]"

-    virustotal.getUrlReport url, (err, res) ->
-      if err
-        msgout = "Virus total: error: `#{err.json.verbose_msg}`."
-        robot.logger.info "#{msgout} [#{msg.envelope.user.name}]"
-        return robot.send {room: msg.envelope.user.name}, msgout
-
-      r = { clean: 0, unclean: 0 }
-      for scanner, obj of res.scans
-        r.clean++ unless obj.detected
-        r.unclean++ if obj.detected
-
-      msgout = "Virus total: `#{res.resource.replace('http:\/\/','')}` rated clean by #{r.clean}, unclean by #{r.unclean}.  More info: #{res.permalink}"
-      robot.logger.info "#{msgout} [#{msg.envelope.user.name}]"
-      return robot.send {room: msg.envelope.user.name}, msgout
-
-    return
+    return queueUrlReport robot, msg, url


publish some settings to all site visitors

--- server/allow_and_startup.coffee
+++ server/allow_and_startup.coffee
@@ -8,7 +8,9 @@
       blurb: ""
     NeuronCatalogConfig.insert doc

-  # send relevant Meteor.settings
+  # Send relevant Meteor.settings.
+  # SECURITY NOTE: These are visible to all site visitors, even those
+  # not in a ReaderRole. So don't add any sensitive information here.
   SettingsToClient.update( {_id: 'settings'},
     {$set:
        specializations: Meteor.settings.NeuronCatalogSpecializations
@@ -23,7 +25,7 @@

 # ----------------------------------------
 Meteor.publish "settings_to_client", ->
-  SettingsToClient.find {} if Roles.userIsInRole(@userId, ReaderRoles)
+  SettingsToClient.find {}
 Meteor.publish "s3_config_status", ->
   S3ConfigStatus.find {} if Roles.userIsInRole(@userId, ReaderRoles)
 Meteor.publish "neuron_catalog_config", ->


Added populate for user reference into "comments.author"

--- server/controllers/deals.coffee
+++ server/controllers/deals.coffee
@@ -6,7 +6,7 @@
 User     = mongoose.model "User"

 exports.deal = (req, res, next, id) ->
-  Deal.findOne(_id: id).exec (err, deal) ->
+  Deal.findOne(_id: id).populate("comments.author").exec (err, deal) ->
     return next(err)  if err
     return next(new Error('Failed to load deal ' + id))  unless deal
     req.deal = deal
@@ -17,13 +17,6 @@
     return next(err)  if err
     return next(new Error('Failed to load shop ' + id))  unless shop
     req.shop = shop
-    next()
-
-exports.user = (req, res, next, id) ->
-  User.findOne(_id: id).exec (err, shop) ->
-    return next(err)  if err
-    return next(new Error('Failed to load user ' + id))  unless user
-    req.user = user
     next()

 exports.create = (req, res) ->


hpub-gen

hpub-gen

--- components/backend/modules/magazine/generators/HpubGenerator.coffee
+++ components/backend/modules/magazine/generators/HpubGenerator.coffee
@@ -23,6 +23,7 @@
       return console.log err if err
       _.each files, (file) ->
         fileName = file.fields.title.value
+        console.log "filename: " + fileName
         if file.fields.fieldrelation.value is "cover"
           fs.copy process.cwd() + "/public/files/" + fileName, process.cwd() + "/public/books/" + title + "/hpub/images/cover.png"
         else if file.fields.fieldrelation.value is "back"
@@ -32,7 +33,6 @@

       # generage INDEX
       Page.find("fields.relation.value": id).exec (err, pages) ->
-
         articleIds = []

         _.each pages, (page) ->
@@ -44,18 +44,19 @@
           return 0

-        Article.find(_id: $in: articleIds).execFind (err, articles) ->
+        Article.find(id: $in: articleIds).execFind (err, articles) ->
           newarticles = {}
           _.each articles, (article) ->
-            newarticles[article._id] = {}
-            newarticles[article._id].title = article.fields.title.value
-            File.find("fields.relation.value": article._id.toString()).execFind (err, files) ->
+            articleId = article._id.toString()
+            newarticles[articleId] = {}
+            newarticles[articleId].title = article.fields.title.value
+            File.find("fields.relation.value": articleId).execFind (err, files) ->
               _.each files, (file) ->
                 if file.fields.fieldrelation.value is "teaserimage"
                   console.log "found teaserImage"
                   fs.copySync process.cwd() + "/public/files/" + file.fields.title.value, process.cwd() + "/public/books/" + title + "/hpub/images/" + file.fields.title.value
-                  newarticles[article._id].teaserimage = file.fields.title.value || null
-
+                  newarticles[articleId].teaserimage = file.fields.title.value || null
+                else console.log("Another image")
               # generage index for baker navigation
               template = fs.readFileSync("./components/magazine/" + theme + "/index.html", "utf8")
               fs.writeFileSync "./public/books/" + title + "/hpub/index.html", ejs.render template,
@@ -142,14 +143,19 @@
         _.each pages, (page) ->
           pagelayout = page.fields.layout.value || "default"
           theme = page.fields.theme?.value || "default"
-          return unless pagelayout
+          #return unless pagelayout
           template = fs.readFileSync("./components/magazine/" + theme + "/pages/" + (pagelayout).trim() + ".html", "utf8")
+          if !page.fields.article.value? then return console.log "page.fields :"; console.log page.fields
+
           Article.findOne(_id: page.fields.article.value).exec (err, article) ->
+
             if err then return console.log err
             articleId = article._id.toString()
             File.find("fields.relation.value": articleId).execFind (err, files) ->
               articlefiles = []
               _.each files, (file) ->
+                console.log "file.fields"
+                console.log file.fields
                 if file.fields.fieldrelation.value isnt "teaserimage"
                   fs.copySync process.cwd() + "/public/files/" + file.fields.title.value, process.cwd() + "/public/books/" + title + "/hpub/images/" + file.fields.title.value
                   articlefiles.push file.fields.title.value


Fixed one more leak.

Fixed one more leak.

--- app/views/play/level/tome/spell_view.coffee
+++ app/views/play/level/tome/spell_view.coffee
@@ -74,14 +74,15 @@
     $(@ace.container).find('.ace_gutter').on 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick

   createACEShortcuts: ->
-    @aceCommands = []
-    addCommand = (c) =>
-      @ace.commands.addCommand c
-      @aceCommands.push c.name
+    @aceCommands = aceCommands = []
+    ace = @ace
+    addCommand = (c) ->
+      ace.commands.addCommand c
+      aceCommands.push c.name
     addCommand
       name: 'run-code'
       bindKey: {win: 'Shift-Enter|Ctrl-Enter|Ctrl-S', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter|Command-S|Ctrl-S'}
-      exec: (e) => @recompile()
+      exec: -> Backbone.Mediator.publish 'tome:manual-cast', {}
     addCommand
       name: 'toggle-playing'
       bindKey: {win: 'Ctrl-P', mac: 'Command-P|Ctrl-P'}
@@ -219,7 +220,7 @@
   recompileIfNeeded: =>
     @recompile() if @recompileNeeded

-  recompile: (cast=true) =>
+  recompile: (cast=true) ->
     @setRecompileNeeded false
     return if @spell.source is @getSource()
     @spell.transpile @getSource()
@@ -519,5 +520,5 @@
     @debugView?.destroy()
     @spell = null
     @session.off 'change:multiplayer', @onMultiplayerChanged, @
-    for fat in ['notifySpellChanged', 'notifyEditingEnded', 'notifyEditingBegan', 'onFirepadLoaded', 'onLoaded', 'recompile', 'toggleBackground', 'setRecompileNeeded', 'onCursorActivity', 'highlightCurrentLine', 'updateAether', 'onCodeChangeMetaHandler', 'recompileIfNeeded', 'currentAutocastHandler']
+    for fat in ['notifySpellChanged', 'notifyEditingEnded', 'notifyEditingBegan', 'onFirepadLoaded', 'onLoaded', 'toggleBackground', 'setRecompileNeeded', 'onCursorActivity', 'highlightCurrentLine', 'updateAether', 'onCodeChangeMetaHandler', 'recompileIfNeeded', 'currentAutocastHandler']
       @[fat] = null


textwrap clears out container if it's too small

--- src/textwrap/textwrap.coffee
+++ src/textwrap/textwrap.coffee
@@ -18,6 +18,8 @@
         if vars.size.value[0] <= vars.height.inner
           text vars
           wrap vars
+        else
+          vars.container.value.html(false)

         print.timeEnd "total draw time" if vars.dev.value


bubble group text now resizes

bubble group text now resizes

--- src/viz/types/bubbles.coffee
+++ src/viz/types/bubbles.coffee
@@ -101,7 +101,7 @@
         w:       size_max * 2
         h:       labelHeight - yMod
         padding: 0
-        resize:  false
+        resize:  true
         color:   legible(fetchColor(vars, d, d.d3plus.depth))
         force:   true
     else


bug in pagination on media and organization page when using filter fixed

--- packages/custom/transparency/coffee/public/controllers/listControllers.coffee
+++ packages/custom/transparency/coffee/public/controllers/listControllers.coffee
@@ -55,7 +55,7 @@
         if newValue isnt oldValue
             update()
     updatePage = ->
-        $scope.items = $scope.filterResult[$scope.page-1..$scope.page-1+$scope.size]
+        $scope.items = $scope.filterResult[($scope.page-1)*$scope.size..($scope.page-1)*$scope.size+$scope.size]
     applyFilter = ->
         $scope.filterResult = $scope.searchResult.filter (e) -> e.name.toLowerCase().indexOf($scope.name.toLowerCase()) > -1
         $scope.count = $scope.filterResult.length
@@ -147,7 +147,7 @@
         if newValue isnt oldValue
             update()
     updatePage = ->
-        $scope.items = $scope.filterResult[$scope.page-1..$scope.page-1+$scope.size]
+        $scope.items = $scope.filterResult[($scope.page-1)*$scope.size..($scope.page-1)*$scope.size+$scope.size]
     applyFilter = ->
         $scope.filterResult = $scope.searchResult.filter (e) -> e.name.toLowerCase().indexOf($scope.name.toLowerCase()) > -1
         $scope.count = $scope.filterResult.length


Teambuilder: Fixed selecting moves by clicking

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -285,7 +285,7 @@
     moveName = $this.data('move-id')
     $moves = @getActivePokemonView().find('.selected_moves')
     $input = $moves.find('input:focus').first()
-    $input ||= $moves.find('input').first()
+    $input = $moves.find('input').first()  if $input.length == 0
     return  if $input.length == 0
     @insertMove($input, moveName)


Bugfix: Suggested tags now appear even when filter is ''.

--- client/coffeescript/templates/tagbox.coffee
+++ client/coffeescript/templates/tagbox.coffee
@@ -48,9 +48,16 @@

   suggested_tags: ->
     filtered = []
-    for tag in @suggested_tags
-      if tag? and tag.indexOf(Session.get('filter_text')) != -1
-        filtered.push(tag)
+    filter = Session.get('filter_text')
+    filter ?= ''
+    if filter is ''
+      for tag in @suggested_tags
+        if tag?
+          filtered.push(tag)
+    else
+      for tag in @suggested_tags
+        if tag? and tag.indexOf(filter) != -1
+          filtered.push(tag)
     return filtered

   tagging_post: ->


find OnboardingSuccessModalShown from storage instead fetching Value

--- client/home/lib/welcome/welcomemodal.coffee
+++ client/home/lib/welcome/welcomemodal.coffee
@@ -31,32 +31,33 @@
   showCongratulationModal: ->

     { appStorageController } = kd.singletons
+    appStorage = appStorageController.storage "WelcomeSteps-#{globals.currentGroup.slug}"
+    buckets = appStorage.storage.bucket
+    welcomeStepsStore = buckets["WelcomeSteps-#{globals.currentGroup.slug}"]
+    isShown = welcomeStepsStore.data.OnboardingSuccessModalShown

-    appStorage = appStorageController.storage "WelcomeSteps-#{globals.currentGroup.slug}"
-    appStorage.fetchValue 'OnboardingSuccessModalShown', (result) ->
+    return  if isShown

-      return  if result
+    appStorage.setValue 'OnboardingSuccessModalShown', yes

-      appStorage.setValue 'OnboardingSuccessModalShown', yes
-
-      modal = new kd.ModalView
-        title : 'Success!'
-        width : 530
-        cssClass : 'Congratulation-modal'
-        content : "
Congratulations. You have completed all items on your onboarding list.
"
+    modal = new kd.ModalView
+      title : 'Success!'
+      width : 530
+      cssClass : 'Congratulation-modal'
+      content : "
Congratulations. You have completed all items on your onboarding list.
"

-      kd.View::addSubView.call modal, new kd.CustomHTMLView
-        cssClass : 'alien'
+    kd.View::addSubView.call modal, new kd.CustomHTMLView
+      cssClass : 'alien'

-      modal.addSubView new kd.CustomHTMLView
-        cssClass : 'image-wrapper'
+    modal.addSubView new kd.CustomHTMLView
+      cssClass : 'image-wrapper'

-      modal.addSubView new kd.ButtonView
-        title : 'KEEP ROCKING!'
-        cssClass: 'GenericButton'
-        callback : -> modal.destroy()
+    modal.addSubView new kd.ButtonView
+      title : 'KEEP ROCKING!'
+      cssClass: 'GenericButton'
+      callback : -> modal.destroy()

   initialShow: ->


Fix base64 UTF-8 Decode & Encode issue

Fix base64 UTF-8 Decode & Encode issue

--- client/landing/app/MainApp/fs/fsfile.coffee
+++ client/landing/app/MainApp/fs/fsfile.coffee
@@ -23,7 +23,7 @@
         {content} = response

         # Convert to String
-        content = atob content
+        content = KD.utils.utf8Decode atob content

       callback.call @, err, content
       @emit "fs.job.finished", err, content
@@ -53,7 +53,7 @@
     @emit "fs.save.started"

     # Convert to base64
-    content = btoa contents
+    content = btoa KD.utils.utf8Encode contents

     @kiteController.run
       kiteName  : 'os'


Use local erlang broker

Use local erlang broker

--- client/landing/app/MainApp/mq.config.coffee
+++ client/landing/app/MainApp/mq.config.coffee
@@ -1,6 +1,7 @@
 BONGO_MQ = do->
   options = {
     encrypted: yes
+    sockURL: 'http://localhost:8008/subscribe'
   }
   switch KD.env
     when 'beta'


Fixed can't expand chip in tree bug

Fixed can't expand chip in tree bug

--- client/src/app/game/board/boardNodes.coffee
+++ client/src/app/game/board/boardNodes.coffee
@@ -11,9 +11,10 @@

   WillChangeLocalVersionEvent.listen -> updateChipPaths()

-  # TODO: change on circuit change as well
-
-  chipHashKeyToPath = {}
+  # If the chip already has a hash key, return it. Else create a new one
+  getOrMakeHashKey = (chip) ->
+    if "$$hashKey" not of chip then chip.$$hashKey = _.uniqueId("hash")
+    return chip.$$hashKey

   # Create map of chips to their paths
   updateChipPaths = ->
@@ -35,15 +36,13 @@
   isOpen = (node) ->
     if not node then return false
     else if node is chips.getCurrentBoard() then return true # Root node is always open
-    else return openNodeKeys[node.$$hashKey] is true
+    else return openNodeKeys[getOrMakeHashKey(node)] is true

   open = (node) ->
-    if node.$$hashKey
-      openNodeKeys[node.$$hashKey] = true
+    openNodeKeys[getOrMakeHashKey(node)] = true

   close = (node) ->
-    if node.$$hashKey
-      openNodeKeys[node.$$hashKey] = false
+    openNodeKeys[getOrMakeHashKey(node)] = false

   labelClicked: (node) =>
     return if node is chips.getCurrentBoard() # Ignore clicks on root node


add back to past course link

add back to past course link

--- coach/src/course/enrollment-code-input.cjsx
+++ coach/src/course/enrollment-code-input.cjsx
@@ -6,6 +6,8 @@
 Course = require './model'
 {AsyncButton, MessageList} = require 'shared'
 User = require '../user/model'
+Navigation = require '../navigation/model'
+

 EnrollmentCodeInput = React.createClass
   displayName: 'EnrollmentCodeInput'
@@ -21,19 +23,31 @@
     return if @props.course.isBusy # double enter
     @startRegistration() if ev.key is ENTER

+  isSecondSemester: ->
+    @props.secondSemester and not @props.isTeacher
+
   renderTitle: ->
-    if @props.secondSemester and not @props.isTeacher
-

+    if @isSecondSemester()
+

A New Semester, A New Enrollment Code

-          Concept Coach requires a new enrollment code each semester. If you’re
-          in the second semester of a two-semester course, get the new code from your
-          instructor and enter it below.
+          Concept Coach requires a new enrollment code each semester.
+          If you’re in the second semester of a two-semester course,
+          get the new code from your instructor and enter it below.

     else
       title = if @props.isTeacher then '' else 'Register for this Concept Coach course'

{title}

+
+  PastCourseLink: ->
+    return null unless @isSecondSemester()
+    <BS.Button bsStyle='link' onClick={@returnToPastCourse}>
+      Return to my past Concept Coach course
+    </BS.Button>
+
+  returnToPastCourse: ->
+    Navigation.channel.emit('show.panel', view: 'task')

   render: ->

@@ -63,6 +77,7 @@
             </BS.InputGroup.Button>
           </BS.InputGroup>
         </BS.FormGroup>
+        <@PastCourseLink />


[api] methods from Matador: delayTimeForKeys, queues

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -249,5 +249,49 @@
     key.progress = results[i] for key, i in keys
     callback null, keys

+  delayTimeForKeys: (keys, callback) ->
+    ideally = errify callback
+    multi   = for key in keys
+      ["zscore", "bull:#{key.type}:delayed", key.id]
+
+    await (@redis.multi multi).exec ideally defer results
+    for key, i in keys
+      # Bull packs delay expire timestamp and job id into a single number. This is mostly
+      # needed to preserve execution order – first part of the resulting number contains
+      # the timestamp and the end contains the incrementing job id. We don't care about
+      # the id, so we can just remove this part from the value.
+      # https://github.com/OptimalBits/bull/blob/e38b2d70de1892a2c7f45a1fed243e76fd91cfd2/lib/scripts.js#L90
+      key.delayUntil = new Date Math.floor results[i]/0x1000
+
+    callback null, keys
+
+  queues: (callback) ->
+    ideally = errify callback
+
+    await @redis.keys "bull:*:id", ideally defer queues
+    for queue in queues
+      name = queue[..-3]
+
+      await @redis.lrange name + ":active", 0, -1, ideally defer allActive
+      active  = []
+      stalled = []
+      for job in allActive
+        await @redis.get "#{name}:#{job}:lock" ideally defer lock
+        if lock? then active.push  job
+        else          stalled.push job
+
+      await @redis.llen  "#{name}:wait",      ideally defer pending
+      await @redis.zcard "#{name}:delayed",   ideally defer delayed
+      await @redis.scard "#{name}:completed", ideally defer completed
+      await @redis.scard "#{name}:failed",    ideally defer failed
+
+      callback null,
+        name:      name[5..]
+        active:    active.length
+        stalled:   stalled.length
+        pending:   pending
+        delayed:   delayed
+        completed: completed
+        failed:    failed

 module.exports = RedisModel


Add TextBuffer::updateCachedDiskContentsAsync

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -72,10 +72,10 @@
     @text.clearUndoStack()

   loadAsync: ->
-    @updateCachedDiskContents()
-    @reload() if @loadFromDisk and @isModified()
-    @text.clearUndoStack()
-    Q(this)
+    @updateCachedDiskContentsAsync().then ->
+      @reload() if @loadFromDisk and @isModified()
+      @text.clearUndoStack()
+      Q(this)

   ### Internal ###

@@ -117,16 +117,15 @@
   subscribeToFile: ->
     @file.on "contents-changed", =>
       @conflict = true if @isModified()
-      @updateCachedDiskContents()
-
-      if @conflict
-        @trigger "contents-conflicted"
-      else
-        @reload()
+      @updateCachedDiskContentsAsync().done =>
+        if @conflict
+          @trigger "contents-conflicted"
+        else
+          @reload()

     @file.on "removed", =>
-      @updateCachedDiskContents()
-      @triggerModifiedStatusChanged(@isModified())
+      @updateCachedDiskContents().done =>
+        @triggerModifiedStatusChanged(@isModified())

     @file.on "moved", =>
       @state.set('relativePath', @project.relativize(@getPath()))
@@ -150,11 +149,14 @@
     @triggerModifiedStatusChanged(false)
     @trigger 'reloaded'

-  # Rereads the contents of the file, and stores them in the cache.
-  #
-  # Essentially, this performs a force read of the file on disk.
+  # Private: Rereads the contents of the file, and stores them in the cache.
   updateCachedDiskContents: ->
     @cachedDiskContents = @file?.read() ? ""
+
+  # Private: Rereads the contents of the file, and stores them in the cache.
+  updateCachedDiskContentsAsync: ->
+    Q(@file.read() ? "").then (contents) ->
+      @cachedDiskContents = contents

   # Gets the file's basename--that is, the file without any directory information.
   #


:art: Better name for text

:art: Better name for text

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -946,9 +946,9 @@
         if charToLeftOfSelection.start.column < 0
           translatedRanges.push(selection)
         else
-          text = @buffer.getTextInRange(charToLeftOfSelection)
-
-          @buffer.insert(selection.end, text)
+          charTextToLeftOfSelection = @buffer.getTextInRange(charToLeftOfSelection)
+
+          @buffer.insert(selection.end, charTextToLeftOfSelection)
           @buffer.delete(charToLeftOfSelection)
           translatedRanges.push(selection.translate(translationDelta))

@@ -968,10 +968,10 @@
         if charToRightOfSelection.end.column > @buffer.lineLengthForRow(charToRightOfSelection.end.row)
           translatedRanges.push(selection)
         else
-          text = @buffer.getTextInRange(charToRightOfSelection)
+          charTextToRightOfSelection = @buffer.getTextInRange(charToRightOfSelection)

           @buffer.delete(charToRightOfSelection)
-          @buffer.insert(selection.start, text)
+          @buffer.insert(selection.start, charTextToRightOfSelection)
           translatedRanges.push(selection.translate(translationDelta))

       @setSelectedBufferRanges(translatedRanges)


Updated Elyssa.Color

Updated Elyssa.Color

* #toHex() returns three-character long hex values if it can
* fadeIn and fadeOut are more precise

--- src/types/color.coffee
+++ src/types/color.coffee
@@ -53,7 +53,10 @@
       tmpG = "0" + tmpG if tmpG.length is 1
       tmpB = "0" + tmpB if tmpB.length is 1

-      "##{tmpR}#{tmpG}#{tmpB}"
+      if tmpR[0] is tmpR[1] and tmpG[0] is tmpG[1] and tmpB[0] is tmpB[1]
+        "##{tmpR[0]}#{tmpG[0]}#{tmpB[0]}"
+      else
+        "##{tmpR}#{tmpG}#{tmpB}"

     # Lighten the color
     #
@@ -86,16 +89,20 @@
     fadeIn: (factor) ->
       factor = Elyssa.Math.clamp(factor)

-      delta = ~~(colorMax * factor)
+      delta = Math.round(colorMax * factor)
       @a += delta
+
+      @a = Elyssa.Math.clamp(@a, 0, colorMax)

       @

     fadeOut: (factor) ->
       factor = Elyssa.Math.clamp(factor)

-      delta = ~~(colorMax * factor)
+      delta = Math.round(colorMax * factor)
       @a -= delta
+
+      @a = Elyssa.Math.clamp(@a, 0, colorMax)

       @


cleaner coffeescript

cleaner coffeescript

--- resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
+++ resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
@@ -54,7 +54,7 @@
             # different beatmapset, format: 1234#567
             linkText = "#{targetBeatmapsetDiscussions.beatmapsetId}##{targetBeatmapsetDiscussions.discussionId}"

-      linkText = linkText ? displayUrl
+      linkText ?= displayUrl

       "#{linkText ? url}"


unneeded

unneeded

--- resources/assets/coffee/react/_components/comments-manager.coffee
+++ resources/assets/coffee/react/_components/comments-manager.coffee
@@ -99,25 +99,6 @@
       userStore.updateWithJSON users

-  mergeCollection: (array, values, prepend) =>
-    result = osu.jsonClone array
-    prepend ?= false
-
-    method = if prepend then 'unshift' else 'push'
-
-    for item in values
-      continue unless item?
-
-      pos = _.findIndex result, (i) -> i.id == item.id
-
-      if pos == -1
-        result[method] item
-      else
-        result[pos] = item
-
-    result
-
-
   jsonStorageId: =>
     "json-comments-manager-state-#{@props.commentableType}-#{@props.commentableId}"


allow negative values for synctex positions

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -151,9 +151,9 @@
 		{page, h, v} = req.query
 		if not page?.match(/^\d+$/)
 			return next(new Error("invalid page parameter"))
-		if not h?.match(/^\d+\.\d+$/)
+		if not h?.match(/^-?\d+\.\d+$/)
 			return next(new Error("invalid h parameter"))
-		if not v?.match(/^\d+\.\d+$/)
+		if not v?.match(/^-?\d+\.\d+$/)
 			return next(new Error("invalid v parameter"))
 		# whether this request is going to a per-user container
 		CompileController._compileAsUser req, (error, user_id) ->


cleaned up and fixed problem with open user opening pojrect

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -78,33 +78,39 @@
 				timer.done()

-	loadEditor: (req, res)->
+	loadEditor: (req, res, next)->
 		timer = new metrics.Timer("load-editor")
 		if !Settings.editorIsOpen
 			return res.render("general/closed", {title:"updating site"})

-		user_id = if req.session.user? then req.session.user._id else 'openUser'
+		if req.session.user?
+			user_id = req.session.user._id
+			anonymous = false
+		else
+			anonymous = true
+			user_id = 'openUser'
+
 		project_id = req.params.Project_id
-
+
 		async.parallel {
 			project: (cb)->
 				Project.findPopulatedById project_id, cb
 			user: (cb)->
-				User.findById user_id, cb
+				if user_id == 'openUser'
+					cb null, defaultSettingsForAnonymousUser(user_id)
+				else
+					User.findById user_id, cb
 			subscription: (cb)->
+				if user_id == 'openUser'
+					return cb()
 				SubscriptionLocator.getUsersSubscription user_id, cb
 		}, (err, results)->
 			if err?
-				return res.send 500
+				logger.err err:err, "error getting details for project page"
+				return next err
 			project = results.project
 			user = results.user
 			subscription = results.subscription
-
-			if user_id == 'openUser'
-				anonymous = true
-				user = defaultSettingsForAnonymousUser(user_id)
-			else
-				anonymous = false

 			SecurityManager.userCanAccessProject user, project, (canAccess, privilegeLevel)->
 				if !canAccess
@@ -150,7 +156,7 @@
 					timer.done()

-defaultSettingsForAnonymousUser = ->
+defaultSettingsForAnonymousUser = (user_id)->
 	id : user_id
 	ace:
 		mode:'none'


chore: android testing omitted

chore: android testing omitted

--- testSpecsApp/test/unit/deviceSpec.coffee
+++ testSpecsApp/test/unit/deviceSpec.coffee
@@ -34,8 +34,10 @@
         waitsFor -> name?

         runs ->
-          if navigator.userAgent.match(/(iPod|iPhone|iPad)/)
+          if not navigator.userAgent.match(/(iPod|iPhone|iPad)/)
             expect( name ).toBe("ios")
+          else
+            expect( "you" ).toBe("fixing this test for that device you are using")

     describe "torch", ->


Use TextEditor::moveToFirstCharacterOfLine

--- lib/fuzzy-finder-view.coffee
+++ lib/fuzzy-finder-view.coffee
@@ -72,7 +72,7 @@
       position = new Point(lineNumber)
       textEditor.scrollToBufferPosition(position, center: true)
       textEditor.setCursorBufferPosition(position)
-      textEditor.moveCursorToFirstCharacterOfLine()
+      textEditor.moveToFirstCharacterOfLine()

   splitOpenPath: (fn) ->
     {filePath} = @getSelectedItem() ? {}


use natural sizes instead

use natural sizes instead

--- lib/image-editor-view.coffee
+++ lib/image-editor-view.coffee
@@ -47,8 +47,8 @@
       'image-view:reset-zoom': => @resetZoom()

     @image.load =>
-      @originalHeight = @image.height()
-      @originalWidth = @image.width()
+      @originalHeight = @image.prop('naturalHeight')
+      @originalWidth = @image.prop('naturalWidth')
       @loaded = true
       @image.show()
       @emitter.emit 'did-load'


activate previous pane after commit window disappears

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -63,6 +63,7 @@
         else
           atom.workspace.destroyActivePane()
         atom.project.getRepo()?.refreshStatus()
+        @currentPane.activate()

   undoAmend: ->
     git.cmd


Fix git-commit-amend

Fix git-commit-amend

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -28,7 +28,14 @@

   constructor: (@amend='') ->
     super
+
+    # This prevents atom from creating more than one Editor to edit the commit
+    # message.
     return if @assignId() isnt 1
+
+    # This sets @isAmending to check if we are amending right now.
+    @isAmending = @amend.length > 0
+
     git.stagedFiles (files) =>
       if @amend isnt '' or files.length >= 1
         git.cmd
@@ -59,31 +66,54 @@
         @subscribe buffer, 'saved', =>
           @commit()
         @subscribe buffer, 'destroyed', =>
-          if @amend is '' then @cleanup() else @undoAmend()
+          if @isAmending then @undoAmend() else @cleanup()

   commit: ->
     args = ['commit', '--cleanup=strip', "--file=#{@filePath()}"]
-    args.push '--amend' if @amend isnt ''
-    @amend = ''
     git.cmd
       args: args,
       options:
         cwd: @dir()
       stdout: (data) =>
         new StatusView(type: 'success', message: data)
-        if atom.workspace.getActivePane().getItems().length > 1
-          atom.workspace.destroyActivePaneItem()
-        else
-          atom.workspace.destroyActivePane()
+        # Set @isAmending to false since it succeeded.
+        @isAmending = false
+
+        # Destroying the active EditorView will trigger our cleanup method.
+        @destroyActiveEditorView()
+
+        # Refreshing the atom repo status to refresh things like TreeView and
+        # diff gutter.
         atom.project.getRepo()?.refreshStatus()
+
+        # Activate the former active pane.
         @currentPane.activate()

-  undoAmend: ->
+        # Refresh git index to prevent bugs on our methods.
+        git.refresh()
+
+      stderr: (err) =>
+        if @isAmending then @undoAmend() else @cleanup()
+
+  destroyActiveEditorView: ->
+    if atom.workspace.getActivePane().getItems().length > 1
+      atom.workspace.destroyActivePaneItem()
+    else
+      atom.workspace.destroyActivePane()
+
+  undoAmend: (err='') ->
     git.cmd
-      args: ['reset', 'HEAD@{1}'],
-      stdout: =>
-        new StatusView(type: 'error', message: 'Commit amend aborted!')
-        @cleanup()
+      args: ['reset', 'ORIG_HEAD'],
+      stdout: ->
+        new StatusView(type: 'error', message: "#{err+': '}Commit amend aborted!")
+      stderr: ->
+        new StatusView(type: 'error', message: 'ERROR! Undoing the amend failed! Please fix your repository manually!')
+      exit: =>
+        # Set @isAmending to false since the amending process has been aborted.
+        @isAmending = false
+
+        # Destroying the active EditorView will trigger our cleanup method.
+        @destroyActiveEditorView()

   cleanup: ->
     Model.resetNextInstanceId()


Pagination helper mods

Pagination helper mods

--- lib/pagination_helper.coffee
+++ lib/pagination_helper.coffee
@@ -3,7 +3,7 @@

   constructor: (page) ->
     @page = if page then parseInt(page) else 1
-    @limit = 1
+    @limit = 1

   render: (model, query, cb) =>
     self = @
@@ -32,12 +32,7 @@

       prev_page = if self.page == 1 then 1 else self.page - 1

-      next_page = (->
-        if self.page == pages
-          pages
-        else
-          self.page + 1
-      )()
+      next_page = if self.page == pages then pages else self.page + 1

       cb({num_pages: pages, current_page: self.page, prev_class: prev_class, next_class: next_class, prev_page: prev_page, next_page: next_page})


Fixed Win7 completer bug.

Fixed Win7 completer bug.

Autocompletes were causing node-shell to crash.

--- lib/plugins/completer.coffee
+++ lib/plugins/completer.coffee
@@ -1,26 +1,26 @@
-
-###
-
-Completer plugin
-================
-
-Provides tab completion. Options passed during creation are:
-
--   `shell`    , (required) A reference to your shell application.
-
-###
-module.exports = (settings) ->
-    # Validation
-    throw new Error 'No shell provided' if not settings.shell
-    shell = settings.shell
-    # Plug completer to interface
-    return unless shell.isShell
-    shell.interface().completer = (text) ->
-        suggestions = []
-        routes = shell.routes
-        for route in routes
-            command = route.command
-            if command.substr(0, text.length) is text
-                suggestions.push command
-        [suggestions, text]
-    null
+
+###
+
+Completer plugin
+================
+
+Provides tab completion. Options passed during creation are:
+
+-   `shell`    , (required) A reference to your shell application.
+
+###
+module.exports = (settings) ->
+    # Validation
+    throw new Error 'No shell provided' if not settings.shell
+    shell = settings.shell
+    # Plug completer to interface
+    return unless shell.isShell
+    shell.interface().completer = (text, cb) ->
+        suggestions = []
+        routes = shell.routes
+        for route in routes
+            command = route.command
+            if command.substr(0, text.length) is text
+                suggestions.push command
+        cb(false, [suggestions, text])
+    null


Support focus-next/previous in paths editor

Closes #28

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -128,7 +128,7 @@
     @lastFocusedElement = $(element)

   focusNextElement: (direction) ->
-    elements = [@resultsView, @findEditor, @replaceEditor].filter (el) -> el.has(':visible').length > 0
+    elements = [@resultsView, @findEditor, @replaceEditor, @pathsEditor].filter (el) -> el.has(':visible').length > 0
     focusedElement = _.find elements, (el) -> el.has(':focus').length > 0 or el.is(':focus')
     focusedIndex = elements.indexOf focusedElement


plz gimme a type system

plz gimme a type system

--- lib/view/input-method.coffee
+++ lib/view/input-method.coffee
@@ -31,7 +31,6 @@
             else
                 []

-
         selectedLeft:   -> _.take(@partialCandidateSymbols, @n)
-        selected:       -> [@partialCandidateSymbols[@n]]
+        selected:       -> _.compact(@partialCandidateSymbols[@n])
         selectedRight:  -> _.drop(@partialCandidateSymbols, @n + 1)


Async member list rendering

Async member list rendering

--- static/js/member-list.coffee
+++ static/js/member-list.coffee
@@ -9,15 +9,47 @@
         @el = @$el[0]
         @_is_rendered = true

-    render: =>
+    render: (sort) =>
+        hits = @$el.find(".statistics-bars > *").addClass "display-none"
+        .filter(".#{sort}-statistics-bar").removeClass "display-none"
+        if hits.length == 0
+            @$el.find(".activity_score-statistics-bar").removeClass "display-none"
         if not @_is_rendered
             @_do_render()
         return @
+
+despam = (f, me) ->
+    prev = $.Deferred().resolve()
+    (args...) ->
+        prev.reject "cancelled"
+        prev.always ->
+            prev = f.apply me, args
+
+async_map = (a, f, {delay}={}) ->
+    promise = $.Deferred()
+    result = []
+    run = ->
+        return if promise.state() != "pending"
+        return promise.resolve result if not a.length
+        try
+            result.push f a[0]
+        catch error
+            promise.reject(error)
+            return
+        a = a[1..]
+        setTimeout run, delay ? 0
+    run()
+
+    return promise
+

 class @MemberListView
     constructor: (el, options={}) ->
         @$el = el
         @extra_filters = options.filters
+        @opts = _.extend
+            itemDelay: 0
+            options

         @children = {}

@@ -69,7 +101,8 @@
                 limit: 500

         data = _.extend data, @extra_filters
-
+
+        @filter = despam @filter, @
         @collection = new MemberList
         @ready = @collection.fetch
             reset: true
@@ -88,18 +121,11 @@
             if ranking > 1.0
                 ranking = 1.0
             stats['activity_ranking'] = ranking
-
+
     filter: ({search, sort}) =>
-        # TODO: This would be more responsive with
-        # asynchronous rendering of the elements. Maybe.
-        # In some profiling runs clearing the parent element took
-        # almost all the time.
-
-        # Hide the element temporarily, so the browser may be
-        # sane enough not to layout the stuff while we mess
-        # with the DOM
         @$el.addClass "display-none"
         @$el.empty()
+        @$el.removeClass "display-none"
         if not search?
             result = (@children[key] for key of @children)
         else
@@ -111,17 +137,11 @@
         result.sort sort_func

         children = []
-        for hit in result
-            hit.render(sort.by)
-            children.push hit.el
-        @$el.append children
-        hits = @$el.find(".statistics-bars > *").addClass "display-none"
-        .filter(".#{sort.by}-statistics-bar").removeClass "display-none"
-        if hits.length == 0
-            @$el.find(".activity_score-statistics-bar").removeClass "display-none"
-
-        @$el.removeClass "display-none"
-
+        render = (hit) =>
+            result = hit.render(sort.by).el
+            @$el.append result
+        async_map result, render, delay: @opts.itemDelay
+
     _process_children: (collection) =>
         for model in collection.models
             item_view = new MemberListItemView model: model


Commenting out regex routes for now

Commenting out regex routes for now

--- http.coffee
+++ http.coffee
@@ -27,9 +27,9 @@
   ['/competitions/all', 'get', listCompetitions]
   ['/competitions/by-owner', 'get', listCompetitionsByOwner]
   ['/competitions/by-membership', 'get', listCompetitionsByMembership]
-  [/competition\/\w+/withdraw/, 'post', withdrawFromCompetition]
-  [/competition\/\w+/join/, 'post', joinCompetition]
-  [/competition\/\w+/, 'get', listCompetition]
+  # [/competition\/\w+/withdraw/, 'post', withdrawFromCompetition]
+  # [/competition\/\w+/join/, 'post', joinCompetition]
+  # [/competition\/\w+/, 'get', listCompetition]
 ]

 # Creates an object suitable for use with paged UIs


Handle tests that end immediately

Handle tests that end immediately

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -100,9 +100,16 @@
       mocha.setup reporter: reporter
       mochaPhantomJS.runner = mocha.run()
       if mochaPhantomJS.runner
-        mochaPhantomJS.runner.on 'end', ->
-          mochaPhantomJS.failures = @failures
+        cleanup = ->
+          mochaPhantomJS.failures = mochaPhantomJS.runner.failures
           mochaPhantomJS.ended = true
+
+        # It's possible for the end event to occur prior to binding,
+        # this is most likely due to having zero tests but we don't want to hang.
+        if mochaPhantomJS.runner?.stats?.end
+          cleanup()
+        else
+          mochaPhantomJS.runner.on 'end', cleanup
     catch error
       false


Activity: remaining reply partial logic improved

--- client/Social/Activity/views/comments/listpreviouslink.coffee
+++ client/Social/Activity/views/comments/listpreviouslink.coffee
@@ -16,8 +16,12 @@
     listedCount = @getDelegate().getItemCount() or replies.length
     count       = Math.min (repliesCount - listedCount), 10

+    partial = if listedCount + count < repliesCount
+    then "Show #{count} of #{repliesCount - listedCount} previous repl#{if count is 1 then 'y' else 'ies'}"
+    else "Show previous #{count} repl#{if count is 1 then 'y' else 'ies'}"
+
     if count > 0
-    then @updatePartial "Show previous #{count} repl#{if count is 1 then 'y' else 'ies'}"
+    then @updatePartial partial
     else @hide()


activity: comments: test: changed default prop type

--- client/activity/lib/components/comments/test/container.coffee
+++ client/activity/lib/components/comments/test/container.coffee
@@ -43,7 +43,7 @@
       expect(viewProps.postComment).toBeA 'function'
       expect(viewProps.onChange).toBeA 'function'
       expect(viewProps.onMentionClick).toBeA 'function'
-      expect(viewProps.comments instanceof immutable.List).toBeTruthy()
+      expect(viewProps.comments instanceof immutable.Map).toBeTruthy()

   describe '::handleCommentInputChange', ->


Remove unused require

Remove unused require

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -1,4 +1,3 @@
-async = require 'async'
 {BufferedNodeProcess} = require 'atom'
 {Emitter} = require 'emissary'


action/roll_die: Don't list GM twice if the GM rolls private dice.

--- Oghma/action/roll_die.coffee
+++ Oghma/action/roll_die.coffee
@@ -19,7 +19,9 @@
     if is_private
       r = 18
       font_size = 18
-      visible_to = [ @O.me().gets( 'name' ), O.GM ]
+      visible_to = [ @O.me().gets( 'name' ) ]
+      if visible_to[0] != O.GM
+        visible_to.push( O.GM )
     else
       r = 25
       font_size = 24


moved check to the correct place

moved check to the correct place

otherwise after loading new facts, it would load more new facts, even if it wasn't in view

--- core/app/backbone/factlink/auto_loading_composite_view.coffee
+++ core/app/backbone/factlink/auto_loading_composite_view.coffee
@@ -26,7 +26,7 @@
     # this function sets the correct state after loading is done, tries to load more if applicable
     # and sets empty state if we are not loading and we have no items
     afterLoad: ->
-      unless @collection._loading
+      unless @collection._loading or not @$el.is(":visible")
         @emptyViewOnWrapper()  if @collection.length is 0
         @collection.loadMore() if @bottomOfViewReached()

@@ -34,7 +34,7 @@
       @unbindScroll()

       $(window).on "scroll.#{@cid}", =>
-        @afterLoad() if @$el.is(":visible")
+        @afterLoad()

     unbindScroll: ->
       $(window).off "scroll.#{@cid}"


update sprout config

update sprout config

--- init.coffee
+++ init.coffee
@@ -1,12 +1,15 @@
-exports.before = (sprout, done) ->
-  console.log 'generating new sprout template'
-  done()
-
 exports.configure = [
-  'name',
-  'github_username',
-  'description'
-]
-
-exports.after = (s, cb) ->
-  # after hook
+  {
+    type: 'input',
+    name: 'name',
+    message: 'What is your project\'s name?'
+  }, {
+    type: 'input',
+    name: 'github_username',
+    message: 'What is your github username?'
+  }, {
+    type: 'input',
+    name: 'description',
+    message: 'A short description of your project'
+  }
+]


Typo.

Typo.

--- init.coffee
+++ init.coffee
@@ -21,10 +21,10 @@
 		console.error error
 		unless dontExit then process.exit 1

-checkArgs = (passed, avaliable) ->
-	unless passed in avaliable
+checkArgs = (passed, available) ->
+	unless passed in available
 		console.log "Unknown argument: #{passed}"
-		console.log "Avaliable: #{avaliable}"
+		console.log "Available: #{available}"
 		process.exit 1

 config = require './config.js'


removed debug, and made conditional clearer

--- js-library/app/js/views/fact.coffee
+++ js-library/app/js/views/fact.coffee
@@ -97,12 +97,11 @@

   switchToState: (to_state) =>
     return if to_state == @state
-    console.info "switching state from '#{@state}' to '#{to_state}'"
-    switch to_state
-      when 'just_visible'
-        @highlighter.highlight()
-      else
-        @highlighter.dehighlight()
+
+    if to_state == 'just_visible'
+      @highlighter.highlight()
+    else
+      @highlighter.dehighlight()
     @state = to_state


Added 'adventure me' feature!

Added 'adventure me' feature!

--- evilbot.coffee
+++ evilbot.coffee
@@ -145,6 +145,29 @@
         output = phrase
       message.say output

+desc 'adventure me'
+hear /adventure me/, (message) ->
+  txts = [
+    "You are in a maze of twisty passages, all alike.",
+    "It is pitch black. You are likely to be eaten by a grue.",
+    "XYZZY",
+    "You eat the sandwich.",
+    "In this feat of unaccustomed daring, you manage to land on your feet without killing yourself.",
+    "Suicide is not the answer.",
+    "This space intentionally left blank.",
+    "I assume you wish to stab yourself with your pinky then?",
+    "Talking to yourself is a sign of impending mental collapse.",
+    "Clearly you are a suicidal maniac. We don't allow psychotics in the cave, since they may harm other adventurers.",
+    "Auto-cannibalism is not the answer.",
+    "Look at self: \"You would need prehensile eyeballs to do that.\"",
+    "The lamp is somewhat dimmer. The lamp is definitely dimmer. The lamp is nearly out. I hope you have more light than the lamp.",
+    "What a (ahem!) strange idea!",
+    "Want some Rye? Course ya do!"
+  ]
+  txt = txts[ Math.floor(Math.random()*txts.length) ]
+
+  message.say txt
+
 desc 'weather in PLACE'
 hear /weather in (.+)/i, (message) ->
   place = message.match[1]


updating

updating

--- vendor/assets/javascripts/meppit-map/groups.js.coffee
+++ vendor/assets/javascripts/meppit-map/groups.js.coffee
@@ -39,6 +39,7 @@
   STROKECOLOR: '#0000ff'
   FILLOPACITY: 0.4
   STROKEOPACITY: 0.8
+  WEIGHT: 3

   constructor: (@map, @data) ->
     super
@@ -98,7 +99,7 @@
     @__style =
       color: @strokeColor
       fillcolor: @fillColor
-      weight: 5
+      weight: @WEIGHT
       opacity: @STROKEOPACITY
       fillOpacity: @FILLOPACITY
     this
@@ -214,7 +215,10 @@
     @__groups[groupId] = new Group @map, data

   _createDefaultGroup: ->
-    @__defaultGroup = new Group @map, name: 'Others'
+    @__defaultGroup = new Group @map,
+      id: -1
+      name: 'Others'
+    @__groups[-1] = @__defaultGroup

   _populateGroup: (group) ->
     for g in @getGroups() when g.position > group.position


Trigger google map resize if size changes

--- app/assets/javascripts/angular/controllers/walking_guide_ctrl.coffee
+++ app/assets/javascripts/angular/controllers/walking_guide_ctrl.coffee
@@ -267,6 +267,11 @@

     $scope.$watch 'widget', autosave, true

+    $scope.$watch 'widget.relics', (newVal, oldVal) ->
+      return if newVal.length == oldVal.length
+      google.maps.event.trigger($scope.map.instance, 'resize')
+    , true
+
     $scope.openDescriptionModal = (relic) ->
       modalInstance = $modal.open
         templateUrl: 'walking-guide/description-modal.html'


Move weighted pp to tooltip

Move weighted pp to tooltip

--- resources/assets/coffee/react/_components/play-detail.coffee
+++ resources/assets/coffee/react/_components/play-detail.coffee
@@ -63,18 +63,17 @@
             className: 'detail-row__detail-row detail-row__detail-row--main'
             span
               className: 'detail-row__text-score detail-row__text-score--pp'
+              title:
+                if score.weight
+                  osu.trans 'users.show.extra.top_ranks.weighted_pp',
+                    percentage: "#{Math.round(score.weight.percentage)}%"
+                    pp: osu.trans('users.show.extra.top_ranks.pp', amount: Math.round(score.weight.pp))
               if score.weight
                 osu.trans('users.show.extra.top_ranks.pp', amount: Math.round(score.pp))
               else
                 score.score.toLocaleString()
           div
             className: 'detail-row__score-details'
-            if score.weight
-              div
-                className: 'detail-row__text-score'
-                osu.trans 'users.show.extra.top_ranks.weighted_pp',
-                  percentage: "#{Math.round(score.weight.percentage)}%"
-                  pp: osu.trans('users.show.extra.top_ranks.pp', amount: Math.round(score.weight.pp))
             div
               className: 'detail-row__text-score'
               osu.trans 'users.show.extra.historical.recent_plays.accuracy',


Solucionado bug de js

Solucionado bug de js


deletions

deletions

--- js/src/service/SharingClient.coffee
+++ js/src/service/SharingClient.coffee
@@ -10,7 +10,6 @@
   'kryptnostic.credential-loader'
   'kryptnostic.crypto-service-loader'
   'kryptnostic.crypto-service-marshaller'
-  'kryptnostic.share-processing-service'
 ], (require) ->
   _                       = require 'lodash'
   Promise                 = require 'bluebird'
@@ -24,7 +23,6 @@
   RsaCryptoService        = require 'kryptnostic.rsa-crypto-service'
   CryptoServiceLoader     = require 'kryptnostic.crypto-service-loader'
   CryptoServiceMarshaller = require 'kryptnostic.crypto-service-marshaller'
-  ShareProcessingService  = require 'kryptnostic.share-processing-service'

   log = Logger.get('SharingClient')


Add code folding hint

Add code folding hint

Too much scrolling.

--- lib/styleguide-view.coffee
+++ lib/styleguide-view.coffee
@@ -17,6 +17,8 @@
           @button outlet: 'expandAllButton', class: 'btn', 'Expand All'

       @main outlet: 'styleguideSections', class: 'styleguide-sections', =>
+
+        # Fold at indent level 5 -> `cmd-k cmd-5`
         @exampleSection 'variables', 'Variables', ->
           @p => @raw '''Use these UI variables in your package's stylesheets. They are set by UI themes and therefore your package will match the overall look. Make sure to @import 'ui-variables' in your stylesheets to use these variables.'''


Fix typo

Fix typo

--- lib/completion-backend.coffee
+++ lib/completion-backend.coffee
@@ -365,7 +365,7 @@
       Promise.resolve (FZ.filter m, prefix)
     else
       new Promise (resolve) =>
-        @process.runList rootDi.getPath(), (modules) =>
+        @process.runList rootDir.getPath(), (modules) =>
           @modListMap.set rootDir, modules
           #refresh every minute
           setTimeout (=> @modListMap.delete rootDir), 60*1000


remove additional "=" when completing property's name

--- lib/controllerProvider.coffee
+++ lib/controllerProvider.coffee
@@ -109,7 +109,7 @@
       # else
       completions.push(
         type: 'property'
-        text: "#{property} = "
+        text: "#{property}"
         displayText: property
         replacementPrefix: prefix
         description: options.description


Update method exchangeAfter

Update method exchangeAfter

--- lib/crossover_operator.coffee
+++ lib/crossover_operator.coffee
@@ -35,13 +35,10 @@
 _randomLocusOf = (c)->
    Math.floor Math.random() * c.length

-_exchangeAfter = (c1, c2, p)->
-   [
-     c1.slice 0, p
-       .concat c2.slice p
-     c2.slice 0, p
-       .concat c1.slice p
-   ]
+_exchangeAfter = (c1, c2, p)-> [
+    c1[...p].concat c2[p...]
+    c2[...p].concat c1[p...]
+  ]

 _exchange = (c1, c2, pos)->
   temp = c1[pos]; c1[pos] = c2[pos]; c2[pos] = temp


Changed the text from Script Arguments to Program arguments and added a run button

--- lib/custom-option-view.coffee
+++ lib/custom-option-view.coffee
@@ -16,12 +16,15 @@
               @label 'Command Arguments:'
               @input type: 'text', class: 'editor mini editor-colors', outlet: 'inputCommandArgs'
             @div class: 'block', =>
-              @label 'Script Arguments:'
+              @label 'Program Arguments:'
               @input type: 'text', class: 'editor mini editor-colors', outlet: 'inputScriptArgs'
             @div class: 'block', =>
               @button class: 'btn btn-primary inline-block-tight', click: 'close', 'Close'
               @button class: 'btn btn-success inline-block-tight', click: 'save', 'Save'
+              @button class: 'btn btn-success inline-block-tight', click: 'run', 'Run'
   close: ->
     atom.workspaceView.trigger "script:toggle-options"
   save: ->
     atom.workspaceView.trigger "script:save-options"
+  run: ->
+    atom.workspaceView.trigger "script:run"


fix unit test

fix unit test

--- test/unit/coffee/UpdateManager/UpdateManagerTests.coffee
+++ test/unit/coffee/UpdateManager/UpdateManagerTests.coffee
@@ -194,9 +194,9 @@
 					.calledWith(@project_id, @doc_id, @updatedDocLines, @version, @appliedOps, @updated_ranges)
 					.should.equal true

-			it "shoould add metadata to the ops" , ->
+			it "should add metadata to the ops" , ->
 				@UpdateManager._addProjectHistoryMetadataToOps
-					.calledWith(@appliedOps, @pathname, @updatedDocLines)
+					.calledWith(@appliedOps, @pathname, @lines)
 					.should.equal true

 			it "should push the applied ops into the history queue", ->


editing text in the input fields will update the temporal search, and allow users to edit minutes and seconds

--- app/assets/javascripts/modules/temporal/temporal_range_selection.js.coffee
+++ app/assets/javascripts/modules/temporal/temporal_range_selection.js.coffee
@@ -1,30 +1,43 @@
 query = window.edsc.models.searchModel.query
+
+updateQueryModel = (type, value) ->
+  switch type
+    when 'range-start'
+      query.temporal_start(value)
+    when 'range-stop'
+      query.temporal_stop(value)
+    when 'recurring'
+      query.temporal_recurring(value)

 $(document).ready ->
   $('#temporal-date-range #temporal_start').datetimepicker({
-    format: 'Y-m-d H:00:00',
+    format: 'Y-m-d H:i:s',
     yearStart: '1960',
     yearEnd: new Date().getFullYear(),
     roundTime: 'round',
     onChangeDateTime: (dp,$input) ->
-      query.temporal_start($input.val().replace(' ','T') + 'Z')
+      updateQueryModel('range-start', $input.val().replace(' ','T') + 'Z')
+    onClose: (dp,$input) ->
+      updateQueryModel('range-start', $input.val().replace(' ','T') + 'Z') if $input.val()?.length > 0
   })

   $('#temporal-date-range #temporal_stop').datetimepicker({
-    format: 'Y-m-d H:00:00',
+    format: 'Y-m-d H:i:s',
     yearStart: '1960',
     yearEnd: new Date().getFullYear(),
     onChangeDateTime: (dp,$input) ->
-      query.temporal_stop($input.val().replace(' ','T') + 'Z')
+      updateQueryModel('range-stop', $input.val().replace(' ','T') + 'Z')
+    onClose: (dp,$input) ->
+      updateQueryModel('range-stop', $input.val().replace(' ','T') + 'Z') if $input.val()?.length > 0
   })

   $('#temporal-recurring #temporal_start').datetimepicker({
-    format: 'm-d H:00:00',
+    format: 'm-d H:i:s',
     className: 'recurring-datetimepicker'
   })

   $('#temporal-recurring #temporal_stop').datetimepicker({
-    format: 'm-d H:00:00',
+    format: 'm-d H:i:s',
     className: 'recurring-datetimepicker'
   })

@@ -50,19 +63,19 @@
     start_day = Math.floor((new Date(start_datetime) - start_date_year) / one_day)
     stop_day = Math.floor((new Date(stop_datetime) - stop_date_year) / one_day)

-    query.temporal_recurring([start_datetime, stop_datetime, start_day, stop_day])
+    updateQueryModel('recurring', [start_datetime, stop_datetime, start_day, stop_day])

   $(document).on 'click', '#clear_temporal_start', ->
     $('#temporal_start').val('')
-    query.temporal_start('')
+    updateQueryModel('range-start', '')

   $(document).on 'click', '#clear_temporal_stop', ->
     $('#temporal_stop').val('')
-    query.temporal_stop('')
+    updateQueryModel('range-stop', '')

   $(document).on 'click', '#temporal-recurring-clear', ->
     $('#temporal-recurring #temporal_start').val('')
     $('#temporal-recurring #temporal_stop').val('')
     $('#temporal-recurring-year-range').slider('setValue', [1960,2013])
     $('#temporal-recurring-year-range-value').text('1960 - 2013')
-    query.temporal_recurring('')
+    updateQueryModel('recurring', '')


first attempt at clearQuestions based on element type

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -47,7 +47,29 @@

   #clear questions
   clearQuestions: (container) ->
+    console.log container
+    # clear out text fields, selects and uncheck radio and checkboxes
+    #TODO set these to default values once we implement default values - kdh
     # container.find("input[type!=hidden]").val("")
+    textFields = container.find(":text").val("")
+    textAreas = container.find("textarea").val("")
+    # un-select dropdown
+    selects = container.find("select")
+    selects.each ->
+      $(this).val("")
+      $(this).trigger("chosen:updated") if $(this).hasClass('chosen')
+    multiselects = container.find("select[multiple]")
+    multiselects.each ->
+      $('#' + $this.attr('id') + ' option:selected').removeAttr("selected")
+      $(this).trigger("chosen:updated") if $(this).hasClass('chosen-multiselect')
+    # uncheck all checkboxes
+    checkboxes = container.find(":checkbox")
+    checkboxes.each ->
+      $(this).prop('checked', false);
+    # un-select radio buttons
+    radiobuttons = container.find(":radio")
+    radiobuttons.each ->
+      $(this).prop('checked', false);

   #evaluate conditional logic rules
   evaluateRules: (operator, answer, value) ->


When a feed has a subscribe alert visible in the start page, it is never removed from the sidebar even if it has no unread entries.

This was already implemented for the case in which the feed has a refresh alert in the start page, now it has been extended for the subscribe alert. There are currently no other alerts that can appear in the start page which have a link to a feed.

This is intended so that the feed is in the scope, even if it has no unread entries, and the link displayed in the subscribe or refresh alert is functional (when it is clicked, the user is shown the entries from that feed).

--- app/assets/javascripts/ng-services/ng-CleanupSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-CleanupSvc.js.coffee
@@ -55,16 +55,23 @@
         if feed.unread_entries > 0
           # Feeds with unread entries are not removed
           return false
-        else
-          job_states = $filter('filter') $rootScope.refresh_feed_job_states, (job_state)->
-            # Find refresh_feed_job_states for this feed
-            return job_state.feed_id == feed.id
-          if job_states? && job_states?.length > 0
-            # Feeds with a job state are not removed
-            return false
-          else
-            # The rest of feeds are removed
-            return true
+
+        # Feeds without unread entries with a refresh feed alert in the start page are not removed
+        refresh_job_states = $filter('filter') $rootScope.refresh_feed_job_states, (job_state)->
+          # Find refresh_feed_job_states for this feed
+          return job_state.feed_id == feed.id
+        if refresh_job_states? && refresh_job_states?.length > 0
+          return false
+
+        # Feeds without unread entries with a subscribe alert in the start page are not removed
+        subscribe_job_states = $filter('filter') $rootScope.subscribe_job_states, (job_state)->
+          # Find subscribe_job_states for this feed
+          return job_state.feed_id == feed.id
+        if subscribe_job_states? && subscribe_job_states?.length > 0
+          return false
+
+        # The rest of feeds are removed
+        return true
       if read_feeds? && read_feeds?.length > 0
         for feed in read_feeds
           if $rootScope.current_feed?.id != feed.id && $rootScope.current_folder?.id != feed.folder_id


Parse JSON and wrap in an Error

Parse JSON and wrap in an Error

--- lib/middleware/helpers.coffee
+++ lib/middleware/helpers.coffee
@@ -3,5 +3,9 @@
 #

 module.exports = (req, res, next) ->
-  res.backboneError = (m, e) -> next e.text
-  next()+  res.backboneError = (m, e) ->
+    res.statusCode  = e.error.status
+    parsed          = JSON.parse e.text
+
+    next new Error(parsed.error)
+  next()


prevent codeglance while dragging the minimap

--- lib/minimap-codeglance.coffee
+++ lib/minimap-codeglance.coffee
@@ -63,14 +63,24 @@
     minimapView = atom.views.getView minimap
     @disposables.add disposable = new CompositeDisposable()

+    isDragging = false;
+
     disposable.add addEventListener minimapView, 'mouseenter', =>
       @codeglanceView.setMinimap minimap

     disposable.add addEventListener minimapView, 'mousemove', ({offsetY}) =>
-      @codeglanceView.showLinesAtOffset offsetY
+      @codeglanceView.showLinesAtOffset offsetY unless isDragging

     disposable.add addEventListener minimapView, 'mouseleave', =>
+      isDragging = false
       @codeglanceView.hide()
+
+    disposable.add addEventListener minimapView, 'mousedown', =>
+      isDragging = true
+      @codeglanceView.hide()
+
+    disposable.add addEventListener minimapView, 'mouseup', ->
+      isDragging = false

     @disposables.add minimap.onDidDestroy =>
       @disposables.remove disposable


:art: improve blockwise duplicate

:art: improve blockwise duplicate

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -43,9 +43,10 @@
   packPath = atom.packages.resolvePackagePath(pack)
   require "#{packPath}/lib/#{path}"

-{pointIsAtEndOfLine, getVimLastBufferRow} = requireFrom('vim-mode-plus', 'utils')
+{pointIsAtEndOfLine, sortRanges, getVimLastBufferRow} = requireFrom('vim-mode-plus', 'utils')
 swrap = requireFrom('vim-mode-plus', 'selection-wrapper')
 Base = requireFrom('vim-mode-plus', 'base')
+BlockwiseSelect = Base.getClass('BlockwiseSelect')
 TransformString = Base.getClass('Operator')

 newState = ->
@@ -302,22 +303,34 @@

     isLinewise = @isLinewise()
     @editor.transact =>
-      for selection in selections
-        if isLinewise
+      if isLinewise
+        for selection in selections
           if @vimState.submode is 'linewise'
             @duplicateLinewise(selection)
           else
             swrap(selection).switchToLinewise =>
               @duplicateLinewise(selection)
-        else
-          switch @direction
-            when 'right', 'left'
+      else
+        switch @direction
+          when 'right', 'left'
+            for selection in selections
               @duplicateCharacterwise(selection)
-            when 'up', 'down'
-              console.log selection.getText()
-              @countTimes =>
-                @duplicateCharacterwise(selection, {topRow, bottomRow})
-
+          when 'up', 'down'
+            lastSelection = @editor.getLastSelection()
+            reversed = lastSelection.isReversed()
+            # selection.setBufferRange(range, {reversed})
+            height = selections.length
+            totalHeight = height * @getCount()
+            lackOfSelection = totalHeight - height
+            ranges = []
+            @countTimes =>
+              for selection in selections
+                ranges.push(@duplicateCharacterwise(selection, {topRow, bottomRow}))
+                selection.destroy() unless selection.isLastSelection()
+            ranges = sortRanges(ranges)
+            range = [_.first(ranges).start, _.last(ranges).end.translate([totalHeight - 1, 0])]
+            lastSelection.setBufferRange(range, {reversed})
+            new BlockwiseSelect(@vimState).execute()
   isCharacterwise: ->
     not @isLinewise()

@@ -335,32 +348,28 @@
         @editor.setTextInBufferRange([point, point], newText)
       when 'left', 'right'
         selection.insertText(newText)
-
     selection.setBufferRange(range, {reversed})

   insertTextAtPoint: (point, text) ->
     @editor.setTextInBufferRange([point, point], text)

   duplicateCharacterwise: (selection, {topRow, bottomRow}={}) ->
-    reversed = selection.isReversed()
     switch @direction
       when 'up', 'down'
         lineTexts = @duplicateText(selection, 1)
         spaces = ' '.repeat(selection.getBufferRange().start.column)
-        insertionStart = switch @direction
+        point = switch @direction
           when 'up' then [topRow-1, Infinity]
           when 'down' then [bottomRow, Infinity]
-        {end} = @insertTextAtPoint(insertionStart, "\n" + spaces)
-        range = @insertTextAtPoint(end, lineTexts.join(""))
-        selection.setBufferRange(range, {reversed})
+        {end} = @insertTextAtPoint(point, "\n" + spaces)
+        @insertTextAtPoint(end, lineTexts.join(""))
       when 'left', 'right'
         lineTexts = @duplicateText(selection, @getCount())
         {start, end} = selection.getBufferRange()
         point = switch @direction
           when 'left' then start
           when 'right' then end
-        range = @insertTextAtPoint(point, lineTexts.join(""))
-        selection.setBufferRange(range, {reversed})
+        @insertTextAtPoint(point, lineTexts.join(""))

   isLinewise: ->
     switch @vimState.submode


fix typo in PBD class

fix typo in PBD class

--- src/api/pbd.coffee
+++ src/api/pbd.coffee
@@ -6,7 +6,7 @@

 exports.delete = $coroutine ({id}) ->
   try
-    VBD = @getObject id, 'PBD'
+    PBD = @getObject id, 'PBD'
   catch
     @throw 'NO_SUCH_OBJECT'


Change notification message position to NE

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -62,7 +62,7 @@
     }, {
       type: ntype
       allow_dismiss: true
-      placement: {from: "bottom", align: "center"}
+      placement: {from: "top", align: "right"}
       delay: 2
       offset: {x: 20, y: 50}
       showProgressbar: true


use last char instead of first

use last char instead of first

--- lib/atom-ternjs-autocomplete.coffee
+++ lib/atom-ternjs-autocomplete.coffee
@@ -68,7 +68,7 @@
                             className: null,
                             _ternDocs: obj.doc,
                             onWillConfirm: ->
-                                if /^[.\"\']$/i.test(prefix[0])
+                                if /^[.\"\']$/i.test(prefix[prefix.length - 1])
                                     this.word = this.prefix + this.word
                             onDidConfirm: ->
                         }


Put package.json inside build for deploy

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,10 +11,15 @@

 		copy:
 			main:
-				expand: true
-				cwd: 'src/'
-				src: ['**', '!coffee/**', '!**/*.less']
-				dest: 'build/<%= relativePath %>'
+        files: [
+          expand: true
+          cwd: 'src/'
+          src: ['**', '!coffee/**', '!**/*.less']
+          dest: 'build/<%= relativePath %>'
+        ,
+          src: ['package.json']
+          dest: 'build/<%= relativePath %>/package.json'
+        ]
 			mocks:
 				src: ['spec/mocks/*.json']
 				dest: 'build/<%= relativePath %>/'


Compile index.pug into public/html.

Compile index.pug into public/html.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -39,7 +39,7 @@
         pretty: true
       index:
         src: 'index.pug'
-        dest: 'index.html'
+        dest: 'public/html/index.html'
       partials:
         expand: true
         cwd: 'src/'