redis testing

redis testing

--- server/server.coffee
+++ server/server.coffee
@@ -55,15 +55,21 @@
 app.use loopback.token(model: app.models.accessToken)
 app.use loopback.cookieParser(app.get("cookieSecret"))

-console.log process.env.rediscloud_39a84

 # Use secure session cookies
 RedisStore = require('connect-redis')(loopback.session)
+if app.get('env') is 'production'
+  redisOptions =
+    host: process.env.REDISCLOUD_URL
+    port: process.env.REDISCLOUD_PORT
+    pass: process.env.REDISCLOUD_PASSWORD
+else redisOptions = {}
+
 app.use loopback.session
   secret: process.env.OPENSHIFT_SECRET_TOKEN or 'Kh2RWaQO1SbU55UbnWXZ8jO3L8JH35zF'
   saveUninitialized: true
   resave: true
-  store: new RedisStore()
+  store: new RedisStore(redisOptions)

 passportConfigurator.init()

@@ -78,6 +84,9 @@
 config = require("./providers.json")
 config['google-login']['clientID'] = process.env.GOOG_CLIENT_ID
 config['google-login']['clientSecret'] = process.env.GOOG_CLIENT_SECRET
+
+
+
 for s, c of config
   c.session = c.session isnt false
   passportConfigurator.configureProvider s, c


Remove dead code (@renderAnswer is null)

--- src/components/exercise/preview.cjsx
+++ src/components/exercise/preview.cjsx
@@ -61,9 +61,6 @@
     content = @props.exercise.content
     question = content.questions[0]

-    unless @props.hideAnswers
-      renderedAnswers = _.map(question.answers, @renderAnswer)
-
     tags = _.clone @props.exercise.tags
     unless @props.displayAllTags
       tags = _.where tags, is_visible: true


Weld two loops into one

Weld two loops into one

--- src/cmd/ls.coffee
+++ src/cmd/ls.coffee
@@ -26,7 +26,8 @@
 remote = (args)->
   filter = vfilter args
   list = []
-  for z in minors remotes() when filter.match z.id
+  for z in remotes() when filter.match z.id
+    minors z
     if last and not semver.cmp last.major, z.major
       last.minors.push z.minor
     else
@@ -36,12 +37,10 @@
   return

 # Split version to major.minor
-minors = (list)->
-  for z in list
-    major = z.major = z.id.slice()
-    major = major[0] = major[0].slice()
-    z.minors = [z.minor = major.pop()]
-  list
+minors = (remote)->
+  major = remote.major = remote.id.slice()
+  major = major[0] = major[0].slice()
+  remote.minors = [remote.minor = major.pop()]

 # Combine list of minors into list of ranges
 ranges = (list)->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -20,17 +20,19 @@
         else
           alpha = 1.0

-        rgb = (parseInt(hexString.substr(i, 1), 16) * 0x11 for i in [0..2])
+        rgb = (parseInt(hexString.substr(i, 1), 16) * 0x11 for i in [0..2])
+        rgb.push(alpha)

-        return rgb.push(alpha)
+        return rgb

       when 6, 8
         if hexString.length == 8
           alpha = (parseInt(hexString.substr(6, 2), 16) / 255.0) || 1.0

         rgb = (parseInt(hexString.substr(2 * i, 2), 16) for i in [0..2])
+        rgb.push(alpha)

-        return rgb.push(alpha)
+        return rgb

       else
         return undefined


Have client's XML in document so we can watch.

--- app/client/clients.coffee
+++ app/client/clients.coffee
@@ -5,7 +5,7 @@
 record_list = (items, schema) ->
   create_form i, schema for i in items

-CLIENTS = [generate_xml_from_schema "client"]
+($ ".current_xml").append CLIENTS = [generate_xml_from_schema "client"]

 open_clients = () ->
   menu_slide () ->


teacher_licenses i18n

teacher_licenses i18n

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -2177,9 +2177,9 @@
 #    peer_feedback: "Peer Feedback Form"
 #    presentation_checklist: "Presentation Checklist"

-#  teacher_licenses:
-#    active_licenses: "Active Licenses"
-#    expired_licenses: "Expired Licenses"
+  teacher_licenses:
+    active_licenses: "可用许可"
+    expired_licenses: "许可已用完"

   share_licenses:
     share_licenses: "分配许可"


fix user deleter test

fix user deleter test

--- services/web/test/UnitTests/coffee/User/UserControllerTests.coffee
+++ services/web/test/UnitTests/coffee/User/UserControllerTests.coffee
@@ -11,6 +11,9 @@

 describe "UserController", ->
 	beforeEach ->
+
+		@UserDeleter =
+			deleteUser: sinon.stub().callsArgWith(1)
 		@UserController = SandboxedModule.require modulePath, requires:
 			"./UserDeleter": @UserDeleter
 			"logger-sharelatex": {log:->}


Added `reload` post action into RemoteButton closes #375

--- wzk/ui/form/RemoteButtonDecorator.coffee
+++ wzk/ui/form/RemoteButtonDecorator.coffee
@@ -1,7 +1,10 @@
+goog.require 'goog.fx.dom.FadeOutAndHide'
+goog.require 'goog.fx.Transition.EventType'
+goog.require 'goog.object'
+
 goog.require 'wzk.ui.form.RemoteButton'
 goog.require 'wzk.resource.Client'
-goog.require 'goog.fx.dom.FadeOutAndHide'
-goog.require 'goog.fx.Transition.EventType'
+

 class wzk.ui.form.RemoteButtonDecorator

@@ -21,6 +24,13 @@
   ###
   @ACTION:
     REMOVE: 'remove'
+
+  ###*
+    @enum {function(wzk.dom.Dom)}
+  ###
+  @POST_ACTIONS =
+    'reload': (self) -> self.dom.getWindow().location.reload(true)
+    'remove': (self) -> self.removeElement()

   ###*
     @param {wzk.dom.Dom} dom
@@ -79,10 +89,9 @@
     @protected
   ###
   onSuccess: =>
-    if @postAction is wzk.ui.form.RemoteButtonDecorator.ACTION.REMOVE
-      @toRemove = @dom.getElement @postElement
-      if @toRemove?
-        setTimeout @fadeOutTrigger(), 1000
+    PA = wzk.ui.form.RemoteButtonDecorator.POST_ACTIONS
+    if goog.object.containsKey(PA, @postAction)
+      PA[@postAction](@)

   ###*
     @protected
@@ -93,3 +102,11 @@
       @dom.removeNode @toRemove
       undefined
     anim.play()
+
+  ###*
+    @protected
+  ###
+  removeElement: ->
+    @toRemove = @dom.getElement(@postElement)
+    if @toRemove?
+      @dom.getWindow().setTimeout(@fadeOutTrigger(), 1000)


Use the glob.

Use the glob.

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -468,7 +468,7 @@

     describe 'when a path is specified', ->
       it 'returns the status of only that path', ->
-        statuses = repo.getStatusForPaths(['dir'])
+        statuses = repo.getStatusForPaths(['dir/**'])
         expect(_.keys(statuses).length).toBe 1

         status = statuses['dir/a.txt']
@@ -479,7 +479,7 @@
       it 'returns the status of only that path2', ->
         fs.writeFileSync(filePath, '', 'utf8')

-        statuses = repo.getStatusForPaths(['dir'])
+        statuses = repo.getStatusForPaths(['dir/**'])
         expect(_.keys(statuses).length).toBe 0

     describe 'when no path is specified', ->
@@ -495,7 +495,7 @@
     describe 'when an empty string is specified', ->
       it 'returns an empty object', ->
         statuses = repo.getStatusForPaths([''])
-        expect(_.keys(statuses).length).toBe 0
+        expect(_.keys(statuses).length).toBe 1

   describe '.getAheadBehindCount()', ->
     beforeEach ->


Fix spec: the sort order of remote references have changed.

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -244,7 +244,7 @@
     it 'returns a list of all the references', ->
       referencesObj =
                       heads: [ 'refs/heads/diff-lines', 'refs/heads/getHeadOriginal', 'refs/heads/master' ]
-                      remotes: [ 'refs/remotes/origin/HEAD', 'refs/remotes/origin/getHeadOriginal', 'refs/remotes/origin/master', 'refs/remotes/upstream/HEAD', 'refs/remotes/upstream/master' ]
+                      remotes: [ 'refs/remotes/origin/getHeadOriginal', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/master', 'refs/remotes/upstream/HEAD', 'refs/remotes/upstream/master' ]
                       tags: [ 'refs/tags/v1.0', 'refs/tags/v2.0' ]

       repo = git.open(path.join(__dirname, 'fixtures/references.git'))


fix octal escape sequence on recent coffeescript

--- src/exceptionFormatter.coffee
+++ src/exceptionFormatter.coffee
@@ -40,7 +40,7 @@

 # Strip all ansi colors from a string.
 stripColors = (str) ->
-    re = /\033\[[0-9;]*m/g
+    re = new RegExp `'\033\[[0-9;]*m'`, 'g'
     return str.replace re, ''

 endsWith = (str, suffix) -> str[-suffix.length..] is suffix


tests: Skeletons for FBP protocol basics

--- spec/protocol.coffee
+++ spec/protocol.coffee
@@ -130,3 +130,22 @@

     # TODO: automatically represent multiple participants of same class as subgraph

+  describe 'adding an edge', ->
+    it 'should succeed'
+    it 'data should now be forwarded'
+
+  describe 'removing a connected edge', ->
+    it 'should succeed'
+    it 'data should now be forwarded'
+
+  describe 'adding a node', ->
+    it 'should succeed'
+    it 'node should produce data'
+
+  describe 'removing a node', ->
+    it 'should succeed'
+    it 'node should not produce data anymore'
+
+  describe 'adding a component', ->
+    it 'should become available'
+    it 'should be instantiable'


fix(identity): Require NylasAPIRequest correctly

--- src/flux/stores/account-store.coffee
+++ src/flux/stores/account-store.coffee
@@ -189,7 +189,7 @@

   refreshHealthOfAccounts: (accountIds) =>
     NylasAPI ?= require '../nylas-api'
-    NylasAPIRequest ?= require '../nylas-api-request'
+    NylasAPIRequest ?= require('../nylas-api-request').default
     Promise.all(accountIds.map (accountId) =>
       return new NylasAPIRequest({
         api: NylasAPI,
@@ -198,6 +198,7 @@
           accountId: accountId,
         },
       }).run().then (json) =>
+        return unless json
         existing = @accountForId(accountId)
         return unless existing # user may have deleted
         existing.fromJSON(json)


add url to response

add url to response

--- src/hubot-pull-request.coffee
+++ src/hubot-pull-request.coffee
@@ -81,8 +81,11 @@
   reject: (res, user, repo, number) ->
     client = @_client()
     client.reject(user, repo, number)
-    .then ->
-      res.send "rejected: #{user}/#{repo}##{number}"
+    .then (result) ->
+      res.send """
+        rejected: #{user}/#{repo}##{number}
+        #{result.html_url}
+      """
     .then null, (err) ->
       res.robot.logger.error err
       res.send 'hubot-fgb: error'
@@ -90,8 +93,11 @@
   review: (res, user, repo, number, reviewer) ->
     client = @_client()
     client.review(user, repo, number, reviewer)
-    .then ->
-      res.send "assigned: #{user}/#{repo}##{number} > #{reviewer}"
+    .then (result) ->
+      res.send """
+        assigned: #{user}/#{repo}##{number} > #{reviewer}
+        #{result.html_url}
+      """
     .then null, (err) ->
       res.robot.logger.error err
       res.send 'hubot-fgb: error'


Sets source='' to empty otherwise it will be set to 'accepted' when querying archived messages Ref: https://github.com/spree/augury_admin/blob/v5/app/controllers/queues_controller.rb#L31

--- app/assets/javascripts/admin/views/queues/index.js.coffee
+++ app/assets/javascripts/admin/views/queues/index.js.coffee
@@ -65,10 +65,13 @@
       start_date = $('#input-date-range').data('daterangepicker').startDate.utc().format()
       end_date   = $('#input-date-range').data('daterangepicker').endDate.utc().format()

+    # sets source='' to empty otherwise it will be set to 'accepted' when querying archived messages
+    # https://github.com/spree/augury_admin/blob/v5/app/controllers/queues_controller.rb#L31
     state: filter_state
     start_date: start_date
     end_date: end_date
     message: message
     consumer_class: consumer_class
+    source: ''
 )


Remove subviews array from home view

Remove subviews array from home view

--- app/assets/javascripts/backbone/views/home_view.js.coffee
+++ app/assets/javascripts/backbone/views/home_view.js.coffee
@@ -6,38 +6,34 @@
   template: JST["backbone/templates/home"]

   initialize: (options) ->
-    @subviews = []
-    @addTileView(tile) for tile in @collection.models
+    @_addTileView(tile) for tile in @collection.models

     @collection.on 'reset', =>
-      for view in @subviews
+      for cid,view of @subViews
         view.tearDown()
-      @subviews.length = 0
       for model in @collection.models
-        @addTileView(model)
+        @_addTileView(model)
       @render()

     @collection.on 'add', (model) =>
-      unless model.id in (view.model.id for view in @subviews)
-        @addTileView(model)
+      unless model.id in (view.model.id for cid,view of @subViews)
+        @_addTileView(model)
         @render()

     @collection.on 'remove', (model) =>
-      viewsToDelete = (view for view in @subviews when view.model.id == model.id)
+      viewsToDelete = (view for cid,view of @subViews when view.model.id == model.id)
       for view in viewsToDelete
         view.tearDown()
-        @subviews = (v for v in @subviews when v isnt view)

-  addTileView: (model) ->
+  _addTileView: (model) ->
     if model.get("aggregate")
       view = new ProjectMonitor.Views.AggregateProjectView(model: model)
     else
       view = new ProjectMonitor.Views.ProjectView(model: model)
-    @subviews.push(view)
     @registerSubView(view)

   render: ->
     @$el.empty()
-    @$el.append(subview.render().$el) for subview in @subviews
+    @$el.append(subview.render().$el) for cid,subview of @subViews
     @


nvrmind, don't spacewheel on homepage

nvrmind, don't spacewheel on homepage

--- app/assets/javascripts/revised/pages/welcome/index.coffee
+++ app/assets/javascripts/revised/pages/welcome/index.coffee
@@ -7,7 +7,7 @@
     h = $(window).height() - $('.wheel-holder').offset().top
     h = 400 if h > 400
     $('.wheel-holder').css('height', "#{h}px")
-    @spaceWheelHolder(true)
+    # @spaceWheelHolder(true)
     $('.testimonial-container').slick
       infinite: false
       lazyLoad: 'ondemand'


remove whitespaces

remove whitespaces

--- app/assets/javascripts/uploadcare/widget/dialog.js.coffee
+++ app/assets/javascripts/uploadcare/widget/dialog.js.coffee
@@ -25,7 +25,7 @@

   currentDialogPr = null

-  ns.isDialogOpened = ->
+  ns.isDialogOpened = ->
     currentDialogPr != null

   ns.closeDialog = ->
@@ -63,21 +63,21 @@
   class Dialog
     constructor: (@settings, files, tab) ->

-      if files
+      if files
         unless $.isArray(files)
           files = [files]
       else
         files = []

       @dfd = $.Deferred()
-      @dfd.always =>
+      @dfd.always =>
         @__closeDialog()

       @content = $(tpl('dialog'))
         .hide()
         .appendTo('body')
         .addClass(if @settings.multiple then 'uploadcare-dialog-multiple')
-
+
       @files = new utils.CollectionOfPromises()

       @__bind()
@@ -99,7 +99,7 @@
       return promise

     apiForTab: (tabName) ->
-      api =
+      api =
         avalibleTabs: @settings.tabs
         fileColl: @files.readOnly()
         onSwitched: $.Callbacks()
@@ -149,7 +149,7 @@
     __prepareTabs: (tab) ->
       @addTab 'preview'
       @__hideTab 'preview'
-
+
       @addTab tabName for tabName in @settings.tabs
       @switchTab(tab || @settings.tabs[0])


tweak

tweak

--- src/editor.coffee
+++ src/editor.coffee
@@ -129,13 +129,11 @@
     this.enable() unless @options.readOnly

   disable: ->
+    this.enable(false)
+
+  enable: (enabled = true) ->
     this.doSilently( =>
-      @root.setAttribute('contenteditable', false)
-    )
-
-  enable: ->
-    this.doSilently( =>
-      @root.setAttribute('contenteditable', true)
+      @root.setAttribute('contenteditable', enabled)
     )

   init: ->


adding elapsedTime

adding elapsedTime

--- src/engine.coffee
+++ src/engine.coffee
@@ -135,7 +135,7 @@
       if running
         window.requestAnimationFrame(animLoop)

-    update = ->
+    update = (elapsedTime) ->
       self.trigger "beforeUpdate"
       self.trigger "update"
       self.trigger "afterUpdate"


Add more eye animation

Add more eye animation


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -3,7 +3,7 @@
     velocity: Point(0, 0)

   follow: (obj) ->
-    I.velocity = obj.I.po
+    I.velocity = obj.I.position().su

   self.bind "update", ->
     I.velocity = player.position().subtract(self.position()).norm().scale(5)


Have wordCount take text rather than html.

--- src/WordCount.coffee
+++ src/WordCount.coffee
@@ -1 +1 @@
-module.exports = (html) -> html.match(/\S+/g).length+module.exports = (text) -> text.match(/\S+/g)?.length ? 0


Update aniSprite.coffee

Update aniSprite.coffee

put all aniSprite specific information in a new object call _ani. Allow the user to define env.SPRITESHEET_ORIENTATION to allow the use of vertically and horizontally aligned sprite sheets.

--- src/aniSprite.coffee
+++ src/aniSprite.coffee
@@ -4,9 +4,9 @@
 The Greenhorn Gaming Engine animated Sprite class
 ###

+#private helper class
 class AniCycle
     constructor: (data) ->
-        #extract data
         @frame = data.start
         @index = data.index
         @start = data.start
@@ -21,6 +21,13 @@
         for own key, value of env.ANISPRITE_DEFAULT_CONFIG
             config[key] ?= value

+        #create primary object
+        @_ani = {}
+
+        #create secondary objects
+        @_ani.cycles = []
+        @_ani.timer = new Timer(true)
+
         #call the Sprite constructor
         super(config)

@@ -28,9 +35,9 @@
     get: (what) ->
         switch what
             when 'cellWidth', 'cellHeight', 'frameRate'
-                @_dis[what]
+                @_ani[what]
             when 'current', 'animation', 'cycle'
-                @_dis.current.name
+                @_ani.current.name
             else
                 super what

@@ -39,40 +46,36 @@
         if what is 'cellWidth' or
         what is 'cellHeight' or
         what is 'frameRate'
-            @_dis[what] = to
+            @_ani[what] = to
         else if what is 'current' or
             what is 'animation' or
             what is 'cycle'
-                if to isnt @_dis.current.name
-                    @_dis.current.frame = 1
-                    for cycle in @_dis.cycles when cycle.name is to
-                        @_dis.current = cycle
-                        return
-        else if what.match /^cycle/
-            @_dis.cycles ?= new Array()
-            @_dis.timer ?= new Timer(true)
-
+                if to isnt @_ani.current.name
+                    @_ani.current.frame = @_ani.current.start
+                    for cycle in @_ani.cycles when cycle.name is to
+                        @_ani.current = cycle
+        else if what.match /^cycle/i
             i = 0
             to.index ?= i += 1
             to.start ?= 1
             to.stop ?= to.start + env.ANICYCLE_DEFAULT_CONFIG.numFrames - 1
             to.name ?= if what.slice(5) then what.slice(5) else env.ANICYCLE_DEFAULT_CONFIG.name
-            @_dis.cycles.push(new AniCycle(to))
-            @_dis.current ?= to
+            @_ani.cycles.push(new AniCycle(to))
+            @_ani.current ?= to
         else
             super what, to
         this

     #animation control
     play: ->
-        @_dis.timer.start()
+        @_ani.timer.start()
         this
     pause: ->
-        @_dis.timer.pause()
+        @_ani.timer.pause()
         this
     stop: ->
-        @_dis.timer.stop()
-        @_dis.current.frame = 1
+        @_ani.timer.stop()
+        @_ani.current.frame = @_ani.current.start
         this

     #update routines
@@ -85,13 +88,21 @@
             @_dis.context.translate @_pos.x, -@_pos.y
             @_dis.context.rotate -@_pos.a

+            #determine slicing index
+            if env.SPRITESHEET_ORIENTATION.toLowerCase() is 'horizontal'
+                sliceX = @_ani.current.frame - 1
+                sliceY = @_ani.current.index - 1
+            else if env.SPRITESHEET_ORIENTATION.toLowerCase() is 'vertical'
+                sliceX = @_ani.current.index - 1
+                sliceY = @_ani.current.frame - 1
+
             #draw frame
             @_dis.context.drawImage(
                 @_dis.image, #spritesheet
-                (@_dis.current.frame - 1) * @_dis.cellWidth, #sx
-                (@_dis.current.index - 1) * @_dis.cellHeight, #sy
-                @_dis.cellWidth, #swidth
-                @_dis.cellHeight, #sheight
+                @_ani.cellWidth * sliceX, #sx
+                @_ani.cellHeight * sliceY, #sy
+                @_ani.cellWidth, #swidth
+                @_ani.cellHeight, #sheight
                 -@_dis.width / 2, #x
                 -@_dis.height / 2, #y
                 @_dis.width, #width
@@ -101,11 +112,11 @@
             @_dis.context.restore()
     _update: ->
         if @_dis.visible
-            if @_dis.timer.getElapsedTime() >= (1000 / @_dis.frameRate)
-                if @_dis.current.frame < @_dis.current.stop
-                    @_dis.current.frame += 1
+            if @_ani.timer.getElapsedTime() >= (1000 / @_ani.frameRate)
+                if @_ani.current.frame < @_ani.current.stop
+                    @_ani.current.frame += 1
                 else
-                    @_dis.current.frame = @_dis.current.start
-                @_dis.timer.restart()
+                    @_ani.current.frame = @_ani.current.start
+                @_ani.timer.restart()
             super()
 #end class AniSprite


Support focus/blur events

Support focus/blur events

Also add a shortcut (ESC) to close tooltip without loosing focus.
Closes #6.

--- src/aria-tips.coffee
+++ src/aria-tips.coffee
@@ -172,14 +172,14 @@
         # Initialize timeout in the scope.
         timeout = null

-        # `mouseover` event handler.
-        onMouseOver = =>
+        # `mouseover`-like event handler.
+        show = =>

             # Since the tooltip's appearance animation make the trigger lose
             # the focus, the tooltip needs to be its own trigger to prevent it
             # from "flashing". It's basically a debounce mechanism.
-            tip.addEventListener 'mouseover', onMouseOver
-            tip.addEventListener 'mouseout', onMouseOut
+            tip.addEventListener 'mouseover', show
+            tip.addEventListener 'mouseout', hide

             # If the tooltip is appearing or disappearing, clear the timeout.
             if timeout
@@ -198,32 +198,44 @@
                     tip.setAttribute 'aria-hidden', false
                 , 300

-        # `mouseover` event handler.
-        onMouseOut = ->
+        # `mouseover`-like event handler.
+        hide = ->

             # For the same reason as in `onMouseOver`, we debounce the
             # `mouseout` event handler.
             clearTimeout timeout
             timeout = setTimeout ->
                 timeout = null
-                tip.removeEventListener 'mouseover', onMouseOver
-                tip.removeEventListener 'mouseout', onMouseOut
+                tip.removeEventListener 'mouseover', show
+                tip.removeEventListener 'mouseout', hide
                 tip.setAttribute 'aria-hidden', true
             , 30

+        # keyboard events handler
+        onKeyUp = (event) ->
+            return unless event.keyCode is 27
+            hide()
+

         # Bind the actual events.
-        @addEventListener 'mouseover', onMouseOver
-        @addEventListener 'mouseout', onMouseOut
+        @addEventListener 'mouseover', show
+        @addEventListener 'focus', show
+        @addEventListener 'mouseout', hide
+        @addEventListener 'blur', hide
+        @addEventListener 'keyup', onKeyUp

         # Since a tooltip management is scoped (there is one pair of handlers
         # for onMouseOver/onMouseOut events for each trigger), we return an
         # unbind function so it can be called later on.
         unbindEvents = =>
-            tip.removeEventListener 'mouseover', onMouseOver
-            tip.removeEventListener 'mouseout', onMouseOut
-            @removeEventListener 'mouseover', onMouseOver
-            @removeEventListener 'mouseout', onMouseOut
+            # Just in case tooltip is displayed when we unbind it, first try to
+            # hide it.
+            hide()
+            @removeEventListener 'mouseover', show
+            @removeEventListener 'focus', show
+            @removeEventListener 'mouseout', hide
+            @removeEventListener 'blur', hide
+            @removeEventListener 'keyup', onKeyUp

         return unbindEvents


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -1,5 +1,5 @@
 ###*
-The `Clampable` module provides helper methods to clamp object properties.
+The `Clampable` module provides helper methods to clamp object properties. This mod

     player = GameObject
       x: 40


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -12,8 +12,9 @@
   clampToBounds: (bounds) ->
     bounds ||= Rectangle x: 0, y: 0, width: App.width, height: App.height

-    self.clamp "x",
-      min: bounds.x + I.width/2
-      max: bounds.width - I.width/2
+    self.clamp
+      x:
+        min: bounds.x + I.width/2
+        max: bounds.width - I.width/2


Add dependencies to header block

Add dependencies to header block

--- src/github.coffee
+++ src/github.coffee
@@ -2,7 +2,11 @@
 #  List and schedule reminders about open pull requests on github
 #
 # Dependencies:
-#   None
+#  - coffeescript
+#  - cron
+#  - octokat
+#  - moment
+#  - underscore
 #
 # Configuration:
 #   HUBOT_GITHUB_TOKEN - Github Application Token


default will compress the css. to get uncompressed use debug mode

--- src/compilers.coffee
+++ src/compilers.coffee
@@ -84,7 +84,7 @@
       .include(path.dirname(_path))
       .set('include css', true)
       .set('line-numbers', compilers.DEBUG)
-      .set('firebug', ('--firebug' in process.argv))
+      .set('compress', unless compilers.DEBUG)
       .render((err, css) ->
         throw err if err
         result = css


Doc Keymap::partialMatchTimeout property

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -78,7 +78,10 @@
   #   :target - The target element of the event.
   @keydownEvent: (key, options) -> keydownEvent(key, options)

+  # Public: The number of milliseconds allowed before pending states caused
+  # by partial matches of multi-keystroke bindings are terminated.
   partialMatchTimeout: 200
+
   pendingPartialMatches: null
   pendingStateTimeoutHandle: null


update titles

update titles

--- src/locals.coffee
+++ src/locals.coffee
@@ -45,26 +45,26 @@
 ]

 team = [
-  person("gtkesh", t("Giorgi Tkeshelashvili"), t("Georgia Tech"), "/images/team/gtkesh.jpg", [
+  person("gtkesh", t("Giorgi Tkeshelashvili"), t("Co-Director"), "/images/team/gtkesh.jpg", [
     link("facebook", "facebook", "https://www.facebook.com/gtkesh")
     link("twitter", "twitter", "https://twitter.com/gtkesh")
     link("google-plus", "google+", "https://plus.google.com/u/0/+GiorgiTkeshelashvili")
     link("github", "github", "https://github.com/gtkesh")
   ])
-  person("safareli", t("Irakli Safareli"), t("Lord of the Code"), "/images/team/safareli.jpg", [
+  person("safareli", t("Irakli Safareli"), t("Co-Director"), "/images/team/safareli.jpg", [
     link("twitter", "twitter", "https://twitter.com/safareli")
     link("google-plus", "google+", "https://google.com/+IrakliSafareli")
     link("youtube-play", "youtube", "https://www.youtube.com/isafareli")
     link("linkedin", "linkedin", "https://www.linkedin.com/in/safareli")
     link("github", "github", "https://github.com/safareli")
   ])
-  person("otarza", t("Otar Zakalashvili"), t("Technology Agnostic"), "/images/team/otarza.jpeg", [
+  person("otarza", t("Otar Zakalashvili"), t("Director of Operations"), "/images/team/otarza.jpeg", [
     link("facebook", "facebook", "https://www.facebook.com/otarza")
     link("twitter", "twitter", "https://twitter.com/otarza")
     link("linkedin", "linkedin", "https://www.linkedin.com/pub/otar-zakalashvili/15/3a8/547/")
     link("google-plus", "google+", "https://plus.google.com/109734750145229671001")
   ])
-  person("demonno", t("Demur Nodia"), t("wow"), "/images/team/demonno.jpg", [
+  person("demonno", t("Demur Nodia"), t("Special Ops"), "/images/team/demonno.jpg", [
     link("facebook", "facebook", "https://www.facebook.com/demuri.nodia")
     link("twitter", "twitter", "https://twitter.com/demurnodia")
     link("google-plus", "google+", "https://plus.google.com/u/0/+DemurNodia")


(Feat) New queries for slave.

(Feat) New queries for slave.

--- src/manage.coffee
+++ src/manage.coffee
@@ -11,107 +11,114 @@
   Users = (require './users')(con, config)

   # For pdfs
-  getUnprocessedSolutionSample: ->
-    rdb
-    .table("Solutions")
-    .eqJoin("exercise", rdb.table("Exercises"))
-    .filter(
-      # Is due, not processed so far and not in process?
-      rdb.row("right")("dueDate").lt(new Date()).and(
-        rdb.row("left")("processed").default(false).ne(true)
-      ).and(
-        rdb.row("left")("processingLock").default(false).ne(true)
+  API =
+    getUnprocessedSolutionSample: ->
+      rdb
+      .table("Solutions")
+      .eqJoin("exercise", rdb.table("Exercises"))
+      .filter(
+        # Is due, not processed so far and not in process?
+        rdb.row("right")("dueDate").lt(new Date()).and(
+          rdb.row("left")("processed").default(false).ne(true)
+        ).and(
+          rdb.row("left")("processingLock").default(false).ne(true)
+        )
+      ).sample(1)("left").nth(0).default(null).run con
+
+    storeTutor: (tutor) ->
+      if !tutor.name? or !tutor.password? or !tutor.contingent?
+        Promise.reject("You must provide a name, password and contingent field")
+      else if typeof tutor.contingent isnt "number"
+        Promise.reject("The tutor contingent must be a number")
+      else
+        (rdb.table("Tutors").insert tutor, conflict: "update").run con
+
+    get: ->
+      utils.toArray(rdb.table('Exercises').run(con))
+
+    getById: (id)->
+      rdb.table('Exercises').get(id).run(con)
+
+    storeExercise: (exercise) ->
+      exercise.activationDate = rdb.ISO8601(exercise.activationDate)
+      exercise.dueDate = rdb.ISO8601(exercise.dueDate)
+      (rdb.table("Exercises").insert exercise, conflict: "update").run con
+
+    listExercises: ->
+      rdb.table("Exercises").coerceTo('array').run(con)
+
+    # Lists users and the group. also adds their total points
+    listUsers: ->
+      rdb.table("Users").map( (user) ->
+        user.merge(
+          group: Group.getGroupForUserQuery(user("id"))
+          totalPoints: Users.getTotalPointsQuery(user)
+        )
+      ).without("solutions").coerceTo('array').run(con)
+
+    listTutors: ->
+      utils.toArray rdb.table("Tutors").without("pw").run(con)
+
+    listGroups: ->
+      utils.toArray rdb.table("Groups").run(con)
+
+    lockSpecificSolutionForPdfProcessor: (id) ->
+      rdb.table("Solutions").get(id).update({"processingLock": true}, {returnChanges: true}).run con
+
+    # Locks a single solution which is due.
+    # Returns the locked solution, or undefined if none is available
+    lockSolutionForPdfProcessor: ->
+      API.getUnprocessedSolutionSample().then (solution) ->
+        if (!solution)
+          Promise.reject "No more processable solution."
+          #new Promise (resolve) ->
+          #  resolve undefined
+        else
+          API.lockSpecificSolutionForPdfProcessor(solution.id)
+
+    resetPdfForSolution: (solutionId) ->
+      rdb.table("Solutions").get(solutionId).run(con).then( (result) ->
+        if !result?
+          Promise.reject "No such solution."
+        else
+          rdb.table("Solutions").get(solutionId).replace( (solution) ->
+            solution.without("processed").without("processingLock").without("pdf")
+          ).run con
       )
-    ).sample(1)("left").nth(0).default(null).run con

-  storeTutor: (tutor) ->
-    if !tutor.name? or !tutor.password? or !tutor.contingent?
-      Promise.reject("You must provide a name, password and contingent field")
-    else if typeof tutor.contingent isnt "number"
-      Promise.reject("The tutor contingent must be a number")
-    else
-      (rdb.table("Tutors").insert tutor, conflict: "update").run con
+    # Save the finished pdf file into the solution
+    insertFinishedPdf: (solutionId, pdfData)->
+      rdb.table("Solutions").get(solutionId).update(
+        processed: true
+        processingLock: false,
+        pdf: pdfData
+      ).run con

-  get: ->
-    utils.toArray(rdb.table('Exercises').run(con))
-
-  getById: (id)->
-    rdb.table('Exercises').get(id).run(con)
-
-  storeExercise: (exercise) ->
-    exercise.activationDate = rdb.ISO8601(exercise.activationDate)
-    exercise.dueDate = rdb.ISO8601(exercise.dueDate)
-    (rdb.table("Exercises").insert exercise, conflict: "update").run con
-
-  listExercises: ->
-    rdb.table("Exercises").coerceTo('array').run(con)
-
-  # Lists users and the group. also adds their total points
-  listUsers: ->
-    rdb.table("Users").map( (user) ->
-      user.merge(
-        group: Group.getGroupForUserQuery(user("id"))
-        totalPoints: Users.getTotalPointsQuery(user)
-      )
-    ).without("solutions").coerceTo('array').run(con)
-
-  listTutors: ->
-    utils.toArray rdb.table("Tutors").without("pw").run(con)
-
-  listGroups: ->
-    utils.toArray rdb.table("Groups").run(con)
-
-  # Locks a single solution which is due.
-  # Returns the locked solution, or undefined if none is available
-  lockSolutionForPdfProcessor: ->
-    @getUnprocessedSolutionSample().then (solution) ->
-      if (!solution)
-        Promise.reject "No more processable solution."
-        #new Promise (resolve) ->
-        #  resolve undefined
-      else
-        rdb
-        .table("Solutions")
-        .get(solution.id)
-        .update({"processingLock": true}, {returnChanges: true})("changes")("new_val").nth(0).run(con)
-
-  resetPdfForSolution: (solutionId) ->
-    rdb.table("Solutions").get(solutionId).replace (solution) ->
-      solution.without("processed").without("processingLock").without("pdf")
-
-  # Save the finished pdf file into the solution
-  insertFinishedPdf: (solutionId, pdfData)->
-    rdb.table("Solutions").get(solutionId).update(
-      processed: true
-      processingLock: false,
-      pdf: pdfData
-    ).run con
-
-  updateOldestSolution: (minAge) ->
-    minAge = minAge or 300
-    if !config.sharejs?.rethinkdb?.db?
-      return Promise.reject "No sharejs database defined"
-    rdb.do(
-      rdb.table("Solutions")
-        .orderBy({index: "lastStore"})
-        .filter(rdb.row("lastStore").add(minAge).lt(rdb.now()))
-        .eqJoin('exercise', rdb.table("Exercises")).nth(0),
-      rdb.table("Solutions")
-        .orderBy({index: "lastStore"})
-        .filter(rdb.row("lastStore").add(minAge).lt(rdb.now()))
-        .nth(0).update(lastStore: rdb.now()),
-      (oldest, update) ->
-        rdb.table("Solutions").get(oldest("left")("id")).update({
-          tasks: oldest("right")("tasks").map( (task) ->
-            # left = Solutions
-            # right = Exercises
-            # id = group : exercises : task("number")
-            rdb.db(config.sharejs.rethinkdb.db)
-              .table(config.sharejs.tableName)
-              .get(
-                rdb.add(oldest("left")("group").coerceTo("string"),":",oldest("right")("id").coerceTo("string"),":",task("number").coerceTo("string"))
-              ).pluck("_data")
-          ).map (s) ->
-            s.merge({"solution": s("_data")}).without("_data")
-          } , {nonAtomic: true})
-        ).run(con)
+    updateOldestSolution: (minAge) ->
+      minAge = minAge or 300
+      if !config.sharejs?.rethinkdb?.db?
+        return Promise.reject "No sharejs database defined"
+      rdb.do(
+        rdb.table("Solutions")
+          .orderBy({index: "lastStore"})
+          .filter(rdb.row("lastStore").add(minAge).lt(rdb.now()))
+          .eqJoin('exercise', rdb.table("Exercises")).nth(0),
+        rdb.table("Solutions")
+          .orderBy({index: "lastStore"})
+          .filter(rdb.row("lastStore").add(minAge).lt(rdb.now()))
+          .nth(0).update(lastStore: rdb.now()),
+        (oldest, update) ->
+          rdb.table("Solutions").get(oldest("left")("id")).update({
+            tasks: oldest("right")("tasks").map( (task) ->
+              # left = Solutions
+              # right = Exercises
+              # id = group : exercises : task("number")
+              rdb.db(config.sharejs.rethinkdb.db)
+                .table(config.sharejs.tableName)
+                .get(
+                  rdb.add(oldest("left")("group").coerceTo("string"),":",oldest("right")("id").coerceTo("string"),":",task("number").coerceTo("string"))
+                ).pluck("_data")
+            ).map (s) ->
+              s.merge({"solution": s("_data")}).without("_data")
+            } , {nonAtomic: true})
+          ).run(con)


Use source names in repots

Use source names in repots

--- workers/librato/lib/librato/index.coffee
+++ workers/librato/lib/librato/index.coffee
@@ -109,12 +109,15 @@
   print "MQ Deliver Rate: " + mq_rate_deliver
   mq_queued =
     name: 'mq.queued'
+    source: 'total'
     value: mq_queue_total
   mq_deliver_rate =
-    name: 'mq.rate.deliver'
+    name: 'mq.rate'
+    source: 'deliver'
     value: mq_rate_deliver
   mq_publish_rate =
-    name: 'mq.rate.publish'
+    name: 'mq.rate'
+    source: 'publish'
     value: mq_rate_publish

   # Combine all stats


Cleans up file

Cleans up file

--- apps/channel/index.coffee
+++ apps/channel/index.coffee
@@ -1,17 +1,12 @@
-#
-# The express app for the "channel" app.
-#
-# Simply exports the express instance to be mounted into the project,
-# and loads the routes.
-#
-
-express = require "express"
-routes = require "./routes"
+express = require 'express'
+routes = require './routes'

 app = module.exports = express()
-app.set "views", __dirname + "/templates"
-app.set "view engine", "jade"
-app.get "/:username/:channel_slug", routes.channel
-app.get "/:username/:channel_slug/embed", routes.embed
-app.get "/:username/:channel_slug/followers", routes.followers
-app.get "/:username/:channel_slug/block/:block_id", routes.channel+
+app.set 'views', "#{__dirname}/templates"
+app.set 'view engine', 'jade'
+
+app.get '/:username/:channel_slug', routes.channel
+app.get '/:username/:channel_slug/block/:block_id', routes.channel
+app.get '/:username/:channel_slug/embed', routes.embed
+app.get '/:username/:channel_slug/followers', routes.followers


Restore support for {selection,nonselection,decimated}_defaults

--- bokehjs/src/coffee/models/renderers/glyph_renderer.coffee
+++ bokehjs/src/coffee/models/renderers/glyph_renderer.coffee
@@ -10,28 +10,34 @@
   initialize: (options) ->
     super(options)

-    @glyph = @build_glyph_view(@mget("glyph"))
-
-    glyph_attrs =  _.omit(_.clone(@mget("glyph").attributes), 'id')
+    base_glyph = @mget("glyph")
+    has_fill = _.contains(base_glyph.mixins, "fill")
+    has_line = _.contains(base_glyph.mixins, "line")
+    glyph_attrs = _.omit(_.clone(base_glyph.attributes), 'id')
+
+    mk_glyph = (defaults) ->
+      attrs = _.clone(glyph_attrs)
+      if has_fill then _.extend(attrs, defaults.fill)
+      if has_line then _.extend(attrs, defaults.line)
+      return new (base_glyph.constructor)(attrs)
+
+    @glyph = @build_glyph_view(base_glyph)

     selection_glyph = @mget("selection_glyph")
     if not selection_glyph?
-      #attrs = _.extend {}, glyph_attrs, @model.selection_defaults
-      selection_glyph = new (@mget("glyph")).constructor(glyph_attrs)
+      selection_glyph = mk_glyph(@model.selection_defaults)
     @selection_glyph = @build_glyph_view(selection_glyph)

     nonselection_glyph = @mget("nonselection_glyph")
     if not nonselection_glyph?
-      #attrs = _.extend {}, glyph_attrs, @model.nonselection_defaults
-      nonselection_glyph = new (@mget("glyph")).constructor(glyph_attrs)
+      nonselection_glyph = mk_glyph(@model.nonselection_defaults)
     @nonselection_glyph = @build_glyph_view(nonselection_glyph)

     hover_glyph = @mget("hover_glyph")
     if hover_glyph?
       @hover_glyph = @build_glyph_view(hover_glyph)

-    #attrs = _.extend {}, glyph_attrs, @model.decimated_defaults
-    decimated_glyph = new (@mget("glyph")).constructor(glyph_attrs)
+    decimated_glyph = mk_glyph(@model.decimated_defaults)
     @decimated_glyph = @build_glyph_view(decimated_glyph)

     @xmapper = @plot_view.frame.get('x_mappers')[@mget("x_range_name")]
@@ -234,9 +240,9 @@
     level: 'glyph'
   }

-  selection_defaults: {}
-  decimated_defaults: {fill_alpha: 0.3, line_alpha: 0.3, fill_color: "grey", line_color: "grey"}
-  nonselection_defaults: {fill_alpha: 0.2, line_alpha: 0.2}
+  selection_defaults: {fill: {}, line: {}}
+  decimated_defaults: {fill: {fill_alpha: 0.3, fill_color: "grey"}, line: {line_alpha: 0.3, line_color: "grey"}}
+  nonselection_defaults: {fill: {fill_alpha: 0.2, line_alpha: 0.2}, line: {}}

 module.exports =
   Model: GlyphRenderer


factor out layout world

factor out layout world

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -37,17 +37,18 @@
     t.ok world.is_array(sprites), 'sprites rx_array'
     t.ok sprite_dict = sprites.at(0), 'sprite'
     t.end()
+
+  test "layout world", (t) ->
+    t.ok level, 'active level'
+    t.ok grid, 'grid'
+    t.ok all_sprites, 'sprites'
+    t.ok sprite, 'sprite'
+    t.end()

   test "layout sprite", (t) ->
     size = 64
     c = size / 2
     transform_result = "translate(#{size},#{size}) rotate(0 #{c} #{c})"
-
-    t.ok level, 'active level'
-    t.ok grid, 'grid'
-    t.ok all_sprites, 'sprites'
-    t.ok sprite, 'sprite'
-    console.log 'sprite', sprite, sprite.get_raw('x'), my.inspect(sprite)
     t.equal sprite.get('position').all().toString(), "1,1", 'sprite position'
     t.ok sprite.get('x') > 0, 'non-zero x'
     t.ok sprite.get('y') > 0, 'non-zero y'


global settings menu delay fix

global settings menu delay fix

--- client/landing/app/MainApp/maincontroller/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainview.coffee
@@ -110,6 +110,7 @@
       appController = KD.getSingleton "kodingAppsController"
       frontApp = appManager.getFrontApp()
       frontAppName = name for name, instances of appManager.appControllers when frontApp in instances
+      console.log frontAppName
       appController.constructor.manifests?[frontAppName]

     @mainSettingsMenuButton = new KDButtonView
@@ -147,9 +148,9 @@
       tabHandleContainer : @mainTabHandleHolder
     ,null

-    @mainTabView.on "PaneDidShow", =>
+    @mainTabView.on "PaneDidShow", => KD.utils.wait 100, =>
       appManifest = getFrontAppManifest()
-      @mainSettingsMenuButton.$()[if appManifest?.menu then "fadeIn" else "fadeOut"]()
+      @mainSettingsMenuButton[if appManifest?.menu then "show" else "hide"]()

     mainController = @getSingleton('mainController')
     mainController.popupController = new VideoPopupController


Pass in a view, not a class

Pass in a view, not a class

--- core/app/backbone/views/evidence/vote_up_down_view.coffee
+++ core/app/backbone/views/evidence/vote_up_down_view.coffee
@@ -41,7 +41,7 @@
       @_openVoteDownPopup()
       @model.disbelieve()

-  _open_vote_popup: (selector, view_klass) ->
+  _open_vote_popup: (selector, contentView) ->
     return if @popoverOpened selector

     @popoverResetAll()
@@ -49,7 +49,7 @@
       side: @_side()
       align: 'top'
       fadeTime: 200
-      contentView: new view_klass model: @model
+      contentView: contentView

   _side: ->
     if @model.get('type') == 'believes'
@@ -60,12 +60,12 @@
   _openVoteUpPopup: ->
     return unless @_canShowPopover()

-    @_open_vote_popup '.js-up', FactRelationVoteUpView
+    @_open_vote_popup '.js-up', new FactRelationVoteUpView model: @model

   _openVoteDownPopup: ->
     return unless @_canShowPopover()

-    @_open_vote_popup '.js-down', FactRelationVoteDownView
+    @_open_vote_popup '.js-down', new FactRelationVoteDownView model: @model

   _canShowPopover: ->
     @model instanceof FactRelation && Factlink.Global.can_haz['vote_up_down_popup']


Update sample proportion class

Update sample proportion class

--- public/js/sample-proportion.coffee
+++ public/js/sample-proportion.coffee
@@ -1,13 +1,34 @@
 class App.SampleProportion

 	SD_TO_GRAPH = 5
+	POINTS_PER_CURVE = 1000

 	constructor: (@participants, @conversions) ->
 		@p = @conversions / @participants
 		@sd = this.getStdDev()
+		@xRange = this.getXAxisRange()
+		@xValues = this.getCurveXValues()
+		@yValues = this.getCurveYValues()

 	getStdDev: ->
 		Math.sqrt @p * (1 - @p) / @participants

 	getXAxisRange: ->
-		new App.Range @p - SD_TO_GRAPH * @sd, @p + SD_TO_GRAPH * @sd+		new App.Range @p - SD_TO_GRAPH * @sd, @p + SD_TO_GRAPH * @sd
+
+	# See: http://en.wikipedia.org/wiki/Normal_distribution
+	getDensity: (x) ->
+		a = 1 / (@sd * Math.sqrt(2 * Math.PI))
+		b = Math.exp(-(Math.pow(x - @p, 2)) / (2 * Math.pow(@sd, 2)))
+		a * b
+
+	getCurveXValues: ->
+		xValues = []
+		interval = (@xRange.max - @xRange.min) / (POINTS_PER_CURVE + 1)
+		xValues.push @xRange.min + interval * (i + 1) for i in [0..POINTS_PER_CURVE - 1]
+		xValues
+
+	getCurveYValues: ->
+		yValues = []
+		yValues.push @getDensity xValue for xValue in @xValues
+		yValues


Add user_map to ProjectControlPanel

Add user_map to ProjectControlPanel

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -38,6 +38,7 @@

 {HelpEmailLink}   = require('./customize')
 {ShowSupportLink} = require('./support')
+{SSHKeyAdder, SSHKeyList} = require('./widget-ssh-keys/main')

 {PROJECT_UPGRADES} = require('smc-util/schema')

@@ -592,7 +593,8 @@
         show_ssh : false

     propTypes :
-        project : rtypes.object.isRequired
+        project  : rtypes.object.isRequired
+        user_map : rtypes.immutable.Map

     open_authorized_keys: (e) ->
         e.preventDefault()
@@ -615,6 +617,7 @@
         if host?
             if @state.show_ssh

+
                     SSH into your project: First add your public key to ~/.ssh/authorized_keys, then use the following username@host:
                     {# WARNING: previous use of <FormControl> here completely breaks copy on Firefox.}

{"#{misc.replace_all(project_id, '-', '')}@#{host}.sagemath.com"}

@@ -1046,13 +1049,13 @@
                         total_project_quotas                 = {total_project_quotas}
                         all_upgrades_to_this_project         = {all_upgrades_to_this_project} />

-                    <HideDeletePanel       key='hidedelete'    project={@props.project} />
+                    <HideDeletePanel key='hidedelete' project={@props.project} />
                 </Col>
                 <Col sm=6>
                     <CollaboratorsPanel  project={@props.project} user_map={@props.user_map} />
-                    <ProjectControlPanel   key='control'       project={@props.project} />
-                    <SageWorksheetPanel    key='worksheet'     project={@props.project} />
-                    <JupyterServerPanel    key='jupyter'        project_id={@props.project_id} />
+                    <ProjectControlPanel key='control' project={@props.project} user_map={@props.user_map} />
+                    <SageWorksheetPanel  key='worksheet' project={@props.project} />
+                    <JupyterServerPanel  key='jupyter' project_id={@props.project_id} />
                 </Col>
             </Row>


Only path turtles

Only path turtles

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -9,34 +9,6 @@
       width: size / 2
       fill: "#00aa00"
     }
-  draw_turtle = (i, j) ->
-    length = cell_size * 0.7
-    width = cell_size * 0.5
-    x = i*cell_size
-    y = j*cell_size
-    head = x + length
-    half_width = width / 2
-    S.g {class: "turtle"}, [
-      S.circle {
-        class: "head"
-        cx: head, cy: y
-        r: half_width
-        fill: "#00aa00"
-      }
-      S.g {class: "legs"}, [
-        draw_leg(x+half_width, y+half_width)
-        draw_leg(x-width, y+half_width)
-        draw_leg(x+half_width, y-3*half_width)
-        draw_leg(x-width, y-3*half_width)
-      ]
-      S.ellipse {
-        class: "body"
-        cx: x, cy: y
-        rx: length, ry: width
-        fill: "#00ff00"
-        stroke: "#88ff88"
-      }
-    ]
   draw_arc = (width, x_sign, y_sign) ->
     "a#{width},#{width} 0 0,1 #{x_sign*width},#{y_sign*width} "
   draw_ellipse = (width, x_sign, y_sign) ->
@@ -68,6 +40,5 @@
       stroke: "black"
     }
     S.path {class: "grid-lines", d: grid_path, stroke: "#ffffff"}
-    draw_turtle(2,4)
     draw_turtle_path(1,2)
   ]


dont error

dont error

--- src/dt-jquery.coffee
+++ src/dt-jquery.coffee
@@ -190,10 +190,10 @@
             @fn.attr(el, key, value)

     onshow: (el) ->
-        @fn.show(el)
+        @fn.show(el) if el._jquery?

     onhide: (el) ->
-        @fn.hide(el)
+        @fn.hide(el) if el._jquery?

     onremove: (el, opts) ->
         return unless el._jquery?


remove comment

remove comment

--- vendor/assets/javascripts/brainstem/loaders/abstract-loader.coffee
+++ vendor/assets/javascripts/brainstem/loaders/abstract-loader.coffee
@@ -198,7 +198,6 @@
       if cacheObject && cacheObject.valid
         subset = _.map cacheObject.results, (result) => @storageManager.storage(result.key).get(result.id)
         if (_.all(subset, (model) => model.dependenciesAreLoaded(@loadOptions)))
-          # if this if is still needed, we'd need to also check to make sure all optional_fields we need are in these models
           @_onLoadSuccess(subset)
           return @externalObject


Scope tether-added classes

Scope tether-added classes

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -19,6 +19,7 @@
       target: @_textContainer(@$highlightElements[0])
       attachment: 'top left'
       targetAttachment: 'top right'
+      classPrefix: 'factlink-tether'

   destroy: ->
     @_tether.destroy()
@@ -64,6 +65,7 @@
       target: @$paragraph[0]
       attachment: 'top left'
       targetAttachment: 'top right'
+      classPrefix: 'factlink-tether'

     FactlinkJailRoot.on 'factlink.factsLoaded factlinkAdded', @_destroyUnlessValid


#880 - removed 'Add from Question Library' from row selector

--- jsapp/xlform_model_view/view.rowSelector.templates.coffee
+++ jsapp/xlform_model_view/view.rowSelector.templates.coffee
@@ -11,7 +11,7 @@
             #{closeRowSelectorButton}

Choose question type

-

+

               Or
               <button class="menu-title btn rowselector_openlibrary">Add from Question Library</button>


begin to evaluate functions

begin to evaluate functions

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -29,6 +29,8 @@
         Builder = IntegerLiteral
       when 'reference'
         Builder = Reference
+      when 'function'
+        Builder = FunctionCall
       else
         throw 'unknown node type'
     new Builder node
@@ -133,6 +135,20 @@
   evaluate: (data) ->
     data[@name]

+class FunctionCall
+  constructor: (node) ->
+    @params = (factory.build param for param in node.parameters)
+    @func = funcs[node.function.toLowerCase()]
+  evaluate: (data) ->
+    vals = (param.evaluate data for param in @params)
+    @func vals
+
+funcs = {
+  'and': (p) -> p.reduce (a, b) -> a && b,
+  'or': (p) -> p.reduce (a, b) -> a || b,
+  'not': (p) -> !p[0]
+}
+
 evaluate = (formula, data) ->
   f = factory.build formula
   f.evaluate data


some renaming

some renaming

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -150,7 +150,7 @@

     setupThreejs (-> setupFonts (-> setupOrbitControls runCallbacks))

-_scene_using_renderer = undefined
+_sceneUsingRenderer = undefined
 _renderer = {}
 # get the best-possible THREE.js renderer (once and for all)
 # based on Detector.js's webgl detection
@@ -158,18 +158,18 @@
     if @WebGLRenderingContext
         canvas = document.createElement 'canvas'
         if canvas.getContext('webgl') or canvas.getContext('experimental-webgl')
-            _default_renderer_type = 'webgl'
-if not _default_renderer_type?
-    _default_renderer_type = 'canvas'
-
-get_renderer = (scene, type) ->
+            _defaultRendererType = 'webgl'
+if not _defaultRendererType?
+    _defaultRendererType = 'canvas'
+
+getRenderer = (scene, type) ->
     # if there is a scene currently using this renderer, tell it to switch to
     # the static renderer.
-    if _scene_using_renderer? and _scene_using_renderer isnt scene
-        _scene_using_renderer.set_static_renderer()
+    if _sceneUsingRenderer? and _sceneUsingRenderer isnt scene
+        _sceneUsingRenderer.setStaticRenderer()

     # now scene takes over using this renderer
-    _scene_using_renderer = scene
+    _sceneUsingRenderer = scene

     if not _renderer[type]?
         switch type
@@ -231,7 +231,7 @@

             @opts.height = if @opts.height? then @opts.height else @opts.width*2/3

-            @set_dynamic_renderer()
+            @setDynamicRenderer()
             @init_orbit_controls()
             @init_on_mouseover()

@@ -268,13 +268,13 @@
         if @opts.renderer is 'canvas'
             @element.title = 'WARNING: using slow non-WebGL canvas renderer'

-    set_dynamic_renderer: ->
+    setDynamicRenderer: ->
         if @renderer_type is 'dynamic'
             # already have it
             return

-        @opts.renderer ?= _default_renderer_type
-        @renderer = get_renderer @, @opts.renderer
+        @opts.renderer ?= _defaultRendererType
+        @renderer = getRenderer @, @opts.renderer
         @renderer_type = 'dynamic'

         # remove the current renderer (if it exists)
@@ -295,7 +295,7 @@
             @animate render: false
         @render_scene true

-    set_static_renderer: ->
+    setStaticRenderer: ->
         if @renderer_type is 'static'
             # already have it
             return
@@ -323,13 +323,13 @@
     init_on_mouseover: ->

         @element.onmouseenter = =>
-            @set_dynamic_renderer()
+            @setDynamicRenderer()

         @element.onmouseleave = =>
-            @set_static_renderer()
+            @setStaticRenderer()

         @element.onclick = =>
-            @set_dynamic_renderer()
+            @setDynamicRenderer()

     # initialize functions to create new vectors, which take into account the scene's 3d frame aspect ratio.
     init_aspect_ratio_functions: ->
@@ -843,10 +843,10 @@
         if @renderer_type isnt 'dynamic'
             # if we don't have the renderer, swap it in, make a static image,
             # then give it back to whoever had it.
-            owner = _scene_using_renderer
-            @set_dynamic_renderer()
-            @set_static_renderer()
-            owner?.set_dynamic_renderer()
+            owner = _sceneUsingRenderer
+            @setDynamicRenderer()
+            @setStaticRenderer()
+            owner?.setDynamicRenderer()
             return

         if not @camera?


fixed reply/send problem. 4d3d3d3 engaged

--- src/scripts/celery-man.coffee
+++ src/scripts/celery-man.coffee
@@ -14,21 +14,21 @@

 module.exports = (robot) ->
   robot.respond /.*celery\s?man/i, (msg) ->
-    msg.reply "http://mlkshk.com/r/4SBP.gif"
+    msg.send "http://mlkshk.com/r/4SBP.gif"
   robot.respond /.*4d3d3d3/i, (msg) ->
-    msg.reply "4d3d3d3 ENGAGED"
-    msg.reply "http://i.imgur.com/w1qQO.gif"
+    msg.send "4d3d3d3 ENGAGED"
+    msg.send "http://i.imgur.com/w1qQO.gif"
   robot.respond /.*add sequence:? oyster/i, (msg) ->
-    msg.reply "http://i.imgur.com/EH2CJ.png"
+    msg.send "http://i.imgur.com/EH2CJ.png"
   robot.respond /.*oyster smiling/, (msg) ->
-    msg.reply "http://i.imgur.com/e71P6.png"
+    msg.send "http://i.imgur.com/e71P6.png"
   robot.respond /do we have any new sequences/i, (msg) ->
-    msg.reply "I have a BETA sequence I have been working on. Would you like to see it?"
+    msg.send "I have a BETA sequence I have been working on. Would you like to see it?"
   robot.respond /.*hat wobble/i, (msg) ->
-    msg.reply "http://i.imgur.com/5kVq4.gif"
+    msg.send "http://i.imgur.com/5kVq4.gif"
   robot.respond /.*flarhgunnstow/i, (msg) ->
-    msg.reply "http://i.imgur.com/X0sNq.gif"
+    msg.send "http://i.imgur.com/X0sNq.gif"
   robot.respond /.*nude tayne/, (msg) ->
-    msg.reply "Not computing. Please repeat:"
+    msg.send "Not computing. Please repeat:"
   robot.respond /NUDE TAYNE/, (msg) ->
-    msg.reply "http://i.imgur.com/yzLcf.png"
+    msg.send "http://i.imgur.com/yzLcf.png"


Remove superflous code

Remove superflous code

--- server/lib/presenters/theme.coffee
+++ server/lib/presenters/theme.coffee
@@ -25,8 +25,4 @@
           theme.indicators = indicators
           callback()
         ).fail(callback)
-    ).then( ->
-      callback null, themes
-    ).fail( (err) ->
-      callback err
     )


Only call upstream when sync is true

Only call upstream when sync is true

--- app/assets/javascripts/action_cable_notifications/collection.coffee
+++ app/assets/javascripts/action_cable_notifications/collection.coffee
@@ -119,8 +119,8 @@
       @data.push (fields)
       @callbacks?.create?.call(this, fields)
       @callbacks?.changed?.call(this, @data) unless options.batching
-
-    upstream.call(this, "create", {fields: fields})
+    else
+      upstream.call(this, "create", {fields: fields})
     fields

   # Update an existing record
@@ -136,8 +136,8 @@
         @callbacks?.update?.call(this, selector, fields, options)
         _.extend(record, fields)
         @callbacks?.changed?.call(this, @data) unless options.batching
-
-      upstream.call(this, "update", {id: record.id, fields: fields})
+      else
+        upstream.call(this, "update", {id: record.id, fields: fields})
       record

   # Update an existing record or inserts a new one if there is no match
@@ -155,6 +155,6 @@
         @data.splice(index, 1)
         @callbacks?.destroy?.call(this, selector)
         @callbacks?.changed?.call(this, @data) unless options.batching
-
-      upstream.call(this, "destroy", {id: record.id})
+      else
+        upstream.call(this, "destroy", {id: record.id})
       record


Removed debug.

Removed debug.

--- app/assets/javascripts/app/controllers/_settings/area_switch.coffee
+++ app/assets/javascripts/app/controllers/_settings/area_switch.coffee
@@ -36,7 +36,6 @@
       subSetting: @subSetting
     )
     for localSetting in @subSetting
-      console.log('localSetting', localSetting.state_current)
       new App.ControllerForm(
         el: @$('.form-item')
         params: localSetting.state_current.value


make preview work with files added before tab init

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab.js.coffee
@@ -16,7 +16,8 @@
     constructor: ->
       super

-      @__doCrop = @settings.crop.enabled
+      $.each @dialogApi.fileColl.get(), (i, file) =>
+        @__setFile file

       @dialogApi.fileColl.onAdd.add @__setFile

@@ -45,9 +46,9 @@
       @__afterRender state

     __afterRender: (state) ->
-      if state is 'unknown' and @__doCrop
+      if state is 'unknown' and @settings.crop.enabled
         @__hideDoneButton()
-      if state is 'image' and @__doCrop
+      if state is 'image' and @settings.crop.enabled
         @__initCrop()

     __hideDoneButton: ->


Added comments to the compare window fix

--- app/assets/javascripts/comparison.js.coffee
+++ app/assets/javascripts/comparison.js.coffee
@@ -74,6 +74,7 @@
       $('.nav_clear_btn:visible').hide()

   show_comparison_window = (num_tries = 0)->
+    # num_tries refers to the number of times the page has tried to refresh the compare window due to failed spec searches. Defaults to 0 unless overridden by the refresh call in buildComparisonMatrix()
     skus = optemo_module.readAllCookieValues(optemo_module.cmpcookie)
     width = undefined

@@ -91,7 +92,8 @@
       # Jquery 1.5 would finish all the requests before building the comparison matrix once
       # With 1.4.2 we can't do that. Keep code for later.
       # $.when.apply(this,reqs).done();
-      buildComparisonMatrix(num_tries)
+      buildComparisonMatrix(num_tries) # Passes num_tries because if the spec search fails, buildComparisonMatrix needs to know
+                                       # how many times it's already tried to refresh the page
       if ($.browser.msie && $.browser.version.substr(0,1)<7) # IE6 comparison page "more specs" spacing issue
         # This is a weird combination of margins, absolute positioning, and an inserted spacer div. It seems stable.
         $('<div> </div>').insertBefore('#info div.togglespecs').css({"height" : "23px", "width" : "1px", "display" : "block", "clear" : "both"})
@@ -150,6 +152,7 @@

   #Build spec matrix from API data
   buildComparisonMatrix = (num_tries = 0)->
+    # num_tries defaults to 0 but otherwise can be overriden if the page has been refreshed by this function (in the case of a failed spec search)
     skus = $('#basic_matrix').attr('data-skus').split(',')
     anchor = $('#hideable_matrix')
     # Build up the direct comparison table. Similar method to views/direct_comparison/index.html.erb
@@ -161,10 +164,11 @@
       array.push(skudata)
     if emptyspecs
       if (num_tries < 2) then show_comparison_window(num_tries + 1) # try 3 times to get the data, then give up
-      alert ('Failed: '+(num_tries+1)+' try/tries')
+                                                                    # this implies a total failure for some inexplicable reason,
+                                                                    # or that there are no specs to find
       $(".togglespecs").hide()
-    else
-      alert ('Succeeded: '+(num_tries+1)+' try/tries')
+    #else
+    #  alert ('Tried '+(num_tries+1)+' time(s)') ####### Uncomment this to see how many tries it takes a product to succeed #######
     grouped_specs = merge_bb_json.apply(null,array)
     #Set up Headers
     for sku,index in skus


added some comments for custom events

added some comments for custom events

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -144,6 +144,7 @@

             o.el.data stateKey, 'decompressed'

+            # bind the element to a custom event named compress
             o.el.on "compress.#{ pluginName }", (e) ->
                 current = $ this

@@ -170,6 +171,7 @@

                 o.opts.onAfterCompress(_this) if $.isFunction(o.opts.onAfterCompress)

+            # bind the element to a custom event named decompress
             o.el.on "decompress.#{ pluginName }", (e) ->
                 current = $ this


Fixed the url for ban.js.coffee model so it can both create and update ban records.

--- app/assets/javascripts/models/ban.js.coffee
+++ app/assets/javascripts/models/ban.js.coffee
@@ -1,6 +1,10 @@
 class Cloudsdale.Models.Ban extends Backbone.Model

-  url: -> "/v1/#{@get('jurisdiction_type')}s/#{@get('jurisdiction_id')}/bans"
+  url: ->
+    if @get('is_transient') == false && @id
+      "/v1/#{@get('jurisdiction_type')}s/#{@get('jurisdiction_id')}/bans/#{@id}.json"
+    else
+      "/v1/#{@get('jurisdiction_type')}s/#{@get('jurisdiction_id')}/bans.json"

   defaults:
     reason: ""


Added an .ace_editor to auto-create content editors

--- app/assets/javascripts/smithy/editor.coffee
+++ app/assets/javascripts/smithy/editor.coffee
@@ -2,13 +2,15 @@
   name = 'template_content' if !name
   textarea_id = name + '-' + id
   editor_id = name + '_editor-' + id
-  $('#'+editor_id).show()
+  $editor = $("##{editor_id}")
+  $textarea = $("##{textarea_id}")
+  $editor.show();
+  $textarea.hide()
   editor = ace.edit(editor_id)
-  $textarea = $('#'+textarea_id)
-  $textarea.hide()
-  create_ace_toolbar(editor, $textarea.attr('data-assets-url'), $textarea.attr('data-pages-url'))
+  create_ace_toolbar(editor, $editor.attr('data-assets-url'), $editor.attr('data-pages-url'))
   session = editor.getSession()
   session.setMode("ace/mode/" + template_type)
+  editor.renderer.setShowGutter(false) if template_type == 'markdown'
   session.setValue($textarea.val())
   session.setTabSize(2)
   session.setUseSoftTabs(true)
@@ -144,4 +146,9 @@
       session.insert({ row: current_row, column: 0 }, '### ')
     when 'h4'
       session.insert({ row: current_row, column: 0 }, '#### ')
-  editor.focus()+  editor.focus()
+
+$ ->
+  $('.ace_editor').each ->
+    return unless $(this).data('id') && $(this).data('type') && $(this).data('name')
+    ace_edit($(this).data('id'), $(this).data('type'), $(this).data('name'))


make exception when handling a message non-fatal

--- src/smc-project/client.coffee
+++ src/smc-project/client.coffee
@@ -389,7 +389,10 @@
             if not mesg.multi_response
                 delete @_hub_callbacks[mesg.id]
                 delete @_hub_client_sockets[socket.id].callbacks[mesg.id]
-            f(mesg)
+            try
+                f(mesg)
+            catch err
+                dbg("WARNING: error handling message from client. -- #{err}")
             return true
         else
             dbg("no callback")


sync: be more careful with project client connection

--- src/smc-project/client.coffee
+++ src/smc-project/client.coffee
@@ -261,13 +261,11 @@
             dbg()
             x = @_hub_client_sockets[socket.id] = {socket:socket, callbacks:{}, activity:new Date()}
             socket.on 'end', =>
-                if not x.callbacks?
-                    # end was already triggered
-                    return
                 dbg("end")
-                for id, cb of x.callbacks
-                    cb?('socket closed')
-                delete x.callbacks  # so additional trigger of end doesn't do anything
+                if x.callbacks?
+                    for id, cb of x.callbacks
+                        cb?('socket closed')
+                    delete x.callbacks  # so additional trigger of end doesn't do anything
                 delete @_hub_client_sockets[socket.id]
                 dbg("number of active sockets now equals #{misc.len(@_hub_client_sockets)}")
                 if misc.len(@_hub_client_sockets) == 0
@@ -348,7 +346,6 @@
                     opts.cb?(if resp.error then resp.error else 'error')
                 else
                     opts.cb?(undefined, resp)
-                delete opts.cb
             @_hub_client_sockets[socket.id].callbacks[opts.message.id] = cb
         # Finally, send the message
         socket.write_mesg('json', opts.message)


webapp/console: stupid mixup of filenames >_<

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -662,7 +662,7 @@

         @element.find("a[href=#initfile]").click () =>
             initfn = misc.console_init_filename(@opts.filename)
-            content = initfile_content(initfn)
+            content = initfile_content(@opts.filename)
             {salvus_client} = require('./salvus_client')
             salvus_client.exec
                     project_id  : @opts.editor?.editor.project_id


Fix variable naming

Fix variable naming

--- src/parser.coffee
+++ src/parser.coffee
@@ -77,12 +77,12 @@

             complete = (errorAlreadyRaised = false) =>
                 return unless response
-                hasAdWithCreative = false
+                noCreatives = true
                 for ad in response.ads
                     return if ad.nextWrapperURL?
                     if ad.creatives.length > 0
-                        hasAdWithCreative = true
-                if not hasAdWithCreative
+                        noCreatives = false
+                if noCreatives
                     # No Ad Response
                     # The VAST <Error> element is optional but if included, the video player must send a request to the URI
                     # provided when the VAST response returns an empty InLine response after a chain of one or more wrapper ads.


ovveride wrapper sequence only if the top sequence is greater than 0

--- src/parser.coffee
+++ src/parser.coffee
@@ -106,7 +106,7 @@
                                             creative.trackingEvents[eventName] or= []
                                             creative.trackingEvents[eventName] = creative.trackingEvents[eventName].concat ad.trackingEvents[eventName]

-                                if ad.sequence?
+                                if ad.sequence? and ad.sequence > 0
                                     wrappedAd.sequence = ad.sequence

                                 response.ads.splice index, 0, wrappedAd


Fix usage example for 'check' method to be compatible with params

--- src/has_event.coffee
+++ src/has_event.coffee
@@ -40,7 +40,7 @@
   element optionally delegated to the supplied selector
   and is handled bu the supplied handler

-  usage: .check(event, type [, selector], handler)
+  usage: .check($el, type [, selector], handler)

   @param $el [Object] The jQuery element on which
   the check will be performed


promisified initResourceProvider

promisified initResourceProvider

--- src/steroids/Providers.coffee
+++ src/steroids/Providers.coffee
@@ -108,53 +108,46 @@
   removeProvider: (provider_name) =>
     @getProviderByName(provider_name).then (provider) =>

-      console.log "removing provider #{provider_name}"
+      console.log "Removing provider #{provider_name}..."
       @config_api.del("/app/#{@getAppId()}/service_providers/#{provider}.json", data, (err, req, res, obj) =>
         console.log 'done'
         @config_api.close()
       )

   initResourceProvider: (provider_name) =>
+    deferred = q.defer()
+
+    console.log "Provisioning a sandbox database for your app..."
+
     unless provider_name?
-      console.log "resource provider not specified"
-      process.exit 1
+      deferred.reject "Resource provider not specified."

     @getProviderByName(provider_name).then (provider) =>

-      unless provider?
-        Help.error()
+      if resourceProviderInitialized(provider_name)
         console.log(
           """
-          Provider with name #{chalk.bold(provider_name)} not found.
-
-          You can list available providers with
-
-            #{chalk.bold("$ steroids providers")}
-
-          You can then add the provider for your app with the command
-
-            #{chalk.bold("$ steroids providers:add providerName")}
-
+          Sandbox database already provisioned and configured at
+
+            #{chalk.bold("config/dolandb.yaml")}
+
+          All good!
           """
         )
-        process.exit 1
-
-      if resourceProviderInitialized(provider_name)
-        Help.error()
-        console.log "Resource provider '#{provider_name}' already initialized."
-        process.exit 1
-
-      console.log "provisioning database from #{provider_name}"
-
-      dolandb = new DolanDB
-      dolandb.createBucketWithCredentials().then(
-        (bucket) =>
-          console.log "done"
-          dolandb.createDolandbConfig("#{bucket.login}#{bucket.password}", bucket.name, bucket.datastore_bucket_id)
-      ).then(
-        (data) =>
+        deferred.resolve()
+      else
+
+        dolandb = new DolanDB
+        dolandb.createBucketWithCredentials().then(
+          (bucket) =>
+            console.log "Database provisioned, creating a local config file..."
+            dolandb.createDolandbConfig("#{bucket.login}#{bucket.password}", bucket.name, bucket.datastore_bucket_id)
+        ).then (data) =>
+          console.log "Local config file created at #{chalk.bold("config/dolandb.yaml")}"
           @updateProviderInfo(provider)
-      )
+          deferred.resolve()
+
+    deferred.promise

   removeResource: (resource_to_be_removed) =>
     #should loop through all providers


show direction on map polylines

show direction on map polylines

--- web/js/views/map_route_leg_view.coffee
+++ web/js/views/map_route_leg_view.coffee
@@ -1,20 +1,31 @@
 define ['underscore', 'utils'], (_, Utils) ->
-
-  legStyles =
-    walk:
-      strokeOpacity: 0
-      icons: [{
-        icon:
-          path: 'M 0,-0.2 0,0.2'
-          strokeOpacity: 1
-          strokeColor: Utils.transportColors['walk']
-        offset: '0',
-        repeat: '7px'
-      }]

   class MapRouteLegView

     constructor: (@leg, @map) ->
+      @legStyles =
+        walk:
+          strokeOpacity: 0
+          icons: [{
+            icon:
+              path: 'M 0,-0.2 0,0.2'
+              strokeOpacity: 1
+            repeat: '7px'
+          }, {
+            icon:
+              path: google.maps.SymbolPath.FORWARD_OPEN_ARROW
+              scale: 2.5
+              strokeOpacity: 1
+            repeat: '100px'
+          }]
+        default:
+          icons: [{
+            icon:
+              path: google.maps.SymbolPath.FORWARD_OPEN_ARROW
+              scale: 2.5
+              strokeOpacity: 1
+            repeat: '100px'
+          }]

     remove: ->
       @line.setMap(null) if @line
@@ -26,7 +37,8 @@
           path: (new google.maps.LatLng point.y, point.x for point in @leg.get('shape'))
           strokeWeight: 4
           strokeColor: Utils.transportColors[@leg.get('type')]
-        }, legStyles[@leg.get('type')])
+          strokeOpacity: 1.0
+        }, @legStyles[@leg.get('type')] or @legStyles['default'])
       google.maps.event.addListener @line, 'click', @onLineClicked
       this


We don't destroy facts anymore, so remove core/jslib wiring

--- app/js/jail_iframe/classes/highlight.coffee
+++ app/js/jail_iframe/classes/highlight.coffee
@@ -12,10 +12,6 @@
   highlight:   -> $(@elements).addClass('fl-core-highlight')
   dehighlight: -> $(@elements).removeClass('fl-core-highlight')

-  destroy: ->
-    $(@elements).contents().unwrap()
-    @show_button.destroy()
-
 FactlinkJailRoot.showCoreHighlight = (factId) ->
   for highlight in highlightsByFactIds[highlightedFactId] || []
     highlight.dehighlight()
@@ -24,10 +20,3 @@

   for highlight in highlightsByFactIds[highlightedFactId] || []
     highlight.highlight()
-
-
-FactlinkJailRoot.destroyCoreHighlight = (factId) ->
-  for fact in highlightsByFactIds[factId] || []
-    fact.destroy()
-  delete highlightsByFactIds[factId]
-


al-html, comment binding

al-html, comment binding

--- src/html/base.coffee
+++ src/html/base.coffee
@@ -8,6 +8,7 @@
 ###

 alight.d.al.html =
+    restrict: 'AM'
     priority: 100
     stopBinding: true
     modifier: {}
@@ -43,10 +44,14 @@
                             env: env
                 return
             prepare: ->
-                self.baseElement = element
-                self.topElement = document.createComment " #{env.attrName}: #{inputName} "
-                f$.before element, self.topElement
-                f$.remove element
+                if element.nodeType is 8
+                    self.baseElement = document.createElement 'div'
+                    self.topElement = element
+                else
+                    self.baseElement = element
+                    self.topElement = document.createComment " #{env.attrName}: #{inputName} "
+                    f$.before element, self.topElement
+                    f$.remove element
                 return
             removeBlock: ->
                 if self.childCD


Only invoke callback once

Only invoke callback once

Previously on errors the callback would be called twice since
close also fires even when error fires.

--- src/rcedit.coffee
+++ src/rcedit.coffee
@@ -27,11 +27,13 @@
   child = spawn rcedit, args

   stderr = ''
-  child.on 'error', (err) ->
-    callback err
+  error = null
+  child.on 'error', (err) -> error ?= err
   child.stderr.on 'data', (data) -> stderr += data
   child.on 'close', (code) ->
-    if code is 0
+    if error?
+      callback error
+    else if code is 0
       callback null
     else
       callback stderr


timeZone fix in predefined ranges

timeZone fix in predefined ranges

--- src/scripts/daterangepicker/config.coffee
+++ src/scripts/daterangepicker/config.coffee
@@ -64,7 +64,7 @@
       else if startDate == 'custom'
         new CustomDateRange(title)
       else
-        new DateRange(title, startDate, endDate)
+        new DateRange(title, startDate.tz(@timeZone()), endDate.tz(@timeZone()))

   _edgeMode: (val) ->
     val = 'inclusive' unless val in ['exclusive', 'inclisuve', 'extended']


Removed unused arguments.

Removed unused arguments.

--- client/create-form.coffee
+++ client/create-form.coffee
@@ -8,18 +8,17 @@
 create_measure_label = (field) ->
   (($ "").text m) if m = (extend_field_type field).measure

-create_form_record = (xml, field, value) ->
+create_form_record = (field, value) ->
   ($ "
").append (field_label field),
                                    (create_input field, value),
                                    (create_measure_label field)

-create_form_struct = (xml, {desc, data}) ->
+create_form_struct = ({desc, data}) ->
   div = $ "
"
   div.append (($ "
").text desc),
              create_form xml, data

-ensure_elements = (x) ->
-  if x.length then x
+ensure_elements = (x) -> if x.length then x

 get_field_input = (parent, x) ->
   (ensure_elements parent.children (x.name or x.type)) or if parent.attr name then parent
@@ -45,7 +44,7 @@
     parent.attr name

 create_form_attribute = (parent, field) ->
-  create_hooked_field parent, field, create_form_attribute_value parent, field
+  create_hooked_field field, create_form_attribute_value parent, field

 create_form_element = (parent, field) ->
   v = field.value


Indentation correction.

Indentation correction.

--- client/publication.coffee
+++ client/publication.coffee
@@ -512,9 +512,9 @@
     return unless Meteor.personId()

     Meteor.call 'add-to-library', @_id, (error, count) =>
-        return Notify.meteorError error, true if error
-
-        Notify.success "Publication added to the library." if count
+      return Notify.meteorError error, true if error
+
+      Notify.success "Publication added to the library." if count

     return # Make sure CoffeeScript does not return anything


things

things

--- src/instagram.coffee
+++ src/instagram.coffee
@@ -21,23 +21,6 @@
 Instagram = require('instagram-node-lib')

 module.exports = (robot) ->
-  robot.respond /(insta tag)( me )?(.*)/i, (msg) ->
-    count = 1
-    authenticateUser(msg)
-    if msg.match[3]
-      text = msg.match[3].trim().split(" ")
-      tag =  text[0]
-      count = parseInt(text[1]) if text[1]
-    else
-      msg.send 'Please provied tag'
-      return
-    Instagram.tags.recent
-      name: tag
-      count: count
-      complete: (data) ->
-       for item in data
-          msg.send item['images']['standard_resolution']['url']
-
   robot.respond /prenium/i, (msg) ->
     authenticateUser(msg)
     Instagram.tags.search


router: support query string

router: support query string

--- src/router.coffee
+++ src/router.coffee
@@ -49,7 +49,7 @@

         if isLocal
           e.preventDefault()
-          router.go $el.pathname + $el.hash
+          router.go $el.pathname + $el.search + $el.hash
       # coffeelint: enable=missing_fat_arrows

     return node


Add some missing block elements

Add some missing block elements

--- src/utils/createStyles.coffee
+++ src/utils/createStyles.coffee
@@ -134,6 +134,10 @@
   pre,
   table,
   fieldset,
+  blockquote,
+  form,
+  noscript,
+  iframe,
   img,
   hr {
     margin: 0;


Forgot some quotes in IteratorView debugging comment.

--- src/view/iterator_view.coffee
+++ src/view/iterator_view.coffee
@@ -1,6 +1,6 @@
 class Batman.IteratorView extends Batman.View
   loadView: ->
-    document.createComment("batman-iterator-#{@iteratorName}=#{@iteratorPath}")
+    document.createComment("batman-iterator-#{@iteratorName}=\"#{@iteratorPath}\"")

   beginAppendItems: ->
     @fragment = document.createDocumentFragment()


Fixed job status ajax

Fixed job status ajax

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

     get_job_status = ->
       $.getJSON "/projects/#{project_id}/jobs/#{job_id}", (data) ->
-        if data.results == ""
+        if data.results == null
           setTimeout get_job_status, 3000
         else
           div.text data.results


Cleans js

Cleans js

--- app/assets/javascripts/ekylibre.js.coffee
+++ app/assets/javascripts/ekylibre.js.coffee
@@ -40,18 +40,18 @@
   E.toggleValidateButton = (container) ->
     requiredFields = container.find('input[data-required]')
     validateItemButton = container.find('button[data-validate]')
+
     toggleState = ->
-      requiredFields.not(':hidden').each ->
-        if $(this).val() == ''
-          validateItemButton.attr("disabled", true)
-          return false
-        else
-          validateItemButton.attr("disabled", null)
+      toDisable = requiredFields.not(':hidden').filter () ->
+        $(this).val() == ''
+      .length
+
+      validateItemButton.attr("disabled", !!toDisable)
+
     toggleState()
     requiredFields.each ->
-      element = $(this)
-      element.on "selector:change", toggleState
-      element.change toggleState
+      $(this).on "selector:change change", toggleState
+
     container.bind 'visibility:change', toggleState

   E.setStorageUnitName = (container) ->


Support v keyboard shortcut.

Support v keyboard shortcut.

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

     # Open original article
     Mousetrap.bind 'v', (event, combo) =>
-      content = $('.entry-header').find('a')[0].click()
+      content = $('#source_link')[0].click()
       event.preventDefault()

     # Open original article


Fix: Adjust in sidebar map fixing on scroll page

--- app/assets/javascripts/newsfeed.js.coffee
+++ app/assets/javascripts/newsfeed.js.coffee
@@ -89,7 +89,7 @@
         map = $("#map").gmap3("get")

         mostraNeighborhoodCount = ->
-          $("#neighborhood-count").show()
+          $(".SidebarMap-neighborhoodCount").show()
           return

         # delay para ajustar o centro do mapa
@@ -98,7 +98,7 @@
           return

         escondeInfos = ->
-          $("#sidebar-map .infos").hide()
+          $(".SidebarMap-infos").hide()
           return

         timeouts.push(setTimeout(pan, 6500))


Refactor editsong react component for Bootstrap.

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -19,33 +19,41 @@

       render: ->
         div className: 'new-song',
-          form onSubmit: @onFormSubmit, ref: 'form',
-            input
-              autoFocus: true
-              onChange: @onFieldChange
-              name: 'name'
-              placeholder: EditSong.MSG_SONG_NAME
-              type: 'text'
-              value: song.name
-            input
-              onChange: @onFieldChange
-              name: 'artist'
-              placeholder: EditSong.MSG_SONG_ARTIST
-              type: 'text'
-              value: song.artist
-            textarea
-              onChange: @onFieldChange
-              name: 'lyrics'
-              placeholder: EditSong.MSG_WRITE_LYRICS_HERE
-              ref: 'lyrics'
-              value: song.lyrics
-            a
-              href: 'http://linkesoft.com/songbook/chordproformat.html'
-              target: '_blank'
-            , EditSong.MSG_HOW_TO_WRITE_LYRICS
-            button
-              type: 'submit'
-            , EditSong.MSG_CREATE_NEW_SONG
+          form onSubmit: @onFormSubmit, ref: 'form', role: 'form',
+            div className: 'form-group',
+              input
+                autoFocus: true
+                className: 'form-control'
+                name: 'name'
+                onChange: @onFieldChange
+                placeholder: EditSong.MSG_SONG_NAME
+                type: 'text'
+                value: song.name
+            div className: 'form-group',
+              input
+                className: 'form-control'
+                onChange: @onFieldChange
+                name: 'artist'
+                placeholder: EditSong.MSG_SONG_ARTIST
+                type: 'text'
+                value: song.artist
+            div className: 'form-group',
+              textarea
+                className: 'form-control'
+                onChange: @onFieldChange
+                name: 'lyrics'
+                placeholder: EditSong.MSG_WRITE_LYRICS_HERE
+                ref: 'lyrics'
+                value: song.lyrics
+              a
+                href: 'http://linkesoft.com/songbook/chordproformat.html'
+                target: '_blank'
+              , EditSong.MSG_HOW_TO_WRITE_LYRICS
+            div className: 'form-group',
+              button
+                className: 'btn btn-default'
+                type: 'submit'
+              , EditSong.MSG_CREATE_NEW_SONG

       componentDidMount: ->
         @chordproTextarea_ = new goog.ui.Textarea ''


testing practice widget initial test of loading expected practice into component

--- test/components/practice.spec.coffee
+++ test/components/practice.spec.coffee
@@ -13,32 +13,27 @@
 VALID_MODEL = require '../../api/courses/1/practice.json'

-helper = (model, courseId) ->
+helper = (model, courseId, tests) ->

   # Load practice in CourseStore
   CourseActions.loaded(model, courseId)
-
-  testPracticeLocation = new RouterTestLocation(['dashboard/'])
+  testPracticeLocation = new RouterTestLocation(['/courses/' + courseId + '/practice/'])

   div = document.createElement('div')
   Router.run routes, testPracticeLocation, (Handler)->
     React.render(<Handler/>, div, ()->
-      console.log div.querySelector('h1')
+      tests?(div)
     )
-
-  # html = React.renderToString(<SinglePractice courseId={courseId} />)
-  # div = document.createElement('div')
-  # div.innerHTML = html
-
-  # div

 describe 'Practice Component', ->
   beforeEach ->
     CourseActions.reset()

-  it 'should load expected practice', ->
-    node = helper(VALID_MODEL, 1)
+  it 'should load expected practice', (done) ->

-    # console.log(node.querySelector('h1'))
-    # expect(node.querySelector('h1')).to.not.be.null
-    # expect(node.querySelector('h1').innerText).to.equal(VALID_MODEL.title)+    tests = (node) ->
+      expect(node.querySelector('h1')).to.not.be.null
+      expect(node.querySelector('h1').innerText).to.equal(VALID_MODEL.title)
+      done()
+
+    helper(VALID_MODEL, 1, tests)


print action after setting action

print action after setting action

--- src/server.coffee
+++ src/server.coffee
@@ -8,9 +8,9 @@
   app.use express.urlencoded()

   app.all '/', (req, res, next) ->
-    console.log '/', action
     action = req.body?.action ? req.query?.action
     gameState = req.body?.game_state ? req.query?.game_state
+    console.log '/', action, gameState
     if typeof gameState is 'string'
       try
         gameState = JSON.parse gameState


Ignore keydown when suggesting on idobata.io

--- src/js/esarea.coffee
+++ src/js/esarea.coffee
@@ -4,6 +4,8 @@
 $(document).on 'keyup', 'textarea', (e) ->
   if location.host.match /github\.com/
     suggesting = !!$('ul.suggestions:visible').length
+  else if location.host.match /idobata\.io/
+    suggesting = !!$('.atwho-view:visible').length
   return

 $(document).on 'keydown', 'textarea', (e) ->


fix typo that broke time keyframe progression

--- src/keyframes.coffee
+++ src/keyframes.coffee
@@ -232,7 +232,10 @@
             cands = kfs.filter (kf) -> kf.direction * dir >= 0 and (alt - kf.altitude)*dir >= 0

             if cands.length > 0
-                return if dir is 1 then H.last(cands) else cands[0]
+              if dir is 1
+                return H.last cands
+              else
+                return cands[0]

             # keyframes of opposite direction, thus before last direction change
             cands = kfs.filter (kf) -> kf.direction*dir <= 0
@@ -247,7 +250,7 @@
             if cands.length > 0
                 return H.last cands

-            return last kfs
+            return H.last kfs

     _get_next_kf: (kfs, keymode, alt, dir) ->
         if keymode is 'altitude'


Prep NoFlo for decaffeination

Prep NoFlo for decaffeination

--- src/lib/NoFlo.coffee
+++ src/lib/NoFlo.coffee
@@ -123,6 +123,7 @@
     deprecated 'Calling noflo.createNetwork without a callback is deprecated'
     callback = (err) ->
       throw err if err
+      return

   # Choose legacy or modern network based on whether graph
   # subscription is needed
@@ -132,12 +133,17 @@
   networkReady = (network) ->
     # Send IIPs
     network.start (err) ->
-      return callback err if err
+      if err
+        callback err
+        return
       callback null, network
+      return

   # Ensure components are loaded before continuing
   network.loader.listComponents (err) ->
-    return callback err if err
+    if err
+      callback err
+      return

     # In case of delayed execution we don't wire it up
     if options.delay
@@ -145,14 +151,19 @@
       return

     # Empty network, no need to connect it up
-    return networkReady network if graph.nodes.length is 0
+    if graph.nodes.length is 0
+      networkReady network
+      return

     # Wire the network up and start execution
     network.connect (err) ->
-      return callback err if err
+      if err
+        callback err
+        return
       networkReady network
-
-  network
+      return
+    return
+  return network

 # ### Starting a network from a file
 #
@@ -179,15 +190,20 @@
     options.subscribeGraph = false

   exports.graph.loadFile file, (err, net) ->
-    return callback err if err
+    if err
+      callback err
+      return
     net.baseDir = options.baseDir if options.baseDir
     exports.createNetwork net, options, callback
+    return
+  return

 # ### Saving a network definition
 #
 # NoFlo graph files can be saved back into the filesystem with this method.
 exports.saveFile = (graph, file, callback) ->
   graph.save file, callback
+  return

 # ## Embedding NoFlo in existing JavaScript code
 #


Clean up help content

Clean up help content

--- src/lib/about.coffee
+++ src/lib/about.coffee
@@ -13,6 +13,18 @@
   General Usage:

     knodeo <command> <parameters>
+
+  General Commands
+  ----------------
+
+  See this help:
+
+  knodeo help
+
+  Create the base working folder tree in the current directory:
+
+  knodeo init
+

   Database Schema Commands
   ------------------------


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -106,7 +106,7 @@
   @returns {Sprite} A sprite object
   ###
   Sprite.load = (url, loadedCallback) ->
-    spriteCache[url] if
+    spriteCache[url] if spriteCache[url]
     img = new Image()
     proxy = LoaderProxy()


events get loaded in constructor.

events get loaded in constructor.

--- plugins/project/project.coffee
+++ plugins/project/project.coffee
@@ -10,9 +10,11 @@
     'Command-Ctrl-N': -> @pane.toggle()

   storageNamespace: ->
-    @.constructor.name + @dir
+    @.constructor.name + @window.path

-  load: ->
+  constructor: (args...) ->
+    super args...
+
     @pane = new ProjectPane @window, @
     @pane.toggle()

@@ -20,7 +22,7 @@
     editor = @window.document
     editor.ace.on 'open', ({filename}) =>
       if File.isDirectory filename
-        @pane.reload filename
+        @pane.reload filename # I don't think this can ever happen.
       else
         openedPaths = @get 'openedPaths', []
         if not _.include openedPaths, filename
@@ -33,6 +35,7 @@
         openedPaths = _.without openedPaths, filename
         @set 'openedPaths', openedPaths

+  load: ->
     # Reopen files (remove ones that no longer exist)
     openedPaths = @get 'openedPaths', []
     for path in openedPaths


fix typo

fix typo

--- resources/assets/coffee/_classes/beatmapset-filter.coffee
+++ resources/assets/coffee/_classes/beatmapset-filter.coffee
@@ -132,7 +132,7 @@
     charParams

-  # For UI purposes; server-side has it's own check.
+  # For UI purposes; server-side has its own check.
   @supporterRequired: (filters) ->
     _.reject ['played', 'rank'], (name) =>
       _.isEmpty(filters[name]) || filters[name] == @getDefault(filters, name)


Disable button if title is empty

Disable button if title is empty

--- resources/assets/coffee/_classes/forum-topic-title.coffee
+++ resources/assets/coffee/_classes/forum-topic-title.coffee
@@ -30,9 +30,9 @@
     addEventListener 'turbolinks:before-cache', @abort
     $(document).on 'click', '.js-forum-topic-title--edit-start', @editShow
     $(document).on 'click', '.js-forum-topic-title--save', @save
-    $(document).on 'keyup', '.js-forum-topic-title--input', @onInput
+    $(document).on 'keyup', '.js-forum-topic-title--input', @onKeyup
     $(document).on 'click', '.js-forum-topic-title--cancel', @cancel
-    $(document).on 'input', '.js-forum-topic-title--input', @syncPadding
+    $(document).on 'input', '.js-forum-topic-title--input', @onInput

   abort: =>
@@ -65,7 +65,12 @@
     @main[0].classList.remove 'hidden'

-  onInput: (e) =>
+  onInput: =>
+    @saveButton[0].disabled = !osu.presence(@input[0].value)?
+    @syncPadding()
+
+
+  onKeyup: (e) =>
     switch e.keyCode
       # enter
       when 13 then @save()


Fix scoreboard switching when user is not supporter

--- resources/assets/coffee/react/beatmapset-page/main.coffee
+++ resources/assets/coffee/react/beatmapset-page/main.coffee
@@ -76,7 +76,9 @@
     @setState
       currentScoreboardType: scoreboardType

-    return if scoreboardType != 'global' && !currentUser.isSupporter
+    if scoreboardType != 'global' && !currentUser.isSupporter
+      @setState scores: []
+      return

     enabledMods = if resetMods
       []


use umobi.dom methods

use umobi.dom methods

--- js/umobi.page.coffee
+++ js/umobi.page.coffee
@@ -11,15 +11,15 @@
   umobi.page =

     # get all pages
-    all: -> $('[data-role="page"]')
+    all: -> $(umobi.dom.queryAll('[data-role="page"]'))

     # get active page
-    active: () -> $('.ui-page-active')
+    active: () -> $(umobi.dom.queryAll('.ui-page-active'))

     # $p: page element object.
     reveal: ($p) ->
       # hide current active page
-      this.active().removeClass('ui-page-active')
+      @active().removeClass('ui-page-active')
       $p.addClass('ui-page-active').trigger('pagereveal')
       $(document).trigger('pagereveal',[$p])

@@ -31,9 +31,10 @@
       umobi.page.reveal($page)

     create: (el) ->
-
       $page = $(el)
       $page.trigger('pagecreate').addClass('ui-page ui-body-c')
+
+      # TODO: rewrite this with umobi.dom
       $h = $page.find('[data-role="header"]').addClass('ui-header') # header container
       $f = $page.find('[data-role="footer"]').addClass('ui-footer')  # footer container
       $c = $page.find('[data-role="content"]').addClass('ui-content') # content container


separate data

separate data

--- js/wlanscript.coffee
+++ js/wlanscript.coffee
@@ -82,7 +82,7 @@

 window.onload = ->
-  $.get "./wifilog.csv", (data) ->
+  $.get "data/wifilog.csv", (data) ->
     netdata = new WiFiData(data)
     $.each netdata.wlandata, (key,value) ->
       $(".wlanselect").append("<option value=\""+key+ "\">"+key+"</option>")
@@ -93,7 +93,6 @@
       key = $('.wlanselect option:selected').val()
       map.setHeatMap(key)
       return
-
+
     key = $('.wlanselect option:first').val()
     map.setHeatMap(key)
-


more null user

more null user

--- resources/assets/coffee/react/beatmap-discussions/discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussion.coffee
@@ -286,8 +286,8 @@
       type: type
       read: @isRead(post)
       users: @props.users
-      user: @props.users[post.user_id]
-      lastEditor: @props.users[post.last_editor_id]
+      user: @props.users[post.user_id] ? @props.users[null]
+      lastEditor: @props.users[post.last_editor_id] ? @props.users[null]
       canBeEdited: @props.currentUser.is_admin || canBeEdited
       canBeDeleted: canBeDeleted
       canBeRestored: canModeratePosts


Adding branching and tagging lookups

Adding branching and tagging lookups

--- src/models/user_application.coffee
+++ src/models/user_application.coffee
@@ -34,28 +34,54 @@
    * @param {Object} (filter) The object keys you wish to return (null is all)
    * @param {Function} (fn) The callback function
   ###
-  all: (filter=null, fn) =>
-    # @current = null
-    # filter or= {}
-    # @list (err, applications) =>
-    #   if (filter.name and Object.keys(filter).length is 1) or err
-    #     return fn(err, applications)
+  all: (filter={}, fn) =>
+    if typeof filter == "function" then fn = filter
+    @current = null
+    @list (err, applications) =>
+      if (filter.name and Object.keys(filter).length is 1) or err
+        return fn(err, applications)

   ###*
    * Searches for the redis objects that match the query.
-   * @param {Object} (query) The query object to search
+   * @param {Object} (name) The name of the application to search for
    * @param {Function} (fn) The callback function
   ###
-  find: (query, fn) =>
-    # @current = null
-    # query or= {}
-    # @list (err, applications) =>
-    #   if err
-    #     return fn(err, applications)
-    #   if query.name in applications
-    #     console.log("GOT IT")
-    #     # Get the user and application hash lookup
+  find: (name, admin=false, fn) =>
+    if typeof admin == "function" then fn = admin
+    @current = null
+    @list (err, applications) =>
+      if err
+        return fn(err, applications)
+      if name in applications
+        # Get the branches for the application
+        # Get the tags for the application
     # fn(null, [])
+
+  ###*
+   * Returns the list of branches for a particular application.
+   * @param {String} (application) The name of the application to retrieve
+  ###
+  branches: (application) =>
+
+  ###*
+   * Returns the branch information and file hashes for the given app/branch.
+   * @param {String} (application) The name of the application to retrieve
+   * @param {String} (branch) The name of the branch to retrieve
+  ###
+  branch_info: (application, branch) =>
+
+  ###*
+   * Returns the list of tags for a particular application.
+   * @param {String} (application) The name of the application to retrieve
+  ###
+  tags: (application) =>
+
+  ###*
+   * Returns the tag information and file hashes for the given app/branch.
+   * @param {String} (application) The name of the application to retrieve
+   * @param {String} (tag) The name of the tag to retrieve
+  ###
+  tag_info: (application, tag) =>

   ###*
    * Searches for the redis object that matches the query.


chat client: fix new session initiation

chat client: fix new session initiation

--- public/assets/chat/chat.coffee
+++ public/assets/chat/chat.coffee
@@ -146,9 +146,7 @@
         @input.val unfinishedMessage

       @show()
-      @open
-        showLoader: false
-        animate: false
+      @open()

       if unfinishedMessage
         @input.focus()
@@ -232,16 +230,16 @@
       @el.find('.zammad-chat-body').append @view('message')(data)
       @scrollToBottom()

-    open: (options = { showLoader: true, animate: true }) =>
+    open: =>
       return if @isOpen

-      if options.showLoader
+      if !@sessionId
         @showLoader()

       @el
         .addClass('zammad-chat-is-open')

-      if options.animate
+      if !@sessionId
         @el.animate { bottom: 0 }, 500, @onOpenAnimationEnd
       else
         @el.css 'bottom', 0
@@ -250,11 +248,8 @@
       @isOpen = true

     onOpenAnimationEnd: =>
-      #setTimeout @onQueue, 1180
-      # setTimeout @onConnectionEstablished, 1180
-      # setTimeout @onAgentTypingStart, 2000
-      # setTimeout @receiveMessage, 5000, "Hello! How can I help you?"
-      @session_init()
+      if !@sessionId
+        @session_init()

     close: (event) =>
       event.stopPropagation() if event