Use tab invisible that is available in Inconsolata

Previously a non-Inconsolata character
was being used which caused alignment issues
with selections and the cursor

Also switch values in editor.coffee to use codes
so they can be viewed in any font.

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -287,9 +287,9 @@

   setInvisibles: (@invisibles={}) ->
     _.defaults @invisibles,
-      eol: '¬',
-      space: '•',
-      tab: '▸'
+      eol: '\u00ac',
+      space: '\u2022',
+      tab: '\u00bb'
     @resetDisplay()

   checkoutHead: -> @getBuffer().checkoutHead()


fix https://github.com/sagemathinc/smc/issues/1952

--- src/smc-webapp/jupyter/codemirror-editor.cjsx
+++ src/smc-webapp/jupyter/codemirror-editor.cjsx
@@ -150,6 +150,9 @@
             return
         @props.actions.redo()

+    shift_tab_key: ->
+        @cm?.unindent_selection()
+
     tab_key: ->
         if not @cm?
             return
@@ -234,14 +237,15 @@
             return
         options0 = options.toJS()
         if @props.actions?
-            options0.extraKeys            ?= {}
-            options0.extraKeys["Tab"]      = @tab_key
-            options0.extraKeys["Up"]       = @up_key
-            options0.extraKeys["Down"]     = @down_key
-            options0.extraKeys["PageUp"]   = @page_up_key
-            options0.extraKeys["PageDown"] = @page_down_key
-            options0.extraKeys["Cmd-/"]    = "toggleComment"
-            options0.extraKeys["Ctrl-/"]   = "toggleComment"
+            options0.extraKeys             ?= {}
+            options0.extraKeys["Shift-Tab"] = @shift_tab_key
+            options0.extraKeys["Tab"]       = @tab_key
+            options0.extraKeys["Up"]        = @up_key
+            options0.extraKeys["Down"]      = @down_key
+            options0.extraKeys["PageUp"]    = @page_up_key
+            options0.extraKeys["PageDown"]  = @page_down_key
+            options0.extraKeys["Cmd-/"]     = "toggleComment"
+            options0.extraKeys["Ctrl-/"]    = "toggleComment"
         else
             options0.readOnly = true


Removed redundant code

Removed redundant code

--- src/views/navigation.coffee
+++ src/views/navigation.coffee
@@ -197,7 +197,7 @@
                         collection: new models.UnitList()

                 when 'search'
-                    if opts?.reason != 'userClickedSearchInputElement' or @searchResults.length > 0
+                    if @searchResults.length > 0
                         # We want to prevent any open UI element from
                         # closing just because the user clicked on the
                         # search bar.
@@ -305,16 +305,13 @@
             @selectedUnits = options.selectedUnits

         onShow: ->
-            searchInputView = new SearchInputView({@searchState, @searchResults, expandCallback: _.bind(@_expandSearch, @)})
+            searchInputView = new SearchInputView({@searchState, @searchResults})
             @search.show searchInputView
             @listenTo searchInputView, 'open', =>
                 @updateClasses 'search'
                 @navigationLayout.updatePersonalisationButtonClass 'search'
             @browse.show new BrowseButtonView()

-        _expandSearch: ->
-            @_open 'search', disableAutoFocus: true, reason: 'userClickedSearchInputElement'
-
         _open: (actionType, opts) ->
             @updateClasses actionType
             @navigationLayout.change actionType, opts
@@ -329,7 +326,7 @@
         toggleOpen: (event) ->
             target = $(event.currentTarget).data('type')
             isNavigationVisible = !!$('#navigation-contents').children().length
-
+
             # An early return if the element is search input
             return if target == 'search'


Edited style is set properly

Edited style is set properly

--- client/components/userSettings/userSettings.coffee
+++ client/components/userSettings/userSettings.coffee
@@ -1,3 +1,7 @@
+Template.userSettings.events
+    "keyup #email": (e) ->
+        Session.set("currentEmailEdited", event.target.value)
+
 UserEmailEdited =
     isEdited: ->
         true
@@ -41,7 +45,21 @@
     needVerificationLink: ->
         false

-UserEmail = UserEmailNonVerified
+UserEmail = ->
+    if (Users.currentUser()?.emails?.length?) and (Users.currentUser().emails.length > 0)
+        email = Users.currentUser().emails[0]
+    else
+        email = {address: "", verified: undefined}
+    currentEmailEdited = Session.get("currentEmailEdited")
+    if currentEmailEdited and (currentEmailEdited != email.address)
+        UserEmailEdited
+    else
+        if email.address == ""
+            UserEmailAbsent
+        else
+            if email.verified
+                UserEmailVerified
+            else UserEmailNonVerified

 Template.userSettings.helpers
     reviewCount: ->
@@ -57,19 +75,19 @@
         Notifications.findByUser(Users.currentUser())

     inputGroupIfEdited: ->
-        if UserEmail.isEdited()
+        if UserEmail().isEdited()
             "input-group"
         else
             ""

     isEdited: ->
-        UserEmail.isEdited()
+        UserEmail().isEdited()

     glyphClass: ->
-        UserEmail.glyphClass()
+        UserEmail().glyphClass()

     comment: ->
-        UserEmail.comment()
+        UserEmail().comment()

     needVerificationLink: ->
-        UserEmail.needVerificationLink()+        UserEmail().needVerificationLink()


buttons

buttons

--- Framework/Views/Controls/Button_Framework.coffee
+++ Framework/Views/Controls/Button_Framework.coffee
@@ -26,8 +26,8 @@
     }, options.onClickStyle)

     @view = Ti.UI.createView({
-      height: Ti.UI.SIZE
-      width: Ti.UI.SIZE
+      height: @startStyle("height")
+      width: @startStyle("width")
       bottom: @startStyle("bottom")
       top: @startStyle("top")
       left: @startStyle("left")


video: controller improve

video: controller improve

--- client/ide/lib/videocollaborationcontroller.coffee
+++ client/ide/lib/videocollaborationcontroller.coffee
@@ -23,42 +23,30 @@
     @videoModel.join()

+  startVideoCollaboration: ->
+
+    @videoModel.start()
+
+
+  endVideoCollaboration: ->
+
+    @videoModel.end()
+
+
+  toggleVideoControl: (type, activeState) ->
+
+    switch type
+      when 'audio' then @videoModel.setAudioState activeState
+      when 'video' then @videoModel.setVideoState activeState
+      when 'end'   then @endVideoCollaboration()
+
+
   leaveVideoCollaboration: ->

   switchToUserVideo: (nickname) ->

     @videoModel.changeActiveParticipant nickname
-
-
-  muteParticipant: (participant) ->
-
-    # @videoModel.muteParticipant participant
-
-
-  unmuteParticipant: (participant) ->
-
-    # @videoModel.unmuteParticipant participant
-
-
-  turnOffParticipantCamera: (participant) ->
-
-    # @videoModel.disableParticipantCamera participant
-
-
-  turnOnParticipantCamera: (participant) ->
-
-    # @videoModel.enableParticipantCamera participant
-
-
-  turnOffCamera: ->
-
-    # @videoModel.disableParticipantCamera nick()
-
-
-  turnOnCamera: ->
-
-    # @videoModel.enableParticipantCamera nick()

   handleVideoAccessQuestionAsked: ->
@@ -68,40 +56,35 @@

   handleVideoEnded: ->
+    @emitToViews 'VideoCollaborationEnded'

   handleVideoActive: (publisher) ->
-
-    @chat.emit 'VideoCollaborationActive', publisher
+    @emitToViews 'VideoCollaborationActive'

   handleVideoParticipantJoined: (participant) ->
-
-    @statusBar.emit 'VideoParticipantDidJoin', participant
-    @chat.emit 'VideoParticipantDidJoin', participant
+    @emitToViews 'VideoParticipantDidJoin', participant

   handleVideoParticipantLeft: (participant) ->
-
-    @statusBar.emit 'VideoParticipantDidLeave', participant
-    @chat.emit 'VideoParticipantDidLeave', participant
+    @emitToViews 'VideoParticipantDidLeave', participant

   handleVideoActiveParticipantChanged: (participant) ->
-
-    @statusBar.emit 'VideoActiveParticipantDidChange', participant
-    @chat.emit 'VideoActiveParticipantDidChange', participant
+    @emitToViews 'VideoActiveParticipantDidChange', participant

   handleVideoParticipantAudioStateChanged: (participant, state) ->
-
-    @statusBar.emit 'VideoParticipantAudioStateDidChange', participant, state
-    @chat.emit 'VideoParticipantAudioStateDidChange', participant, state
+    @emitToViews 'VideoParticipantAudioStateDidChange', participant

   handleVideoParticipantCameraStateChanged: (participant, state) ->
-
-    @statusBar.emit 'VideoParticipantCameraStateDidChange', participant, state
-    @chat.emit 'VideoParticipantCameraStateDidChange', participant, state
+    @emitToViews 'VideoParticipantCameraStateDidChange', participant

+  emitToViews: (args...) ->
+    @statusBar.emit args...
+    @chat.emit args...
+
+


Change the variable name

Change the variable name

--- client/ide/lib/videocollaborationcontroller.coffee
+++ client/ide/lib/videocollaborationcontroller.coffee
@@ -185,9 +185,9 @@

       return showError err if err?

-      pLength = @participants.asArray().length
+      participantsLen = @participants.asArray().length

-      if plan.planTitle is 'free' and (pLength > 2 or (pLength is 2 and isVideoActive))
+      if plan.planTitle is 'free' and (participantsLen > 2 or (participantsLen is 2 and isVideoActive))
         return new LimitedVideoCollaborationFree

       callback()


removed fetchGroupsWithPendingInvitations method from jaccount

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -113,10 +113,6 @@
         updateFlags:
           (signature [String], Function)
         fetchGroups: [
-          (signature Function)
-          (signature Object, Function)
-        ]
-        fetchGroupsWithPendingInvitations: [
           (signature Function)
           (signature Object, Function)
         ]
@@ -977,10 +973,6 @@
       JGroup.some _id:$in:(rel.sourceId for rel in rels), options, callback

-  fetchGroupsWithPendingInvitations:(options, callback)->
-    @fetchGroupsWithPending '', 'sent', options, callback
-
-
   cancelRequest: secure (client, slug, callback)->
     options = targetOptions: selector: status: 'pending'
     JGroup = require './group'


embed from youtu.be fixed

embed from youtu.be fixed

--- packages/rocketchat-oembed/server/providers.coffee
+++ packages/rocketchat-oembed/server/providers.coffee
@@ -58,6 +58,7 @@
 			data.urlObj.pathname = consumerUrl.pathname
 			data.urlObj.query = consumerUrl.query
 			delete data.urlObj.search
+			delete data.urlObj.host

 	return data
 , RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before'


add unit tests

add unit tests

--- services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee
+++ services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee
@@ -206,7 +206,7 @@
 			@hash = crypto.createHash('sha1').update(JSON.stringify(@lines)).digest('hex')
 			@ranges = { comments: "mock", entries: "mock" }

-			@rclient.exec = sinon.stub().callsArg(0)
+			@rclient.exec = sinon.stub().callsArg(0, null, [@hash])

 		describe "with a consistent version", ->
 			beforeEach ->
@@ -255,6 +255,10 @@

 			it "should call the callback", ->
 				@callback.called.should.equal true
+
+			it 'should not log any errors', ->
+				@logger.error.calledWith()
+					.should.equal false

 		describe "with an inconsistent version", ->
 			beforeEach ->
@@ -298,6 +302,20 @@
 				@rclient.del
 					.calledWith("Ranges:#{@doc_id}")
 					.should.equal true
+
+		describe "with a corrupted write", ->
+			beforeEach ->
+				@badHash = "INVALID-HASH-VALUE"
+				@rclient.exec = sinon.stub().callsArgWith(0, null, [@badHash])
+				@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
+				@RedisManager.updateDocument @doc_id, @lines, @version, @ops, @ranges, @callback
+
+			it 'should log a hash error', ->
+				@logger.error.calledWith()
+					.should.equal true
+
+			it "should call the callback", ->
+				@callback.called.should.equal true

 	describe "putDocInMemory", ->
 		beforeEach ->
@@ -305,10 +323,10 @@
 			@rclient.sadd = sinon.stub().yields()
 			@rclient.del = sinon.stub()
 			@rclient.eval = sinon.stub()
-			@rclient.exec.yields()
 			@lines = ["one", "two", "three"]
 			@version = 42
 			@hash = crypto.createHash('sha1').update(JSON.stringify(@lines)).digest('hex')
+			@rclient.exec = sinon.stub().callsArgWith(0, null, [@hash])
 			@ranges = { comments: "mock", entries: "mock" }

 		describe "with non-empty ranges", ->
@@ -344,6 +362,10 @@
 				@rclient.sadd
 					.calledWith("DocsIn:#{@project_id}", @doc_id)
 					.should.equal true
+
+			it 'should not log any errors', ->
+				@logger.error.calledWith()
+					.should.equal false

 		describe "with empty ranges", ->
 			beforeEach (done) ->
@@ -358,6 +380,15 @@
 				@rclient.set
 					.calledWith("Ranges:#{@doc_id}", JSON.stringify(@ranges))
 					.should.equal false
+
+		describe "with a corrupted write", ->
+			beforeEach (done) ->
+				@rclient.exec = sinon.stub().callsArgWith(0, null, ["INVALID-HASH-VALUE"])
+				@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, done
+
+			it 'should log a hash error', ->
+				@logger.error.calledWith()
+					.should.equal true

 	describe "removeDocFromMemory", ->
 		beforeEach (done) ->


[FIX] crash editing content with tinymce, TODO: check if markdown crash too

--- common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -18,11 +18,11 @@

 #   This is a workaround for the fact that tinyMCE's baseURL property is not getting correctly set on AWS
 #   instances (like sandbox). It is not necessary to explicitly set baseURL when running locally.
-    tinyMCE.baseURL = "#{baseUrl}/js/vendor/tiny_mce"
+    tinyMCE.baseURL = "#{baseUrl}/js/vendor/tinymce"
     @tiny_mce_textarea = $(".tiny-mce", @element).tinymce({
-      script_url : "#{baseUrl}/js/vendor/tiny_mce/tiny_mce.js",
-      theme : "advanced",
-      skin: 'studio',
+      script_url : "#{baseUrl}/js/vendor/tinymce/tinymce.min.js",
+      theme : "modern",
+      skin: 'lightgray',
       schema: "html5",
       # Necessary to preserve relative URLs to our images.
       convert_urls : false,
@@ -30,7 +30,7 @@
       content_css : "#{baseUrl}/css/tiny-mce.css",
       # The default popup_css path uses an absolute path referencing page in which tinyMCE is being hosted.
       # Supply the correct relative path instead.
-      popup_css: "#{baseUrl}/js/vendor/tiny_mce/themes/advanced/skins/default/dialog.css",
+#      popup_css: "#{baseUrl}/js/vendor/tinymce/themes/advanced/skins/default/dialog.css",
       formats : {
         # Disable h4, h5, and h6 styles as we don't have CSS for them.
         h4: {},
@@ -42,12 +42,12 @@
       # Disable visual aid on borderless table.
       visual:false,
       # We may want to add "styleselect" when we collect all styles used throughout the LMS
-      theme_advanced_buttons1 : "formatselect,bold,italic,underline,|,bullist,numlist,outdent,indent,|,blockquote,wrapAsCode,|,link,unlink",
-      theme_advanced_toolbar_location : "top",
-      theme_advanced_toolbar_align : "left",
-      theme_advanced_statusbar_location : "none",
-      theme_advanced_resizing : true,
-      theme_advanced_blockformats : "p,pre,h1,h2,h3",
+      theme_modern_buttons1 : "formatselect,bold,italic,underline,|,bullist,numlist,outdent,indent,|,blockquote,wrapAsCode,|,link,unlink",
+      theme_modern_toolbar_location : "top",
+      theme_modern_toolbar_align : "left",
+      theme_modern_statusbar_location : "none",
+      theme_modern_resizing : true,
+      theme_modern_blockformats : "p,pre,h1,h2,h3",
       width: '100%',
       height: '400px',
       setup : @setupTinyMCE,
@@ -65,19 +65,17 @@
     @element.on('click', '.editor-tabs .tab', @onSwitchEditor)

   setupTinyMCE: (ed) =>
+    debugger
     ed.addButton('wrapAsCode', {
       title : 'Code',
       image : "#{baseUrl}/images/ico-tinymce-code.png",
+      stateSelector : 'code'
       onclick : () ->
         ed.formatter.toggle('code')
         # Without this, the dirty flag does not get set unless the user also types in text.
         # Visual Editor must be marked as dirty or else we won't populate the Advanced Editor from it.
         ed.isNotDirty = false
     })
-
-    ed.onNodeChange.add((editor, command, e) ->
-      command.setActive('wrapAsCode', e.nodeName == 'CODE')
-    )

     @visualEditor = ed


run: configure all contexts with filter

run: configure all contexts with filter

--- lib/run.coffee
+++ lib/run.coffee
@@ -41,12 +41,12 @@
     each(contexts)
     .parallel(true)
     .run (host, ctx, next) =>
-      # Filter by hosts
-      return next() if params.hosts? and multimatch(host, params.hosts).indexOf(host) is -1
       @emit 'context', ctx
       for module in Object.keys ctx.tree.modules
         module = ctx.tree.modules[module]
         module.configure.call ctx, ctx if module.configure
+      # Filter by hosts
+      return next() if params.hosts? and multimatch(host, params.hosts).indexOf(host) is -1
       # console.log ctx.config.ryba
       # return next()
       ctx.tree.middlewares params, (err, middlewares) =>


ONGOING toggle select2

ONGOING toggle select2

--- app/assets/javascripts/initialization.js.coffee
+++ app/assets/javascripts/initialization.js.coffee
@@ -42,6 +42,7 @@
       $(".scheme-finder-frontend").addClass("select2-open")
     )).on("select2-open", -> (
       $(".select2-selected").addClass("select2-result-unselectable").removeClass("select2-result-selectable")
+      $(".select2-dropdown-open").attr( "data-value", $(".select2-dropdown-open").closest(".input").find("select").select2("val") )
       $(".select2-drop").each( -> (
         if $(this).css("display") == "block"
           $(".select2-drop").removeClass("height-checked")
@@ -66,6 +67,17 @@
   document.addEventListener('touchstart', (e) -> (
     xStart = e.touches[0].screenX
     yStart = e.touches[0].screenY
+
+    if $(e.target).attr("class") == "select2-result-label"
+      select2_selected = $(e.target).closest(".select2-drop").find("li").index($(e.target).closest("li"))
+      clicked_select = $(".select2-dropdown-open").closest(".input").find("select")
+      select_values = $(".select2-dropdown-open").attr("data-value")
+      clicked_value = clicked_select.find("option:eq("+(select2_selected-1)+")").val()
+      new_values = select_values
+      #if select2_selected > -1
+      #  if $(e.target).closest(".select2-drop").find("li:eq("+select2_selected+")").hasClass("select2-selected")
+      #    new_values = select_values.splice(select_values.indexOf(clicked_value), 1)
+      $(".scheme-finder-frontend header.page-header h1").text("["+select_values+"] "+select_values.indexOf(clicked_value)+":"+clicked_value)
   ))

   document.addEventListener('touchmove', (e) -> (


add warning if allowed mode is not valid

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -95,6 +95,10 @@
           switch allowedMode
             when "flight" then @allowedModes.push(constants.MODE_ARBITRARY)
             when "oblique" then @allowedModes.push(constants.MODE_ARBITRARY_PLANE)
+        else
+            # flight and oblique mode do not work with non-uint8 data
+            if allowedMode is "flight" or allowedMode is "oblique"
+              Toast.error("#{allowedMode} mode was allowed but does not work with more-than-8-bit data.")

         switch allowedMode
           when "orthogonal" then @allowedModes.push(constants.MODE_PLANE_TRACING)


Trying to call product properties on show page

--- app/assets/javascripts/sprangular/routes.coffee
+++ app/assets/javascripts/sprangular/routes.coffee
@@ -31,7 +31,7 @@
         product: (Status, Catalog, $route) ->
           slug = $route.current.params.id

-          Status.findCachedProduct(slug) || Catalog.find(slug)
+          Catalog.find(slug)

     .when '/t/:path*',
       controller: 'ProductListCtrl'


fix butt problems

fix butt problems

--- src/Main.coffee
+++ src/Main.coffee
@@ -132,9 +132,9 @@
       if pad.isDown(Phaser.Gamepad.XBOX360_A)
         moves.but2 = true
       if pad.isDown(Phaser.Gamepad.XBOX360_X)
-        moves.but2 = true
+        moves.but3 = true
       if pad.isDown(Phaser.Gamepad.XBOX360_B)
-        moves.but2 = true
+        moves.but4 = true

     if @kb.isDown(Phaser.Keyboard.UP)
       moves.up = true
@@ -147,11 +147,11 @@
     if @kb.isDown(Phaser.Keyboard.W)
       moves.but1 = true
     if @kb.isDown(Phaser.Keyboard.S)
-      moves.but1 = true
+      moves.but2 = true
     if @kb.isDown(Phaser.Keyboard.A)
-      moves.but1 = true
+      moves.but3 = true
     if @kb.isDown(Phaser.Keyboard.D)
-      moves.but1 = true
+      moves.but4 = true

     moves


sync atom config

sync atom config

--- config.cson
+++ config.cson
@@ -1,6 +1,8 @@
 "*":
   "atom-typescript": {}
-  core: {}
+  core:
+    closeDeletedFileTabs: true
+    telemetryConsent: "limited"
   editor:
     invisibles: {}
     showIndentGuide: true
@@ -8,10 +10,13 @@
     tabLength: 4
   "exception-reporting":
     userId: "f7a458c4-0059-94aa-5d24-065e8a046778"
-  linter:
-    errorPanelHeight: 50
+  linter: {}
+  "linter-ui-default":
+    showPanel: true
   "markdown-preview":
     useGitHubStyle: true
+  "tree-view":
+    hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false
 ".source.ts":


Updating

Updating

--- config.cson
+++ config.cson
@@ -41,6 +41,7 @@
     enableMenu: true
     enableToolBar: true
     launchOnStartup: false
+  linter: {}
   "markdown-writer":
     textStyles:
       codeblock:
@@ -57,9 +58,5 @@
   "toggle-soft-wrap":
     linter:
       showErrorPanel: false
-  "tool-bar":
-    visible: false
-  "uber-juno":
-    disable: true
   welcome:
     showOnStartup: false


simplified code

simplified code

--- src/caffeine/caffeine.coffee
+++ src/caffeine/caffeine.coffee
@@ -70,10 +70,17 @@
   compile: (code, options = {})->
     # log "Caffeine.compile", code: code, options: options

-    if match = code.match(Caffeine.multiLineMetaCompiledSectionRegExp) || code.match(Caffeine.oneLineMetaCompiledSectionRegExp)
-      [_, metaCode, rest] = match
-      {compiled:{js}} = @compiler.compile metaCode
-      evalInContext js, @
-      rest && @metaCompiler.compile rest, options
+    if match = @matchMetaCompileBlock code
+      {metaCode, code} = match
+      evalInContext @compiler.compile(metaCode).compiled.js, @
+      @metaCompiler.compile code, options
     else
       @compiler.compile code, options
+
+  matchMetaCompileBlock: (code) ->
+    if match = code.match(Caffeine.multiLineMetaCompiledSectionRegExp) ||
+        code.match(Caffeine.oneLineMetaCompiledSectionRegExp)
+      [_, metaCode, code] = match
+      metaCode: metaCode
+      code: code || ""
+


Removing jQuery

Removing jQuery

--- test/dummy/app/assets/javascripts/views/main/users/user_registration_form.coffee
+++ test/dummy/app/assets/javascripts/views/main/users/user_registration_form.coffee
@@ -13,11 +13,13 @@

   _created: (data) ->
     this.connectWith new App.Models.User(id: data.id)
-    $('form').add('#sign_in_paragraph').hide 'slow'
-    $('#verification_info').show 'slow'
+    document.querySelector('form').style.display = 'none'
+    document.getElementById('sign_in_paragraph').classList.remove('none')
+    document.getElementById('verification_info').classList.remove('none')
     flash = new App.Views.Shared.Flash notice: data.notice
     flash.render()

-  _confirming: -> $('#verification_info').text $('#verification_progress').text()
+  _confirming: ->
+    document.getElementById('verification_info').textContent = document.getElementById('verification_progress').textContent

   _confirmed: -> window.location.href = '/user/sessions/new?event=confirmed'


Updated syntax.

Updated syntax.

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -89,11 +89,9 @@
   ext  = path.extname(source) or path.basename(source)
   lang = languages[ext]

-  unless lang?
-    # Should fail silently instead and move onto the next file
-    #throw new Error "Chalkboard does not support type #{ext}"
-    return null
-
+  # Should fail silently when langauge is not supported
+  # and move onto the next file
+  return null unless lang?

   regex = "^\\s*#{lang.symbol}{1,2}#{commentRegexStr}"
   lang.commentRegex = new RegExp regex


Added Captains Draft match id.

Added Captains Draft match id.

--- public/src/steam-api/steam-api-dota-match.coffee
+++ public/src/steam-api/steam-api-dota-match.coffee
@@ -6,19 +6,20 @@
       game_mode_lookup = {
         0: 'None'
         1: 'All Pick'
-        2: 'Captain\'s Mode'
+        2: 'Captains Mode'
         3: 'Random Draft'
         4: 'Single Draft'
         5: 'All Random'
         6: 'Intro'
         7: 'Diretide'
-        8: 'Reverse Captain\'s Mode'
+        8: 'Reverse Captains Mode'
         9: 'The Greeviling'
         10: 'Tutorial'
         11: 'Mid Only'
         12: 'Least Played'
         13: 'New Player Pool'
         14: 'Compendium Matchmaking'
+        16: 'Captains Draft'
       }

       constructor: (matchId) ->


delete/change some comments in file_use

delete/change some comments in file_use

--- src/smc-webapp/file_use.cjsx
+++ src/smc-webapp/file_use.cjsx
@@ -28,52 +28,9 @@

 DOC:

-To mark a file as read: @props.redux.getActions('file_use').mark_file(@props.project_id, @props.path, 'chat')
-
-TODO:
-
-- [x] (0:30)  basic structure and plan
-- [x] (0:15?) (0:22) sorted file use by last_edited timestamp
-- [x] (0:30?) (0:24) display items a little more readably
-- [x] (0:30?) (0:55) search
-- [x] (0:30?) (0:27) click to open file
-- [x] (0:45?) (0:25) -- proper handling of .sage-chat, ipython, etc. extensions -- seems not working right -- see hub.
-- [x] (0:45?) (1:22) notification number
-- [x] (0:30?) deal with this in misc_page.coffee: `#u = require('activity').important_count()`
-- [x] (1:45) fix subtle backend database issues needed for marking read/seen
-- [x] (0:45?) (2:06) mark read
-    - [x] (0:42) make an action that takes a single id or array of them as input and marks them all read/seen/etc.
-    - [x] (0:24) make clicking mark that one as read
-    - [x] (1:00) mark all as read button
-- [x] (0:45?) (0:03) mark all seen
-- [x] (0:30?) (0:12) click to open file needs to open the chat if there are unseen chats
-- [x] (1:00?) (1:02) if list of projects you collaborate on changes, must reset the file_use table, since the files you watched change as a result; client or server side?
-- [x] (0:45?) (0:18) delete old polling based activity notification code from hub; delete old activity notification code from page
-- [x] (1:00?) (0:30) cursor and enter to open first thing in notification search -- like in log
-- [x] (2:00?) (2:15) make pretty:
-   - [x] make wider
-   - [x] move mark_all_read button to upper right
-   - [x] spacing around file_use item
-   - [x] color to indicate read
-   - [x] color to indicate unseen
-   - [x] comment indication
-   - [x] truncate long project name
-   - [x] truncate long user name
-   - [x] truncate long filename
-   - [x] nicer layout
-   - [x] file type icons
-   - [x] special highlight first entry so user knows can open on enter
-   - [x] use timeago for when
-   - [x] truncate too many names
-- [x] (2:00?) optimize
-   - [x] (0:30?) (0:30) only show first few notifications and have a button to show more
-   - [x] (1:30?) (0:29) switch to immutable.js info and shouldComponentUpdate and optimize when do search
-
-
-LATER/UNRELATED:
-
-- [ ] (1:00?) address this comment in client.coffee "TODO: must group all queries in one call."
-- [ ] (1:00?) in general, open_file needs some sort of visual feedback while it is happening (in any situation)
+To mark a file as read:
+
+    redux.getActions('file_use').mark_file(project_id, path, 'chat')

 ###


fix serious bug in hiding the hidden/deleted buttons in the projects listing: (1) if a project was both hidden and deleted get no buttons! (2) also the comments and function names confused projects with files (!?), and (3) there were two copies of the functions

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -1110,37 +1110,25 @@
     # Consolidate the next two functions.
     ###

-    # Returns true if this project has any hidden files
-    has_hidden_files : ->
-        for project in @project_list()
-            if project_is_in_filter(project, true, false)
-                return true
-        return false
-
-
-    # Returns true if this project has any deleted files
-    has_deleted_files : ->
+    # Returns true if the user has any deleted projects
+    has_deleted_projects : ->
         for project in @project_list()
             if project_is_in_filter(project, false, true)
                 return true
         return false

-    ###
-    # Consolidate the next two functions.
-    ###
-
-    # Returns true if this project has any hidden files
-    has_hidden_files : ->
+    # Returns true if the user has any hidden projects
+    has_hidden_projects : ->
         for project in @project_list()
-            if project_is_in_filter(project, true, false)
+            if project_is_in_filter(project, true, false) or project_is_in_filter(project, true, true)
                 return true
         return false

     # Returns true if this project has any deleted files
-    has_deleted_files : ->
+    has_deleted_projects : ->
         for project in @project_list()
-            if project_is_in_filter(project, false, true)
+            if project_is_in_filter(project, false, true) or project_is_in_filter(project, true, true)
                 return true
         return false

@@ -1160,8 +1148,8 @@
                         <ProjectsFilterButtons
                             hidden  = {@props.hidden}
                             deleted = {@props.deleted}
-                            show_hidden_button = {@has_hidden_files() or @props.hidden}
-                            show_deleted_button = {@has_deleted_files() or @props.deleted} />
+                            show_hidden_button = {@has_hidden_projects() or @props.hidden}
+                            show_deleted_button = {@has_deleted_projects() or @props.deleted} />
                     </Col>
                 </Row>
                 <Row>


Add angular declaration so it works

Add angular declaration so it works

--- source/js/main.coffee
+++ source/js/main.coffee
@@ -1 +1,7 @@
 #= jquery/jquery
+
+app = angular.module 'barebones', []
+
+app.controller 'mainController', ($scope) ->
+    $scope.appName = 'BareBones'
+


Add functionality to add specific url to findOrInitialize on backbone user model.

--- app/assets/javascripts/collections/users.js.coffee
+++ app/assets/javascripts/collections/users.js.coffee
@@ -35,6 +35,7 @@
       if ids.length > 0

         if (options.specific_endpoint == true)
+          url = if options.url then options.url else @url()
           data = null
         else
           data = { ids: ids }
@@ -42,6 +43,7 @@
         @fetch
           add: true
           data: data
+          url: url
           success: (resp, status, xhr) =>
             options.success(resp, status, xhr) if options.success
           error: (resp, xhr, _options) =>


Bug fix

Bug fix

--- app/assets/javascripts/embeditor/adapter.js.coffee
+++ app/assets/javascripts/embeditor/adapter.js.coffee
@@ -30,7 +30,7 @@

     embed: (html) ->
         @wrapper.html(html)
-        @element[Embeditor.PlacementFunctions(@display.placement)](@wrapper)
+        @element[Embeditor.PlacementFunctions[@display.placement]](@wrapper)


fix drawing multiple prev marks by assigning unique key

--- app/assets/javascripts/lib/mark-drawing-mixin.cjsx
+++ app/assets/javascripts/lib/mark-drawing-mixin.cjsx
@@ -6,7 +6,7 @@
     # DEBUG CODE
     # console.log 'PREVIOUS MARKS: ', @props.subject.child_subjects_info
     previousMarks =
-      for previousMark in @props.subject.child_subjects_info
+      for previousMark, i in @props.subject.child_subjects_info
         toolName = previousMark.spec.toolName
         ToolComponent = markingTools[toolName]
         scale = @getScale()
@@ -24,7 +24,7 @@
             height = previousMark.spec.height

         <ToolComponent
-          key={@props.subject.id}
+          key={i}
           mark={previousMark.spec}
           disabled={true}
           isPriorMark={true}


TP-6278 - Change syndicate redundancy url

--- app/assets/javascripts/syndication/tools.js.coffee
+++ app/assets/javascripts/syndication/tools.js.coffee
@@ -235,9 +235,9 @@

     redundancy_action_plans:
       en:
-        path: "en/action-plans/redundancy"
-      cy:
-        path: "cy/action-plans/dileu-swydd"
+        path: "en/tools/redundancy-pay-calculator"
+      cy:
+        path: "cy/tools/cyfrifiannell-tal-diswyddo"
       width: "100%"
       title: "Redundancy action plan"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -74,7 +74,7 @@
 Get the file extension of a string.

     "README.md".extension() # => "md"
-    "README".extens
+    "README".extension

 @name extension
 @methodOf String#


fix index/images lightbox js itemSelector

--- app/cells/folio/console/index/images/images.coffee
+++ app/cells/folio/console/index/images/images.coffee
@@ -1,3 +1,3 @@
 window.makeFolioLightbox('.f-c-index-images',
-                         itemSelector: '.f-c-index-images__item'
+                         itemSelector: '.f-c-index-images__item[data-lightbox-src]'
                          individual: true)


Sidebar: Append subviews when ready

Sidebar: Append subviews when ready

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -474,9 +474,10 @@

     super

-    @addMachineList()
-    @addFollowedTopics()
-    @addMessages()
+    kd.getSingleton('mainController').ready =>
+      @addMachineList()
+      @addFollowedTopics()
+      @addMessages()

   initiateFakeCounter: ->


removing fetch() call

removing fetch() call

--- client/controllers/incidentReports/incidentForm.coffee
+++ client/controllers/incidentReports/incidentForm.coffee
@@ -48,7 +48,7 @@

     url = @incidentData.url[0]
     if url
-      @incidentData.articleSource = _.findWhere(@data.articles.fetch(),
+      @incidentData.articleSource = _.findWhere(@data.articles,
         url: url
       )?._id

@@ -82,7 +82,7 @@
     type is Template.instance().incidentType.get()

   articles: ->
-    Template.instance().data.articles.fetch()
+    Template.instance().data.articles

   showCountForm: ->
     type = Template.instance().incidentType.get()


Sanity check just for non super-admins

Sanity check just for non super-admins

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -408,7 +408,7 @@
           callback? msg : "App is already installed!"
         else
           log "installing the app: #{app.title}"
-          if not app.approved
+          if not app.approved and not KD.checkFlag 'super-admin'
             err = "This app is not approved, installation cancelled."
             log err
             callback? err


a small note

a small note

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -275,6 +275,8 @@
                     Check Console for more details.

                   """
+                  # We may after put a full stck to the output
+                  # It looks weird for now.
                   #
#{error.stack}

       console.warn error.message, error


Downscale clone as well

Downscale clone as well

--- app/scripts/components/entities/PlayerClone.coffee
+++ app/scripts/components/entities/PlayerClone.coffee
@@ -3,7 +3,7 @@
     @requires 'Enemy, playerShip'

   playerClone: (attr = {}) ->
-    @attr _.defaults(attr, h: 60, w: 94, health: 900)
+    @attr _.defaults(attr, h: 45, w: 71, health: 900)
     @origin 'center'
     @flipX()
     @colorOverride '#808080', 'partial'


update webgl to refactored props

update webgl to refactored props

--- bokehjs/src/coffee/models/glyphs/bokehgl.coffee
+++ bokehjs/src/coffee/models/glyphs/bokehgl.coffee
@@ -65,7 +65,7 @@
     m = 4
     colorname = prefix + '_color'
     alphaname = prefix + '_alpha'
-
+
     if not visual.doit
       # Don't draw (draw transparent)
       vbo.used = false
@@ -514,7 +514,7 @@
                   d = max(d, min(abs(x.x),abs(x.y)));
               */
           }
-
+
           return d;
       }

@@ -524,7 +524,7 @@
           if( v_color.a <= 0.0 ) {
               discard;
           }
-
+
           // Test if dash pattern is the solid one (0)
           bool solid =  (u_dash_index == 0.0);

@@ -542,14 +542,14 @@
           vec2 dash_caps = u_dash_caps;
           float line_start = 0.0;
           float line_stop = v_length;
-
+
           // Apply miter limit; fragments too far into the miter are simply discarded
           if( (dx < v_segment.x) || (dx > v_segment.y) ) {
               float into_miter = max(v_segment.x - dx, dx - v_segment.y);
               if (into_miter > u_miter_limit*v_linewidth/2.0)
                 discard;
           }
-
+
           // Solid line --------------------------------------------------------------
           if( solid ) {
               d = abs(dy);
@@ -775,7 +775,7 @@

     draw: (indices, mainGlyph, trans) ->
       mainGlGlyph = mainGlyph.glglyph
-
+
       if mainGlGlyph.data_changed
         mainGlGlyph._baked_offset = [trans.dx, trans.dy]  # float32 precision workaround; used in _bake() and below
         mainGlGlyph._set_data()
@@ -811,7 +811,7 @@
       @prog.set_uniform('u_offset', 'vec2', [trans.dx - baked_offset[0], trans.dy - baked_offset[1]])
       @prog.set_uniform('u_scale_aspect', 'vec2', [sx, sy])
       @prog.set_uniform('u_scale_length', 'float', [scale_length])
-
+
       if @I_triangles.length < 65535
         # Data is small enough to draw in one pass
         @index_buffer.set_size(@I_triangles.length*2)
@@ -863,7 +863,7 @@
       @vbo_texcoord.set_data(0, @V_texcoord)

     _set_visuals: () ->
-
+
       color = color2rgba(@glyph.visuals.line.line_color.value(), @glyph.visuals.line.line_alpha.value())
       cap = @CAPS[@glyph.visuals.line.line_cap.value()]
       join = @JOINS[@glyph.visuals.line.line_join.value()]
@@ -902,8 +902,8 @@

       # Init array of implicit shape nx2
       n = @nvertices
-      _x = new Float64Array(@glyph.x)
-      _y = new Float64Array(@glyph.y)
+      _x = new Float64Array(@glyph._x)
+      _y = new Float64Array(@glyph._y)

       # Init vertex data
       V_position = Vp = new Float32Array(n*2)
@@ -1158,13 +1158,13 @@
     # The main glyph has the data, *this* glyph has the visuals.
     mainGlGlyph = mainGlyph.glglyph
     nvertices = mainGlGlyph.nvertices
-
+
     # Upload data if we must. Only happens for main glyph.
     if mainGlGlyph.data_changed
       mainGlGlyph._baked_offset = [trans.dx, trans.dy]  # float32 precision workaround; used in _set_data() and below
       mainGlGlyph._set_data(nvertices)
       mainGlGlyph.data_changed = false
-    else if @glyph.radius? and (trans.sx != @last_trans.sx or trans.sy != @last_trans.sy)
+    else if @glyph._radius? and (trans.sx != @last_trans.sx or trans.sy != @last_trans.sy)
       # Keep screen radius up-to-date for circle glyph. Only happens when a radius is given
       @last_trans = trans
       @vbo_s.set_data(0, new Float32Array((s*2 for s in @glyph.sradius)))
@@ -1180,7 +1180,7 @@
     @prog.set_uniform('u_canvas_size', 'vec2', [trans.width, trans.height])
     @prog.set_uniform('u_offset', 'vec2', [trans.dx - baked_offset[0], trans.dy - baked_offset[1]])
     @prog.set_uniform('u_scale', 'vec2', [trans.sx, trans.sy])
-
+
     # Select buffers from main glyph
     # (which may be this glyph but maybe not if this is a (non)selection glyph)
     @prog.set_attribute('a_x', 'float', [mainGlGlyph.vbo_x, 0, 0])
@@ -1240,22 +1240,22 @@
     @vbo_s.set_size(n)
     # Upload data for x and y, apply a baked-in offset for float32 precision (issue #3795)
     # The exact value for the baked_offset does not matter, as long as it brings the data to less extreme values
-    xx = new Float64Array(@glyph.x)
-    yy = new Float64Array(@glyph.y)
+    xx = new Float64Array(@glyph._x)
+    yy = new Float64Array(@glyph._y)
     for i in [0...nvertices]
        xx[i] += @_baked_offset[0]
        yy[i] += @_baked_offset[1]
     @vbo_x.set_data(0, new Float32Array(xx))
     @vbo_y.set_data(0, new Float32Array(yy))
     # Angle if available; circle does not have angle. If we don't set data, angle is default 0 in glsl
-    if @glyph.angle?
-      @vbo_a.set_data(0, new Float32Array(@glyph.angle))
+    if @glyph._angle?
+      @vbo_a.set_data(0, new Float32Array(@glyph._angle))
     # Radius is special; some markes allow radius in data-coords instead of screen coords
     # @radius tells us that radius is in units, sradius is the pre-calculated screen radius
-    if @glyph.radius?
+    if @glyph._radius?
       @vbo_s.set_data(0, new Float32Array((s*2 for s in @glyph.sradius)))
     else
-      @vbo_s.set_data(0, new Float32Array(@glyph.size))
+      @vbo_s.set_data(0, new Float32Array(@glyph._size))

   _set_visuals: (nvertices) ->
     attach_float(@prog, @vbo_linewidth, 'a_linewidth', nvertices, @glyph.visuals.line, 'line_width')


Fix draggable rounding

Fix draggable rounding

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -175,9 +175,11 @@
 			t: Date.now() # We don't use timeStamp because it's different on Chrome/Safari

 		# See if horizontal/vertical was set and set the offset
+
 		point = @layer.point
-		point.x += event.delta.x * (1 / @layer.canvasScaleX() * @layer.scale * @layer.scaleX) if @horizontal
-		point.y += event.delta.y * (1 / @layer.canvasScaleY() * @layer.scale * @layer.scaleY) if @vertical
+		point.x = @_layerStartPoint.x + (event.offset.x * (1 / @layer.canvasScaleX() * @layer.scale * @layer.scaleX))
+		point.y = @_layerStartPoint.y + (event.offset.y * (1 / @layer.canvasScaleY() * @layer.scale * @layer.scaleY))
+

 		# Constraints and overdrag
 		point = @_constrainPosition(point, @_constraints, @overdragScale) if @_constraints


payment: finish implementing CRUD for account screen

now we can list, create, edit and remove payment methods

--- client/landing/app/Applications/Account.kdapplication/views/paymentmethods.coffee
+++ client/landing/app/Applications/Account.kdapplication/views/paymentmethods.coffee
@@ -38,8 +38,9 @@

     list.on 'reload', (data) => @loadItems()

-  editPaymentMethod: ({ accountCode }) ->
-    @showModal()
+  editPaymentMethod: (data) ->
+    paymentController = KD.getSingleton 'paymentController'
+    @showModal data

   removePaymentMethod: ({ accountCode }, item) ->
     paymentController = KD.getSingleton 'paymentController'
@@ -64,20 +65,20 @@
       iconClass : "plus"
       callback  : => @showModal()

-  showModal: ->
+  showModal: (initialBillingInfo) ->
     paymentController = KD.getSingleton 'paymentController'
-    modal = paymentController.createBillingInfoModal 'user', {}
-    paymentController.fetchBillingInfo 'user', (err, initialBillingInfo) =>

-      modal.setBillingInfo initialBillingInfo.billing  if initialBillingInfo?
+    modal = paymentController.createBillingInfoModal()

-      modal.on 'PaymentInfoSubmitted', (updatedBillingInfo) =>
-        paymentController.updateBillingInfo updatedBillingInfo, (err, res) =>
-          if err
-            new KDNotificationView title: err.message
-          else
-            modal.destroy()
-            @loadItems()
+    modal.setBillingInfo initialBillingInfo.billing  if initialBillingInfo?
+
+    modal.on 'PaymentInfoSubmitted', (updatedBillingInfo) =>
+      paymentController.updateBillingInfo updatedBillingInfo, (err, res) =>
+        if err
+          new KDNotificationView title: err.message
+        else
+          modal.destroy()
+          @loadItems()

@@ -99,13 +100,20 @@

     @billingMethod = new BillingMethodView {}, @getData().billing

+    @billingMethod.on 'BillingEditRequested', =>
+      @emit 'PaymentMethodEditRequested', data
+
     @editLink = new CustomLinkView
       title: 'edit'
-      click: => @emit 'PaymentMethodEditRequested', data
+      click: (e) =>
+        e.preventDefault()
+        @emit 'PaymentMethodEditRequested', data

     @removeLink = new CustomLinkView
       title: 'remove'
-      click: => @emit 'PaymentMethodRemoveRequested', data
+      click: (e) =>
+        e.preventDefault()
+        @emit 'PaymentMethodRemoveRequested', data

   viewAppended: JView::viewAppended


removes default transition duration

removes default transition duration

--- source/javascripts/views/chartView.coffee
+++ source/javascripts/views/chartView.coffee
@@ -74,7 +74,6 @@
     @setScales()

     @svg.selectAll("path").transition()
-      .duration(500)
       .attr("d", @line(@model.get('data')))

     @svg.select(".axis")


Use correct log file name

Use correct log file name

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -36,7 +36,7 @@
 logOptions =
   streams: [
     { level: 'error', stream: process.stderr }
-    { level: argv.logLevel, path: "#{argv.logDir}/sphere-stock-xml-import_#{argv.projectKey}.log" }
+    { level: argv.logLevel, path: "#{argv.logDir}/sphere-order-xml-import_#{argv.projectKey}.log" }
   ]
 logOptions.silent = argv.logSilent if argv.logSilent
 logger = new Logger logOptions


Rename command for products updates import

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -59,16 +59,16 @@
             process.exit 1

     program
-      .command 'import-product-updates'
+      .command 'import-products-updates'
       .description 'Imports Brickfox product stock and price changes into your SPHERE.IO project.'
       .option '--products <file>', 'XML file containing products to import'
       .option '--mapping <file>', 'JSON file containing Brickfox to SPHERE.IO mapping'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --mapping <file> --products <file>'
       .action (opts) ->

-        validateGlobalOpts(opts, 'import-product-updates')
-        validateOpt(opts.mapping, 'mapping', 'import-product-updates')
-        validateOpt(opts.products, 'products', 'import-product-updates')
+        validateGlobalOpts(opts, 'import-products-updates')
+        validateOpt(opts.mapping, 'mapping', 'import-products-updates')
+        validateOpt(opts.products, 'products', 'import-products-updates')

         logger = new ProductUpdateImportLogger
           src: if argv.debug then argv.debug else false


Adjust command line options description.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -10,13 +10,13 @@
 .alias('sourceProjects', 's')
 .alias('targetProject', 't')
 .alias('statsPort', 'p')
-.describe('sourceProjects', 'Sphere.io project credentials. The messages from these projects would be processed. It has following format: `prj1-key:clientId:clientSecret[,prj2-key:clientId:clientSecret][,...]`.')
-.describe('targetProject', 'Sphere.io credentials of the target project. It has following format: `prj-key:clientId:clientSecret`.')
+.describe('sourceProjects', 'Sphere.io project credentials. The messages from these projects would be processed. Format: `prj1-key:clientId:clientSecret[,prj2-key:clientId:clientSecret][,...]`.')
+.describe('targetProject', 'Sphere.io credentials of the target project. Format: `prj-key:clientId:clientSecret`.')
 .describe('statsPort', 'The port of the stats HTTP server.')
-.describe('processorName', 'The name of this processor. It would be used to rebember, which messaged are already processed.')
-.describe('printStats', 'Whether to print stats to the consome every 3 seconds.')
+.describe('processorName', 'The name of this processor. Name is used to rebember, which messaged are already processed.')
+.describe('printStats', 'Whether to print stats to the console every 3 seconds.')
 .describe('awaitTimeout', 'How long to wait for the message ordering (in ms).')
-.describe('heartbeatInterval', 'How How often are messages retrieved from sphere project (in ms).')
+.describe('heartbeatInterval', 'How often are messages retrieved from sphere project (in ms).')
 .describe('fetchHours', 'How many hours of messages should be fetched (in hours).')
 .default('statsPort', 7777)
 .default('awaitTimeout', 120000)
@@ -85,4 +85,4 @@
 #.fail (error) ->
 #  console.error "Errror during setup"
 #  console.error error.stack
-#.done()+#.done()


add to Collection synchronize interface

add to Collection synchronize interface

--- src/collection.coffee
+++ src/collection.coffee
@@ -3,19 +3,35 @@

 class Sirisus.Collection
-
+  #remote - ajax calls, should return json for model
   #klass Model klass, must be extend BaseModel
-  constructor: (klass, options = {every: 0, on_add: @on_add, on_remove: @on_remove, remote: ->}) ->
+  constructor: (klass, klasses = [], options = {every: 0, on_add: @on_add, on_remove: @on_remove, remote: null}) ->
     throw new Error("Collection must be used only with `BaseModel` inheritor") if klass.__super__.constructor.name isnt 'BaseModel'
     @_array = []
+    @_klasses = klasses
     @_type  = klass.name
+
     @on_add    = options.on_add || @on_add
     @on_remove = options.on_remove || @on_add
-    @remove    = options.remove
+
+    if options.remote
+      @remote = ->
+        result = options.remote()
+        @push(klass.from_json(result, @_klasses))
+    @_timer    = null
     _start_sync(every)

   @_start_sync: (every) ->
-    #timeout and push when every != 0
+    if (every != 0)
+      @_timer = setInterval(@remote, every)
+
+
+  unsync: () ->
+    if @_timer
+      clearInterval(@_timer)
+
+  sync: (every) ->
+    @_start_sync(every)

   #check if is instance of BaseModel
   push: (model) ->


add Collection#to_json

add Collection#to_json

--- src/collection.coffee
+++ src/collection.coffee
@@ -51,7 +51,8 @@
     throw new Error("Collection must be used only with `BaseModel` inheritor") if klass.__super__.constructor.name isnt 'BaseModel'
     @_array = []
     @_klasses = klasses
-    @_type  = klass.name
+    @_klass = klass
+    @_type  = @_klass.name

     @on_add = options.on_add || @on_add
     @on_push = options.on_push || @on_push
@@ -169,9 +170,15 @@
   all: () ->
     @_array

-  # return size of collection
+  # @return [Numeric] size of collection
   size: () ->
     @_array.length
+
+  # convert collection to json
+  # @return [JSON]
+  to_json: () ->
+    z = for e in @_array then e.to_json()
+    JSON.parse(JSON.stringify(z))

   # @nodoc
   on_remove: (model) ->


Skip test entirely until there's a workaround

--- test/javascripts/unit/time_ago_test.js.coffee
+++ test/javascripts/unit/time_ago_test.js.coffee
@@ -29,10 +29,10 @@

   assertTimeAgo "#{day} at #{time}", "days", 5

-test "this year", ->
-  # This will fail on the first 7 days of the year.
-  # Not sure how to test without somehow stubbing Date.
-  if moment().dayOfYear() > 7
+# This will fail on the first 7 days of the year.
+# Not sure how to test without somehow stubbing Date.
+if moment().dayOfYear() > 7
+  test "this year", ->
     date = moment().subtract("days", 7).format "MMM D"
     assertTimeAgo "on #{date}", "days", 7


Remove warnings

Remove warnings

--- views/titlesList.cjsx
+++ views/titlesList.cjsx
@@ -81,7 +81,7 @@
             if @state.showData.titles? and @state.showData.titles isnt []
               for title, index in @state.showData.titles
                 <OverlayTrigger key={index} placement='right' overlay={
-                  <Popover>
+                  <Popover id="titles">

{title}


tie from SessionController to Bandango.currentSession

--- app/assets/javascripts/controllers/session_controller.js.coffee
+++ app/assets/javascripts/controllers/session_controller.js.coffee
@@ -8,3 +8,5 @@
   loggedIn: (->
     !!@get("user")
   ).property("user")
+
+Bandango.currentSession = Bandango.SessionController.create()


Scenario 43. Added

Scenario 43. Added

Scenario 43. Two tags with two attributes, two spaces at the beginning
of the two attributes

--- test/scenario-based-testing.coffee
+++ test/scenario-based-testing.coffee
@@ -444,3 +444,13 @@
     # console.log output
     expectedOutput = fs.readFileSync './test/scenarios/scenario-42-expected.html', 'utf8'
     # console.log expectedOutput
+
+  it 'Scenario 43. Two tags with two attributes, two spaces at the beginning of the two attributes', ->
+
+    input = fs.readFileSync './test/scenarios/scenario-43-input.cohtml', 'utf8'
+    # console.log input
+    output = toHtml5 input
+    # console.log output
+    expectedOutput = fs.readFileSync './test/scenarios/scenario-43-expected.html', 'utf8'
+    # console.log expectedOutput
+    expect(output).to.equal(expectedOutput)


wanzun

wanzun

--- views/components/settings/parts/others.cjsx
+++ views/components/settings/parts/others.cjsx
@@ -23,7 +23,7 @@

 Magica

myzWILLmake

Chibaheit

-
 KochiyaOcean

+
 KochiyaOcean

malichan

         </Col>


More consistent logging statement style

More consistent logging statement style

--- jquery.infinite-pages.js.coffee
+++ jquery.infinite-pages.js.coffee
@@ -96,12 +96,12 @@
     # Pause firing of events on scroll
     pause: ->
       @options.state.paused = true
-      @_log("Scroll checks paused")
+      @_log "Scroll checks paused"

     # Resume firing of events on scroll
     resume: ->
       @options.state.paused = false
-      @_log("Scroll checks resumed")
+      @_log "Scroll checks resumed"
       @check()

   # Define the plugin


open links in browser

open links in browser

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -1,4 +1,5 @@
 moment = require 'moment'
+shell = require 'shell'

 {nameof, linkto, later, forceredraw}  = require './vutil'

@@ -24,6 +25,12 @@
             }
             fallback_name: name
         }, silent:true
+
+onclick = (e) ->
+  e.preventDefault()
+  address = e.currentTarget.getAttribute 'href'
+  if address[0] == '/' then address = "http:" + address
+  shell.openExternal address

 # helper method to group events in time/user bunches
 groupEvents = (es, entity) ->
@@ -69,7 +76,7 @@
                     clz = ['ugroup']
                     clz.push 'self' if entity.isSelf(u.cid)
                     div class:clz.join(' '), ->
-                        a href:linkto(u.cid), class:'sender', sender
+                        a href:linkto(u.cid), {onclick}, class:'sender', sender
                         div class:'umessages', ->
                             for e in u.event
                                 div key:e.event_id, class:'message', ->
@@ -91,7 +98,7 @@
         if seg.type == "LINE_BREAK" then br(); continue
         f = seg.formatting ? {}
         href = seg?.link_data?.link_target
-        ifpass(href, ((f) -> a {href}, f)) ->
+        ifpass(href, ((f) -> a {href, onclick}, f)) ->
             ifpass(f.bold, b) ->
                 ifpass(f.italics, i) ->
                     ifpass(f.underline, u) ->


fixed code formating

fixed code formating

--- _src/logger.coffee
+++ _src/logger.coffee
@@ -190,16 +190,14 @@
 		_count = Object.keys( @READERS ).length
 		@warning "destroy #{_count} readers"

-		async.each(
-		@READERS,
-		( reader, acb ) =>
+		async.each( @READERS, ( reader, acb ) =>
 			@debug "reader destroy: " + _name
 			reader.destroy =>
 				@debug "reader destroyed: " + _name
 				acb()
 				return
-			return,
-		( err ) =>
+			return
+		, ( err ) =>
 			@debug "all readers destroyed"
 			@removeAllListeners()
 			cb()


Activity: inputwidget hidden for private messages

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -107,7 +107,7 @@

   createInputWidget: ->

-    return  if @getOption("type") in ["post", "message"]
+    return  if @getOption("type") in ['post', 'privatemessage']

     channel = @getData()


styleguide: Removed erroneous fat arrow, and incorrect indent level

--- client/app/lib/providers/managed/helpers.coffee
+++ client/app/lib/providers/managed/helpers.coffee
@@ -130,8 +130,8 @@
           .then (payload) -> kallback null, payload
           .catch    (err) -> kallback err

-    queryPromise.catch (err) =>
-        kallback err  if err
+    queryPromise.catch (err) ->
+      kallback err  if err

   # First, query for the kites to find the original kites
   # After that's done, start the heartbeat.
@@ -142,7 +142,7 @@
       # Call the heartbeat immediately *after* setInterval,
       # so we can clear it if it is immediately available.
       heartbeat()
-    .catch (err) =>
+    .catch (err) ->
       kallback err
   return


no more delimiters

no more delimiters

--- tentacle-transformer.coffee
+++ tentacle-transformer.coffee
@@ -12,13 +12,12 @@

   toProtocolBuffer: (msg) =>
     msgProto = new @MicrobluProto msg
-    console.log "msg is: #{JSON.stringify(msgProto)}"
-    console.log "incoming message is: #{JSON.stringify(msg, null, 2)}"
+    #console.log "msg is: #{JSON.stringify(msgProto)}"
+    #console.log "incoming message is: #{JSON.stringify(msg, null, 2)}"
     _.each _.keysIn(@msgProto), (key) =>
       console.log "message has key: #{key}"
-
-    msgProto.encodeDelimited()
-    msgProto.toBuffer()
+    msgProto.encode()
+    return Buffer.concat [msgProto.toBuffer(), new Buffer([0])]

   addData: (data) =>
     console.log "adding data"
@@ -27,7 +26,7 @@

   toJSON: () =>
     return unless @buffer?.remaining() > 0
-    decoded = @MicrobluProto.decodeDelimited @buffer
+    decoded = @MicrobluProto.decode @buffer
     return null if !decoded
     @buffer.compact()
     return JSON.parse decoded.encodeJSON()


Authorize `afterPut`, `afterPost`, `afterPatch` and `afterDelete` attributes to be passed to the addMock method

--- source/app/plasticine.coffee
+++ source/app/plasticine.coffee
@@ -100,6 +100,10 @@
       'patch'
       'delete'
       'afterGet'
+      'afterPut'
+      'afterPost'
+      'afterPatch'
+      'afterDelete'
     ]
     model[key] = params[key] for key in available_params
     model.setUp()


Show activity spinner while saving

Show activity spinner while saving

--- client/skr/screens/time-tracking/EditEntry.cjsx
+++ client/skr/screens/time-tracking/EditEntry.cjsx
@@ -25,6 +25,7 @@
     render: ->
         props = {sm:12, editOnly: true, model: @entry}
         <div className="entry-body">
+            <LC.NetworkActivityOverlay model={@entry} roundedCorners />
             <BS.Row>
                 <LC.SelectField {...props} sm={9}
                     name='customer_project' labelField="code" />


fix typo in comparing worker ID

fix typo in comparing worker ID

--- admin/admin.coffee
+++ admin/admin.coffee
@@ -293,7 +293,7 @@
       throw new Meteor.Error(500, e.toString())

     # Just check that it's actually the same worker here.
-    unless asst.workerId is asstData.workerId
+    unless asst.workerId is asstData.WorkerId
       throw new Meteor.Error(500, "Worker ID doesn't match")

     Assignments.update asstId,


Ignore comment nodes in label

Ignore comment nodes in label

--- server/cron/downloadMaterials.coffee
+++ server/cron/downloadMaterials.coffee
@@ -134,7 +134,7 @@
                 id++
             materials.push(await makeSpecialPage(activity.id + "_" + id, order + id, indent, child, keepResourcesInTree))
             id++
-        else
+        else if child.nodeType != 8  # Node.COMMENT_NODE
             currentText += child.outerHTML || child.nodeValue
     if currentText
         materials.push(await makeLabelMaterial(activity.id + "_" + id, order + id, indent, currentText))


make link for jupyter plain clearer

make link for jupyter plain clearer

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -578,7 +578,9 @@
                 {@render_jupyter_link()}

-                (If the above link doesn't work, refresh it.)
+
+                (The first time you click the above link it will probably fail; refresh and try again.)
+

         </ProjectSettingsPanel>


OX-368 cloudify example added

OX-368 cloudify example added

--- modules/levelcreator/app/assets/javascripts/level_creator/plugins/cloudify.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugins/cloudify.coffee
@@ -13,11 +13,24 @@
     input :
       rgba: "Uint8Array"
       dimensions : '[]'
+    r : "0 - 255"
+    g : "0 - 255"
+    b : "0 - 255"
+    a : "0 - 255"
+  EXAMPLES : [
+      { description : "recoloring using RGB", lines :
+        [ "time(start: 0, end : 10) ->"
+          "  importSlides(start:0, end: 10)"
+          "  filterSegmentationByDistance(distance: 100, mode: \"<\")"
+          "  cloudify(r: 0, g: 0, b: 255, a: 170)"
+        ]
+      }
+    ]

   ready: false
   cloud: null
-  size: 32
+  size: 16

   constructor : () ->
@@ -62,3 +75,5 @@
       rgba[l + 1] = g
       rgba[l + 2] = b
       rgba[l + 3] = ao * a
+
+    rgba


Remove log link from support ticket [#89350576]

we are keep uploading logs, incase of requirement for it. but we are not sending the link with the support ticket.

--- client/app/lib/commonviews/helpsupportmodal.coffee
+++ client/app/lib/commonviews/helpsupportmodal.coffee
@@ -53,8 +53,9 @@
       {submit} = @modalTabs.forms.Main.buttons
       {subject, message} = form

-      if @_logUrl?
-        message += "\n\n --- LOGS: #{@_logUrl} --- \n"
+      # if @_logUrl? ~ We are uploading logs but not including it
+      #                into the support ticket ~ GG #89350576
+      #   message += "\n\n --- LOGS: #{@_logUrl} --- \n"

       request = $.ajax "#{global.location.origin}/-/support/new",
         type        : "POST"


Client: Activity: Sidebar: Render all sidebar list items

--- client/app/lib/components/sidebarlist/index.coffee
+++ client/app/lib/components/sidebarlist/index.coffee
@@ -18,10 +18,7 @@

   renderChildren: ->

-    { itemComponent: Component, selectedId, threads, previewCount, componentProp } = @props
-
-    if componentProp is 'SidebarMessagesListItem'
-      threads = threads.slice 0, previewCount
+    { itemComponent: Component, selectedId, threads, componentProp } = @props

     threads.toList().map (thread) ->
       channel = thread.get 'channel'


Slave: Make sure incoming socket is still active during handoff

--- src/streammachine/slave/index.coffee
+++ src/streammachine/slave/index.coffee
@@ -322,12 +322,14 @@

         translator.on "stream_listener", (obj,socket) =>
             if !@_seen[ obj.key ]
-                # create an output and attach it to the proper stream
-                output = new @Outputs[ obj.client.output ] @streams[ obj.stream ],
-                    socket:     socket
-                    client:     obj.client
-                    startTime:  new Date(obj.startTime)
-                    minuteTime: new Date(obj.minuteTime)
+                # check and make sure they haven't disconnected mid-flight
+                if socket && !socket.destroyed
+                    # create an output and attach it to the proper stream
+                    output = new @Outputs[ obj.client.output ] @streams[ obj.stream ],
+                        socket:     socket
+                        client:     obj.client
+                        startTime:  new Date(obj.startTime)
+                        minuteTime: new Date(obj.minuteTime)

                 @_seen[obj.key] = 1

@@ -375,7 +377,7 @@
             gRT = setTimeout =>
                 @log.debug "Failed to get rewind buffer response."
                 cb? "Failed to get a rewind buffer response."
-            , 2000
+            , 15000

             # connect to: @master.options.host:@master.options.port


tests: add some missing collectionShell tests

test the `switchShell` and `showView` methods

--- coffee/test/shells/collection.shell.spec.coffee
+++ coffee/test/shells/collection.shell.spec.coffee
@@ -187,6 +187,227 @@

   describe 'CollectionShell.MediaView', ->

+    describe 'MediaView::switchShell', ->
+
+      it 'should be a function', ->
+        expect(typeof MediaView::switchShell).toBe 'function'
+
+      it 'should call `hideView`', ->
+        view = new MediaView viewOptions()
+        spyOn view, 'hideView'
+
+        expect(view.hideView).not.toHaveBeenCalled()
+        view.switchShell 0
+        expect(view.hideView).toHaveBeenCalled()
+
+      it 'should set currentIndex to index', ->
+        view = new MediaView viewOptions()
+        view.currentIndex = 5
+
+        expect(view.currentIndex).toBe 5
+        view.switchShell 0
+        expect(view.currentIndex).toBe 0
+
+      it 'should call `showView`', ->
+        view = new MediaView viewOptions()
+        spyOn view, 'showView'
+
+        expect(view.showView).not.toHaveBeenCalled()
+        view.switchShell 0
+        expect(view.showView).toHaveBeenCalled()
+
+      it 'should call `showView` with index', ->
+        view = new MediaView viewOptions()
+        spyOn view, 'showView'
+
+        expect(view.showView).not.toHaveBeenCalled()
+        view.switchShell 0
+        expect(view.showView).toHaveBeenCalled()
+        expect(view.showView.mostRecentCall.args[0]).toBe 0
+
+      it 'should call `_updateProgressBar`', ->
+        view = new MediaView viewOptions()
+        spyOn view, '_updateProgressBar'
+
+        expect(view._updateProgressBar).not.toHaveBeenCalled()
+        view.switchShell 0
+        expect(view._updateProgressBar).toHaveBeenCalled()
+
+
+    describe 'MediaView::showView', ->
+
+      it 'should be a function', ->
+        expect(typeof MediaView::showView).toBe 'function'
+
+      it 'should access shellView at given index', ->
+        view = new MediaView viewOptions()
+        spyOn(view, 'shellView').andCallThrough()
+
+        expect(view.shellView).not.toHaveBeenCalled()
+        view.showView 0
+        expect(view.shellView).toHaveBeenCalled()
+        expect(view.shellView).toHaveBeenCalledWith 0
+
+      it 'should return shellView at given index', ->
+        view = new MediaView viewOptions()
+        spyOn(view, 'shellView').andCallThrough()
+
+        expect(view.shellView).not.toHaveBeenCalled()
+        shellView = view.showView 0
+        expect(view.shellView).toHaveBeenCalled()
+        expect(view.shellView).toHaveBeenCalledWith 0
+        expect(shellView).toBe view.shellView 0
+
+      it 'should append shellView at given index to $el', ->
+        view = new MediaView viewOptions()
+        view.render()
+        shellView = view.shellView 0
+        shellView.$el.remove()
+
+        expect(shellView.el.parentNode).not.toBe view.el
+        view.showView 0
+        expect(shellView.el.parentNode).toBe view.el
+
+      it 'should show the shellView at given index', ->
+        view = new MediaView viewOptions()
+        view.render()
+        shellView = view.shellView 0
+        shellView.$el.addClass 'hidden'
+
+        expect(shellView.$el.hasClass 'hidden').toBe true
+        view.showView 0
+        expect(shellView.$el.hasClass 'hidden').toBe false
+
+
+    describe 'MediaView::hideView', ->
+
+      it 'should be a function', ->
+        expect(typeof MediaView::hideView).toBe 'function'
+
+      it 'should access shellView at given index', ->
+        view = new MediaView viewOptions()
+        spyOn(view, 'shellView').andCallThrough()
+
+        expect(view.shellView).not.toHaveBeenCalled()
+        view.hideView 0
+        expect(view.shellView).toHaveBeenCalled()
+        expect(view.shellView).toHaveBeenCalledWith 0
+
+      it 'should return shellView at given index', ->
+        view = new MediaView viewOptions()
+        spyOn(view, 'shellView').andCallThrough()
+
+        expect(view.shellView).not.toHaveBeenCalled()
+        shellView = view.hideView 0
+        expect(view.shellView).toHaveBeenCalled()
+        expect(view.shellView).toHaveBeenCalledWith 0
+        expect(shellView).toBe view.shellView 0
+
+      it 'should hide the shellView at given index', ->
+        view = new MediaView viewOptions()
+        view.render()
+        shellView = view.shellView 0
+        shellView.$el.removeClass 'hidden'
+
+        expect(shellView.$el.hasClass 'hidden').toBe false
+        view.hideView 0
+        expect(shellView.$el.hasClass 'hidden').toBe true
+
+      it 'should pause the shellView at given index', ->
+        view = new MediaView viewOptions()
+        view.render()
+        shellView = view.shellView 0
+        spyOn shellView, 'pause'
+
+        expect(shellView.pause).not.toHaveBeenCalled()
+        view.hideView 0
+        expect(shellView.pause).toHaveBeenCalled()
+
+
+    describe 'MediaView::showPrevious', ->
+
+      it 'should be a function', ->
+        expect(typeof MediaView::showPrevious).toBe 'function'
+
+      it 'should pause playback if playOnChangeShell is false', ->
+        view = new MediaView viewOptions playOnChangeShell: false
+        spyOn view, 'pause'
+
+        expect(view.pause).not.toHaveBeenCalled()
+        view.showPrevious()
+        expect(view.pause).toHaveBeenCalled()
+
+      it 'should not pause playback if playOnChangeShell is true', ->
+        view = new MediaView viewOptions playOnChangeShell: true
+        spyOn view, 'pause'
+
+        expect(view.pause).not.toHaveBeenCalled()
+        view.showPrevious()
+        expect(view.pause).not.toHaveBeenCalled()
+
+      it 'should get index of previous shell', ->
+        view = new MediaView viewOptions playOnChangeShell: true
+        spyOn(view, 'correctedIndex').andReturn 0
+        view.currentIndex = 8
+
+        expect(view.correctedIndex).not.toHaveBeenCalled()
+        view.showPrevious()
+        expect(view.correctedIndex).toHaveBeenCalled()
+        expect(view.correctedIndex).toHaveBeenCalledWith 7
+
+      it 'should call switchShell with index of previous shell', ->
+        view = new MediaView viewOptions playOnChangeShell: true
+        spyOn(view, 'correctedIndex').andReturn 'fakeIndex'
+        spyOn view, 'switchShell'
+
+        expect(view.switchShell).not.toHaveBeenCalled()
+        view.showPrevious()
+        expect(view.switchShell).toHaveBeenCalled()
+        expect(view.switchShell.mostRecentCall.args[0]).toBe 'fakeIndex'
+
+
+    describe 'MediaView::showNext', ->
+
+      it 'should be a function', ->
+        expect(typeof MediaView::showNext).toBe 'function'
+
+      it 'should pause playback if playOnChangeShell is false', ->
+        view = new MediaView viewOptions playOnChangeShell: false
+        spyOn view, 'pause'
+
+        expect(view.pause).not.toHaveBeenCalled()
+        view.showNext()
+        expect(view.pause).toHaveBeenCalled()
+
+      it 'should not pause playback if playOnChangeShell is true', ->
+        view = new MediaView viewOptions playOnChangeShell: true
+        spyOn view, 'pause'
+
+        expect(view.pause).not.toHaveBeenCalled()
+        view.showNext()
+        expect(view.pause).not.toHaveBeenCalled()
+
+      it 'should get index of next shell', ->
+        view = new MediaView viewOptions playOnChangeShell: true
+        spyOn(view, 'correctedIndex').andReturn 0
+        view.currentIndex = 8
+
+        expect(view.correctedIndex).not.toHaveBeenCalled()
+        view.showNext()
+        expect(view.correctedIndex).toHaveBeenCalled()
+        expect(view.correctedIndex).toHaveBeenCalledWith 9
+
+      it 'should call switchShell with index of next shell', ->
+        view = new MediaView viewOptions playOnChangeShell: true
+        spyOn(view, 'correctedIndex').andReturn 'fakeIndex'
+        spyOn view, 'switchShell'
+
+        expect(view.switchShell).not.toHaveBeenCalled()
+        view.showNext()
+        expect(view.switchShell).toHaveBeenCalled()
+        expect(view.switchShell.mostRecentCall.args[0]).toBe 'fakeIndex'
+
+
     describe 'MediaView::correctedIndex', ->

       it 'should cycle index if options.shellsCycle', ->


finally

finally

--- app/assets/javascripts/pt/tags/confirm.js.coffee
+++ app/assets/javascripts/pt/tags/confirm.js.coffee
@@ -1,32 +1,70 @@
 window.pt ||= tags: []

 pt.tags.push
-  confirm:
+  confirmDelete:
     event: 'click'
     callback: ->
-      $el  = $ this
+      $el  = $confirmContainer  = $ this
       attr = $el.ptAttr()
-      url  = attr.confirm
+      url  = attr.confirmDelete

-      $el.data 'ptConfirmLoaded', true
+      if attr.confirmContainer
+        $confirmContainer = $el.closest(attr.confirmContainer)

-      buttons = [
-        {
-          label: 'Yes'
-          class: 'btn-primary'
-          callback: ->
-            $.ajax
-              url: url
-              type: 'DELETE'
-              success: ->
-                $('#pt-modal-body').closest('.bootbox').modal('hide')
-        }
-        {
-          label: 'Cancel'
-          callback: -> $('#pt-modal-body').closest('.bootbox').modal('hide')
-        }
-      ]
+      unless $el.data 'ptConfirmLoaded'
+        $el.data 'ptConfirmLoaded', true

-      $bootbox = bootbox.dialog false, buttons, { header: attr.confirmTitle or 'Are you sure?', keyboard: true  }
-      $bootbox.find('.modal-body').remove()
-      $bootbox.find('.modal-footer a:last').removeClass 'btn-primary'
+        $html = $ '<div>',
+          style: 'text-align: center;'
+
+        $deleteButton = $ '<button>',
+          class: 'btn btn-danger delete'
+          style: 'margin-right: 10px;'
+          text: 'Delete'
+        $html.append $deleteButton
+
+        $cancelButton = $ '<button>',
+          class: 'btn cancel'
+          text: 'Cancel'
+        $html.append $cancelButton
+
+        $confirmContainer.on 'click', '.popover-content .delete', ->
+          $.ajax
+            url: url
+            type: 'DELETE'
+            success: ->
+              $confirmContainer.popover 'hide'
+
+        $confirmContainer.on 'click', '.popover-content .cancel', ->
+          $confirmContainer.popover 'hide'
+
+        $confirmContainer.popover
+          content: $html
+          html: true
+          trigger: 'manual'
+          title: attr.confirmTitle or 'Are you sure you want to delete?'
+          placement: attr.confirmPlacement or 'right'
+          container: $confirmContainer
+
+      $confirmContainer.popover 'show'
+#
+#       buttons = [
+#         {
+#           label: 'Yes'
+#           class: 'btn-primary'
+#           callback: ->
+#             $.ajax
+#               url: url
+#               type: 'DELETE'
+#               success: ->
+#                 $('#pt-modal-body').closest('.bootbox').modal('hide')
+#         }
+#         {
+#           label: 'Cancel'
+#           callback: -> $('#pt-modal-body').closest('.bootbox').modal('hide')
+#         }
+#       ]
+#
+#       $bootbox = bootbox.dialog false, buttons, { header: attr.confirmTitle or 'Are you sure?', keyboard: true  }
+#       $bootbox.find('.modal-body').remove()
+#       $bootbox.find('.modal-footer a:last').removeClass 'btn-primary'


Remove unnecessary logging

Remove unnecessary logging

--- files.coffee
+++ files.coffee
@@ -160,13 +160,11 @@

         i = 0
         binary = ''
-        # console.log file.byteLength
         while i < file.byteLength
           binary += String.fromCharCode(file.buffer[i])
           i++

         last = (chunksQty * partsQty <= totalSentChunks)
-        # console.log "LAST", last, chunksQty * partsQty, totalSentChunks

         cleanName = (str) ->
           str.replace(/\.\./g, '').replace /\//g, ''
@@ -193,7 +191,6 @@
           _storagePath:    self.storagePath
           _downloadRoute:  self.downloadRoute

-        # console.log "Last, First", last, first

         if first
           fs.outputFileSync pathPart, binary, 'binary'
@@ -338,10 +335,8 @@
         size: partSize
         part: i
         chunksQty: if @chunkSize < partSize then Math.ceil(partSize / @chunkSize) else 1
-      # console.log "if #{@chunkSize} < #{partSize} then Math.ceil(partSize / @chunkSize) else 1", if @chunkSize < partSize then Math.ceil(partSize / @chunkSize) else 1
       i++

-    # console.log parts

     end = (error, data) ->
       console.timeEnd('insert') if self.debug
@@ -360,16 +355,13 @@

       fileReader.onload = (chunk) ->
         ++totalSentChunks
-        # console.log "currentChunk", currentChunk
         onProgress and onProgress((uploaded / file.size) * 100)
-        # console.log "PROGRESS: ", (uploaded / file.size) * 100

         uploaded   += self.chunkSize
         binary      = chunk.srcElement or chunk.target
         arrayBuffer = new Uint8Array binary.result
         last        = (part is streams and currentChunk >= chunksQtyInPart)

-        # console.log "isLastPart?", last, "#{currentChunk} >= #{chunksQtyInPart}"

         if chunksQtyInPart is 1
           Meteor.call self.methodNames.MeteorFileWrite, arrayBuffer, fileData, meta, first, chunksQtyInPart, currentChunk, totalSentChunks, randFileName, part, streams, file.size, (error, data) ->
@@ -377,19 +369,16 @@
               end error, data
         else
           Meteor.call self.methodNames.MeteorFileWrite, arrayBuffer, fileData, meta, first, chunksQtyInPart, currentChunk, totalSentChunks, randFileName, part, streams, file.size, (error, data)->
-            # console.log "DATA.last", data.last, error, data
             if data.chunk + 1 <= chunksQtyInPart
               from         = currentChunk * self.chunkSize
               to           = from + self.chunkSize

-              # console.log "Send chunk ##{currentChunk} of part ##{part} | #{from} #{to}"
               fileReader.readAsArrayBuffer filePart.slice from, to
               currentChunk = ++data.chunk
             else if data.last
               end error, data
         first = false

-      # console.log "Send first chunk of part"
       fileReader.readAsArrayBuffer filePart.slice 0, self.chunkSize

     # for part, index in parts
@@ -398,9 +387,7 @@
       Meteor.setTimeout ((parts, i) ->
         return () ->
           part = parts[i]
-          # console.log "Upload part#", part
           fileReader = new FileReader
-          # console.log "Slice file to parts: #{index}"
           upload(file.slice(part.from, part.to), i + 1, part.chunksQty, fileReader)
       )(parts, i)
       ,


Override authorNick in manifest with delegate' nickname

--- server/app/bongo/models/app.coffee
+++ server/app/bongo/models/app.coffee
@@ -112,6 +112,8 @@
       if err
         callback err
       else
+        # Override author info with delegate
+        data.manifest.authorNick = delegate.getAt 'profile.nickname'
         data.manifest.author = "#{delegate.getAt 'profile.firstName'} #{delegate.getAt 'profile.lastName'}"

       if app


Fix cursor position in empty indents

Fix cursor position in empty indents

--- src/controller.coffee
+++ src/controller.coffee
@@ -260,7 +260,8 @@
         line++ if head.type is 'newline'

       bound = @view.getViewFor(@cursor.parent).bounds[line]
-      if @cursor.nextVisibleToken()?.type is 'indentEnd' or
+      if @cursor.nextVisibleToken()?.type is 'indentEnd' and
+         @cursor.prev?.prev.type isnt 'indentStart' or
          @cursor.next is @tree.end
         @drawCursor new draw.Point bound.x, bound.bottom()
       else


add any-drop to expansion function blocks, pass block to expansion function

--- src/controller.coffee
+++ src/controller.coffee
@@ -1264,11 +1264,20 @@

       # Substitute in expansion for this palette entry, if supplied.
       expansion = @clickedBlockPaletteEntry.expansion
-      # Call expansion() function with no parameter to get the initial value
+
+      # Call expansion() function with no parameter to get the initial value.
       if 'function' is typeof expansion then expansion = expansion()
       if (expansion) then expansion = parseBlock(@mode, expansion)
       @draggingBlock = (expansion or @draggingBlock).clone()
+
+      # Special @draggingBlock setup for expansion function blocks.
       if 'function' is typeof @clickedBlockPaletteEntry.expansion
+        # Any block generated from an expansion function should be treated as
+        # any-drop because it can change with subsequent expansion() calls.
+        if 'mostly-value' in @draggingBlock.classes
+          @draggingBlock.classes.push 'any-drop'
+
+        # Attach expansion() function and lastExpansionText to @draggingBlock.
         @draggingBlock.lastExpansionText = expansion
         @draggingBlock.expansion = @clickedBlockPaletteEntry.expansion

@@ -1361,38 +1370,37 @@
     # Redraw the main canvas
     @redrawMain()

-Editor::getClosestDroppableBlock = (position) ->
+Editor::getClosestDroppableBlock = (mainPoint) ->
   best = null; min = Infinity

   if not (@dropPointQuadTree)
     return null

   testPoints = @dropPointQuadTree.retrieve {
-    x: position.x - MAX_DROP_DISTANCE
-    y: position.y - MAX_DROP_DISTANCE
+    x: mainPoint.x - MAX_DROP_DISTANCE
+    y: mainPoint.y - MAX_DROP_DISTANCE
     w: MAX_DROP_DISTANCE * 2
     h: MAX_DROP_DISTANCE * 2
   }, (point) =>
     unless (point.acceptLevel is helper.DISCOURAGE) and not event.shiftKey
       # Find a modified "distance" to the point
       # that weights horizontal distance more
-      distance = position.from(point)
+      distance = mainPoint.from(point)
       distance.y *= 2; distance = distance.magnitude()

       # Select the node that is closest by said "distance"
-      if distance < min and position.from(point).magnitude() < MAX_DROP_DISTANCE and
+      if distance < min and mainPoint.from(point).magnitude() < MAX_DROP_DISTANCE and
          @view.getViewNodeFor(point._droplet_node).highlightArea?
         best = point._droplet_node
         min = distance
   best

-Editor::isDragPointOverSocket = (position) ->
+Editor::getClosestDroppableBlockFromPosition = (position) ->
   if not @currentlyUsingBlocks
-    return true
+    return nil

   mainPoint = @trackerPointToMain(position)
-  dropBlock = @getClosestDroppableBlock(mainPoint)
-  return (dropBlock) and dropBlock.type is 'socket'
+  @getClosestDroppableBlock(mainPoint)

 Editor::getAcceptLevel = (drag, drop) ->
   if drop.type is 'socket'
@@ -1421,14 +1429,18 @@
       point.y + @draggingOffset.y
     )

-    # If there is an expansion function, call it again here:
+    # If there is an expansion function, call it again here.
     if (@draggingBlock.expansion)
-      # Call expansion() with the position for all drag moves:
-      expansionText = @draggingBlock.expansion(position)
+      # Call expansion() with the closest droppable block for all drag moves.
+      expansionText = @draggingBlock.expansion(@getClosestDroppableBlockFromPosition(position))
+
+      # Create replacement @draggingBlock if the returned text is new.
       if expansionText isnt @draggingBlock.lastExpansionText
         newBlock = parseBlock(@mode, expansionText)
         newBlock.lastExpansionText = expansionText
         newBlock.expansion = @draggingBlock.expansion
+        if 'any-drop' in @draggingBlock.classes
+          newBlock.classes.push 'any-drop'
         @draggingBlock = newBlock
         @drawDraggingBlock()


Add support for literate programming lidr files

This support is preliminary. Editing files would probably require a dedicated grammar file, but typechecking and basic commands, such as hole resolution and documentation look up work. Programming tasks such as case splitting and adding definitions, require that the output is corrected afterwards.

--- grammars/language-idris.cson
+++ grammars/language-idris.cson
@@ -1,6 +1,6 @@
 name: 'Idris'
 scopeName: 'source.idris'
-fileTypes: ['idr']
+fileTypes: ['idr', 'lidr']
 patterns:
   [
     {


Hide popup after logging in

Hide popup after logging in

--- resources/assets/coffee/_classes/user-login.coffee
+++ resources/assets/coffee/_classes/user-login.coffee
@@ -41,6 +41,7 @@
     $('.js-user-header').html data.header
     $('.js-user-header-popup').html data.header_popup
     $.publish 'user:update', data.user.data
+    @nav.hidePopup()
     osu.pageChange()

     Turbolinks.clearCache()


Resize frame <html>, to have it work on iOS Safari

--- app/js/jail_iframe/classes/control_iframe.coffee
+++ app/js/jail_iframe/classes/control_iframe.coffee
@@ -27,8 +27,14 @@
     @sizeFrameToFitContent()

   sizeFrameToFitContent: ->
+    @frameBody.parentElement.style.width = 'auto'
+
     @el.style.width = @frameBody.clientWidth  + 'px'
     @el.style.height = @frameBody.clientHeight + 'px'
+
+    # Also resize the frame <html> itself for iOS Safari,
+    # which resizes iframes themselves based on their inner size
+    @frameBody.parentElement.style.width = @frameBody.clientWidth  + 'px'

   fadeIn: ->
     @$el.addClass 'factlink-control-visible'


geoip: payload is added to message for logged out pages

--- servers/lib/crawler/helpers.coffee
+++ servers/lib/crawler/helpers.coffee
@@ -125,7 +125,8 @@
     createdAt        : renderCreatedAt activity
     commentCount     : repliesCount
     likeCount        : actorsCount
-
+    payload          : message.payload
+
   queue = [
     ->
       prepareComments models, activity, (err, replies) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -15,6 +15,6 @@
     x: 50
     y: 30

-
+  equals en

 module()


Update taxon after sort

Update taxon after sort

--- backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxonomy.js.coffee
@@ -3,6 +3,15 @@
 get_taxonomy = ->
   Spree.ajax
     url: "#{Spree.routes.taxonomy_path}?set=nested"
+
+update_taxon = ({id, parent_id, child_index}) ->
+  Spree.ajax
+    type: "PUT"
+    dataType: "json"
+    url: "#{Spree.routes.taxonomy_taxons_path}/#{id}"
+    data:
+      taxon: {parent_id, child_index}
+    error: redraw_tree

 draw_tree = (taxonomy) ->
   $('#taxonomy_tree')
@@ -28,6 +37,12 @@
   restore_sort_targets()
   ui.placeholder.parents('ul').addClass('ui-sortable-over')

+handle_move = (el) ->
+  update_taxon
+    id: el.data('taxon-id')
+    parent_id: el.parent().closest('li').data('taxon-id')
+    child_index: el.index()
+
 @setup_taxonomy_tree = (taxonomy_id) ->
   return unless taxonomy_id?
   taxons_template_text = $('#taxons-list-template').text()
@@ -40,3 +55,5 @@
       sortover: (e, ui) ->
         highlight_sort_targets(ui)
       sortstop: restore_sort_targets
+      sortupdate: (e, ui) ->
+        handle_move(ui.item) unless ui.sender?


Detect and prepend http:// in professional buyer form

--- apps/pro_buyer/pages/complete/client/view.coffee
+++ apps/pro_buyer/pages/complete/client/view.coffee
@@ -1,6 +1,7 @@
 Promise = require 'bluebird-q'
 { stringify } = require 'qs'
 { invoke, pick, last } = require 'underscore'
+{ startsWith } = require 'underscore.string'
 { View, Model } = require 'backbone'
 { isPhoneLike } = require '../../../../../components/util/device.coffee'
 analyticsHooks = require '../../../../../lib/analytics_hooks.coffee'
@@ -31,8 +32,14 @@

     # If the URL is the default value; remove
     # the http:// so that it passes validation
-    if ($url = @$('input[name="company_website"]')).val() is 'http://'
+    $url = @$('input[name="company_website"]')
+    urlValue = $url.val()
+    if urlValue is 'http://'
       $url.val ''
+
+    # Prepend with http:// if not blank and missing protocol.
+    else if urlValue isnt '' and not startsWith(urlValue, 'http://')
+      $url.val "http://#{urlValue}"

     form = new Form $form: @$('form')
     return unless form.isReady()


#202 quick and dirty imperative fix for `o` after Vjj then cmd-shift-d place cursor incorrect position.

--- lib/cursor-style-manager.coffee
+++ lib/cursor-style-manager.coffee
@@ -20,6 +20,11 @@
         traversal.column -= 1
     when 'linewise'
       bufferPoint = swrap(selection).getCharacterwiseHeadPosition()
+      # FIXME need to update original getCharacterwiseHeadPosition?
+      # to reflect outer vmp command modify linewise selection?
+      [startRow, endRow] = selection.getBufferRowRange()
+      if selection.isReversed()
+        bufferPoint.row = startRow

       traversal = if isSoftWrapped
         screenPoint = editor.screenPositionForBufferPosition(bufferPoint)


Don't store invalid window dimensions on Windows

--- src/atom.coffee
+++ src/atom.coffee
@@ -248,7 +248,15 @@
     @setWindowDimensions(windowDimensions)

   storeWindowDimensions: ->
-    @state.windowDimensions = @getWindowDimensions()
+    windowDimensions = @getWindowDimensions()
+
+    if process.platform is 'win32'
+      # Work around https://github.com/atom/atom-shell/issues/473
+      # Don't store the window if its dimensions are completely offscreen
+      return if windowDimensions.x + windowDimensions.width <= 0
+      return if windowDimensions.height + windowDimensions.height <= 0
+
+    @state.windowDimensions = windowDimensions

   # Public: Get the load settings for the current window.
   #


(feature) Use open connection for RethinkDB Session.

--- src/auth.coffee
+++ src/auth.coffee
@@ -1,15 +1,35 @@

 session = require 'express-session'

-module.exports = (userLogin) ->
+module.exports = (con, userLogin) ->
   (app, config) ->
-    app.use session(
-      secret: config.session.secret
-      saveUninitialized: true    # FIXME: true? yes/no?
-      resave: true               # FIXME: true? yes/no?
-      cookie:
-        secure: config.ssl.enable
-    )
+    if(con)
+      console.log("using rethinkdb store!")
+      RDBStore = require('express-session-rethinkdb')(session, con);
+
+
+      rDBStore = new RDBStore({
+        table: 'session',
+        sessionTimeout: 1209600, # two weeks
+        flushInterval: 60000
+      });
+
+      app.use session(
+        secret: config.session.secret
+        saveUninitialized: true    # FIXME: true? yes/no?
+        resave: true               # FIXME: true? yes/no?
+        cookie:
+          secure: config.ssl.enable
+        store: rDBStore
+      )
+    else
+      app.use session(
+        secret: config.session.secret
+        saveUninitialized: true    # FIXME: true? yes/no?
+        resave: true               # FIXME: true? yes/no?
+        cookie:
+          secure: config.ssl.enable
+      )

     app.use "/api/login", (req, res) ->
       userLogin(req.body.id, req.body.password).then((correct) ->


Rename build_errors()/build_all_errors() to find_errors()/find_all_errors().

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -84,11 +84,11 @@
   all_expectations.length = 0

 verify_all_expectations = ->
-  errors = build_all_errors()
+  errors = find_all_errors()
   throw new Error( errors ) unless errors == ""

-build_all_errors = ->
-  ( expectation.build_errors() for expectation in all_expectations ).join("")
+find_all_errors = ->
+  ( expectation.find_errors() for expectation in all_expectations ).join("")

 #
@@ -185,7 +185,7 @@
       ( @_args.length == args.length ) and
       ( @_args.every ( element, i ) -> element == args[ i ] )

-  build_errors: ->
+  find_errors: ->
     if not @_called then "'#{@_method_name}(#{@_args})' was never called\n" else ""

   # private


Turn off default up/down scrolling from ScrollView

When https://github.com/atom/atom/pull/2368 was merged, ScrollView
gained the ability to scroll when the up and down arrow keys are
pressed (more precisely, when core:move-up or core:move-down
events are triggered).

Since Results View uses a ScrollView, this means that core:move-up
and core:move-down events both select the previous/next item
in the Results View, and make the Results View scroll up/down.

This change turns off the scrolling behavior by removing the
ScrollView's default event handler for the core:move-up and
core:move-down events.

Fixes #238.

--- lib/project/results-view.coffee
+++ lib/project/results-view.coffee
@@ -12,6 +12,10 @@

     @pixelOverdraw = 100
     @lastRenderedResultIndex = 0
+
+    # turn off default scrolling behavior from ScrollView
+    @off 'core:move-up'
+    @off 'core:move-down'

     @on 'core:move-down', =>
       @selectNextResult()


Only print exit code if non-null

Only print exit code if non-null

Fixes #8.

--- lib/python-debugger-view.coffee
+++ lib/python-debugger-view.coffee
@@ -123,7 +123,7 @@
     @backendDebugger.stderr.on "data", (data) =>
       @processDebuggerOutput(data)
     @backendDebugger.on "exit", (code) =>
-      @addOutput("debugger exits with code: " + code.toString().trim())
+      @addOutput("debugger exits with code: " + code.toString().trim()) if code?

   stopApp: ->
     @backendDebugger?.stdin.write("\nexit()\n")


add extra step for start

add extra step for start

--- slides/triangles/coffee/tri.coffee
+++ slides/triangles/coffee/tri.coffee
@@ -532,7 +532,7 @@
   display = (error, data) ->
     setupSearch(data)
     plotData("#vis", data, plot)
-    plot.start()
+    #plot.start()

   queue()
     # .defer(d3.tsv, "data/color_palettes_rgb.txt")


Fix typos.

Fix typos.

--- common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -72,7 +72,7 @@
       data['src'] = rewriteStaticLinks(data['src'], @base_asset_url, '/static/')

   saveImage: (data) =>
-    # Called when the image plugin in saved. Input arg is the JSON version of the image data.
+    # Called when the image plugin is saved. Input arg is the JSON version of the image data.
     if data['src']
       data['src'] = rewriteStaticLinks(data['src'], '/static/', @base_asset_url)

@@ -82,12 +82,12 @@
       data['href'] = rewriteStaticLinks(data['href'], @base_asset_url, '/static/')

   saveLink: (data) =>
-    # Called when the link plugin in saved. Input arg is the JSON version of the link data.
+    # Called when the link plugin is saved. Input arg is the JSON version of the link data.
     if data['href']
       data['href'] = rewriteStaticLinks(data['href'], '/static/', @base_asset_url)

   showCodeEditor: (codeEditor) =>
-    # Called when the CodeMirror Editor is displayed to convert links to show satic prefix.
+    # Called when the CodeMirror Editor is displayed to convert links to show static prefix.
     # The input argument is the CodeMirror instance.
     content = rewriteStaticLinks(codeEditor.getValue(), @base_asset_url, '/static/')
     codeEditor.setValue(content)


removed no-longer needed -statePropertyKeyTest

--- src/art/engine/core/epoched_object.coffee
+++ src/art/engine/core/epoched_object.coffee
@@ -16,7 +16,6 @@
 {propInternalName} = BaseObject
 blankOptions = {}
 {stateEpoch} = StateEpoch
-statePropertyKeyTest = /^_[a-z].*$/    # anything with an underscore then letter at the beginning

 module.exports = class EpochedObject extends BaseObject
   @propsEq: propsEq = plainObjectsDeepEq
@@ -409,7 +408,7 @@
     ret

   _getChangingStateKeys: ->
-    k for k, v of @_pendingState when statePropertyKeyTest.test(k) && (!shallowPropsEq @[k], @_pendingState[k])
+    k for k, v of @_pendingState when !shallowPropsEq @[k], @_pendingState[k]

   _logPendingStateChanges: ->
     oldValues = {}


Fixes #7. We now ignore bots unless asked not to.

--- src/hubot-slack-relay.coffee
+++ src/hubot-slack-relay.coffee
@@ -19,7 +19,9 @@
 module.exports = (robot) ->

   BRAIN_KEY = 'hubot-slack-relay.storage'
+  IGNORE_BOTS = process.env.IGNORE_BOTS or true
   relays = []
+  client = robot.adapter.client

   brainLoaded = () =>
     #load brain data
@@ -181,6 +183,8 @@

   robot.hear /(.+)/i, (res) =>

+    if IGNORE_BOTS and  client.getUserByID(res.message.user.id).is_bot then return
+
     for relay in relays
       if res.message.room isnt relay.localRoom then continue
       user = { room: relay.remoteRoom }
@@ -188,6 +192,9 @@
         invalidChannelError relay

   robot.enter (res) ->
+
+    if IGNORE_BOTS and  client.getUserByID(res.message.user.id).is_bot then return
+
     for relay in relays
       if res.message.room isnt relay.localRoom then continue
       user = { room: relay.remoteRoom }
@@ -195,6 +202,9 @@
         invalidChannelError relay

   robot.leave (res) ->
+
+    if IGNORE_BOTS and  client.getUserByID(res.message.user.id).is_bot then return
+
     for relay in relays
       if res.message.room isnt relay.localRoom then continue
       user = { room: relay.remoteRoom }


Use the kodingAppsController's approveApp method before marking the app as approved

--- client/app/Applications/Apps.kdapplication/Views/appview.coffee
+++ client/app/Applications/Apps.kdapplication/Views/appview.coffee
@@ -31,6 +31,8 @@
             callback? err
       ]
     , app
+
+    appsController = @getSingleton("kodingAppsController")

     if KD.checkFlag 'super-admin'
       @approveButton = new KDToggleButton
@@ -39,8 +41,13 @@
         defaultState    : if app.approved then "Disapprove" else "Approve"
         states          : [
           "Approve", (callback)->
-            app.approve yes, (err)->
-              callback? err
+            appsController.approveApp app, (err)=>
+              if not err
+                app.approve yes, (err)->
+                  if err then warn err
+                  callback? err
+              else
+                callback? err
           "Disapprove", (callback)->
             app.approve no, (err)->
               callback? err
@@ -55,7 +62,6 @@
       else
         @likeButton.setState "Like"

-    appsController = @getSingleton("kodingAppsController")

     if app.versions?.length > 1
       menu =


Fix vim fat-finger

Fix vim fat-finger

--- src/backburner.coffee
+++ src/backburner.coffee
@@ -165,10 +165,10 @@
         @thisTask.rejectWith this

 # https://developer.mozilla.org/en/javascript/reference/global_objects/array/foreach
-backburner.foreach = (v, bodyfn, context) ->
+backburner.forEach = (v, bodyfn, context) ->
     context ?= {}
     context._v = v
-    context._foreachbodyfn = bodyfn
+    context._forEachBodyFn = bodyfn
     if _.isArray v
         p = backburner['for'] forEachArraySetupFn,
             forEachArrayTestFn,