Update downloadMaterials.coffee

Update downloadMaterials.coffee

Изменил код

--- server/cron/downloadMaterials.coffee
+++ server/cron/downloadMaterials.coffee
@@ -682,11 +682,11 @@
         for a in links
             href = a.href
             key = @getUrlKey(href)
-            if href.startsWith.("https://algoprog.ru")
-              newhref = href.slice(19)
+            if href.startsWith("https://algoprog.ru")
+              newhref = href.slice("https://algoprog.ru".length)
+            else if href.startsWith("http://algoprog.ru")
+              newhref = href.slice("http://algoprog.ru".length)
             else
-              newhref = href.slice(18)
-            if  href.startsWith.("https://algoprog.ru") == false and href.startsWith.("http://algoprog.ru") == false
               if not key
                  continue
               if not (key of @urlToMaterial)
@@ -698,7 +698,7 @@
             a.href = newhref
             a.setAttribute("onclick", "window.goto('#{newhref}')();return false;")
         body = document.getElementsByTagName("body")[0]
-        material.content = body.innerHTML
+        material.content = body.innerHTML
     correctInternalLinks: ->
         promises = []
         for id, material of @materials


Rework and rename to Player#_updateVolumeOnPlayerLand.

No more throttling or manual easing. Uses fading (potentially can be
overridden?) and only updates on player land. This mechanic also matches
the gameplay better.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -81,7 +81,7 @@
       @background.update()
       @player.update()

-      @_updateMusic() unless @ended
+      @_updateVolumeOnPlayerLand()

       @camera.updateShake()
       if @_shakeOnPlayerFall()
@@ -172,7 +172,7 @@

     _addMusic: ->
       @userVolume = 1
-      @music = @add.audio 'bgm', 0, yes
+      @music = @add.audio 'bgm', 0.2, yes
       @music.mute = @developing or @debugging
       @music.play()
       @gui?.add(@music, 'mute').listen()
@@ -243,16 +243,14 @@
         @camera.unfollow()
         @player.cursors = null

-    _updateMusic: _.throttle ->
-      # The music gets louder the higher the player gets. Uses easing so the
-      # volume smoothly updates on each landing.
-      targetVolume = ((@platforms.tilemap.heightInPixels - @player.sprite.y) /
-                       @platforms.tilemap.heightInPixels) ** 1.3
-      volume = @music.volume + ((targetVolume - @music.volume) / 24) # Ease into target, with fps
-      @userVolume = @math.clamp @userVolume, 0, 2
-      volume *= @userVolume # Factor in user controls.
-      @music.volume = @math.clamp volume, 0.2, 0.8
-    , 42, { leading: on } # ms/f at 24fps
+    _updateVolumeOnPlayerLand: ->
+      return no unless @player.nextState is 'landing'
+      # The music gets louder the higher the player gets.
+      volume = ((@platforms.tilemap.heightInPixels - @player.sprite.y) /
+                 @platforms.tilemap.heightInPixels) ** 1.3
+      volume *= @math.clamp @userVolume, 0, 2 # Factor in user controls.
+      volume = @math.clamp volume, 0.2, 0.8
+      @music.fadeTo Timer.SECOND, volume

   _.extend PlayState::, AnimationMixin, DebugMixin, DebugDisplayMixin, TextMixin


Remove extra line

Remove extra line

--- resources/assets/coffee/_classes/wiki.coffee
+++ resources/assets/coffee/_classes/wiki.coffee
@@ -122,7 +122,6 @@
   stickyToc: (_e, target) =>
     return if !@floatToc[0]?

-
     # not floating
     if target != 'wiki-toc'
       @floatToc[0].style.position = 'absolute'


move/hide menu portal on window resize

move/hide menu portal on window resize

--- resources/assets/lib/play-detail-menu.coffee
+++ resources/assets/lib/play-detail-menu.coffee
@@ -36,21 +36,33 @@

   componentDidMount: =>
+    $(window).on 'throttled-resize.#{@uuid}', @resize
     $(document).on "turbolinks:before-cache.#{@uuid}", () =>
       @removePortal()
+
+
+  resize: () =>
+    return if !@state.active
+
+    # disappear if the tree the menu is in is no longer displayed
+    if !@menu.current.offsetParent?
+      @portal.style.display = 'none'
+      return
+
+    $element = $(@menu.current)
+    { top, left } = $element.offset()
+
+    @portal.style.display = 'block'
+    @portal.style.position = 'absolute'
+    @portal.style.top = "#{Math.floor(top + $element.height() / 2)}px"
+    @portal.style.left = "#{Math.floor(left + $element.width())}px"

   componentDidUpdate: (_prevProps, prevState) =>
     return if prevState.active == @state.active

     if @state.active
-      $element = $(@menu.current)
-      { top, left } = $element.offset()
-
-      @portal.style.position = 'absolute'
-      @portal.style.top = "#{Math.floor(top + $element.height() / 2)}px"
-      @portal.style.left = "#{Math.floor(left + $element.width())}px"
-
+      @resize()
       @addPortal()

       $(document).on "click.#{@uuid} keydown.#{@uuid}", @hide


Set namespace

Set namespace

--- review_front/app/adapters/application.coffee
+++ review_front/app/adapters/application.coffee
@@ -3,6 +3,7 @@
 `import $ from 'jquery'`

 Adapter = DS.ActiveModelAdapter.extend
+  namespace: '/api/v2'
   headers: Ember.computed(->
     'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
   ).volatile()


Supply datepicker options in crm.flip_calendar

In datepicker.js.coffee we make sure that all datepicker inputs are invoked with the same options.  However, the datepicker on the task form is not enabled by default and is brought into existence separately by crm.flip_calendar.  This means it doesn't use the 'yy-mm-dd' format and so the wrong dates are set on the task (or completely incorrect dates blocking the creation / editting of the task entirely).

The solution is to use the same options for the datepicker call in crm.flip_calendar as we do in datepicker.js.coffee.

This fixes #328.

--- app/assets/javascripts/crm.js.coffee
+++ app/assets/javascripts/crm.js.coffee
@@ -164,7 +164,10 @@
       if value is "specific_time"
         $("#task_bucket").toggle() # Hide dropdown.
         $("#task_calendar").toggle() # Show editable date field.
-        $("#task_calendar").datepicker().focus() # Focus to invoke calendar popup.
+        $("#task_calendar").datepicker({
+          showOn: 'focus',
+          changeMonth: true,
+          dateFormat: 'yy-mm-dd'}).focus() # Focus to invoke calendar popup.

     #----------------------------------------------------------------------------


Smallfix: Catch some exceptions

Smallfix: Catch some exceptions

--- app/coffee/directives/history.coffee
+++ app/coffee/directives/history.coffee
@@ -1,3 +1,11 @@
+_.mixin({
+    findByValues: (collection, property, values) ->
+        return _.filter(collection, (item) ->
+            return _.contains(values, item[property])
+        )
+})
+
+
 GmHistoryDirective = ($compile, $rootScope, $i18next) ->
     restrict: "A"
     require: "?ngModel"
@@ -7,7 +15,10 @@
                 return switch name
                     when "status"
                         if value
-                            return scope.constants.usStatuses[value].name
+                            try
+                                return scope.constants.usStatuses[value].name
+                            catch
+                                return null
                         return null
                     when "tags"
                         if value
@@ -21,26 +32,40 @@
                         else null
                     when "watchers"
                         if value
-                            return _.map(value, (v) -> scope.constants.users[v].full_name).join(", ")
+                            watchers_ids = _.map(value, (v) -> parseInt(v, 10))
+                            watchers = _.findByValues(scope.constants.users, "id", watchers_ids)
+                            return _.map(watchers, "full_name").join(", ")
                         return null
                     else value
             issue: (name, value) ->
                 return switch name
                     when "type"
                         if value
-                            return scope.constants.issueTypes[value].name
+                            try
+                                return scope.constants.issueTypes[value].name
+                            catch
+                                return null
                         return null
                     when "priority"
                         if value
-                            return scope.constants.priorities[value].name
+                            try
+                                return scope.constants.priorities[value].name
+                            catch
+                                return null
                         return null
                     when "status"
                         if value
-                            return scope.constants.issueStatuses[value].name
+                            try
+                                return scope.constants.issueStatuses[value].name
+                            catch
+                                return null
                         return null
                     when "severity"
                         if value
-                            return scope.constants.severities[value].name
+                            try
+                                return scope.constants.severities[value].name
+                            catch
+                                return null
                         return null
                     when "tags"
                         if value
@@ -48,7 +73,10 @@
                         else null
                     when "assigned_to"
                         if value
-                            return scope.constants.users[value].full_name
+                            try
+                                return scope.constants.users[value].full_name
+                            catch
+                                return null
                         return $i18next.t("common.unassigned")
                     when "is_blocked"
                         if value is true
@@ -58,7 +86,9 @@
                         else null
                     when "watchers"
                         if value
-                            return _.map(value, (v) -> scope.constants.users[v].full_name).join(", ")
+                            watchers_ids = _.map(value, (v) -> parseInt(v, 10))
+                            watchers = _.findByValues(scope.constants.users, "id", watchers_ids)
+                            return _.map(watchers, "full_name").join(", ")
                         return null
                     else value
             task: (name, value) ->
@@ -69,11 +99,17 @@
                         else null
                     when "status"
                         if value
-                            return scope.constants.taskStatuses[value].name
+                            try
+                                return scope.constants.taskStatuses[value].name
+                            catch
+                                return null
                         return null
                     when "assigned_to"
                         if value
-                            return scope.constants.users[value].full_name
+                            try
+                                return scope.constants.users[value].full_name
+                            catch
+                                return null
                         return $i18next.t("common.unassigned")
                     when "is_iocaine", "is_blocked"
                         if value is true
@@ -83,7 +119,9 @@
                         else null
                     when "watchers"
                         if value
-                            return _.map(value, (v) -> scope.constants.users[v].full_name).join(", ")
+                            watchers_ids = _.map(value, (v) -> parseInt(v, 10))
+                            watchers = _.findByValues(scope.constants.users, "id", watchers_ids)
+                            return _.map(watchers, "full_name").join(", ")
                         return null
                     else value
         }


Export File and Directory through require('atom')

Conflicts:
	exports/atom.coffee

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -1,5 +1,6 @@
 TextBuffer = require 'text-buffer'
 {Point, Range} = TextBuffer
+{File, Directory} = require 'pathwatcher'
 {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
 {deprecate} = require 'grim'

@@ -11,6 +12,8 @@
   TextBuffer: TextBuffer
   Point: Point
   Range: Range
+  File: File
+  Directory: Directory
   Emitter: Emitter
   Disposable: Disposable
   CompositeDisposable: CompositeDisposable


remove unused variable

remove unused variable

--- app/js/jail_iframe/util/modal.coffee
+++ app/js/jail_iframe/util/modal.coffee
@@ -26,7 +26,7 @@

   closeModal_highlightNewFactlink: (fact, id) ->
-    fct = FactlinkJailRoot.selectRanges(FactlinkJailRoot.search(fact), id)
+    FactlinkJailRoot.selectRanges(FactlinkJailRoot.search(fact), id)
     FactlinkJailRoot.trigger 'factlinkAdded'
     @closeModal_noAction()
     FactlinkJailRoot.showFactlinkCreatedNotification()


bugfix prevent display if there is no limit in items data

--- modules/gui/lib/widgets/ListBrowser.coffee
+++ modules/gui/lib/widgets/ListBrowser.coffee
@@ -92,12 +92,12 @@
         ##
         _display: (items, data, done)->
             @__root.html ''
-
-            if (data?.size || 0) / (data?.limit || 5) <= 1
+
+            if !data.limit || (data?.size || 0) / data.limit <= 1
                 done?()

             else
-                curr = parseInt data.offset / data.limit
+                curr = (data.offset||0) / data.limit

                 last = (data.size - (rest = data.size % data.limit)) / data.limit
                 if rest is 0
@@ -113,7 +113,7 @@
                     each: (i, next)=>

                         link = @_createLink i, 'page', ''+(i + 1), 'page'
-
+
                         if i is curr
                             link.addClass 'actual'


Fix date_to_string regarding daylight savings -- Related to 2047

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3228,27 +3228,55 @@
                     position_scrollbar()

+        # JavaScript doesn't let us set a timezone
+        # So we create a date shifted of the timezone difference
+        # Then replace the timezone of the JS date with the one from the ReQL object
         date_to_string: (date) =>
-            if date.timezone?
-                timezone = date.timezone
-
-                # Extract data from the timezone
-                timezone_array = date.timezone.split(':')
-                sign = timezone_array[0][0] # Keep the sign
-                timezone_array[0] = timezone_array[0].slice(1) # Remove the sign
-
-                # Save the timezone in minutes
-                timezone_int = (parseInt(timezone_array[0])*60+parseInt(timezone_array[1]))*60
-                if sign is '-'
-                    timezone_int = -1*timezone_int
-                # Add the user local timezone
-                timezone_int += (new Date()).getTimezoneOffset()*60
+            timezone = date.timezone
+
+            # Extract data from the timezone
+            timezone_array = date.timezone.split(':')
+            sign = timezone_array[0][0] # Keep the sign
+            timezone_array[0] = timezone_array[0].slice(1) # Remove the sign
+
+            # Save the timezone in minutes
+            timezone_int = (parseInt(timezone_array[0])*60+parseInt(timezone_array[1]))*60
+            if sign is '-'
+                timezone_int = -1*timezone_int
+
+            # d = real date with user's timezone
+            d = new Date(date.epoch_time*1000)
+
+            # Add the user local timezone
+            timezone_int += d.getTimezoneOffset()*60
+
+            # d_shifted = date shifted with the difference between the two timezones
+            # (user's one and the one in the ReQL object)
+            d_shifted = new Date((date.epoch_time+timezone_int)*1000)
+
+            # If the timezone between the two dates is not the same,
+            # it means that we changed time between (e.g because of daylight savings)
+            if d.getTimezoneOffset() isnt d_shifted.getTimezoneOffset()
+                # d_shifted_bis = date shifted with the timezone of d_shifted and not d
+                d_shifted_bis = new Date((date.epoch_time+timezone_int-(d.getTimezoneOffset()-d_shifted.getTimezoneOffset())*60)*1000)
+
+                if d_shifted.getTimezoneOffset() isnt d_shifted_bis.getTimezoneOffset()
+                    # We moved the clock forward -- and therefore cannot generate the appropriate time with JS
+                    # Let's create the date outselves...
+                    str_pieces = d_shifted_bis.toString().match(/([^ ]* )([^ ]* )([^ ]* )([^ ]* )(\d{2})(.*)/)
+                    hours = parseInt(str_pieces[5])
+                    hours++
+                    console.log hours
+                    if hours.toString().length is 1
+                        hours = "0"+hours.toString()
+                    else
+                        hours = hours.toString()
+                    #Note str_pieces[0] is the whole string
+                    raw_date_str = str_pieces[1]+" "+str_pieces[2]+" "+str_pieces[3]+" "+str_pieces[4]+" "+hours+str_pieces[6]
+                else
+                    raw_date_str = d_shifted_bis.toString()
             else
-                timezone = '+00:00'
-                timezone_int = (new Date()).getTimezoneOffset()*60
-
-            # Tweak epoch and create a date
-            raw_date_str = (new Date((date.epoch_time+timezone_int)*1000)).toString()
+                raw_date_str = d_shifted.toString()

             # Remove the timezone and replace it with the good one
             return raw_date_str.slice(0, raw_date_str.indexOf('GMT')+3)+timezone


Fix flickering active user indicator badge


Fix destroy() and c/c issue?

Fix destroy() and c/c issue?

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1562,18 +1562,20 @@
                 $(content_container).scroll ->
                     $(".wrapper_scrollbar").scrollLeft($(content_container).scrollLeft())

-                    # Sometimes we don't have to display the scrollbar (when the results are not shown because the query is too big)
-                    if $(window).scrollTop()+$(window).height() < $(content_container).offset().top+20 # bottom of the window < beginning of $('.json_table_container') // 20 pixels is the approximate height of the scrollbar (so we don't show JUST the scrollbar)
-                        that.$('.wrapper_scrollbar').hide()
-                    # We show the scrollbar and stick it to the bottom of the window because there is more content below
-                    else if $(window).scrollTop()+$(window).height() < $(content_container).offset().top+$(content_container).height() # bottom of the window < end of $('.json_table_container')
-                        that.$('.wrapper_scrollbar').show()
-                        that.$('.wrapper_scrollbar').css 'overflow', 'auto'
-                        that.$('.wrapper_scrollbar').css 'margin-bottom', '0px'
-                    # And sometimes we "hide" it
-                    else
-                        # We can not hide .wrapper_scrollbar because it would break the binding between wrapper_scrollbar and content_container
-                        that.$('.wrapper_scrollbar').css 'overflow', 'hidden'
+                position_scrollbar = ->
+                    if $(content_container).offset()?
+                        # Sometimes we don't have to display the scrollbar (when the results are not shown because the query is too big)
+                        if $(window).scrollTop()+$(window).height() < $(content_container).offset().top+20 # bottom of the window < beginning of $('.json_table_container') // 20 pixels is the approximate height of the scrollbar (so we don't show JUST the scrollbar)
+                            that.$('.wrapper_scrollbar').hide()
+                        # We show the scrollbar and stick it to the bottom of the window because there is more content below
+                        else if $(window).scrollTop()+$(window).height() < $(content_container).offset().top+$(content_container).height() # bottom of the window < end of $('.json_table_container')
+                            that.$('.wrapper_scrollbar').show()
+                            that.$('.wrapper_scrollbar').css 'overflow', 'auto'
+                            that.$('.wrapper_scrollbar').css 'margin-bottom', '0px'
+                        # And sometimes we "hide" it
+                        else
+                            # We can not hide .wrapper_scrollbar because it would break the binding between wrapper_scrollbar and content_container
+                            that.$('.wrapper_scrollbar').css 'overflow', 'hidden'

                 that = @
                 position_scrollbar()
@@ -1663,3 +1665,5 @@
         destroy: =>
             $(document).unbind 'mousemove', @handle_mousemove
             $(document).unbind 'mouseup', @handle_mouseup
+            $(window).unbind 'scroll'
+            $(window).unbind 'resize'


update CDN class to bypass running app.config when no 'app' specified

--- app/templates/app/src/CDN/CDN.coffee
+++ app/templates/app/src/CDN/CDN.coffee
@@ -73,7 +73,7 @@

     Config = require('Config')(app)
     # whitelist cdn endpoints
-    app.config ['$sceDelegateProvider', ($sceDelegateProvider)->
+    app?.config? ['$sceDelegateProvider', ($sceDelegateProvider)->

         endpoints = Config.get('cdn')
         cdn = ['self']
@@ -87,6 +87,6 @@
         $sceDelegateProvider.resourceUrlWhitelist(cdn)
     ]

-    app.service 'CDN', -> CDN
+    app?.service? 'CDN', -> CDN
     # make class available through 'require'
     return CDN


removing shouldWatch=true, was only for debugging purposes

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -70,7 +70,6 @@

 buildAndTest = (shouldWatch, done) ->
-  shouldWatch = true
   build(shouldWatch).on 'end', ->
     buildTests(shouldWatch).on 'end', ->
       config =


Improved shown realname of email addresses.

--- app/assets/javascripts/app/index.coffee
+++ app/assets/javascripts/app/index.coffee
@@ -212,7 +212,14 @@
           if result
             result = result + ', '
           if item.name
-            result = result + App.Utils.htmlEscape(item.name) + ' '
+            item.name = item.name
+              .replace(',', '')
+              .replace(';', '')
+              .replace('"', '')
+              .replace('\'', '')
+            if item.name.match(/\@|,|;|\^|\+|#|§|\$|%|&|\/|\(|\)|=|\?|\*/)
+              item.name = "\"#{item.name}\""
+            result = "#{result}#{App.Utils.htmlEscape(item.name)} "
           if item.address
             result = result + " <#{App.Utils.htmlEscape(item.address)}&gt"
         result


Sankey: link tooltips. Closes #1247

Sankey: link tooltips. Closes #1247

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -443,6 +443,13 @@
             my: 'right center'
             at: 'top center'

+        $("#{@container_selector()} g.link path").qtip
+          content: -> $(this).attr('data-tooltip')
+          position:
+            target: 'mouse'
+          style:
+            classes: "ui-tooltip-tipsy"
+
     # callbacks
     #
     unselectedLinkOpacity = 0.1
@@ -510,6 +517,7 @@
         .attr("display", (link) ->
           if link.value() == 0.0 then 'none' else 'inline'
         )
+        .attr("data-tooltip", (d) => @value_formatter d.value())

     # returns the height of the tallest column
     max_column_value: =>


some initial work on monitoring changes in the chain for nested/deep bindings

--- app/assets/javascripts/lib/bb.js.coffee
+++ app/assets/javascripts/lib/bb.js.coffee
@@ -38,42 +38,53 @@
         updateView($el, bindingDestination, bindingFilter, result)
       toView()

-      context = view
-      _.find(bindingSourceProps, (bindingSourceProp) ->
-        switch
-          when context instanceof Backbone.View
-            return true unless context[bindingSourceProp]?
-            if _.isFunction(context[bindingSourceProp])
-              context.model?.startRecording?()
-              result = context[bindingSourceProp].call(context)
-              if context.model?.finishRecording?
-                modelAttributes = context.model.finishRecording()
-                _.each(modelAttributes, (ignored, attribute) -> view.listenTo(context.model, "change:#{attribute}", toView))
-              context = result
-            else
-              context = context[bindingSourceProp]
-            return false
-          when context instanceof Backbone.Model
-            if context[bindingSourceProp]?
+      attachBindings = ->
+        bindingChain = []
+        context = view
+        chainIncomplete = _.find(bindingSourceProps, (bindingSourceProp) ->
+          switch
+            when context instanceof Backbone.View
+              return true unless context[bindingSourceProp]?
               if _.isFunction(context[bindingSourceProp])
-                context.startRecording?()
+                context.model?.startRecording?()
                 result = context[bindingSourceProp].call(context)
-                if context.finishRecording?
-                  modelAttributes = context.finishRecording()
-                  _.each(modelAttributes, (ignored, attribute) -> view.listenTo(context, "change:#{attribute}", toView))
+                if context.model?.finishRecording?
+                  modelAttributes = context.model.finishRecording()
+                  #_.each(modelAttributes, (ignored, attribute) -> view.listenTo(context.model, "change:#{attribute}", toView))
+                  _.each(modelAttributes, (ignored, attribute) -> bindingChain.push({model: context.model, attribute}))
                 context = result
               else
                 context = context[bindingSourceProp]
+              return false
+            when context instanceof Backbone.Model
+              if context[bindingSourceProp]?
+                if _.isFunction(context[bindingSourceProp])
+                  context.startRecording?()
+                  result = context[bindingSourceProp].call(context)
+                  if context.finishRecording?
+                    modelAttributes = context.finishRecording()
+                    #_.each(modelAttributes, (ignored, attribute) -> view.listenTo(context, "change:#{attribute}", toView))
+                    _.each(modelAttributes, (ignored, attribute) -> bindingChain.push({model: context, attribute}))
+                  context = result
+                else
+                  context = context[bindingSourceProp]
+              else
+                #view.listenTo(context, "change:#{bindingSourceProp}", toView)
+                bindingChain.push({model: context, attribute: bindingSourceProp})
+                context = context.get(bindingSourceProp)
+              return false
+            when not context?
+              debugger
+              return true
             else
-              view.listenTo(context, "change:#{bindingSourceProp}", toView)
-              context = context.get(bindingSourceProp)
-            return false
-          when not context?
-            return true
-          else
-            context = context[bindingSourceProp]
-            return false
-      )
+              context = context[bindingSourceProp]
+              return false
+        )
+        if not chainIncomplete?
+          bindingLeaf = bindingChain.pop()
+          view.listenTo(bindingLeaf.model, "change:#{bindingLeaf.attribute}", toView) if bindingLeaf?
+        _.each(bindingChain, (bindingNode) -> view.listenTo(bindingNode.model, "change:#{bindingNode.attribute}", attachBindings))
+      attachBindings()

 viewMixin =
   initBindings: -> initBindings(@)


Persist "copied to clipboard" button state

--- app/assets/javascripts/papers.js.coffee
+++ app/assets/javascripts/papers.js.coffee
@@ -18,7 +18,7 @@
     $(this).addClass("btn-success")
     $(this).text("Copied to clipboard")

-  clip.on 'mouseout', ->
+  clip.on 'mouseover', ->
     $(this).removeClass("btn-success")
     $(this).text("Copy Citation")
 else


Add comment

Add comment

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -21,11 +21,12 @@
       connectOutlets: (router, context) ->
         router.get('applicationController').connectOutlet('userProfile', context)

+    goToUserProfile: Ember.Route.transitionTo('root.userProfile')
+    goToRoot: Ember.Route.transitionTo('root.index')
+
+    # Called as action on the navigation bar. This should live someplace else.
     doLookUp: (router, e) ->
       if screenName = $('.js-user-search-input').val().replace(/^@/, '')
         Ember.run =>
           router.transitionTo('root.userProfile', App.User.find(screenName: screenName))
       $('.js-user-search-input').val('').blur()
-
-    goToUserProfile: Ember.Route.transitionTo('root.userProfile')
-    goToRoot: Ember.Route.transitionTo('root.index')


Fix expiry check in `othentic.status`.

Fix expiry check in `othentic.status`.

The expiry check was reversed, and could've returned
tokens that were expired by 30mins.

--- publications/publications.coffee
+++ publications/publications.coffee
@@ -43,8 +43,7 @@
     expiries = _.values(tokensExpiries)
     if expiries.length
       newestToken = Math.max.apply(Math, expiries)
-      newestToken += accessTokenExpiry
-      newestToken -= recheckInterval
+      newestToken += (accessTokenExpiry - recheckInterval)
       return [Othentic.STATUS_CONNECTED, newestToken]
     else
       return [Othentic.STATUS_AVAILABLE, null]
@@ -84,7 +83,10 @@
       userId: userId
       type: Othentic.TokenStore.TYPE_ACCESS
       createdAt: {
-        $gt: +(new Date()) - accessTokenExpiry - recheckInterval
+        # Return tokens that are unexpired, but shorten
+        #  the expiry date by the `recheckInterval`
+        # This means tokens will "expire" early to the user.
+        $gt: +(new Date()) - (accessTokenExpiry - recheckInterval)
       }
     }).observe({
       added: onTokenAdded,


Removing unnecesary source file for projects

--- app/coffee/modules/projects/main.coffee
+++ app/coffee/modules/projects/main.coffee
@@ -1,138 +0,0 @@
-###
-# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
-# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
-# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# File: modules/common/attachments.coffee
-###
-
-taiga = @.taiga
-module = angular.module("taigaProject")
-bindOnce = @.taiga.bindOnce
-
-class ProjectsController extends taiga.Controller
-    @.$inject = [
-        "$scope",
-        "$q",
-        "$tgResources",
-        "$rootScope",
-        "$tgNavUrls",
-        "$tgAuth",
-        "$tgLocation",
-        "$appTitle",
-        "$projectUrl",
-        "$tgConfig",
-        "tgLoader"
-    ]
-
-    constructor: (@scope, @q, @rs, @rootscope, @navUrls, @auth, @location, @appTitle, @projectUrl, @config
-                  tgLoader) ->
-        @appTitle.set("Projects")
-
-        if !@auth.isAuthenticated()
-            @location.path(@navUrls.resolve("login"))
-
-        @.user = @auth.getUser()
-
-        @.projects = []
-        promise = @.loadInitialData()
-
-        promise.then () =>
-            @scope.$emit("projects:loaded", @.projects)
-
-        promise.then null, @.onInitialDataError.bind(@)
-
-        # Finally
-        promise.finally tgLoader.pageLoaded
-
-    loadInitialData: ->
-        return @rs.projects.listByMember(@rootscope.user?.id).then (projects) =>
-            @.projects = {'recents': projects.slice(0, 8), 'all': projects}
-            for project in projects
-                project.url = @projectUrl.get(project)
-
-            return projects
-
-    logout: ->
-        @auth.logout()
-        @location.path(@navUrls.resolve("login"))
-
-    isFeedbackEnabled: ->
-        return @config.get("feedbackEnabled")
-
-    sendFeedback: ->
-        @rootscope.$broadcast("feedback:show")
-
-module.controller("ProjectsController", ProjectsController)
-
-
-class ProjectController extends taiga.Controller
-    @.$inject = [
-        "$scope",
-        "$tgResources",
-        "$tgRepo",
-        "$routeParams",
-        "$q",
-        "$rootScope",
-        "$appTitle",
-        "$tgLocation",
-        "$tgNavUrls"
-    ]
-
-    constructor: (@scope, @rs, @repo, @params, @q, @rootscope, @appTitle, @location, @navUrls) ->
-        promise = @.loadInitialData()
-
-        promise.then () =>
-            @appTitle.set(@scope.project.name)
-            @scope.$emit("regenerate:project-pagination")
-
-        promise.then null, @.onInitialDataError.bind(@)
-
-    loadInitialData: ->
-        promise = @.loadProject()
-        promise.then(=> @.loadProjectStats())
-        return promise
-
-    loadProject: ->
-        return @rs.projects.getBySlug(@params.pslug).then (project) =>
-            @scope.projectId = project.id
-            @scope.project = project
-            @scope.$emit("project:loaded", @scope.project)
-            return project
-
-    loadProjectStats: ->
-        return @rs.projects.stats(@scope.projectId).then (stats) =>
-            @scope.stats = stats
-            return stats
-
-module.controller("ProjectController", ProjectController)
-
-ProjectsListDirective = ($compile, $template) ->
-    template = $template.get('project/project-list.html', true)
-
-    link = ($scope, $el, $attrs, $ctrls) ->
-        render = (projects) ->
-            $el.html($compile(template({projects: projects}))($scope))
-            $scope.$emit("regenerate:project-pagination")
-
-        $scope.$on "projects:loaded", (ctx, projects) ->
-            render(projects.all) if projects.all?
-
-    return {
-        link: link
-    }
-
-module.directive("tgProjectsList", ["$compile", "$tgTemplate", ProjectsListDirective])


remove console.log

remove console.log

--- app/assets/javascripts/effective_datatables/inline_crud.js.coffee
+++ app/assets/javascripts/effective_datatables/inline_crud.js.coffee
@@ -163,7 +163,6 @@

     if EffectiveForm.remote_form_refresh_datatables.find((id) -> $table.attr('id').startsWith(id) || id == 'all') && $table != $except
       if $table != $source
-        console.log("Refreshing #{$table}")
         $table.DataTable().draw()

 # Cancel button clicked. Blow away new tr, or restore edit tr


:two:

:two:

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -8,15 +8,15 @@
 #   None
 #
 # Commands:
-#   trigger phrase with response - Make the robot respond with "response" when it hears "phrase"
-#   trigger remove phrase        - Delete the given trigger
+#   trigger phrase to response - Make the robot respond with "response" when it hears "phrase"
+#   trigger remove phrase      - Delete the given trigger
 #
 # Author:
 #   skalnik

 module.exports = (robot) ->
-  robot.respond /trigger (.*) with (.*)/, (msg) ->
+  robot.respond /trigger (.*) to (.*)/, (msg) ->
     trigger = msg.match[1].trim()
     response = msg.match[2].trim()


fix(feedback): Links should open in the user's default browser

--- internal_packages/feedback/lib/feedback-button.cjsx
+++ internal_packages/feedback/lib/feedback-button.cjsx
@@ -74,13 +74,21 @@
         'height': height,
         'title': 'Feedback'

+      onOpenURL = (event, href) ->
+        shell = require 'shell'
+        shell.openExternal(href)
+        event.preventDefault()
+
       # Disable window close, hide instead
       w.on 'close', (event) ->
         # inside the window we prevent close - here we route close to hide
         event.preventDefault() # this does nothing, contrary to the docs
         w.hide()
       w.on 'closed', (event) ->
-        window.feedbackWindow = null # if the window does get closed, clear our ref to it
+        # if the window does get closed, clear our ref to it
+        window.feedbackWindow = null
+      w.webContents.on('new-window', onOpenURL)
+      w.webContents.on('will-navigate', onOpenURL)

       url = path.join __dirname, '..', 'feedback.html'
       w.loadUrl("file://#{url}?#{params}")


text.coffee: duplicate assignment to same variable

Signed-off-by: Thaddee Tyl <bf078aa284c515a655d62585a115c6ca630fa7b2@gmail.com>

--- src/types/text.coffee
+++ src/types/text.coffee
@@ -22,7 +22,7 @@

 text.name = 'text'

-text.create = text.create = -> ''
+text.create = -> ''

 strInject = (s1, pos, s2) -> s1[...pos] + s2 + s1[pos..]


Change folder to match new osKite default

--- client/app/Applications/Viewer.kdapplication/AppController.coffee
+++ client/app/Applications/Viewer.kdapplication/AppController.coffee
@@ -19,7 +19,7 @@

       failure    : (options, cb)->
         correctPath = \
-          "/home/#{KD.nick()}/Sites/#{KD.nick()}.#{KD.config.userSitesDomain}/"
+          "/home/#{KD.nick()}/Web/"
         KD.getSingleton("appManager").notify "File must be under: #{correctPath}"

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


home/myteam: get member informations from helper function

--- client/home/lib/myteam/components/hometeamteammates/member.coffee
+++ client/home/lib/myteam/components/hometeamteammates/member.coffee
@@ -34,8 +34,8 @@
     if role is 'owner'
       items.push { title: 'Make member', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'member') }
     else if role is 'admin'
+      items.push { title: 'Make owner', key: 'mameowner', onClick: @props.handleRoleChange.bind(this, @props.member, 'owner') }
       items.push { title: 'Make member', key: 'makemember', onClick: @props.handleRoleChange.bind(this, @props.member, 'member') }
-      items.push { title: 'Make owner', key: 'mameowner', onClick: @props.handleRoleChange.bind(this, @props.member, 'owner') }
       items.push { title: 'Disable user', key: 'disableuser', onClick: @props.handleRoleChange.bind(this, @props.member, 'kick') }
     else if role is 'Invitation Sent'
       items.push {title: 'Resend Invitation', key: 'resend', onClick: @props.handleInvitation.bind(this, @props.member, 'resend')}
@@ -48,22 +48,29 @@
     return items

+  getData: (member) ->
+
+    nickname = member.getIn ['profile', 'nickname']
+    email = member.getIn ['profile', 'email']
+    role = member.get 'role'
+    firstName = member.getIn ['profile', 'firstName']
+    lastName = member.getIn ['profile', 'lastName']
+    fullName = "#{firstName} #{lastName}"
+
+    if member.get('status') is 'pending'
+      role = 'Invitation Sent'
+      firstName = member.get('firstName') or ''
+      lastName = member.get('lastName') or ''
+      fullName = member.get 'email'
+      email = "#{firstName} #{lastName}"
+      nickname = ''
+
+    return { nickname, email, role, firstName, lastName, fullName }
+
+
   render: ->

-    nickname = @props.member.getIn(['profile', 'nickname'])
-    email = @props.member.getIn(['profile', 'email'])
-    role = @props.member.get 'role'
-    firstName = @props.member.getIn(['profile', 'firstName'])
-    lastName = @props.member.getIn(['profile', 'lastName'])
-    fullName = "#{firstName} #{lastName}"
-
-    if @props.member.get('status') is 'pending'
-      role = 'Invitation Sent'
-      firstName = @props.member.get('firstName') or ''
-      lastName = @props.member.get('lastName') or ''
-      fullName = @props.member.get 'email'
-      email = "#{firstName} #{lastName}"
-      nickname = ''
+    { nickname, email, role, firstName, lastName, fullName } = @getData @props.member


fixed bug with 'supporting' missing in empty view

--- core/app/backbone/views/fact_relations/fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations/fact_relations_view.coffee
@@ -16,7 +16,7 @@
   className: 'fact-relation-listing'

   itemView: Backbone.View
-  itemViewOptions: => type: @collection.type
+  itemViewOptions: => type: @options.type
   emptyView: EmptyEvidenceView

   addChildView: (item, collection, options) ->
@@ -74,6 +74,7 @@
         model: @model
       @factRelationsRegion.show new EvidenceListView
         collection: @joinedSortedCollection()
+        type: @model.relations().type
         item_type: 'fact_relation'
     else
       @hideRegion @factRelationSearchRegion


Adjusted JS default time out so that the client would not die before the server connection dies

--- lib/assets/javascripts/firehose/long_poll.js.coffee
+++ lib/assets/javascripts/firehose/long_poll.js.coffee
@@ -15,8 +15,7 @@
     # Protocol schema we should use for talking to WS server.
     @config.longPoll.url ||= "http:#{@config.uri}"
     # How many ms should we wait before timing out the AJAX connection?
-    # TODO why was this set at 0s?
-    @config.longPoll.timeout ||= 0
+    @config.longPoll.timeout ||= 20000

     # TODO - What is @_lagTime for? Can't we just use the @_timeout value?
     # We use the lag time to make the client live longer than the server.


sending success and failure handlers to calatrava bridge ajax function

--- lib/calatrava/templates/kernel/app/calatrava.coffee
+++ lib/calatrava/templates/kernel/app/calatrava.coffee
@@ -149,6 +149,8 @@
       method: method
       body: bodyStr
       headers: customHeaders
+      success: success
+      failure: failure

   successfulResponse: (requestId, response) ->
     successHandlersById[requestId](response)


Fix the min and max bug

Fix the min and max bug

--- src/angular-moment-timepicker.coffee
+++ src/angular-moment-timepicker.coffee
@@ -231,7 +231,7 @@

       return stepsByWeek

-    scope.$watch('minDate + maxDate + steps.length', ->
+    scope.$watchGroup(['minDate', 'maxDate', 'steps'], ->
       return if not scope.steps?
       for step in scope.steps
         stepDate = moment(step.value)
@@ -277,7 +277,7 @@
       period = step.step
       amount = step.amount

-      scope.steps = for i in [0..amount] by 1
+      scope.steps = for i in [0..amount - 1] by 1
         currentStep = {
           past: step.compare(stepDate, scope.position) < 0
           future: step.compare(stepDate, scope.position) > 0
@@ -459,10 +459,10 @@
     )

     scope.$next = ->
-      scope.position = moment(moment(scope.position) + (3 * viewsSteps[scope.view].step)).toDate()
+      scope.position = moment(moment(scope.position) + viewsSteps[scope.view].step).toDate()

     scope.$previous = ->
-      scope.position = moment(moment(scope.position) + (3 * viewsSteps[scope.view].step)).toDate()
+      scope.position = moment(moment(scope.position) - viewsSteps[scope.view].step).toDate()

     selecting = null
     changeRange = (newDate) ->
@@ -494,7 +494,9 @@
                              view="view"
                              active-before="lab"
                              active-after="laa"
-                             hovered="hover">
+                             hovered="hover"
+                             min-date="min"
+                             max-date="max">
       </moment-datetimepicker>

@@ -503,7 +505,9 @@
                              view="view"
                              active-before="rab"
                              active-after="raa"
-                             hovered="hover">
+                             hovered="hover"
+                             min-date="min"
+                             max-date="max">

       <moment-datetimepicker selected="middle"
@@ -511,7 +515,9 @@
                              view="view"
                              active-before="mab"
                              active-after="maa"
-                             hovered="hover">
+                             hovered="hover"
+                             min-date="min"
+                             max-date="max">
       </moment-datetimepicker>


fix(gulp): prevent stop of gulp watch on error

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -46,6 +46,15 @@
 typeScript            = require 'gulp-typescript'
 uglify                = require 'gulp-uglify'

+onError = (e) ->
+	isArray  = Array.isArray e
+	err      = e.message or e
+	errors   = if isArray then err else [err]
+	messages = (error for error in errors)
+
+	gutil.log gutil.colors.red message for message in messages
+	@emit 'end'
+
 appUrl = "http://localhost:#{DEV_PORT}"

 bowerComponents = do ->
@@ -82,6 +91,7 @@
 	bower
 		.commands
 		.install components, {}, {directory: BOWER_DIRECTORY}
+		.on 'error', onError
 		.on 'end', (results) ->
 			deferred.resolve results

@@ -106,11 +116,13 @@
 	gulp
 		.src BOWER_DIRECTORY
 		.pipe clean()
+		.on 'error', onError

 gulp.task 'clean:working', ->
 	gulp
 		.src [COMPONENTS_DIRECTORY, TEMP_DIRECTORY, DIST_DIRECTORY, DOCS_DIRECTORY]
 		.pipe clean()
+		.on 'error', onError

 gulp.task 'coffee', ['coffeelint', 'ngClassify'], ->
 	options =
@@ -119,6 +131,7 @@
 	gulp
 		.src '**/*.coffee', cwd: TEMP_DIRECTORY
 		.pipe coffee options
+		.on 'error', onError
 		.pipe gulp.dest TEMP_DIRECTORY

 gulp.task 'coffeelint', ->
@@ -138,7 +151,9 @@
 			'!app/app.coffee'
 		], cwd: SRC_DIRECTORY
 		.pipe coffeelint options
+		.on 'error', onError
 		.pipe coffeelint.reporter()
+		.on 'error', onError

 gulp.task 'copy:temp', ['clean:working', 'normalizeComponents'], ->
 	gulp
@@ -179,6 +194,7 @@
 	gulp
 		.src '**/*.spec.{coffee,js}', cwd: E2E_DIRECTORY
 		.pipe protractor.protractor options
+		.on 'error', onError

 gulp.task 'e2e-driver', protractor.webdriver_standalone

@@ -191,6 +207,7 @@
 	gulp
 		.src '**/*.jade', cwd: TEMP_DIRECTORY
 		.pipe jade options
+		.on 'error', onError
 		.pipe gulp.dest TEMP_DIRECTORY

 gulp.task 'karma', ->
@@ -234,12 +251,14 @@
 	gulp
 		.src '**/*.less', cwd: TEMP_DIRECTORY
 		.pipe less options
+		.on 'error', onError
 		.pipe gulp.dest TEMP_DIRECTORY

 gulp.task 'markdown', ['copy:temp'], ->
 	gulp
 		.src '**/*.{md,markdown}', cwd: TEMP_DIRECTORY
 		.pipe markdown()
+		.on 'error', onError
 		.pipe gulp.dest TEMP_DIRECTORY

 gulp.task 'ngClassify', ['copy:temp'], ->
@@ -251,6 +270,7 @@
 	gulp
 		.src '**/*.coffee', cwd: TEMP_DIRECTORY
 		.pipe ngClassify options
+		.on 'error', onError
 		.pipe gulp.dest TEMP_DIRECTORY

 gulp.task 'normalizeComponents', ['bower', 'clean:working'], ->
@@ -279,11 +299,13 @@
 	gulp
 		.src 'index.html', cwd: DIST_DIRECTORY
 		.pipe open '', options
+		.on 'error', onError

 gulp.task 'reload', ['build'], ->
 	gulp
 		.src 'index.html', cwd: DIST_DIRECTORY
 		.pipe connect.reload()
+		.on 'error', onError

 gulp.task 'scripts', ['coffee', 'typeScript']

@@ -331,6 +353,7 @@
 		gulp
 			.src files, cwd: TEMP_DIRECTORY
 			.pipe includify()
+			.on 'error', onError
 			.on 'end', (data) ->
 				deferred.resolve data

@@ -347,6 +370,7 @@
 		gulp
 			.src 'index.html', cwd: TEMP_DIRECTORY
 			.pipe template data
+			.on 'error', onError
 			.pipe gulp.dest TEMP_DIRECTORY
 			.on 'end', ->
 				deferred.resolve()
@@ -367,6 +391,7 @@
 	gulp
 		.src '**/*.ts', cwd: TEMP_DIRECTORY
 		.pipe typeScript()
+		.on 'error', onError
 		.pipe gulp.dest TEMP_DIRECTORY

 gulp.task 'views', ['jade', 'markdown']


Remove warning from vim when run in gulp.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -9,8 +9,8 @@
 remotePluginPath = 'razor-x/vimrc'
 pluginPath = "#{homePath}/.vim/bundle/vimrc"

-installBundle = () -> $.shell('vim -c PluginInstall -c quitall')
-cleanBundle = () -> $.shell('vim -c PluginClean -c quitall')
+installBundle = () -> $.shell("sh -c '</dev/tty vim -c PluginUpdate -c quitall'")
+cleanBundle = () -> $.shell("sh -c '</dev/tty vim -c PluginClean -c quitall'")

 gulp.task 'default', ['watch']

@@ -23,6 +23,7 @@
   .pipe gulp.dest(homePath)

   gulp.src('')
+  .pipe installBundle()
   .pipe installBundle()
   .pipe cleanBundle()


remove deprecated gulp.run

remove deprecated gulp.run

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -80,7 +80,7 @@
 	, null, console.log
 	return undefined

+gulp.task 'watch', ->
+  gulp.watch './src/*.coffee', ['js']

-gulp.task 'default', ['js'], ->
-	gulp.watch './src/*.coffee', ->
-		gulp.run 'clean-build', 'js'
+gulp.task 'default', ['js', 'watch']


remove `gulp *-dev`

remove `gulp *-dev`

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -67,13 +67,6 @@
     done
   ]

-gulp.task 'build-dev', [
-  'build-html-dev'
-  'build-resource'
-  'build-script-dev'
-  'build-style-dev'
-]
-
 gulp.task 'build-font', ->
   gulp.src [
     './node_modules/font-awesome/fonts/*-webfont*'
@@ -83,12 +76,6 @@
 gulp.task 'build-html', ->
   baseDir = './dist/'
   loadEntriesV3('./data/**/*.md').forEach (entry) ->
-    prerenderEntry(baseDir, entry)
-  prerender(baseDir + 'index.html', null)
-
-gulp.task 'build-html-dev', ->
-  baseDir = './dist/'
-  loadEntriesV3('./data/nofile.md').forEach (entry) ->
     prerenderEntry(baseDir, entry)
   prerender(baseDir + 'index.html', null)

@@ -121,14 +108,6 @@
   ]
   null

-gulp.task 'build-script-dev', (done) ->
-  run.apply run, [
-    'build-script-coffee-dev'
-    'build-script-browserify-dev'
-    done
-  ]
-  null
-
 gulp.task 'build-script-browserify', ->
   bundled = browserify
     debug: false
@@ -140,26 +119,9 @@
   .pipe uglify()
   .pipe gulp.dest './dist/scripts/'

-gulp.task 'build-script-browserify-dev', ->
-  bundled = browserify
-    debug: true
-  .add './.tmp/src/scripts/index.js'
-  .bundle()
-  ignoreError bundled
-  .pipe source 'main.js'
-  .pipe buffer()
-  .pipe gulp.dest './dist/scripts/'
-
 gulp.task 'build-script-coffee', ->
   gulp.src './src/scripts/**/*.coffee'
   .pipe coffee()
-  .pipe gulp.dest './.tmp/src/scripts/'
-
-gulp.task 'build-script-coffee-dev', ->
-  gulp.src './src/scripts/**/*.coffee'
-  .pipe sourcemaps.init()
-  .pipe ignoreError coffee()
-  .pipe sourcemaps.write()
   .pipe gulp.dest './.tmp/src/scripts/'

 gulp.task 'build-style', ->
@@ -172,29 +134,11 @@
   .pipe minifyCss()
   .pipe gulp.dest './dist/styles/'

-gulp.task 'build-style-dev', ->
-  gulp.src [
-    './src/styles/**/*.less'
-    './node_modules/font-awesome/css/font-awesome.min.css'
-  ]
-  .pipe ignoreError less()
-  .pipe concat 'main.css'
-  .pipe minifyCss()
-  .pipe gulp.dest './dist/styles/'
-
 gulp.task 'build-test', ->
   gulp.src './test/**/*.coffee'
   .pipe sourcemaps.init()
   .pipe coffee()
   .pipe espower()
-  .pipe sourcemaps.write()
-  .pipe gulp.dest './.tmp/test/'
-
-gulp.task 'build-test-dev', ->
-  gulp.src './test/**/*.coffee'
-  .pipe sourcemaps.init()
-  .pipe ignoreError coffee()
-  .pipe ignoreError espower()
   .pipe sourcemaps.write()
   .pipe gulp.dest './.tmp/test/'

@@ -216,7 +160,3 @@
 gulp.task 'test', ['build-test'], ->
   gulp.src './.tmp/test/**/*.js'
   .pipe mocha()
-
-gulp.task 'test-dev', ['build-test-dev'], ->
-  gulp.src './.tmp/test/**/*.js'
-  .pipe ignoreError mocha()


proper db

proper db

--- sockets.coffee
+++ sockets.coffee
@@ -1,7 +1,8 @@
 app = require "./settings"
 db = require "./models"

-db.sequelize.sync().success(() ->
+db.sequelize.sync().complete (err) ->
+  throw err if err
   server = app.listen (process.env.PORT || 8000), () ->
     console.log('APPLICATION STARTED ON PORT %d'.green, process.env.PORT || 8000)


Fix score input

Fix score input

--- app/scripts/components/TextInput.coffee
+++ app/scripts/components/TextInput.coffee
@@ -33,11 +33,12 @@
       index = Math.min(index + 1, length - 1)
       @_updateCursor(index)

-      if index is oldIndex and index is length - 1
+      if (index is oldIndex) and (index is length - 1)
         player.unbind('Up')
         player.unbind('Down')
         player.unbind('Left')
         player.unbind('Right')
+        player.unbind('Fire')
         @defer.resolve(name)

     @cursor = Crafty.e('2D, DOM, Text')


feat(HomeCtrl): scroll to the "unsynced remote" section on load

--- app/scripts/controllers/HomeCtrl.coffee
+++ app/scripts/controllers/HomeCtrl.coffee
@@ -55,6 +55,7 @@

 		$scope.getList ->
 			$( "body" ).scrollspy target: "#menu"
+			window.scroll 0, $( "#remote" ).offset().top

 ]


Added basic render by state method, but there is no interface for selecting state or toggling between them yet.

--- app/scripts/controllers/district.coffee
+++ app/scripts/controllers/district.coffee
@@ -8,6 +8,7 @@
     $scope.warning = null
     $scope.state_district = {state: null, district: null}
     $scope.district_reps = []
+    $scope.map_width = 0
     $scope.FIPS_to_state = {1:'AL', 2:'AK', 4:'AZ', 5:'AR', 6:'CA', 8:'CO', 9:'CT', 10:'DE', 11:'DC', 12:'FL', 13:'GA', 15:'HI', 16:'ID', 17:'IL', 18:'IN', 19:'IA', 20:'KS', 21:'KY', 22:'LA', 23:'ME', 24:'MD', 25:'MA', 26:'MI', 27:'MN', 28:'MS', 29:'MO', 30:'MT', 31:'NE', 32:'NV', 33:'NH', 34:'NJ', 35:'NM', 36:'NY', 37:'NC', 38:'ND', 39:'OH', 40:'OK', 41:'OR', 42:'PA', 44:'RI', 45:'SC', 46:'SD', 47:'TN', 48:'TX', 49:'UT', 50:'VT', 51:'VA', 53:'WA', 54:'WV', 55:'WI', 56:'WY', 60:'AS', 64:'FM', 66:'GU', 68:'MH', 69:'MP', 70:'PW', 72:'PR', 74:'UM', 78:'VI'}

     $scope.getLocation = () ->
@@ -59,7 +60,7 @@
       d3.select('.districts').selectAll('path').classed('selected', false)
       district_element = d3.select(d3.select('.districts').selectAll('path').filter((d, i) -> return this.textContent == "#{$scope.state_district.state}-#{$scope.state_district.district}")[0][0])
       district_element.attr('class', 'selected')
-      district_element.call($scope.zoomIn)
+      if $scope.map_width is 960 then district_element.call($scope.zoomIn)

     $scope.drawMap = () ->
       ready = (error, us, congress) ->
@@ -93,22 +94,26 @@
         $('#map_holder').on("dblclick", () ->
           $scope.zoomOut()
         )
-      width = 960
+
+      $scope.map_width = 960
       height = 500
       path = d3.geo.path()
-      svg = d3.select("#map_holder").append("svg").attr("width", width).attr("height", height)
+      svg = d3.select("#map_holder").append("svg").attr("width", $scope.map_width).attr("height", height)
       $scope.usMap = svg.append("g").attr("id", "map_with_districts")
-      tooltip = d3.select("#map_holder")
+      tooltip = $scope.makeTooltip()
+      dialog = d3.select("#map_dialog")
+        .style("opacity", 1e-6)
+        .style("z-index", "15")
+      $scope.makeMapGradients()
+      $scope.makeMapGradients()
+      queue().defer(d3.json, "data/us.json").defer(d3.json, "data/us-congress-113.json").await ready
+
+    $scope.makeTooltip = () ->
+      return d3.select("#map_holder")
         .append("div")
         .attr("class", "map_tooltip")
         .style("z-index", "10")
         .style("visibility", "hidden")
-      dialog = d3.select("#map_dialog")
-        .style("opacity", 1e-6)
-        .style("z-index", "15")
-
-      $scope.makeMapGradients()
-      queue().defer(d3.json, "data/us.json").defer(d3.json, "data/us-congress-113.json").await ready

     $scope.makeMapGradients = () ->
       d3.select("#map_holder").select("svg")
@@ -144,6 +149,7 @@
           .attr("stop-color", (d)->  return d.color)

     $scope.zoomIn = (d) ->
+      console.log "Called zoomIn"
       element = d.node()
       bbox = element.getBBox()
       x = bbox.x + bbox.width/2
@@ -160,7 +166,7 @@
       $scope.state_district = {state: null, district: null}
       $scope.district_reps = []
       d3.select('#map_dialog').transition().duration(750).style("opacity", 1e-6)
-      $scope.usMap.transition().duration(750).attr("transform", "translate(0,0)scale(1)").style "stroke-width", 1 + "px"
+      if $scope.map_width is 960 then $scope.usMap.transition().duration(750).attr("transform", "translate(0,0)scale(1)").style "stroke-width", 1 + "px"

     $scope.showDistrictDialog = () ->
       $('#map_dialog').html($compile("<sub-view template='partials/district_reps'></sub-view>")($scope))
@@ -172,7 +178,6 @@
     $scope.defaultFocus = () ->
       # TODO: refactor this to not use an API call but only scope variables if possible.
       if $routeParams.bioguide_id.length > 0
-        console.log $routeParams.bioguide_id
         ApiGet.congress "legislators?bioguide_id=#{$routeParams.bioguide_id}", (error, data) ->
           if not error
             if not data[0].district
@@ -184,8 +189,68 @@
           else console.log "Error, Senator/Rep not found."
       else console.log "No parameter"

+    $scope.drawMapByState = (state_FIPS) ->
+      state_districts = []
+      ready = (error, us, congress) ->
+        for obj in topojson.feature(congress, congress.objects.districts).features
+          if obj.id and JSON.stringify(obj.id).slice(0, -2) is state_FIPS
+            state_districts.push(obj)
+        #find min top/left, find width/height
+        district = $scope.usMap.append("g").attr("class", "districts").attr("clip-path", "url(#clip-land)").selectAll("path").data(state_districts).enter().append("path").attr("d", path).text (d) ->
+          "#{$scope.FIPS_to_state[d.id / 100 | 0]}-#{d.id % 100}"
+        district_boundaries = $scope.usMap.append("path").attr("class", "district-boundaries").attr("clip-path", "url(#clip-land)").datum(topojson.mesh(congress, congress.objects.districts, (a, b) ->
+          (a.id / 1000 | 0) is (b.id / 1000 | 0) and (a.id and JSON.stringify(a.id).slice(0, -2) is state_FIPS)
+        )).attr "d", path
+        district.on("mouseover", () ->
+          return tooltip.style("visibility", "visible").text(d3.select(this).text())
+        ).on("mousemove", () ->
+          return tooltip.style("top", (event.pageY-27)+"px").style("left", (event.pageX+"px"))
+        ).on("mouseout", () ->
+          return tooltip.style("visibility", "hidden")
+        ).on("click", () ->
+          if !$scope.district_reps.length and $scope.zoomed
+            $scope.usMap.transition().duration(750).attr("transform", "translate(0,0)scale(1)").style "stroke-width", 1 + "px"
+            d3.select('#map_dialog').transition().duration(750).style("opacity", 1e-6)
+            $scope.state_district = {state: null, district: null}
+            $scope.district_reps = []
+          else
+            district_id = d3.select(this).text()
+            $scope.state_district = {state: district_id.slice(0, 2), district: district_id.slice(3, 6)}
+            $scope.$apply()
+        )
+        $('#map_holder').on("dblclick", () ->
+          $scope.zoomOut()
+        )
+        # The below is based off zoom code, maybe refactor to call zoom or related func
+        bbox = d3.select("#map_with_districts").node().getBBox()
+        o_x = bbox.x + bbox.width/2
+        o_y = bbox.y + bbox.height/2
+        # scale = 100/Math.sqrt(Math.pow(bbox.width, 2) + Math.pow(bbox.height,2))
+        scale = 1
+        $scope.usMap.attr("transform", "translate(" + 220 / 2 + "," + 220 / 2 + ")scale(" + scale + ")translate(" + -o_x + "," + -o_y + ")").style "stroke-width", 1.5
+
+
+      #    element = d.node()
+      # bbox = element.getBBox()
+      # x = bbox.x + bbox.width/2
+      # y = bbox.y + bbox.height/2
+      # if y < 106 and (bbox.height > 53 or bbox.width > 53) then y += (106 - y)
+      # scale = 200/Math.sqrt(Math.pow(bbox.width, 2) + Math.pow(bbox.height,2))
+      # $scope.usMap.transition().duration(750).attr("transform", "translate(" + 960 / 2 + "," + 600 / 2 + ")scale(" + scale + ")translate(" + -x + "," + -y + ")").style "stroke-width", 1.5 / scale + "px"
+
+      $scope.map_width = 220
+      height = 220
+      path = d3.geo.path()
+      svg = d3.select("#map_holder").append("svg").attr("width", $scope.map_width).attr("height", height)
+      $scope.usMap = svg.append("g").attr("id", "map_with_districts")
+      dialog = d3.select("#map_dialog").style("opacity", 1e-6).style("z-index", "15")
+      tooltip = $scope.makeTooltip()
+      $scope.makeMapGradients()
+      queue().defer(d3.json, "data/us.json").defer(d3.json, "data/us-congress-113.json").await ready
+
     $scope.drawMap()
     $scope.defaultFocus()
+    # $scope.drawMapByState("49")

     $scope.$watch('state_district', (newVals, oldVals) ->
       if $scope.state_district.state


Remove groups property from IP spec

Remove groups property from IP spec

--- spec/IP.coffee
+++ spec/IP.coffee
@@ -12,13 +12,6 @@
     chai.expect(open.type).to.equal 'openBracket'
     chai.expect(close.type).to.equal 'closeBracket'
     chai.expect(data.type).to.equal 'data'
-
-  it 'should attach data groups explicitly', ->
-    data = new noflo.IP 'data', "Payload"
-    chai.expect(data.groups).to.be.an.instanceof Array
-    chai.expect(data.groups).to.have.lengthOf 0
-    data.groups = ['foo', 'bar']
-    chai.expect(data.groups).to.have.lengthOf 2

   it 'should be moved to an owner', ->
     p = new noflo.IP 'data', "Token"


server - fixed broken build due to prettyjson dev dep

--- server/gaQueries.coffee
+++ server/gaQueries.coffee
@@ -2,7 +2,7 @@
 Promise = require 'bluebird'
 _ = require 'lodash-node'
 moment = require 'moment'
-pj = require('prettyjson').render
+#pj = require('prettyjson').render

 # custom
 config = require "./config"


Setting parent property to fallback

Setting parent property to fallback


Fix empty item

Fix empty item

--- parts/prophet-info.cjsx
+++ parts/prophet-info.cjsx
@@ -19,6 +19,10 @@
           item = window.$slotitems[itemId]
         else
           item = window._slotitems[itemId]
+        item ?=
+          api_type: []
+          api_name: '???'
+          api_level: 0
         itemType = item?.api_type?[3]
         kiraValue = item.api_level ? item.api_level : 0
         <div key={i} className="slotitem-container-mini">


Fix overlay border-radii, respecting the radius of the knob and the hitArea

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -39,11 +39,9 @@
 			name: "fill"

 		@knobOverlay = new Layer
-			backgroundColor: null
 			name: "knobOverlay"

 		@sliderOverlay = new Layer
-			backgroundColor: null
 			name: "sliderOverlay"

 		super options
@@ -72,7 +70,8 @@
 		@knob.draggable.momentumOptions = {friction: 5, tolerance: 0.25}
 		@knob.draggable.bounce = false
 		@knob.draggable.propagateEvents = false
-		@knob.borderRadius = @knobOverlay.borderRadius = @knobSize / 2
+		@knob.borderRadius = @knobSize / 2
+		@knobOverlay.borderRadius = (@knob.borderRadius * 2) + (@hitArea / 4)

 		# If hitArea isn't defined, remove the overlays
 		if not @hitArea
@@ -166,7 +165,7 @@
 		@fill.style.borderRadius = "#{radius}px 0 0 #{radius}px"

 	_setOverlayRadius: =>
-		@knobOverlay.borderRadius = @knob.borderRadius
+		@knobOverlay.borderRadius = (@knob.borderRadius * 2) + (@hitArea / 4)

 	@define "bounded", @simpleProperty("bounded", false)


Set a default value on init to avoid rounding issues and to always start at half the maximum value.

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -66,6 +66,9 @@
 		# On click/touch of the slider, update the value
 		@on(Events.TouchStart, @_touchDown)

+		# Set default value
+		@value = @max / 2
+

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


Fixed obtaining system country code setting

--- server/methods/getSecurityBanner.coffee
+++ server/methods/getSecurityBanner.coffee
@@ -8,13 +8,14 @@
 		perms = new Jedis.AccessPermission permissionIds
 			.toArray();

-		#systemCountryCode = Jedis.accessManager.getPermissions(Meteor.settings.public.system.countryCode);
-		#if( systemCountryCode.length === 0 ) {
-		#	console.log('System country not found.  Defaulting to USA');
+		systemCountryCode = Jedis.accessManager.getPermissions(Jedis.settings.get('public').system.countryCode)
+
+		if systemCountryCode.length is 0
+			console.log 'System country not found.  Defaulting to USA'
 			systemCountryCode = _id: '300', trigraph: 'USA', label: 'United States', type: 'Release Caveat'
-		#} else {
-		#	systemCountryCode = systemCountryCode[0];
-		#}
+		else
+			systemCountryCode = systemCountryCode[0];
+

 		# Obtain classification, add to banner. If none, default: 'UNCLASSIFIED'
@@ -54,8 +55,9 @@
 		# if still contains entries, hard-code system country code at front else 'NOFORN'
 		reltoLabels.splice(0, 0, ( if reltoLabels.length > 0 then 'REL TO ' + systemCountryCode.trigraph else 'NOFORN'))
 		reltoLabels = reltoLabels.join ', '
-

+
+		# stitch everything together
 		banner.classificationId = classification._id
 		banner.text = _.compact [classification.label.toUpperCase(), sciSapLabels, reltoLabels]
 			.join ' // '


Package settings typo since package name change

--- server/packages/nodemailer/index.coffee
+++ server/packages/nodemailer/index.coffee
@@ -86,7 +86,7 @@
 exports.sendMail = (type, mail) ->

 	path = config.get 'path'
-	siteEmail = config.get 'packageSettings:sendmail:siteEmail'
+	siteEmail = config.get 'packageSettings:nodemailer:siteEmail'

 	sandboxId = null


activity: fix recursive public route

activity: fix recursive public route

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -15,7 +15,7 @@
   KD.registerRoutes 'Activity',

     '/:name?/Activity/Public' : ({params: {name}}) ->
-      @handleRoute "/#{if name then name else ''}/Activity/Public",
+      @handleRoute "/#{if name then name else ''}/Activity/Public/Liked",
         replaceState: yes
         shouldPushState: no


terminal: maximize terminal if chromeapp

--- client/Terminal/AppController.coffee
+++ client/Terminal/AppController.coffee
@@ -101,6 +101,9 @@
     return  if shouldReturn

     @getView().ready =>
+      if query.chromeapp
+        query.fullscreen = yes # forcing fullscreen
+
       @getView().handleQuery query

   ringBell: do (bell = try new Audio '/a/audio/bell.wav') -> (event) ->


Some work on diplay of email content

Some work on diplay of email content

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -186,6 +186,8 @@
                         rich: prepared.rich
                         imagesWarning: imagesWarning
                         composing: @state.composing
+                        displayImages: @displayImages
+                        displayHTML: @displayHTML
                     div className: 'clearfix'
         else
             li
@@ -551,6 +553,14 @@
     addAddress: (address) ->
         ContactActionCreator.createContact address

+    displayImages: (event) ->
+        event.preventDefault()
+        @setState messageDisplayImages: true
+
+    displayHTML: (event) ->
+        event.preventDefault()
+        @setState messageDisplayHTML: true
+
 MessageContent = React.createClass
     displayName: 'MessageContent'

@@ -574,7 +584,7 @@
                                 className: 'btn btn-default',
                                 type: "button",
                                 ref: 'imagesDisplay',
-                                onClick: @displayImages,
+                                onClick: @props.displayImages,
                                 t 'message images display'
                 iframe
                     'data-message-id': @props.message.get 'id'
@@ -588,7 +598,7 @@
                 #    button
                 #       className: 'btn btn-default',
                 #       type: "button",
-                #       onClick: @displayHTML,
+                #       onClick: @props.displayHTML,
                 #       t 'message html display'
                 div className: 'preview',
                     p dangerouslySetInnerHTML: { __html: @props.rich }
@@ -603,14 +613,12 @@
         #   and resize the frame
         if @refs.content
             frame = @refs.content.getDOMNode()
+            doc = frame.contentDocument or frame.contentWindow?.document
             loadContent = (e) =>
-                step = 0
                 doc = frame.contentDocument or frame.contentWindow?.document
                 if doc?
                     doc.documentElement.innerHTML = @props.html
                     updateHeight = (e) ->
-                        e?.preventDefault()
-                        e?.stopPropagation()
                         height = doc.body.getBoundingClientRect().height
                         frame.style.height = "#{height + 60}px"
                         step++
@@ -628,27 +636,19 @@
                     # In Chrome, addEventListener is forbidden by iframe sandboxing
                     #doc.body.addEventListener 'load', updateHeight, true
                     #frame.contentWindow?.addEventListener 'resize', updateHeight, true
-
                 else
                     # try to display text only
                     @setState messageDisplayHTML: false

-            if type is 'mount'
-                try loadContent()
+            if type is 'mount' and doc.readyState isnt 'complete'
                 frame.addEventListener 'load', loadContent
             else
                 loadContent()
+        else
+            console.warn "No ref.content"

     componentDidMount: ->
         @_initFrame('mount')

     componentDidUpdate: ->
         @_initFrame('update')
-
-    displayImages: (event) ->
-        event.preventDefault()
-        @setState messageDisplayImages: true
-
-    displayHTML: (event) ->
-        event.preventDefault()
-        @setState messageDisplayHTML: true


add e200 200x200 video link

add e200 200x200 video link

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -128,7 +128,7 @@
       description_cart: "2 eye cameras. Resolution: 200x200 @200fps. IR illumination."
       weight: 31
       title_video: "Download sample video"
-      link_video: "#"
+      link_video: "https://drive.google.com/file/d/1lSjtWTm3LTHAm7b3NKZmBOBsqetQkkX-/view?usp=sharing"

 get_camera_data = ->
   camera_data = get_eye_cam_data()


Changed top from 190 to 10.

Changed top from 190 to 10.

--- reminders.widget/reminders.coffee
+++ reminders.widget/reminders.coffee
@@ -75,7 +75,7 @@
 """

 style: """
-    top: 190px
+    top: 10px
     left: 10px
     color: #fff
     background: rgba(0,0,0,0.2)


sneak in quick update for passing on tab index to breadcrumbs

--- src/components/breadcrumb/index.cjsx
+++ src/components/breadcrumb/index.cjsx
@@ -45,7 +45,7 @@
     {step, crumb, goToStep, className} = @props
     {isCorrect, isIncorrect, isCurrent, isCompleted, isEnd, crumbType} = @state

-    propsToPassOn = _.pick(@props, 'onMouseEnter', 'onMouseLeave', 'style')
+    propsToPassOn = _.pick(@props, 'onMouseEnter', 'onMouseLeave', 'style', 'tabIndex')

     if isCurrent
       title = "Current Step (#{crumbType})"


clean-up events for googleAnalytics tracking, add event.visibleMarker and fix event.maps rendering to show marker, add support for setting.allowPublicAddress, clean-up code for first test release

--- src/app/people/service.authentication.coffee
+++ src/app/people/service.authentication.coffee
@@ -27,7 +27,7 @@
         return devConfig.loginUser( person.id , true)
         .then (user)->
           $rootScope.$emit 'user:sign-in', $rootScope['user']
-          utils.ga_Send('send', 'event', 'category', 'action', 'sign-in', 10)
+          utils.ga_Send('send', 'event', 'aaa', 'sign-in', 'sign-in', 10)
           return fnComplete?(user) || user
       .catch (err)->
         $rootScope.$emit 'user:sign-out'


fix slot lv

fix slot lv

--- views/henseiItem.cjsx
+++ views/henseiItem.cjsx
@@ -29,7 +29,7 @@
             if @props.ship[3] isnt [] and @props.ship[3][index]? and @props.ship[3][index] isnt null
               lv = @props.ship[3][index]
             else
-              lv = 0
+              lv = null
             if @props.ship[4] isnt [] and @props.ship[4][index]? and @props.ship[4][index] isnt null
               alv = @props.ship[4][index]
             else


Fixed: ical export in public area was still not working

--- server/controllers/events.coffee
+++ server/controllers/events.coffee
@@ -116,7 +116,7 @@

 module.exports.ical = (req, res) ->
     key = req.query.key
-    calendar = new VCalendar organization:'Cozy Cloud', title:'Cozy Calendar'
+    calendar = new VCalendar organization:'Cozy Cloud', title: 'Cozy Calendar'
     calendar.add req.event.toIcal()
     res.header 'Content-Type': 'text/calendar'
     res.send calendar.toString()
@@ -127,7 +127,7 @@
     if not visitor = req.event.getGuest key
         return res.send error: 'invalid key', 401

-    calendar = new ical.VCalendar organization: 'Cozy', 'Cozy Calendar'
+    calendar = new VCalendar organization: 'Cozy', title: 'Cozy Calendar'
     calendar.add req.event.toIcal()
     res.header 'Content-Type': 'text/calendar'
     res.send calendar.toString()


Fix update behavior with share

Fix update behavior with share

If an event is created and is then shared with someone, the share
process is triggered (unlike before).

--- server/controllers/events.coffee
+++ server/controllers/events.coffee
@@ -98,15 +98,18 @@
     data = req.body
     data.lastModification = moment().tz('UTC').toISOString()
     req.event.updateAttributes data, (err, event) ->
-
         if err?
             res.status(500).send error: "Server error while saving event"
-        else if req.query.sendMails is 'true'
-            dateChanged = data.start isnt start
-            MailHandler.sendInvitations event, dateChanged, (err, updatedEvent) ->
-                res.send (updatedEvent or event)
-        else
-            res.send event
+        else
+            ShareHandler.sendShareInvitations event, (err, updatedEvent) ->
+                if req.query.sendMails is 'true'
+                    dateChanged = data.start isnt start
+
+                    MailHandler.sendInvitations (updatedEvent or event),
+                    dateChanged, (err, updatedEvent) ->
+                        res.send (updatedEvent or event)
+                else
+                    res.send event

 module.exports.delete = (req, res) ->


Form

Form

- Deprecate form.right: Put the right content undernith the center content.

--- src/elements/Form/Form.coffee
+++ src/elements/Form/Form.coffee
@@ -162,7 +162,7 @@
 				continue
 			@table[0].classList.add(cls+"-table")

-		td_classes = ("cui-padding cui-form-td cui-form-#{k}" for k in ["left", "center", "right"])
+		td_classes = ("cui-padding cui-form-td cui-form-#{k}" for k in ["left", "center"]) # , "right"])

 		# CUI.error "Form.renderTable", @table[0], @__horizontal, @getFields().length

@@ -177,6 +177,8 @@
 				v.DOM
 			else if $.isFunction(v)
 				getAppend(v(info))
+			else if isEmpty(v)
+				null
 			else
 				v

@@ -207,38 +209,50 @@
 			right = fopts.right

 			if right
-				console.error("Form.renderTable: form.right is deprecated. Remove this from your code.")
+				console.error("Form.renderTable: form.right is deprecated. Remove this from your code. Form:", @, "Field:", _field, "Field#", idx)
+
+			append_content = []
+			append_left = null
+			use_field_as_label = false

 			for app, idx in [fopts, _field, right]
 				attrs = {}
 				if skip_next
 					skip_next = false
 					continue
+
 				if idx == 0
 					if app.label
-						content = getAppend(app.label, _field)
+						append_left = getAppend(app.label, _field)
 					else if app.use_field_as_label
+						use_field_as_label = true
 						assert(not @__horizontal, "Form.render", "form.use_field_as_label not supported for horizontal Forms", opts: @opts)
-						content = getAppend(_field, _field)
-						attrs.colspan = 2
+						append_left = getAppend(_field, _field)
 						skip_next = true
 					else
-						content = null
+						append_left = null
 				else
 					content = getAppend(app, _field)
-
-				if idx == 0 and content
-					has_left = true
-
-				if idx == 2 and content
-					has_right = true
-
-				tds.push($td(td_classes[idx], attrs).append(content))
+					if content != null
+						append_content.push(content)
+
+			if append_left != null
+				has_left = true
+
+			if use_field_as_label
+				td = $td(td_classes[0], colspan: 2)
+				td.append(append_left)
+				for a_c in append_content
+					td.append(a_c)
+				tds.push(td)
+			else
+				tds.push($td(td_classes[0]).append(append_left))
+				tds.push($td(td_classes[1]).append(append_content))

 			if @__horizontal
 				tr_labels.append(tds[0])
 				tr_fields.append(tds[1])
-				tr_rights.append(tds[2])
+				# tr_rights.append(tds[2])
 			else
 				tr = $tr("cui-form-tr cui-form-tr-vertical").appendTo(@table)

@@ -252,10 +266,10 @@
 			else
 				@table.addClass("cui-form-table-has-not-left-column")

-			if has_right
-				@table.addClass("cui-form-table-has-right-column")
-			else
-				@table.addClass("cui-form-table-has-not-right-column")
+			# if has_right
+			# 	@table.addClass("cui-form-table-has-right-column")
+			# else
+			# 	@table.addClass("cui-form-table-has-not-right-column")

 		Events.listen


fix broken logic when passing multiple arguments to isDown

--- coffeescript/love.js/keyboard.coffee
+++ coffeescript/love.js/keyboard.coffee
@@ -25,11 +25,11 @@
     canvas.addEventListener "keyup", keyup, true

   isDown: (key, others...) =>
-    if @keysDown[key]
-      return true
+    if !@keysDown[key]
+      return false
     else
       if others.length == 0
-        return false
+        return true
       else
         return @isDown(others...)


KDModalView: disable cancel option is added

--- components/modals/KDModalView.coffee
+++ components/modals/KDModalView.coffee
@@ -19,6 +19,7 @@

     options.helpContent  or= null
     options.helpTitle    or= "Need help?"
+    options.disableCancel ?= no

     super options, data

@@ -100,7 +101,7 @@
     button.destroy()  for own _key, button of @buttons

   click:(e)->
-    @destroy() if $(e.target).is(".closeModal")
+    @cancel() if $(e.target).is(".closeModal")
     if $(e.target).is(".showHelp")
       {helpContent} = @getOptions()
       if helpContent
@@ -191,8 +192,9 @@
         @setClass "active"

   cancel:->
-    @emit 'ModalCancelled'
-    @destroy()
+    unless @getOptions().disableCancel
+      @emit 'ModalCancelled'
+      @destroy()

   destroy:->
     $(window).off "keydown.modal"


Add 'Pkg Groups', make paths relative to home

--- projects.thinke540.cson
+++ projects.thinke540.cson
@@ -3,8 +3,7 @@
     title: "Badge 2017"
     group: "HackRVA"
     paths: [
-      "/home/aflanagan/Devel/hackrva/Harmony-Badge-2017/firmware"
-      "/home/aflanagan/Devel/microchip/harmony/v1_09"
+      "~/Devel/hackrva/Harmony-Badge-2017/firmware"
     ]
     icon: "icon-circuit-board"
   }
@@ -12,7 +11,7 @@
     title: "Hacker Web"
     group: "HackRVA"
     paths: [
-      "/home/aflanagan/Devel/hackrva/hacker-web"
+      "~/Devel/hackrva/hacker-web"
     ]
     icon: "ion-social-html5"
   }
@@ -20,23 +19,15 @@
     title: "PyBadge"
     group: "HackRVA"
     paths: [
-      "/home/aflanagan/Devel/hackrva/PyBadge"
+      "~/Devel/hackrva/PyBadge"
     ]
     icon: "icon-circuit-board"
-  }
-  {
-    title: "LF Word Count"
-    group: "Atom"
-    paths: [
-      "/home/aflanagan/Devel/atom/lloyd-flanagan-word-count"
-    ]
-    icon: "atom-icon"
   }
   {
     title: "Atom Configuration"
     group: "Atom"
     paths: [
-      "/home/aflanagan/Devel/atom/atom_configuration"
+      "~/Devel/atom/atom_configuration"
     ]
     icon: "atom-icon"
   }
@@ -44,7 +35,7 @@
     title: "Atom Utl"
     group: "Atom"
     paths: [
-      "/home/aflanagan/Devel/atom/atom-language-utl"
+      "~/Devel/atom/atom-language-utl"
     ]
     icon: "atom-icon"
   }
@@ -52,7 +43,7 @@
     title: "Language Mips"
     group: "Atom"
     paths: [
-      "/home/aflanagan/Devel/atom/language-mips"
+      "~/Devel/atom/language-mips"
     ]
     icon: "atom-icon"
   }
@@ -60,7 +51,7 @@
     title: "My Website"
     group: "Web"
     paths: [
-      "/home/aflanagan/Devel/adrian-l-flanagan"
+      "~/Devel/adrian-l-flanagan"
     ]
     icon: "fa-html5"
   }
@@ -68,7 +59,7 @@
     title: "Css Compare"
     group: "Python"
     paths: [
-      "/home/aflanagan/Devel/css_compare"
+      "~/Devel/css_compare"
     ]
     icon: "devicon-css3-plain"
   }
@@ -76,7 +67,7 @@
     title: "Node Find"
     group: "Node"
     paths: [
-      "/home/aflanagan/Devel/node/node_find"
+      "~/Devel/node/node_find"
     ]
     icon: "icon-search"
   }
@@ -84,7 +75,7 @@
     title: "Bookmark Server"
     group: "Web"
     paths: [
-      "/home/aflanagan/Devel/node/bkmk_server"
+      "~/Devel/node/bkmk_server"
     ]
     icon: "fa-bookmark"
   }
@@ -92,7 +83,7 @@
     title: "Utl Indexer"
     group: "BHMG"
     paths: [
-      "/home/aflanagan/Devel/utl_indexer"
+      "~/Devel/utl_indexer"
     ]
     icon: "fa-server"
   }
@@ -100,7 +91,7 @@
     title: "Gmail Intfc"
     group: "Mail"
     paths: [
-      "/home/aflanagan/Devel/gmail/mail-electron-intfc"
+      "~/Devel/gmail/mail-electron-intfc"
     ]
     icon: "icon-mail-read"
   }
@@ -108,17 +99,17 @@
     title: "Gmail Web Svcs"
     group: "Mail"
     paths: [
-      "/home/aflanagan/Devel/gmail/mail-web-srvcs"
+      "~/Devel/gmail/mail-web-srvcs"
     ]
     icon: "icon-mail-read"
   }
   {
-    title: "Active Editor Info"
+    title: "Pkg Groups"
     group: "Atom"
     paths: [
-      "/home/aflanagan/Devel/atom/active-editor-info"
+      "~/Devel/atom/pkg-groups"
+      "~/Devel/atom/etch"
     ]
     icon: "atom-icon"
-    devMode: true
   }
 ]


test data updating

test data updating

--- spec/lanes/react/mixins/DataSpec.coffee
+++ spec/lanes/react/mixins/DataSpec.coffee
@@ -1,21 +1,21 @@
 describe "Lanes.React.DataMixin", ->

-    it "fires setState for a model", ->
+    it "fires forceUpdate for a model", ->
         model = Lanes.Test.makeModel
             props:
                 id: 'integer'
                 foo: 'string'
         comp = Lanes.Test.makeComponent({}, model: model)
-        spyOn(comp, 'setState')
+        spyOn(comp, 'forceUpdate')
         model.foo = "bar"
-        expect(comp.setState).toHaveBeenCalledWith( model: model )
+        expect(comp.forceUpdate).toHaveBeenCalled()

-    it "fires setState for a collection", ->
+    it "fires forceUpdate for a collection", ->
         collection = Lanes.Test.makeCollection()
         comp = Lanes.Test.makeComponent({}, collection: collection)
-        spyOn(comp, 'setState')
+        spyOn(comp, 'forceUpdate')
         collection.add([{}])
-        expect(comp.setState).toHaveBeenCalledWith( collection: collection )
+        expect(comp.forceUpdate).toHaveBeenCalled()

     it "obeys global pubsub settings", ->
         spyOn(Lanes.Models.PubSub, 'add').and.callThrough()
@@ -39,17 +39,18 @@
             props:
                 foo: 'string'
                 bar: 'string'
+
         comp = Lanes.Test.makeComponent({
-            bindDataEvents:
+            bindEvents:
                 model: 'change:bar'
         }, {
             model: model
         })
-        spyOn(comp, 'setState')
+        spyOn(comp, 'forceUpdate')
         model.foo = "bar"
-        expect(comp.setState).not.toHaveBeenCalled()
+        expect(comp.forceUpdate).not.toHaveBeenCalled()
         model.bar = "foo"
-        expect(comp.setState).toHaveBeenCalledWith( model: model )
+        expect(comp.forceUpdate).toHaveBeenCalled()

     it "can rebind events", ->
         Model = Lanes.Test.defineModel
@@ -60,13 +61,14 @@
         comp = Lanes.Test.makeComponent({}, {
             model: m1
         })
-        spyOn(comp, 'setState')
+        spyOn(comp, 'forceUpdate')
         m1.foo = "bar"
-        expect(comp.setState).toHaveBeenCalled()
+        expect(comp.forceUpdate).toHaveBeenCalled()

-        comp.data.rebind(model: m2)
-        comp.setState.calls.reset()
+        comp.modelBindings.reset(model: m2)
+        comp.forceUpdate.calls.reset()
+
         m1.foo = "a different bar"
-        expect(comp.setState).not.toHaveBeenCalled()
+        expect(comp.forceUpdate).not.toHaveBeenCalled()
         m2.foo = "bar"
-        expect(comp.setState).toHaveBeenCalled()
+        expect(comp.forceUpdate).toHaveBeenCalled()


:boar: Update jshint task

:boar: Update jshint task

--- grunt/jshint.coffee
+++ grunt/jshint.coffee
@@ -1,3 +1,3 @@
 module.exports = (grunt) ->
-  files: ['gruntfile.js', 'src/**/*.js'],
+  files: ['gruntfile.js', 'src/**/*.js', 'src/bezier-easing.js'],
   options: grunt.file.readJSON '.jshintrc'


Find referenced classes in .qui files.

Find referenced classes in .qui files.

--- grunt/quidoc.coffee
+++ grunt/quidoc.coffee
@@ -61,7 +61,10 @@
   # setters invoked on the base class.
   content: ( source ) ->
     match = @regexes.content.exec source
-    match?[1]
+    # Return the first non-empty form of content which matched
+    for i in [1..match.length]
+      if match?[i]
+        return match?[i]

   # Return the name for the control class defined in the source text.
   controlClassName: ( source ) ->
@@ -98,8 +101,8 @@

   # Return the array of classes both implicitly and explicitly required.
   requiresClasses: ( source ) ->
-    implicit = @implicitRequiredClasses source
-    explicit = @explicitRequiredClasses source
+    implicit = ( @implicitRequiredClasses source ) ? []
+    explicit = ( @explicitRequiredClasses source ) ? []
     requiresClasses = implicit.concat explicit
     unique = @_unique requiresClasses
     unique.sort()
@@ -143,20 +146,6 @@
       [a-zA-Z0-9\$][\w]+      # JavaScript identifier
     )
   ///
-  comments: ///
-    (                         # First group captures the comment
-      (?:                     # Non-capturing group for each comment line
-        \r?\n\x20\x20# .*     # A comment line indented two spaces
-      )
-      +                       # Any non-zero number of comment lines
-    )
-    \r?\n\x20\x20             # Identifier line indented two spaces
-    (                         # Second group captures the identifier
-      [a-zA-Z0-9\$][\w]+      # JavaScript member identifier
-    )
-    :                         # Colon terminates identifier
-  ///g
-  commentText: /^\s*#[ ]?(.*)/gm
   content: ///
     \r?\n                     # Start of a line
     \s\s                      # Required indent of two spaces
@@ -170,6 +159,20 @@
       )+
     )
   ///
+  comments: ///
+    (                         # First group captures the comment
+      (?:                     # Non-capturing group for each comment line
+        \r?\n\x20\x20# .*     # A comment line indented two spaces
+      )
+      +                       # Any non-zero number of comment lines
+    )
+    \r?\n\x20\x20             # Identifier line indented two spaces
+    (                         # Second group captures the identifier
+      [a-zA-Z0-9\$][\w]+      # JavaScript member identifier
+    )
+    :                         # Colon terminates identifier
+  ///g
+  commentText: /^\s*#[ ]?(.*)/gm
   referencedClasses: ///
     \r?\n                     # Start of a line
     \s+                       # Whitespace
@@ -196,6 +199,12 @@

 # Extracts docs from QuickUI markup controls
 markupDocsExtractor = new DocsExtractor
+  baseClassName: ///
+    <prototype>               # Opening prototype tag
+    \s*                       # Optional whitespace
+    <                         # Open tag for base class instance
+    (\w+)                     # Group captures base class name
+  ///
   className: ///
     <script>                  # Opening script tag
     \s*                       # Whitespace
@@ -230,6 +239,44 @@
     :                         # Colon terminates identifier
   ///g
   commentText: /^\s*\*[ ]?(.*)/gm
+  content: ///
+    (?:                       # Non-capturing group for first part of OR
+      <prototype>             # Opening prototype tag
+      \s*                     # Optional whitespace
+      <[^>]+>                 # Opening base class instance tag
+      (                       # Group captures content
+        [\s\S]*               # Anything up to...
+      )
+      </prototype>            # Closing prototype tag
+    )
+    |                         # OR
+    (?:                       # Non-capturing group for second part of OR
+      <content>               # Opening content tag
+      (                       # Group captures content
+        [\s\S]*               # Anything up to...
+      )
+      </content>              # Closing content tag
+    )
+  ///
+  referencedClasses: ///
+    <                         # Start tag
+    (                         # Group captures referenced class name
+      [A-Z]                   # Must start with an uppercase letter
+      \w*                     # More letters
+    )
+    .*                        # Optional tag parameters
+    >                         # End tag
+  ///g
+  requiresClasses: ///
+    _requiresClasses          # Expected identifer "_requiresClasses"
+    :                         # Colon
+    \s+                       # Whitespace
+    (                         # Group captures array of required classes
+      \[                      # Opening bracket
+        [^\]]+                # Array contents -- everything but a closing bracket
+      \]                      # Closing bracket
+    )
+  ///

 extractors =


Hide bpm on Beatmaps that have silly BPM values (#4819)

Hide bpm on Beatmaps that have silly BPM values

--- resources/assets/coffee/react/_components/beatmap-basic-stats.coffee
+++ resources/assets/coffee/react/_components/beatmap-basic-stats.coffee
@@ -44,7 +44,9 @@
           beatmap[stat]

       value =
-        if stat == 'total_length'
+        if stat == 'bpm'
+          if value > 1000 then '—' else osu.formatNumber(value)
+        else if stat == 'total_length'
           formatDuration value
         else
           osu.formatNumber(value)


Find file path and act on it one after the other to avoid race condition

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -152,13 +152,22 @@

 	replaceFile: (project_or_id, file_id, fsPath, callback)->
 		Project.getProject project_or_id, "", (err, project) ->
+			return callback(err) if err?
 			findOpts =
 				project_id:project._id
 				element_id:file_id
 				type:"file"
-			projectLocator.findElement findOpts, (err, fileRef, path)=>
-				FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, fsPath, (err)->
+			FileStoreHandler.uploadFileFromDisk project._id, file_id, fsPath, (err)->
+				return callback(err) if err?
+				# Note there is a potential race condition here (and elsewhere)
+				# If the file tree changes between findElement and the Project.update
+				# then the path to the file element will be out of date. In practice
+				# this is not a problem so long as we do not do anything longer running
+				# between them (like waiting for the file to upload.)
+				projectLocator.findElement findOpts, (err, fileRef, path)=>
+					return callback(err) if err?
 					tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, "sl_req_id_here", (error) ->
+						return callback(err) if err?
 						conditons = _id:project._id
 						inc = {}
 						inc["#{path.mongo}.rev"] = 1


Validation: getValidator now supports one level objects to return method.

--- src/lib/validation/validation.coffee
+++ src/lib/validation/validation.coffee
@@ -212,11 +212,17 @@
 ###*
   Returns validator function by type.

-  @param {string} type Validation type. See `spark.validation.TYPE_TO_METHOD`
-  @return {Function|null}
-###
-spark.validation.getValidator = (type) ->
-  return spark.validation.TYPE_TO_METHOD[type] or null
+  @param {string} type  Validation type. See `spark.validation.TYPE_TO_METHOD`
+  @param {string} value Value to select correct validator.
+  @return {Function|null} Validator function
+###
+spark.validation.getValidator = (type, value) ->
+  method = spark.validation.TYPE_TO_METHOD[type]
+
+  if spark.validation.isObject method
+    method = method[value]
+
+  return method or null

 spark.validation.EMAIL_REGEX = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/


Add pattern-matching rules for numeric literals

Rules "borrowed" from a package I wrote late last year:
https://github.com/Alhadis/language-maxscript

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -5,6 +5,8 @@
 patterns: [

 	{ include: "#strings" }
+	{ include: "#float"   }
+	{ include: "#int"     }

 	# Comments
 	{
@@ -23,34 +25,70 @@
 	}
 ]

+
 repository:
+
+	# Floating-point numbers
+	float: {
+		patterns: [{
+			name:  "constant.numeric.float.apl"
+			match: '(?<!\\$)\\b(\\d+)(\\.)(\\d+([Ee]([\\+\\-])?\\d+)?)\\b'
+			captures:
+				1: {name: "leading.decimal"}
+				2: {name: "decimal.separator"}
+				3: {name: "trailing.decimal"}
+				4: {name: "exponential.decimal"}
+		}, {
+			name:  "constant.numeric.float.no-trailing-digits.apl"
+			match: '\\b(\\d+)(\\.)(?!\\w)'
+			captures:
+				1: {name: "leading.decimal"}
+				2: {name: "decimal.separator"}
+		}, {
+			name:  "constant.numeric.float.no-leading-digits.apl"
+			match: '(?<!\\w)(\\.)(\\d+([Ee]([\\+\\-])?\\d+)?)\\b'
+			captures: {
+				1: {name: "decimal.separator"}
+				2: {name: "trailing.decimal"}
+				3: {name: "exponential.decimal"}
+			}
+		}]
+	}
+
+
+	# Integers
+	int: {
+		patterns: [{
+			name:  "constant.numeric.int.apl"
+			match: '(?<!\\$)\\b\\d+\\b'
+		}]
+	}
+
+
+	# String literals
 	strings:
-		patterns: [
-			{
-				name: "string.quoted.single.apl"
-				begin: "'"
-				end:   "'|$"
-				beginCaptures:
-					0: {name: "punctuation.definition.string.begin.apl"}
-				endCaptures:
-					0: {name: "punctuation.definition.string.end.apl"}
-				patterns: [{
-					match: "[^']*[^'\\n\\r\\\\]$"
-					name: "invalid.illegal.string.apl"
-				}]
-			}
-
-			{
-				name: "string.quoted.double.apl",
-				begin: '"'
-				end:   '"|$'
-				beginCaptures:
-					0: {name: "punctuation.definition.string.begin.apl"}
-				endCaptures:
-					0: {name: "punctuation.definition.string.end.apl"}
-				patterns: [{
-					match: '[^"]*[^"\\n\\r\\\\]$'
-					name: "invalid.illegal.string.apl"
-				}]
-			}
-		]
+		patterns: [{
+			name: "string.quoted.single.apl"
+			begin: "'"
+			end:   "'|$"
+			beginCaptures:
+				0: {name: "punctuation.definition.string.begin.apl"}
+			endCaptures:
+				0: {name: "punctuation.definition.string.end.apl"}
+			patterns: [{
+				match: "[^']*[^'\\n\\r\\\\]$"
+				name: "invalid.illegal.string.apl"
+			}]
+		}, {
+			name: "string.quoted.double.apl",
+			begin: '"'
+			end:   '"|$'
+			beginCaptures:
+				0: {name: "punctuation.definition.string.begin.apl"}
+			endCaptures:
+				0: {name: "punctuation.definition.string.end.apl"}
+			patterns: [{
+				match: '[^"]*[^"\\n\\r\\\\]$'
+				name: "invalid.illegal.string.apl"
+			}]
+		}]


increase sign-in timeout a bit

increase sign-in timeout a bit

--- salvus/page/account.coffee
+++ salvus/page/account.coffee
@@ -357,6 +357,7 @@
         email_address : $("#sign_in-email").val().trim()
         password      : $("#sign_in-password").val().trim()
         remember_me   : true
+        timeout       : 30
         cb            : (error, mesg) ->
             $("#sign_in-button").icon_spin()
             if error