SIdebarMachineSharePopup: Minor PR fixes.

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -137,15 +137,15 @@
       { router, mainView } = kd.singletons

       doNavigation = =>
-        route = "/IDE/#{machine.uid}/my-workspace" # permanent shared route
-
-        unless machine.isPermanent() # collaboration route
+        if machine.isPermanent()
+          route = "/IDE/#{machine.uid}/my-workspace" # permanent shared route
+        else # collaboration route
           route = "/IDE/#{@getOptions().channelId}"

         # route to permanent shared url to open the ide
         router.handleRoute route

-        # defer sidebar redraw sidebar to properly select workspace
+        # defer sidebar redrawing to properly select workspace
         kd.utils.defer =>
           mainView.activitySidebar.redrawMachineList()
           @destroy()


client: stackwizard: main modal markup changes

--- client/stacks/lib/views/stacks/onboarding/onboardingview.coffee
+++ client/stacks/lib/views/stacks/onboarding/onboardingview.coffee
@@ -233,10 +233,10 @@
       {{> @providerSelectionView}}
       {{> @configurationView}}
       {{> @codeSetupView}}
-      <div class="footer">
+      <footer>
         {{> @backButton}}
         {{> @nextButton}}
         {{> @skipLink}}
-      </div>
-      {{> @stackPreview}}
+        {{> @cancelButton}}
+      </footer>
     '''


Use addEventListener to wait for the document to load instead of overwriting document.onreadystatechange. Otherwise when using multiple Caman instances before the page fully loads, only the last executes. Fixes issue #41.

--- src/core/camaninstance.coffee
+++ src/core/camaninstance.coffee
@@ -31,10 +31,10 @@
       if document.readyState is "complete"
         @domLoaded(args, type)
       else
-        # FIXME This overwrite behavior is bad, root cause of issue #41
-        document.onreadystatechange = =>
+        listener = =>
           if document.readyState is "complete"
             @domLoaded(args, type)
+        document.addEventListener "readystatechange", listener, false

   domLoaded: (args, type) ->
     # Begin initialization


Better element selection on scroll.

Better element selection on scroll.

--- foreground.coffee
+++ foreground.coffee
@@ -246,7 +246,7 @@

     (event, clear = false) ->
       # Figure out the direction of the last scroll.  If we're at or close to the top of the window, then we
-      # assume "down".
+      # just assume "down".
       direction = if previousPageYOffset < pageYOffset or pageYOffset < 100 then "down" else "up"
       previousPageYOffset = pageYOffset
       cancel()
@@ -255,8 +255,8 @@
           timer = null
           if @element and not Common.isInViewport @element
             @clearSelection()
-          if not @element and element = @getElements(direction)[0]
-            if Common.isInViewport element
+          if not @element
+            if element = (ele for ele in @getElements direction when Common.isInViewport ele)[0]
               @selectElement element, false

 Wrapper =


change JCredentialData target type from string to object reference due to failing in bongo

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -95,7 +95,7 @@
     relationships     :

       data            :
-        targetType    : 'JCredentialData'
+        targetType    : JCredentialData
         as            : 'data'

   @getName = -> 'JCredential'


account: assign payment label to instance variable

--- client/app/lib/avatararea/accountpopup.coffee
+++ client/app/lib/avatararea/accountpopup.coffee
@@ -14,8 +14,8 @@

     { groupsController } = kd.singletons

-    @avatarPopupContent.addSubView new CustomLinkView
-      title      : 'Upgrade plan'
+    @avatarPopupContent.addSubView @paymentActionLabel = new CustomLinkView
+      title      : 'Upgrade Plan'
       href       : '/Pricing'
       cssClass   : 'bottom-separator'
       click      : @bound 'goToPricing'


Hide button loader when update process is failed

--- client/app/lib/payment/creditcardmodal.coffee
+++ client/app/lib/payment/creditcardmodal.coffee
@@ -56,7 +56,10 @@

   initEvents: ->

-    @on 'CreditCardUpdateFailed', showError
+    @on 'CreditCardUpdateFailed', (err) =>
+      @submitButton.hideLoader()
+      showError err
+
     @on 'CreditCardUpdateSucceeded', @bound 'handleSuccess'
     @on 'GotValidationResult', @bound 'handleValidationResult'
     @on 'FailedAttemptLimitReached', @bound 'handleLimitReached'


[master] read all symbols

[master] read all symbols

--- merger/compact.coffee
+++ merger/compact.coffee
@@ -13,7 +13,6 @@
 sync = ->
   symbolList = do ->
     JSON.parse fs.readFileSync('../data/sse_50.json', 'ascii')
-  symbolList = ['600036']
   database.redis.getConnection 0
   .then (keyRedis) ->
     database.redis.getConnection 2


Add Jasmine specs to test various injection merge scenarios.

--- spec/coffee/Deft/mixin/Injectable.coffee
+++ spec/coffee/Deft/mixin/Injectable.coffee
@@ -25,5 +25,231 @@
 		return
 	)

+
+	it( 'should should merge subclass injections with parent class injections', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier2", "identifier1" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+			inject: [ 'identifier2' ]
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass' )
+
+		return
+	)
+
+	it( 'should should merge 3 levels of inherited injections', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier3", "identifier4", "identifier2", "identifier1" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+			inject: [ 'identifier2' ]
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+			inject: [ 'identifier3', 'identifier4' ]
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
+	it( 'should should merge 3 levels of inherited injections when 2nd level object has no injections', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier2", "identifier3", "identifier1" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+			inject: [ 'identifier2', 'identifier3' ]
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
+	it( 'should should merge 3 levels of inherited injections when only 3nd level object has injections', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier1", "identifier2" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+			inject: [ 'identifier1', 'identifier2' ]
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
+	it( 'should should merge 3 levels of inherited injections when only top level object has injections', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1', 'identifier2' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier1", "identifier2" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
+	it( 'should should merge inherited injections when some injections are string lists', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier2", "identifier3", "identifier1" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+			inject: 'identifier2,identifier3'
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
+	it( 'should should merge inherited injections when some injections are objects', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "identifier2", "identifier3", "identifier1" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+			inject:
+				identifier2: 'identifier2'
+				identifier3: 'identifier3'
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
+	it( 'should should allow child injections into a property to override parent injections into that property', ->
+		Ext.define( 'ExampleClass',
+			mixins: [ 'Deft.mixin.Injectable' ]
+			inject: [ 'identifier1', 'identifier2', 'identifier3' ]
+
+			constructor: ->
+				expect( Deft.Injector.inject ).toHaveBeenCalledWith( @inject, @, false )
+				expect( Ext.Object.getValues( @inject ) ).toEqual( [ "overriddenIdentifier1", "overriddenIdentifier2", "identifier4", "identifier3" ] )
+		)
+
+		Ext.define( 'ExampleSubClass',
+			extend: 'ExampleClass'
+
+		)
+
+		Ext.define( 'ExampleSubClass2',
+			extend: 'ExampleSubClass'
+			inject:
+				identifier1: 'overriddenIdentifier1'
+				identifier2: 'overriddenIdentifier2'
+				identifier4: 'identifier4'
+
+		)
+
+		spyOn( Deft.Injector, 'inject' ).andCallFake( -> return )
+
+		exampleInstance = Ext.create( 'ExampleSubClass2' )
+
+		return
+	)
+
 	return
+
 )


remove pistachios from kd: WIP

remove pistachios from kd: WIP

--- client/ide/filetree/itemviews/fileitem.coffee
+++ client/ide/filetree/itemviews/fileitem.coffee
@@ -1,4 +1,6 @@
 class NFileItemView extends KDCustomHTMLView
+
+  JView.mixin @prototype

   loaderRequiredEvents = ['job', 'remove', 'save', 'saveAs']


Set null as channel id when session is over

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -734,6 +734,7 @@
       envHelpers.detachSocialChannel @workspaceData, (err) =>
         throwError err  if err

+    @workspaceData.channelId = null
     callback()


Fixed detecting if response is json (#21)

The is_json check looked at each key and evaluated if the current key was `Content-Type` and had `json` in it. However, since it was looping over all the keys at the same time then `is_json` would get overwritten since at some point there would be another header which wasn't `Content-Type` and the check would fail.

--- APIBlueprintGenerator.coffee
+++ APIBlueprintGenerator.coffee
@@ -22,7 +22,8 @@
     is_json = false
     for key, value of exchange.responseHeaders
       if key in ['Content-Type', 'Connection', 'Date', 'Via', 'Server', 'Content-Length']
-        is_json = (key == 'Content-Type' && value.search(/(json)/i) > -1)
+        if key == 'Content-Type'
+          is_json = value.search(/(json)/i) > -1
         continue

       headers.push({ key: key, value: value })


refactor(macSpinner): Switched to use $animate

--- src/directives/spinner.coffee
+++ src/directives/spinner.coffee
@@ -10,7 +10,7 @@
 @param {String}  mac-spinner-color Color of all the bars (default #2f3035)
 ###

-angular.module("Mac").directive "macSpinner", ["util", (util) ->
+angular.module("Mac").directive "macSpinner", ["$animate", "util", ($animate, util) ->
   restrict: "E"
   replace:  true
   template: """

"""
@@ -32,7 +32,7 @@
       styl[vendor(bar[0], "transform")] = "rotate(#{degree}deg) translate(0, 130%)"
       bar.css styl

-      element.append bar
+      $animate.enter bar, element

     ($scope, element, attrs) ->
       defaults =


Form

Form

- Fixed form.right output (also its deprecated, it should still work).

--- src/elements/Form/Form.coffee
+++ src/elements/Form/Form.coffee
@@ -595,7 +595,7 @@
 					console.error("Form.renderTable: form.right is deprecated. Use 'hint' instead. Form:", @, "Field:", field, "Field#", field_idx)
 					# append deprecated stuff to the hint div
 					# you should use ".hint" instead
-					append(get_append(form._form.right), hint_div)
+					append(get_append(field._form.right), hint_div)

 			if field instanceof Form and field.renderAsBlock()


fix route when updating of quantity

fix route when updating of quantity

--- app/assets/javascripts/spree/backend/spree_subscriptions.js.coffee
+++ app/assets/javascripts/spree/backend/spree_subscriptions.js.coffee
@@ -53,12 +53,11 @@

   false

-subscriptionItemURL = (subscription_item_id) ->
-  #url = Spree.pathFor('api/subscription') + "/" + subscription_id + "/subscription_items/" + subscription_item_id + ".json"
-  url = Spree.pathFor('api/subscription_items') + "/" + subscription_item_id + ".json"
+subscriptionItemURL = (subscription_id, subscription_item_id) ->
+  url = Spree.pathFor('api/subscriptions/' + subscription_id + '/subscription_items/' + subscription_item_id)

 adjustSubscriptionItem = (subscription_item_id, quantity) ->
-  url = subscriptionItemURL(subscription_item_id)
+  url = subscriptionItemURL(subscription_id, subscription_item_id)
   $.ajax(
     type: "PUT",
     url: Spree.url(url),


Remove Fusion from artwork model.

Remove Fusion from artwork model.

--- models/artwork.coffee
+++ models/artwork.coffee
@@ -18,7 +18,7 @@
   _.extend @prototype, ArtworkHelpers

   urlRoot: ->
-    "#{sd.FUSION_URL or sd.API_URL}/api/v1/artwork"
+    "#{sd.API_URL}/api/v1/artwork"

   bidSuccessUrl: -> "#{@href()}/confirm-bid"


Support images/Markdown

Support images/Markdown

--- models/auction.coffee
+++ models/auction.coffee
@@ -1,7 +1,14 @@
 _ = require 'underscore'
+{ Markdown, Image } = require 'artsy-backbone-mixins'
+{ SECURE_IMAGES_URL } = require('sharify').data
 Sale = require './sale.coffee'
+ImageSizes = require './mixins/image_sizes.coffee'

 module.exports = class Auction extends Sale
+  _.extend @prototype, Markdown
+  _.extend @prototype, Image(SECURE_IMAGES_URL)
+  _.extend @prototype, ImageSizes
+
   href: ->
     "/auction/#{@id}"


distinct keys of multiple objects; build flat representation of struct keys devided by dots; helper for logging all args of a callback fn

--- tools/objects.coffee
+++ tools/objects.coffee
@@ -36,8 +36,26 @@
     ##
     ##
     ##
-    keys: (obj)->
-        return (key for key of obj)
+    keys: (objs...)->
+        ## empty
+        if !objs.length
+            return []
+
+        ## only one -> return its keys
+        if objs.length = 1
+            return (key for key of objs[0])
+
+        ## many -> concat distinct
+        keys = objects.keys objs.pop()
+
+        if objs.length
+            for obj in objs
+                for key in objects.keys obj # length 1 recursion
+                    if keys.indexOf(key) is -1
+                        keys.push key
+
+        return keys
+

     ##
@@ -86,6 +104,23 @@

         ##
         next()
+
+
+    ##
+    ##
+    ##
+    flatten: (obj, map, prefix)->
+        map ?= {}
+
+        if obj
+            for k, v of obj
+                key = if prefix then prefix+'.'+k else k
+                if floyd.tools.objects.isObject v
+                    objects.flatten v, map, key
+                else
+                    map[key] = v
+
+        return map

     ##
     ##
@@ -558,6 +593,17 @@
         stream.on 'end', ()->
             fn null, Buffer.concat(data, length), stream

+    ##
+    ##
+    ##
+    argsLoggingCallback: (fn, logger)->
+        logger ?= console
+        return (args...)->
+
+            for arg in args
+                logger.log arg
+            fn.apply null, args
+
 ##
 ##
 ##


Changed tagline for real this time

Changed tagline for real this time

--- docpad.coffee
+++ docpad.coffee
@@ -3,7 +3,7 @@
   templateData:
     site:
       title: 'Tank Studios'
-      tagline: 'Walking with Jesus in a Modern World'
+      tagline: 'Following Jesus in a Modern World'
       description: 'Blogging about my walk with Jesus in a modern world'
       logo: '/img/logo.ico'
       url: 'http://blog.tankstudios.net/'


Added log backup in localStorage (just in case).

--- src/log.coffee
+++ src/log.coffee
@@ -13,7 +13,10 @@
         @save()

     save: ->
-        fs.writeFile 'log', JSON.stringify(@entries), (err)->
+        str_entries = JSON.stringify(@entries)
+        #saving backup
+        localStorage.myoulog_backup = str_entries
+        fs.writeFile 'log', str_entries, (err)->
             if err then console.log err
             console.log 'The file was saved!'

@@ -55,6 +58,8 @@
             # Reading from localStorage if log file doesn't exist.
             console.log 'Reading deprecated log from localStorage. \nIt will be cleared after this operation.'
             old_log = (localStorage.myoulog? and JSON.parse(localStorage.myoulog)) or []
+            # moved to myoulog_backup to save it in case of loose your log
+            localStorage.myoulog_backup = localStorage.myoulog
             localStorage.removeItem 'myoulog'
             # It will also save the log file.
             log.add_multiple_entries old_log


Add support for Dyalog APL namespace tokens

Fixes #6.

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -286,6 +286,8 @@
 			{match: "∇",       name: "keyword.operator.nabla.apl"}
 			{match: "⍫",       name: "keyword.operator.lock.apl"}
 			{match: "⎕",       name: "keyword.operator.quad.apl"}
+			{match: "##",      name: "constant.language.namespace.parent.apl"}
+			{match: "#",       name: "constant.language.namespace.root.apl"}

 			# Unused


improve pattern

improve pattern

--- grammars/nsL.cson
+++ grammars/nsL.cson
@@ -24,7 +24,7 @@
     name: "keyword.nsl"
   }
   {
-    match: "^\\s*(?i)(uninstall\\s)?(function|page|section)\\b"
+    match: "^\\s*(?i)(uninstall\\s+)?(function|page|section)\\b"
     name: "keyword.control.nsl"
   }
   {


Update copy task to use variables and new naming scheme

--- grunt/copy.coffee
+++ grunt/copy.coffee
@@ -8,7 +8,7 @@
       expand: true,
       cwd: 'assets',
       src: ['*/**'],
-      dest: 'build/debug/assets',
+      dest: '<%= buildDebug %>/assets',
       filter: (filepath) ->
         not ((filepath.indexOf(path.sep + 'scenes') >= 0) || (filepath.indexOf(path.sep + 'prefabs') >= 0))
     }]
@@ -16,12 +16,12 @@
     files: [{
       expand: true,
       src: ['favicon.ico', '*.png'],
-      dest: 'build/debug/'
+      dest: '<%= buildDebug %>/'
     }]
-  production:
+  release:
     files: [{
       expand: true
-      cwd: 'build/debug/'
+      cwd: '<%= buildDebug %>/'
       src: ['*/**']
-      dest: 'build/production/'
+      dest: '<%= buildRelease %>/'
     }]


Update Atom configurations

Update Atom configurations

- use soft tab explicitly
- set JSX indent size to 2 in Atom beautify

--- .atom/config.cson
+++ .atom/config.cson
@@ -5,6 +5,8 @@
     html:
       indent_size: 2
     js:
+      indent_size: 2
+    jsx:
       indent_size: 2
   "atom-typescript": {}
   "autoclose-html":
@@ -57,6 +59,7 @@
     invisibles: {}
     preferredLineLength: 120
     showInvisibles: true
+    tabType: "soft"
   "exception-reporting":
     userId: "757f6ce6-cf27-cab7-5d9f-ec17d5a39efd"
   "git-history":


atom: update to atom 1.0

atom: update to atom 1.0

--- .atom/config.cson
+++ .atom/config.cson
@@ -2,15 +2,10 @@
   editor:
     fontSize: 12
     tabLength: 4
-    invisibles:
-      {}
+    invisibles: {}
     showInvisibles: true
     preferredLineLength: 100
   core:
-    themes: [
-      "one-dark-ui"
-      "solarized-dark-syntax"
-    ]
     audioBeep: false
     disabledPackages: [
       "jshint"
@@ -39,16 +34,12 @@
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
-  "atom-fuzzy-grep":
-    {}
+  "atom-fuzzy-grep": {}
   "vim-mode":
     useClipboardAsDefaultRegister: true
-  "file-explorer":
-    {}
-  "git-blame-plus":
-    {}
-  "git-blame":
-    {}
+  "file-explorer": {}
+  "git-blame-plus": {}
+  "git-blame": {}
   "linter-jscs":
     onlyConfig: true
     messageType: "warning"


Don't include tag prefix and suffix

Don't include tag prefix and suffix

Just include the tag name in the symbols view
instead of the lengthy namespace prefix and signature
suffix.

Refs #134

--- src/tag-generator.coffee
+++ src/tag-generator.coffee
@@ -6,40 +6,13 @@

   constructor: (@path, @callback) ->

-  parsePrefix: (section = "") ->
-    if section.indexOf('class:') is 0
-      section.substring(6)
-    else if section.indexOf('namespace:') is 0
-      section.substring(10)
-    else if section.indexOf('file:') is 0
-      section.substring(5)
-    else if section.indexOf('signature:') is 0
-      section.substring(10)
-    else if section.indexOf('struct:') is 0
-      section.substring(7)
-    else
-      section
-
   parseTagLine: (line) ->
     sections = line.split('\t')
-    return null if sections.length < 4
-
-    label = sections[0]
-    line = parseInt(sections[2]) - 1
-    if sections.length > 5
-      if prefix = @parsePrefix(sections[4])
-        label = "#{prefix}::#{label}"
-      if suffix = @parsePrefix(sections[5])
-        label = "#{label}#{suffix}"
+    if sections.length > 3
+      position: new Point(parseInt(sections[2]) - 1)
+      name: sections[0]
     else
-      if suffix = @parsePrefix(sections[4])
-        label = "#{label}#{suffix}"
-
-    tag =
-      position: new Point(line, 0)
-      name: label
-
-    return tag
+      null

   generate: ->
     options =


make debug logging much safer

make debug logging much safer

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -405,7 +405,7 @@
                             socket.on 'mesg', (type, mesg) =>
                                 if type == 'json'
                                     if mesg.event == 'project_state_update'
-                                        winston.debug("state_update #{misc.to_json(mesg)}")
+                                        winston.debug("state_update #{misc.to_safe_str(mesg)}")
                                         p = @_project_cache[mesg.project_id]
                                         if p? and p.host == opts.host  # ignore updates from wrong host
                                             p._state      = mesg.state
@@ -416,7 +416,7 @@
                                             if STATES[mesg.state].stable
                                                 p.emit('stable', mesg.state)
                                     else
-                                        winston.debug("mesg (hub <- #{opts.host}): #{misc.to_json(mesg)}")
+                                        winston.debug("mesg (hub <- #{opts.host}): #{misc.to_safe_str(mesg)}")
                             cb()
         ], (err) =>
             opts.cb(err, @_socket_cache[opts.host])
@@ -1399,7 +1399,7 @@
         if opts.bwlimit
             args.push('--bwlimit')
             args.push(opts.bwlimit)
-        dbg("created args=#{misc.to_json(args)}")
+        dbg("created args=#{misc.to_safe_str(args)}")
         target_project = undefined
         async.series([
             (cb) =>
@@ -1490,7 +1490,7 @@
                         args.push("--time")
                     for k in ['path', 'start', 'limit']
                         args.push("--#{k}"); args.push(opts[k])
-                    dbg("get listing of files using options #{misc.to_json(args)}")
+                    dbg("get listing of files using options #{misc.to_safe_str(args)}")
                     @_action
                         action : 'directory_listing'
                         args   : args
@@ -1805,7 +1805,7 @@
         args    : required
         timeout : TIMEOUT
         cb      : required
-    winston.debug("smc_compute: running #{misc.to_json(opts.args)}")
+    winston.debug("smc_compute: running #{misc.to_safe_str(opts.args)}")
     misc_node.execute_code
         command : "sudo"
         args    : ["#{process.env.SALVUS_ROOT}/scripts/smc_compute.py", "--btrfs", BTRFS, '--bucket', BUCKET, '--archive', ARCHIVE].concat(opts.args)
@@ -1813,8 +1813,8 @@
         bash    : false
         path    : process.cwd()
         cb      : (err, output) =>
-            #winston.debug(misc.to_json(output))
-            winston.debug("smc_compute: finished running #{misc.to_json(opts.args)} -- #{err}")
+            #winston.debug(misc.to_safe_str(output))
+            winston.debug("smc_compute: finished running #{misc.to_safe_str(opts.args)} -- #{err}")
             if err
                 if output?.stderr
                     opts.cb(output.stderr)
@@ -1944,7 +1944,7 @@
         if opts.args?
             args = args.concat(opts.args)
         args.push(@project_id)
-        dbg("args=#{misc.to_json(args)}")
+        dbg("args=#{misc.to_safe_str(args)}")
         smc_compute
             args    : args
             timeout : opts.timeout
@@ -1963,7 +1963,7 @@
             args       : undefined
             cb         : undefined
             after_command_cb : undefined   # called after the command completes (even if it is long)
-        dbg = @dbg("command(action=#{opts.action}, args=#{misc.to_json(opts.args)})")
+        dbg = @dbg("command(action=#{opts.action}, args=#{misc.to_safe_str(opts.args)})")
         state = undefined
         state_info = undefined
         assigned   = undefined
@@ -1973,7 +1973,7 @@
                 dbg("get state")
                 @state
                     cb: (err, s) =>
-                        dbg("got state=#{misc.to_json(s)}, #{err}")
+                        dbg("got state=#{misc.to_safe_str(s)}, #{err}")
                         if err
                             opts.after_command_cb?(err)
                             cb(err)
@@ -1989,7 +1989,7 @@
                     opts.args.shift()
                 state_info = STATES[state]
                 if not state_info?
-                    err = "bug / internal error -- unknown state '#{misc.to_json(state)}'"
+                    err = "bug / internal error -- unknown state '#{misc.to_safe_str(state)}'"
                     dbg(err)
                     opts.after_command_cb?(err)
                     cb(err)


storage: working on the backup system

storage: working on the backup system

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -1676,19 +1676,27 @@
         cb      : (err, results) ->
             if err
                 cb(err); return
-            topology = {}
-            for r in results
-                datacenter = r[0]; host = r[1]; vnodes = r[2]
-                if not topology[datacenter]?
-                    topology[datacenter] = {}
-                topology[datacenter][host] = {vnodes:vnodes}
-                all_hosts.push(host)
-                host_to_datacenter[host] = datacenter
-            winston.debug(misc.to_json(topology))
-            hashrings = {}
-            for dc, obj of topology
-                hashrings[dc] = new HashRing(obj)
-            cb?()
+            init_hashrings_2(results, cb)
+
+init_hashrings_2 = (results, cb) ->
+    topology = {}
+    for r in results
+        datacenter = r[0]; host = r[1]; vnodes = r[2]
+        if not topology[datacenter]?
+            topology[datacenter] = {}
+        topology[datacenter][host] = {vnodes:vnodes}
+        all_hosts.push(host)
+        host_to_datacenter[host] = datacenter
+    winston.debug(misc.to_json(topology))
+    hashrings = {}
+    for dc, obj of topology
+        hashrings[dc] = new HashRing(obj)
+    cb?()
+
+# TODO -- hard coding results from above so backup node doesn't need DB access yet!
+exports.init2 = (cb) ->
+    results = [["0","10.1.1.4",256],["0","10.1.2.4",256],["0","10.1.3.4",256],["0","10.1.4.4",256],["0","10.1.5.4",256],["0","10.1.6.4",256],["0","10.1.7.4",256],["2","10.3.1.4",256],["2","10.3.2.4",256],["2","10.3.3.4",256],["2","10.3.4.4",256],["1","10.1.10.4",256],["1","10.1.11.4",256],["1","10.1.12.4",256],["1","10.1.13.4",256],["1","10.1.14.4",256],["1","10.1.15.4",256],["1","10.1.16.4",256],["1","10.1.17.4",256],["1","10.1.18.4",256],["1","10.1.19.4",256],["1","10.1.20.4",256],["1","10.1.21.4",256]]
+    init_hashrings_2(results, cb)

 exports.locations = locations = (opts) ->
     opts = defaults opts,
@@ -1839,6 +1847,7 @@
                 # Now make array of the elements in d that have this newest version
                 # choose one at random.  This way if one machine is down/busted once,
                 # there is a chance we will hit another one next time.
+                # TODO: really we should try each target in turn, from best to worst, until one succeeds.
                 d2 = (x for x in d when x.version == dest.version)
                 dest = d2[misc.randint(0,d2.length-1)]

@@ -2352,6 +2361,137 @@

 ###
+# Backup -- backup all projects to a single zpool.
+###
+
+exports.backup_project = backup_project = (opts) ->
+    opts = defaults opts,
+        project_id : required
+        remote     : undefined
+        cb         : undefined
+    dbg = (m) -> winston.debug("backup_project(project_id:'#{opts.project_id}'): #{m}")
+    dbg("backup the project with given id from some remote server to the local projects zpool.")
+    if not opts.remote?
+        # We try each of the locations that have the project until one works.
+        done = false
+        f = (remote, cb) ->
+            if done
+                cb()
+            else
+                backup_project
+                    project_id : opts.project_id
+                    remote     : remote
+                    cb         : (err) ->
+                        if not err
+                            done = true
+                        cb()
+        remotes = _.flatten(locations(project_id:opts.project_id))
+        async.mapSeries remotes, f, () ->
+            if done
+                opts.cb()
+            else
+                opts.cb("unable to make a backup using any replica")
+    else
+        f                = filesystem(opts.project_id)
+        local_snapshots  = undefined
+        remote_snapshots = undefined
+        remote_base      = undefined
+        tmp              = undefined
+        clean_up         = false
+        async.series([
+            (cb) ->
+                dbg("determine local snapshots")
+                misc_node.execute_code
+                    command     : "sudo"
+                    args        : ['zfs', 'list', '-r', '-t', 'snapshot', '-o', 'name', '-s', 'creation', f]
+                    timeout     : 60
+                    err_on_exit : true
+                    cb          : (err, output) ->
+                        if err
+                            if output?.stderr? and output.stderr.indexOf('not exist') != -1
+                                dbg("no local snapshots yet")
+                                local_snapshots = []
+                                cb()
+                            else
+                                cb(err)
+                        else
+                            v = output.stdout.split('\n')
+                            n = f.length + 1
+                            local_snapshots = (a.slice(n) for a in v when a.slice(n))
+                            dbg("local_snapshots = #{misc.to_json(local_snapshots).slice(0,300)}...")
+                            cb()
+            (cb) ->
+                dbg("determine remote snapshots")
+                execute_on
+                        host    : opts.remote
+                        command : "sudo zfs list -r -t snapshot -o name -s creation #{f}"
+                        timeout : 300
+                        cb      : (err, output) ->
+                            if err
+                                cb(err)
+                            else
+                                dbg("getting remote_snapshots")# -- output=#{misc.to_json(output)}")
+                                v = output.stdout.split('\n')
+                                n = f.length + 1
+                                remote_snapshots = (a.slice(n) for a in v  when a.slice(n))
+                                dbg("remote_snapshots = #{misc.to_json(remote_snapshots).slice(0,300)}...")
+                                cb()
+            (cb) ->
+                dbg("determining replication strategy")
+                i = local_snapshots.length
+                if i == 0
+                    dbg("just get the full remote")
+                    cb()
+                else
+                    if remote_snapshots.indexOf(local_snapshots[i-1]) == -1
+                        dbg("the remote snapshot list doesn't include the newest local snapshot")
+                        # We do *NOT* rollback, since the whole point of the backup system is
+                        # to make something that can't lose history, despite what might happen
+                        # on any compute machine.
+                        cb("clone strategy to deal with forks not implemented")
+                    else
+                        remote_base = local_snapshots[i-1]
+                        cb()
+            (cb) ->
+                dbg("create replication stream")
+                start = if remote_base? then "-I #{f}@#{remote_base}" else ""
+                tmp = "#{STORAGE_TMP}/.backup-#{opts.project_id}-remote-#{opts.remote}-#{remote_base}-local.lz4"
+                fs.exists tmp, (exists) ->
+                    if exists
+                        cb("#{tmp} already exists -- delete and try again")
+                    else
+                        clean_up = true
+                        execute_on
+                                host    : opts.remote
+                                command : "sudo zfs send -RD #{start} #{f}@#{remote_snapshots[remote_snapshots.length-1]} | lz4c - > #{tmp} "
+                                timeout : 7200
+                                cb      : cb
+            (cb) ->
+                dbg("copy replication stream back to backup host")
+                misc_node.execute_code
+                    command     : "scp -o StrictHostKeyChecking=no storage@#{opts.remote}:#{tmp} #{tmp}"
+                    timeout     : 7200
+                    err_on_exit : false
+                    cb          : cb
+
+            (cb) ->
+                dbg("apply replication stream")
+                misc_node.execute_code
+                    command : "cat #{tmp} | lz4c -d - | sudo zfs recv #{f}"
+                    timeout : 7200
+                    cb      : cb
+        ], (err) ->
+            if clean_up
+                execute_on
+                    host    : opts.remote
+                    command : "rm #{tmp}"
+                fs.unlink(tmp)
+            opts.cb?(err)
+        )
+
+
+
+###
 # Migrate -- throw away code for migrating from the old /mnt/home/blah projects to new ones
 ###


GoogleDriveのinsertをModelに内に移行

GoogleDriveのinsertをModelに内に移行

--- public/coffee/gdrive.coffee
+++ public/coffee/gdrive.coffee
@@ -2,38 +2,19 @@
   CLIENT_ID: '966231612988-cmob8calt2b646p4sddlb4410q2eekmq' +
   '.apps.googleusercontent.com'
   SCOPES: 'https://www.googleapis.com/auth/drive'
+  BOUNDARY = '-------314159265358979323846'

-   # Start the file upload.
-   # @param {Object} evt Arguments from the file selector.
-  uploadFile: (evt) ->
-    that = this
-    console.log('upload file')
-
-    content = $('#markdown > textarea').val()
-    title = 'test_file.txt'
-    console.log(content)
-    gapi.client.load('drive', 'v2', ->
-      that.insertFile(title, content)
-    )
-
-   # Insert new file.
-   # @param {File} fileData File object to read data from.
-   # @param {Function} callback Function to call when the request is complete.
-  insertFile: (title, content, callback) ->
-    that = GDrive.prototype
-    console.log('insert file')
-    boundary = '-------314159265358979323846'
-    delimiter = "\r\n--" + boundary + "\r\n"
-    close_delim = "\r\n--" + boundary + "--"
+  requestBody: (title, content)->
+    delimiter = "\r\n--" + this.BOUNDARY + "\r\n"
+    close_delim = "\r\n--" + this.BOUNDARY + "--"

     contentType = 'text/plain'
     metadata = {
       'title': title,
       'mimeType': contentType
     }
-    console.log(metadata)

-    base64Data = that.utf8_to_b64(content)
+    base64Data = this.utf8_to_b64(content)
     multipartRequestBody =
       delimiter +
       'Content-Type: application/json\r\n\r\n' +
@@ -45,36 +26,25 @@
       base64Data +
       close_delim

-    request = gapi.client.request({
-      'path': '/upload/drive/v2/files',
-      'method': 'POST',
-      'params': {'uploadType': 'multipart'},
-      'headers': {
-        'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
-      },
-      'body': multipartRequestBody})
-    if (!callback)
-      callback = (file) ->
-        console.log(file)
-    request.execute(callback)
+    return multipartRequestBody

   utf8_to_b64: (str) ->
     window.btoa( unescape(encodeURIComponent( str ) ) )

+
 GDriveModel = Backbone.Model.extend({
   initialize: ()->
-    this.CLIENT_ID = '966231612988-cmob8calt2b646p4sddlb4410q2eekmq'
-    + '.apps.googleusercontent.com'
-    this.SCOPES = 'https://www.googleapis.com/auth/drive'
+    this.gdrive = new GDrive()
     this.authResult = this.authorize()
+    this.file = null

   authorize: ()->
     that = this
     return null unless gapi.auth

     gapi.auth.authorize({
-      'client_id': this.CLIENT_ID,
-      'scope': this.SCOPES,
+      'client_id': this.gdrive.CLIENT_ID,
+      'scope': this.gdrive.SCOPES,
       'immediate': true },
       (authResult)->
         # immediate: trueが失敗したときはflaseで再チャレンジ
@@ -85,25 +55,55 @@
         else
           console.log("immediate false")
           gapi.auth.authorize({
-            'client_id': that.CLIENT_ID,
-            'scope': that.SCOPES,
+            'client_id': this.gdrive.CLIENT_ID,
+            'scope': this.gdrive.SCOPES,
             'immediate': false },
             (authResult)->
               that.authResult = authResult
           )
     )
+
+  upload: (title, content) ->
+    that = this
+    console.log('upload file')
+
+    console.log(content)
+    gapi.client.load('drive', 'v2', ->
+      that.insert(title, content)
+    )
+
+  insert: (title, content, callback) ->
+    that = this
+    console.log('insert file')
+    multipartRequestBody = this.gdrive.requestBody(title, content)
+    request = gapi.client.request({
+      'path': '/upload/drive/v2/files',
+      'method': 'POST',
+      'params': {'uploadType': 'multipart'},
+      'headers': {
+        'Content-Type': 'multipart/mixed; boundary="' +
+        this.gdrive.BOUNDARY + '"'
+      },
+      'body': multipartRequestBody})
+    if (!callback)
+      callback = (file) ->
+        console.log(file)
+        that.file = file
+    request.execute(callback)
 })
 GDriveView = Backbone.View.extend({
   el: '#gdrive'
   events:
     'click [name=authorize-button]': 'gapi_authorize'
-    'click [name=upload-button]': 'upload_text'
+    'click [name=upload-button]': 'upload'
   initialize: ()->

   gapi_authorize: ()->
     this.model.authorize()
-  upload_text: ()->
-    GDrive.prototype.uploadFile()
+  upload: ()->
+    content = $('#markdown > textarea').val()
+    title = 'test_file.txt'
+    this.model.upload(title, content)
 })

 gdriveModel = new GDriveModel()


Amend the error thrown by the i18n library into a developer warning instead.
Reasons:

The useFallback option actually means that the attempted load and fail is actually a valid procedure, and not one to throw an error.
The location of the error (in the generic get accessor for the locale storage) makes it pretty near impossible to catch this error.
The error thrown prevents subsequent ajax callbacks from firing (ajaxComplete, ajaxError, etc) and thus the fail cannot be picked up externally.

--- src/extras/batman.i18n.coffee
+++ src/extras/batman.i18n.coffee
@@ -45,7 +45,7 @@
           url: "/locales/#{k}.json"
           success: (data) => @set k, data[k]
           error: (xhr) ->
-            throw new Error("Couldn't load locale file #{k}!")
+            Batman.developer.warn("Couldn't load locale file #{k}!")
       @_storage[k]
     set: (k, v) -> @_storage[k] = v
     unset: (k) ->


There is no member

There is no member

--- client/app/Applications/Members.kdapplication/AppController.coffee
+++ client/app/Applications/Members.kdapplication/AppController.coffee
@@ -72,6 +72,7 @@
       itemClass             : MembersListItemView
       listControllerClass   : MembersListViewController
       limitPerPage          : 10
+      noItemFoundText       : "There is no member."
       # singleDataSource      : (selector, options, callback)=>
         # filterFunc selector, options, callback
       help                  :


fix add groups to use tutor name as id not available to tutor accounts

--- src/app/groups/partials/directives/group-selector-directive.coffee
+++ src/app/groups/partials/directives/group-selector-directive.coffee
@@ -19,7 +19,7 @@
       if $scope.project #in a student context
         tutorial_id = $scope.project.tutorial.id
       else #convenor/tutor
-        tutorial_id = _.find $scope.unit.tutorials, (tutorial) -> tutorial.tutor.id == $scope.assessingUnitRole.id
+        tutorial_id = _.find $scope.unit.tutorials, (tutorial) -> tutorial.tutor_name == $scope.assessingUnitRole.name
         if not tutorial_id
           tutorial_id = $scope.unit.tutorials[0].id
           $scope.staffFilter = 'all'


improve: clear multi-cursor when repeated regardless of clearMultipleCursorsOnEscapeInsertMode setting

--- lib/operator-insert.coffee
+++ lib/operator-insert.coffee
@@ -107,8 +107,7 @@
           @repeatInsert(selection, @lastChange?.newText ? '')
           moveCursorLeft(selection.cursor)

-      if settings.get('clearMultipleCursorsOnEscapeInsertMode')
-        @vimState.clearSelections()
+      @vimState.clearSelections()

     else
       @checkpoint = {}


Fix logic for moving authors in groups.

Fix logic for moving authors in groups.

--- app/assets/javascripts/controllers/author_group_controller.js.coffee
+++ app/assets/javascripts/controllers/author_group_controller.js.coffee
@@ -14,6 +14,21 @@
     op = {add: 'incrementProperty', remove: 'decrementProperty'}
     relevantAuthors.invoke(op[operation], 'position')

+  shiftAuthorPositions: (author, authorList, oldPosition, newPosition)->
+    if oldPosition < newPosition
+      newPosition = newPosition - 1
+      relevantAuthors = authorList.filter (a)->
+        a != author && (a.get('position') > oldPosition) && (a.get('position') <= newPosition)
+      relevantAuthors.invoke('decrementProperty', 'position')
+
+    else
+      relevantAuthors = authorList.filter (a)->
+        a != author && (a.get('position') < oldPosition) && (a.get('position') >= newPosition)
+      relevantAuthors.invoke('incrementProperty', 'position')
+
+    author.set('position', newPosition)
+    author.save()
+
   actions:
     toggleAuthorForm: (authorGroup=null) ->
       @set('currentAuthorGroup', authorGroup)
@@ -21,6 +36,7 @@
       false

     saveNewAuthor: (newAuthor) ->
+      newAuthor.position = @get('authors.length') + 1
       author = @store.createRecord('author', newAuthor)
       author.set('authorGroup', @get('model'))
       author.save().then (author) =>
@@ -33,11 +49,12 @@
       author.destroyRecord().then (author) =>
         @updateAuthorPositions(author, @get('authors'), 'remove')

-    changeAuthorGroup: (author, position) ->
-      @updateAuthorPositions(author, author.get('authorGroup.authors'), 'remove')
-
-      author.set('authorGroup', @get('model'))
-      author.set('position', position)
-      author.save()
-
-      @updateAuthorPositions(author, @get('authors'), 'add')
+    changeAuthorGroup: (author, newPosition) ->
+      if author.get('authorGroup') == @get('model')
+        @shiftAuthorPositions(author, @get('model.authors'), author.get('position'), newPosition)
+      else
+        @updateAuthorPositions(author, author.get('authorGroup.authors'), 'remove')
+        author.set('authorGroup', @get('model'))
+        author.set('position', newPosition)
+        author.save()
+        @updateAuthorPositions(author, @get('authors'), 'add')


Add a few more comments in example.

Add a few more comments in example.

--- examples/express/lib/app.coffee
+++ examples/express/lib/app.coffee
@@ -13,8 +13,8 @@
 # Stylus middleware. Serves bunlded CSS/Stylus files from assets/css at
 # static/css.
 app.use '/static/css', stylus.middleware
-  src: root '/assets/css'
-  dest: root '/static/css'
+  src: root 'assets/css'
+  dest: root 'static/css'

 # Express static middleware. Having this first in the middleware stack ensures
 # that static js files are still served normally, instead of being bunlded by
@@ -25,8 +25,14 @@
 # required from there. Serves bundled JavaScript files from assets/js at
 # static/js.
 app.use '/static/js', requisite.middleware
+  # Entry point for our client-side code is assets/js/app.coffee. The extension
+  # is optional, requisite will figure it out.
+  entry: root 'assets/js/app'
+
+  # Export entry module globally as 'app'
   export: 'app'
-  entry: root '/assets/js/app'
+
+  # Additional paths to look for required modules in
   paths: [root()]

 app.use connect.logger 'dev'


Client: Activity: Added onOutSideClick handler.

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -28,6 +28,20 @@
     document.addEventListener 'mousedown', @handleOutsideMouseClick

+  handleOutsideMouseClick: (e) =>
+
+    return  unless @refs.AllParticipantsMenu
+
+    target             = e.target
+    moreButtonEl       = @refs.showMoreButton.getDOMNode()
+    participantsMenuEl = @refs.AllParticipantsMenu.getDOMNode()
+
+    if ((@isNodeInContainer target, moreButtonEl) or (@isNodeInContainer target, participantsMenuEl))
+      return
+
+    e.stopPropagation()
+    @setState showAllParticipants: no
+

   isNodeInContainer: (el, container) ->
     while el
@@ -56,8 +70,9 @@
     else @setState addNewParticipantMode: yes

-  onShowMoreParticipantButtonClick: ->
+  onShowMoreParticipantButtonClick: (e) ->

+    e.stopPropagation()
     if @state.showAllParticipants is yes
     then @setState showAllParticipants: no
     else @setState showAllParticipants: yes
@@ -112,7 +127,7 @@
     moreCount = Math.min moreCount, 99

-

+

         {moreCount}+

@@ -125,7 +140,7 @@

     { participants } = @props

-

+

Other participants

         {@renderAvatars(participants, yes)}


Rackspace: create is ready with required information

--- workers/social/lib/social/models/computeproviders/rackspace.coffee
+++ workers/social/lib/social/models/computeproviders/rackspace.coffee
@@ -5,6 +5,18 @@
   @ping = (client, options, callback)->

     callback null, "Rackspace is cool #{ client.r.account.profile.nickname }!"
+
+  @create = (client, options, callback)->
+
+    { imageId, credential, instanceType, region } = options
+
+    meta =
+      type     : "rackspace"
+      imageId  : imageId      ? "bb02b1a3-bc77-4d17-ab5b-421d89850fca"
+      flavorId : instanceType ? "2"
+      region   : region       ? "IAD"
+
+    callback null, { meta, credential }

   @fetchAvailable = (client, options, callback)->


remove ? operator

remove ? operator

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -257,5 +257,5 @@
     date = parseInt req.params.date
     if chat_data()[room]?[year]?[month]?[date]?
       res.type 'text/html'
-      res.send render_items "#{year}/#{month}/#{date}", chat_data()[room]?[year]?[month]?[date]?
+      res.send render_items "#{year}/#{month}/#{date}", chat_data()[room][year][month][date]
     else not_found res


removed okay button and made dismiss button primary for request pending modal

--- client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
+++ client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
@@ -109,7 +109,7 @@
             height         : 'auto'
             overlay        : yes
             buttons        :
-              Okay         :
+              Dismiss      :
                 style      : 'modal-clean-green'
                 loader     :
                   color    : "#ffffff"
@@ -130,9 +130,6 @@
                     if @policy.approvalEnabled
                     then @access.show()
                     else @request.show()
-              Dismiss      :
-                style      : "modal-cancel"
-                callback   : -> modal.destroy()

     @invited      = new CustomLinkView
       tagName     : "a"


Prep for more tests

Prep for more tests

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -18,22 +18,35 @@
     scrollable: true
   , options

-  init = false
+  $window    = $(window)
   $container = $(".#{@options.container_class}")
+  $children  = $container.children()
   $control   = $('<div>', class: 'slides-control')
+  multiplier = 1
+  init       = false
+  width      = $window.width()
+  height     = $window.height()

   # Private
   initialize = =>
     return if init
     init = true
+    multiplier = findMultiplier()

-    $container.wrap($control)
+    $control = $container.wrap($control)
+
+    setupContainers()
+    setupChildren()
+
     $container.trigger('slides.init')

     @mobile = navigator.userAgent.match(/mobile/i)
     @start()

     this
+
+  findMultiplier = =>
+    if @size() == 1 then 1 else 3

   next = =>
     index = @current + 1
@@ -69,9 +82,41 @@

   positions = =>
     @current ||= 0
-    @next = next()
-    @prev = prev()
+    @next      = next()
+    @prev      = prev()
     false
+
+  setupContainers = ->
+    $control.css
+      width: width * multiplier
+      height: height
+      left: -width # if @size() > 1
+
+  setupChildren = =>
+    # if @size() > 1
+    $children.not('.current').css
+      display: 'none'
+      position: 'absolute'
+      overflow: 'hidden'
+      top: 0
+      left: width
+      zIndex: 0
+
+    adjustSlidesSize $children
+
+  adjustSlidesSize = ($el) =>
+    that = this
+    $el.each (i) ->
+      $(this).width(width).height(height)
+
+      # if that.size() > 1
+      $(this).css
+        left: width
+
+      # adjustImagePosition $('img', this).not('.keep-original')
+
+    $container.trigger('slides.sized')
+

   # Public
   @destroy = =>
@@ -85,6 +130,8 @@
     delete @play_id

   @start = =>
+    $container.fadeIn('fast')
+
     @animate 'next'

     if options.play
@@ -100,8 +147,21 @@
   @animate = (direction) =>
     parseHash() || parse(direction)

-    $container.find('.current').removeClass('current')
-    $container.children().eq(@next).addClass('current')
+    position = width * 2
+    offset = -position
+
+    $children.removeClass('current')
+    $children.eq(@current).addClass('current').css
+      left: position
+      display: 'block'
+
+    $control.animate
+      useTranslate3d: @mobile
+      left: offset
+    , options.slide_speed
+    , options.slide_easing
+    , =>
+      false

   positions()


configDir is acceptable when common dir is not prepared

--- src/lib/custom-configs.coffee
+++ src/lib/custom-configs.coffee
@@ -31,11 +31,11 @@
                 [configName, ext] = configFile.split('.')
                 configs[configName] = require(envDir + '/' + configFile) if ext in ['coffee', 'js', 'json']

-        # if not exists, put it.
         commonDir = "#{configDir}/common"
-        for configFile in fs.readdirSync(commonDir)
-            [configName, ext] = configFile.split('.')
-            configs[configName] ?= require(commonDir + '/' + configFile) if ext in ['coffee', 'js', 'json']
+        if fs.existsSync commonDir
+            for configFile in fs.readdirSync(commonDir)
+                [configName, ext] = configFile.split('.')
+                configs[configName] ?= require(commonDir + '/' + configFile) if ext in ['coffee', 'js', 'json']

         return configs


#3 on_progress колбек теперь выдает контекст скопированного файла/папки

--- copy-files.coffee
+++ copy-files.coffee
@@ -178,7 +178,7 @@
           @statistics.copiedSize += ctx.srcAttr.size

     if @options.on_progress? and _.isFunction(@options.on_progress)
-      @options.on_progress(this)
+      @options.on_progress(ctx)

     if --ctx.copying is 0 and not ctx.lock
       ctx.callback(ctx.status)


toggle invisibles, markdown fix

toggle invisibles, markdown fix

--- keymap.cson
+++ keymap.cson
@@ -20,6 +20,7 @@
   'ctrl-a': 'editor:move-to-first-character-of-line'
   'ctrl-shift-a': 'editor:select-to-first-character-of-line'
   'ctrl-/': 'core:undo'
+  'alt-shift-i': 'window:toggle-invisibles'

 # great. ctrl-k is delete-to-end-of-line in emacs
 # in atom it's a prefix key, so we'd have to remap a lot of keys
@@ -40,3 +41,7 @@
 # meanwhile emacs-plus package overrides other key for fuzzy-finder
 'platform-linux':
   'ctrl-alt-x': 'fuzzy-finder:toggle-file-finder'
+
+# lots of contention for ctrl-shift-m
+'atom-workspace atom-text-editor:not([mini])[data-grammar^="text md"]':
+  'ctrl-shift-M': 'markdown-preview:toggle'


WS Client: don't relay SET_LOCAL_CLIENT_FILTER message

--- src/listeners/wsClient.coffee
+++ src/listeners/wsClient.coffee
@@ -18,7 +18,10 @@
   if type is 'CONNECT_REQUEST'
     rspType = if _fSocketConnected then 'WS_CONNECTED' else 'WS_DISCONNECTED'
     ifExtension.tx {type: rspType}
-  if not (type in ['CONNECT_REQUEST', 'CONNECT_RESPONSE', 'GET_LOCAL_CLIENT_FILTER'])
+  if not (type in [
+    'CONNECT_REQUEST', 'CONNECT_RESPONSE',
+    'GET_LOCAL_CLIENT_FILTER', 'SET_LOCAL_CLIENT_FILTER',
+  ])
     _txMsg {type, data}
   return


try again

try again

--- angular/test/protractor/helpers/page_helper.coffee
+++ angular/test/protractor/helpers/page_helper.coffee
@@ -13,7 +13,7 @@
   loadPath: (path, timeout = 40000) ->
     browser.get('dev/'+path, timeout)

-  waitForReload: (time=3000)->
+  waitForReload: (time=5000)->
     browser.driver.sleep(time)
     browser.waitForAngular()


Fixes data-dismiss behaviour (remove, don't just hide)

--- app/assets/javascripts/bootstrap_patches.js.coffee
+++ app/assets/javascripts/bootstrap_patches.js.coffee
@@ -31,4 +31,6 @@
 $.unobtrusive () ->
   $('[data-dismiss]').on 'click', () ->
     selector = '.' + $(this).data('dismiss')
-    $(this).closest(selector).fadeOut('fast')
+    target = $(this).closest(selector)
+    target.fadeOut 'fast', () ->
+      $(this).remove()


Fix formatting of dates in the load chart

--- app/assets/javascripts/charts/load_chart.js.coffee
+++ app/assets/javascripts/charts/load_chart.js.coffee
@@ -71,7 +71,7 @@

   formatDateFromFrame: (frame) =>
     multiplier =
-      switch @data.length
+      switch @data[0].values.length
         when 35040 then 900000
         when 8760  then 3600000
         else            -1


support scan operations on redis cluster

--- app/coffee/RedisManager.coffee
+++ app/coffee/RedisManager.coffee
@@ -2,6 +2,7 @@
 redis = require("redis-sharelatex")
 rclient = redis.createClient(Settings.redis.history)
 Keys = Settings.redis.history.key_schema
+async = require "async"

 module.exports = RedisManager =

@@ -33,12 +34,19 @@
 		rclient.smembers Keys.docsWithHistoryOps({project_id}), callback

 	# iterate over keys asynchronously using redis scan (non-blocking)
+	# handle all the cluster nodes or single redis server
 	_getKeys: (pattern, callback) ->
+		nodes = rclient.nodes?('master') || [ rclient ];
+		doKeyLookupForNode = (node, cb) ->
+			RedisManager._getKeysFromNode node, pattern, cb
+		async.concatSeries nodes, doKeyLookupForNode, callback
+
+	_getKeysFromNode: (node, pattern, callback) ->
 		cursor = 0  # redis iterator
 		keySet = {} # use hash to avoid duplicate results
 		# scan over all keys looking for pattern
 		doIteration = (cb) ->
-			rclient.scan cursor, "MATCH", pattern, "COUNT", 1000, (error, reply) ->
+			node.scan cursor, "MATCH", pattern, "COUNT", 1000, (error, reply) ->
 				return callback(error) if error?
 				[cursor, keys] = reply
 				for key in keys
@@ -50,21 +58,20 @@
 		doIteration()

 	# extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
+	# or DocsWithHistoryOps:{57fd0b1f53a8396d22b2c24b} (for redis cluster)
 	_extractIds: (keyList) ->
-		ids = (key.split(":")[1] for key in keyList)
+		ids = for key in keyList
+			m = key.match(/:\{?([0-9a-f]{24})\}?/) # extract object id
+			m[1]
 		return ids

-	# this will only work on single node redis, not redis cluster
 	getProjectIdsWithHistoryOps: (callback = (error, project_ids) ->) ->
-		return callback(new Error("not supported")) if rclient.nodes?
 		RedisManager._getKeys Keys.docsWithHistoryOps({project_id:"*"}), (error, project_keys) ->
 			return callback(error) if error?
 			project_ids = RedisManager._extractIds project_keys
 			callback(error, project_ids)

-	# this will only work on single node redis, not redis cluster
 	getAllDocIdsWithHistoryOps: (callback = (error, doc_ids) ->) ->
-		return callback(new Error("not supported")) if rclient.nodes?
 		# return all the docids, to find dangling history entries after
 		# everything is flushed.
 		RedisManager._getKeys Keys.uncompressedHistoryOps({doc_id:"*"}), (error, doc_keys) ->


removing eslintrc.cson

removing eslintrc.cson

do not like the format of the output. no easy way to re-format. eslint
allows for comments in the json file. will put them there.

--- eslintrc.cson
+++ eslintrc.cson
@@ -1,128 +0,0 @@
-{
-  "ecmaFeatures": {
-    "modules": true
-    "experimentalObjectRestSpread": true
-  }
-
-  "env": {
-    "browser": false
-    "es6": true
-    "node": true
-  }
-
-  "globals": {
-    "document": false
-    "navigator": false
-    "window": false
-  },
-
-  "rules": {
-    "accessor-pairs": 2
-    "arrow-spacing": [2, { "before": true, "after": true }]
-    "brace-style": [2, "1tbs", { "allowSingleLine": true }]
-    "comma-dangle": [2, "never"]
-    "comma-spacing": [2, { "before": false, "after": true }]
-    "comma-style": [2, "last"]
-    "constructor-super": 2
-    "curly": [2, "multi-line"]
-    "dot-location": [2, "property"]
-    "eol-last": 2
-    "eqeqeq": [2, "allow-null"]
-    "generator-star-spacing": [2, { "before": true, "after": true }]
-    "handle-callback-err": [2, "^(err|error)$" ]
-    "indent": [2, 2, { "SwitchCase": 1 }]
-    # Standard does not allow multiple spaces before or after colon
-    "key-spacing": [0, { "beforeColon": false, "afterColon": true }]
-    "new-cap": [2, { "newIsCap": true, "capIsNew": false }]
-    "new-parens": 2
-    "no-array-constructor": 2
-    "no-caller": 2
-    "no-class-assign": 2
-    "no-cond-assign": 2
-    "no-const-assign": 2
-    "no-control-regex": 2
-    "no-debugger": 2
-    "no-delete-var": 2
-    "no-dupe-args": 2
-    "no-dupe-keys": 2
-    "no-duplicate-case": 2
-    "no-empty-character-class": 2
-    "no-empty-label": 2
-    "no-eval": 2
-    "no-ex-assign": 2
-    "no-extend-native": 2
-    "no-extra-bind": 2
-    "no-extra-boolean-cast": 2
-    "no-extra-parens": [2, "functions"]
-    "no-fallthrough": 2
-    "no-floating-decimal": 2
-    "no-func-assign": 2
-    "no-implied-eval": 2
-    "no-inner-declarations": [2, "functions"]
-    "no-invalid-regexp": 2
-    "no-irregular-whitespace": 2
-    "no-iterator": 2
-    "no-label-var": 2
-    "no-labels": 2
-    "no-lone-blocks": 2
-    "no-mixed-spaces-and-tabs": 2
-    # Standard does not allow multi-spaces
-    "no-multi-spaces": 0
-    "no-multi-str": 2
-    "no-multiple-empty-lines": [2, { "max": 1 }]
-    "no-native-reassign": 2
-    "no-negated-in-lhs": 2
-    "no-new": 2
-    "no-new-func": 2
-    "no-new-object": 2
-    "no-new-require": 2
-    "no-new-wrappers": 2
-    "no-obj-calls": 2
-    "no-octal": 2
-    "no-octal-escape": 2
-    "no-proto": 2
-    "no-redeclare": 2
-    "no-regex-spaces": 2
-    "no-return-assign": 2
-    "no-self-compare": 2
-    "no-sequences": 2
-    "no-shadow-restricted-names": 2
-    "no-spaced-func": 2
-    "no-sparse-arrays": 2
-    "no-this-before-super": 2
-    "no-throw-literal": 2
-    "no-trailing-spaces": 2
-    "no-undef": 2
-    "no-undef-init": 2,
-    "no-unexpected-multiline": 2
-    "no-unneeded-ternary": 2
-    "no-unreachable": 2
-    "no-unused-vars": [2, { "vars": "all", "args": "none" }]
-    "no-useless-call": 2
-    "no-with": 2
-    # Standard goes either way with plugin but use `always` in base config
-    "object-curly-spacing": [2, "never"]
-    "one-var": [2, { "initialized": "never" }]
-    # Standard uses operator-linebreak `after`
-    "operator-linebreak": [2, "before"]
-    "quotes": [2, "single", "avoid-escape"]
-    "radix": 2
-    "semi": [2, "never"]
-    "space-after-keywords": [2, "always"]
-    "space-before-blocks": [2, "always"]
-    "space-before-function-paren": [2, "always"]
-    "space-in-parens": [2, "never"]
-    "space-infix-ops": 2
-    "space-return-throw-case": 2
-    "space-unary-ops": [2, { "words": true, "nonwords": false }]
-    "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }]
-    "use-isnan": 2
-    "valid-typeof": 2
-    "wrap-iife": [2, "any"]
-    "yoda": [2, "never"]
-
-    # Standard Plugin Options but just using eslint because I am fine with them.
-    "array-bracket-spacing": [2, "never"]
-    "computed-property-spacing": [2, "never"]
-  }
-}


test(karma): Removed file upload

test(karma): Removed file upload


Follow up: Fixed issue #1442 - SSL certificate verification for certificate not matching hostnames fail.

--- app/assets/javascripts/app/controllers/_integration/exchange.coffee
+++ app/assets/javascripts/app/controllers/_integration/exchange.coffee
@@ -262,7 +262,7 @@
       success: (data, status, xhr) =>
         if data.result isnt 'ok'

-          if data.message.indexOf('certificate verify failed') is -1
+          if data.message.indexOf('certificate') is -1
             @showSlide('js-discover')
             @showAlert('js-discover', data.message)
           else


Revert removal of js extension

Revert removal of js extension

--- src/metacoffee/command.coffee
+++ src/metacoffee/command.coffee
@@ -12,4 +12,4 @@
 targetFileName = path.basename fileName, path.extname fileName

 mkdirp.sync targetDirectory
-fs.writeFileSync (path.join targetDirectory, targetFileName), compiled, "utf-8"
+fs.writeFileSync (path.join targetDirectory, targetFileName + '.js'), compiled, "utf-8"


getting rid of hello and by messages

getting rid of hello and by messages

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -69,7 +69,7 @@
   enterReplies = ['Hi', 'Hello friend.', 'I see you :eye: :eye:']
   leaveReplies = ['Finally he left. We didn\'t like him anyway', 'You won\'t be missed here ;p', 'This channel is not for quitters. I am glad he left!']

-  robot.enter (res) ->
-    res.send res.random enterReplies
-  robot.leave (res) ->
-    res.send res.random leaveReplies+  # robot.enter (res) ->
+  #   res.send res.random enterReplies
+  # robot.leave (res) ->
+  #   res.send res.random leaveReplies


update wallet needs to be unlocked logic

--- app/js/controllers/root.coffee
+++ app/js/controllers/root.coffee
@@ -98,7 +98,8 @@
             when off
                 Observer.unregisterObserver Wallet.observer_config()

-        navigate_to('unlockwallet') if Info.info.wallet_open and !unlocked
+        # navigate_to('unlockwallet') if Info.info.wallet_open and !unlocked
+        navigate_to('unlockwallet') if !Info.info.wallet_open or !unlocked
     , true

     $scope.clear_form_errors = (form) ->


fucking with the alan script

fucking with the alan script

--- scripts/fierce.coffee
+++ scripts/fierce.coffee
@@ -115,6 +115,7 @@
   robot.hear /(^| )alan/i, (msg) ->
     roll = Math.floor(Math.random() * 20) + 1
       if roll is 1
+        msg send 'I hate this script'
         msg.send "http://www.youtube.com/watch?v=aXqkNF8fgs8"

   robot.hear /right about now/i, (msg) ->


Debug

Debug

--- scripts/mailin.coffee
+++ scripts/mailin.coffee
@@ -24,6 +24,7 @@

     messageId = data.headers['Message-ID']
     host = messageId.replace(/^<|>$/g, "").split(/@/)[1]
+    console.log data.envelope

     switch host
       when "disqus.net"


Typo fix.

Typo fix.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -125,4 +125,4 @@
             # { masonest: 3670, awebot: 3860, knexem: 3455 }
             # msg.send "#{Util.inspect(recall)}"

-            msg.send "#{username}" for username in recall"
+            msg.send "#{username}" for username in recall


Add flash message if no request found

Add flash message if no request found

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -181,7 +181,7 @@
           for field, message of request._issues.attribute
             flash.error = "#{field}: #{message}"

-  .controller 'RequestListCtrl', ($scope, $location, Request) ->
+  .controller 'RequestListCtrl', ($scope, $location, Request, flash, gettext) ->
     $scope.where = $location.search()
     $scope.filterStatus = () ->
       $location.search($scope.where)
@@ -194,6 +194,8 @@
         query.where['attribute.waterpoint_id'] = $scope.where.waterpoint_id
       Request.query query, (requests) ->
         $scope.requests = requests._items
+        if $scope.requests.length == 0
+          flash.info = gettext("No request matching the criteria!")
     $scope.filterStatus()

   .controller 'RequestTriageCtrl', ($scope, $routeParams, $filter,


Old embed url redirect.

Old embed url redirect.

--- server-src/dev.coffee
+++ server-src/dev.coffee
@@ -31,6 +31,7 @@
     /^\/address\/[^\/]+\/[^\/]+\/[^\/]+$/, # with id path
     /^\/division\/[^\/]+\/[^\/]+$/, # with id path
     /^\/division$/, # with query string
+    /^\/area$/
 ]

 staticFileHelper = (fpath) ->
@@ -74,6 +75,21 @@
         res.render template, vars

 requestHandler = makeHandler('home.jade')
+
+# This handler can be removed once it's certain it
+# has no users.
+redirectHandler = (req, res, next) ->
+    if req.path.match /^\/area/
+        bbox = req.query.bbox
+        if bbox?
+            res.redirect 301, config.url_prefix + "?bbox=#{bbox}"
+            return
+    else if req.path.match /^\/unit/
+        divs = req.query.divisions
+        if divs?
+            res.redirect 301, config.url_prefix + "unit?ocd_id=#{divs}"
+            return
+    next()

 handleUnit = (req, res, next) ->
     if req.query.service? or req.query.division?
@@ -134,6 +150,7 @@

     # Static files handler
     @use STATIC_URL, express.static staticDir
+    @use config.url_prefix + 'embed', redirectHandler
     # Redirect all trailing slash urls to slashless urls
     @use slashes(false)
     # Expose the original sources for better debugging


cleanup

cleanup

--- app/templates/_Gulpfile.coffee
+++ app/templates/_Gulpfile.coffee
@@ -250,36 +250,13 @@
 #*------------------------------------*\
 #   $DEV UPDATE
 #*------------------------------------*/
-# 	devUpdate:
-# 		options:
-# 			reportUpdated: false
-# 			semver: true
-# 			packages:
-# 				devDependencies: true
-# 				dependencies: false
-# 			packageJson: null
-# 		check:
-# 			updateType: 'report'
-# 		ask:
-# 			options:
-# 				updateType: 'prompt'
-# 		up:
-# 			options:
-# 				updateType: 'force'
-
-
-# #*------------------------------------*\
-# #   $TASKS
-# #*------------------------------------*/
-# grunt.registerTask 'default', ['watch']
-# grunt.registerTask 'optim', ['imageoptim', 'svgmin']
+# No exsisting gulp task runner
+
+#*------------------------------------*\
+#   $TASKS
+#*------------------------------------*/
+gulp.task 'default', ['watch']
 gulp.task "build", ["uglifyJs", "minify"]
-# grunt.registerTask 'depcheck', ['devUpdate:check']
-# grunt.registerTask 'depask', ['devUpdate:ask']
-# grunt.registerTask 'depup', ['devUpdate:up']
-
-
-#*------------------------------------*\
-#   $DEFAULT
-#*------------------------------------*/
-gulp.task 'default', ['watch']
+# grunt's devUpdate:check alternative
+# grunt's devUpdate:ask alternative
+# grunt's devUpdate:up alternative


Silly parse int error.  Should be fixed.

Silly parse int error.  Should be fixed.

--- app/assets/javascripts/connect_four.js.coffee
+++ app/assets/javascripts/connect_four.js.coffee
@@ -23,9 +23,9 @@
   all_checkers = []
   $(".#{color}.checker").each ->
     [x,y] = @.id.split("pos")[1].split("_")
-    all_checkers.push {x:x,y:y}
+    all_checkers.push {x:parseInt(x),y:parseInt(y)}
   for checker in all_checkers
-    positions = find_four(parseInt(checker.x),parseInt(checker.y),all_checkers)
+    positions = find_four(checker.x,checker.y,all_checkers)
     if positions?
       connect_four.valid_msg "Game completed. #{color[0].toUpperCase() + color[1..-1].toLowerCase()} wins!", ->
         drawline(positions[0].x,positions[0].y,positions[1].x,positions[1].y)
@@ -55,7 +55,7 @@
   included = false
   return true if acc==4
   for object in a
-    included = true if (parseInt(object.x) == parseInt(x) && parseInt(object.y) == parseInt(y))
+    included = true if (object.x == x && object.y == y)
   return false if not included or x<0 or x>6 or y<0 or y>5
   x-=1 if direction == "left" or direction == "bottomleft" or direction == "topleft"
   x+=1 if direction == "right" or direction == "topright" or direction == "bottomright"


clean comments

clean comments

--- app/assets/javascripts/dante/editor.js.coffee
+++ app/assets/javascripts/dante/editor.js.coffee
@@ -654,8 +654,6 @@
       #removes previous selected nodes
       $(@el).find(".is-selected").removeClass("is-selected")

-
-
       utils.log @isLastChar()

       #smart list support
@@ -670,13 +668,6 @@
       _.each @widgets, (w)=>
         if w.handleEnterKey
           w.handleEnterKey(e, parent);
-
-
-      # #embeds or extracts
-      # if parent.hasClass("is-embedable")
-      #   @embed_widget.getEmbedFromNode($(anchor_node))
-      # else if parent.hasClass("is-extractable")
-      #   @embed_extract_widget.getExtractFromNode($(anchor_node))

       #supress linebreak into embed page text unless last char
       if parent.hasClass("graf--mixtapeEmbed") or parent.hasClass("graf--iframe") or parent.hasClass("graf--figure")


better handling of removal of first graf element

--- app/assets/javascripts/dante/editor.js.coffee
+++ app/assets/javascripts/dante/editor.js.coffee
@@ -735,7 +735,13 @@

       if $(utils_anchor_node).hasClass("section-content") || $(utils_anchor_node).hasClass("graf--first")
         utils.log "SECTION DETECTED FROM KEYUP #{_.isEmpty($(utils_anchor_node).text())}"
-        return false if _.isEmpty($(utils_anchor_node).text())
+        if _.isEmpty($(utils_anchor_node).text())
+          next_graf =  $(utils_anchor_node).next(".graf")[0]
+          if next_graf
+            @setRangeAt next_graf
+            $(utils_anchor_node).remove()
+            @setupFirstAndLast()
+          return false

       if _.isNull(anchor_node)
         @handleNullAnchor()
@@ -743,14 +749,19 @@

       if $(anchor_node).hasClass("graf--first")
         utils.log "THE FIRST ONE! UP"
+
+        if @.getSelectedText() is @.getNode().textContent
+          utils.log "remove selection dectected"
+          @.getNode().innerHTML = "<br>"
+
         @markAsSelected(anchor_node)
         @setupFirstAndLast()
         false

-      if anchor_node
-        @markAsSelected(anchor_node)
-        @setupFirstAndLast()
-        @displayTooltipAt($(@el).find(".is-selected"))
+      #if anchor_node
+      #  @markAsSelected(anchor_node)
+      #  @setupFirstAndLast()
+      #  @displayTooltipAt($(@el).find(".is-selected"))

     #arrows key
     if _.contains([37,38,39,40], e.which)


Added a method to the cloud model to figure out if a user is moderator of it.

--- app/assets/javascripts/models/cloud.js.coffee
+++ app/assets/javascripts/models/cloud.js.coffee
@@ -123,3 +123,6 @@

     return { cloud: obj }

+  isModerator: (user) ->
+    _.include(@get('moderator_ids'), user.id)
+


Encapsulate repository map code into a class

For better abstraction and accessibility of map functions I have
refactored the whole code into its own class.

Signed-off-by: Konrad Reiche <4ff0213f56e2082fcd8c882d04c546881c579ce4@gmail.com>

--- app/assets/javascripts/repositories.js.coffee
+++ app/assets/javascripts/repositories.js.coffee
@@ -2,11 +2,63 @@

 $ ->

-  $("a[data-target='#map']").on 'shown.bs.tab', (e) ->
+  class RepositoryMap

-    # Custom marker for visualizing the score
-    scoreIcon = (score) ->
+    TILE_URL = "http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.png"

+    constructor: (target) ->
+      @map = L.map(target).setView([51.505, -0.09], 2)
+      options = {attribution: null, subdomains: "1234", type: "osm"}
+      L.tileLayer(TILE_URL, options).addTo(@map)
+
+      markers = @initMarkers()
+      @initControls(markers)
+
+    initControls: (markers) ->
+      toggleMarkerIcons = @toggleMarkerIconsFunc(markers)
+
+      onAdd = (map) ->
+        controlDiv = L.DomUtil.create("div", "leaflet-control-command")
+        L.DomEvent
+          .addListener(controlDiv, "click", L.DomEvent.stopPropagation)
+          .addListener(controlDiv, "click", L.DomEvent.preventDefault)
+          .addListener(controlDiv, "click", () -> toggleMarkerIcons())
+
+        controlUiClass = "leaflet-control-score-interior"
+        controlUi = L.DomUtil.create("div", controlUiClass, controlDiv)
+        controlUi.title = "Toggle Scores"
+
+        controlButtonClass = "control-link glyphicon glyphicon-dashboard"
+        controlButton = L.DomUtil.create("a", controlButtonClass, controlUi)
+
+        return controlDiv
+
+      options = { position: "topleft" }
+      L.Control.Command = L.Control.extend({ options: options, onAdd: onAdd })
+
+      scoreControl = new L.Control.Command({})
+      @map.addControl(scoreControl)
+
+    initMarkers: () ->
+      markers = []
+
+      for repository in gon.repositories
+        score = calculateScore(repository)
+        latitude = repository['latitude']
+        longitude  = repository['longitude']
+
+        marker = L.marker([latitude, longitude], {icon: @getScoreIcon(score)}).addTo(@map)
+        marker.bindPopup(repository['name'])
+        markers.push(marker)
+
+      return markers
+
+    toggleMarkerIconsFunc: (markers) ->
+      return () ->
+        for marker in markers
+          console.log marker
+
+    getScoreIcon: (score) ->
       if score == null
         markerClass = "marker-score-unknown"
       else if score >= 0.0 <= 0.29
@@ -17,51 +69,16 @@
         markerClass = "marker-score-high"

       score = if score then Math.round(score * 100) else "-"
+      options = {
+        className: "marker-score #{markerClass}",
+        html: "
#{score}
",
+        iconSize: [50, 50]
+      }

-      L.divIcon({
-        className: "marker-score #{markerClass}"
-        html: "
#{score}
",
-        iconSize: [50, 50]})
+      L.divIcon(options)

-    scoreControlCommand = () ->
-      alert("Working")
-
-    map = L.map('map-canvas').setView [51.505, -0.09], 2
-    L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.png', {
-      attribution: null,
-      subdomains: '1234',
-      type: 'osm',
-    }).addTo(map)
-
-    L.Control.Command = L.Control.extend({
-      options: {
-        position: "topleft"
-      },
-      onAdd: (map) ->
-        controlDiv = L.DomUtil.create("div", "leaflet-control-command")
-        L.DomEvent
-          .addListener(controlDiv, "click", L.DomEvent.stopPropagation)
-          .addListener(controlDiv, "click", L.DomEvent.preventDefault)
-          .addListener(controlDiv, "click", () -> scoreControlCommand())
-
-        controlInterface = L.DomUtil.create("div", "leaflet-control-score-interior", controlDiv)
-        controlInterface.title = "Toggle Scores"
-        controlButton = L.DomUtil.create("a", "control-link glyphicon glyphicon-dashboard", controlInterface)
-
-        return controlDiv
-    })
-
-    scoreControl = new L.Control.Command({})
-    map.addControl(scoreControl)
-
-    for repository in gon.repositories
-      score = calculateScore(repository)
-
-      latitude = repository['latitude']
-      longitude  = repository['longitude']
-
-      marker = L.marker([latitude, longitude], {icon: scoreIcon(score)}).addTo map
-      marker.bindPopup repository['name']
+  $("a[data-target='#map']").on 'shown.bs.tab', (e) ->
+    new RepositoryMap("map-canvas")

   if isPath("/repositories/:repository_id/snapshots/:snapshot_id/metric/:metric_id")
     sm = new ScoreMeter(".metric.score-meter", gon.score)


update column headers when dragging columns around

fixes CNVS-35474

test plan:
* visit Gradezilla
* drag columns around
* ensure column headers always display intended text

Change-Id: I4de75c0cad7eccc837fb9d6a343a74bae31373f6
Reviewed-on: https://gerrit.instructure.com/105895
Reviewed-by: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
Tested-by: Jenkins
QA-Review: Anju Reddy <851202533fb1f0b615e4547ba226c76996d060dd@instructure.com>
Product-Review: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>

--- app/coffeescripts/gradezilla/Gradebook.coffee
+++ app/coffeescripts/gradezilla/Gradebook.coffee
@@ -478,6 +478,8 @@
       else
         @storeCustomColumnOrder()

+      @updateColumnHeaders()
+
     reorderCustomColumns: (ids) ->
       $.ajaxJSON(@options.reorder_custom_columns_url, "POST", order: ids)

@@ -503,6 +505,8 @@
       columns.sort @makeColumnSortFn(newSortOrder)
       columns.splice(0, 0, frozen...)
       @grid.setColumns(columns)
+
+      @updateColumnHeaders()

     makeColumnSortFn: (sortOrder) =>
       fn = switch sortOrder.sortType


Refactor token generator, and use crypto for numerics

--- services/web/app/coffee/Features/Project/ProjectTokenGenerator.coffee
+++ services/web/app/coffee/Features/Project/ProjectTokenGenerator.coffee
@@ -12,19 +12,33 @@
   #   a y), and lower case "l" is omitted, because in many fonts it is
   #   indistinguishable from an upper case "I" (and sometimes even the number 1).
 	TOKEN_ALPHA: 'bcdfghjkmnpqrstvwxyz'
+	TOKEN_NUMERICS: '123456789'
+
+	_randomString: (length, alphabet) ->
+		result = ''
+		crypto.randomBytes(length).map(
+			(b) -> result += alphabet[b % alphabet.length]
+		)
+		return result

 	# Generate a 12-char token with only characters from TOKEN_ALPHA,
 	# suitable for use as a read-only token for a project
 	readOnlyToken: () ->
-		length = 12
-		tokenAlpha = ProjectTokenGenerator.TOKEN_ALPHA
-		result = ''
-		crypto.randomBytes(length).map( (a) -> result += tokenAlpha[a % tokenAlpha.length] )
-		return result
+		return ProjectTokenGenerator._randomString(
+			12,
+			ProjectTokenGenerator.TOKEN_ALPHA
+		)

 	# Generate a longer token, with a numeric prefix,
 	# suitable for use as a read-and-write token for a project
 	readAndWriteToken: () ->
-		numerics = Math.random().toString().slice(2, 12)
-		token = ProjectTokenGenerator.readOnlyToken()
-		return "#{numerics}#{token}"
+		numerics = ProjectTokenGenerator._randomString(
+			10,
+			ProjectTokenGenerator.TOKEN_NUMERICS
+		)
+		token = ProjectTokenGenerator._randomString(
+			12,
+			ProjectTokenGenerator.TOKEN_ALPHA
+		)
+		fullToken = "#{numerics}#{token}"
+		return fullToken


Show message and auto refresh on force disconnect

--- services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -47,9 +47,18 @@

 			@ide.socket.on 'forceDisconnect', (message) =>
 				@$scope.$apply () =>
+					@$scope.permissions.write = false
 					@$scope.connection.forced_disconnect = true
-				@socket.disconnect()
-
+				@ide.socket.socket.disconnect()
+				@ide.showGenericMessageModal("Please Refresh", """
+					We're performing maintenance on ShareLaTeX and you need to refresh the editor.
+					Sorry for any inconvenience.
+					The editor will refresh in automatically in 10 seconds.
+				""")
+				setTimeout () ->
+					location.reload()
+				, 10 * 1000
+
 		joinProject: () ->
 			@ide.socket.emit 'joinProject', {
 				project_id: @ide.project_id


(- fix util) Clearing up a bug with bound constructors' with()

Stupidly enough, it turns out that natively bound functions *don't* retain member properties of the
original function. Thus, when we bind constructors in with(), those constructors no longer have any
“class methods.” Fucking nasty. Whatever.

--- Paws/Source/utilities.coffee
+++ Paws/Source/utilities.coffee
@@ -84,11 +84,17 @@
    #
    #    The idiomatic way around this, is to store the options object to a local variable if you
    #    will be needing it across multiple reactor ticks. This, in my experience, is an edge-case.
+   # ----
+   # TODO: This could all be a lot more succinct, and prettier.
    parameterizable: (klass) ->
       klass.with = (_) ->
          it = construct this, klass
-         it.with(_)
-         return klass.bind(it)
+         it.with _
+         bound = klass.bind(it)
+         utilities.infect bound, klass
+         bound._ = _
+         process.nextTick => delete bound._
+         bound

       klass.prototype.with = (@_) ->
          process.nextTick => delete @_


fix(dashboard): re-load season data on login state change

--- frontend/directives/memberSeasonView.coffee
+++ frontend/directives/memberSeasonView.coffee
@@ -7,9 +7,12 @@
     "User"
     "toastr"
     ($scope, $http, User, toastr) ->
-      return unless User.loggedIn
       $scope.seasons = []
-      $http.get("/api/match/seasons", params: member: true)
-        .success (data) ->
-          $scope.seasons = data?.seasons
+      refreshSeasons = () ->
+        return unless User.loggedIn
+        $http.get("/api/match/seasons", params: member: true)
+          .success (data) ->
+            $scope.seasons = data?.seasons
+      refreshSeasons()
+      $scope.$on("currentUser:login:changed", -> refreshSeasons())
 ]


append svg element

append svg element

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -39,6 +39,17 @@
     @rootView.on 'keyup', @track
     @rootView.on 'mousedown', @track

+  append: ->
+    w = @.width()
+    h = @.height()
+    [pt, pl, pb, pr] = [0,0,0,0]
+
+    d3.select(@.get(0)).append('svg')
+      .attr('width', w)
+      .attr('height', h)
+    .append('g')
+      .attr('transform', "translate(#{pl},#{pt})")
+
   track: =>
     date = new Date
     times = time date
@@ -57,6 +68,7 @@
   attach: ->
     @rootView.append @
     @focus()
+    @append()

   detach: =>
     super()


fix cross order

fix cross order

--- graphics/002-rotated-wahlque/rotater.coffee
+++ graphics/002-rotated-wahlque/rotater.coffee
@@ -15,7 +15,7 @@
         sin = Math.sin(phi)
         # by Rodrigues' rotation formula
         rotated = vec3.add(vec3.expand(axisz, cos), vec3.expand(vec3.cross(axisx, axisz), sin))
-        axisy = vec3.cross(axisx, rotated)
+        axisy = vec3.cross(rotated, axisx)
         [axisx, axisy, rotated]

     rotater.down = (frame, step) ->
@@ -25,7 +25,7 @@
         sin = Math.sin(phi)
         # by Rodrigues' rotation formula
         rotated = vec3.add(vec3.expand(axisz, cos), vec3.expand(vec3.cross(axisx, axisz), sin))
-        axisy = vec3.cross(axisx, rotated)
+        axisy = vec3.cross(rotated, axisx)
         [axisx, axisy, rotated]

     rotater.left = (frame, step) ->
@@ -35,7 +35,7 @@
         sin = Math.sin(phi)
         # by Rodrigues' rotation formula
         rotated = vec3.add(vec3.expand(axisz, cos), vec3.expand(vec3.cross(axisy, axisz), sin))
-        axisx = vec3.cross(axisy, rotated)
+        axisx = vec3.cross(rotated, axisy)
         [axisx, axisy, rotated]

     rotater.right = (frame, step) ->
@@ -45,7 +45,7 @@
         sin = Math.sin(phi)
         # by Rodrigues' rotation formula
         rotated = vec3.add(vec3.expand(axisz, cos), vec3.expand(vec3.cross(axisy, axisz), sin))
-        axisx = vec3.cross(axisy, rotated)
+        axisx = vec3.cross(rotated, axisy)
         [axisx, axisy, rotated]

     rotater


mongo import: using "epoch" millisec dates instead of Date because of the mongoimport problem with negative dates

--- data/imports/aiddata.coffee
+++ data/imports/aiddata.coffee
@@ -1,3 +1,30 @@
+
+#################    CONFIG     #################
+USE_MONGOIMPORT = true
+
+DEBUG_updateRowsLimit =  null  # set for debugging
+
+OMIT_NULL_VALUE_FIELDS_IN_COMMITMENTS_JSON = true
+
+IMPORT_COMMITMENT_DATES_AS_ = "epoch"    # Possibilities:  "epoch" "date" "string"
+# "date" won't work for negative dates
+# as long as https://jira.mongodb.org/browse/SERVER-961 is not fixed
+
+
+
+
+PATH = "data/imports/"
+TEMP_DIR = "temp"
+AIDDATA_TEMP_FILE = PATH + TEMP_DIR + "/aiddata.json"
+LOCATIONS_TEMP_FILE = PATH + TEMP_DIR + "/locations.json"
+
+################# END OF CONFIG #################
+
+
+
+
+
+
 console.log ">>>>>>>>>> Importing AidData >>>>>>>>>>"

 fs = require 'fs'
@@ -12,16 +39,6 @@
 pg = require 'pg'
 pgurl = dbconf.postgres

-DEBUG_updateRowsLimit =  null  # set for debugging
-OMIT_NULL_VALUE_FIELDS_IN_COMMITMENTS_JSON = true
-
-PATH = "data/imports/"
-TEMP_DIR = "temp"
-AIDDATA_TEMP_FILE = PATH + TEMP_DIR + "/aiddata.json"
-LOCATIONS_TEMP_FILE = PATH + TEMP_DIR + "/locations.json"
-IMPORT_COMMITMENT_DATES_AS_DATES = true
-
-USE_MONGOIMPORT = true

 mongodb = pgclient = null

@@ -308,10 +325,9 @@
     fd = fs.openSync(AIDDATA_TEMP_FILE, 'w')

     dateFieldQ = (field, q) ->
-      if IMPORT_COMMITMENT_DATES_AS_DATES
-        "EXTRACT(EPOCH FROM #{q})*1000 AS #{field}"
-      else
-        "#{q} AS #{field}"
+      switch IMPORT_COMMITMENT_DATES_AS_
+        when "date", "epoch" then "EXTRACT(EPOCH FROM #{q})*1000 AS #{field}"
+        when "string" then "#{q} AS #{field}"

     dateFields =
       "date" : "COALESCE(commitment_date, start_date, to_timestamp(to_char(year, '9999'), 'YYYY'))"
@@ -320,7 +336,6 @@
       "end_date" : "end_date"

     dateFieldsSelect = (dateFieldQ(f,q) for f,q of dateFields).join(',')
-

     query = pgclient.query "
         SELECT
@@ -340,7 +355,8 @@
       row.origin = locationNameToCode[row.donor]
       row.dest = locationNameToCode[row.recipient]

-      if IMPORT_COMMITMENT_DATES_AS_DATES
+      # won't work for negative dates as long as https://jira.mongodb.org/browse/SERVER-961 is not fixed
+      if IMPORT_COMMITMENT_DATES_AS_ is "date"
         for f,q of dateFields
           if row[f]?
             row[f] = { "$date" : row[f] }


closing tag

closing tag

--- app/lib/notifier.coffee
+++ app/lib/notifier.coffee
@@ -14,6 +14,7 @@

         <button class='action-button'>Continue</button>
+

       ")
     @el.appendTo $('body')


treesame commit of origin/stable/2022-10-12

Treesame-Commit-Id: 5f1424c003d60c9c3141621da06258785297e8d2

--- ui/features/conferences/backbone/views/ConferenceView.coffee
+++ ui/features/conferences/backbone/views/ConferenceView.coffee
@@ -52,8 +52,8 @@
     @model.on('change', @render)

   syncAttendees: (e) ->
-    @el.querySelector("a[data-testid='settings-cog']").classList.add('ui-state-disabled')
-    @el.querySelector("a[data-testid='start-button']").setAttribute('disabled', '')
+    @el.querySelector("a[data-testid='settings-cog']")?.classList.add('ui-state-disabled')
+    @el.querySelector("a[data-testid='start-button']")?.setAttribute('disabled', '')
     atag = e.target
     form = atag.parentElement.querySelector('form')
     conference_name = form.querySelector("[name='web_conference[title]']").value || ''
@@ -70,15 +70,15 @@
         @el.querySelector('.conference-loading-indicator').style.display = "none"
         ReactDOM.unmountComponentAtNode(spinnerDomEl)
         $.flashMessage(conference_name + I18n.t(" Attendees Synced!"))
-        @el.querySelector("a[data-testid='settings-cog']").classList.remove('ui-state-disabled')
-        @el.querySelector("a[data-testid='start-button']").removeAttribute('disabled')
+        @el.querySelector("a[data-testid='settings-cog']")?.classList.remove('ui-state-disabled')
+        @el.querySelector("a[data-testid='start-button']")?.removeAttribute('disabled')
       error: =>
         @show(@model)
         @el.querySelector('.conference-loading-indicator').style.display = "none"
         ReactDOM.unmountComponentAtNode(spinnerDomEl)
         $.flashError(conference_name + I18n.t(" Attendees Failed to Sync."))
-        @el.querySelector("a[data-testid='settings-cog']").classList.remove('ui-state-disabled')
-        @el.querySelector("a[data-testid='start-button']").removeAttribute('disabled')
+        @el.querySelector("a[data-testid='settings-cog']")?.classList.remove('ui-state-disabled')
+        @el.querySelector("a[data-testid='start-button']")?.removeAttribute('disabled')
     )
     @$(form).submit()


Fix background-page error on "yy".

Fix background-page error on "yy".

The return value from copyToClipboard is passed to sendResponse by the
chrome.runtime.onMessage.addListener listener.  This was using the
(somewhat arbitrary) value returned by Clipboard.copy, which turns out
to be circular, causing an error.

This fixes that.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -170,7 +170,7 @@
 #
 # Copies some data (request.data) to the clipboard.
 #
-copyToClipboard = (request) -> Clipboard.copy(request.data)
+copyToClipboard = (request) -> Clipboard.copy(request.data); null

 #
 # Selects the tab with the ID specified in request.id


activity feed: fix bug where if err when fetching activities, spinner loaded forever

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -51,6 +51,8 @@

     @status = KD.getSingleton "status"
     @status.on "reconnected", (conn)=> @refresh()  if conn?.reason is "internetDownForLongTime"
+
+    @on "activitiesCouldntBeFetched", => @listController.hideLazyLoader()

   loadView:->
     @getView().feedWrapper.ready (controller)=>
@@ -178,9 +180,9 @@
     {CStatusActivity} = KD.remote.api
     eventSuffix = "#{@getFeedFilter()}_#{@getActivityFilter()}"
     CStatusActivity.fetchPublicActivityFeed options, (err, cache)=>
+      return @emit "activitiesCouldntBeFetched", err  if err
+
       cache.overview.reverse()  if cache.overview
-
-      return @emit "activitiesCouldntBeFetched", err  if err

       @sanitizeCache cache, (err, sanitizedCache)=>
         if err


emit event on any directory changes(rename/move/remove)

--- client/finder/lib/filetree/controllers/nfindertreecontroller.coffee
+++ client/finder/lib/filetree/controllers/nfindertreecontroller.coffee
@@ -262,6 +262,7 @@

     Promise.all(results).then =>
       @notify "#{deletedNodes.length} item#{if deletedNodes.length > 1 then 's' else ''} deleted!", "success"
+      @emit 'NodesRemoved', deletedNodes
       @removeNodeView node for node in deletedNodes

     .catch (err) =>
@@ -282,6 +283,7 @@

       nodeData.rename newValue, (err)=>
         if err then @notify null, null, err
+        @emit 'NodeRenamed', nodeData, newValue

       # @setKeyView()
       @beingEdited = null
@@ -330,6 +332,7 @@
         movedNodes.push node

     Promise.all(results).then =>
+      @emit 'NodesMoved', movedNodes, targetItem
       @notify "#{movedNodes.length} item#{if movedNodes.length > 1 then 's' else ''} moved!", "success"
       @removeNodeView node for node in movedNodes
       @refreshFolder targetNodeView


Workspace: Clean-up. Removed unused methods.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -206,52 +206,6 @@
     @workspaceRef.remove()
     KD.utils.wait 2000, => # check for user is still connected
       @forcedToDisconnect = no
-
-  showDisconnectedModal: ->
-    return if @forcedToDisconnect
-
-    if @amIHost()
-      title   = "Disconnected from remote"
-      content = "It seems, you have been disconnected from Firebase server. You cannot continue this session."
-    else
-      title   = "Host disconnected"
-      content = "It seems, host is disconnected from Firebase server. You cannot continue this session."
-
-    @disconnectedModal = new KDBlockingModalView
-      title            : title
-      appendToDomBody  : no
-      content          : "<p>#{content}</p>"
-      cssClass         : "host-disconnected-modal"
-      overlay          : no
-      width            : 470
-      buttons          :
-        Start          :
-          title        : "Start New Session"
-          callback     : =>
-            @disconnectedModal.destroy()
-            @startNewSession()
-        Join           :
-          title        : "Join Another Session"
-          callback     : =>
-            @disconnectedModal.destroy()
-            @showJoinModal()
-        Exit           :
-          title        : "Exit App"
-          cssClass     : "modal-cancel"
-          callback     : =>
-            @disconnectedModal.destroy()
-            appManager = KD.getSingleton "appManager"
-            appManager.quit appManager.frontApp
-
-    @disconnectedModal.on "KDObjectWillBeDestroyed", =>
-      delete @disconnectedModal
-      @disconnectOverlay.destroy()
-
-    @disconnectOverlay = new KDOverlayView
-      parent           : KD.singletons.mainView.mainTabView.activePane
-      isRemovable      : no
-
-    @container.getDomElement().append @disconnectedModal.getDomElement()

   showJoinModal: ->
     options        = @getOptions()
@@ -281,29 +235,6 @@
     @joinSession { sessionKey }
     modal.destroy()

-  showShareView: (panel, workspace, event) ->
-    button   = KD.instances[event.currentTarget.id]
-    shareUrl = "#{location.origin}/Develop/#{@getOptions().name}?sessionKey=#{@sessionKey}"
-    new JContextMenu
-      cssClass    : "activity-share-popup"
-      type        : "activity-share"
-      delegate    : this
-      x           : button.getX() + 25
-      y           : button.getY() + 25
-      arrow       :
-        placement : "top"
-        margin    : -10
-      lazyLoad    : yes
-    , customView  : new SharePopup {
-        url       : shareUrl
-        shortenURL: false
-        twitter   :
-          text    : "Learn, code and deploy together to powerful VMs - @koding, the dev environment from the future! #{shareUrl}"
-        linkedin  :
-          title   : "Join me @koding!"
-          text    : "Learn, code and deploy together to powerful VMs - @koding, the dev environment from the future! #{shareUrl}"
-      }
-
   createUserListContainer: ->
     @container.addSubView @userListContainer = new KDView
       cssClass : "user-list"


Workspace: Updated broadcast reference listening point.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -55,11 +55,6 @@

       @emit "WorkspaceSyncedWithRemote"

-      @broadcastRef.on "value", (snapshot) =>
-        message = snapshot.val()
-        return if not message or not message.data or message.data.sender is @nickname
-        @displayBroadcastMessage message.data
-
     @workspaceRef.child("users").on "child_added", (snapshot) =>
       @fetchUsers()

@@ -80,6 +75,11 @@
       KD.utils.wait 1500, =>
         @workspaceRef.once "value", (snapshot) =>
           @showDisconnectedModal()  unless snapshot.val() or @disconnectedModal
+
+    @broadcastRef.on "value", (snapshot) =>
+      message = snapshot.val()
+      return if not message or not message.data or message.data.sender is @nickname
+      @displayBroadcastMessage message.data

     @broadcastMessage
       title     : "#{@nickname} has joined the session"


Server: logged out home updated

Server: logged out home updated

--- workers/social/lib/social/render/loggedout/kodinghome.coffee
+++ workers/social/lib/social/render/loggedout/kodinghome.coffee
@@ -16,17 +16,17 @@
       <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1" />
       <link rel="shortcut icon" href="/a/images/favicon.ico" />
       <link rel="fluid-icon" href="/a/images/logos/fluid512.png" title="Koding" />
-      <link rel="stylesheet" href="/a/out/css/kd.css?#{KONFIG.version}" />
-      <link rel="stylesheet" href="/a/out/css/main.css?#{KONFIG.version}" />
+      <link rel="stylesheet" href="/a/site.landing/css/kd.css?#{KONFIG.version}" />
+      <link rel="stylesheet" href="/a/site.landing/css/main.css?#{KONFIG.version}" />
     </head>
-    <body>
+    <body class='home'>

       <!--[if IE]><script>(function(){window.location.href='/unsupported.html'})();</script><![endif]-->

-      <script src="/a/out/js/pistachio.js?#{KONFIG.version}"></script>
-      <script src="/a/out/js/kd.libs.js?#{KONFIG.version}"></script>
-      <script src="/a/out/js/kd.js?#{KONFIG.version}"></script>
-      <script src="/a/out/js/main.js?#{KONFIG.version}"></script>
+      <script src="/a/site.landing/js/pistachio.js?#{KONFIG.version}"></script>
+      <script src="/a/site.landing/js/kd.libs.js?#{KONFIG.version}"></script>
+      <script src="/a/site.landing/js/kd.js?#{KONFIG.version}"></script>
+      <script src="/a/site.landing/js/main.js?#{KONFIG.version}"></script>
     </body>
     </html>
     """