Update max_machines every time the user hit a key. Fix #1308

--- admin/static/coffee/namespaces/replicas.coffee
+++ admin/static/coffee/namespaces/replicas.coffee
@@ -162,8 +162,6 @@

             @datacenter.on 'all', @render

-            @model.on 'change:replica_affinities', @compute_max_machines_and_render
-            @model.on 'change:primary_uuid', @compute_max_machines_and_render
             @model.on 'change:primary_uuid', @render
             progress_list.on 'all', @render_progress

@@ -171,7 +169,6 @@
             directory.on 'all', @render_status

             @render_progress()
-            @compute_max_machines_and_render()

         remove_parent_alert: (event) ->
             event.preventDefault()
@@ -225,25 +222,20 @@

             return @

-        compute_max_machines_and_render: =>
-            if @datacenter is universe_datacenter
-                @max_machines = machines.length
-                for datacenter_id of @model.get('replica_affinities')
-                    if datacenter_id isnt universe_datacenter.get('id')
-                        @max_machines -= @model.get('replica_affinities')[datacenter_id]
-                if @model.get('primary_uuid') isnt universe_datacenter.get('id')
-                    @max_machines -= 1
-            else
-                @max_machines = machines.length
-                for datacenter_id of @model.get('replica_affinities')
-                    if datacenter_id isnt @datacenter.get('id')
-                        @max_machines -= @model.get('replica_affinities')[datacenter_id]
-                if @model.get('primary_uuid') isnt @datacenter.get('id')
-                    @max_machines -= 1
-
-                @need_explanation = @max_machines < DataUtils.get_datacenter_machines(@datacenter.get('id')).length
-                @max_machines = Math.min @max_machines, DataUtils.get_datacenter_machines(@datacenter.get('id')).length
-
+        # Compute how many replica we can set for @datacenter
+        compute_max_machines: =>
+            @max_machines = machines.length
+            for datacenter_id of @model.get('replica_affinities')
+                if datacenter_id isnt @datacenter.get('id')
+                    @max_machines -= @model.get('replica_affinities')[datacenter_id]
+            if @model.get('primary_uuid') isnt @datacenter.get('id')
+                @max_machines -= 1
+
+            # If we can't use all our machines in the datacenter because the replicas value of Universe is too high, we give a little more explanation
+            @need_explanation = @max_machines < DataUtils.get_datacenter_machines(@datacenter.get('id')).length
+            @max_machines = Math.min @max_machines, DataUtils.get_datacenter_machines(@datacenter.get('id')).length
+
+            # We render only if we are not editing
             if @current_state isnt @states[1]
                 @render()

@@ -259,6 +251,9 @@
             @.$('.replicas_acks-alert').html ''

         check_replicas_acks: (event) =>
+            # Update @max_machines
+            @compute_max_machines()
+
             if event?.which? and event.which is 13
                 @submit_replicas_acks()

@@ -461,8 +456,6 @@
         destroy: =>
             @datacenter.off 'all', @render

-            @model.off 'change:replica_affinities', @compute_max_machines_and_render
-            @model.off 'change:primary_uuid', @compute_max_machines_and_render
             @model.off 'change:primary_uuid', @render
             progress_list.off 'all', @render_progress


lib/api: data-page html is cached after first read from disk.

--- lib/api/index.coffee
+++ lib/api/index.coffee
@@ -1,6 +1,15 @@
 FS    = require('fs')
 Path  = require('path')
 {dfs} = require('../shared/utils')
+
+# Caches HTML fetched from disk for a given page.
+# This is an application-global cache.  Each Browser instance parses the
+# HTML to create its own nodes from the cached HTML, but we only read from
+# disk once.
+#
+# key   - page path
+# value - html
+PageHTMLCache = {}

 class Page
     constructor : (options) ->
@@ -90,17 +99,25 @@
             if page?
                 if !page['id'] || !page['src']
                     throw new Error("Missing id or src for data-page")
-                pendingPages++
+                page['container'] = elem
                 pagePath = Path.resolve(basePath, page['src'])
                 console.log("Loading page from: #{pagePath}")
-                # TODO: check an application-global cache for the html from
-                #       this pagePath.
-                FS.readFile pagePath, 'utf8', (err, data) ->
-                    if err then throw err
+
+                pendingPages++
+                handlePageData = (data) ->
                     page['html'] = data
-                    page['container'] = elem
                     pages[page['id']] = new Page(page)
-                    if --pendingPages == 0
-                        if callback then callback(pages)
+                    if (--pendingPages == 0) && callback? then callback(pages)
+
+                if PageHTMLCache[pagePath]
+                    # Need to do this on nextTick or else pendingPages will
+                    # re-zero immediately.
+                    process.nextTick () ->
+                        handlePageData(PageHTMLCache[pagePath])
+                else
+                    FS.readFile pagePath, 'utf8', (err, data) ->
+                        if err then throw err
+                        PageHTMLCache[pagePath] = data
+                        handlePageData(data)

 module.exports = InBrowserAPI


Fix `injectHints` might return undefined markers

The `inner` function of `injectHints` now returns an empty array instead
of undefined if no markers could be produced, since all results of the
`inner` function are recursively concatenated. VimFx seems to have worked
well even though some entries in the array of markers returned by
`injectHints` could be undefined. However, on the Huffman branch, the
undefined entries sometimes caused a TypeError to be thrown, which
eventually resulted in that none of the hint markers were given any hints.

--- extension/packages/hints.coffee
+++ extension/packages/hints.coffee
@@ -39,7 +39,7 @@
         for frame in document.defaultView.frames
           markers = markers.concat(inner(frame.document, markers.length+1))

-        return markers
+    return markers or []

   return inner(document)


made node webkit version connect to 127.0.0.1:4502

--- client/init.coffee
+++ client/init.coffee
@@ -9,7 +9,7 @@
       on: (e, f) -> (@[e] ?= []).push f; return
     socket = new FakeSocket; socket1 = new FakeSocket; socket.other = socket1; socket1.other = socket
     server = require './server'
-    setTimeout (-> server.serve insecure: true, socket: socket1), 2000 # wait for interpreter to bind to :4502
+    setTimeout (-> server.serve socket: socket1, 'host:port': '127.0.0.1:4502'), 3000 # give the interpreter some time to start
   else
     socket = io()


Remove the callback parameter of removeTab()

callback parameter is never passed to removeTab(), so adding it to
the selectionChangeHandlers list causes 'Can not call method call() of undefined'
error after closing the tab.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -234,12 +234,9 @@
   previousTab: (callback) -> selectTab(callback, "previous")
   firstTab: (callback) -> selectTab(callback, "first")
   lastTab: (callback) -> selectTab(callback, "last")
-  removeTab: (callback) ->
+  removeTab: ->
     chrome.tabs.getSelected(null, (tab) ->
-      chrome.tabs.remove(tab.id)
-      # We can't just call the callback here because we need to wait
-      # for the selection to change to consider this action done.
-      selectionChangedHandlers.push(callback))
+      chrome.tabs.remove(tab.id))
   restoreTab: (callback) ->
     # TODO(ilya): Should this be getLastFocused instead?
     chrome.windows.getCurrent((window) ->


Help dialog; fix show-advanced-command button position.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -91,7 +91,7 @@
   replacementStrings =
     version: currentVersion
     title: customTitle || "Help"
-    tip: if showCommandNames then "Tip: click command names to yank them to the clipboard." else ""
+    tip: if showCommandNames then "Tip: click command names to yank them to the clipboard." else " "

   for own group of Commands.commandGroups
     replacementStrings[group] =


Fix sync.init.

Fix sync.init.

--- background_scripts/sync.coffee
+++ background_scripts/sync.coffee
@@ -34,7 +34,7 @@
     chrome.storage.onChanged.addListener (changes, area) -> Sync.listener changes, area

   init: ->
-    chrome.storage.onChanged.addListener (changes, area) -> Sync.listener changes, area
+    @pull()

   # Asynchronous fetch from synced storage, called only at startup.
   pull: ->


Update datepicker.js.coffee

Update datepicker.js.coffee


VideoCollaboration: Added isVideoActive method.

--- client/ide/lib/videocollaborationcontroller.coffee
+++ client/ide/lib/videocollaborationcontroller.coffee
@@ -196,6 +196,14 @@
     @chat?.emit args...

+  isVideoActive: (callback) ->
+
+    state = @videoModel.state.active
+
+    callback? state
+    return state
+
+
   canUserStartVideo: (callback = kd.noop) ->

     { paymentController } = kd.singletons


Make split view class optional in layout builder.

--- client/ide/workspace/workspacelayoutbuilder.coffee
+++ client/ide/workspace/workspacelayoutbuilder.coffee
@@ -37,10 +37,11 @@

         views.push wrapper

-    splitView = new SplitViewWithOlderSiblings { type, sizes, views, cssClass }
+    SplitViewClass = @getOptions().splitViewClass or SplitViewWithOlderSiblings
+    splitView      = new SplitViewClass { type, sizes, views, cssClass }
     @splitViews[@baseSplitName] = splitView  if @baseSplitName

     return splitView

-  getSplitByName: (name) ->
+  getSplitViewByName: (name) ->
     return @splitViews[name] or null


Reset slideshow button

Reset slideshow button

--- app/assets/javascripts/components/slideshow.coffee
+++ app/assets/javascripts/components/slideshow.coffee
@@ -52,7 +52,8 @@
         observe('i', (next, prev) => @fetch() if next.i == prev.i || @getOffset(@params.i) != @getOffset(prev.i))

       @header.extraControls = [
-        { icon: 'fa-play', text: 'Auto', callback: @toggleAutoplay }
+        { icon: 'fa-play', text: 'Auto', callback: @toggleAutoplay },
+        { icon: 'fa-step-backward', text: 'Start', callback: (=> @params.i = 0) }
       ]

     $onDestroy: ->


Added sorting function for sorting elements after adding a new weight. Bug occured when weight wasn't the newest one

--- app/assets/javascripts/components/user_show.coffee
+++ app/assets/javascripts/components/user_show.coffee
@@ -8,6 +8,7 @@
     weights: []
   addWeight: (weight) ->
     weights = React.addons.update(@state.weights, { $unshift: [weight] })
+    weights = weights.sort((a,b) => new Date(b.registered) - new Date(a.registered))
     @setState weights: weights
   deleteWeight: (weight) ->
     index = @state.weights.indexOf weight


Extract Formula typeahead's suggest method

Makes for easier testing, which should be automated soon.

--- app/assets/javascripts/formula_typeahead.js.coffee
+++ app/assets/javascripts/formula_typeahead.js.coffee
@@ -6,26 +6,29 @@

     @$el.typeahead
       source: (query, process) =>
-        # Simple minded suggestion: if there are no ='s after the last separator
-        # (that is, one of ()+&|), then suggest a property, otherwise a value
-        separators = ['(', ')', '+', '&', '|']
+        @suggest query

-        i = Math.max.apply null, $.map(separators, (s) -> query.lastIndexOf(s))
-        # Advance to the last separator
-        prefix  = query.slice 0, i+1
-        current = query.slice i+1, -1
+  suggest: (query) ->
+    # Simple minded suggestion: if there are no ='s after the last separator
+    # (that is, one of ()+&|), then suggest a property, otherwise a value
+    separators = ['(', ')', '+', '&', '|']

-        if current.indexOf('=') != -1
-          # Advance to the = sign ...
-          j = current.indexOf('=')
-          prefix += current.slice 0, j+1
-          current = current.slice j+1, -1
-          # and capture the leading whitespace
-          ws = current.match /^\s+/
-          prefix += ws[0] if ws
-          $.map @values, (v) -> prefix + v
-        else
-          # Capture the leading whitespace
-          ws = current.match /^\s+/
-          prefix += ws[0] if ws
-          $.map @properties, (p) -> prefix + p
+    i = Math.max.apply null, $.map(separators, (s) -> query.lastIndexOf(s))
+    # Advance to the last separator
+    prefix  = query.slice 0, i+1
+    current = query.slice i+1, -1
+
+    if current.indexOf('=') != -1
+      # Advance to the = sign ...
+      j = current.indexOf('=')
+      prefix += current.slice 0, j+1
+      current = current.slice j+1, -1
+      # and capture the leading whitespace
+      ws = current.match /^\s+/
+      prefix += ws[0] if ws
+      $.map @values, (v) -> prefix + v
+    else
+      # Capture the leading whitespace
+      ws = current.match /^\s+/
+      prefix += ws[0] if ws
+      $.map @properties, (p) -> prefix + p


updating documentation on collection class

--- app/assets/javascripts/luca/core/collection.coffee
+++ app/assets/javascripts/luca/core/collection.coffee
@@ -6,13 +6,13 @@
                                     "before:fetch",
                                     "after:response"

-collection.defines
+collection.publicConfiguration
   model: Luca.Model
   # cachedMethods refers to a list of methods on the collection
   # whose value gets cached once it is ran.  the collection then
   # binds to change, add, remove, and reset events and then expires
   # the cached value once these events are fired.
-
+  #
   # cachedMethods expects an array of strings representing the method name
   # or objects containing @method and @resetEvents properties.  by default
   # @resetEvents are 'add','remove',reset' and 'change'.
@@ -22,6 +22,17 @@
   # and return the filtered results that way, then set this to true
   remoteFilter: false

+  # setting a cache key to a string, or to a function which returns
+  # a string, will determine where the collections' models are pulled
+  # from if they are available on page load.  also known as "bootstrapping"
+  # your collections with data.  The cache key will look in whatever object
+  # is set in Luca.config.modelBootstrap for its models.
+  cache_key: undefined
+
+  # Which CollectionManager should we register with? Expects either a string
+  # which will get resolved into a global variable, or a
+  manager: undefined
+
   initialize: (models=[], @options)->
     _.extend @, @options
     @_reset()
@@ -44,7 +55,7 @@
     @name ||= @registerAs
     @manager ||= @registerWith

-    @manager = if _.isFunction(@manager) then @manager() else @manager
+    @manager = Luca.util.read(@manager) if @manager?

     # if they specify a
     if @name and not @manager
@@ -345,6 +356,9 @@
       return Backbone.QueryCollection::query.call(@, filter, options)
     else
       @models
+
+collection.register()
+

 # Global Collection Observer
 _.extend Luca.Collection.prototype,


image resize improvements

image resize improvements

--- app/assets/javascripts/storytime/wysiwyg.js.coffee
+++ app/assets/javascripts/storytime/wysiwyg.js.coffee
@@ -70,10 +70,14 @@
         mediumEditor.deactivate()

     $('body').on "keyup", "#medium-image-width", () ->
-      newWidth = parseFloat($(this).val())
-      $(".medium-active-image").css("width", newWidth)
-      $(".medium-active-image").css("height", "")
-      $("#medium-image-height").val($(".medium-active-image").css("height"))
+      if $(this).val() == "" || $(this).val() == "auto"
+        $(".medium-active-image").css("width", "")
+        $("#medium-image-height").val("")
+      else
+        newWidth = parseFloat($(this).val())
+        $(".medium-active-image").css("width", newWidth)
+        $(".medium-active-image").css("height", "")
+        $("#medium-image-height").val($(".medium-active-image").css("height"))

     $('body').on "keyup", "#medium-image-height", () ->
       # set the height, then find the new width and set that and remove the height so image remains responsive
@@ -88,8 +92,8 @@
   openImageControls: (image) ->
     image.before(editorDiv)
     image.addClass("medium-active-image")
-    $("#medium-image-width").val(image.css("width"))
-    $("#medium-image-height").val(image.css("height"))
+    $("#medium-image-width").val(image[0].style.width)
+    $("#medium-image-height").val(image[0].style.height)

   closeImageControls: ->
     $(".medium-editor img").removeClass("medium-active-image")


Use the presence payload to update the stored user.

--- app/assets/javascripts/views/clouds/chat.js.coffee
+++ app/assets/javascripts/views/clouds/chat.js.coffee
@@ -178,7 +178,7 @@
   # Returns false.
   refreshPresence: (payload) ->
     if @.$(".chat-online-list > li[data-user-id=#{payload.id}]").length == 0
-      user = new Cloudsdale.Models.User(payload)
+      user = session.get('users').findOrInitialize(payload)
       user_view = new Cloudsdale.Views.CloudsChatUser(model: user, topic: @model)
       @.$(".chat-online-list").append(user_view.el)
     else


Moved where we fetch users and starts listening to a cloud.

--- app/assets/javascripts/views/clouds/show.js.coffee
+++ app/assets/javascripts/views/clouds/show.js.coffee
@@ -19,6 +19,9 @@

     @render()
     @bindEvents()
+
+    @model.users().cachedFetch()
+    @model.users().listen()

     @refreshGfx()

@@ -72,9 +75,6 @@
     else
       @chat_view = new Cloudsdale.Views.CloudsChat(model: @model)
       @.$('.float-container > .container-inner > .chat-wrapper').replaceWith(@chat_view.el)
-
-    @model.users().cachedFetch()
-    @model.users().listen()

     # if @.$('.cloud-sidebar-bottom').children().length == 0
     #   @users_view = new Cloudsdale.Views.CloudsUsers(model: @model)


Clean up comments

Clean up comments

--- app/javascript/src/classes/url_hash_handler.coffee
+++ app/javascript/src/classes/url_hash_handler.coffee
@@ -11,8 +11,7 @@
     @current_hash = @parse(@get_raw_hash())
     @normalize @current_hash

-    ### The last value received by the hashchange event: ###
-
+    # The last value received by the hashchange event:
     @last_hashchange = @current_hash.clone()
     @hashchange_event = @hashchange_event.bindAsEventListener(this)
     Element.observe window, 'hashchange', @hashchange_event
@@ -46,7 +45,6 @@
       return
     return

-  ###
   # Set handlers to normalize and denormalize the URL hash.
   #
   # Denormalizing a URL hash can convert the URL hash to something clearer for URLs.  Normalizing
@@ -62,8 +60,6 @@
   #
   # The denormalize callback will only be called with normalized input.  The normalize callback
   # may receive any combination of normalized or denormalized input.
-  ###
-
   set_normalize: (norm, denorm) ->
     @normalize = norm
     @denormalize = denorm
@@ -82,12 +78,9 @@
     @fire_observers old_hash, new_hash
     return

-  ###
   # Parse a hash, returning a Hash.
   #
   # #a/b?c=d&e=f -> {"": 'a/b', c: 'd', e: 'f'}
-  ###
-
   parse: (hash) ->
     if !hash?
       hash = ''
@@ -104,14 +97,11 @@
       while i < hash_query_values.length
         keyval = hash_query_values[i]

-        ### a=b ###
-
+        # a=b
         key = keyval.split('=', 1)[0]

-        ### If the key is blank, eg. "#path?a=b&=d", then ignore the value.  It'll overwrite
+        # If the key is blank, eg. "#path?a=b&=d", then ignore the value.  It'll overwrite
         # the path, which is confusing and never what's wanted.
-        ###
-
         if key == ''
           ++i
           continue
@@ -126,11 +116,8 @@
     s = '#'
     path = hash.get('')
     if path?
-
-      ### For the path portion, we only need to escape the params separator ? and the escape
+      # For the path portion, we only need to escape the params separator ? and the escape
       # character % itself.  Don't use encodeURIComponent; it'll encode far more than necessary.
-      ###
-
       path = path.replace(/%/g, '%25').replace(/\?/g, '%3f')
       s += path
     params = []
@@ -150,12 +137,8 @@
     s

   get_raw_hash: ->
-
-    ###
     # Firefox doesn't handle window.location.hash correctly; it decodes the contents,
     # where all other browsers give us the correct data.  http://stackoverflow.com/questions/1703552
-    ###
-
     pre_hash_part = window.location.href.split('#', 1)[0]
     window.location.href.substr pre_hash_part.length

@@ -167,22 +150,18 @@
   get: (key) ->
     @current_hash.get key

-  ###
   # Set keys in the URL hash.
   #
   # UrlHash.set({id: 50});
   #
   # If replace is true and the History API is available, replace the state instead
   # of pushing it.
-  ###
-
   set: (hash, replace) ->
     new_hash = @current_hash.merge(hash)
     @normalize new_hash
     @set_all new_hash, replace
     return

-  ###
   # Each call to UrlHash.set() will immediately set the new hash, which will create a new
   # browser history slot.  This isn't always wanted.  When several changes are being made
   # in response to a single action, all changes should be made simultaeously, so only a
@@ -200,8 +179,6 @@
   # If replace is true and the History API is available, replace the state instead of pushing
   # it.  If any set_deferred call consolidated into a single update has replace = false, the
   # new state will be pushed.
-  ###
-
   set_deferred: (hash, replace) ->
     @deferred_sets.push hash
     if replace
@@ -231,10 +208,8 @@
     new_hash = @construct(query_params)
     if window.location.hash != new_hash

-      ### If the History API is available, use it to support URL replacement.  FF4.0's pushState
+      # If the History API is available, use it to support URL replacement.  FF4.0's pushState
       # is broken; don't use it.
-      ###
-
       if window.history and window.history.replaceState and window.history.pushState and !navigator.userAgent.match('Firefox/[45].')
         url = window.location.protocol + '//' + window.location.host + window.location.pathname + new_hash
         if replace
@@ -244,16 +219,13 @@
       else
         window.location.hash = new_hash

-    ### Explicitly fire the hashchange event, so it's handled quickly even if the browser
+    # Explicitly fire the hashchange event, so it's handled quickly even if the browser
     # doesn't support the event.  It's harmless if we get this event multiple times due
     # to the browser delivering it normally due to our change.
-    ###
-
     @hashchange_event null
     return

-  ### Observe changes to the specified key.  If key is null, watch for all changes. ###
-
+  # Observe changes to the specified key.  If key is null, watch for all changes.
   observe: (key, func) ->
     observers = @observers.get(key)
     if !observers?


fix axis creation for spectrogram

fix axis creation for spectrogram

LinearAxis exposed as model/collection

--- bokehjs/demo/spectrogram/coffee/spectrogram.coffee
+++ bokehjs/demo/spectrogram/coffee/spectrogram.coffee
@@ -173,7 +173,7 @@

 class SpectrogramPlot
   constructor: (options) ->
-    @cmap = new LinearColorMapper({}, {
+    @cmap = new LinearColorMapper.Model({
       palette: all_palettes["YlGnBu-9"],
       low: 0,
       high: 10


map the 'retain' values to session ids

map the 'retain' values to session ids

--- services/web/app/coffee/Features/User/UserSessionsManager.coffee
+++ services/web/app/coffee/Features/User/UserSessionsManager.coffee
@@ -61,6 +61,7 @@
 	revokeAllUserSessions: (user, retain, callback=(err)->) ->
 		if !retain
 			retain = []
+		retain = retain.map((i) -> UserSessionsManager._sessionKey(i))
 		if !user
 			logger.log {}, "no user to revoke sessions for, returning"
 			return callback(null)
@@ -70,7 +71,7 @@
 			if err
 				logger.err {err, user_id: user._id, sessionSetKey}, "error getting contents of UserSessions set"
 				return callback(err)
-			keysToDelete = _.filter(sessionKeys, (k) => k not in retain)
+			keysToDelete = _.filter(sessionKeys, (k) -> k not in retain)
 			logger.log {user_id: user._id, count: keysToDelete.length}, "deleting sessions for user"
 			rclient.multi()
 				.del(keysToDelete)


Hide V1 projects from shared filter

Hide V1 projects from shared filter

--- services/web/public/coffee/main/project-list/project-list.coffee
+++ services/web/public/coffee/main/project-list/project-list.coffee
@@ -114,6 +114,10 @@

 				# Hide projects we own if we only want to see shared projects
 				if $scope.filter == "shared" and project.accessLevel == "owner"
+					visible = false
+
+				# Hide projects from V1 if we only want to see shared projects
+				if $scope.filter == "shared" and project.isOLProject
 					visible = false

 				# Hide projects we don't own if we only want to see owned projects


Add fetchMongoDatabases wrapper method, add meta data for mysql

--- kites/databases/api.coffee
+++ kites/databases/api.coffee
@@ -49,7 +49,8 @@
         delete set.Db
         set.dbUser = set.User
         delete set.User
-
+        set.dbType = 'mysql'
+        set.dbHost = mySQL.dbHost
       __resReport(error,result,callback)

   createMysqlDatabase : (options,callback)->
@@ -108,6 +109,19 @@
   #***************** MongoDB ********************#
   #**********************************************#

+  fetchMongoDatabases : (options, callback)->
+
+    # this method will list mysql databases for the user
+    # object will be returned:
+
+    #
+    # options =
+    #   username : String # Kodingen username, db username will be generated wiht username+dbName (db username max 16 symbols, will be truncated)
+    #     ^^^^ wrong - this kite should not know anything about how kodingen works
+
+    mongoDB.fetchDatabaseList options,(error,result)->
+      __resReport(error,result,callback)
+
   createMongoDatabase : (options,callback)->

     # this method will create mongoDB database and user


Restore dropdown state when unchecking all issues

--- app/assets/javascripts/labels_select.js.coffee
+++ app/assets/javascripts/labels_select.js.coffee
@@ -318,6 +318,20 @@
             @indeterminateIds = _this.getIndeterminateIds()
       )

+    @bindEvents()
+
+  bindEvents: ->
+    $('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
+
+  onSelectCheckboxIssue: ->
+    return if $('.selected_issue:checked').length
+
+    # Remove inputs
+    $('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
+
+    # Also restore button text
+    $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
+
   getIndeterminateIds: ->
     label_ids = []


Bug fix in ResourceProxy test.

Bug fix in ResourceProxy test.

--- test-src/browser/resource_proxy.coffee
+++ test-src/browser/resource_proxy.coffee
@@ -47,8 +47,8 @@
         test.done()

     'test fetch' : (test) ->
-        server = new Server(Path.join(__dirname, '..', '..',
-                            'test-src', 'files'))
+        filesPath = Path.join(__dirname, '..', '..', 'test-src', 'files')
+        server = new Server(filesPath)
         # mock response obejct
         class Response
             constructor : (expected) ->


Removing an non UTF-8 character

Removing an non UTF-8 character

--- app/assets/javascripts/socialhistory.js.coffee
+++ app/assets/javascripts/socialhistory.js.coffee
@@ -7,7 +7,7 @@

 ###*

-The Social History Observation is used to define the patients occupational, personal (e.g. lifestyle),
+The Social History Observation is used to define the patient's occupational, personal (e.g. lifestyle),
 social, and environmental history and health risk factors, as well as administrative data such as
 marital status, race, ethnicity and religious affiliation. The types of conditions
 described have been constrained to the SNOMED CT code system using constrained code set, 2.16.840.1.113883.3.88.12.80.60:


refactor updateUserInfo function

refactor updateUserInfo function

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1291,15 +1291,15 @@
       callback null, { user, account }

-  updateUserInfo = (options, queue, callback, fetchData) ->
+  updateUserInfo = (options, callback) ->

     { user, ip, country, region, username, client, account, clientId } = options

-    _queue = [
+    queue = [

       ->
         # updating user's location related info
-        return _queue.next()  unless ip? and country? and region?
+        return queue.next()  unless ip? and country? and region?

         locationModifier =
           $set                       :
@@ -1308,15 +1308,15 @@
             'registeredFrom.country' : country

         user.update locationModifier, ->
-          _queue.next()
+          queue.next()

       ->
         JUser.persistOauthInfo username, client.sessionToken, (err) ->
           return callback err  if err
-          _queue.next()
-
-      ->
-        return _queue.next()  unless username?
+          queue.next()
+
+      ->
+        return queue.next()  unless username?

         _options =
           account        : account
@@ -1327,15 +1327,11 @@

         JUser.changeUsernameByAccount _options, (err, newToken_) ->
           return callback err  if err
-          fetchData newToken_
-          _queue.next()
-
-      ->
-        queue.next()
+          return callback null, newToken_

     ]

-    daisy _queue
+    daisy queue

   updateAccountInfo = (options, queue, callback) ->
@@ -1487,18 +1483,22 @@
           queue.next()

       ->
-        userInfo =
-          { email, username, password, lastName, firstName, emailFrequency }
-
+        userInfo = {
+          email, username, password, lastName, firstName, emailFrequency
+        }
         createUser { userInfo }, (err, data) ->
           return callback err  if err
           { user, account } = data
           queue.next()

       ->
-        updateUserInfo {
+        params = {
           user, ip, country, region, username, client, account, clientId
-        }, queue, callback, (newToken_) -> newToken = newToken_
+        }
+        updateUserInfo params, (err, newToken_) ->
+          return callback err  if err
+          newToken = newToken_
+          queue.next()

       ->
         updateAccountInfo { account, referrer, username }, queue, callback


DevTools: When workspace ready follow change events from editors and call proper method to compile&run the app in preview view

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -98,6 +98,17 @@
             }
           ]
       ]
+
+    @workspace.ready =>
+
+      {JSEditor, CSSEditor} = panes = @workspace.activePanel.panesByName
+
+      JSEditor.codeMirrorEditor.on "change", \
+        _.debounce (@bound 'previewApp'), 500
+
+      CSSEditor.codeMirrorEditor.on "change", \
+        _.debounce (@bound 'previewCss'), 500
+
   previewApp:->

     time 'Compile took:'


client: fix copy

client: fix copy

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -412,7 +412,7 @@
       numberOfVms = Object.keys(KD.singletons.vmController.vmsInfo).length

     if title and /CPU limit reached/.test title
-      title = "You've exceeded the allowed number of concurrent vms. Please upgrade."
+      title = "Please upgrade to run more VMs"
       KD.remote.api.JErrorLog.create { error : "cpu_limit_reached", numberOfVms }, ->
     else
       title = "Your vm failed to start. Please try again later."


Fixed: default calendar for new event

Fixed: default calendar for new event

--- client/app/models/event.coffee
+++ client/app/models/event.coffee
@@ -8,10 +8,11 @@
     urlRoot: 'events'

     defaults: ->
+        defaultCalendar =  window.app.tags.at(0) or t('default calendar name')
         details: ''
         description: ''
         place: ''
-        tags: [t('default calendar name')]
+        tags: [defaultCalendar]

     getDiff: ->
         return @getEndDateObject().diff @getStartDateObject(), 'days'


added delete account view to includes

added delete account view to includes

--- client/landing/includes.coffee
+++ client/landing/includes.coffee
@@ -708,6 +708,7 @@
   "app/Applications/Account.kdapplication/account/accSettingsPaymentMethods.coffee",
   "app/Applications/Account.kdapplication/account/accSettingsSubscriptions.coffee",
   "app/Applications/Account.kdapplication/account/accSettingsPersReferralSystem.coffee",
+  "app/Applications/Account.kdapplication/account/deleteaccountview.coffee",
   "app/Applications/Account.kdapplication/AppView.coffee",

   # GROUP DASHBOARD


Update index.coffee

Update index.coffee

--- client/stacks/lib/index.coffee
+++ client/stacks/lib/index.coffee
@@ -21,7 +21,7 @@
       items    : [
         { slug : 'My-Stacks',               title : 'My Stacks',              viewClass : YourStacksView }
         { slug : 'My-Stack-Templates',      title : 'My Stack Templates',     viewClass : MyStackTemplatesView }
-        { slug : 'Group-Stack-Templates',   title : 'Group Stack Templates',  viewClass : GroupStackTemplatesView }
+        { slug : 'Group-Stack-Templates',   title : 'Team Stack Templates',   viewClass : GroupStackTemplatesView }
       ]


JCredential: setPermissionFor to support mode options

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -273,7 +273,7 @@
             callback null, []

-  setPermissionFor: (target, { user, owner, role }, callback) ->
+  setPermissionFor: (target, { user, owner, role, mode }, callback) ->

     Relationship.remove
       targetId : @getId()
@@ -281,8 +281,13 @@
     , (err) =>

       if user
-        options      = { as: if owner then 'owner' else 'user' }
-        options.data = { role }  if role
+        options = { as: if owner then 'owner' else 'user' }
+
+        if role or mode
+          options.data = {}
+          options.data.role = role  if role
+          options.data.mode = mode  if mode
+
         target.addCredential this, options, (err) -> callback err
       else
         callback err
@@ -291,7 +296,7 @@
   shareWith: (client, options, callback) ->

     { delegate } = client.connection
-    { target, user, owner, role } = options
+    { target, user, owner, role, mode } = options
     user ?= yes

     # Owners cannot unassign them from a credential
@@ -311,10 +316,10 @@
         target.fetchOwnAccount (err, account) =>
           if err or not account
             return callback new KodingError 'Failed to fetch account.'
-          @setPermissionFor account, { user, owner }, callback
+          @setPermissionFor account, { user, owner, mode }, callback

       else if target instanceof JGroup
-        @setPermissionFor target, { user, owner, role }, callback
+        @setPermissionFor target, { user, owner, role, mode }, callback

       else
         callback new KodingError 'Target does not support credentials.'


Groups: emails are trimmed for preventing empty lines

--- client/Dashboard/views/groupsinvitationview.coffee
+++ client/Dashboard/views/groupsinvitationview.coffee
@@ -138,6 +138,7 @@
       callback           : ({emails, message, saveMessage})=>

         KD.whoami().fetchFromUser "email", (err, userEmail)=>
+          emails = emails.trim()
           emailList = emails.split(/\n/).map (email)-> email.trim()
           if userEmail in emailList
             @inviteByEmail.modalTabs.forms.invite.buttons.Send.hideLoader()


ComputePlansModalFree: use provided plan to set message

--- client/Main/providers/computeplansmodalfree.coffee
+++ client/Main/providers/computeplansmodalfree.coffee
@@ -2,15 +2,18 @@

   constructor:(options = {}, data)->

-    super
-      cssClass : 'free-plan'
-      message  : options.message ? "Free users are restricted to one VM.
"
+    options.cssClass = 'free-plan'
+    super options, data
+

   viewAppended:->

     @addSubView content = new KDView
-      cssClass : 'message'
-      partial  : @getOption 'message'
+      cssClass     : 'message'
+      partial      : {
+        'free'     : "Free users are restricted to one VM.
"
+        'hobbyist' : "Hobbyist plan is restricted to only one VM.
"
+      }[@getOption 'plan']

     content.addSubView new CustomLinkView
       title    : 'Upgrade your account for more VMs RAM and Storage'


Payment modal for VM creation

Payment modal for VM creation

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -112,6 +112,40 @@
     canCreateSharedVM   = "owner" in KD.config.roles or "admin" in KD.config.roles
     canCreatePersonalVM = "member" in KD.config.roles

+    # Take this to a better place, possibly to payment controller.
+    makePayment = (type, host, callback)->
+      if type is 'shared'
+        group = KD.singletons.groupsController.getCurrentGroup()
+        group.checkPayment (err, payments)->
+          if err or payments.length is 0
+
+            # Copy account creator's billing information
+            KD.remote.api.JRecurlyPlan.getUserAccount (err, data)->
+              warn err
+              if err or not data
+                data = {}
+
+              # These will go into Recurly module
+              delete data.cardNumber
+              delete data.cardMonth
+              delete data.cardYear
+              delete data.cardCV
+
+              modal = createAccountPaymentMethodModal data, (newData, onError, onSuccess)->
+                group.makePayment newData, (err, subscription)->
+                  if err
+                    onError err
+                  else
+                    KD.singletons.vmController.createGroupVM type, vmCreateCallback
+                    onSuccess()
+                    callback()
+          else
+            KD.singletons.vmController.createGroupVM type, vmCreateCallback
+            callback()
+      else
+        KD.singletons.vmController.createGroupVM type, vmCreateCallback
+        callback()
+
     vmCreateCallback  = (err, vm)->
       if err
         warn err
@@ -165,10 +199,9 @@

                 form                = modal.modalTabs.forms["Create VM"]
                 {personal, shared}  = form.buttons
-
-                return log formData, "take from here and make the payment"
-
-                @createGroupVM formData.type, vmCreateCallback
+
+                makePayment formData.type, @paymentPlans[formData.host].code, ->
+                  modal.destroy()
               buttons               :
                 personal            :
                   title             : "Create a <b>Personal</b> VM"


Environment: ActionTypes: Define new action types

--- client/app/lib/flux/environment/actiontypes.coffee
+++ client/app/lib/flux/environment/actiontypes.coffee
@@ -13,4 +13,12 @@
   'INVITATION_ACCEPTED'
   'COLLABORATION_INVITATION_REJECTED'
   'SHARED_VM_INVITATION_REJECTED'
+
+  'WORKSPACE_CREATED'
+  'WORKSPACE_DELETED'
+  'WORKSPACE_COULD_NOT_CREATE'
+  'SHOW_ADD_WORKSPACE_VIEW'
+  'HIDE_ADD_WORKSPACE_VIEW'
+  'WORKSPACE_DELETED_FAIL'
+  'WORKSPACE_SELECTED'
 }


make teaspoon happy

make teaspoon happy

--- test/javascripts/models/paper_test.js.coffee
+++ test/javascripts/models/paper_test.js.coffee
@@ -1,7 +1,7 @@
 #= require test_helper

 moduleForModel 'paper', 'Unit: Paper Model',
-  needs: ['model:user', 'model:figure', 'model:journal', 'model:phase', 'model:task', 'model:comment', 'model:litePaper']
+  needs: ['model:user', 'model:figure', 'model:journal', 'model:supportingInformationFile', 'model:phase', 'model:task', 'model:comment', 'model:litePaper']

 test 'displayTitle displays short title if title is missing', ->
   shortTitle = 'test short title'


deleted a file

deleted a file

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -1,311 +0,0 @@
-path = require 'path'
-_ = require 'underscore-plus'
-{CompositeDisposable, Emitter} = require 'event-kit'
-fs = require 'fs-plus'
-PathWatcher = require 'pathwatcher'
-File = require './file'
-{repoForPath} = require './helpers'
-realpathCache = {}
-
-module.exports =
-class Directory
-  constructor: ({@allowedFiles, @name, fullPath, @symlink, @expansionState, @isRoot, @ignoredPatterns, @useSyncFS, @stats}) ->
-
-    @destroyed = false
-    @emitter = new Emitter()
-    @subscriptions = new CompositeDisposable()
-
-    if atom.config.get('tree-view.squashDirectoryNames')
-      fullPath = @squashDirectoryNames(fullPath)
-
-    @path = fullPath
-    @realPath = @path
-    if fs.isCaseInsensitive()
-      @lowerCasePath = @path.toLowerCase()
-      @lowerCaseRealPath = @lowerCasePath
-
-    @isRoot ?= false
-    @expansionState ?= {}
-    @expansionState.isExpanded ?= false
-    @expansionState.entries ?= {}
-    @status = null
-    @entries = {}
-
-    @submodule = repoForPath(@path)?.isSubmodule(@path)
-
-    @subscribeToRepo()
-    @updateStatus()
-    @loadRealPath()
-
-  destroy: ->
-    @destroyed = true
-    @unwatch()
-    @subscriptions.dispose()
-    @emitter.emit('did-destroy')
-
-  onDidDestroy: (callback) ->
-    @emitter.on('did-destroy', callback)
-
-  onDidStatusChange: (callback) ->
-    @emitter.on('did-status-change', callback)
-
-  onDidAddEntries: (callback) ->
-    @emitter.on('did-add-entries', callback)
-
-  onDidRemoveEntries: (callback) ->
-    @emitter.on('did-remove-entries', callback)
-
-  onDidCollapse: (callback) ->
-    @emitter.on('did-collapse', callback)
-
-  onDidExpand: (callback) ->
-    @emitter.on('did-expand', callback)
-
-  loadRealPath: ->
-    if @useSyncFS
-      @realPath = fs.realpathSync(@path)
-      @lowerCaseRealPath = @realPath.toLowerCase() if fs.isCaseInsensitive()
-    else
-      fs.realpath @path, realpathCache, (error, realPath) =>
-        return if @destroyed
-        if realPath and realPath isnt @path
-          @realPath = realPath
-          @lowerCaseRealPath = @realPath.toLowerCase() if fs.isCaseInsensitive()
-          @updateStatus()
-
-  # Subscribe to project's repo for changes to the Git status of this directory.
-  subscribeToRepo: ->
-    repo = repoForPath(@path)
-    return unless repo?
-
-    @subscriptions.add repo.onDidChangeStatus (event) =>
-      @updateStatus(repo) if @contains(event.path)
-    @subscriptions.add repo.onDidChangeStatuses =>
-      @updateStatus(repo)
-
-  # Update the status property of this directory using the repo.
-  updateStatus: ->
-    repo = repoForPath(@path)
-    return unless repo?
-
-    newStatus = null
-    if repo.isPathIgnored(@path)
-      newStatus = 'ignored'
-    else
-      status = repo.getDirectoryStatus(@path)
-      if repo.isStatusModified(status)
-        newStatus = 'modified'
-      else if repo.isStatusNew(status)
-        newStatus = 'added'
-
-    if newStatus isnt @status
-      @status = newStatus
-      @emitter.emit('did-status-change', newStatus)
-
-  # Is the given path ignored?
-  isPathIgnored: (filePath) ->
-    if atom.config.get('tree-view.hideVcsIgnoredFiles')
-      repo = repoForPath(@path)
-      return true if repo? and repo.isProjectAtRoot() and repo.isPathIgnored(filePath)
-
-    if atom.config.get('tree-view.hideIgnoredNames')
-      for ignoredPattern in @ignoredPatterns
-        return true if ignoredPattern.match(filePath)
-
-    false
-
-  # Does given full path start with the given prefix?
-  isPathPrefixOf: (prefix, fullPath) ->
-    fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep
-
-  isPathEqual: (pathToCompare) ->
-    @path is pathToCompare or @realPath is pathToCompare
-
-  # Public: Does this directory contain the given path?
-  #
-  # See atom.Directory::contains for more details.
-  contains: (pathToCheck) ->
-    return false unless pathToCheck
-
-    # Normalize forward slashes to back slashes on windows
-    pathToCheck = pathToCheck.replace(/\//g, '\\') if process.platform is 'win32'
-
-    if fs.isCaseInsensitive()
-      directoryPath = @lowerCasePath
-      pathToCheck = pathToCheck.toLowerCase()
-    else
-      directoryPath = @path
-
-    return true if @isPathPrefixOf(directoryPath, pathToCheck)
-
-    # Check real path
-    if @realPath isnt @path
-      if fs.isCaseInsensitive()
-        directoryPath = @lowerCaseRealPath
-      else
-        directoryPath = @realPath
-
-      return @isPathPrefixOf(directoryPath, pathToCheck)
-
-    false
-
-  # Public: Stop watching this directory for changes.
-  unwatch: ->
-    if @watchSubscription?
-      @watchSubscription.close()
-      @watchSubscription = null
-
-    for key, entry of @entries
-      entry.destroy()
-      delete @entries[key]
-
-  # Public: Watch this directory for changes.
-  watch: ->
-    try
-      @watchSubscription ?= PathWatcher.watch @path, (eventType) =>
-        switch eventType
-          when 'change'  then @reload()
-          when 'delete' then @destroy()
-
-
-  getEntries: ->
-    try
-      names = fs.readdirSync(@path)
-
-    catch error
-      names = []
-    names.sort(new Intl.Collator(undefined, {numeric: true, sensitivity: "base"}).compare)
-
-    files = []
-    directories = []
-
-    for name in names
-      fullPath = path.join(@path, name)
-
-      #console.log("FILE = "+fullPath)
-      #continue if !name.includes("json")
-      continue if @isPathIgnored(fullPath)
-      #filter here
-      #
-      #if !fullPath.includes(".coffee") then continue
-      stat = fs.lstatSyncNoException(fullPath)
-      symlink = stat.isSymbolicLink?()
-      stat = fs.statSyncNoException(fullPath) if symlink
-      statFlat = _.pick stat, _.keys(stat)...
-      for key in ["atime", "birthtime", "ctime", "mtime"]
-        statFlat[key] = statFlat[key]?.getTime()
-
-      if stat.isDirectory?()
-        if(!@allowedFiles.isDirAllowed(fullPath)) then  continue
-        if @entries.hasOwnProperty(name)
-          # push a placeholder since this entry already exists but this helps
-          # track the insertion index for the created views
-          directories.push(name)
-        else
-          expansionState = @expansionState.entries[name]
-          directories.push(new Directory({@allowedFiles, name, fullPath, symlink, expansionState, @ignoredPatterns, @useSyncFS, stats: statFlat}))
-      else if stat.isFile?()
-        if @entries.hasOwnProperty(name)
-          # push a placeholder since this entry already exists but this helps
-          # track the insertion index for the created views
-          files.push(name)
-        else
-          #console.log("FILE NAME "+fullPath)
-          #filter here
-          if(@allowedFiles.isFileAllowed(fullPath))
-          #console.log("@allowedFiles")
-          #console.log(@allowedFiles)
-            files.push(new File({name, fullPath, symlink, realpathCache, @useSyncFS, stats: statFlat}))
-
-    @sortEntries(directories.concat(files))
-
-  normalizeEntryName: (value) ->
-    normalizedValue = value.name
-    unless normalizedValue?
-      normalizedValue = value
-    if normalizedValue?
-      normalizedValue = normalizedValue.toLowerCase()
-    normalizedValue
-
-  sortEntries: (combinedEntries) ->
-    if atom.config.get('tree-view.sortFoldersBeforeFiles')
-      combinedEntries
-    else
-      combinedEntries.sort (first, second) =>
-        firstName = @normalizeEntryName(first)
-        secondName = @normalizeEntryName(second)
-        firstName.localeCompare(secondName)
-
-  # Public: Perform a synchronous reload of the directory.
-  reload: ->
-    newEntries = []
-    removedEntries = _.clone(@entries)
-    index = 0
-
-    for entry in @getEntries()
-      if @entries.hasOwnProperty(entry)
-        delete removedEntries[entry]
-        index++
-        continue
-
-      entry.indexInParentDirectory = index
-      index++
-      newEntries.push(entry)
-
-    entriesRemoved = false
-    for name, entry of removedEntries
-      entriesRemoved = true
-      entry.destroy()
-
-      if @entries.hasOwnProperty(name)
-        delete @entries[name]
-
-      if @expansionState.entries.hasOwnProperty(name)
-        delete @expansionState.entries[name]
-
-    @emitter.emit('did-remove-entries', removedEntries) if entriesRemoved
-
-    if newEntries.length > 0
-      @entries[entry.name] = entry for entry in newEntries
-      @emitter.emit('did-add-entries', newEntries)
-
-  # Public: Collapse this directory and stop watching it.
-  collapse: ->
-    @expansionState.isExpanded = false
-    @expansionState = @serializeExpansionState()
-    @unwatch()
-    @emitter.emit('did-collapse')
-
-  # Public: Expand this directory, load its children, and start watching it for
-  # changes.
-  expand: ->
-    @expansionState.isExpanded = true
-    @reload()
-    @watch()
-    @emitter.emit('did-expand')
-
-  serializeExpansionState: ->
-    expansionState = {}
-    expansionState.isExpanded = @expansionState.isExpanded
-    expansionState.entries = {}
-    for name, entry of @entries when entry.expansionState?
-      expansionState.entries[name] = entry.serializeExpansionState()
-    expansionState
-
-  squashDirectoryNames: (fullPath) ->
-    squashedDirs = [@name]
-    loop
-      try
-        contents = fs.listSync fullPath
-      catch error
-        break
-      break if contents.length isnt 1
-      break if not fs.isDirectorySync(contents[0])
-      relativeDir = path.relative(fullPath, contents[0])
-      squashedDirs.push relativeDir
-      fullPath = path.join(fullPath, relativeDir)
-
-    if squashedDirs.length > 1
-      @squashedNames = [squashedDirs[0..squashedDirs.length - 2].join(path.sep) + path.sep, _.last(squashedDirs)]
-
-    return fullPath


Correct a typo, add some tests for consistency

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -59,7 +59,7 @@
           continue if (computedStyle.getPropertyValue('float') == 'none' &&
             computedStyle.getPropertyValue('position') != 'absolute')
           childClientRect = @getVisibleClientRect(child)
-          continue if clientRect == null
+          continue if childClientRect == null or childClientRect.width < 3 or childClientRect.height < 3
           return childClientRect

       else


Dragging out a starfish marker updates its bounding circle. Closes #6.

--- scripts/controllers/CreaturePicker.coffee
+++ scripts/controllers/CreaturePicker.coffee
@@ -115,10 +115,18 @@

 		line

+	createStrayBoundingCircle: (cx, cy) =>
+		circle = @paper.circle cx, cy
+		circle.attr style.line
+		#@strayAxes.push circle
+
+		circle
+
 	dragThreshold: 10
 	mouseMoves: 0
 	movementCircle: null
 	movementAxis: null
+	movementBoundingCircle: null
 	onMouseMove: (e) =>
 		return unless @mouseIsDown and not @disabled
 		return if @strayCircles.length % 2 is 0 and not @movementCircle
@@ -126,10 +134,14 @@
 		@mouseMoves += 1
 		return if @mouseMoves < @dragThreshold

-		@movementCircle = @createStrayCircle() unless @movementCircle?
-		@movementAxis = @createStrayAxis() unless @movementAxis?
+		@movementCircle ||= @createStrayCircle()
+		@movementAxis ||= @createStrayAxis()
+
+		if @selectedSpecies is 'seastar'
+			@movementBoundingCircle ||= @createStrayBoundingCircle @strayCircles[0].attr('cx'), @strayCircles[0].attr('cy')

 		{left, top} = @selectionArea.offset()
+
 		@movementCircle.attr
 			cx: e.pageX - left
 			cy: e.pageY - top
@@ -137,16 +149,22 @@
 		secondLastCircle = @strayCircles[@strayCircles.length - 2]
 		@movementAxis.attr
 			path: Marker::lineBetween secondLastCircle, @movementCircle
+
+		@movementBoundingCircle?.attr
+			r: @movementAxis.getTotalLength()

 	onMouseUp: (e) =>
 		return unless @mouseIsDown and not @disabled
 		@mouseIsDown = false
 		@mouseMoves = 0

+		@checkStrays()
+
 		@movementCircle = null
 		@movementAxis = null

-		@checkStrays()
+		@movementBoundingCircle?.remove()
+		@movementBoundingCircle = null

 	createCircleMarker: (x, y) =>
 		marking = @createMarking()


Caching header items.

Caching header items.

--- edf.coffee
+++ edf.coffee
@@ -33,7 +33,7 @@
 		# Open a handle.
 		@_handle	= fs.openSync @edf_path, "r"

-		@_header_spec	= { }
+		@_header_item	= { }

 		# Run through and populate _signals using the specs.
 		for i in [0..parseInt(@get_header_item( "num_signals_in_data_record"))]
@@ -55,23 +55,15 @@

 	_get_header_spec: ( name ) ->

-		# If we have a local instance copy of the data already, simply return it.
-		if @_header_spec[name]?
-			return @_header_spec[name]
-
 		position = 0

 		# Iterate over all the spec objects.
 		for x in _header_spec

-			# Match found. Figure out the position of it, set to local instance
-			# return.
+			# Match found. Figure out the position of it, return.
 			if x.name is name
 				_o		= x
 				_o["position"]	= position
-
-				# Set to local instance so that we can return it faster later on.
-				@_header_spec[name]	= _o

 				return _o

@@ -116,9 +108,19 @@
 		k

 	get_header_item: ( name ) ->
-		# Wraps the _get_header_spec call and _get_buffer_slice into one handy method.
+
+		# Check local instance cache first.
+		if @_header_item[name]?
+			return @_header_item[name]
+
+		# Get the spec for the given header item.
 		spec = @_get_header_spec name
-		@_get_buffer_slice( spec.length, spec.position ).toString( ).trim( )
+
+		# Set the instance wide cache to be the slice..
+		@_header_item[name] = @_get_buffer_slice( spec.length, spec.position ).toString( ).trim( )
+
+		# Return the now cached item.
+		@_header_item[name]

 	get_signal_item: ( signal_index, name ) ->
 		# Wraps getting the signal spec and slicing the buffer and returning a string.


Center the search result when it’s offscreen

Fixes #278

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -296,7 +296,9 @@
     return unless @markers?.length > 0

     if marker = @markers[markerIndex]
-      @findModel.getEditor().setSelectedBufferRange(marker.getBufferRange(), autoscroll: true, flash: true)
+      editor = @findModel.getEditor()
+      editor.setSelectedBufferRange(marker.getBufferRange(), flash: true)
+      editor.scrollToCursorPosition(center: true)

   setSelectionAsFindPattern: =>
     pattern = @findModel.getEditor().getSelectedText()


Fixes display bug in ride engine

Fixes display bug in ride engine

--- coffee/game/ride_engine.coffee
+++ coffee/game/ride_engine.coffee
@@ -46,7 +46,8 @@
                               () =>
                                 z = new PickupZone(id, @grid)
                                 z.setDuration @pickupDuration
-                                z.setAnimationDelay = @pickupAnimationDelay
+                                z.setAnimationDelay @pickupAnimationDelay
+                                z.show()
                                 @pickupZones[id] = z
                                 id++
                             ,
@@ -67,6 +68,7 @@
         d = new DropZone(id, @grid, zone.getColor())
         d.setDuration @dropDuration
         d.setAnimationDelay @dropAnimationDelay
+        d.show()
         @dropZones[id] =
           startTime: Date.now()
           zone: d


made everything work:

made everything work:

fixed some for loops not iterating the correct number of times

nextToken was not being calculated correctly

added a 'defaultReply' in case the bot is dumb and empty

made the quad be chosen differently depending on the origin

fixed a pointer bug

--- lib/jsMegaHal.coffee
+++ lib/jsMegaHal.coffee
@@ -31,7 +31,7 @@
 	###
 	@markov - the markov order to use for this jsMegaHal instance, defaults to 4
 	###
-	constructor: (@markov = 4) ->
+	constructor: (@markov = 4, @defaultReply = '') ->

 	###
 	generate a number between min and max, inclusive
@@ -79,7 +79,8 @@
 		partsLength = parts.length

 		#split the tokens into quads of @markov length
-		for i in [0..partsLength - @markov - 1] by 1
+		for i in [0...partsLength - (@markov - 1)] by 1
+
 			quad = new Quad [ parts[i..i+3]... ]
 			@addQuad quad

@@ -100,7 +101,7 @@

 			#and then get the next tokens
 			if i < partsLength - @markov
-				nextToken = parts[i+1]
+				nextToken = parts[i+@markov]
 				@next[quad.hash()] = [] if not (quad.hash() of @next)
 				@next[quad.hash()].push nextToken

@@ -126,14 +127,18 @@
 		if word and (word of @words) then quads = @words[word] else quads = Object.keys @quads

 		#empty brain? nothing to say
-		return if quads.length is 0
+		return @defaultReply if quads.length is 0

-		quad = middleQuad = quads[@randomInt 0, quads.length-1]
+		#quads are generated differently depending on origin
+		quadHash = quads[@randomInt 0, quads.length-1]
+		quadHash = quadHash.tokens.join(',') if typeof quadHash isnt 'string'
+		quad = middleQuad = @quads[quadHash]

-		parts = quad.tokens
+		#shallow copy this guy, otherwise problems happen
+		parts = quad.tokens.slice 0

 		#while we don't have an end, generate a next token
-		while not quad?.canEnd
+		while not quad.canEnd
 			nextTokens = @next[quad.hash()]

 			#no tokens, no dice -- skip
@@ -141,7 +146,7 @@
 			nextToken = nextTokens[@randomInt 0, nextTokens.length-1]

 			#we have a new quad, so we should probably ensure it's in our quad list
-			newQuad = new Quad [quad.tokens[0..@markov-1]..., nextToken]
+			newQuad = new Quad [quad.tokens[1...@markov]..., nextToken]
 			@addQuad newQuad

 			#change to our new quad
@@ -151,12 +156,12 @@
 		quad = middleQuad

 		#while we don't have a beginning, generate a previous token
-		while not quad?.canStart
+		while not quad.canStart
 			prevTokens = @prev[quad.hash()]
 			break if not prevTokens
 			prevToken = prevTokens[@randomInt 0, prevTokens.length-1]

-			newQuad = new Quad [quad.tokens[0..@markov-1]..., prevToken]
+			newQuad = new Quad [prevToken, quad.tokens[0...@markov-1]...]
 			@addQuad newQuad
 			quad = @quads[newQuad.hash()]
 			parts.unshift prevToken


Added support for TypeScript

Added support for TypeScript

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -252,6 +252,14 @@
     singleLineComment: ['--']
     ignorePrefix:      '}'
     foldPrefix:        '^'
+
+  TypeScript:
+    nameMatchers:      ['.ts']
+    pygmentsLexer:     'ts'
+    multiLineComment:  ['/*', '*', '*/']
+    singleLineComment: ['//']
+    ignorePrefix:      '}'
+    foldPrefix:        '^'

   YAML:
     nameMatchers:      ['.yml', '.yaml']


Tune daybreak

Tune daybreak

--- src/lazerscripts/stage1/sunrise.coffee
+++ src/lazerscripts/stage1/sunrise.coffee
@@ -78,7 +78,7 @@
     speed = @options.speed ? 1
     @options.skipTo ?= 0

-    colorDuration = (30000 / speed)
+    colorDuration = (45000 / speed)
     @sequence(
       @setLocation x: .97, y: .74
       @parallel(
@@ -90,7 +90,7 @@
         )
         @movePath [
           [.75, .61]
-        ], rotate: no, skip: @options.skipTo, speed: speed * 20
+        ], rotate: no, skip: @options.skipTo, speed: speed * 10
       )
     )


Change the selected illustration when a point moves Listen to point marks changing. Try to check if they have moved inside a rectangle and select that rectangle if so. Use a timeout to throttle the checking/looping somewhat.

--- overrides.coffee
+++ overrides.coffee
@@ -138,6 +138,22 @@
   current_tool = (tool for tool in ms.tools when tool.outline?.el is e.target)[0]
   current_tool?.el.classList.add 'selected'

+moving = false
+ms.on 'marking-surface:change', (mark) ->
+  unless moving
+    moving = true
+    setTimeout ->
+      if mark?._taskIndex is 2
+        current_tool?.el.classList.remove 'selected'
+
+        for tool in ms.tools when tool.mark._taskIndex is 1
+          rectangle = tool.mark
+          if mark.inside rectangle
+            current_tool = tool
+            current_tool?.el.classList.add 'selected'
+      moving = false
+    , 500
+
 ms.addEvent 'marking-surface:tool:select', ({detail}) ->
   pinpoint = detail[0] if detail[0].mark._taskIndex is 2
   current_tool?.el.classList.remove 'selected' if pinpoint?


Update simplepool.coffee

Update simplepool.coffee

--- examples/simplepool.coffee
+++ examples/simplepool.coffee
@@ -3,7 +3,5 @@

 SimplePool = require "#{process.cwd()}/lib/simplepool"

-config = {}
-
-simplepool = new SimplePool()
-simplepool.init config
+simplepool = new SimplePool(['obj1', 'obj2'])
+simplepool.get()


views for each module

views for each module

--- modulware.coffee
+++ modulware.coffee
@@ -17,8 +17,10 @@
   basedir: null # with `null` => process.cwd()
   configOnLocals: true
   defaultMountpoint: '/' # is able to contain a $module variable, e.g. '/$module'
-  expressjsSettings:
-    'view engine': 'jade'
+  localConfigKey: 'config'
+  expressjs:
+    set:
+      'view engine': 'jade'
 }

 _options = {}
@@ -42,7 +44,7 @@
   return @log.error('instanceMethod argument needs to be a function') if typeof instanceMethod isnt 'function'
   exports.buildModuleIndex()
   app = exports._applyOnInstanceMethod(instanceMethod)
-  app.locals.config = @getConfig() if _options.configOnLocals
+  app.locals.settings[_options.localConfigKey] = @getConfig() if _options.configOnLocals
   return app

 exports.log = {
@@ -174,13 +176,12 @@
   # in order of `before:` and `after:`

   for moduleName in _sortedRoutes
-
     # if we deal with express instance method
     # we create a new express / app instance for each module
     if mountToApp
       app = instanceMethod()
-      for settingKey of options.expressjsSettings
-        app.set(settingKey, options.expressjsSettings[settingKey])
+      for settingKey of options.expressjs?.set
+        app.set(settingKey, options.expressjs.set[settingKey])

     routes = modules[moduleName].routes.routes
     codeFiles = {}


Revert pull request #94

Revert pull request #94

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -231,7 +231,7 @@
   execute: (count=1) ->
     @undoTransaction =>
       _.times count, =>
-        @editor.joinLine()
+        @editor.joinLines()

 #
 # Repeat the last operation


Teamwork: Added a method to fetch file content from GitHub.

--- client/Teamwork/Views/teamworkapp.coffee
+++ client/Teamwork/Views/teamworkapp.coffee
@@ -159,6 +159,7 @@
       for manifest in @playgroundsManifest when playground is manifest.name
         {manifestUrl} = manifest

+    # TODO: doCurlRequest is useless here, use fetchGitHubFileContent method.
     @doCurlRequest manifestUrl, (err, manifest) =>
       @teamwork?.destroy()
       @createTeamwork @mergePlaygroundOptions manifest, playground
@@ -220,3 +221,24 @@
           callback error, manifest
         when "md"
           callback errorMessage, KD.utils.applyMarkdown error, contents
+
+  fetchGitHubFileContent: (path, callback, type, username, repoName) ->
+    window.ali = @
+    callback or= noop
+    type     or= "json"
+    username or= "koding"
+    repoName or= "Teamwork"
+
+    $.ajax
+      url           : "https://api.github.com/repos/#{username}/#{repoName}/contents/#{path}",
+      crossDomain   : true
+      dataType      : "jsonp"
+      success       : (response) =>
+        content     = atob response.data.content.replace /\n/g, ""
+        if type is "json"
+          try
+            content = JSON.parse content
+          catch err
+            warn "File content is not a valid JSON."
+
+        callback err, content


refactor vumeter helper

refactor vumeter helper

--- client/components/vumeter/vumeter.coffee
+++ client/components/vumeter/vumeter.coffee
@@ -1,6 +1,7 @@
 clamp = (value, min, max) -> Math.min Math.max(min, value), max

-Template.vumeter.meter = (vumeter) ->
-  'green': clamp vumeter, 0, 50
-  'yellow': clamp vumeter - 50, 0, 35
-  'red': clamp vumeter - 85, 0, 15
+Template.vumeter.helpers
+  meter: (vumeter) ->
+    'green': clamp vumeter, 0, 50
+    'yellow': clamp vumeter - 50, 0, 35
+    'red': clamp vumeter - 85, 0, 15


Refactoring.

Refactoring.

--- app/assets/javascripts/doorkeeper_sync.js.coffee
+++ app/assets/javascripts/doorkeeper_sync.js.coffee
@@ -24,7 +24,7 @@

     syncDoorkeeper = ->
       path = $(@).data().path
-      event_url = $('.field-doorkeeper-event-url').val()
+      eventUrl = $('.field-doorkeeper-event-url').val()
       $imgLoading = $('.img-loading')
       $imgLoading.show()

@@ -33,7 +33,7 @@
       $('.result-icon-success,.result-icon-error').hide()

       $.ajax
-        url: "#{path}?event_url=#{event_url}"
+        url: "#{path}?event_url=#{eventUrl}"
         dataType: "json"
         success: (results) ->
           setDoorkeeperInfo(results)


fixing error

fixing error

--- app/assets/javascripts/ext/cmaps/cmaps.js.coffee
+++ app/assets/javascripts/ext/cmaps/cmaps.js.coffee
@@ -7,8 +7,8 @@
     @_markers = {}
   mapInit: ->
     mapControl = new micello.maps.MapControl(@mapViewer)
-    console.log "#{@mapViewer}"
-    console.log "#{@apiKey}"
+    console.log sys.inspect @mapViewer
+    console.log sys.inspect @apiKey
     mapDataObject = mapControl.getMapData()
     mapDataObject.loadCommunity(24657)
     @map = mapDataObject


Application form no longer throws a javascript error related to extra questions when loading the page without a chapter selected

--- app/assets/javascripts/extra_questions.js.coffee
+++ app/assets/javascripts/extra_questions.js.coffee
@@ -16,8 +16,9 @@
   $('.extra-questions .extra-question').removeClass('showing')

 set_new_questions = (questions) ->
-  show_question_section() if questions.length
-  show_one(question) for question in questions
+  if questions != undefined
+    show_question_section() if questions.length
+    show_one(question) for question in questions

 show_question_section = ->
   $('.extra-questions').show()


no preview is the new unknown preview

no preview is the new unknown preview

--- app/assets/javascripts/mtl/document_modal.coffee
+++ app/assets/javascripts/mtl/document_modal.coffee
@@ -34,7 +34,7 @@

 """

-MTL.templates.preview_unknown = """
+MTL.templates.no_preview = """

       <%= MTL.icon('visibility_off', { size: 'large grey-text' }) %>
@@ -74,7 +74,7 @@
     when isPDF(filename) && PDFObject.supportsPDFs
       PDFObject.embed(url, $container, { width: '800px', height: $(window).height() + 'px' })
     else
-      $container.append(MTL.renderTemplate('preview_unknown', { filename: filename, url: url }))
+      $container.append(MTL.renderTemplate('no_preview', { filename: filename, url: url }))

 findOrCreateDocumentModal = ->
   return $('
').appendTo('body') unless $('body > .document-modal').first().length


Removed un-needed code

Removed un-needed code

--- tests/unit/specs/spec_server.coffee
+++ tests/unit/specs/spec_server.coffee
@@ -33,7 +33,6 @@
     exec_ret =
       "stdout":
         "pipe": sinon.spy()
-        "on": sinon.spy()
       "stderr":
         "pipe": sinon.spy()
       "stdin":


added functionality to fix tooltips in modals

--- app/assets/javascripts/pt/tags/tooltip.js.coffee
+++ app/assets/javascripts/pt/tags/tooltip.js.coffee
@@ -12,7 +12,7 @@
           html: true
           title: attr.tooltip
           placement: attr.tooltipPlacement or 'right'
-          container: 'body'
+          container: attr.tooltipContainer or 'body'
           trigger: 'manual'

         $el.tooltip 'show'
@@ -24,4 +24,10 @@
 pt.tags.push
   tooltip:
     event: 'mouseleave'
-    callback: -> $(this).tooltip 'hide'
+    callback: ->
+      $el = $ this
+      attr = $el.ptAttr()
+      if not attr.tooltipContainer
+        $el.popover 'hide'
+      else
+        $el.closest(attr.tooltipContainer).find('.tooltip').remove()


Gradient fill text positioning rectangles.

--- app/assets/javascripts/text_positioner.js.coffee
+++ app/assets/javascripts/text_positioner.js.coffee
@@ -25,6 +25,15 @@
       cornersize: 20
       lockRotation: true
       hasRotatingPoint: false
+
+    rect.setGradientFill
+      x1: '0%'
+      y1: '0%'
+      x2: '100%'
+      y2: '100%'
+      colorStops:
+        0: 'black'
+        1: 'white'

     @fabric_canvas.add rect


Fixed joyride disable when you are not authenticated

--- app/modules/services/current-user.service.coffee
+++ app/modules/services/current-user.service.coffee
@@ -72,6 +72,9 @@
             .then (projects) => @.setProjects(projects)

     disableJoyRide: (section) ->
+        if !@.isAuthenticated()
+            return
+
         if section
             @._joyride[section] = false
         else


Apply line styles on display-updated.

Apply line styles on display-updated.

... but only after we've received a marker change event.

--- lib/side-view.coffee
+++ lib/side-view.coffee
@@ -14,18 +14,31 @@
   installIn: (editorView) ->
     @appendTo editorView.overlayer
     @reposition(editorView)
+    @remark(editorView)

     @side.refBannerMarker.on "changed", =>
       @reposition(editorView)

-  reposition: (editorView, initial) ->
-    # @side.lines().addClass("conflict-line #{@side.klass()}")
+    updateScheduled = true
+
+    @side.marker.on "changed", =>
+      updateScheduled = true
+
+    editorView.on "editor:display-updated", =>
+      if updateScheduled
+        @remark(editorView)
+        updateScheduled = false
+
+  reposition: (editorView) ->
     anchor = editorView.renderedLines.offset()
     ref = @side.refBannerOffset()

     @offset top: ref.top + anchor.top
     @height @side.refBannerLine().height()

+  remark: (editorView) ->
+    @side.lines().addClass("conflict-line #{@side.klass()}")
+
   useMe: ->
     @side.resolve()


Documentation

Documentation

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -642,7 +642,8 @@
     else
       @selectEntry(entryToSelect)
       @showFullMenu()
-
+
+  # Toggles the tree view side (from left to right & vice versa) when the user hits the menu dropdown option
   onSideToggled: (newValue) ->
     if @isVisible()
       @detach()


Unfocus tree-view on double-click

Unfocus tree-view on double-click

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -211,6 +211,7 @@
       when 2
         if entry instanceof FileView
           atom.workspace.getActivePaneItem().terminatePendingState?()
+          @unfocus()
         else if entry instanceof DirectoryView
           entry.toggleExpansion(isRecursive)


#87 eliminate complex eof/lastRow check since we no longer have to care such situation.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -152,35 +152,23 @@

   updateCursorStyle: ->
     selections = @editor.getSelections()
+
     cursorElements = @editorElement.shadowRoot.querySelectorAll('div.cursor')
     # [FIXME] Just for spec pass without error. In specmode
     # Its not proper way of avoiding spec error. but need time.
-    return unless ((cursorElements.length is selections.length) and cursorElements.length)
+    unless ((cursorElements.length is selections.length) and cursorElements.length)
+      return

     for [s, {style}] in _.zip(selections, cursorElements)
       {cursor} = s
       if @submode is 'linewise'
         unless s.isReversed()
-          {start, end} = s.getBufferRange()
-          unless start.row is end.row is @editor.getLastBufferRow()
-            style.setProperty('top', '-1.5em') unless s.isEmpty()
+          style.setProperty('top', '-1.5em') unless s.isEmpty()
         if point = swrap(s).getCharacterwiseHeadPosition()
           style.setProperty('left', "#{point.column}ch")
       else
         unless s.isReversed()
-          if cursor.isAtBeginningOfLine()
-            # In visual-mode, cursor colum 0 means whole line selected
-            # and in this case, cursor position is at [nextRow, 0]
-            # So I offset one row up by stylesheet.
-            isAtAtomEof = cursor.getBufferPosition().isEqual(@editor.getEofBufferPosition())
-            if isAtAtomEof and s.isEmpty()
-              # hide cursor, I can display cursor but its missleading.
-              # Because when you move cursor, EOB position not included as selection.
-              style.setProperty('left', '-2ch')
-            else
-              style.setProperty('top', '-1.5em')
-          else
-            style.setProperty('left', '-1ch')
+          style.setProperty('left', '-1ch')

     new Disposable ->
       for {style} in cursorElements
@@ -199,9 +187,9 @@
       when 'blockwise'
         @editor.getCursors().filter (c) -> swrap(c.selection).isBlockwiseHead()

+    toggleClassByCondition(@editorElement, 'reversed', @editor.getLastSelection().isReversed())
     for c, i in @editor.getCursors()
       if c in cursors
         c.setVisible(true) unless c.isVisible()
-        toggleClassByCondition(@editorElement, 'reversed', c.selection.isReversed())
       else
         c.setVisible(false)


debug

debug

--- ai.coffee
+++ ai.coffee
@@ -134,8 +134,8 @@
 		if total == -v
 			losses.push( (i for i in indices when subboard[i] is 0)[0] )

+	console.log(wins, losses)
 	moves = [wins...,losses...]
-	console.log("Moves:", moves)
 	if moves.length == 0
 		return ais["random"].move()
 	selection = moves[0]


adjust message text

adjust message text

--- lib/Bumped.messages.coffee
+++ lib/Bumped.messages.coffee
@@ -7,12 +7,12 @@
   ADD_FILE            : (file) -> "'#{file}' has been added."
   ADD_ALREADY_FILE    : (file) -> "'#{file}' is already added."
   REMOVE_FILE         : (file) -> "'#{file}' has been removed."
-  DETECTED_FILE       : (file) -> "Detected '#{file}' in the directory."
+  DETECTED_FILE       : (file) -> "'#{file}' has been detected in the directory."
   CREATED_VERSION     : (version) -> "Created version '#{version}'."
   CURRENT_VERSION     : (version) -> "Current version is '#{version}'."

   # Negative
-  NOT_REMOVE_FILE     : (file) -> "#{file} can\'t be removed because isn\'t declared."
-  NOT_DETECTED_FILE   : (file) -> "'#{file}' hasn\'t beedn detect in the directory."
+  NOT_REMOVE_FILE     : (file) -> "#{file} can\'t be removed because previously hasn\' been declared."
+  NOT_DETECTED_FILE   : (file) -> "'#{file}' hasn\'t been detected in the directory."
   NOT_VALID_VERSION   : (version) -> "version '#{version}' provided to release is not valid."
   NOT_GREATER_VERSION : (last, old) -> "version '#{last}' is not greater that the current '#{old}' version."


Rm countdown method add won+lost countdown methods

The beginCountdown method was supposed to handle both won and lost state
changes. However it depended on submit previously and in refactoring as
three specific methods a bug was introduced where the beginCountdown
method would only transition to a lost state even if the target state
was a won.

Two new methods targeting a won and lost state noe replace this single
method.

The level and lives cookies are also reset at the begining of the
countdown methods, rather than at the point of transition. This was
motivated by an edge case where a user who has run out of lives could
reload the page before the countdown has finished to be brought back to
the lives and level state they were in before they lost their final
life. They would be given a new set of waveforms so it doesn't
nessesarily matter, but a reset on win or lose is the intended result so
ensuring consitencey is important here.

--- coffee/game.coffee
+++ coffee/game.coffee
@@ -633,7 +633,7 @@
           $('#tick-noclick').hide()
           if $('#dial-col').data('max-level') == $('#dial-col').data('level')
             $('#won').show()
-            beginCountdown(5)
+            beginWonCountdown(5)
           else
             $('#continue').show()

@@ -654,7 +654,7 @@
           if @lives == 1
             $('#tick-noclick').hide()
             $('#lost').show()
-            @beginCountdown(5)
+            @beginLostCountdown(5)
           else
             # Toggle tick
             $('#tick-noclick').hide()
@@ -687,13 +687,9 @@
       window.open('/game.html', '_self')

     lost: () ->
-      setCookie('lives', 3)
-      setCookie('level', 1)
       window.open('/lost.html', '_self')

     complete: () ->
-      setCookie('lives', 3)
-      setCookie('level', 1)
       window.open('/complete.html', '_self')

     #
@@ -707,9 +703,22 @@
     # @param  {int} t Starting countdown value
     # @return {undefined}
     #
-    beginCountdown: (t) ->
+    beginLostCountdown: (t) ->
+      setCookie('lives', 3)
+      setCookie('level', 1)
+
       $('.counter').text t
       if t==0
         @lost()
       else
-        _.delay @beginCountdown, 1000, t-1
+        _.delay @beginLostCountdown, 1000, t-1
+
+    beginWonCountdown: (t) ->
+      setCookie('lives', 3)
+      setCookie('level', 1)
+
+      $('.counter').text t
+      if t==0
+        @complete()
+      else
+        _.delay @beginWonCountdown, 1000, t-1


reticulating squash

reticulating squash

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -547,16 +547,22 @@
     console.log("updating object")
     console.log(x)

+    ###
     msg = {
       Computers: [],
       Switches: []
     }
-
-    ido = { OID: x.id, Data: x.props }
+    ###
+    msg = { Elements: [] }
+
+    ido = { OID: x.id, Type: x.constructor.name, Element: x.props }
+    msg.Elements.push(ido)
+    ###
     switch
       when x instanceof BaseElements.Computer then msg.Computers.push(ido)
       when x instanceof BaseElements.Switch then msg.Switches.push(x.props)
       else console.log('update not implemented for object')
+    ###

     console.log(msg)


add array#set() function

add array#set() function

--- coffee/util.coffee
+++ coffee/util.coffee
@@ -15,15 +15,6 @@

 ###
- clear array.
-###
-if not ('clear' in Array.prototype)
-  Array.prototype.clear = () ->
-    while (this.length > 0) then this.pop()
-    return
-
-
-###
  format string.
 ###
 if not ('format' in String.prototype)
@@ -35,6 +26,24 @@
         args = arguments
         rep_fn = (m, k) ->  return args[parseInt(k)]
     return this.replace /\{(\w+)\}/g, rep_fn
+
+
+###
+ clear array.
+###
+if not ('clear' in Array.prototype)
+  Array.prototype.clear = () ->
+    while (this.length > 0) then this.pop()
+    return
+
+
+###
+ remove all elements in array, and insert new array's all element.
+###
+if not ('set' in Array.prototype)
+  Array.prototype.set = (newArray) ->
+    [].splice.apply(this, [0, this.length].concat(newArray))
+    return

 @util = {


Client: Teams: Added group and machineId to MachineShareActionTaken event options data

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -748,7 +748,7 @@
       'users.id' : user._id
     }, { $set : { 'users.$.approved' : yes } }
     , (err) =>
-      options = { action: 'approve', @uid }
+      options = { action: 'approve', @uid, group: client?.context?.group, machineId: @getId() }
       client.connection.delegate.sendNotification 'MachineShareActionTaken', options
       callback err

@@ -775,7 +775,7 @@
       permanent : yes

     @shareWith options, (err) =>
-      options               = { action: 'deny', @uid, group: client?.context?.group }
+      options               = { action: 'deny', @uid, group: client?.context?.group, machineId: @getId() }
       [ owner ]             = @users.filter (user) -> return user.owner
       { notifyByUsernames } = require '../notify'


refactor

refactor

--- lib/codeglance-view.coffee
+++ lib/codeglance-view.coffee
@@ -1,33 +1,34 @@
 'use strict'
+{Point} = require 'atom'
+
 class CodeglanceView extends HTMLElement
   createdCallback: ->
-    @editorView = document.createElement 'atom-text-editor'
-    @editor = @editorView.getModel()
-    @appendChild @editorView
-    gutter.hide() for gutter in @editor.getGutters()
+    @appendChild editorView = document.createElement 'atom-text-editor'
+    @codeglanceEditor = editorView.getModel()
+    gutter.hide() for gutter in @codeglanceEditor.getGutters()

   attach: ->
-    textEditorView = atom.views.getView @minimap.getTextEditor()
-    textEditorView.shadowRoot.appendChild this
+    @getTextEditorView().shadowRoot.appendChild this

   detach: ->
     @parentNode?.removeChild this

   resize: ->
     minimapView = atom.views.getView @minimap
-    @style.width = "calc(100% - #{getComputedStyle(minimapView).width})"
+    minimapWidth = getComputedStyle(minimapView).width
+    @style.width = "calc(100% - #{minimapWidth})"

     nLines = atom.config.get 'minimap-codeglance.numberOfLines'
-    lineHeight = @editor.getLineHeightInPixels()
+    lineHeight = @codeglanceEditor.getLineHeightInPixels()
     @style.height = nLines * lineHeight + 'px'

-  resetGrammar: ->
-    @editor.setGrammar switch atom.config.get 'minimap-codeglance.highlightCode'
-      when true then @minimap.getTextEditor().getGrammar()
+  updateGrammar: ->
+    @codeglanceEditor.setGrammar switch atom.config.get 'minimap-codeglance.highlightCode'
+      when true then @getTextEditor().getGrammar()
       when false then atom.grammars.grammarForScopeName 'text.plain.null-grammar'

-  resetText: ->
-    @editor.setText @minimap.getTextEditor().getText()
+  updateText: ->
+    @codeglanceEditor.setText @getTextEditor().getText()

   show: (parentTextEditor) ->
     # hide until content is visible
@@ -46,54 +47,57 @@

   showLineNumbers: ->
     @removeAttribute 'data-hide-gutter'
-    @editor.gutterWithName('line-number').show()
+    @codeglanceEditor.gutterWithName('line-number').show()

   hideLineNumbers: ->
     @setAttribute 'data-hide-gutter', ''
-    @editor.gutterWithName('line-number').hide()
+    @codeglanceEditor.gutterWithName('line-number').hide()

-  setMinimap: (@minimap) ->
+  setMinimap: (minimap) ->
+    return if @minimap is minimap
+    @minimap = minimap
     @attach()
     @resize()
-    @resetGrammar()
-    @resetText()
+    @updateGrammar()
+    @updateText()

-  showLinesAtOffset: (offsetInPixels) ->
+  getTextEditor: ->
+    @minimap.getTextEditor()
+
+  getTextEditorView: ->
+    atom.views.getView @getTextEditor()
+
+  showLinesAtOffset: (offset) ->
     @resize() if @clientHeight is 0
-    minimapPosition = @offsetToMinimapPosition offsetInPixels
-    sourceScreenPosition = @sourceScreenRowForMinimapPosition minimapPosition
-    ownScreenPosition = @ownScreenPositionForSourceScreenPosition sourceScreenPosition
-    @scrollToScreenPosition ownScreenPosition
-    @alignWithCursor offsetInPixels.y
+    sourceScreenPosition = @sourceScreenPositionForMinimapOffset offset
+    return @hide() if sourceScreenPosition.row > @getTextEditor().getLastScreenRow()
+    screenPosition = @screenPositionForSourceScreenPosition sourceScreenPosition
+    @scrollToScreenPosition screenPosition
+    @alignWithCursor offset.y

-  offsetToMinimapPosition: (offsetInPixels) ->
+  sourceScreenPositionForMinimapOffset: (offset) ->
+    firstVisibleScreenRow = @minimap.getFirstVisibleScreenRow()
+    lineHeight = @minimap.charHeight + @minimap.interline
+    row = firstVisibleScreenRow + Math.floor offset.y / lineHeight
     columnWidth = @minimap.charWidth
-    lineHeight = @minimap.charHeight + @minimap.interline
-    column: Math.floor offsetInPixels.x / columnWidth
-    row: Math.floor offsetInPixels.y / lineHeight
+    column = Math.floor offset.x / columnWidth
+    new Point row, column

-  sourceScreenRowForMinimapPosition: (minimapPosition) ->
-    firstVisibleScreenRow = @minimap.getFirstVisibleScreenRow()
-    column: minimapPosition.column
-    row: minimapPosition.row + firstVisibleScreenRow
-
-  ownScreenPositionForSourceScreenPosition: (sourceScreenPosition) ->
-    return null if sourceScreenPosition.row > @minimap.getTextEditor().getLastScreenRow()
-    bufferPosition = @minimap.getTextEditor().bufferPositionForScreenPosition sourceScreenPosition
-    @editor.screenPositionForBufferPosition bufferPosition
+  screenPositionForSourceScreenPosition: (sourceScreenPosition) ->
+    bufferPosition = @getTextEditor().bufferPositionForScreenPosition sourceScreenPosition
+    @codeglanceEditor.screenPositionForBufferPosition bufferPosition

   scrollToScreenPosition: (screenPosition) ->
-    return @hide() unless screenPosition?.row
-    @editor.setCursorScreenPosition screenPosition
+    @codeglanceEditor.setCursorScreenPosition screenPosition, autoscroll: true
     @show()

-  alignWithCursor: (cursorOffsetInPixels) ->
+  alignWithCursor: (offset) ->
     return if @position isnt 'cursor'
-    offsetInPixels = cursorOffsetInPixels - @clientHeight / 2
-    offsetInPixels = Math.max @getMinOffsetY(), offsetInPixels
-    offsetInPixels = Math.min @getMaxOffsetY(), offsetInPixels
+    offsetY = offset - @clientHeight / 2
+    offsetY = Math.max @getMinOffsetY(), offsetY
+    offsetY = Math.min @getMaxOffsetY(), offsetY
     requestAnimationFrame =>
-      @style.transform = "translateY(#{offsetInPixels}px)"
+      @style.transform = "translateY(#{offsetY}px)"

   getMinOffsetY: ->
     -parseInt getComputedStyle(this).borderTopWidth
@@ -104,8 +108,8 @@

   destroy: ->
     @detach()
-    @editor.destroy()
-    [@editor, @editorView, @minimap] = []
+    @codeglanceEditor.destroy()
+    [@codeglanceEditor, @minimap] = []

 module.exports = document.registerElement 'minimap-codeglance',
   prototype: CodeglanceView.prototype


changed method variable to type

changed method variable to type

--- lib/computed-fields.coffee
+++ lib/computed-fields.coffee
@@ -17,8 +17,8 @@
   constructor: (@collection, @name, @updateMethod) ->
     field = this
     return unless @updateMethod?
-    callUpdate = (method) -> (userId, doc, fieldNames) ->
-      thisValue = field._getThis this, doc, userId, fieldNames, method
+    callUpdate = (type) -> (userId, doc, fieldNames) ->
+      thisValue = field._getThis this, doc, userId, fieldNames, type
       field.updateMethod.call thisValue, @transform()
     @collection.after.insert callUpdate 'insert'
     @collection.after.update callUpdate 'update'
@@ -40,9 +40,9 @@
     findDoc = (doc) ->
       _id = options.findId(doc)
       field.collection.findOne _id: _id
-    callUpdate = (method) -> (userId, doc, fieldNames) ->
+    callUpdate = (type) -> (userId, doc, fieldNames) ->
       fieldDoc = findDoc doc
-      thisValue = field._getThis this, fieldDoc, userId, fieldNames, method
+      thisValue = field._getThis this, fieldDoc, userId, fieldNames, type
       options.update.call thisValue, fieldDoc, @transform()
     collection.after.insert callUpdate 'insert'
     collection.after.update callUpdate 'update'


Add date to action log

Add date to action log

--- bootstrap/log.coffee
+++ bootstrap/log.coffee
@@ -29,7 +29,8 @@
     ctx.run.on 'end', close
     ctx.run.on 'action', (ctx, status) ->
       return unless status is ctx.STARTED
-      msg = "\n#{ctx.action.name}\n#{pad ctx.action.name.length, '', '-'}\n"
+      date = (new Date).toISOString()
+      msg = "\n#{date} #{ctx.action.name}\n#{pad date.length+ctx.action.name.length, '', '-'}\n"
       log.out.write msg
       log.err.write msg
     ctx.run.on 'error', (err) ->


chore(tidy): removed old/unused code

chore(tidy): removed old/unused code

--- vue/src/shared/services/group_service.coffee
+++ vue/src/shared/services/group_service.coffee
@@ -75,15 +75,6 @@
           component: 'ExportDataModal'
           props:
             group: group
-          # component: 'ConfirmModal'
-          # props:
-          #   confirm:
-          #     submit: group.export
-          #     text:
-          #       title:    'group_export_modal.title'
-          #       helptext: 'group_export_modal.body'
-          #       submit:   'group_export_modal.submit'
-          #       flash:    'group_export_modal.flash'

     install_slack:
       name: 'install_slack.modal_title'


Boyscouting.

Boyscouting.

--- core/app/backbone/views/fact_view.coffee
+++ core/app/backbone/views/fact_view.coffee
@@ -32,7 +32,7 @@
   events:
     "click a.discussion_link" : "triggerDiscussionClick"

-  template: "facts/_fact"
+  template: "facts/fact"

   templateHelpers: ->
     'modal?' : FactlinkApp.modal is true
@@ -54,6 +54,7 @@
       @setPopover()

     @$(".authority").tooltip()
+
     if FactlinkApp.guided
       sometimeWhen(
         => @$el.is ":visible"


feat($tests): createEnv and other env method exported

In order for other helpers or matchers to use the createEnv method,
it was exposed throught the exports object.

--- specs/support/helpers/spec_helpers.coffee
+++ specs/support/helpers/spec_helpers.coffee
@@ -1,5 +1,5 @@

-createEnv = (block, context) ->
+exports.createEnv = (block, context) ->
   env = spectacular.env.clone()
   env.options.noColors = true
   spyOn(env.runner, 'loadSpecs').andCallFake -> do block
@@ -9,7 +9,7 @@
     context.results = @buildResults.apply this, arguments
   env

-runEnvExpectingNormalTermination = (env, context, async) ->
+exports.runEnvExpectingNormalTermination = (env, context, async) ->
   oldEnv = spectacular.env
   env.run()
   .then (status) ->
@@ -21,7 +21,7 @@
     oldEnv.load()
     async.reject reason

-runEnvExpectingInterruption = (env, context, async) ->
+exports.runEnvExpectingInterruption = (env, context, async) ->
   oldEnv = spectacular.env
   env.run()
   .then (status) =>


Update atom configurations

Update atom configurations

--- atom/dot-atom/config.cson
+++ atom/dot-atom/config.cson
@@ -9,10 +9,16 @@
     '''
     startDir: "projectDirOfFile"
     startupCode: "{\"Python 3\": \"%matplotlib inline\"}"
+  "advanced-open-file":
+    createDirectories: true
+    fuzzyMatch: true
   "atom-beautify":
     bash:
       beautify_on_save: true
       indent_size: 2
+    executables:
+      isort:
+        path: "/usr/bin/env isort"
     general:
       beautifyEntireFileOnSave: false
     markdown:
@@ -20,9 +26,11 @@
       commonmark: true
       default_beautifier: "Remark"
     python:
-      default_beautifier: "yapf"
+      beautify_on_save: true
+      default_beautifier: "pybeautifier"
       formater: "yapf"
       sort_imports: true
+      style_config: "{based_on_style: pep8, align_closing_bracket_with_visual_indent: false, blank_line_before_nested_class_or_def: true, indent_width: 4, join_multiple_lines: false, split_before_named_assigns: false}"
     r:
       beautify_on_save: true
       indent_size: 2
@@ -30,6 +38,8 @@
       disabled: true
   "atom-clock":
     showClockIcon: true
+  "atom-fuzzy-grep":
+    detectGitProjectAndUseGitGrep: false
   "atom-html-preview":
     enableMathJax: true
     scrollToCursor: true
@@ -44,12 +54,15 @@
     scrollOnError: true
   "build-make":
     jobs: 1
+  "code-peek": {}
   core:
     disabledPackages: [
       "language-gfm"
       "language-python"
       "atom-ide-ui"
       "ide-python"
+      "linter-pylint"
+      "linter-flake8"
     ]
     packagesWithKeymapsDisabled: []
     telemetryConsent: "no"
@@ -71,8 +84,12 @@
       "source.pweave.md"
       "text.md"
     ]
-  "linter-ui-default":
-    showPanel: true
+  "linter-pylama":
+    executablePath: "~/.pyenv/shims/pylama, pylama, pylama.exe"
+    interpreter: "~/.pyenv/shims/python, python, python.exe"
+    pylamaVersion: "external"
+    usePylint: true
+  "linter-ui-default": {}
   "markdown-preview-plus":
     enablePandoc: true
     grammars: [
@@ -106,6 +123,7 @@
       scrollback: 8000
     style:
       fontFamily: "Hack"
+  "status-bar": {}
   welcome:
     showOnStartup: false
 ".basic.html.text":


starttab applications WIP

starttab applications WIP

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -73,18 +73,16 @@
       @redirectMouseMoveEvent e
     , yes

+
+    unless window.location.hostname is 'localhost'
+      window.onbeforeunload = (event) =>

-    window.onbeforeunload = (event) =>
-      # event.preventDefault();
-      # event.stopImmediatePropagation();
-      # event.stopPropagation();
+        event or= window.event
+        msg = "Please make sure that you saved all your work."
+
+        event.returnValue = msg if event # For IE and Firefox prior to version 4

-      event or= window.event
-      msg = "Please make sure that you saved all your work."
-
-      event.returnValue = msg if event # For IE and Firefox prior to version 4
-
-      return msg unless window.location.hostname isnt 'localhost'
+        return msg

   setDragInAction:(action = no)->
     $('body')[if action then "addClass" else "removeClass"]("dragInAction")


Add line to message

Add line to message

--- lib/go-to-line-view.coffee
+++ lib/go-to-line-view.coffee
@@ -87,5 +87,5 @@
     if editor = atom.workspace.getActiveEditor()
       @storeFocusedElement()
       atom.workspaceView.append(this)
-      @message.text("Enter a row:column to go to")
+      @message.text("Enter a line row:column to go to")
       @miniEditor.focus()


Fix missing clusters

Fix missing clusters

--- web/source/javascripts/angular-app.js.coffee
+++ web/source/javascripts/angular-app.js.coffee
@@ -197,8 +197,8 @@

       clusterColor = d3.scale.category20()
       # hulls TODO switch to actual groups
-      hulls = clusterGroup.selectAll('g.hull-group').data(selectedClustering.clusters, (c) -> c.name)
-      hulls.exit().remove()
+      clusterGroup.selectAll('*').remove()
+      hulls = clusterGroup.selectAll('g.hull-group').data(selectedClustering.clusters, (c) -> selectedClustering.name+c.name)
       hullGroup = hulls.enter().append('g').attr('class', (cluster) -> "hull-group #{cluster.name}")
       hullGroup.append('path').attr('class', (cluster) -> "hull #{cluster.name}")
         .attr 'fill', (cluster) -> clusterColor(cluster.name)


Should match any line ending, including \r (Old Mac), \n (POSIX), and \r\n (Windows)

--- lib/linter-pyflakes.coffee
+++ lib/linter-pyflakes.coffee
@@ -16,7 +16,7 @@

   # A regex pattern used to extract information from the executable's output.
   # regex: ""/path/to/python/file.py:28: redefinition of unused 'models' from line 5"
-  regex: ':(?<line>\\d+): (?<message>.*?)\n'
+  regex: ':(?<line>\\d+): (?<message>.*?)[\r\n]+'

   constructor: (editor)->
     super editor


provide locking state again for development easiness.

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -28,7 +28,18 @@
     if @vimState.isVisualMode() and op.isOperator()
       debug "push INPLICIT Motion.CurrentSelection"
       @stack.push(new CurrentSelection(@vimState))
-    @process()
+    @withLock =>
+      @process()
+
+  withLock: (callback) ->
+    try
+      @processing = true
+      callback()
+    finally
+      @processing = false
+
+  isProcessing: ->
+    @processing

   process: ->
     debug '-> @process(): start'


Stop always showing error on installs

Stop always showing error on installs

Closes #10

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -58,7 +58,7 @@
   errorLines = []
   stderr = (lines) -> errorLines.push(lines)
   exit = (code) ->
-    if code is 0 and false
+    if code is 0
       atom.packages.activatePackage(name) if activateOnSuccess
       callback()
     else


Dont wrap clickspec in quotes for execFile

--- lib/pdf-editor-view.coffee
+++ lib/pdf-editor-view.coffee
@@ -149,11 +149,11 @@
                     initialColumn: 0

         synctexPath = atom.config.get('pdf-view.syncTeXPath')
-        clickspec = '"' + page + ":" + x + ":" + y + ":" + @filePath + '"'
+        clickspec = page + ":" + x + ":" + y + ":" + @filePath
         if synctexPath
           execFile synctexPath, ["edit", "-o", clickspec], callback
         else
-          cmd = "synctex edit -o " + clickspec
+          cmd = "synctex edit -o " + '"' + clickspec + '"'
           exec cmd, callback


try to fix ag not work for win #9

try to fix ag not work for win #9

--- lib/provider/search.coffee
+++ lib/provider/search.coffee
@@ -69,6 +69,7 @@
         stderr: stderr
         exit: -> resolve(items)
         options:
+          stdio: ['ignore', 'pipe', 'pipe']
           cwd: project
           env: process.env
       )


Add StatusList to RepositoryView

Add StatusList to RepositoryView

--- lib/repository-view.coffee
+++ lib/repository-view.coffee
@@ -21,6 +21,14 @@
     @detailsNode    = @querySelector('.details')
     @resizeableNode = @querySelector('.resizeable')
     @resizerNode    = @querySelector('.repository-view-resizer')
+
+    # Child Nodes
+    @statusListView = new StatusListView()
+    @statusListView.initialize(@)
+    @summaryNode.appendChild(@statusListView)
+
+    @statusListView.focus()
+    @statusListView.update()

     @width(width) if width > 0
     @handleEvents()


Update modules.coffee

Update modules.coffee

--- lib/runtime/modules.coffee
+++ lib/runtime/modules.coffee
@@ -1,7 +1,7 @@
 {CompositeDisposable} = require 'atom'

 {client} = require '../connection'
-{selector} = require '../ui'
+selector = require '../ui/selector'

 module.exports =
   activate: ->


+ test Codec constructor can pass a codec instance and return it directly.

--- test/abstract-codec-test.coffee
+++ test/abstract-codec-test.coffee
@@ -101,6 +101,15 @@
           myCodec = new MyCodec(13)
           testCodecInstance myCodec, MyBufferCodec, 13
           myCodec.should.be.not.equal Codec("MyBuffer")
+        it "should bypass the codec object instance", ->
+          myCodec = Codec('MyBuffer', 33)
+          my = Codec(myCodec)
+          my.should.be.equal myCodec
+        it "should bypass the codec object instance and expand the bufferSize", ->
+          myCodec = Codec('MyBuffer', 33)
+          my = Codec(myCodec, 900)
+          my.should.be.equal myCodec
+          my.bufferSize.should.at.least 900

       describe ".aliases", ->
         class MyAliasCodec


fix ajax call.

fix ajax call.

--- chat/grails-app/assets/javascripts/chat.coffee
+++ chat/grails-app/assets/javascripts/chat.coffee
@@ -158,7 +158,7 @@
         setMessageNumberBadgeOnDay(targetDay, count)
     else if targetDay <= today
         $.ajax {
-            url: "countMessages"
+            url: "/countMessages"
             data: { room: $('#chatRoomSelected').val(), day: targetDay }
         }
         .done ( msg ) ->


try disabling maxAge on cookie. Think it was wrong.

--- lib/server.passport.coffee
+++ lib/server.passport.coffee
@@ -78,7 +78,7 @@
   userSession =
     token: req.sessionID
     user: user
-  resp.cookie('userSession', JSON.stringify(userSession), {maxAge: 900000, secret: false})
+  resp.cookie('userSession', JSON.stringify(userSession), {maxAge: null, secret: false})
   resp.redirect("/slideshow/")

 slideshowRedirect = (service) ->


More analytics hooks

More analytics hooks

--- web/coffee/game_support.coffee
+++ web/coffee/game_support.coffee
@@ -36,6 +36,7 @@

   apiCall "POST", "/api/problems/feedback", postData
   .done (data) ->
+    ga('send', 'event', 'Problem', 'Review', 'Game');
     apiNotify data
     ig.game.getEntitiesByType(EntityQuestions)[0].CloseLearnPanel();
     ig.gui.element.action('showGroup', 'Group_QA');