Remove deprecated calls from workspace view spec

--- spec/workspace-view-spec.coffee
+++ spec/workspace-view-spec.coffee
@@ -14,8 +14,11 @@
     atom.workspace = new Workspace
     atom.workspaceView = new WorkspaceView(atom.workspace)
     atom.workspaceView.enableKeymap()
-    atom.workspaceView.openSync(pathToOpen)
     atom.workspaceView.focus()
+
+    waitsForPromise ->
+      atom.workspace.open(pathToOpen)
+

   describe "@deserialize()", ->
     viewState = null
@@ -32,17 +35,21 @@
     describe "when the serialized WorkspaceView has an unsaved buffer", ->
       it "constructs the view with the same panes", ->
         atom.workspaceView.attachToDom()
-        atom.workspaceView.openSync()
-        editorView1 = atom.workspaceView.getActiveView()
-        buffer = editorView1.getEditor().getBuffer()
-        editorView1.splitRight()
-        expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
-
-        simulateReload()
-
-        expect(atom.workspaceView.getEditorViews().length).toBe 2
-        expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
-        expect(atom.workspaceView.title).toBe "untitled - #{atom.project.getPath()}"
+
+        waitsForPromise ->
+          atom.workspace.open()
+
+        runs ->
+          editorView1 = atom.workspaceView.getActiveView()
+          buffer = editorView1.getEditor().getBuffer()
+          editorView1.splitRight()
+          expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
+
+          simulateReload()
+
+          expect(atom.workspaceView.getEditorViews().length).toBe 2
+          expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
+          expect(atom.workspaceView.title).toBe "untitled - #{atom.project.getPath()}"

     describe "when there are open editors", ->
       it "constructs the view with the same panes", ->
@@ -51,41 +58,54 @@
         pane2 = pane1.splitRight()
         pane3 = pane2.splitRight()
         pane4 = pane2.splitDown()
-        pane2.activateItem(atom.project.openSync('b'))
-        pane3.activateItem(atom.project.openSync('../sample.js'))
-        pane3.activeItem.setCursorScreenPosition([2, 4])
-        pane4.activateItem(atom.project.openSync('../sample.txt'))
-        pane4.activeItem.setCursorScreenPosition([0, 2])
-        pane2.focus()
-
-        simulateReload()
-
-        expect(atom.workspaceView.getEditorViews().length).toBe 4
-        editorView1 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(0)').view()
-        editorView3 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(1)').view()
-        editorView2 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(0)').view()
-        editorView4 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(1)').view()
-
-        expect(editorView1.getEditor().getPath()).toBe atom.project.resolve('a')
-        expect(editorView2.getEditor().getPath()).toBe atom.project.resolve('b')
-        expect(editorView3.getEditor().getPath()).toBe atom.project.resolve('../sample.js')
-        expect(editorView3.getEditor().getCursorScreenPosition()).toEqual [2, 4]
-        expect(editorView4.getEditor().getPath()).toBe atom.project.resolve('../sample.txt')
-        expect(editorView4.getEditor().getCursorScreenPosition()).toEqual [0, 2]
-
-        # ensure adjust pane dimensions is called
-        expect(editorView1.width()).toBeGreaterThan 0
-        expect(editorView2.width()).toBeGreaterThan 0
-        expect(editorView3.width()).toBeGreaterThan 0
-        expect(editorView4.width()).toBeGreaterThan 0
-
-        # ensure correct editorView is focused again
-        expect(editorView2.isFocused).toBeTruthy()
-        expect(editorView1.isFocused).toBeFalsy()
-        expect(editorView3.isFocused).toBeFalsy()
-        expect(editorView4.isFocused).toBeFalsy()
-
-        expect(atom.workspaceView.title).toBe "#{path.basename(editorView2.getEditor().getPath())} - #{atom.project.getPath()}"
+
+        waitsForPromise ->
+          atom.project.open('b').then (editor) ->
+            pane2.activateItem(editor)
+
+        waitsForPromise ->
+          atom.project.open('../sample.js').then (editor) ->
+            pane3.activateItem(editor)
+
+        runs ->
+          pane3.activeItem.setCursorScreenPosition([2, 4])
+
+        waitsForPromise ->
+          atom.project.open('../sample.txt').then (editor) ->
+            pane4.activateItem(editor)
+
+        runs ->
+          pane4.activeItem.setCursorScreenPosition([0, 2])
+          pane2.focus()
+
+          simulateReload()
+
+          expect(atom.workspaceView.getEditorViews().length).toBe 4
+          editorView1 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(0)').view()
+          editorView3 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(1)').view()
+          editorView2 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(0)').view()
+          editorView4 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(1)').view()
+
+          expect(editorView1.getEditor().getPath()).toBe atom.project.resolve('a')
+          expect(editorView2.getEditor().getPath()).toBe atom.project.resolve('b')
+          expect(editorView3.getEditor().getPath()).toBe atom.project.resolve('../sample.js')
+          expect(editorView3.getEditor().getCursorScreenPosition()).toEqual [2, 4]
+          expect(editorView4.getEditor().getPath()).toBe atom.project.resolve('../sample.txt')
+          expect(editorView4.getEditor().getCursorScreenPosition()).toEqual [0, 2]
+
+          # ensure adjust pane dimensions is called
+          expect(editorView1.width()).toBeGreaterThan 0
+          expect(editorView2.width()).toBeGreaterThan 0
+          expect(editorView3.width()).toBeGreaterThan 0
+          expect(editorView4.width()).toBeGreaterThan 0
+
+          # ensure correct editorView is focused again
+          expect(editorView2).toHaveFocus()
+          expect(editorView1).not.toHaveFocus()
+          expect(editorView3).not.toHaveFocus()
+          expect(editorView4).not.toHaveFocus()
+
+          expect(atom.workspaceView.title).toBe "#{path.basename(editorView2.getEditor().getPath())} - #{atom.project.getPath()}"

     describe "where there are no open editors", ->
       it "constructs the view with no open editors", ->
@@ -101,9 +121,9 @@
     it "hands off focus to the active pane", ->
       activePane = atom.workspaceView.getActivePaneView()
       $('body').focus()
-      expect(activePane.hasFocus()).toBe false
+      expect(activePane).not.toHaveFocus()
       atom.workspaceView.focus()
-      expect(activePane.hasFocus()).toBe true
+      expect(activePane).toHaveFocus()

   describe "keymap wiring", ->
     commandHandler = null
@@ -111,7 +131,7 @@
       commandHandler = jasmine.createSpy('commandHandler')
       atom.workspaceView.on('foo-command', commandHandler)

-      atom.keymaps.bindKeys('name', '*', 'x': 'foo-command')
+      atom.keymaps.add('name', '*': {'x': 'foo-command'})

     describe "when a keydown event is triggered in the WorkspaceView", ->
       it "triggers matching keybindings for that event", ->
@@ -128,7 +148,8 @@

     describe "when the project has a path", ->
       beforeEach ->
-        atom.workspaceView.openSync('b')
+        waitsForPromise ->
+          atom.workspace.open('b')

       describe "when there is an active pane item", ->
         it "sets the title to the pane item's title plus the project path", ->
@@ -143,7 +164,7 @@

       describe "when the active pane's item changes", ->
         it "updates the title to the new item's title plus the project path", ->
-          atom.workspaceView.getActivePaneView().showNextItem()
+          atom.workspaceView.getActivePaneView().activateNextItem()
           item = atom.workspace.getActivePaneItem()
           expect(atom.workspaceView.title).toBe "#{item.getTitle()} - #{atom.project.getPath()}"

@@ -158,7 +179,7 @@
           pane = atom.workspaceView.getActivePaneView()
           pane.splitRight()
           initialTitle = atom.workspaceView.title
-          pane.showNextItem()
+          pane.activateNextItem()
           expect(atom.workspaceView.title).toBe initialTitle

     describe "when the root view is deserialized", ->
@@ -244,15 +265,15 @@
       paneView2 = atom.workspaceView.getActivePaneView()

       expect(paneView1).not.toBe paneView2
-      expect(atom.workspaceView.getPanes()).toHaveLength 2
+      expect(atom.workspaceView.getPaneViews()).toHaveLength 2
       atom.workspaceView.trigger('core:close')

       expect(atom.workspaceView.getActivePaneView().getItems()).toHaveLength 1
-      expect(atom.workspaceView.getPanes()).toHaveLength 1
+      expect(atom.workspaceView.getPaneViews()).toHaveLength 1
       atom.workspaceView.trigger('core:close')

       expect(atom.workspaceView.getActivePaneView().getItems()).toHaveLength 0
-      expect(atom.workspaceView.getPanes()).toHaveLength 1
+      expect(atom.workspaceView.getPaneViews()).toHaveLength 1

   describe "the scrollbar visibility class", ->
     it "has a class based on the style of the scrollbar", ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/bounded.coffee
+++ test/bounded.coffee
@@ -1,4 +1,4 @@
-test "Bounds should give correct offset position", ->
+test "Bounds", ->
   obj = GameObject
     x: 5
     y: 10
@@ -7,5 +7,8 @@

   obj.include(Bounded)

-  equals obj.bounds().x, 5
+  equals obj.bounds().x, 5, "returns correct x, y, width, height"
+  equals obj.bounds().y, 10
+  equals obj.bounds().width, 50
+  equals obj.bounds().height, 100


Support anyone/anybody/someone.

Support anyone/anybody/someone.

--- prviewer.coffee
+++ prviewer.coffee
@@ -225,7 +225,7 @@

             # Convert "IAN/MARK" to ['statico', 'mlogan']
             for name in names
-              if name in ['everyone', 'all']
+              if name in ['everyone', 'all', 'anyone', 'someone', 'anybody']
                 # Add all reviewers.
                 reviewers[name] = true for _, name of settings.reviewers
               else if name of settings.reviewers


When switching, also keep empty strings. Only don't set content if nothing is passed in at all (null)

--- core/app/backbone/views/evidence/add_evidence_form_view.coffee
+++ core/app/backbone/views/evidence/add_evidence_form_view.coffee
@@ -69,13 +69,13 @@
           factlink_id: @options.fact_id
           type: @options.type

-  switchToCommentView: (content) ->
+  switchToCommentView: (content=null) ->
     @inputRegion.switchTo 'add_comment_view'
-    @inputRegion.getView('add_comment_view').setFormContent(content) if content
+    @inputRegion.getView('add_comment_view').setFormContent(content) if content?

-  switchToFactRelationView: (content) ->
+  switchToFactRelationView: (content=null) ->
     @inputRegion.switchTo 'search_view'
-    @inputRegion.getView('search_view').setQuery(content) if content
+    @inputRegion.getView('search_view').setQuery(content) if content?

   showError: -> @$('.js-error').show()
   hideError: -> @$('.js-error').hide()


Give subcomments class a name

Give subcomments class a name

--- core/app/backbone/views/sub_comments/react_sub_comments.coffee
+++ core/app/backbone/views/sub_comments/react_sub_comments.coffee
@@ -1,4 +1,4 @@
-ReactSubCommentsAdd = React.createClass
+React.defineClass('SubCommentsAdd')
   getInitialState: ->
     phase: 'new'


webapp: fix info link #2609

webapp: fix info link #2609

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -34,7 +34,8 @@

 misc = require('smc-util/misc')
 {APP_TAGLINE} = require('smc-util/theme')
-{APP_BASE_URL, APP_ICON, APP_ICON_WHITE, APP_LOGO_NAME, APP_LOGO_NAME_WHITE} = require('./art')
+{APP_ICON, APP_ICON_WHITE, APP_LOGO_NAME, APP_LOGO_NAME_WHITE} = require('./art')
+{APP_BASE_URL} = require('./misc_page')

 $.get window.app_base_url + "/registration", (obj, status) ->
     if status == 'success'


snappy -- do not break mobile

snappy -- do not break mobile

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -369,6 +369,7 @@
                     @render_editor_tab()

     render: ->
+        console.log("Render ProjectContentViewer ", @props.is_visible)
         <div
             className = {if not @props.is_visible then "hide"}
             style={overflowY:'auto', overflowX:'hidden', flex:1, height:0, position:'relative'}>
@@ -548,7 +549,6 @@
                 />
         return v.concat(@render_editor_tabs(active_path, group))

-
     render : ->
         if not @props.open_files_order?
             return <Loading />
@@ -697,6 +697,7 @@
             </div> if not @props.fullscreen}
             <ErrorBoundary>
                 <ProjectContentViewer
+                    is_visible      = {true}
                     project_id      = {@props.project_id}
                     project_name    = {@props.name}
                     active_tab_name = {@props.active_project_tab}


put in a hack so mobile project list drop down isn't unusable.

--- src/smc-webapp/projects_nav.cjsx
+++ src/smc-webapp/projects_nav.cjsx
@@ -297,7 +297,7 @@

         <Nav
             className = 'smc-dropdown-projects'
-            style     = {display:'flex', margin:'0', flex:'1', fontSize:'25px', textAlign:'center', padding:'5px'}
+            style     = {display:'flex', margin:'0', flex:'1', fontSize:'25px', textAlign:'center', padding:'5px', zIndex: 1, background: 'white'}
             >
             <NavDropdown
                 id        = "smc-top-project-nav-dropdown"


Fixed possible bug, with coordinates.

Fixed possible bug, with coordinates.

--- api/points.coffee
+++ api/points.coffee
@@ -26,8 +26,8 @@
             useGPS = false
             console.log "/points/check/#{req.params.code} user: #{req.session.user.id} without GPS"
         else
-            lat = req.query.lat
-            lng = req.query.lng
+            lat = parseFloat (req.query.lat).replace(',', '.')
+            lng = parseFloat (req.query.lng).replace(',', '.')
             console.log "/points/check/#{req.params.code} user: #{req.session.user.id} @ lat: #{lat} lng: #{lng}"

         User.findOne


fix zoom sidebar toc issue.

fix zoom sidebar toc issue.

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -246,12 +246,14 @@
       # @previewElement = d.previewElement
       @previewElement.innerHTML = d.html
       @sidebarTOC?.innerHTML = d.tocHTML
-      @previewElement.style.zoom = d.zoomLevel
       @graphData = d.graphData
       @codeChunksData = d.codeChunksData
       @presentationMode = d.presentationMode
       @slideConfigs = d.slideConfigs
       @filesCache = d.filesCache
+
+      @zoomLevel = d.zoomLevel
+      @setZoomLevel()

       if @presentationMode
         @previewElement.setAttribute 'data-presentation-preview-mode', ''
@@ -1141,20 +1143,23 @@
   resizeEvent: ()->
     @scrollMap = null

+  setZoomLevel: ()->
+    @previewElement.style.zoom = @zoomLevel
+    if @enableSidebarTOC
+      @previewElement.style.width = "calc(100% - #{268 / @zoomLevel}px)"
+    @scrollMap = null
+
   zoomIn: ()->
     @zoomLevel = parseFloat(getComputedStyle(@previewElement).zoom) + 0.1
-    @previewElement.style.zoom = @zoomLevel
-    @scrollMap = null
+    @setZoomLevel()

   zoomOut: ()->
     @zoomLevel = parseFloat(getComputedStyle(@previewElement).zoom) - 0.1
-    @previewElement.style.zoom = @zoomLevel
-    @scrollMap = null
+    @setZoomLevel()

   resetZoom: ()->
     @zoomLevel = 1.0
-    @previewElement.style.zoom = @zoomLevel
-    @scrollMap = null
+    @setZoomLevel()

   ###
   convert './a.txt' '/a.txt'


fix minor bug

fix minor bug

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -1035,7 +1035,7 @@

         i += 1

-    if slideConfigs[i-1]['vertical'] # end of vertical slides
+    if i > 0 and slideConfigs[i-1]['vertical'] # end of vertical slides
       output += "</section>"

     """


Handle case where statistic is undefined.  Same as RocketChat #581.

--- packages/rocketchat-lib/server/functions/getStatistics.coffee
+++ packages/rocketchat-lib/server/functions/getStatistics.coffee
@@ -46,9 +46,24 @@

 	result = ChatRoom.mapReduce(m, r, { finalize: f, out: "rocketchat_mr_statistics" })

-	statistics.maxRoomUsers = MapReducedStatistics.findOne({ _id: 1 }).value.max
-	statistics.avgChannelUsers = MapReducedStatistics.findOne({ _id: 'c' }).value.avg
-	statistics.avgPrivateGroupUsers = MapReducedStatistics.findOne({ _id: 'p' }).value.avg
+	statistics.maxRoomUsers = 0
+	statistics.avgChannelUsers = 0
+	statistics.avgPrivateGroupUsers = 0
+
+	if MapReducedStatistics.findOne({ _id: 1 })
+		statistics.maxRoomUsers = MapReducedStatistics.findOne({ _id: 1 }).value.max
+	else
+		console.log 'max room user statistic not found'.red
+
+	if MapReducedStatistics.findOne({ _id: 'c' })
+		statistics.avgChannelUsers = MapReducedStatistics.findOne({ _id: 'c' }).value.max
+	else
+		console.log 'channel user statistic not found'.red
+
+	if MapReducedStatistics.findOne({ _id: 'p' })
+		statistics.avgPrivateGroupUsers = MapReducedStatistics.findOne({ _id: 'p' }).value.max
+	else
+		console.log 'private group user statistic not found'.red

 	os = Npm.require('os')
 	statistics.os =


fixed dynamic module loading for build

fixed dynamic module loading for build

Former-commit-id: 14f9f3871a910f19d18f4afcf388334a8d7f06dc [formerly 0cc43a253f0d31ad67e17330bdd3c526d9cfb2bc]
Former-commit-id: 504220b53079a5decb84b812087bd1388c1c6d94

--- js/msa/stage.coffee
+++ js/msa/stage.coffee
@@ -1,4 +1,4 @@
-define ["./utils"], (Utils) ->
+define ["msa/utils"], (Utils) ->
   class Stage

     constructor: (@msa) ->


add XenStorage motion in main view

add XenStorage motion in main view

--- app/modules/home/index.coffee
+++ app/modules/home/index.coffee
@@ -71,7 +71,22 @@
     $scope.force_stopVM = (id) -> xo.vm.stop id, true
     $scope.rebootVM = xo.vm.restart
     $scope.force_rebootVM = (id) -> xo.vm.restart id, true
-    $scope.migrateVM = xo.vm.migrate
+    $scope.migrateVM = (id, hostId) ->
+      (xo.vm.migrate id, hostId).catch (error) ->
+      modal.confirm
+        title: 'VM migrate'
+        message: 'This VM can\'t be migrated with Xen Motion to this host because they don\'t share any storage. Do you want to try a Xen Storage Motion?'
+
+      .then ->
+        notify.info {
+          title: 'VM migration'
+          message: 'The migration process started'
+        }
+
+        xo.vm.migrate_pool {
+          id
+          target_host_id: hostId
+        }
     $scope.snapshotVM = (id) ->
       vm = xo.get (id)
       date = dateFilter Date.now(), 'yyyy-MM-ddTHH:mmZ'


Better status message handling

Better status message handling

--- app/classify.cjsx
+++ app/classify.cjsx
@@ -11,6 +11,28 @@
 User = require 'zooniverse/models/user'

 steps = require './lib/steps'
+
+outOfSubjectsMessage = '''
+
We're out of data!

+
+
Unfortunately, we don't have any more videos to show you right now!
+  Either all current videos have been explored, or you personally
+  may have explored every available video. We should have more
+  data for you fairly soon, so don't forget about us!

+
+
In the meantime, you can continue working on this project
+  by helping us ID and name individual chimps on Chimp & See Talk.
+  Or check out any of our dozens of other Zooniverse projects at
+  zooniverse.org. Thanks!

+'''
+
+fetchFailMessage = '''
+
Hey Chimp & See explorer—something's gone wrong!
+  Our system is having trouble finding a video for you right now. We're sorry about that!

+
+
Let us know about this bug on the Technical Support
+  board on C&S Talk, and we'll try to get it fixed as soon as possible. Thanks for your help, efforts, and patience!

+'''

 module?.exports = React.createClass
   displayName: 'Classify'
@@ -31,6 +53,7 @@
     skipImages: false
     srcWidth: null
     toggleSkip: false
+    noSubjects: false

   componentDidMount: ->
     Subject.on 'select', @onSubjectSelect
@@ -81,43 +104,27 @@
     @checkSrcWidth()

   onSubjectFetchFail: ->
-    unless Subject.count() is 0
-      @refs.statusMessage.getDOMNode().innerHTML =
-        '''
Hey Chimp & See explorer—something's gone wrong!
-          Our system is having trouble finding a video for you right now. We're sorry about that!

-
-
Let us know about this bug on the Technical Support
-          board on C&S Talk, and we'll try to get it fixed as soon as possible. Thanks for your help, efforts, and patience!

-        '''
-    else
-      @refs.statusMessage.getDOMNode().innerHTML =
-        '''
We're out of data!

-
-
Unfortunately, we don't have any more videos to show you right now!
-        Either all current videos have been explored, or you personally
-        may have explored every available video. We should have more
-        data for you fairly soon, so don't forget about us!

-
-
In the meantime, you can continue working on this project
-        by helping us ID and name individual chimps on Chimp & See Talk.
-        Or check out any of our dozens of other Zooniverse projects at
-        zooniverse.org. Thanks!

-      '''
+    @setState({
+      video: null
+      previews: null
+      location: null
+      classification: null
+      noSubjects: true
+    }, ->
+      unless Subject.count() is 0
+        @refs.statusMessage.getDOMNode().innerHTML = fetchFailMessage
+      else
+        @refs.statusMessage.getDOMNode().innerHTML = outOfSubjectsMessage
+    )

   onNoSubjects: ->
-    @refs.statusMessage.getDOMNode().innerHTML =
-      '''
We're out of data!

-
-
Unfortunately, we don't have any more videos to show you right now!
-      Either all current videos have been explored, or you personally
-      may have explored every available video. We should have more
-      data for you fairly soon, so don't forget about us!

-
-
In the meantime, you can continue working on this project
-      by helping us ID and name individual chimps on Chimp & See Talk.
-      Or check out any of our dozens of other Zooniverse projects at
-      zooniverse.org. Thanks!

-    '''
+    @setState({
+      video: null
+      previews: null
+      location: null
+      classification: null
+      noSubjects: true
+    }, -> @refs.statusMessage.getDOMNode().innerHTML = outOfSubjectsMessage)

   checkSrcWidth: ->
     image = new Image()
@@ -239,7 +246,8 @@
           disableSkip={@disableSkip}
         />
       }
-

+      {if @state.noSubjects
+

}
       <SlideTutorial tutorialIsOpen={@state.tutorialIsOpen} closeTutorial={@closeTutorial} tutorialType={@state.tutorialType} />


Relabel and reactivate the lab button

Relabel and reactivate the lab button

--- app/partials/main-header.cjsx
+++ app/partials/main-header.cjsx
@@ -15,7 +15,7 @@
     discover: 'Discover'
     learn: 'Learn'
     talk: 'Talk'
-    lab: 'The lab'
+    lab: 'Build a project'

 module.exports = React.createClass
   displayName: 'MainHeader'
@@ -67,7 +67,7 @@
           {unless process.env.NODE_ENV is 'production'
             <Link to="talk" className="main-nav-item"><Translate content="mainNav.talk" /></Link>}
           <hr />
-          {if @state.user? and process.env.NODE_ENV isnt 'production'
+          {if @state.user?
             <Link to="lab" className="main-nav-item"><Translate className="minor" content="mainNav.lab" /></Link>}
         </nav>
         {if @state.user?


fix issues with tabs

fix issues with tabs

--- app/scripts/directives.coffee
+++ app/scripts/directives.coffee
@@ -41,19 +41,25 @@
                 default : 0
             ]

-        s.setSelected = (index) ->
-            if not contentScope.tabset.tabs[index]
-                index = contentScope.tabset.tabs.length - 1
-            # contentScope.tabset.tabs[index].active = true
+        s.setSelected = (index, ignoreCheck) ->
+            if not ignoreCheck and index not of s.fixedTabs
+                index = s.maxTab
+
             s.activeTab = index

         initTab = parseInt($location.search()[attr.tabHash]) or 0
         $timeout (() ->
+            s.fixedTabs = {}
+            s.maxTab = -1
+            for tab in contentScope.tabset.tabs
+                s.fixedTabs[tab.index] = tab
+                if tab.index > s.maxTab
+                    s.maxTab = tab.index
             s.setSelected(initTab)
             watchHash()), 0

         s.newDynamicTab = () ->
-            $timeout(() -> s.setSelected (contentScope.tabset.tabs.length - 1), 0)
+            $timeout (() -> s.setSelected(s.maxTab + 1, true)), 0


Convert to ItemView

Convert to ItemView

--- src/scripts/workbench/views/datastream/chart_view.js.coffee
+++ src/scripts/workbench/views/datastream/chart_view.js.coffee
@@ -1,11 +1,10 @@
-class Workbench.Views.DatastreamChartView extends Backbone.View
-  initialize: ->
+class Workbench.Views.DatastreamChartView extends Backbone.Marionette.ItemView
+  template: false

-  remove: ->
+  onBeforeDestroy: ->
     @geocensChart.chart.destroy() if @chart
-    super()

-  render: ->
+  onRender: ->
     @$el.animate(height: 400).promise().done(=>
       @geocensChart = @$el.GeocensChart
         datastream: @model.object


:fire:

:fire:

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -1043,13 +1043,6 @@
   hasPixelPositionRequirements: ->
     @lineHeight? and @baseCharacterWidth?

-  pixelPositionForScreenPositionInTile: (tileStartRow, screenPosition, clip) ->
-    position = @pixelPositionForScreenPosition(screenPosition, clip)
-    position.top -= tileStartRow * @lineHeight
-    position.top += @scrollTop
-    position.left += @scrollLeft
-    position
-
   pixelPositionForScreenPosition: (screenPosition, clip=true) ->
     screenPosition = Point.fromObject(screenPosition)
     screenPosition = @model.clipScreenPosition(screenPosition) if clip


strip out old testing, which was a mess...

--- test/lib/test-get-files.coffee
+++ test/lib/test-get-files.coffee
@@ -1,159 +1,8 @@
 assert = require 'assert'
-getFiles = require '../../lib/file-list'
+getFiles = require '../../lib/get-files'

-describe 'test file-list', ->
+describe 'test get-files', ->

-  errorMessagePattern = /getFiles requires `options` value/
+  describe 'for all scopes', ->

-  it 'should throw error when options is undefined', ->
-
-    assert.throws getFiles, errorMessagePattern
-
-  it 'should throw error when options is null', ->
-
-    assert.throws getFiles.bind null, null, errorMessagePattern
-
-  describe 'for *nix', ->
-
-    describe 'with valid id', ->
-      expected = [
-        '/usr/lib/node_modules/id/id.conf'
-        '/usr/lib/node_modules/id/id.json'
-        '/usr/lib/node_modules/id/.idrc'
-        '/usr/lib/node_modules/id/idrc'
-
-        '/usr/local/lib/node_modules/id/id.conf'
-        '/usr/local/lib/node_modules/id/id.json'
-        '/usr/local/lib/node_modules/id/.idrc'
-        '/usr/local/lib/node_modules/id/idrc'
-
-        '/etc/id.conf'
-        '/etc/id.json'
-        '/etc/.idrc'
-        '/etc/idrc'
-
-        '/etc/id/id.conf'
-        '/etc/id/id.json'
-        '/etc/id/.idrc'
-        '/etc/id/idrc'
-
-        '~/.conf/id.conf'
-        '~/.conf/id.json'
-        '~/.conf/.idrc'
-        '~/.conf/idrc'
-        '~/.conf/id'
-
-        '~/conf/id.conf'
-        '~/conf/id.json'
-        '~/conf/.idrc'
-        '~/conf/idrc'
-        '~/conf/id'
-
-        '~/id.conf'
-        '~/id.json'
-        '~/.idrc'
-        '~/idrc'
-
-        '~/id/id.conf'
-        '~/id/id.json'
-        '~/id/.idrc'
-        '~/id/idrc'
-      ]
-
-      # TODO: how do i test the pwd hierarchy ones??
-      pwd = [
-        '.conf/id.conf'
-        '.conf/id.json'
-        '.conf/.idrc'
-        '.conf/idrc'
-        '.conf/id'
-
-        'conf/id.conf'
-        'conf/id.json'
-        'conf/.idrc'
-        'conf/idrc'
-        'conf/id'
-
-        'id.conf'
-        'id.json'
-        '.idrc'
-        'idrc'
-
-        'id/id.conf'
-        'id/id.json'
-        'id/.idrc'
-        'id/idrc'
-      ]
-
-      files = getFiles id:'id', platform:'darwin'
-      for file,index in files
-        do (file, index) ->
-          if index < expected.length
-            it 'should have: ' + expected[index], ->
-              assert.equal file, expected[index]
-
-  describe 'for windows', ->
-
-    process.env.ALLUSERSPROFILE ?= '/allusers'
-    process.env.APPDATA        ?= '/appdata'
-    process.env.USERPROFILE    ?= process.env.HOME
-
-    describe 'with valid id', ->
-
-      alldata = process.env.ALLUSERSPROFILE
-      appdata = process.env.APPDATA
-      home    = process.env.USERPROFILE
-
-      expected = [
-        alldata + '/id.ini'
-        alldata + '/id.json'
-        alldata + '/idrc'
-
-        alldata + '/id/id.ini'
-        alldata + '/id/id.json'
-        alldata + '/id/idrc'
-
-        appdata + '/id.ini'
-        appdata + '/id.json'
-        appdata + '/idrc'
-
-        appdata + '/id/id.ini'
-        appdata + '/id/id.json'
-        appdata + '/id/idrc'
-
-        home + '/conf/id.ini'
-        home + '/conf/id.json'
-        home + '/conf/idrc'
-        home + '/conf/id'
-
-        home + '/id.ini'
-        home + '/id.json'
-        home + '/idrc'
-
-        home + '/id/id.ini'
-        home + '/id/id.json'
-        home + '/id/idrc'
-      ]
-
-      # TODO: how do i test the pwd hierarchy ones??
-      pwd = [
-        'conf/id.ini'
-        'conf/id.json'
-        'conf/idrc'
-        'conf/id'
-
-        'id.ini'
-        'id.json'
-        'idrc'
-
-        'id/id.ini'
-        'id/id.json'
-        'id/idrc'
-      ]
-
-      files = getFiles id:'id', platform:'win32'
-      for file,index in files
-        do (file, index) ->
-          if index < expected.length
-            it 'should have: ' + expected[index], ->
-              assert.equal file, expected[index]
+    it 'should produce a good file list'


Fix #27

Fix #27

--- lib/simulator.coffee
+++ lib/simulator.coffee
@@ -99,6 +99,7 @@

 simulateAerialCombat = (sortieShip, enemyShip, kouku) ->
   list = []
+  return list unless kouku?
   if kouku.api_edam?
     for damage, i in kouku.api_edam
       continue if (kouku.api_ebak_flag[i] <= 0 && kouku.api_erai_flag[i] <= 0) || i == 0


Remove remaining .view() calls

Remove remaining .view() calls

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -43,7 +43,7 @@
                 # mouse right click or ctrl click as right click on darwin platforms
                 (e.button is 2 || e.ctrlKey && process.platform is 'darwin')

-      entryToSelect = currentTarget.view()
+      entryToSelect = e.currentTarget

       if e.shiftKey
         @selectContinuousEntries(entryToSelect)
@@ -335,9 +335,9 @@
   openSelectedEntry: (changeFocus) ->
     selectedEntry = @selectedEntry()
     if selectedEntry instanceof DirectoryView
-      selectedEntry.view().toggleExpansion()
+      selectedEntry.toggleExpansion()
     else if selectedEntry instanceof FileView
-      atom.workspaceView.open(selectedEntry.getPath(), { changeFocus })
+      atom.workspaceView.open(selectedEntry.getPath(), {changeFocus})

   moveSelectedEntry: ->
     entry = @selectedEntry()
@@ -574,7 +574,7 @@
       @scrollTop(top + offset)

   scrollToBottom: ->
-    if lastEntry = @root?.find('.entry:last').view()
+    if lastEntry = @root?.find('.entry:last')[0]
       @selectEntry(lastEntry)
       @scrollToEntry(lastEntry)

@@ -604,7 +604,7 @@
   # => ['selected/path/one', 'selected/path/two', 'selected/path/three']
   # Returns Array of selected item paths
   selectedPaths: ->
-    $(item).view().getPath() for item in @list.find('.selected')
+    item.getPath() for item in @list.element.querySelectorAll('.selected')

   # Public: Selects items within a range defined by a currently selected entry and
   #         a new given entry. This is shift+click functionality
@@ -629,9 +629,7 @@
   #
   # Returns given entry
   selectMultipleEntries: (entry)->
-    entry = entry?.view()
-    return false unless entry?
-    entry.addClass('selected')
+    entry?.classList.add('selected')
     entry

   # Public: Toggle full-menu class on the main list element to display the full context


fix parentSelected

fix parentSelected

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -1071,7 +1071,7 @@

       for target in @getSelectedEntries()
         entryPath = target.querySelector(".name").dataset.path
-        parentSelected = @dragPaths.some (path) -> entryPath.startsWith path
+        parentSelected = target.parentNode.closest(".entry.selected")

         if parentSelected
           @deselect [target]
@@ -1130,8 +1130,7 @@
         return if initialPaths.includes(newDirectoryPath)

         entry.classList.remove('selected')
-
-        parentSelected = initialPaths.some (path) -> newDirectoryPath.startsWith path
+        parentSelected = entry.parentNode.closest('.entry.selected')
         return if parentSelected

         # iterate backwards so files in a dir are moved before the dir itself


Update uber-juno.coffee

Update uber-juno.coffee

--- lib/uber-juno.coffee
+++ lib/uber-juno.coffee
@@ -19,6 +19,7 @@
     'julia-client.enableMenu': true
     'julia-client.enableToolBar': true
     'julia-client.launchOnStartup': true
+    'editor.scrollPastEnd': true

   configSetup: ->
     for k, v of @defaultConfig


More robust error handling

More robust error handling

--- lib/vie-redis.coffee
+++ lib/vie-redis.coffee
@@ -52,13 +52,16 @@
             for subject in subjects
               itemInstance = vie.entities.addOrUpdate
                 '@subject': subject
+              ,
+                 overrideAttributes: true
               itemInstance.fetch
                 success: (item) ->
                   instances.push item
                   if instances.length >= subjects.length
                     options.success instances
                 error: (error) ->
-                  console.log "ERR", err
+                  console.log "Failed to fetch #{model.object} #{model.predicate} #{subject}"
+                  options.error err
         else
           throw "When seeking Collections, you must provide predicate and object"


:art: for Operator command!!

:art: for Operator command!!

Still all spec pass.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -85,37 +85,17 @@
       'copy-from-line-below': => InsertMode.copyCharacterFromBelow(@editor, this)

     @registerOperationCommands
-      # Operator
+      # Operators not following naming conventions.
       'activate-insert-mode': 'Insert'
       'activate-replace-mode': 'ReplaceMode'
       'substitute': => ['Change', new Motions.MoveRight(@editor, this)]
-      'substitute-line': 'SubstituteLine'
-      'insert-after': 'InsertAfter'
-      'insert-after-end-of-line': 'InsertAfterEndOfLine'
-      'insert-at-beginning-of-line': 'InsertAtBeginningOfLine'
-      'insert-above-with-newline': 'InsertAboveWithNewline'
-      'insert-below-with-newline': 'InsertBelowWithNewline'
-      'delete': 'Delete'
-      'change': 'Change'
       'change-to-last-character-of-line': => ['Change', new Motions.MoveToLastCharacterOfLine(@editor, this)]
       'delete-right': => ['Delete', new Motions.MoveRight(@editor, this)]
       'delete-left': => ['Delete', new Motions.MoveLeft(@editor, this)]
       'delete-to-last-character-of-line': => ['Delete', new Motions.MoveToLastCharacterOfLine(@editor, this)]
-      'toggle-case': 'ToggleCase'
-      'upper-case': 'UpperCase'
-      'lower-case': 'LowerCase'
-      'toggle-case-now': 'ToggleCaseNow'
-      'yank': 'Yank'
       'yank-line': => ['Yank', new Motions.MoveToRelativeLine(@editor, this)]
-      'put-before': 'PutBefore'
-      'put-after': 'PutAfter'
-      'join': 'Join'
-      'indent': 'Indent'
-      'outdent': 'Outdent'
-      'auto-indent': 'AutoIndent'
-      'increase': 'Increase'
-      'decrease': 'Decrease'
-
+
+      # Motions
       'move-left': => new Motions.MoveLeft(@editor, this)
       'move-up': => new Motions.MoveUp(@editor, this)
       'move-down': => new Motions.MoveDown(@editor, this)
@@ -167,26 +147,49 @@
       'select-around-parentheses': => new TextObjects.SelectInsideBrackets(@editor, '(', ')', true)
       'select-around-paragraph': => new TextObjects.SelectAParagraph(@editor, true)
       'register-prefix': (e) => @registerPrefix(e)
-      'repeat': 'Repeat'
       'repeat-search': => new Motions.RepeatSearch(@editor, this)
       'repeat-search-backwards': => new Motions.RepeatSearch(@editor, this).reversed()
       'move-to-mark': => new Motions.MoveToMark(@editor, this)
       'move-to-mark-literal': => new Motions.MoveToMark(@editor, this, false)
-      'mark': 'Mark'
       'find': => new Motions.Find(@editor, this)
       'find-backwards': => new Motions.Find(@editor, this).reverse()
       'till': => new Motions.Till(@editor, this)
       'till-backwards': => new Motions.Till(@editor, this).reverse()
       'repeat-find': => new @globalVimState.currentFind.constructor(@editor, this, repeated: true) if @globalVimState.currentFind
       'repeat-find-reverse': => new @globalVimState.currentFind.constructor(@editor, this, repeated: true, reverse: true) if @globalVimState.currentFind
-      'replace': 'Replace'
       'search': => new Motions.Search(@editor, this)
       'reverse-search': => new Motions.Search(@editor, this).reversed()
       'search-current-word': => new Motions.SearchCurrentWord(@editor, this)
       'bracket-matching-motion': => new Motions.BracketMatchingMotion(@editor, this)
       'reverse-search-current-word': => new Motions.SearchCurrentWord(@editor, this).reversed()

-    @registerScrollCommands [
+    @registerNewOperationCommands Operators, [
+      'substitute-line'
+      'insert-after'
+      'insert-after-end-of-line'
+      'insert-at-beginning-of-line'
+      'insert-above-with-newline'
+      'insert-below-with-newline'
+      'delete'
+      'change'
+      'toggle-case'
+      'upper-case'
+      'lower-case'
+      'toggle-case-now'
+      'yank'
+      'put-before',
+      'put-after'
+      'join'
+      'indent'
+      'outdent'
+      'auto-indent'
+      'increase'
+      'decrease'
+      'repeat'
+      'mark'
+      'replace'
+    ]
+    @registerNewOperationCommands Scroll, [
       'scroll-down'
       'scroll-up'
       'scroll-cursor-to-top'
@@ -228,15 +231,17 @@
         commands[name] = (event) => @operationStack.push(fn(event))
     @registerCommands(commands)

-  # Register Scroll command.
-  # By maping command name to correspoinding scroll class.
-  #  e.g. scroll-down -> ScrollDown
-  registerScrollCommands: (scrollCommands) ->
+  # 'New' is 'new' way of registration to distinguish exisiting function.
+  # By maping command name to correspoinding class.
+  #  e.g.
+  # join -> Join
+  # scroll-down -> ScrollDown
+  registerNewOperationCommands: (kind, names) ->
     commands = {}
-    for name in scrollCommands
+    for name in names
       do (name) =>
         klass = _.capitalize(_.camelize(name))
-        commands[name] = => new Scroll[klass](this)
+        commands[name] = => new kind[klass](this)
     @registerOperationCommands(commands)

   onDidFailToCompose: (fn) ->


improve the upgrade red warning/error

improve the upgrade red warning/error

--- src/smc-webapp/app_shared.cjsx
+++ src/smc-webapp/app_shared.cjsx
@@ -1,6 +1,6 @@
 {React, ReactDOM, rclass, redux, rtypes, Redux, Actions, Store, COLOR} = require('./smc-react')
 {Button, Col, Row, Modal, NavItem} = require('react-bootstrap')
-{Icon, Tip} = require('./r_misc')
+{Icon, Space, Tip} = require('./r_misc')
 {salvus_client} = require('./salvus_client')

 {HelpPage} = require('./r_help')
@@ -304,11 +304,11 @@
         if @props.new_version.min_version > salvus_client.version()

-                THIS IS A CRITICAL UPDATE. YOU MUST
-                window.location.reload()} style={color: 'white', fontWeight: 'bold', textDecoration: 'underline'}>
+                THIS IS A CRITICAL UPDATE. YOU MUST <Space/>
+                window.location.reload()} style={cursor:'pointer', color: 'white', fontWeight: 'bold', textDecoration: 'underline'}>
                     RELOAD THIS PAGE

-                 IMMEDIATELY OR YOU WILL BE DISCONNECTED.  Sorry for the inconvenience.
+                <Space/> IMMEDIATELY OR YOU WILL BE DISCONNECTED.  Sorry for the inconvenience.

     render_close: ->
@@ -334,8 +334,8 @@
             width           : '70%'
             marginTop       : '1em'

-            <Icon name='refresh' /> New Version Available: upgrade by clicking on
-            window.location.reload()} style={color: 'white', fontWeight: 'bold', textDecoration: 'underline'}>
+            <Icon name='refresh' /> New Version Available: upgrade by clicking on <Space/>
+            window.location.reload()} style={cursor:'pointer', color: 'white', fontWeight: 'bold', textDecoration: 'underline'}>
                 reload this page
             .
             {@render_close()}


fix how coffee is built

fix how coffee is built

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -86,7 +86,7 @@
 #     $COMPILE COFFEE
 #*------------------------------------*/
 gulp.task "coffee", () ->
-  gulp.src ["#{conf.path.dev.coffee}/**/*.coffee"]
+  gulp.src ["./#{conf.path.dev.coffee}/**/*.coffee"]
     .pipe plumber(conf.plumber)
     .pipe cache(coffee({bare: true}).on('error', gutil.log))
     .pipe remember()


update melanistic gray squirrel description

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -375,7 +375,7 @@
     melanisticGraySquirrel:
       label: 'Squirrel, Melanistic Gray'
       description: '''
-        This animal has a color morph to change its fur from gray to black. You may occasionally see them during the color-changing process when they will appear to be patchy brown and black.
+        Melanism is a genetic trait that produces extra melanin in the skin and fur, making an organism appear jet black if it has two sets of this recessive gene or brown/black if it only has one.  Gray squirrels are known to carry this unusual genetic trait.   This trait may be beneficial to gray squirrels living at northern latitudes as they would lose less heat during the cold winter months.  The photo in this ID is of a brown/black melanistic gray squirrel.
       '''

   differences:


core:select-up/down should work in mini editor as well as main editor.

Fixes #6445.

--- src/text-editor-element.coffee
+++ src/text-editor-element.coffee
@@ -244,6 +244,8 @@
   'core:move-right': -> @moveRight()
   'core:select-left': -> @selectLeft()
   'core:select-right': -> @selectRight()
+  'core:select-up': -> @selectUp()
+  'core:select-down': -> @selectDown()
   'core:select-all': -> @selectAll()
   'editor:move-to-previous-word': -> @moveToPreviousWord()
   'editor:select-word': -> @selectWordsContainingCursors()
@@ -297,8 +299,6 @@
   'core:move-to-bottom': -> @moveToBottom()
   'core:page-up': -> @pageUp()
   'core:page-down': -> @pageDown()
-  'core:select-up': -> @selectUp()
-  'core:select-down': -> @selectDown()
   'core:select-to-top': -> @selectToTop()
   'core:select-to-bottom': -> @selectToBottom()
   'core:select-page-up': -> @selectPageUp()


Replace trailing-newline with detect-newline-at-eof

--- src/brave-mouse/validate.coffee
+++ src/brave-mouse/validate.coffee
@@ -4,7 +4,7 @@
 detectIndent = require 'detect-indent'
 detectNewline = require 'detect-newline'
 detectTrailingWhitespace = require 'detect-trailing-whitespace'
-trailingNewline = require 'trailing-newline'
+detectNewlineAtEof = require 'detect-newline-at-eof'
 maxLineLength = require 'max-line-length'

 module.exports = (filePath, callback) ->
@@ -54,7 +54,7 @@
 						is: !hasTrailingWhitespace

 			if editorconfigProperties.hasOwnProperty 'insert_final_newline'
-				hasFinalNewline = trailingNewline fileContents
+				hasFinalNewline = !!detectNewlineAtEof fileContents

 				if hasFinalNewline isnt editorconfigProperties.insert_final_newline
 					results.insert_final_newline =


CredentialHelper: fix linter issue FP

CredentialHelper: fix linter issue FP

--- workers/social/testhelper/models/computeproviders/credentialhelper.coffee
+++ workers/social/testhelper/models/computeproviders/credentialhelper.coffee
@@ -51,7 +51,7 @@

     addToRemoveList client, credential.identifier  if credential

-    console.log ">>> ERROR ON CREATE CREDENTIAL", err  if err
+    console.log '>>> ERROR ON CREATE CREDENTIAL', err  if err

     callback err, { credential }


Stupid developer, elements must be closed. Closes #15

--- app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
+++ app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
@@ -279,6 +279,7 @@
       geometry = if data.orientation == 'portrait' then 'x300>' else '480x>'
       url = "#{@options.base_url}#{@options.thumb_url}/#{geometry}?uid=#{data.uid}"
       html = Mustache.to_html(@tpl('show'),{ filename: data.uid, src: url, orientation: data.orientation })
+      console.log html
       $.facebox("
#{html}
")
     @touch(data)

@@ -490,12 +491,12 @@

-
{{ filename }}

+
{{ filename }}

'
     pdf: '

-
{{ filename }}

+
{{ filename }}

'
     empty: '
Oops. There\'s nothing here. You should upload something.
'


Fix typo in month conditional

Fix typo in month conditional

--- app/assets/javascripts/controllers/user_index_controller.js.coffee
+++ app/assets/javascripts/controllers/user_index_controller.js.coffee
@@ -140,7 +140,7 @@
     if months > 0
       if str.length > 0
         str += ", "
-      str += months + " " + (if month == 1 then "month" else "months")
+      str += months + " " + (if months == 1 then "month" else "months")
     if days > 0
       if str.length > 0
         str += ", "


fix postMessage origin check

fix postMessage origin check

--- app/assets/javascripts/uploadcare/widget/tabs/remote-tab.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/remote-tab.js.coffee
@@ -15,8 +15,10 @@
           if tab == service
             @createIframe()

+        nos = (str) -> str.replace(/^https/, 'http')
+
         $(window).on "message", ({originalEvent: e}) =>
-          goodOrigin = e.origin is @settings.socialBase
+          goodOrigin = nos(e.origin) is nos(@settings.socialBase)
           goodSource = e.source is @iframe?[0]?.contentWindow
           if goodOrigin and goodSource
             message = JSON.parse e.data


hacky way to fix #7

hacky way to fix #7

--- app/assets/javascripts/views/order_items/order_item_view.js.coffee
+++ app/assets/javascripts/views/order_items/order_item_view.js.coffee
@@ -9,6 +9,7 @@

   removeOrderItem: ->
     @get("model").deleteRecord()
+    @$().addClass("hidden") unless @get("model.isNew") # hacky way to hide view when editing

   didInsertElement: ->
     @$(".glyphicon").on "click", $.proxy(@removeOrderItem, @)


Fix subscribers feed pagination

Fix subscribers feed pagination

--- lib/assets/javascripts/views/subscribers_feed.js.coffee
+++ lib/assets/javascripts/views/subscribers_feed.js.coffee
@@ -26,5 +26,5 @@
     relationships: relationships

   renderPostHTML: (post) =>
-    @constructor.partials['_relationship'].render({ relationship: post }, @constructor.partials)
+    @constructor.partials['relationship'].render({ relationship: post }, @constructor.partials)


handle backbutton correctly and add some comments

--- src/shared/component.coffee
+++ src/shared/component.coffee
@@ -1,3 +1,7 @@
+# responsibilities:
+# toplevel component and entry point for the clientside.
+# manages and owns url-cursor, keeps it up to date with browser url and feeds it to
+# router component which dispatches on it.
 module.exports.ComponentRoot = (
   React
   reactKup
@@ -7,25 +11,34 @@
 ) ->
   React.createClass
     mixins: [Cursors]
-    # The only component that should need to define `getInitialState` is the root
-    # component. Child components can define their initial state, but state that
+    # the only component that should need to define `getInitialState` is the root
+    # component. child components can define their initial state, but state that
     # is passed into them via parent cursors will override the corresponding
     # initial state.
     getInitialState: ->
       {
+        # initialize url-cursor
         url: getCurrentPath()
       }
+    handlePopstate: (event) ->
+      this.update {url: {$set: getCurrentPath()}}
     componentDidMount: ->
       that = this
       console.log 'ComponentHistory.componentDidMount'
+      # update the url-cursor whenever an item is received from the clickStream
       that.props.clickStream.each (data) ->
         that.update {url: {$set: data.relative}}
+      window.addEventListener 'popstate', this.handlePopstate
+    componentWillUnmount: ->
+      that = this
+      console.log 'ComponentHistory.componentDidMount'
+      window.removeEventListener 'popstate', this.handlePopstate
     render: ->
       that = this
       reactKup (k) ->
         k.div {className: 'ComponentRoot'}, ->
           k.div "ComponentRoot"
-          # dispatches on history
+          # router dispatches on url-cursor
           k.build ComponentRouter,
             cursors:
               url: that.getCursor('url')


Add spec for pattern matching of Error types

--- src/spec/errors.spec.coffee
+++ src/spec/errors.spec.coffee
@@ -1,4 +1,5 @@
 _ = require 'underscore'
+Promise = require 'bluebird'
 {Errors} = require '../lib/main'

 ERRORS = [
@@ -56,3 +57,17 @@
       expect(ce instanceof Error).toBe true
       expect(ce instanceof Errors.SphereError).toBe true
       expect(ce instanceof Errors.SphereHttpError[error.name]).toBe true
+
+    it "should use #{error.name} constructor for pattern matching", (done) ->
+      ce = new Errors.SphereHttpError[error.name] 'Ooops'
+      r = ->
+        new Promise (resolve, reject) ->
+          setTimeout ->
+            reject ce
+          , 100
+      r().then -> done 'It should have been rejected'
+      .catch Errors.SphereHttpError[error.name], (e) ->
+        expect(e.message).toBe 'Ooops'
+        done()
+      .catch (e) -> done 'It should have caught the exception before'
+      .done()


minor changes/fixes and reorderings

minor changes/fixes and reorderings

--- library/semantic/flavours.coffee
+++ library/semantic/flavours.coffee
@@ -31,7 +31,7 @@
 # prerequisites necessary to properly make usage of the Semantic UI
 # frontend framework that atre shipped within the primary framework.
 # This frontend scaffolding is used to style the provided components.
-module.exports.Semantic = class Semantic extends Preflight
+assert module.exports.Semantic = class Semantic extends Preflight

     # This is a marker that indicates to some internal subsystems
     # that this class has to be considered abstract and therefore


Use cookies for callback instead of session

--- app/routes.coffee
+++ app/routes.coffee
@@ -13,7 +13,8 @@
     @app.get '/oauthcallback', passport.authenticate('google', { failureRedirect: '/login' }), @afterPassportLogin

   afterPassportLogin: (request, response) =>
-    {callbackUrl} = request.session
+    {callbackUrl} = request.cookies
+    delete request.cookies.callbackUrl
     return response.status(401).send(new Error 'Invalid User') unless request.user
     return response.status(201).send(request.user) unless callbackUrl?
     uriParams = url.parse callbackUrl
@@ -26,7 +27,10 @@
     response.render 'index'

   storeCallbackUrl: (request, response, next) =>
-    request.session.callbackUrl = request.query.callbackUrl
+    if request.query.callbackUrl?
+      request.cookies.callbackUrl = request.query.callbackUrl
+    else
+      delete request.cookies.callbackUrl
     next()

 module.exports = Router


s/domo/dōmo

s/domo/dōmo

--- docs/dom.coffee
+++ docs/dom.coffee
@@ -81,7 +81,7 @@
     H3 "Example"

     P {},
-      "The following is a simple, self-contained example using domo in the browser. "
+      "The following is a simple, self-contained example using dōmo in the browser. "
       "It replaces the existing documentElement with a DOM tree, using a simple function as a CSS mixin."

     DIV class: "sub",
@@ -96,7 +96,7 @@

             HTML({lang: "en"},
               HEAD(
-                TITLE("Simple domo example"),
+                TITLE("Simple dōmo example"),
                 STYLE({type: "text/css"},
                   CSS("body", {textAlign: "center", fontSize: 100}),
                   CSS("h1", opacity(50), {background: "#000", color: "#fff"})
@@ -108,20 +108,20 @@
           <\/script>
         """

-    H3 "Why domo?"
+    H3 "Why dōmo?"

-    UL class: "why-domo",
+    UL class: "why-dōmo",
       LI B "Reduce your exposure to XSS attacks. "
       "Writing straight from JavaScript to DOM without an HTML step means you don't need to rely on a library to sanitize your rendered data because the browser does it for you automatically."

       LI B "Eliminate a separate compile step. "
-      "With domo, 'compilation' is done in the same JavaScript process as your code, which means that any syntax errors are thrown with line numbers in the same JavaScript process."
+      "With dōmo, 'compilation' is done in the same JavaScript process as your code, which means that any syntax errors are thrown with line numbers in the same JavaScript process."

-      LI B "Don't let implentations drive architectural decisions. "
+      LI B "Don't let implementations drive architectural decisions. "
       "If you're writing a view that renders a DOM node, you can write it directly within the rendering code for convenience, and then pull it out into its own file when the size of your app requires it."

       LI B "Use JavaScript syntax everywhere. "
-      "Instead of remembering which HAML or LESS symbols map to which behaviors like looping or escaping or conditionals or negation, just use JavaScript."
+      "Instead of remembering which HAML or LESS symbols map to which behaviors, like looping or escaping or conditionals or negation, just use JavaScript."

       LI B "Decouple your syntax sugar. "
       "Instead of choosing a template engine or CSS compiler with the ideal syntax, just use JavaScript, and do your sweetening on a more reliable level. CoffeeScript can go a long way in making DOM building code look like HAML, for example."
@@ -135,11 +135,11 @@
       LI B "Lessen the burden of context switching. "
       "Whether using JavaScript on both the client and server enables code reuse is debatable, but that it prevents the overhead of switching contexts between languages is less so. It may be subjective, but I think using one language everywhere reduces the congitive overhead for web app development."

-    H3 "Using domo on the server"
+    H3 "Using dōmo on the server"

     P {},
-      "domo really shines when used to build DOM code on the client. "
-      "But since you'll likely need to render an HTML client in the first place, domo also ships with a window.document shim for rendering HTML on the server. "
+      "dōmo really shines when used to build DOM code on the client. "
+      "But since you'll likely need to render an HTML client in the first place, dōmo also ships with a window.document shim for rendering HTML on the server. "
       "It's a small (under 1KB minizipped) mock DOM implementation with just enough logic to render HTML. "

     P {},
@@ -147,5 +147,5 @@
       "Just `require('domo')`, create a DOM, and then use the `outerHTML` property to serialize it into HTML."

     P {},
-      "domo also adds a top-level `DOCUMENT` function for creating an entire HTML document with doctype, like this:"
+      "dōmo also adds a top-level `DOCUMENT` function for creating an entire HTML document with doctype, like this:"


Fix undefined variable error.

Fix undefined variable error.

Conflicts:

	src/controllers/torrents.coffee

--- src/controllers/torrents.coffee
+++ src/controllers/torrents.coffee
@@ -143,12 +143,12 @@
             torrent.uploader = req.session.user.name
           if fields.useexternaltracker
             torrent.external_tracker = torrentInfo.announce
-          redis.SET 'torrent:'+infohash+':desc', md.toHTML(fields.description)
-          torrent.generatePermalink (err) ->
-            torrent.save (err) ->
-              fs.writeFile (__dirname+'/../../torrents/' + infohash + '.torrent'), data, (err) ->
-                fs.unlink f_path, (err) ->
-                  res.redirect ('/torrent/' + torrent.permalink)
+          redis.SET 'torrent:'+infohash+':desc', md.toHTML(fields.description), (err, data) ->
+            torrent.generatePermalink (err) ->
+              torrent.save (err) ->
+                fs.writeFile (__dirname+'/../../torrents/' + infohash + '.torrent'), data, (err) ->
+                  fs.unlink f_path, (err) ->
+                    res.redirect ('/torrent/' + torrent.permalink)

   if files.torrent.size > 0
     process_file files.torrent.path
@@ -232,7 +232,8 @@
         replies.shift()
         doc.peers = replies.shift()
         doc.seeds = replies.shift()
-        redis.GET 'torrent:' + doc.infohash + ':desc', (err, data) -> # not part of the multi because that does weird things with arrays
+        key_desc = 'torrent:'+doc.infohash+':desc'
+        redis.GET key_desc, (err, data) -> # not part of the multi because that does weird things with arrays
           if data == null # this is probably completely unnecessary
             conv = md.toHTML doc.description
             redis.SET key_desc, conv


extract col

extract col

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -51,10 +51,12 @@
     t.equal body.attr.class[0], header._LABEL, 'header label'

     t.ok body.body, "header rows"
-    t.ok child = body.body[0], "first child"
-    t.equal child.attr.class[0], 'game', 'child label'
+    t.ok row = body.body[0], "first row"
+    t.equal row.tag, 'div', 'row tag'
+    t.equal row.attr.class[0], 'game', 'row label'

     t.ok cols = body.body[3], 'last row'
+    t.ok col = cols.body[0], 'first col'
     t.end()

   test "render children", (t) ->


switch tests back to global relay

switch tests back to global relay

--- tests/helper.coffee
+++ tests/helper.coffee
@@ -36,7 +36,7 @@
 # instructions at https://github.com/vault12/zax#installation

 # select a preset (local vs remote) or edit your own settings
-localTest = true
+localTest = false

 if localTest
   # local testing


Add api doc.

Add api doc.

--- src/testing/findable.coffee
+++ src/testing/findable.coffee
@@ -362,6 +362,7 @@
  *
  * @module testing
  * @class Table
+ * @extends Findable
 ###
 class Table extends Findable
   ###*


Fix focus on tab selection

Fix focus on tab selection

When you select the terminal tab, the terminal should be ready to type

--- lib/TermView.coffee
+++ lib/TermView.coffee
@@ -92,12 +92,20 @@

   attachResizeEvents: ->
     setTimeout (=>  @resizeToPane()), 10
-    @on 'focus', @resizeToPane
+    @on 'focus', @onFocus
     $(window).on 'resize', => @resizeToPane()

   detachResizeEvents: ->
-    @off 'focus', @resizeToPane
+    @off 'focus', @onFocus
     $(window).off 'resize'
+
+  focus: ->
+    @resizeToPane()
+    @focusTerm()
+
+  focusTerm: ->
+    @term.element.focus()
+    @term.focus()

   resizeToPane: ->
     {cols, rows} = @getDimensions()


Mini Commit

Mini Commit

--- lib/d-struct.coffee
+++ lib/d-struct.coffee
@@ -3,7 +3,7 @@
 platform = require('os').platform
 paths = atom.project.getPaths().shift()

-# TODO: linux support still needs to be added
+# TODO:--linux-support-still-needs-to-be-added-- Nope! All done
 commands =
     build:
         win32: "start cmd /k \"cd #{paths} && dub run\""
@@ -33,4 +33,3 @@
     dub_build()
   dubrun: ->
     dub_run()
-


PDFDocument#write callback support

PDFDocument#write callback support

--- lib/document.coffee
+++ lib/document.coffee
@@ -80,8 +80,8 @@
         @page.content.add str
         return this # make chaining possible

-    write: (filename) ->
-         fs.writeFile filename, @output(), 'binary'
+    write: (filename, callback) ->
+         fs.writeFile filename, @output(), 'binary', callback

     output: ->
        out = []


manyboxplots: axis below

manyboxplots: axis below

--- manyboxplots/manyboxplots.coffee
+++ manyboxplots/manyboxplots.coffee
@@ -39,11 +39,12 @@
      .attr("fill", d3.rgb(200, 200, 200))

   # axis on left
-  axis = svg.append("g")
+  LaxisData = [-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1]
+  Laxis = svg.append("g")

   # axis: white lines
-  axis.append("g").selectAll("line.axis")
-     .data([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1])
+  Laxis.append("g").selectAll("empty")
+     .data(LaxisData)
      .enter()
      .append("line")
      .attr("class", "line")
@@ -55,8 +56,8 @@
      .attr("stroke", "white")

   # axis: labels
-  axis.append("g").selectAll("text.axis")
-     .data([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1])
+  Laxis.append("g").selectAll("empty")
+     .data(LaxisData)
      .enter()
      .append("text")
      .attr("class", "axis")
@@ -65,6 +66,36 @@
      .attr("y", (d) -> yScale(d))
      .attr("dominant-baseline", "middle")
      .attr("text-anchor", "end")
+
+
+  # axis on left
+  BaxisData = [50, 100, 150, 200, 250, 300, 350, 400, 450]
+  Baxis = svg.append("g")
+
+  # axis: white lines
+  Baxis.append("g").selectAll("empty")
+     .data(BaxisData)
+     .enter()
+     .append("line")
+     .attr("class", "line")
+     .attr("class", "axis")
+     .attr("y1", pad)
+     .attr("y2", h-pad)
+     .attr("x1", (d) -> xScale(d))
+     .attr("x2", (d) -> xScale(d))
+     .attr("stroke", "white")
+
+  # axis: labels
+  Baxis.append("g").selectAll("empty")
+     .data(BaxisData)
+     .enter()
+     .append("text")
+     .attr("class", "axis")
+     .text((d) -> d)
+     .attr("y", h-pad*0.7)
+     .attr("x", (d) -> xScale(d))
+     .attr("dominant-baseline", "middle")
+     .attr("text-anchor", "middle")

   # curves for quantiles
@@ -84,7 +115,7 @@
   # vertical rectangles representing each array
   indRectGrp = svg.append("g")

-  indRect = indRectGrp.selectAll("rect.ind")
+  indRect = indRectGrp.selectAll("empty")
                  .data(data.ind)
                  .enter()
                  .append("rect")
@@ -105,7 +136,7 @@
   rightAxis = svg.append("g")

   for j in [0...nQuant]
-    rightAxis.selectAll("text.qu")
+    rightAxis.selectAll("empty")
        .data(data.qu)
        .enter()
        .append("text")
@@ -128,6 +159,5 @@
      .attr("stroke-width", 2)
      .attr("fill", "none")

-
 # load json file and call draw function
 d3.json("hypo.json", draw)


make provider disposable

make provider disposable

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -21,6 +21,10 @@
     data: { references: @references, possibleWords: @possibleWords }
   }

+  dispose: () ->
+    for watch in @watchedFiles
+      watch.close()
+
   # TODO Could add a system to register additional readers
   fileReaders: [new BibtexReader(), new CiteprocReader(), new YamlReader()]

@@ -38,9 +42,12 @@
       @possibleWords = saveState.possibleWords
     else
       @updateReferences(@referenceFiles)
-    @updateReferences(@referenceFiles)
-
+
+    @watchedFiles = []
     @registerReferenceFiles(@referenceFiles)
+
+    # @updateReferences(@referenceFiles)
+

     atom.config.onDidChange "autocomplete-bibtex.bibtex", (newReferenceFiles, oldReferenceFiles) =>
       # TODO might want to add modifed checks to update references
@@ -158,13 +165,14 @@
     @possibleWords = @buildWordList(@references)

   registerReferenceFiles: (referenceFiles) =>
+    @watchedFiles = []
     try
       for file in referenceFiles
         if fs.statSync(file).isFile()
           watch = fs.watch(file, (eventType, filename) =>
-            console.log 'Updating files'
             @updateReferences(referenceFiles)
           )
+          @watchedFiles.push(watch)
         else
           console.warn("'#{file}' does not appear to be a file, so autocomplete-bibtex will not try to parse it.")
     catch error


Refactor checkDone

Refactor checkDone

--- lib/surveyor.coffee
+++ lib/surveyor.coffee
@@ -80,7 +80,10 @@
     errs = null
     procs = {}
     numProcs = 0
-    checkDone = (slave, err, proc) ->
+    checkDone = (err, info) ->
+      slave = info.slave
+      proc = info.proc
+      opts = info.opts
       numProcs--
       if err?
         errs = [] if !errs?
@@ -104,11 +107,11 @@
           repoData.delta--
           do (target) =>
             @spawn target, repoData.opts, checkDone
-  @spawn = (slave, opts, cb) ->
-    @populateOptions slave, opts, (err, opts) ->
-      return checkDone slave, err, null if err?
-      cavalry.spawn slave, opts, (err, res) ->
-        cb slave, err, res
+  @spawn = (slave, opts, cb) =>
+    @populateOptions slave, opts, (err, opts) =>
+      return cb err, {slave: slave, proc: null, opts: opts} if err?
+      cavalry.spawn slave, opts, (err, res) =>
+        cb err, {slave: slave, proc: res, opts: opts}
   @updatePortMap = (slave, opts, pid) ->
     if opts.env? and opts.env.PORT?
       model.portMap ?= {}


Fixes chaining after inline implicit objects

--- rewriter.coffee
+++ rewriter.coffee
@@ -160,11 +160,6 @@
         tokens.splice i, 0, generate 'CALL_END', ')'
         i += 1

-      endAllImplicitCalls = ->
-        while inImplicitCall()
-          endImplicitCall()
-        return
-
       startImplicitObject = (j, startsLine = yes) ->
         idx = j ? i
         stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes]
@@ -289,15 +284,11 @@
       #     f a
       #     .g b
       #     .h a
-      #
-      if inImplicitCall() and tag in CALL_CLOSERS and
-         (prevTag is 'OUTDENT' or prevToken.newLine)
-        endAllImplicitCalls()
-        return forward(1)

       stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS

-      if tag in IMPLICIT_END
+      newLine = prevTag is 'OUTDENT' or prevToken.newLine
+      if tag in IMPLICIT_END or tag in CALL_CLOSERS and newLine
         while inImplicit()
           [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
           # Close implicit calls when reached end of argument list
@@ -495,4 +486,4 @@
 LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']

 # Tokens that close open calls when they follow a newline.
-CALL_CLOSERS = ['.', '?.', '::', '?::']
+CALL_CLOSERS     = ['.', '?.', '::', '?::']


save api key to common config file to fix #26

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -11,7 +11,6 @@
 unloadHandler = null
 lastHeartbeat = 0
 lastFile = ''
-apiKey = null
 statusBarTileView = null
 pluginReady = false

@@ -75,8 +74,7 @@
     )
     cleanupOnUninstall()
     setupEventHandlers()
-    setApiKey()
-    pluginReady = true
+    setupConfigs()
     statusBarTileView?.setTitle('WakaTime initialized')
     statusBarTileView?.setStatus()
     @settingChangedObserver = atom.config.observe 'wakatime', @settingChangedHandler
@@ -96,34 +94,82 @@
     statusBarTileView?.destroy()
     @settingChangedObserver?.dispose()

-  settingChangedHandler: (newValue) ->
-    if newValue.showStatusBarIcon
+  settingChangedHandler: (settings) ->
+    if settings.showStatusBarIcon
       statusBarTileView?.show()
     else
       statusBarTileView?.hide()
+    if pluginReady
+      apiKey = settings.apikey
+      if isValidApiKey(apiKey)
+        atom.config.set 'wakatime.apikey', '' # clear setting so it updates in UI
+        atom.config.set 'wakatime.apikey', 'Saved in your ~/.wakatime.cfg file'
+        saveApiKey apiKey
+
+saveApiKey = (apiKey) ->
+  configFile = path.join getUserHome(), '.wakatime.cfg'
+  fs.readFile configFile, 'utf-8', (err, inp) ->
+    if err?
+      console.warn 'Error: could not read wakatime config file'
+      inp = ''
+    String::startsWith ?= (s) -> @slice(0, s.length) == s
+    String::endsWith   ?= (s) -> s == '' or @slice(-s.length) == s
+    contents = []
+    currentSection = ''
+    found = false
+    for line in inp.split('\n')
+      if line.trim().startsWith('[') and line.trim().endsWith(']')
+        if currentSection == 'settings' and not found
+          contents.push('api_key = ' + apiKey)
+          found = true
+        currentSection = line.trim().substring(1, line.trim().length - 1).toLowerCase()
+        contents.push(line)
+      else if currentSection == 'settings'
+        parts = line.split('=')
+        currentKey = parts[0].trim()
+        if currentKey == 'api_key'
+          if not found
+            contents.push('api_key = ' + apiKey)
+            found = true
+        else
+          contents.push(line)
+      else
+        contents.push(line)
+
+    if not found
+      if currentSection != 'settings'
+        contents.push('[settings]')
+      contents.push('api_key = ' + apiKey)
+
+    fs.writeFile configFile, contents.join('\n'), {encoding: 'utf-8'}, (err2) ->
+      if err2?
+        msg = 'Error: could not write to wakatime config file'
+        console.error msg
+        statusBarTileView?.setStatus('Error')
+        statusBarTileView?.setTitle(msg)

 getUserHome = ->
   process.env[if process.platform == 'win32' then 'USERPROFILE' else 'HOME'] || ''

-setApiKey = ->
-  loadApiKey (err, key) ->
-    if err
-      console.log err.message
-    else
-      apiKey = key
-
-loadApiKey = (cb) ->
-  key = atom.config.get('wakatime.apikey')
-  return cb(null, key) if key? && key.length > 0
-  wakatimeConfigFile = path.join getUserHome(), '.wakatime.cfg'
-  fs.readFile wakatimeConfigFile, 'utf-8', (err, configContent) ->
-    return cb(new Error('could not read wakatime config')) if err?
-    wakatimeConfig = ini.parse configContent
-    key = wakatimeConfig?.settings?.api_key
-    if key?
-      cb null, key
-    else
-      cb new Error('wakatime key not found')
+setupConfigs = ->
+  configFile = path.join getUserHome(), '.wakatime.cfg'
+  fs.readFile configFile, 'utf-8', (err, configContent) ->
+    pluginReady = true
+    if err?
+      console.log 'Error: could not read wakatime config file'
+      return
+    commonConfigs = ini.decode configContent
+    if commonConfigs? and commonConfigs.settings?
+      atomConfigs = atom.config.get('wakatime')
+      atomConfigs.apikey = commonConfigs.settings.api_key
+      atomConfigs.debug = not not commonConfigs.settings.debug
+      atom.config.set 'wakatime', atomConfigs
+
+isValidApiKey = (key) ->
+  if not key?
+    return false
+  re = new RegExp('^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$', 'i')
+  return re.test key

 enoughTimePassed = (time) ->
   return lastHeartbeat + 120000 < time
@@ -329,8 +375,8 @@
   currentFile = file.path
   if isWrite or enoughTimePassed(time) or lastFile isnt currentFile
     pythonLocation (python) ->
-      return unless python? && apiKey?
-      args = [cliLocation(), '--file', currentFile, '--key', apiKey, '--plugin', 'atom-wakatime/' + packageVersion]
+      return unless python?
+      args = [cliLocation(), '--file', currentFile, '--plugin', 'atom-wakatime/' + packageVersion]
       if isWrite
         args.push('--write')
       if lineno?


look in all default install locations for python exe

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -65,30 +65,50 @@
     else
         if not locations?
             locations = [
-                'pythonw',
-                'python',
-                '/usr/local/bin/python',
+                "pythonw",
+                "python",
+                "/usr/local/bin/python",
                 "/usr/bin/python",
-                '\\python37\\pythonw',
-                '\\python36\\pythonw',
-                '\\python35\\pythonw',
-                '\\python34\\pythonw',
-                '\\python33\\pythonw',
-                '\\python32\\pythonw',
-                '\\python31\\pythonw',
-                '\\python30\\pythonw',
-                '\\python27\\pythonw',
-                '\\python26\\pythonw',
-                '\\python37\\python',
-                '\\python36\\python',
-                '\\python35\\python',
-                '\\python34\\python',
-                '\\python33\\python',
-                '\\python32\\python',
-                '\\python31\\python',
-                '\\python30\\python',
-                '\\python27\\python',
-                '\\python26\\python',
+                "\\python37\\pythonw",
+                "\\Python37\\pythonw",
+                "\\python36\\pythonw",
+                "\\Python36\\pythonw",
+                "\\python35\\pythonw",
+                "\\Python35\\pythonw",
+                "\\python34\\pythonw",
+                "\\Python34\\pythonw",
+                "\\python33\\pythonw",
+                "\\Python33\\pythonw",
+                "\\python32\\pythonw",
+                "\\Python32\\pythonw",
+                "\\python31\\pythonw",
+                "\\Python31\\pythonw",
+                "\\python30\\pythonw",
+                "\\Python30\\pythonw",
+                "\\python27\\pythonw",
+                "\\Python27\\pythonw",
+                "\\python26\\pythonw",
+                "\\Python26\\pythonw",
+                "\\python37\\python",
+                "\\Python37\\python",
+                "\\python36\\python",
+                "\\Python36\\python",
+                "\\python35\\python",
+                "\\Python35\\python",
+                "\\python34\\python",
+                "\\Python34\\python",
+                "\\python33\\python",
+                "\\Python33\\python",
+                "\\python32\\python",
+                "\\Python32\\python",
+                "\\python31\\python",
+                "\\Python31\\python",
+                "\\python30\\python",
+                "\\Python30\\python",
+                "\\python27\\python",
+                "\\Python27\\python",
+                "\\python26\\python",
+                "\\Python26\\python",
             ]
         args = ['--version']
         if locations.length is 0


logErrorsToLogger: validate logger on method call (refs #43)

--- src/Pump.coffee
+++ src/Pump.coffee
@@ -218,16 +218,15 @@
     @

   logErrorsToLogger: (logger) ->
+    if not (logger?.error instanceof Function)
+      throw new Error 'logger must have an .error() method'
     @errorBuffer().on 'write', (errorRecord) =>
       name = errorRecord.pump ? '(root)'
-      if logger.hasOwnProperty 'error'
-          if @_debug
-              logger.error "Error in pump #{name}:"
-              logger.error errorRecord.error.stack ? errorRecord.error
-          else
-              logger.error "Error in pump #{name}: #{errorRecord.error}"
+      if @_debug
+        logger.error "Error in pump #{name}:"
+        logger.error errorRecord.error.stack ? errorRecord.error
       else
-          @logErrorsToConsole()
+        logger.error "Error in pump #{name}: #{errorRecord.error}"
     @

   debug: ->


localise beatmap search breadcrumbs

localise beatmap search breadcrumbs

--- resources/assets/coffee/react/beatmaps/search-panel.coffee
+++ resources/assets/coffee/react/beatmaps/search-panel.coffee
@@ -55,16 +55,17 @@
   renderBreadcrumbs: =>
     return null unless currentUser.id?

+    # TODO: replace with component that takes an array of {name, link}.
     ol className: 'sticky-header-breadcrumbs',
       li className: 'sticky-header-breadcrumbs__item',
         span
           className: 'sticky-header-breadcrumbs__link'
-          'Beatmapsets'
+          osu.trans 'beatmapsets.index.guest_title'

       li className: 'sticky-header-breadcrumbs__item',
         span
           className: 'sticky-header-breadcrumbs__link'
-          'Search'
+          osu.trans 'home.search.title'

   renderStickyContent: =>


Fix Atom bar

Fix Atom bar

--- atom/toolbar.cson
+++ atom/toolbar.cson
@@ -3,15 +3,15 @@
     type: "button"
     callback: "tree-view:add-file"
     tooltip: "New file"
-    iconset: "ion"
-    icon: "document"
+    iconset: "fi"
+    icon: "page-add"
   }
   {
     type: "button"
     callback: "tree-view:add-folder"
     tooltip: "New folder"
-    iconset: "ion"
-    icon: "folder"
+    iconset: "fi"
+    icon: "folder-add"
   }
   {
     type: "button"
@@ -31,8 +31,8 @@
     type: "button"
     callback: "pane:close"
     tooltip: "Close pane"
-    iconset: "fa"
-    icon: "times-circle-o"
+    iconset: "fi"
+    icon: "trash"
   }
   {
     type: "spacer"
@@ -96,14 +96,14 @@
     type: "button"
     callback: "command-palette:toggle"
     tooltip: "Command Palette..."
-    iconset: "ion"
-    icon: "navicon-round"
+    iconset: "fi"
+    icon: "skull"
   }
   {
     type: "button"
     callback: "settings-view:open"
     tooltip: "Settings"
-    iconset: "ion"
-    icon: "gear-a"
+    iconset: "fi"
+    icon: "widget"
   }
 ]


Hide transpose button.

Hide transpose button.

--- openchordcharts/static/coffee/chart.coffee
+++ openchordcharts/static/coffee/chart.coffee
@@ -119,19 +119,25 @@
   elements:
     ".chords": "chordsDiv"
     ".properties .key select": "keySelect"
+    ".properties .key input[type='submit']": "submitButton"
     ".properties .key form": "transposeForm"

   events:
+    "change .properties .key select": "onKeySelectChange"
     "submit .properties .key form": "onTransposeFormSubmit"

   constructor: (options) ->
     super
+    @submitButton.detach()
     Chart.bind "change", @onChartChange
     Chart.create options.chart

   onChartChange: (chart, sourceEvent, options) =>
     @render chart
     window.document.title = "#{chart.title} (#{chart.key})"
+
+  onKeySelectChange: (event) =>
+    @transposeForm.trigger "submit"

   onTransposeFormSubmit: (event) =>
     event.preventDefault()


Rm unsupported ternary, use window from props

This way tests can substitute out a spy for it

--- src/user/login-gateway.cjsx
+++ src/user/login-gateway.cjsx
@@ -9,9 +9,13 @@

   propTypes:
     title: React.PropTypes.string
+    window: React.PropTypes.shape(
+      open: React.PropTypes.func
+    )

   getDefaultProps: ->
     title: 'You need to login or signup in order to use ConceptCoach™'
+    window: window

   getInitialState: ->
     loginWindow: false
@@ -21,13 +25,12 @@

     width  = Math.min(1000, window.screen.width - 20)
     height = Math.min(800, window.screen.height - 30)
-    options = ["toolbar=no", "location=" + (window.opera ? "no" : "yes"),
+    options = ["toolbar=no", "location=" + (if @props.window.opera then "no" else "yes"),
       "directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no",
       "width=" + width, "height=" + height,
       "top="   + (window.screen.height - height) / 2,
       "left="  + (window.screen.width - width)   / 2].join()
-
-    loginWindow = window.open(@urlForLogin(), 'oxlogin', options)
+    loginWindow = @props.window.open(@urlForLogin(), 'oxlogin', options)
     @setState({loginWindow})
     _.delay(@windowClosedCheck, SECOND)

@@ -61,7 +64,7 @@
     User.endpoints.login + '?parent=' + encodeURIComponent(window.location.href)

   loginLink: (msg) ->
-    <a data-bypass onClick={@openLogin} href={@urlForLogin()}>
+    <a data-bypass className='login' onClick={@openLogin} href={@urlForLogin()}>
       {msg}
     </a>


Allow changing moderation policy from the moderation panel

--- @client/dashboard/moderation.coffee
+++ @client/dashboard/moderation.coffee
@@ -1,23 +1,23 @@
 window.moderation_options = [
     {
-      label: "No content moderation."
+      label: "No content moderation"
       value: 0
     }

     {
-      label: "Do not publicly post content until after approval."
+      label: "Do not publicly post content until after approval"
       value: 1
       explanation: "Suggested only for extremely adversarial or sensitive forums. Hosts will receive email notifications when there is content to review. To keep the conversation flowing, hosts should be extremely responsive to reviewing new posts."
     }

     {
-      label: "Post content immediately, but withhold email notifications until after approval."
+      label: "Post content immediately, but withhold email notifications until after approval"
       value: 2
       explanation: "Suggested for most public engagement. All content is posted immediately, but won’t trigger email notifications, nor will the new posts be present in any email summary of recent activity. After review, they will be part of recent activity summaries. This policy will avoid the worst consequence of detrimental content: an enthusiastic participant getting an email linking them to a nasty attack on them, while at the same time lessening the pressure on moderators to review content promptly."
     }

     {
-      label: "Post content immediately, review later.",
+      label: "Post content immediately, review later",
       value: 3
       explanation: "Use this if you’re not very concerned about detrimental posts and/or you don’t have capacity to review new content in a timely fashion. Hosts will receive email notifications when there is content to review. Suggested for most engagement amongst people who have formal or communal ties."
     }
@@ -108,26 +108,36 @@
     DIV null,
       DIV
         style:
-          marginBottom: 24
+          marginBottom: 12
+          fontSize: 20
         "Your moderation policy: "

-        SPAN
-          style:
-            fontStyle: 'italic'
-            padding: 2
-          if moderation_policy
-            moderation_policy.label
-          else
-            "no moderation"
-
-        ". Visit the "
+
+        SELECT
+          style:
+            fontSize: 20
+          onChange: (ev) ->
+            subdomain.moderation_policy = ev.target.value
+            save subdomain, ->
+              #saving the subdomain shouldn't always dirty moderations
+              #(which is expensive), so just doing it manually here
+              arest.serverFetch('/page/dashboard/moderate')
+
+          for option in moderation_options
+            OPTION
+              value: option.value
+              option.label
+      DIV
+        style:
+          marginBottom: 24
+        "Visit the "
         A
           href: '/dashboard/application'
           style:
             textDecoration: 'underline'
             fontWeight: 700
           "forum settings"
-        " to change the policy."
+        " for more explanation about moderation policy options."

       UL


add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

Revert Merge branch 'fix_ajax_navigation_menu'

prevent url load on collapse sign

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

Revert Merge branch 'fix_ajax_navigation_menu'

prevent url load on collapse sign

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

Revert Merge branch 'fix_ajax_navigation_menu'

prevent url load on collapse sign

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

Revert Merge branch 'fix_ajax_navigation_menu'

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

Revert Merge branch 'fix_ajax_navigation_menu'

prevent url load on collapse sign

add a prevent calls form page if click on +/- symbol navigation

relocate preventDefault

Revert "relocate preventDefault"

This reverts commit 46898366a5b7d11c7aa9cefd8f5618805d583a83.

Revert Merge branch 'fix_ajax_navigation_menu'

prevent url load on collapse sign

relocate code and add restrict

--- BackofficeBundle/Resources/public/coffee/orchestraLib.coffee
+++ BackofficeBundle/Resources/public/coffee/orchestraLib.coffee
@@ -196,3 +196,8 @@
       location.reload()
     return
   smartConfirm('fa-refresh', $(e.currentTarget).data('message-title'), $(e.currentTarget).data('message-text'), callbacks)
+
+#SMARTADMIN:JARVISMENU PREVENT LOAD PAGE ON EXTENDS ACCORDION MENU CALLS
+$("#left-panel nav").on("click",".collapse-sign em[class*='fa-'][class*='-square-o']", (event) ->
+   event.preventDefault()
+)


If skip count is less then limit means we reached the limit This change also resolves our forever loading bug.

--- client/landing/app/Applications/Feeder.kdapplication/FeedController.coffee
+++ client/landing/app/Applications/Feeder.kdapplication/FeedController.coffee
@@ -110,17 +110,24 @@
     options.skip  = @resultsController.listControllers[filter.name].itemsOrdered.length
     options

+  emitLoadCompleted:(filter)=>
+    listController = @resultsController.listControllers[filter.name]
+    listController.propagateEvent KDEventType : 'LazyLoadComplete'
+    return listController
+
   loadFeed:(filter = @selection)->

     options  = @getFeedOptions()
     selector = @getFeedSelector()
-    # log selector,options
-    filter.dataSource selector, options, (err, items)=>
-      listController = @resultsController.listControllers[filter.name]
-      listController.propagateEvent KDEventType : 'LazyLoadComplete'
-      unless err
-        listController.instantiateListItems items
-        if items.length is options.limit and listController.scrollView.getScrollHeight() <= listController.scrollView.getHeight()
-          @loadFeed filter
-      else
-        warn err
+
+    if options.skip isnt 0 and options.skip < options.limit # Dont load forever
+      @emitLoadCompleted filter
+    else
+      filter.dataSource selector, options, (err, items)=>
+        listController = @emitLoadCompleted filter
+        unless err
+          listController.instantiateListItems items
+          if items.length is options.limit and listController.scrollView.getScrollHeight() <= listController.scrollView.getHeight()
+            @loadFeed filter
+        else
+          warn err


Updated atom config

Updated atom config

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -1,7 +1,7 @@
 'exception-reporting':
   'userId': '5392f155-a911-a1c0-cdd2-604cd7bf4ef0'
 'release-notes':
-  'viewedVersion': '0.84.0'
+  'viewedVersion': '0.90.0'
 'welcome':
   'showOnStartup': false
 'metrics':


Fix another spinner alignment

Fix another spinner alignment

--- resources/assets/coffee/react/_components/comment-editor.coffee
+++ resources/assets/coffee/react/_components/comment-editor.coffee
@@ -82,7 +82,7 @@
               text:
                 top:
                   if @state.posting
-                    el Spinner
+                    el Spinner, modifiers: 'center-inline'
                   else
                     @buttonText()
         else


translation corrected

translation corrected

--- app/assets/javascripts/selector.js.coffee
+++ app/assets/javascripts/selector.js.coffee
@@ -421,7 +421,7 @@
           @._handleClear($filteredUnroll, data.clear)
           @._handleDisable($filteredUnroll, $filteringUnroll, data.disable) if data.disable
         .fail (e) =>
-          @._handleDisable($filteredUnroll, $filteringUnroll, I18n.translate('actions.front-end.unroll.server_error'))
+          @._handleDisable($filteredUnroll, $filteringUnroll, I18n.translate('front-end.unroll.server_error'))
           console.error('Error while trying to filter an unroll', e)

     _handleScope: ($filteredUnroll, scope_url) ->


Deleted versions.js.coffee

Deleted versions.js.coffee

--- app/assets/javascripts/versions.js.coffee
+++ app/assets/javascripts/versions.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


Prevent default action when closing zen mode

--- app/assets/javascripts/zen_mode.js.coffee
+++ app/assets/javascripts/zen_mode.js.coffee
@@ -16,6 +16,7 @@
     $(document).on 'keydown', (e) =>
       if e.keyCode is $.ui.keyCode.ESCAPE
         @exitZenMode()
+        e.preventDefault()

     $(window).on 'hashchange', @updateZenModeFromLocationHash


Remove some magic numbers in DemandVisualizer

--- app/coffeescripts/DemandVisualizer.coffee
+++ app/coffeescripts/DemandVisualizer.coffee
@@ -32,9 +32,13 @@
     @$el.html @vizWindow(elemId: @link.id, content: windowContent)

   renderLegend: (graphEl) ->
+    textY = 30
+    legendTitleWidth = 100
+    legendLabelWidth = 75
+
     graphEl.append('svg:text').
       attr('x', 10).
-      attr('y', 30).
+      attr('y', textY).
       style('font-size', '20px').
       text('Legend')

@@ -42,17 +46,17 @@
       name = vtype.get('name')

       graphEl.append('svg:text').
-        attr('x', 100 + 50*idx).
-        attr('y', 30).
+        attr('x', legendTitleWidth + legendLabelWidth*idx).
+        attr('y', textY).
         style('font-size', '14px').
         style('background-color', vehicleTypeColors[idx]).
         text(name)

       graphEl.append('svg:rect').
         attr('id', "legend-#{idx}").
-        attr('x', 100 + 50*idx).
+        attr('x', 100 + legendLabelWidth*idx).
         attr('y', 10).
-        attr('width', 50).
+        attr('width', legendLabelWidth).
         attr('height', 30).
         attr('opacity', '0.4').
         attr('enabled', true).


Remove trailing slashes and HTML file extension from prettyfied URLs

--- app/scripts/lib/handlebars-helpers.coffee
+++ app/scripts/lib/handlebars-helpers.coffee
@@ -11,6 +11,8 @@
       new Handlebars.SafeString result

     prettyUrl: (url) ->
-      url.replace /^((http|https):\/\/)?(www.)?/, ""
+      url.replace(/^((http|https):\/\/)?(www.)?/, "")
+         .replace(/\/$/, "")
+         .replace(/\.html$/, "")

   Handlebars.registerHelper(name, helper) for name, helper of helpers


safer version of finding team by code

safer version of finding team by code

--- controllers/teams.coffee
+++ controllers/teams.coffee
@@ -8,9 +8,9 @@

 # middleware
 loadTeam = (req, res, next) ->
-  if id = req.param('id')
+  if id = req.params.id
     try
-      Team.findOne $or: [ {_id: id}, {code: id} ], (err, team) ->
+      Team.findById id, (err, team) ->
         return next err if err
         return next 404 unless team
         req.team = team
@@ -18,6 +18,12 @@
     catch error
       throw error unless error.message == 'Id cannot be longer than 12 bytes'
       return next 404
+  else if code = req.params.code
+    Team.findOne code: code, (err, team) ->
+      return next err if err
+      return next 404 unless team
+      req.team = team
+      next()
   else
     next()


Converter bugfix

Converter bugfix

--- converter/convert.coffee
+++ converter/convert.coffee
@@ -77,7 +77,10 @@
 		material.textures.push "textures/#{match[0]}.tex"
 		infile = path.resolve(inputDir, texture.TextureSource)
 		outfile = path.resolve(basePath, newtex.url)
-		copyFile infile, outfile
+		try
+			copyFile infile, outfile
+		catch e
+			console.log e

 convertSkeletons = (skeletons) ->
 	for skeleton in skeletons


slightly thicker connector lines

slightly thicker connector lines

--- dramagrama.coffee
+++ dramagrama.coffee
@@ -105,7 +105,7 @@
 			parsedBit.arrow = ""
 			names = @extractNamesFromSolidLine line
 		else if @hasDashedLine line
-			parsedBit.arrow = "--"
+			parsedBit.arrow = "-"
 			names = @extractNamesFromDashedLine line
 		else
 			names = {first:line, second:""}
@@ -235,7 +235,7 @@

 	drawLine: (point1, point2, arrowMessage, arrowStyle) ->
 		@paper.path("M{0},{1}L{2},{3}", point1.x, point1.y, point2.x, point2.y)
-		.attr({"stroke-dasharray": arrowStyle})
+		.attr({"stroke-dasharray": arrowStyle, "stroke-width": 2})

 		return unless arrowMessage
 		midpoint = point1.y + (point2.y - point1.y)/2


fix tool rsync test

fix tool rsync test

--- test/unit/model/serv_tool.coffee
+++ test/unit/model/serv_tool.coffee
@@ -67,9 +67,9 @@
         @exec.firstCall.calledWithMatch(/^git clone/).should.be.true

       it 'should rsync the tool to all box servers', ->
-        @exec.calledWithMatch(/^rsync .* \/opt\/tools\/test .*premium/).should.be.true
-        @exec.calledWithMatch(/^rsync .* \/opt\/tools\/test .*free-ec2/).should.be.true
-        @exec.calledWithMatch(/^rsync .* \/opt\/tools\/test .*ds-ec2/).should.be.true
+        @exec.calledWithMatch(/^run-this-one rsync .* \/opt\/tools\/ .*premium/).should.be.true
+        @exec.calledWithMatch(/^run-this-one rsync .* \/opt\/tools\/ .*free-ec2/).should.be.true
+        @exec.calledWithMatch(/^run-this-one rsync .* \/opt\/tools\/ .*ds-ec2/).should.be.true

       before (done) ->
         @spy = sinon.spy JSON, 'parse'
@@ -111,9 +111,9 @@
         @tool.gitCloneOrPull dir: "test/tmp/repos", done

       it 'should rsync the tool to all box servers', ->
-        @exec.calledWithMatch(/^rsync .* \/opt\/tools\/test .*premium/).should.be.true
-        @exec.calledWithMatch(/^rsync .* \/opt\/tools\/test .*free-ec2/).should.be.true
-        @exec.calledWithMatch(/^rsync .* \/opt\/tools\/test .*ds-ec2/).should.be.true
+        @exec.calledWithMatch(/^run-this-one rsync .* \/opt\/tools\/ .*premium/).should.be.true
+        @exec.calledWithMatch(/^run-this-one rsync .* \/opt\/tools\/ .*free-ec2/).should.be.true
+        @exec.calledWithMatch(/^run-this-one rsync .* \/opt\/tools\/ .*ds-ec2/).should.be.true

       before (done) ->
         @fsRead = sinon.stub fs, 'readFile', (path_, cb) ->


losing selection no longer should update toolbar

--- test/unit/modules/toolbar.coffee
+++ test/unit/modules/toolbar.coffee
@@ -49,8 +49,6 @@
     it('button', ->
       @quill.setSelection(1, 1)
       expect(Quill.DOM.hasClass(@button, 'sc-active')).toBe(true)
-      @quill.setSelection(null)
-      expect(Quill.DOM.hasClass(@button, 'sc-active')).toBe(false)
     )

     it('dropdown', ->


Return when calling callback

Return when calling callback

--- services/track-changes/app/coffee/ConversionManager.coffee
+++ services/track-changes/app/coffee/ConversionManager.coffee
@@ -61,7 +61,7 @@

 				if !lastCompressedUpdate?
 					# No saved versions, no raw updates, nothing to do
-					callback()
+					return callback()

 				compressedUpdates = [lastCompressedUpdate]
 				for rawUpdate in rawUpdates


ADDED noty notice after sending a message

--- app/assets/javascripts/angular/controllers/SearchCtrl.coffee
+++ app/assets/javascripts/angular/controllers/SearchCtrl.coffee
@@ -40,14 +40,18 @@

   $scope.submitMessage = (user, messageText) ->
     if _.indexOf($scope.usersCommunicatedWith, user.id) == -1 and user.id != $scope.currentUser.id
-      User.createConversationWithMessage($scope.currentUser, user, messageText).then (response) ->
-        $scope.messageText = ''
+      User.createConversationWithMessage($scope.currentUser, user, messageText).success (response) ->
+        noty { text: 'Successfully sent a message!', type: 'success'}
         $scope.usersCommunicatedWith.push user.id
         reloadUser()
+      .error (response) ->
+        noty { text: 'Message cannot be sent! Please try again', type: 'error'}
     else
       conversation = _.find($scope.conversations, (conversation) -> conversation.created_by == user.id || conversation.created_for == user.id)
       User.createMessageTo($scope.currentUser, user, messageText, conversation.id).success (response) ->
-        $scope.messageText = ''
+        noty { text: 'Successfully sent a message!', type: 'success'}
+      .error (response) ->
+        noty { text: 'Message cannot be sent! Please try again', type: 'error'}
     $(".modal").modal('hide')
     return


Play online sound only once at time.

Play online sound only once at time.

--- app/assets/javascripts/app/models/online_notification.coffee
+++ app/assets/javascripts/app/models/online_notification.coffee
@@ -19,8 +19,11 @@
     preferences = App.Session.get('preferences')
     return if !preferences
     return if !App.OnlineNotification.soundEnabled()
+    sound = App.Config.get('latest_online_notification_sond')
+    return if sound && !sound.ended
     file = App.OnlineNotification.soundFile()
     sound = new Audio("assets/sounds/#{file}")
+    App.Config.set('latest_online_notification_sond', sound)
     sound.play()

   ###


readdded highlight of active line but only when focused

--- app/assets/javascripts/rails_embed_editor/application.coffee
+++ app/assets/javascripts/rails_embed_editor/application.coffee
@@ -43,7 +43,13 @@
   editor.setAutoScrollEditorIntoView();
   editor.setOption("maxLines", 40);
   editor.setOption("minLines", 5);
+
+  #highlight the current line only if its focus
   editor.setHighlightActiveLine(false)
+  editor.on "focus", () ->
+    editor.setHighlightActiveLine(true)
+  editor.on "blur", () ->
+    editor.setHighlightActiveLine(false)

   editor.getSession().addMarker(options['highlight'], "editor_highlight", "text") unless options['highlight'] == null


Reduce size of text in confirmation dialog

--- app/assets/javascripts/transam_core/confirm_dialog.js.coffee
+++ app/assets/javascripts/transam_core/confirm_dialog.js.coffee
@@ -3,7 +3,7 @@
 $.rails.allowAction = (element) ->
   # The message is something like "Are you sure?"
   message = element.data('confirm')
-  # If there's no message, there's no data-confirm attribute,
+  # If there's no message, there's no data-confirm attribute,
   # which means there's nothing to confirm
   return true unless message
   # Clone the clicked element (probably a delete link) so we can use it in the dialog box.
@@ -24,7 +24,7 @@

-
#{message}

+
#{message}

                       Cancel
@@ -44,4 +44,3 @@
   $modal_html.modal()
   # Prevent the original link from working
   return false
-


fix unit tests

fix unit tests

--- services/real-time/test/unit/coffee/EventLoggerTests.coffee
+++ services/real-time/test/unit/coffee/EventLoggerTests.coffee
@@ -10,7 +10,7 @@
 		@start = Date.now()
 		tk.freeze(new Date(@start))
 		@EventLogger = SandboxedModule.require modulePath, requires:
-			"logger-sharelatex": @logger = {error: sinon.stub()}
+			"logger-sharelatex": @logger = {error: sinon.stub(), warn: sinon.stub()}
 			"metrics-sharelatex": @metrics = {inc: sinon.stub()}
 		@channel = "applied-ops"
 		@id_1 = "random-hostname:abc-1"