better tree focus

better tree focus

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -86,11 +86,6 @@
   # Enter Insert Mode
   'ctrl-i': 'vim-mode-plus:activate-insert-mode'

-###################################################
-################ Pending Operation ################
-'atom-text-editor.vim-mode-plus.operator-pending-mode.surround-pending':
-  's': 'vim-mode-plus:inner-current-line'
-
 ##############################################
 ################ NORMAL MODE #################
 'atom-text-editor.vim-mode-plus.normal-mode':
@@ -110,14 +105,6 @@
   '\' shift-c': 'window:move-active-item-to-pane-above'
   '\' shift-t': 'window:move-active-item-to-pane-below'

-  # Surround
-  'y s': 'vim-mode-plus:surround'
-
-  'd s': 'vim-mode-plus:delete-surround-any-pair'
-  'd S': 'vim-mode-plus:delete-surround'
-
-  'c s': 'vim-mode-plus:change-surround-any-pair'
-  'c S': 'vim-mode-plus:change-surround'

 ##############################################
 ################ INSERT MODE #################
@@ -131,9 +118,6 @@
 ##############################################
 ################ VISUAL MODE #################
 'atom-text-editor.vim-mode-plus.visual-mode':
-  # Surround
-  'S': 'vim-mode-plus:surround'
-
   # Exit Visual Mode - Escape is too far away.
   'ctrl-u': 'vim-mode-plus:activate-normal-mode'
   # Enter Insert Mode
@@ -198,7 +182,7 @@
   'ctrl-t': 'fuzzy-finder:toggle-file-finder'

   # Tree View Toggle
-  'ctrl+\\':     'tree-view:toggle-focus'
+  '\' /':     'tree-view:toggle-focus'
   '\' \\':   'tree-view:toggle'

   # Show the current file in the tree
@@ -252,7 +236,7 @@
   't':   'core:move-down'
   'h':   'tree-view:collapse-directory'
   'n':   'tree-view:open-selected-entry'
-  '\\':  'tree-view:toggle-focus'
+  '\' /':  'tree-view:toggle-focus'


terminal browserified +1

terminal browserified +1

--- client/Terminal/lib/routes.coffee
+++ client/Terminal/lib/routes.coffee
@@ -1,8 +1,13 @@
-do ->
+kd = require 'kd'
+registerRoutes = require 'app/util/registerRoutes'
+isLoggedIn = require 'app/util/isLoggedIn'

-  KD.registerRoutes 'Terminal',
+module.exports = ->
+
+  registerRoutes 'Terminal',
     '/:name?/Terminal' : (routeInfo, state, path) ->
-      if !KD.isLoggedIn()
-        KD.singletons.router.handleRoute "/Login"
+      if !isLoggedIn()
+        kd.singletons.router.handleRoute "/Login"
       else
-        KD.singletons.router.handleRoute path.replace(/\/Terminal/, '/IDE')
+        kd.singletons.router.handleRoute path.replace(/\/Terminal/, '/IDE')
+


Fixed: menu was not shown on responsive mode because it wasn't a component refresh criteria

--- client/app/components/menu.coffee
+++ client/app/components/menu.coffee
@@ -16,7 +16,8 @@
     shouldComponentUpdate: (nextProps, nextState) ->
         return not Immutable.is(nextProps.mailboxes, @props.mailboxes) or
                not Immutable.is(nextProps.selectedMailbox, @props.selectedMailbox) or
-               not _.isEqual(nextProps.layout, @props.layout)
+               not _.isEqual(nextProps.layout, @props.layout) or
+               nextProps.isResponsiveMenuShown isnt @props.isResponsiveMenuShown

     render: ->
         selectedMailboxUrl = @buildUrl


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -65,7 +65,6 @@

             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!

-
25 ноября занятия не будет, я немного приболел.

             {
             if @state.result?.error
                 <Alert bsStyle="danger">


Renaming method.

Renaming method.

--- src/toaster/filetype/coffee/optimizer/loader.coffee
+++ src/toaster/filetype/coffee/optimizer/loader.coffee
@@ -20,7 +20,7 @@
     # all the necessary configs and a hash map containing the layer location
     # for each module that was merged into it.

-    loader = @_get_amd_loader()
+    loader = @get_amd_loader()

     if paths?
       paths = (util.inspect paths).replace /\s/g, ''
@@ -50,7 +50,7 @@

     fs.writeFileSync release_path, loader

-  _get_amd_loader:->
+  get_amd_loader:->
     rjs_path = path.join @toaster.toaster_base, 'node_modules'
     rjs_path = path.join rjs_path, 'requirejs', 'require.js'
     fs.readFileSync rjs_path, 'utf-8'


udefine binds to the global object in Node.js

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -4,7 +4,10 @@
 hasModule = module? and module.exports

 # Root object hook
-do (root = module?.exports ? this) ->
+# TODO: Binding to global on Node.js is not the best idea (I know it's bad
+#  practice), but what are the alternatives? (Putting it into a requirable
+#  module is not really an for small-scale projects)
+do (root = if hasModule then global ? this) ->
   root.udefine or= (name, deps, factory) ->
     throw new Error 'A udefine module needs to have a name' unless name?


Allow selectedClassification to be redefined in initContext

When $scope.classifications is redefined, selectedClassification should be re-determined.

--- src/components/widgets/accounts-class-comparison/accounts-class-comparison.directive.coffee
+++ src/components/widgets/accounts-class-comparison/accounts-class-comparison.directive.coffee
@@ -33,20 +33,17 @@
   w.initContext = ->
     $scope.isDataFound = angular.isDefined(w.content) && !_.isEmpty(w.content.summary) && !_.isEmpty(w.content.companies)
     if $scope.isDataFound
-
-      $scope.classifications = _.map w.content.summary, (summary) ->
+      $scope.classifications = _.map(w.content.summary, (summary) ->
         klass = summary.classification
         {
           label: translate(klass)
           labelTranslate: summary.classification_key if summary.classification_key?
           value: klass
         }
-      # return { label: _.capitalize(summary.toLowerCase()), value: summary }
-
-      if !$scope.selectedClassification
-        $scope.selectedClassification = angular.copy(_.find $scope.classifications, {
-          value: w.metadata.classification || $scope.classifications[0].value
-        })
+      )
+      $scope.selectedClassification = angular.copy(_.find $scope.classifications, {
+        value: w.metadata.classification || $scope.classifications[0].value
+      })

   $scope.getTotals = ->
     amount = _.find(w.content.summary, (sum) ->


add icon to upload button

add icon to upload button

--- src/views/themes/default/coffee/global/forms.coffee
+++ src/views/themes/default/coffee/global/forms.coffee
@@ -4,6 +4,14 @@
 ###
 ###
  uppercase booleans easier to read, custom code in watcher to change it, not a coffee feature.
+###
+
+###
+ start icon codes from font awesome
+###
+icon_upload = ""
+###
+ end icon codes
 ###
 $html = $('html')
 $body = $('body')
@@ -16,8 +24,10 @@
   #endif
   if dragndrop
     dropzone = "
This box is also a file drop zone.
"
+    drag_class = ' dropzone'
   else
     dropzone = ""
+    drag_class = ''
   #endif
   return $("""

@@ -26,7 +36,7 @@

New

Existing

-
#{dropzone}
+
#{dropzone}

             #{input}

@@ -281,13 +291,14 @@
             dragndrop
           )
           label = $this.attr('title').replace(/Select/, 'Upload')
+          button_html = "#{icon_upload}#{label}"
           if $html.hasClass('ie')
             $uploader.find('.upload').append(
-              "#{label}"
+              "#{button_html}"
             )
           else
             $uploader.find('.upload').append(
-              "#{label}"
+              "#{button_html}"
             )
           #endif
           $('body').append($uploader)


Hide adder at correct places

Hide adder at correct places

--- annotran/static/scripts/widget-controller.coffee
+++ annotran/static/scripts/widget-controller.coffee
@@ -121,6 +121,9 @@

         crossframe.call "resetDOM"

+        if $scope.$root.mode == "view"
+          crossframe.call "hideAdder"
+
         if selectedUser != undefined
           if !$scope.$root.editOnly
             crossframe.call "stashAnnotations", []


add new test cases

add new test cases

--- workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
@@ -1,15 +1,19 @@
-
-{ PLANS, PROVIDERS, fetchGroupStackTemplate, revive,
-  fetchUsage, checkTemplateUsage } = require './computeutils'
-
 { daisy
   expect
+  ObjectId
   withDummyClient
   withConvertedUser
+  expectAccessDenied
+  generateRandomString
   checkBongoConnectivity
   generateDummyUserFormData } = require '../../../../testhelper'
-
-ComputeProvider = require './computeprovider'
+{ withConvertedUserAndCredential } = require \
+  '../../../../testhelper/models/computeproviders/credentialhelper'
+{ withConvertedUserAnd } = require  \
+  '../../../../testhelper/models/computeproviders/computeproviderhelper'
+
+{ PROVIDERS, revive }   = require './computeutils'
+ComputeProvider         = require './computeprovider'

 # this function will be called once before running any test
@@ -36,7 +40,7 @@
   describe '#ping()', ->

     it 'should be able to ping for the given provider', (done) ->
-
+
       withConvertedUser ({ client, account }) ->
         client.r  = { account }
         queue     = []
@@ -59,21 +63,198 @@

     it 'should not be able to ping if user doesnt have the right to ping', (done) ->

+      expectAccessDenied ComputeProvider, 'ping$', { provider : PROVIDERS.google }, done
+
+
+  describe '#create()', ->
+
+    it 'should be able to create a new compute provider', (done) ->
+
+      withConvertedUserAnd ['Provisioner', 'Stack'], (data) ->
+        { client, user, account, provisioner, stack, group } = data
+
+        client.r      = { group : group.slug, user, account }
+        label         = generateRandomString()
+        generatedFrom = new ObjectId
+        provider      = 'aws'
+
+        options = {
+          client, stack, provider, label, generatedFrom
+          users : []
+          provisioners : [provisioner.slug]
+        }
+
+        ComputeProvider.create client, options, (err, machine) ->
+          expect(err?.message).to.not.exist
+          expect(machine).to.be.an 'object'
+          expect(machine.provider).to.be.equal 'aws'
+          expect(machine.generatedFrom).to.be.equal generatedFrom
+          expect(machine.provisioners).to.have.length 1
+          expect(machine.provisioners[0]).to.be.equal provisioner.slug
+          expect(machine.groups).to.have.length 1
+          expect(machine.groups[0].id).to.be.deep.equal group._id
+          expect(machine.domain).to.exist
+          expect(machine.slug).to.exist
+          expect(machine.status).to.be.an 'object'
+          expect(machine.assignee).to.be.an 'object'
+          done()
+
+
+  describe '#create$()', ->
+
+    it 'should fail if use doesnt have permission', (done) ->
+
+      expectAccessDenied ComputeProvider, 'create$', {}, done
+
+
+  describe '#fetchAvailable()', ->
+
+    it 'should be able to fetch availabe object', (done) ->
+
+      withConvertedUserAndCredential ({ client, credential }) ->
+
+        options =
+          provider: 'google'
+          credential : credential
+
+        ComputeProvider.fetchAvailable client, options, (err, data) ->
+          expect(err?.message).to.not.exist
+          expect(data).to.be.an 'array'
+          done()
+
+
+    it 'should fail if no provider is given', (done) ->
+
       withDummyClient ({ client }) ->
-
-        ComputeProvider.ping$ client, { provider : PROVIDERS.google }, (err, data) ->
-          expect(err?.message).to.be.equal 'Access denied'
-          done()
-
-
-  describe.skip '#create()', ->
-
-    it 'should fail when user is not registered', (done) ->
+        ComputeProvider.fetchAvailable client, { provider : null }, (err, data) ->
+          expect(err?.message).to.be.equal 'No such provider.'
+          done()
+
+
+    it 'should fail if credential is not provided', (done) ->

       withDummyClient ({ client }) ->

-
-
+        options =
+          provider: 'google'
+
+        ComputeProvider.fetchAvailable client, options, (err, data) ->
+          expect(err?.message).to.be.equal 'Credential is required.'
+          done()
+
+
+    it 'should fail if given credential is not valid', (done) ->
+
+      withDummyClient ({ client }) ->
+
+        options =
+          provider: 'google'
+          credential : 'invalid'
+
+        ComputeProvider.fetchAvailable client, options, (err, data) ->
+          expect(err?.message).to.be.equal 'Credential failed.'
+          done()
+
+
+  describe '#fetchUsage', ->
+
+    it 'should be able to fetch the usage', (done) ->
+
+      withConvertedUserAndCredential ({ client, credential }) ->
+        options = { credential, provider : 'aws' }
+        ComputeProvider.fetchUsage client, options, (err, usage) ->
+          expect(err).to.not.exist
+          expect(usage).to.be.an 'object'
+          done()
+
+
+  describe '#fetchPlans', ->
+
+    it 'should fail if user doesnt have permission', (done) ->
+
+      expectAccessDenied ComputeProvider, 'fetchPlans', done
+
+
+    it 'should be able to fetch plans', (done) ->
+
+      withConvertedUserAndCredential ({ client, credential }) ->
+        ComputeProvider.fetchPlans client, (err, usage) ->
+          expect(err).to.not.exist
+          expect(usage).to.be.an 'object'
+          done()
+
+
+  describe '#update()', ->
+
+    it 'should if no machine id is given', (done) ->
+
+      withConvertedUserAndCredential ({ client, credential }) ->
+
+        options =
+          provider : 'aws'
+          credential : credential
+
+        ComputeProvider.update client, options, (err) ->
+          expect(err?.message).to.be.equal 'A valid machineId and an update option required.'
+          done()
+
+
+    it 'should be able to update compute provider data', (done) ->
+
+      withConvertedUserAnd ['Credential', 'ComputeProvider'], (data) ->
+        { client, credential, machine } = data
+
+        options =
+          provider   : 'aws'
+          credential : credential
+          machineId  : machine._id + ''
+          alwaysOn   : true
+
+        ComputeProvider.update client, options, (err) ->
+          expect(err?.message).to.not.exist
+          done()
+
+
+  describe '#remove()', ->
+
+    it 'should fail if not implemented yet', (done) ->
+
+      withConvertedUserAnd ['Credential', 'ComputeProvider'], (data) ->
+        { client, credential } = data
+
+        options =
+          provider   : 'aws'
+          credential : credential
+
+        ComputeProvider.remove client, options, (err) ->
+          expect(err?.message).to.be.equal 'Not implemented yet.'
+          done()
+
+
+  describe '#createStackFromTemplate', ->
+
+    it 'should fail if user doesnt have permission', (done) ->
+
+      expectAccessDenied ComputeProvider, 'createStackFromTemplate', {}, done
+
+
+  describe 'createGroupStack', ->
+
+    it 'should be able to create group stack', (done) ->
+
+      # creating a new group and a new StackTemplate for that group
+      groupSlug = generateRandomString()
+      options   = { context : { group : groupSlug} }
+
+      withConvertedUserAnd ['Group', 'StackTemplate'], options, (data) ->
+        { group, client } = data
+
+        ComputeProvider.createGroupStack data.client, (err, groupStack) ->
+          expect(err?.message).to.not.exist
+          expect(groupStack).to.exist
+          expect(groupStack).to.be.an 'object'
+          expect(groupStack.originId).to.be.deep.equal client._id
+          done()


Profile: name parameter in /slug/name route is no longer optional

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -283,7 +283,7 @@

       '/:name/Groups'          : createSectionHandler 'Groups'

-      '/:slug/:name?' : ({params, query}, model, route) ->
+      '/:slug/:name' : ({params, query}, model, route) ->
         (createContentHandler 'Members') arguments...

       '/:name?/Invitation/:inviteCode': ({params:{inviteCode, name}})=>
@@ -341,9 +341,7 @@

           switch model?.bongo_?.constructorName
             when 'JAccount'
-              # workaround. when a route as /:name?/:name2? is defined there occurs two redirection
-              # if we remove JAccount case then handleNotFound is called - CtF
-              return
+              (createContentHandler 'Members') routeInfo, [model]
             when 'JGroup'
               (createSectionHandler 'Activity') routeInfo, model
             when 'JNewApp'


payment: determine if the operation is upgrade from state

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -34,7 +34,7 @@

     {
       planTitle, planInterval, reducedMonth
-      currentPlan, yearPrice
+      currentPlan, yearPrice, isUpgrade
     } = @state

     planIntervalPartial = if planInterval is 'month'
@@ -61,8 +61,6 @@
     @successMessage = new KDCustomHTMLView
       cssClass : 'success-msg hidden'
       partial  : ''
-
-    isUpgrade = PaymentWorkflow.isUpgrade currentPlan, planTitle

     buttonPartial = if isUpgrade
     then 'UPGRADE YOUR PLAN'


AccountModal: Remove delete account/leave group section.

--- client/account/lib/index.coffee
+++ client/account/lib/index.coffee
@@ -133,6 +133,10 @@

     groupSlug  = kd.singletons.groupsController.getGroupSlug()
     items = []
+
+    index = if isKoding() then 1 else 0
+    NAV_ITEMS.danger.items.splice index, 1 # remove leave or danger
+
     for own sectionKey, section of NAV_ITEMS
       mergeables = []
       for item in section.items


IDE: alignment

IDE: alignment

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -35,31 +35,32 @@
       'go to right tab'     : 'goToRightTab'
       'go to tab number'    : 'goToTabNumber'
       'fullscren ideview'   : 'toggleFullscreenIDEView'
+
     keyBindings: [
-      { command: 'find file by name',   binding: 'ctrl+alt+o', global: yes }
-      { command: 'search all files',    binding: 'ctrl+alt+f', global: yes }
-      { command: 'split vertically',    binding: 'ctrl+alt+v', global: yes }
-      { command: 'split horizontally',  binding: 'ctrl+alt+h', global: yes }
-      { command: 'merge splitview',     binding: 'ctrl+alt+m', global: yes }
-      { command: 'preview file',        binding: 'ctrl+alt+p', global: yes }
-      { command: 'save all files',      binding: 'ctrl+alt+s', global: yes }
-      { command: 'create new file',     binding: 'ctrl+alt+n', global: yes }
-      { command: 'create new terminal', binding: 'ctrl+alt+t', global: yes }
-      { command: 'create new browser',  binding: 'ctrl+alt+b', global: yes }
-      { command: 'create new drawing',  binding: 'ctrl+alt+d', global: yes }
-      { command: 'toggle sidebar',      binding: 'ctrl+alt+k', global: yes }
-      { command: 'close tab',           binding: 'ctrl+alt+w', global: yes }
-      { command: 'go to left tab',      binding: 'ctrl+alt+[', global: yes }
-      { command: 'go to right tab',     binding: 'ctrl+alt+]', global: yes }
-      { command: 'go to tab number',    binding: 'mod+1',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+2',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+3',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+4',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+5',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+6',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+7',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+8',      global: yes }
-      { command: 'go to tab number',    binding: 'mod+9',      global: yes }
+      { command: 'find file by name',   binding: 'ctrl+alt+o',       global: yes }
+      { command: 'search all files',    binding: 'ctrl+alt+f',       global: yes }
+      { command: 'split vertically',    binding: 'ctrl+alt+v',       global: yes }
+      { command: 'split horizontally',  binding: 'ctrl+alt+h',       global: yes }
+      { command: 'merge splitview',     binding: 'ctrl+alt+m',       global: yes }
+      { command: 'preview file',        binding: 'ctrl+alt+p',       global: yes }
+      { command: 'save all files',      binding: 'ctrl+alt+s',       global: yes }
+      { command: 'create new file',     binding: 'ctrl+alt+n',       global: yes }
+      { command: 'create new terminal', binding: 'ctrl+alt+t',       global: yes }
+      { command: 'create new browser',  binding: 'ctrl+alt+b',       global: yes }
+      { command: 'create new drawing',  binding: 'ctrl+alt+d',       global: yes }
+      { command: 'toggle sidebar',      binding: 'ctrl+alt+k',       global: yes }
+      { command: 'close tab',           binding: 'ctrl+alt+w',       global: yes }
+      { command: 'go to left tab',      binding: 'ctrl+alt+[',       global: yes }
+      { command: 'go to right tab',     binding: 'ctrl+alt+]',       global: yes }
+      { command: 'go to tab number',    binding: 'mod+1',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+2',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+3',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+4',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+5',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+6',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+7',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+8',            global: yes }
+      { command: 'go to tab number',    binding: 'mod+9',            global: yes }
       { command: 'fullscren ideview',   binding: 'mod+shift+enter', global: yes }
     ]


AppController: Code styling.

AppController: Code styling.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -287,8 +287,9 @@

   unmountMachine: (machineData) ->

-    panel        = @workspace.getView()
-    filesPane    = panel.getPaneByName 'filesPane'
+    panel     = @workspace.getView()
+    filesPane = panel.getPaneByName 'filesPane'
+
     filesPane.emit 'MachineUnmountRequested', machineData


update tooltip when mapped key is updated

--- client/prefs-ui-keyboard.coffee
+++ client/prefs-ui-keyboard.coffee
@@ -59,7 +59,7 @@
     </select>
   """
   .on 'focus', '.key input', -> setTimeout (=> $(@).select(); return), 1; return
-  .on 'blur', '.key input', -> $(@).val $(@).val()[-1..] || ' '; return
+  .on 'blur', '.key input', -> $(@).val(v = $(@).val()[-1..] || ' ').prop 'title', "U+#{hex ord(v), 4}"; return
   .on 'mouseover mouseout', '.key input', (e) -> $(@).toggleClass 'hover', e.type == 'mouseover'; return
   if !prefs.keyboardLocale() then prefs.keyboardLocale(if navigator.language == 'en-GB' then 'GB' else 'US')
   $('#keyboard-locale').val(prefs.keyboardLocale()).change ->


Oz Levels - Remove code editor dead space (#787)


release 1.3.2

release 1.3.2

--- src/version.coffee
+++ src/version.coffee
@@ -1 +1 @@
-FormRenderer.VERSION = '1.3.1'
+FormRenderer.VERSION = '1.3.2'


private message: pass noop as callback to JPrivateMessage.create

--- client/landing/app/CommonViews/workspace/collaborativeworkspaceuserlist.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspaceuserlist.coffee
@@ -171,7 +171,7 @@

     return if to is nickname

-    KD.remote.api.JPrivateMessage.create { to, subject, body }
+    KD.remote.api.JPrivateMessage.create { to, subject, body }, noop

     new KDNotificationView
       title    : "Invitation sent to #{to}"


support summaryTemplate

support summaryTemplate

--- ui.coffee
+++ ui.coffee
@@ -49,7 +49,7 @@
 class ui.ComponentType
   constructor: ({@name, @width, @height, @isCounter, @stackProperty, @stackAfter,
                  @displayNameSingular, @displayNamePlural,
-                 @draggable, @template} = {}) ->
+                 @draggable, @template, @summaryTemplate} = {}) ->
     check @name, String
     check @width, Match.Optional Number
     check @height, Match.Optional Number
@@ -67,6 +67,7 @@
     @displayNamePlural ?= @displayNameSingular
     check @displayNamePlural, String
     check @template, Match.Optional String
+    check @summaryTemplate, Match.Optional String
     @draggable ?= false
     check @draggable, Boolean

@@ -130,7 +131,7 @@
             Blaze.With (new ui.ComponentStack uiType: @, container: container, stack: value, count: count), =>
               @getCounterTemplate count, container.game().rules
           else
-            Blaze.With (type: @, stack: value, container: container), =>
+            Blaze.With (uiType: @, stack: value, container: container), =>
               Blaze.Each (-> cursor), =>
                 @getCounterTemplate count, container.game().rules
     else


Revert that.

Revert that.

--- app/assets/javascripts/web/views/message_view.js.coffee
+++ app/assets/javascripts/web/views/message_view.js.coffee
@@ -7,25 +7,25 @@
   shouldScrollToMessage: false

   willInsertElement: () ->
-    # if @get('parentView').state == "inDOM"
-    #   distance = @get('parentView.desiredScroll')
-    #   @get('parentView').scrollToFromBottom(distance) if distance
-    #   @set('shouldScrollToMessage', !@get('parentView').isReadingHistory())
+    if @get('parentView').state == "inDOM"
+      distance = @get('parentView.desiredScroll')
+      @get('parentView').scrollToFromBottom(distance) if distance
+      @set('shouldScrollToMessage', !@get('parentView').isReadingHistory())

     @_super()

   didInsertElement: ->
-    # @get('parentView').scrollToBottom( force: @get('shouldScrollToMessage') ) if @get('parentView')
-    # setTimeout =>
-    #   @get('parentView').scrollToBottom( force: @get('shouldScrollToMessage') ) if @get('parentView')
-    #   @set('shouldScrollToMessage', false)
-    # , 100
+    @get('parentView').scrollToBottom( force: @get('shouldScrollToMessage') ) if @get('parentView')
+    setTimeout =>
+      @get('parentView').scrollToBottom( force: @get('shouldScrollToMessage') ) if @get('parentView')
+      @set('shouldScrollToMessage', false)
+    , 100

-    # if @get('context').get('hasSameAuthorAsPrevious')
-    #   @.$().prevAll('.message').first().removeClass('message-last')
+    if @get('context').get('hasSameAuthorAsPrevious')
+      @.$().prevAll('.message').first().removeClass('message-last')

-    # if @get('context').get('hasSameAuthorAsNext')
-    #   @.$().nextAll('.message').first().removeClass('message-first')
+    if @get('context').get('hasSameAuthorAsNext')
+      @.$().nextAll('.message').first().removeClass('message-first')

     @.$('a[internal]').on('click', (e) =>
       event.preventDefault()


Fix hiding tags that have unread items.

Fix hiding tags that have unread items.

--- app/assets/javascripts/counts_behavior.js.coffee
+++ app/assets/javascripts/counts_behavior.js.coffee
@@ -18,10 +18,11 @@

 feedbin.applyCounts = (useHideQueue) ->
   $('[data-behavior~=needs_count]').each (index, countContainer) =>
-    group = $(countContainer).data('count-group')
-    groupId = $(countContainer).data('count-group-id')
-    collection = $(countContainer).data('count-collection')
-    countHide = $(countContainer).data('count-hide') == 'on'
+    countContainer = $(countContainer)
+    group = countContainer.data('count-group')
+    groupId = countContainer.data('count-group-id')
+    collection = countContainer.data('count-collection')
+    countHide = countContainer.data('count-hide') == 'on'

     if !collection
       collection = 'unread'
@@ -29,7 +30,7 @@
         collection = 'starred'

     counts = feedbin.Counts.get().counts[collection][group]
-    countWas = $(countContainer).text() * 1
+    countWas = countContainer.text() * 1
     count = 0

     if groupId
@@ -37,15 +38,15 @@
         count = counts[groupId].length
     else
       count = counts.length
-    $(countContainer).text(count)
+    countContainer.text(count)

     if count == 0
-      $(countContainer).addClass('hide')
+      countContainer.addClass('hide')
     else
-      $(countContainer).removeClass('hide')
+      countContainer.removeClass('hide')

     if groupId || countHide
-      container = $(countContainer).parents('li').first()
+      container = countContainer.parents('li').first()
       feedId = $(container).data('feed-id')
       if useHideQueue
         if count == 0 && countWas > 0
@@ -176,13 +177,17 @@
     feedbin.specialCollection = $(event.currentTarget).data('special-collection')

     # Drain hide queue if this isn't the same collection
-    if feedbin.selectedFeed && feedbin.selectedFeed[0] != $(event.currentTarget)[0]
-      $.each feedbin.hideQueue, (index, feed_id) ->
-        count = feedbin.Counts.get().entriesInFeed(feed_id)
-        if feed_id != undefined && count == 0
-          item = $("li[data-feed-id=#{feed_id}]", '.feeds')
-          $(item).addClass('zero-count')
-      feedbin.hideQueue.length = 0
+    if feedbin.selectedFeed
+      isParent = $(event.currentTarget).parent('li[data-tag-id]').parent(feedbin.selectedFeed).length != 0
+      isSelf = feedbin.selectedFeed.is(event.currentTarget)
+      if !isParent && !isSelf
+        $.each feedbin.hideQueue, (index, feed_id) ->
+          if feed_id != undefined
+            item = $(".feeds li[data-feed-id=#{feed_id}]")
+            count = $('[data-behavior~=needs_count]', item).text() * 1
+            if count == 0
+              $(item).addClass('zero-count')
+        feedbin.hideQueue.length = 0

     feedbin.selectedFeed = $(event.currentTarget)
     return


OX-444 fixed direction bug

OX-444 fixed direction bug

--- app/assets/javascripts/oxalis/model/route.coffee
+++ app/assets/javascripts/oxalis/model/route.coffee
@@ -115,13 +115,14 @@
         for edge in @data.trees[0].edges
           sourceNodeNm = @scaleInfo.voxelToNm(@findNodeInList(@trees[0].nodes, edge.source).pos)
           targetNodeNm = @scaleInfo.voxelToNm(@findNodeInList(@trees[0].nodes, edge.target).pos)
-          if sourceNodeNm != targetNodeNm
+          if sourceNodeNm[0] != targetNodeNm[0] or sourceNodeNm[1] != targetNodeNm[1] or sourceNodeNm[2] != targetNodeNm[2]
             @firstEdgeDirection = [targetNodeNm[0] - sourceNodeNm[0],
                                    targetNodeNm[1] - sourceNodeNm[1],
                                    targetNodeNm[2] - sourceNodeNm[2]]
             break

       if @firstEdgeDirection
+        console.log @firstEdgeDirection
         @flycam.setSpaceDirection(@firstEdgeDirection)
         @flycam3d.setDirection(V3.normalize(@firstEdgeDirection))


remove spurious views adversely affecting performance

--- demo/coffee/grid_plot.coffee
+++ demo/coffee/grid_plot.coffee
@@ -60,8 +60,6 @@
     div = $('

')
     $('body').append(div)
     myrender = ->
-      p1view = new plot1.default_view(model: plot1)
-      p2view = new plot2.default_view(model: plot2)
       gridview = new gridplot.default_view(model: gridplot)
       div.append(gridview.$el)
       console.log('Grid Plot Test')


version絞り込みが動作しない不具合を修正

version絞り込みが動作しない不具合を修正

--- app/assets/javascripts/stores/score_store.coffee
+++ app/assets/javascripts/stores/score_store.coffee
@@ -46,20 +46,24 @@
     when AbilitysheetConstants.SHOW_SCORE_DATA
       for id, score of scores
         continue unless score.state is payload.state
+        scores[id] ||= {}
         scores[id].display = ''
       ScoreStore.emitChange()
     when AbilitysheetConstants.HIDE_SCORE_DATA
       for id, score of scores
         continue unless score.state is payload.state
+        scores[id] ||= {}
         scores[id].display = 'none'
       ScoreStore.emitChange()
     when AbilitysheetConstants.SHOW_SHEET_DATA
       for id, sheet of SheetStore.get()
         continue unless sheet.version is payload.version
+        scores[id] ||= {}
         scores[id].display = ''
       ScoreStore.emitChange()
     when AbilitysheetConstants.HIDE_SHEET_DATA
       for id, sheet of SheetStore.get()
         continue unless sheet.version is payload.version
+        scores[id] ||= {}
         scores[id].display = 'none'
       ScoreStore.emitChange()


Add epic points for Raider, and a guess for Gozanti.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -526,6 +526,7 @@
                 "Reinforce"
             ]
             huge: true
+            epic_points: 1.5
             maneuvers: [
                 [ 0, 0, 0, 0, 0, 0 ]
                 [ 0, 1, 1, 1, 0, 0 ]
@@ -545,6 +546,7 @@
                 "Target Lock"
             ]
             huge: true
+            epic_points: 1.5
             maneuvers: [
                 [ 0, 0, 0, 0, 0, 0 ]
                 [ 0, 1, 1, 1, 0, 0 ]
@@ -635,6 +637,7 @@
             hull: 9
             shields: 5
             huge: true
+            epic_points: 2 # guessing it's the same as rebel transport
             actions: [
                 "Recover"
                 "Reinforce"


srgb - persist show concluded enrollments

fixes CNVS-12034

test plan
- in srgb, enable show concluded enrollments
- refresh srgb, verify that it persists
- check gb2, verify that the setting persists
- disable the setting in srgb
- verify in srgb and gb2 that it is disabled
- in gb2, enable and disable
- verify that srgb setting matches that set in gb2

Change-Id: Ic62384c5757759c440edf4c5b392a776d4dbb30f
Reviewed-on: https://gerrit.instructure.com/32883
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Mike Nomitch <74443f6f110b6b7b934c89126258ed3d843d673f@instructure.com>
QA-Review: Caleb Guanzon <54fa1f30d451e4f3c341a8f3c47c9215fd0be9c6@instructure.com>
Product-Review: Liz Abinante <ce3305c9be70e1a5a9ff49df19e1a5b24d5a47f8@instructure.com>

--- app/coffeescripts/ember/screenreader_gradebook/controllers/screenreader_gradebook_controller.coffee
+++ app/coffeescripts/ember/screenreader_gradebook/controllers/screenreader_gradebook_controller.coffee
@@ -85,7 +85,15 @@

     hideStudentNames: false

-    showConcludedEnrollments: false
+    showConcludedEnrollments: (->
+      userSettings.contextGet('show_concluded_enrollments') or false
+    ).property().volatile()
+
+    updateshowConcludedEnrollmentsSetting: ( ->
+      isChecked = @get('showConcludedEnrollments')
+      if isChecked?
+        userSettings.contextSet 'show_concluded_enrollments', isChecked
+    ).observes('showConcludedEnrollments')

     selectedStudent: null


fix typo fatal

fix typo fatal

--- exp-listener.cjsx
+++ exp-listener.cjsx
@@ -48,16 +48,16 @@
           @setState
             exp: body.api_member_exp
     else if !@props.eoAccounted #listen before EOACCOUNTED
-        {path, body} = e.detail
-        {exRate} = @state
+      {path, body} = e.detail
+      {exRate} = @state
       switch path
-        when '/kcsapi/api_req_mission/result'
-          if body.api_get_exmap_rate isnt 0
+        when '/kcsapi/api_req_sortie/battleresult'
+          if body.api_get_exmap_rate? and body.api_get_exmap_rate isnt 0
             exRate += parseInt body.api_get_exmap_rate
             @setState {exRate}
         when '/kcsapi/api_req_map/next'
           if body.api_get_eo_rate?
-            exRate += body.api_get_eo_rate
+            exRate += parseInt body.api_get_eo_rate
             @setState {exRate}
   render: ->
     {baseSenka, data} = @props


change filter to exclude in getProjectDocs

--- services/document-updater/app/coffee/HttpController.coffee
+++ services/document-updater/app/coffee/HttpController.coffee
@@ -39,12 +39,12 @@

 	getProjectDocs: (req, res, next = (error) ->) ->
 		project_id = req.params.project_id
-		# filter is string of existing docs "id:version,id:version,..."
-		filterItems = req.query?.filter?.split(',') or []
-		logger.log project_id: project_id, filter: filterItems, "getting docs via http"
+		# exclude is string of existing docs "id:version,id:version,..."
+		excludeItems = req.query?.exclude?.split(',') or []
+		logger.log project_id: project_id, exclude: excludeItems, "getting docs via http"
 		timer = new Metrics.Timer("http.getAllDocs")
 		excludeVersions = {}
-		for item in filterItems
+		for item in excludeItems
 			[id,version] = item?.split(':')
 			excludeVersions[id] = version
 		logger.log {project_id: project_id, excludeVersions: excludeVersions}, "excluding versions"


add logging to each stage of archiving

add logging to each stage of archiving

--- services/track-changes/app/coffee/DocArchiveManager.coffee
+++ services/track-changes/app/coffee/DocArchiveManager.coffee
@@ -41,17 +41,22 @@
 						return callback()
 					MongoManager.getLastCompressedUpdate doc_id, (error, update) ->
 						return callback(error) if error?
+						logger.log {doc_id, project_id}, "archiving got last compressed update"
 						MongoManager.markDocHistoryAsArchiveInProgress doc_id, update, (error) ->
 							return callback(error) if error?
+							logger.log {doc_id, project_id}, "marked doc history as archive in progress"
 							MongoAWS.archiveDocHistory project_id, doc_id, update, (error) ->
 								if error?
+									logger.log {doc_id, project_id, error}, "error exporting document to S3"
 									MongoManager.clearDocHistoryAsArchiveInProgress doc_id, update, (err) ->
 										return callback(err) if err?
+										logger.log {doc_id, project_id}, "cleared archive in progress flag"
 										callback(error)
 								else
 									logger.log doc_id:doc_id, project_id:project_id, "exported document to S3"
 									MongoManager.markDocHistoryAsArchived doc_id, update, (error) ->
 										return callback(error) if error?
+										logger.log {doc_id, project_id}, "marked doc history as archived"
 										callback()

 	unArchiveAllDocsChanges: (project_id, callback = (error, docs) ->) ->


use replacement for trailing separator removal too

--- src/slug.coffee
+++ src/slug.coffee
@@ -89,5 +89,5 @@
         result += char
     result = result.replace(/^\s+|\s+$/g, '') # trim leading/trailing spaces
     result = result.replace(/[-\s]+/g, replacement) # convert spaces
-    result.replace(/-$/, '') # remove trailing separator
+    result.replace("#{replacement}$", '') # remove trailing separator


More chart fixes

More chart fixes

--- resources/assets/coffee/react/beatmapset-page/info.coffee
+++ resources/assets/coffee/react/beatmapset-page/info.coffee
@@ -90,8 +90,8 @@
                 ]

       div className: 'beatmapset-info__box beatmapset-info__box--success-rate',
-        div className: 'beatmap-success-rate',
-          if @props.beatmapset.has_scores
+        if @props.beatmapset.has_scores
+          div className: 'beatmap-success-rate',
             h3
               className: 'beatmap-success-rate__header'
               osu.trans 'beatmapsets.show.info.success-rate'
@@ -115,7 +115,8 @@
             div
               className: 'beatmap-success-rate__chart'
               ref: 'chartArea'
-          else
+        else
+          div className: 'beatmap-success-rate',
             div
               className: 'beatmap-success-rate__empty'
               osu.trans 'beatmapsets.show.info.no_scores'


Hide menu page title when showing menu

Hide menu page title when showing menu

--- resources/assets/coffee/react/changelog-build/main.coffee
+++ resources/assets/coffee/react/changelog-build/main.coffee
@@ -94,7 +94,7 @@

   renderHeaderTitle: =>
     div className: 'osu-page-header-v3 osu-page-header-v3--changelog',
-      div className: 'osu-page-header-v3__title js-nav2--header-title',
+      div className: 'osu-page-header-v3__title js-nav2--hidden-on-menu-access',
         div className: 'osu-page-header-v3__title-icon',
           div className: 'osu-page-header-v3__icon'
         h1


Minor fixes.

Minor fixes.

--- src/spec.coffee
+++ src/spec.coffee
@@ -443,7 +443,7 @@

         if not $isVMRunning.call this
           {
-            size: +@genval.memory_dynamic_min
+            size: +@genval.memory_static_max
           }
         else if (memory = guest_metrics?.memory)?.used
           {
@@ -455,7 +455,7 @@
             size: if metrics
               +metrics.memory_actual
             else
-              +@genval.memory_dynamic_min
+              +@genval.memory_static_max
           }

       power_state: -> @genval.power_state
@@ -470,7 +470,7 @@
       CPUs: {
         number: ->
           {metrics} = @data
-          if metrics
+          if ($isVMRunning.call this) and metrics
             +metrics.VCPUs_number
           else
             +@genval.VCPUs_at_startup


Move editor property definition

Move editor property definition

--- src/trix/elements/trix_editor_element.coffee
+++ src/trix/elements/trix_editor_element.coffee
@@ -68,10 +68,6 @@

   # Properties

-  editor:
-    get: ->
-      @editorController?.editor
-
   trixId:
     get: ->
       if @hasAttribute("trix-id")
@@ -101,6 +97,10 @@
         element = makeElement("input", type: "hidden", id: inputId)
         @parentElement.insertBefore(element, @nextElementSibling)
         element
+
+  editor:
+    get: ->
+      @editorController?.editor

   name:
     get: ->


fix(spellcheck): Unwrap nodes with simpler code with less side effects

--- internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.coffee
+++ internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.coffee
@@ -13,7 +13,7 @@
     SpellcheckCache[word]

   @onContentChanged: ({editor}) =>
-    @walkTree(editor)
+    @update(editor)

   @onShowContextMenu: ({editor, event, menu}) =>
     selection = editor.currentSelection()
@@ -39,86 +39,115 @@

   @applyCorrection: (editor, range, selection, correction) =>
     DOMUtils.Mutating.applyTextInRange(range, selection, correction)
-    @walkTree(editor)
+    @update(editor)

   @learnSpelling: (editor, word) =>
     spellchecker.add(word)
     delete SpellcheckCache[word]
-    @walkTree(editor)
+    @update(editor)

-  @walkTree: (editor) =>
-    # Remove all existing spellcheck nodes
-    spellingNodes = editor.rootNode.querySelectorAll('spelling')
-    for node in spellingNodes
-      editor.whilePreservingSelection =>
-        DOMUtils.unwrapNode(node)
+  @update: (editor) =>
+    @_unwrapWords(editor)
+    @_wrapMisspelledWords(editor)

-    # Normalize to make sure words aren't split across text nodes
-    editor.rootNode.normalize()
-
+  # Creates a shallow copy of a selection object where anchorNode / focusNode
+  # can be changed, and provides it to the callback provided. After the callback
+  # runs, it applies the new selection if `snapshot.modified` has been set.
+  #
+  # Note: This is different from ExposedSelection because the nodes are not cloned.
+  # In the callback functions, we need to check whether the anchor/focus nodes
+  # are INSIDE the nodes we're adjusting.
+  #
+  @_whileApplyingSelectionChanges: (cb) =>
     selection = document.getSelection()
     selectionSnapshot =
       anchorNode: selection.anchorNode
       anchorOffset: selection.anchorOffset
       focusNode: selection.focusNode
       focusOffset: selection.focusOffset
-    selectionImpacted = false
+      modified: false

-    treeWalker = document.createTreeWalker(editor.rootNode, NodeFilter.SHOW_TEXT)
-    nodeList = []
-    nodeMisspellingsFound = 0
+    cb(selectionSnapshot)

-    while (treeWalker.nextNode())
-      nodeList.push(treeWalker.currentNode)
+    if selectionSnapshot.modified
+      selection.setBaseAndExtent(selectionSnapshot.anchorNode, selectionSnapshot.anchorOffset, selectionSnapshot.focusNode, selectionSnapshot.focusOffset)

-    # Note: As a performance optimization, we stop spellchecking after encountering
-    # 10 misspelled words. This keeps the runtime of this method bounded!
+  # Removes all of the <spelling> nodes found in the provided `editor`.
+  # It normalizes the DOM after removing spelling nodes to ensure that words
+  # are not split between text nodes. (ie: doesn, 't => doesn't)
+  @_unwrapWords: (editor) =>
+    @_whileApplyingSelectionChanges (selectionSnapshot) =>
+      spellingNodes = editor.rootNode.querySelectorAll('spelling')

-    while (node = nodeList.shift())
-      break if nodeMisspellingsFound > 10
-      str = node.textContent
+      for node in spellingNodes
+        if selectionSnapshot.anchorNode is node
+          selectionSnapshot.anchorNode = node.firstChild
+        if selectionSnapshot.focusNode is node
+          selectionSnapshot.focusNode = node.firstChild

-      # https://regex101.com/r/bG5yC4/1
-      wordRegexp = /(\w[\w'’-]*\w|\w)/g
+        selectionSnapshot.modified = true
+        node.parentNode.insertBefore(node.firstChild, node) while (node.firstChild)
+        node.parentNode.removeChild(node)

-      while ((match = wordRegexp.exec(str)) isnt null)
-        break if nodeMisspellingsFound > 10
-        misspelled = @isMisspelled(match[0])
+    editor.rootNode.normalize()

-        if misspelled
-          # The insertion point is currently at the end of this misspelled word.
-          # Do not mark it until the user types a space or leaves.
-          if selectionSnapshot.focusNode is node and selectionSnapshot.focusOffset is match.index + match[0].length
-            continue
+  # Traverses all of the text nodes within the provided `editor`. If it finds a
+  # text node with a misspelled word, it splits it, wraps the misspelled word
+  # with a <spelling> node and updates the selection to account for the change.
+  @_wrapMisspelledWords: (editor) =>
+    @_whileApplyingSelectionChanges (selectionSnapshot) =>
+      treeWalker = document.createTreeWalker(editor.rootNode, NodeFilter.SHOW_TEXT)
+      nodeList = []
+      nodeMisspellingsFound = 0

-          if match.index is 0
-            matchNode = node
-          else
-            matchNode = node.splitText(match.index)
-          afterMatchNode = matchNode.splitText(match[0].length)
+      while (treeWalker.nextNode())
+        nodeList.push(treeWalker.currentNode)

-          spellingSpan = document.createElement('spelling')
-          spellingSpan.classList.add('misspelled')
-          spellingSpan.innerText = match[0]
-          matchNode.parentNode.replaceChild(spellingSpan, matchNode)
+      # Note: As a performance optimization, we stop spellchecking after encountering
+      # 30 misspelled words. This keeps the runtime of this method bounded!

-          for prop in ['anchor', 'focus']
-            if selectionSnapshot["#{prop}Node"] is node
-              if selectionSnapshot["#{prop}Offset"] > match.index + match[0].length
-                selectionImpacted = true
-                selectionSnapshot["#{prop}Node"] = afterMatchNode
-                selectionSnapshot["#{prop}Offset"] -= match.index + match[0].length
-              else if selectionSnapshot["#{prop}Offset"] > match.index
-                selectionImpacted = true
-                selectionSnapshot["#{prop}Node"] = spellingSpan.childNodes[0]
-                selectionSnapshot["#{prop}Offset"] -= match.index
+      while (node = nodeList.shift())
+        break if nodeMisspellingsFound > 30
+        str = node.textContent

-          nodeMisspellingsFound += 1
-          nodeList.unshift(afterMatchNode)
-          break
+        # https://regex101.com/r/bG5yC4/1
+        wordRegexp = /(\w[\w'’-]*\w|\w)/g

-    if selectionImpacted
-      selection.setBaseAndExtent(selectionSnapshot.anchorNode, selectionSnapshot.anchorOffset, selectionSnapshot.focusNode, selectionSnapshot.focusOffset)
+        while ((match = wordRegexp.exec(str)) isnt null)
+          break if nodeMisspellingsFound > 30
+          misspelled = @isMisspelled(match[0])
+
+          if misspelled
+            # The insertion point is currently at the end of this misspelled word.
+            # Do not mark it until the user types a space or leaves.
+            if selectionSnapshot.focusNode is node and selectionSnapshot.focusOffset is match.index + match[0].length
+              continue
+
+            if match.index is 0
+              matchNode = node
+            else
+              matchNode = node.splitText(match.index)
+            afterMatchNode = matchNode.splitText(match[0].length)
+
+            spellingSpan = document.createElement('spelling')
+            spellingSpan.classList.add('misspelled')
+            spellingSpan.innerText = match[0]
+            matchNode.parentNode.replaceChild(spellingSpan, matchNode)
+
+            for prop in ['anchor', 'focus']
+              if selectionSnapshot["#{prop}Node"] is node
+                if selectionSnapshot["#{prop}Offset"] > match.index + match[0].length
+                  selectionSnapshot.modified = true
+                  selectionSnapshot["#{prop}Node"] = afterMatchNode
+                  selectionSnapshot["#{prop}Offset"] -= match.index + match[0].length
+                else if selectionSnapshot["#{prop}Offset"] > match.index
+                  selectionSnapshot.modified = true
+                  selectionSnapshot["#{prop}Node"] = spellingSpan.childNodes[0]
+                  selectionSnapshot["#{prop}Offset"] -= match.index
+
+            nodeMisspellingsFound += 1
+            nodeList.unshift(afterMatchNode)
+            break

   @finalizeSessionBeforeSending: ({session}) ->
     body = session.draft().body


lets use 30 second timeout on test

lets use 30 second timeout on test

--- src/test.coffee
+++ src/test.coffee
@@ -10,7 +10,7 @@
   throw new Error("Missing CLIENT_ID and/or CLIENT_SECRET")

 describe 'Youku', ->
-  this.timeout 10000
+  this.timeout 30000
   describe 'Constructor', ->
     describe 'new Youku()', ->
       defaults = {}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text.coffee
+++ src/text.coffee
@@ -1,7 +1,7 @@
 Score = (I={}) ->
   Object.reverseMerge I,
     duration: 40
-    font: 'V'
+    font: 'VT'
     points: 500
     alpha: 1


findAll implementation

findAll implementation

--- app/adapters/application.coffee
+++ app/adapters/application.coffee
@@ -39,8 +39,13 @@
         catch error
             reject error

-    findAll: (store, type, sinceToken) ->
-        throw "I DIDN'T IMPLEMENT THIS SHIT"
+    findAll: (store, type) -> new Promise (resolve) =>
+        ids = @getModelIds type
+        promises = _.collect ids, (id) => @find store, type, id
+        Ember.RSVP.all(promises).then (results) ->
+            result = { }
+            result[type.typeKey] = _.pluck results, type.typeKey
+            resolve result

     findQuery: (store, type, query) ->
         throw "I DIDN'T IMPLEMENT THIS SHIT"


Show link hints when aria-hidden=true

Show link hints when aria-hidden=true

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -637,8 +637,8 @@
           visibleElements.push areasAndRects...

     # Check aria properties to see if the element should be ignored.
-    if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or
-        element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"])
+    # Note that we're showing hints for elements with aria-hidden=true. See #3501 for discussion.
+    if (element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"])
       return [] # This element should never have a link hint.

     # Check for AngularJS listeners on the element.


Reinstate highlighting of first hint.

Reinstate highlighting of first hint.

Applies to filtered hints only.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -159,7 +159,7 @@
     @stableSortCount = 0
     @hintMarkers = (@createMarkerFor desc for desc in hintDescriptors)
     @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints)
-    @markerMatcher.fillInMarkers @hintMarkers
+    @markerMatcher.fillInMarkers @hintMarkers, @.getNextZIndex.bind this

     @hintMode = new Mode
       name: "hint/#{@mode.name}"
@@ -507,12 +507,12 @@
     marker.innerHTML = spanWrap(marker.hintString +
         (if marker.showLinkText then ": " + linkText else ""))

-  fillInMarkers: (hintMarkers) ->
+  fillInMarkers: (hintMarkers, getNextZIndex) ->
     @renderMarker marker for marker in hintMarkers when marker.isLocalMarker

-    # We use @filterLinkHints() here (although we know that all of the hints will match) to fill in the hint
-    # strings.  This ensures that we always get hint strings in the same order.
-    @filterLinkHints hintMarkers
+    # We use @getMatchingHints() here (although we know that all of the hints will match) to get an order on
+    # the hints and highlight the first one.
+    @getMatchingHints hintMarkers, 0, getNextZIndex

   getMatchingHints: (hintMarkers, tabCount, getNextZIndex) ->
     # At this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest


#101 intercept narrow:close with 2 purpose( put in comment)

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -75,6 +75,7 @@
   registerCommands: ->
     atom.commands.add @editorElement,
       'core:confirm': => @confirm()
+      'narrow:close': (event) => @narrowClose(event)
       'narrow-ui:confirm-keep-open': => @confirm(keepOpen: true)
       'narrow-ui:protect': => @toggleProtect()
       'narrow-ui:preview-item': => @preview()
@@ -288,6 +289,17 @@

       @provider.updateRealFile(changes)
       @setModifiedState(false)
+
+  # This function is mapped from `narrow:close`
+  # To differentiate `narrow:close` for protected narrow-editor.
+  # * Two purpose.
+  # 1. So that don't close non-protected narrow-editor when narrow:close is
+  #   invoked from protected narrow-editor
+  # 2. To re-focus to caller editor for not interfering regular preview-then-close-by-ctrl-g flow.
+  narrowClose: (event) ->
+    if @isProtected()
+      event.stopImmediatePropagation()
+      @activateProviderPane()

   # Just setting cursor position works but it lost goalColumn when that row was skip item's row.
   moveUpOrDown: (event, direction) ->


add test for clientId -> siteId map

add test for clientId -> siteId map

--- src/packages/collaboration/spec/collaboration-spec.coffee
+++ src/packages/collaboration/spec/collaboration-spec.coffee
@@ -126,3 +126,7 @@
         expect(hostParticipantExitedHandler).toHaveBeenCalledWith(login: 'octocat', clientId: guestSession.clientId)
         expect(leaderSession.getParticipants()).toEqual [login: 'hubot', clientId: leaderSession.clientId]
         expect(leaderSession.getOtherParticipants()).toEqual []
+
+        siteIdMap = leaderSession.getClientIdToSiteIdMap()
+        expect(siteIdMap.get(leaderSession.clientId)).toEqual 1
+        expect(siteIdMap.get(guestSession.clientId)).toEqual 2


Another variable name typo.

Another variable name typo.

NOTE: I need to build a test suite already.

--- src/view.coffee
+++ src/view.coffee
@@ -132,7 +132,7 @@
             else
                 success = true

-            if sucess is false then return false
+            if success is false then return false

             @root.trigger(@eventPrefix + 'transition', {
                 from: @state


SocialWorker: JGroup: remove MemberAdded, MemberRemoved shared static events

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -96,8 +96,6 @@
       slug          : 'unique'
     sharedEvents    :
       static        : [
-        { name: 'MemberAdded',      filter: -> null }
-        { name: 'MemberRemoved',    filter: -> null }
         { name: 'MemberRolesChanged' }
         { name: 'GroupDestroyed' }
         { name: 'broadcast' }


Update bundle doesn't use limits anymore.

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1298,13 +1298,11 @@
   updateBundle: (formData, callback = (->)) ->
     @fetchBundle (err, bundle) =>
       return callback err  if err?
-      bundle.update $set: { overagePolicy: formData.overagePolicy,  }, callback
-      bundle.fetchLimits (err, limits) ->
-        return callback err  if err?
-        queue = limits.map (limit) -> ->
-          limit.update { $set: quota: formData.quotas[limit.title] }, fin
-        dash queue, callback
-        fin = queue.fin.bind queue
+      bundle.update $set: {
+        overagePolicy: formData.overagePolicy
+        sharedVM     : formData.sharedVM
+        allocation   : formData.allocation
+      }, callback

   updateBundle$: permit 'change bundle',
     success: (client, formData, callback)->


remove coffeelint task

remove coffeelint task

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -93,9 +93,6 @@
 		#   $CONTRIB-WATCH
 		#*------------------------------------*/
 		watch:
-			gruntfile:
-				files: 'Gruntfile.coffee',
-				tasks: ['coffeelint']
 			css:
 				files: [
 					'<%= pkg.path.scss %>/**/*.scss',


Added a second focus event half a second later becasue firefox doesn't handle the first one properly

--- SingularityUI/app/models/Request.coffee
+++ SingularityUI/app/models/Request.coffee
@@ -532,7 +532,9 @@
                     $('#browse-to-sandbox').on('click', () => $('#filename').prop('disabled', true))
                     $('#autoTail').on('click', () => $('#filename').prop('disabled', false))
                     $('#filename').prop 'disabled', false if taskRunAfterStart is 'autoTail'
-                    $('.vex-dialog-button, .vex-primary, .vex-first').focus()
+                    focusFn = () => $('.vex-dialog-button, .vex-primary, .vex-first').focus()
+                    focusFn()
+                    setTimeout focusFn, 500 # For Firefox...

                 callback: (data) =>
                     if data.commandLineInput


Do operator instead of immediate execution.

--- src/wolf.coffee
+++ src/wolf.coffee
@@ -31,12 +31,11 @@
     return destination

 # Return a unique id, with an optional prefix.
-wolf.getUniqueId = (() ->
+wolf.getUniqueId = do ->
     id = 0
     return (prefix="") ->
         id += 1
         return prefix + id
-)()

 # Patch any missing properties from defaults to options and return
 # the resulting object.


Add keymaps

Add keymaps

--- keymaps/magic-tag.cson
+++ keymaps/magic-tag.cson
@@ -7,5 +7,8 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-# 'atom-workspace':
-#   'ctrl-alt-o': 'magic-tag:delete'
+'.platform-darwin atom-text-editor:not([mini])':
+  'cmd-t cmd-d': 'magic-tag:delete'
+
+'.platform-win32, .platform-linux atom-text-editor:not([mini])':
+  'ctrl-t ctrl-d': 'magic-tag:delete'


update content model's url to include .json

--- assets/js/models/content.coffee
+++ assets/js/models/content.coffee
@@ -1,3 +1,6 @@
 define ['backbone', 'backbone_relational'], (Backbone) ->
   class Content extends Backbone.RelationalModel
     urlRoot: '/api/content'
+
+    url: ->
+      "#{@urlRoot}/#{encodeURIComponent(@id)}.json"


add more Sublime Text files

add more Sublime Text files

--- grammars/sublime-text.cson
+++ grammars/sublime-text.cson
@@ -1,3 +1,4 @@
+# Lets add all Sublime Text JSON files, not just those support by the package
 fileTypes: [
   "sublime-build"
   "sublime-commands"


Test merging user data

Test merging user data

--- test/bot.coffee
+++ test/bot.coffee
@@ -319,6 +319,21 @@
     should.equal userperiod.real_name, @stubs.userperiod.real_name
     should.equal userperiod.email_address, @stubs.userperiod.profile.email

+  it 'Should merge with user data which is stored by other program', ->
+    originalUser =
+      something: 'something'
+
+    @slackbot.robot.brain.userForId @stubs.user.id, originalUser
+    @slackbot.loadUsers(null, @stubs.responseUsersList)
+
+    user = @slackbot.robot.brain.data.users[@stubs.user.id]
+    should.equal user.id, @stubs.user.id
+    should.equal user.name, @stubs.user.name
+    should.equal user.real_name, @stubs.user.real_name
+    should.equal user.email_address, @stubs.user.profile.email
+    should.equal user.slack.misc, @stubs.user.misc
+    should.equal user.something, originalUser.something
+
   it 'Should detect wrong response from web api', ->
     @slackbot.loadUsers(null, @stubs.wrongResponseUsersList)
     should.equal @slackbot.robot.brain.data.users[@stubs.user.id], undefined


Tests: fill out UI test suite!

Tests: fill out UI test suite!

--- test/ui._coffee
+++ test/ui._coffee
@@ -1,13 +1,126 @@
+#
+# General UI tests.
+#
+# TODO: We should have a separate test suite for properly testing the image
+# submission flow, e.g. new --> queued --> progress --> ready or failed.
+# That could/should cover both the UI and API since states are transient.
+# That should also test that fetching the actual image tiles works too.
+#
+
 app = (require 'supertest')(require '../app')
 {expect} = require 'chai'
+urls = require './fixtures/urls'

+
+## Helpers
+
+# Matches e.g. '/Abc123' and captures the ID:
+VIEW_PAGE_REGEX = /// ^/ (\w+) $ ///
+
+EXISTING_ID = null
+
+
+## Tests

 describe 'UI', ->

-    describe 'Homepage', ->
+    describe 'Home page', ->

         it 'should return Zoom.it copy', (_) ->
             app.get '/'
                 .expect 200
-                .expect /Zoom\.it/i
+                .expect /Zoom\.it/
                 .end _
+
+    describe 'About page', ->
+
+        # TEMP
+        it 'should redirect to the homepage for now', (_) ->
+            app.get '/pages/about'
+                .expect 302
+                .expect 'Location', '/'
+                .end _
+
+    describe 'Create page', ->
+
+        # TEMP
+        it 'should redirect to the homepage for now', (_) ->
+            app.get '/pages/create'
+                .expect 302
+                .expect 'Location', '/'
+                .end _
+
+    describe 'URL handler', ->
+
+        it 'should reject empty URLs', (_) ->
+            app.get '/?url='
+                .expect 400
+                .expect /Missing/i
+                .end _
+
+        it 'should reject malformed URLs', (_) ->
+            app.get "/?url=#{encodeURIComponent urls.MALFORMED}"
+                .expect 400
+                .expect /full URL/i
+                .end _
+
+        it 'should reject URLs w/out protocol', (_) ->
+            app.get "/?url=#{encodeURIComponent urls.NO_PROTOCOL}"
+                .expect 400
+                .expect /full URL/i
+                .end _
+
+        it 'should reject non-HTTP URLs', (_) ->
+            app.get "/?url=#{encodeURIComponent urls.NON_HTTP}"
+                .expect 400
+                .expect /full URL/i
+                .end _
+
+        # TEMP
+        it 'should reject new HTTP URLs for now', (_) ->
+            app.get "/?url=#{encodeURIComponent urls.randomize urls.IMAGE}"
+                .expect 503
+                .expect /Please wait/i
+                .end _
+
+        # TODO: Need reliable existing image across local dev envs.
+        it.skip 'should redirect existing HTTP URLs to view page', (_) ->
+            resp = app.get "/?url=#{encodeURIComponent urls.IMAGE}"
+                .expect 301
+                .expect 'Location', VIEW_PAGE_REGEX
+                .end _
+
+            EXISTING_ID = (resp.headers.location.match VIEW_PAGE_REGEX)[1]
+
+    describe 'View page', ->
+
+        # TODO: Need reliable existing image across local dev envs.
+        it.skip 'should return viewer for existing image', (_) ->
+            app.get "/#{EXISTING_ID}"
+                .expect 200
+                .expect /// #{EXISTING_ID} ///  # anywhere, e.g. <title>
+                .end _
+
+        it 'should return 404 for non-existent image', (_) ->
+            app.get '/99999999'
+                .expect 404
+                .expect /No content/i
+                .end _
+
+    describe 'Embed', ->
+
+        # TODO: Need reliable existing image across local dev envs.
+        it.skip 'should return JS for existing image', (_) ->
+            app.get "/#{EXISTING_ID}.js"
+                .expect 200
+                .expect 'Content-Type', 'application/javascript'
+                .expect /// "#{EXISTING_ID}" ///  # e.g. as JSON
+                .end _
+
+        # TEMP: We should be returning JS instead; known FIXME in the code.
+        # Until that's fixed, testing our current behavior.
+        it 'should return 404 for non-existent image', (_) ->
+            app.get '/99999999.js'
+                .expect 404
+                .expect /No content/i
+                .end _


fixes #3782. double-clicking on red (out of range) assets will not result in a selection

--- web/volume/slot.coffee
+++ web/volume/slot.coffee
@@ -187,7 +187,7 @@

       select: (event) ->
         return false if $scope.editing == 'position'
-        ruler.selection = new TimeSegment(if @full then null else @)
+        ruler.selection = new TimeSegment(if @full || !ruler.range.overlaps(@) then null else @)
         if isFinite(@l) && !@contains(ruler.position)
           seekOffset(@l)
         else if @full


Remove debug output

Remove debug output

--- app/assets/javascripts/rglossa/components/centre/results/geo_distribution_map_window.jsx.coffee
+++ app/assets/javascripts/rglossa/components/centre/results/geo_distribution_map_window.jsx.coffee
@@ -260,8 +260,6 @@
         label: key)
     else []

-    console.log(@props.data)
-
     `

         <GeoDistributionMap ref="map" initLat={62} initLon={6} initZoom={4} width={650} height={540} points={points} />


Remove unused prop

Remove unused prop

--- app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_regex_input.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_regex_input.jsx.coffee
@@ -5,7 +5,6 @@
     query: React.PropTypes.string.isRequired
     handleQueryChanged: React.PropTypes.func.isRequired
     handleSearch: React.PropTypes.func.isRequired
-    handleKeyDown: React.PropTypes.func.isRequired

   componentDidMount: ->
     @refs.searchfield.getDOMNode().focus()


display task id in tracing view #1018

display task id in tracing view #1018

--- app/assets/javascripts/oxalis/view/viewmode/right-menu/dataset_info_view.coffee
+++ app/assets/javascripts/oxalis/view/viewmode/right-menu/dataset_info_view.coffee
@@ -20,7 +20,6 @@

   templateHelpers :
     chooseUnit : ->
-
       if(@zoomLevel < 1000)
         return @zoomLevel.toFixed(0) + " nm"
       else if (@zoomLevel < 1000000)
@@ -42,8 +41,14 @@

   serializeData : ->

+    annotationType = @model.get("tracingType")
+    task = @model.get("tracing").task
+
+    # In case we have a task display its id as well
+    if task then annotationType += " #{task.formattedHash}"
+
     return {
-      annotationType : @model.get("tracingType")
+      annotationType : annotationType
       zoomLevel : @calculateZoomLevel()
       dataSetName : @model.get("dataset").get("name")
       treeCount : @model.skeletonTracing?.trees.length


Assign notifications in Atom environment constructor

--- src/atom.coffee
+++ src/atom.coffee
@@ -151,12 +151,17 @@

   # Call .loadOrCreate instead
   constructor: (@state) ->
+    {@mode} = @state
+
     @emitter = new Emitter
     @disposables = new CompositeDisposable
-    {@mode} = @state
+
     DeserializerManager = require './deserializer-manager'
     @deserializers = new DeserializerManager()
     @deserializeTimings = {}
+
+    NotificationManager = require './notification-manager'
+    @notifications = new NotificationManager

   # Sets up the basic services that should be available in all modes
   # (both spec and application).
@@ -196,7 +201,6 @@
     ViewRegistry = require './view-registry'
     CommandRegistry = require './command-registry'
     TooltipManager = require './tooltip-manager'
-    NotificationManager = require './notification-manager'
     PackageManager = require './package-manager'
     Clipboard = require './clipboard'
     GrammarRegistry = require './grammar-registry'
@@ -220,7 +224,6 @@
     @keymaps = new KeymapManager({configDirPath, resourcePath})
     @keymaps.subscribeToFileReadFailure()
     @tooltips = new TooltipManager
-    @notifications = new NotificationManager
     @commands = new CommandRegistry
     @views = new ViewRegistry
     @registerViewProviders()


Fix modal spin

Fix modal spin

--- src/base.coffee
+++ src/base.coffee
@@ -45,7 +45,7 @@
   @hide: ->
     console.log("ModalSpin.hide(#{@count} -> #{@count-1})")
     @count -= 1
-    @spin.hide() if @count == 0
+    @spin.modal('hide') if @count == 0

 ###*
 @class


Update translations.coffee

Update translations.coffee

--- addon/locales/zh-tw/translations.coffee
+++ addon/locales/zh-tw/translations.coffee
@@ -155,7 +155,7 @@
     "driver_completed": "司機已確認預約"
     "waiting": "正在等候司機確認"
     "display_driver_details": "司機確認預約後，閣下即能查閱司機及車輛的資料。"
-    "ensure_time": "Please ensure your delivery will arrive at our location by 4pm at the latest. Our volunteers will unload the vehicle at the end."
+    "ensure_time": "請確保在下午4時或之前送抵本會。我們的義工會代為落貨。"
     "time": "預約時間"
     "id": "預約帳號"
     "driver_screen": "司機介面"


Update translations.coffee

Update translations.coffee

--- addon/locales/zh-tw/translations.coffee
+++ addon/locales/zh-tw/translations.coffee
@@ -271,7 +271,7 @@
       "thanks" : "確定，謝謝！"

     "confirm_van" :
-      "crossroads": "Crossroads (Tuen Mun)"
+      "crossroads": "十字路會（屯門）"
       "other-msg" : "並不包括隧道費及泊車費等費用"
       "title" : "確定車輛"
       "base_fee" : "基本費用"
@@ -288,8 +288,8 @@
       "goods_6ft": "Goods >6ft"
       "trolley": "Trolley"
       "estimate": "Estimate"
-      "confirm_total_price": "Estimate does not include tunnel/bridge fees, parking and other misc. fees. Please confirm total price with driver before loading."
-      "ggv_link": "More about fees on the GoGoVan website"
+      "confirm_total_price": "預計價格並不包括隧道或天橋費、泊車以及其他雜費。請在上貨前與司機確認最終價錢。"
+      "ggv_link": "請到GoGovan網站查閱更多有關費用詳情。"

     "book_van" :
       "title" : "預約貨車"
@@ -301,9 +301,9 @@
       "van_trolly_cost" : "需借用手推車（每輛10元）"
       "truck_trolly_cost" : "需借用手推車（每輛20元）"
       "porterage" : "需協助搬運（需與司機商量）"
-      "longer_goods": "Goods longer than 6ft (183cm)"
-      "longer_goods_6ft": "longer than 6ft +$10"
-      "longer_goods_6ft_tall": "longer than 6ft & taller than 2ft(60cm) +$20"
+      "longer_goods": "貨物超過6尺長（183厘米）"
+      "longer_goods_6ft": "超過6尺長 +$10"
+      "longer_goods_6ft_tall": "超過6尺長和2尺高（60厘米） +$20"
       "extra_time_charge" : "額外時間（已包括15分鐘搬運時間，超過15分鐘可能需要額外收費）"
       "get_quote" : "報價"
       "date": "日期"


fix behaviour of empty history

fix behaviour of empty history

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3866,8 +3866,9 @@
             if @$('.no_history').length > 0
                 @$('.no_history').slideUp 'fast', ->
                     $(@).remove()
-                    that.container.adjust_collapsible_panel_height
-                        is_at_bottom: is_at_bottom
+                    if that.state is 'visible'
+                        that.container.adjust_collapsible_panel_height
+                            is_at_bottom: is_at_bottom
             else if @state is 'visible'
                 @container.adjust_collapsible_panel_height
                     delay_scroll: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -308,6 +308,16 @@
   equal color2.b, 19
   equal color2.a, 1.0

+test "#darken$", ->
+  color = Color2(45, 64, 39)
+
+  color.darken$(0.1)
+
+  equal color.r, 22
+  equal color.g, 32
+  equal color.b, 19
+  equal color.a, 1.0
+
 test "#lighten", ->
   color = Color2(22, 32, 19)

@@ -317,6 +327,16 @@
   equal color2.g, 64
   equal color2.b, 39
   equal color2.a, 1.0
+
+test "#lighten$", ->
+  color = Color2(22, 32, 19)
+
+  color.lighten$(0.1)
+
+  equal color.r, 45
+  equal color.g, 64
+  equal color.b, 39
+  equal color.a, 1.0

 test "#mixWith", ->
   color1 = Color2(50, 40, 60, 0.3)


Comment

Comment

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2370,7 +2370,7 @@
             try
                 @current_results = []

-                # If the user can show more results, it means that we have buffer one row, so we put it back in @current_results
+                # If there are more results to show, we had to buffer one row, so we add that one extra row to the current results
                 @current_results.push @extra_row
                 @extra_row = undefined

@@ -2585,7 +2585,7 @@
                         if @current_results.length < @limit # We display @limit results per page, if we don't have enough, we keep requesting more
                             @current_results.push data
                             @state.cursor.next get_result_callback
-                        else if @current_results.length is @limit # If we have enough result, we still want to know if there are more results available
+                        else if @current_results.length is @limit # If we've reached the limit of results we can show, we still want to know if there are more results available
                             @extra_row = data # We requested an extra row, but won't display it on the current page, so we need to save it for later (when the user wants to show more results)
                             get_result_callback() # Display results
                         return true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -169,11 +169,13 @@
   ok color1.equal(color2)

 test "#toString", ->
-  noAlpha = Color(30, 40, 23)
-  withAlpha = Color(29, 49, 50, 0.45)
+  noAlpha = Color2(30, 40, 23)
+  withAlpha = Color2(29, 49, 50, 0.45)
+  decimalValues = Color2(34.7, 52.2, 50.1, 0.45)

   equal noAlpha.toString(), "rgba(30, 40, 23, 1)"
   equal withAlpha.toString(), "rgba(29, 49, 50, 0.45)"
+  equal decimalValues.toString(), "rgba(35, 52, 50, 0.45)"

 test "#toHex", ->
   color = Color2(23, 45, 100)
@@ -181,11 +183,10 @@
   equal color.toHex(), "#172d64"

 test "#toHsl", ->
-  color = Color(45, 29, 20)
+  color = Color2(45, 29, 20)

   hsl = color.toHsl()

-  # why won't this value round in #toHsl???
   equal hsl[0].round(), 22
   equal hsl[1].toFixed(2), 0.38
   equal hsl[2].toFixed(2), 0.13


do not check the error code when audio/video retrieving fails

--- src/frontend/connections.coffee
+++ src/frontend/connections.coffee
@@ -255,9 +255,10 @@
                 @sendStreamState id
             @emitter.trigger 'camera.localstream', (createVideoTag stream)
         , (error) =>
-            if error? and error.code is 1
-                @emitter.trigger 'notif.text.ko',
-                    text : "It seems that we can't access your hardware."
+            do @toggleCamera if @userMedia.video
+            do @toggleMicro if @userMedia.audio
+            @emitter.trigger 'notif.text.ko',
+                text : "It seems that we can't access your hardware."
             @emitter.trigger 'activable.unlock'
             if not MOZILLA and $('p#messageCam').hasClass "hidden"
                 $('p#messageCam').removeClass "hidden"


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -24,6 +24,7 @@
     width: 10
     height: 10

+
   fastEnemy.include(Follow)
   fastEnemy.follow(player)


Optimize the mkdirp test

Optimize the mkdirp test

--- test/mkdirp.coffee
+++ test/mkdirp.coffee
@@ -4,6 +4,7 @@

 test "mkdirp", ->
   deepDir = 'test/mkdirp/deep/dir'
+
   process.argv = [
     'node', 'coffee'
     '-o', deepDir
@@ -11,20 +12,16 @@
   ]
   command.run()

-  compiledPath = path.join(
-    deepDir
-    path.basename(__filename, '.coffee') + '.js'
-  )
-
-  # Coffee's test suit doesn't support async task.
-  # Here some hack.
-  setTimeout ->
-    ok fs.existsSync(compiledPath)
-  , 1000
+  name = path.basename __filename, '.coffee'
+  compiledPath = path.join deepDir, "#{name}.js"

   process.on 'exit', ->
-    fs.unlinkSync compiledPath
-    fs.rmdirSync 'test/mkdirp/deep/dir'
-    fs.rmdirSync 'test/mkdirp/deep'
-    fs.rmdirSync 'test/mkdirp'
+    exists = fs.existsSync compiledPath

+    try
+      fs.unlinkSync compiledPath
+      fs.rmdirSync 'test/mkdirp/deep/dir'
+      fs.rmdirSync 'test/mkdirp/deep'
+      fs.rmdirSync 'test/mkdirp'
+
+    ok exists


Show loss as the last series in Sankey

Show loss as the last series in Sankey

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -24,13 +24,13 @@
           {id: 'agriculture',                  column: 2, label: 'agriculture',            color: '#FFD700'},
           {id: 'flexibility',                  column: 2, label: 'flexibility',            color: '#013220'},
           {id: 'other',                        column: 2, label: 'other',                  color: '#E07033'},
-          {id: 'loss',                         column: 2, label: 'loss',                   color: '#DCDCDC'},
           {id: 'export1',                      column: 2, label: 'export1',                color: '#43464B'},
           {id: 'export2',                      column: 2, label: 'export2',                color: '#43464B'},
           {id: 'export3',                      column: 2, label: 'export3',                color: '#43464B'},
           {id: 'export4',                      column: 2, label: 'export4',                color: '#43464B'},
           {id: 'export5',                      column: 2, label: 'export5',                color: '#43464B'},
-          {id: 'export6',                      column: 2, label: 'export6',                color: '#43464B'}
+          {id: 'export6',                      column: 2, label: 'export6',                color: '#43464B'},
+          {id: 'loss',                         column: 2, label: 'loss',                   color: '#DCDCDC'}
         ]
         links: [
           {left: 'solar',                      right: 'network',          gquery: 'solar_to_network_in_sankey', color: '#ffcc00'},
@@ -54,13 +54,13 @@
           {left: 'network',                    right: 'agriculture',      gquery: 'network_to_agriculture_in_sankey', color: '#1f77b4'},
           {left: 'network',                    right: 'flexibility',      gquery: 'network_to_flexibility_in_sankey', color: '#1f77b4'},
           {left: 'network',                    right: 'other',            gquery: 'network_to_other_in_sankey', color: '#1f77b4'},
-          {left: 'network',                    right: 'loss',             gquery: 'network_to_loss_in_sankey', color: '#DCDCDC'},
           {left: 'network',                    right: 'export1',          gquery: 'network_to_export1_in_sankey', color: '#1f77b4'},
           {left: 'network',                    right: 'export2',          gquery: 'network_to_export2_in_sankey', color: '#1f77b4'},
           {left: 'network',                    right: 'export3',          gquery: 'network_to_export3_in_sankey', color: '#1f77b4'},
           {left: 'network',                    right: 'export4',          gquery: 'network_to_export4_in_sankey', color: '#1f77b4'},
           {left: 'network',                    right: 'export5',          gquery: 'network_to_export5_in_sankey', color: '#1f77b4'},
-          {left: 'network',                    right: 'export6',          gquery: 'network_to_export6_in_sankey', color: '#1f77b4'}
+          {left: 'network',                    right: 'export6',          gquery: 'network_to_export6_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'loss',             gquery: 'network_to_loss_in_sankey', color: '#DCDCDC'}
         ]
     biomass_sankey:
       data:


Fix layer color initialization

Fix layer color initialization

--- app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.coffee
+++ app/assets/javascripts/oxalis/geometries/materials/plane_material_factory.coffee
@@ -22,6 +22,11 @@
         value : 0

+  convertColor : (color) ->
+
+    return _.map color, (e) -> e / 255
+
+
   createTextures : ->

     # create textures
@@ -33,13 +38,14 @@
       @textures[shaderName].binaryCategory = binary.category
       @textures[shaderName].binaryName = binary.name

+    layerColors = @model.dataset.get("layerColors")
     for shaderName, texture of @textures
       @uniforms[shaderName + "_texture"] = {
         type : "t"
         value : texture
       }
       unless texture.binaryCategory == "segmentation"
-        color = _.map @model.binary[texture.binaryName].color, (e) -> e / 255
+        color = @convertColor(layerColors[texture.binaryName])
         @uniforms[shaderName + "_weight"] = {
           type : "f"
           value : 1
@@ -74,7 +80,7 @@

     @listenTo(@model.dataset, "change:layerColors change:layerColors.*" , (model, layerColors) ->
       for name, color of layerColors
-        color = _.map color, (e) -> e / 255
+        color = @convertColor(color)
         uniformName = @sanitizeName(name) + "_color"
         @uniforms[uniformName].value = new THREE.Vector3(color...)
       return


focus start time field when modal opens

focus start time field when modal opens

--- app/assets/javascripts/layout.js.coffee
+++ app/assets/javascripts/layout.js.coffee
@@ -8,6 +8,9 @@
   content_element = element.append('

')
   content_element.find('div.ajax-content').load $(this).data('reveal-url'), ->
     initControls()
+    setTimeout ->
+      $('#time_entry_start_time').focus()
+    , 100

 $(document).on 'mouseover', '.has-tip', ->
     $(this).popover


[backport] fix the location method that check the current route params

--- app/coffee/modules/base/location.coffee
+++ app/coffee/modules/base/location.coffee
@@ -30,7 +30,9 @@
         return $location

     $location.isInCurrentRouteParams = (name, value) ->
-        return $route.current.params[name] == value
+        params = _.merge($route.current.params, $location.search())
+
+        return params[name] == value

     return $location


fix issue with long folder names in file tree

fixes CNVS-1572

This isn't the best solution but works. We are redoing the folder tree
some I wouldn't worry about finding the best solution as this is most
likely going to be deleted soon.

Test Plan

Given you are a user that can create a folder
And you are using Firefox/Chrome/Safari
When you go new files
And you have folders with super duper long names
Then you should see those names truncated with a "..."
And when you hover over a folder there should be a tooltip display
the full folder name

Change-Id: Ic2e1f95eab009a838de075838ece7a36996aa1ff
Reviewed-on: https://gerrit.instructure.com/55691
Reviewed-by: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>
Tested-by: Jenkins
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Product-Review: Jon Willesen <eb5ce5e6a76c243326fecfa94534101600f87bd9@instructure.com>

--- app/coffeescripts/views/TreeView.coffee
+++ app/coffeescripts/views/TreeView.coffee
@@ -6,7 +6,8 @@
   'compiled/views/PaginatedCollectionView'
   'compiled/views/TreeItemView'
   'jst/TreeCollection'
-], (Backbone, $, _, preventDefault, PaginatedCollectionView, TreeItemView, collectionTemplate) ->
+  'compiled/str/TextHelper'
+], (Backbone, $, _, preventDefault, PaginatedCollectionView, TreeItemView, collectionTemplate, textHelper) ->

   class TreeView extends Backbone.View

@@ -50,6 +51,26 @@
     render: ->
       @renderSelf()
       @renderContents()
+      @truncateLinks()
+
+    truncateLinks: ->
+      truncLength = 37
+      items = @$('.truncate')
+      items.each ->
+        $this = $(this)
+        the_text = $this.text()
+        $this.attr('data-tooltip', '')
+
+        # only truncate text that hasn't already been truncated
+        # This is neccessary because the "render" function calls each node once,
+        # then a final render with all of the nodes. Yikes.
+        if (the_text.length > truncLength)
+          truncateText = textHelper.truncateText(the_text, {max: truncLength})
+          $this.text(truncateText)
+          $this.attr('title', the_text)
+
+        # Handles any folders that didn't get a tooltip added to them
+        $this.attr('title', the_text) unless $this.attr('title')?

     toggle: (event) ->
       # prevent it from bubbling up to parents and from following link
@@ -75,6 +96,7 @@
             $('.' + _this.selectedStyleClass).each((key, element) => $(element).removeClass(@selectedStyleClass))
             $(event.target).addClass(@selectedStyleClass)
           @onClick?(event, @model)
+        @$labelInner.addClass('truncate')

         $label = $("""
           <a


Fix incorrect scope-names in macro/string definitions

The requests for appending an existing value shouldn't be highlighted as
though they're initialising a new one (which is what the "storage" scope
is meant for).

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -81,11 +81,10 @@
 			# Generic requests without formatting
 			name: "meta.function.request.$2.roff"
 			begin: "(?x) ^([.'])[ \t]*
-				(ab|ad|af|as|bd|bp|br|c2|cc|ce|cf|ch|cs|cu|da|di|dt|ec|em
-				|eo|ev|ex|fc|fi|fl|fp|ft|hc|hw|hy|ig|in|it|lc|lg|lf|ll|ls
-				|lt|mc|mk|na|ne|nf|nh|nm|nn|ns|nx|os|pc|pi|pl|pm|pn|po|ps
-				|rd|rm|rn|rr|rs|rt|so|sp|ss|sv|sy|ta|tc|ti|tl|tm|tr|uf|vs
-				|wh)"
+				(ab|ad|af|bd|bp|br|c2|cc|ce|cf|ch|cs|cu|da|di|dt|ec|em|eo
+				|ev|ex|fc|fi|fl|fp|ft|hc|hw|hy|ig|in|it|lc|lg|lf|ll|ls|lt
+				|mc|mk|na|ne|nf|nh|nm|nn|ns|nx|os|pc|pi|pl|pm|pn|po|ps|rd
+				|rm|rn|rr|rs|rt|so|sp|ss|sv|sy|ta|tc|ti|tl|tm|tr|uf|vs|wh)"
 			end: "(?<!\\\\)(?=\n)"
 			beginCaptures:
 				1: {name: "punctuation.definition.request.roff"}
@@ -107,14 +106,15 @@

 			# Macro definition: No terminator specified
 			name: "meta.function.definition.request.$2.roff"
-			begin: "^([.'])[ \t]*(de|am)\\s*(\\S+?)?\\s*(\\\\\".*)?$"
+			begin: "^([.'])[ \t]*((de)|(am))\\s*(\\S+?)?\\s*(\\\\\".*)?$"
 			end:   "^\\.[ \t]*\\."
 			beginCaptures:
 				1: {name: "punctuation.definition.request.roff"}
-				2: {name: "storage.type.function.roff"}
-				3: {name: "entity.name.function.roff"}
-				4: {patterns: [{include: "#escapes"}]}
-				5: {patterns: [{include: "#params"}]}
+				3: {name: "storage.type.function.roff"}
+				4: {name: "entity.name.function.roff"}
+				5: {name: "variable.parameter.roff"}
+				6: {patterns: [{include: "#escapes"}]}
+				7: {patterns: [{include: "#params"}]}
 			endCaptures:
 				0: {name: "punctuation.definition.request.roff"}
 			patterns: [{include: "$self"}]
@@ -122,14 +122,15 @@

 			# Macro definition: Terminator included
 			name: "meta.function.definition.request.with-terminator.$2.roff"
-			begin: "^([.'])[ \t]*(de|am)\\s*(\\S+)\\s*(\"[^\"]+\"?|\\S+?(?=\\s|\\\\\"))?(.*)$"
-			end:   "^(\\.)[ \t]*((\\4)(?=$|\\s|\\\\(?:$|\")))"
+			begin: "^([.'])[ \t]*((de)|(am))\\s*(\\S+)\\s*(\"[^\"]+\"?|\\S+?(?=\\s|\\\\\"))?(.*)$"
+			end:   "^(\\.)[ \t]*((\\6)(?=$|\\s|\\\\(?:$|\")))"
 			beginCaptures:
 				1: {name: "punctuation.definition.request.roff"}
-				2: {name: "storage.type.function.roff"}
-				3: {name: "entity.name.function.roff"}
-				4: {name: "entity.terminator.roff", patterns: [{include: "#string"}]}
-				5: {patterns: [{include: "#params"}]}
+				3: {name: "storage.type.function.roff"}
+				4: {name: "entity.name.function.roff"}
+				5: {name: "variable.parameter.roff"}
+				6: {name: "entity.terminator.roff", patterns: [{include: "#string"}]}
+				7: {patterns: [{include: "#params"}]}
 			endCaptures:
 				1: {name: "punctuation.definition.request.roff"}
 				2: {name: "entity.terminator.roff"}
@@ -150,13 +151,14 @@

 			# String definition
 			name: "meta.function.request.$2.roff"
-			begin: "^([.'])[ \t]*(ds)[ \t]*(\\S+)?[ \t]*(.*)$"
+			begin: "^([.'])[ \t]*((ds)|(as))[ \t]*(\\S+)?[ \t]*(.*)$"
 			end:   "(?<!\\\\)$"
 			beginCaptures:
 				1: {name: "punctuation.definition.request.roff"}
-				2: {name: "storage.type.var.roff"}
-				3: {name: "variable.parameter.roff"}
-				4: {patterns: [{include: "#params"}]}
+				3: {name: "storage.type.var.roff"}
+				4: {name: "entity.name.function.roff"}
+				5: {name: "variable.parameter.roff"}
+				6: {patterns: [{include: "#params"}]}
 		},{

 			# Underlined passage


Fix incorrect keyword scopes

Fix incorrect keyword scopes

--- grammars/viml.cson
+++ grammars/viml.cson
@@ -215,9 +215,12 @@

 	keyword:
 		patterns: [{
-			name: "keyword.control.viml"
-			match: "\\b(if|while|for|unlet|return|end(if|for|while)?|else(if)?|do|in|:)\\b"
-		}, {
+			name: "keyword.control.$1.viml"
+			match: "\\b(if|while|for|return|try|catch|finally|end(if|for|while|try)?|else(if)?|do|in|:)\\b"
+		},{
+			name: "keyword.operator.$1.viml"
+			match: "\\b(unlet)\\b"
+		},{
 			name: "storage.type.let.viml"
 			match: "\\blet\\b"
 		}]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -6,8 +6,9 @@

 asyncTest "callback and cache", ->
   callback = (s) ->
+    sprite = Sprite.load

   sprite = Sprite.load "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12NgoC4AAABQAAEiE+h1AAAAAElFTkSuQmCC"

-  sprite = Sprite.load
+
 module()


fixed race condition in stream test

fixed race condition in stream test

--- test/stream.coffee
+++ test/stream.coffee
@@ -28,7 +28,7 @@
       @stream = new Stream mongoWatchPolicy @users

       # Then the receiver should return the expected data
-      receiver = (name, event) ->
+      receiver = (name, event) =>
         should.exist event.timestamp

         switch name
@@ -46,6 +46,11 @@
               root: 'users'
             }

+            # And something event worthy happens
+            # (only trigger this once initial data has been received)
+            @users.insert {email: 'graham@daventry.com'}, (err, status) ->
+              should.not.exist err
+
           when 'delta'
             {root, oplist} = event
             [{data, operation}] = oplist
@@ -58,7 +63,3 @@
       # When a receiver is registered
       @stream.register {sessionId: 5}, receiver, (err) =>
         should.not.exist err
-
-        # And something event worthy happens
-        @users.insert {email: 'graham@daventry.com'}, (err, status) ->
-          should.not.exist err


tweaks to notifications collection

tweaks to notifications collection

--- app/js/collections/notifications.coffee
+++ app/js/collections/notifications.coffee
@@ -12,6 +12,7 @@
     @filter = options.filter if options.filter
     @data = options.data || {}
     @on 'reset', -> @select(undefined)
+    @on 'sync', @savePollInterval

   # Default filter accepts all models
   filter: (model) -> true
@@ -19,10 +20,12 @@
   fetch: (options = {}) ->
     @oldestTimestamp = @donePaginating = null if options.reset
     options.data = _.extend({}, @data, options.data || {})
+    # Only report success if response is modified
+    options.ifModified = true

     # API uses If-Modified-Since to determine which notifications to fetch. We
     # want all of them if the collection is currently empty.
-    if @isEmpty()
+    unless @oldestTimestamp
       options.beforeSend = (xhr) =>
         xhr.setRequestHeader('If-Modified-Since', '')

@@ -45,7 +48,7 @@
     clearTimeout @pollTimer
     @pollTimer = setTimeout @poll, @pollInterval

-    @fetch(remove: false, reset: false).then(@savePollInterval)
+    @fetch(remove: false, reset: false)

   stopPolling: ->
     clearTimeout @pollTimer if @pollTimer
@@ -69,6 +72,6 @@
     model if @filter(model)

   # Internal: Save the poll interval returned from the API
-  savePollInterval: (data, status, xhr) ->
-    if interval = xhr.getResponseHeader('X-Poll-Interval')
+  savePollInterval: (collection, _, options) ->
+    if interval = options.xhr.getResponseHeader('X-Poll-Interval')
       @pollInterval = Number(interval) * 1000


check su variabile, se server non rispondesse

--- app/scripts/controllers/register.coffee
+++ app/scripts/controllers/register.coffee
@@ -69,7 +69,7 @@
     # check function
     $scope.checkResponse = (data) ->
       # check response # {"status":"ko","errors":{"username":{"param":"username","msg":"Username già utilizzato","value":"ciaociao"}}}
-      if data.status is 'ok'
+      if data?.status is 'ok'
         $scope.isRegistered = true
       else
         # fill errors


show loading indicator initially and hide when infinite scroll is done (closes #2435)

--- apps/fair/components/booths/view.coffee
+++ apps/fair/components/booths/view.coffee
@@ -7,6 +7,8 @@
 qs = require 'querystring'
 FilterNav = require '../../../../components/filter/nav/view.coffee'
 deslugify = require '../../../../components/deslugify/index.coffee'
+mediator = require '../../../../lib/mediator.coffee'
+
 navSectionsTemplate = -> require('./nav_sections.jade') arguments...

 module.exports = class BoothsView extends Backbone.View
@@ -42,6 +44,8 @@
     # Render the navigation dropdown sections
     @fair.fetchSections success: @renderSections

+    mediator.on 'infinite:scroll:end', @hideSpinner, @
+
   renderSections: (sections) =>
     hash = {}
     sections.each (section) -> hash[section.get 'section'] = section.get('section')
@@ -65,7 +69,7 @@
     )

   renderShows: (items) =>
-    return @$('#fair-booths-spinner').hide() unless items.models.length > 0
+    return @hideSpinner() unless items.length > 0
     items.urlRoot = @shows.url
     @feedView?.destroy()
     @feedView = new ShowsFeed
@@ -74,6 +78,8 @@
       additionalParams: @params.toJSON()
       hideSeeMoreButtons: (if @params.get('artist') then true else false)
     @feedView.feedName = 'Fair Feed'
+
+  hideSpinner: ->
     @$('#fair-booths-spinner').hide()

   toggleBoothCount: =>


fix hololens cart description

fix hololens cart description

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -529,14 +529,14 @@
       title_product: 'HoloLens Binocular Add-on'
       title_store: 'HoloLens Binocular Add-on'
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 200hz eye tracking cameras, high speed world camera, clip on attachment mounts, and USB connector clip."
-      description_cart: "120hz binocular eye tracking add-on with high speed world camera for your Microsoft HoloLens AR headset."
+      description_cart: "200hz binocular eye tracking add-on with high speed world camera for your Microsoft HoloLens AR headset."
       img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
     hololens_wnone_e200b:
       cost: 1400
       title_product: 'HoloLens Binocular Add-on'
       title_store: 'HoloLens Binocular Add-on'
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 200hz eye tracking cameras, clip on attachment mounts, and USB connector clip."
-      description_cart: "120hz binocular eye tracking add-on for your Microsoft HoloLens AR headset."
+      description_cart: "200hz binocular eye tracking add-on for your Microsoft HoloLens AR headset."
       img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
     usbcupgrade:
       cost: 210


whitespace removal to please travis

whitespace removal to please travis

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -39,10 +39,10 @@
       MathJax?.Hub.Queue(['Typeset', MathJax.Hub], @$el.get(0))

       # Converts a TERP link to an OST-hosted iframe
-      @$el.find('a[href*="terp-"]').each () ->
+      @$el.find('a[href*="terp-"]').each () ->
         terpCode = $(this).attr('href').match(/#terp\-(.*)/)[1]
-        $(this).replaceWith("<iframe class='terp'
-                                     src='https://stormy-wave-8747.herokuapp.com/terp/#{terpCode}/quiz_start'
+        $(this).replaceWith("<iframe class='terp'
+                                     src='https://stormy-wave-8747.herokuapp.com/terp/#{terpCode}/quiz_start'
                                      height='600px' width='800px' frameborder='0' seamless='seamless'>
                              </iframe>")


fixing spacing

fixing spacing

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -122,7 +122,7 @@
                   $el.attr('href', "/contents/#{@owner.getVersionedId()}:#{pageNumber}")
                   $el.attr('data-page', pageNumber)

-            @addRelNoFollowToOutsideLinks($temp)
+          @addRelNoFollowToOutsideLinks($temp)

           # Copy data-mark-prefix and -suffix from ol to li so they can be used in css
           $temp.find('ol[data-mark-prefix] > li, ol[data-mark-suffix] > li,


Also strip fragment from redirect_uri passed in OAuth request.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -1,7 +1,7 @@
 google_oauth_parameters_for_fusion_tables =
   response_type: 'token'
   client_id: '891199912324.apps.googleusercontent.com'
-  redirect_uri: window.location.href
+  redirect_uri: window.location.href.replace("#{location.hash}",'')
   scope: 'https://www.googleapis.com/auth/fusiontables'
   approval_prompt: 'auto'


hub/stripe sync: do nothing on err

hub/stripe sync: do nothing on err

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -828,10 +828,14 @@
                     query : "SELECT email_address, first_name, last_name FROM accounts"
                     where : "account_id = $::UUID" : opts.account_id
                     cb    : one_result (err, x) ->
-                        locals.email_address = x.email_address
-                        locals.first_name = x.first_name ? ''
-                        locals.last_name  = x.last_name  ? ''
-                        cb(err)
+                        if err?
+                            cb(err)
+                            return
+                        else
+                            locals.email_address = x.email_address
+                            locals.first_name = x.first_name ? ''
+                            locals.last_name  = x.last_name  ? ''
+                            cb()
             (cb) =>
                 if not opts.customer_id?
                     cb(); return


fullpieagent testing

fullpieagent testing