Moderation: Applied Synonym Creation to tags

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -73,6 +73,8 @@
           (signature Function)
         fetchRandomFollowers:
           (signature Object, Function)
+        createSynonym:
+          (signature Object, Function)
       static        :
         one: [
           (signature Object, Function)
@@ -137,6 +139,10 @@
       meta          : require 'bongo/bundles/meta'
       synonyms      : [String]
       group         : String
+      type          :
+        type        : String
+        enum        : ['invalid type', ['deleted', 'synonym','complete']]
+        default     : 'complete'
       # owner         : ObjectId
     relationships   :->
       JAccount = require './account'
@@ -148,6 +154,9 @@
       follower      :
         targetType  : JAccount
         as          : 'follower'
+      synonymOf   :
+        targetType  : JTag
+        as          : "synonymOf"
       content       :
         targetType  : [
           "JNewStatusUpdate"
@@ -193,6 +202,7 @@

     selector or= {}
     selector['data.flags.isLowQuality'] = $ne: yes
+    selector['type'] = $ne: "deleted"

     @fetchContents selector, options, (err, contents)->
       if err then callback err
@@ -243,26 +253,52 @@
               callbackForEach null, tag for tag in existingTags
       ]

+  create = (data, creator, callback) ->
+    tag = new JTag data
+    tag.createSlug (err, slug)->
+      if err then callback err
+      else
+        tag.slug = slug.slug
+        tag.slug_ = slug.slug
+        tag.save (err)->
+          if err
+            callback err
+          else
+            tag.addCreator creator, (err)->
+              if err
+                callback err
+              else
+                callback null, tag
+
   @create = permit 'create tags',
     success: (client, data, callback)->
       {delegate} = client.connection
+      data.group = client.context
+      create data, delegate, callback
+
+  createSynonym : permit ['create tags', 'read tags'],
+    success: (client, title, callback)->
+      addSynonym = (tag) =>
+        @addSynonymOf tag, (err) =>
+          return callback err if err
+          @update $set: type :'synonym', (err) =>
+            return callback err if err
+            @constructor.emit 'TagIsSynonym', {tagId:@getId()}
+            callback null
+
+      if (@type is 'deleted' or @type is 'synonym')
+        return callback new KodingError "Topic is already set as #{@type}!"
+
+      {delegate} = client.connection
       {group} = client.context
-      tag = new this data
-      tag.group = group
-      tag.createSlug (err, slug)->
-        if err then callback err
+      JTag.one {title}, (err, tag) =>
+        return callback err if err
+        unless tag
+          create {title, group}, delegate, (err, tag) =>
+            return callback err if err
+            addSynonym tag
         else
-          tag.slug = slug.slug
-          tag.slug_ = slug.slug
-          tag.save (err)->
-            if err
-              callback err
-            else
-              tag.addCreator delegate, (err)->
-                if err
-                  callback err
-                else
-                  callback null, tag
+          addSynonym tag

   @findSuggestions = (client, seed, options, callback)->
     {limit, blacklist, skip} = options


Proxy selection methods

Proxy selection methods

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -127,7 +127,7 @@
       if character?
         @delegate?.inputControllerWillPerformTyping()
         @responder?.insertString(character)
-        @setInputSummary(textAdded: character, didDelete: @responder?.selectionIsExpanded())
+        @setInputSummary(textAdded: character, didDelete: @selectionIsExpanded())

     dragenter: (event) ->
       event.preventDefault()
@@ -198,7 +198,7 @@
       if (composedString = event.data)?
         @delegate?.inputControllerWillPerformTyping()
         @responder?.insertString(composedString)
-        @setInputSummary(textAdded: composedString, didDelete: @responder?.selectionIsExpanded())
+        @setInputSummary(textAdded: composedString, didDelete: @selectionIsExpanded())
       @mutationObserver.start()

   keys:
@@ -252,22 +252,23 @@
       left: (event) ->
         if @selectionIsInCursorTarget()
           event.preventDefault()
-          @responder?.expandSelectionInDirection("backward")
+          @expandSelectionInDirection("backward")

       right: (event) ->
         if @selectionIsInCursorTarget()
           event.preventDefault()
-          @responder?.expandSelectionInDirection("forward")
+          @expandSelectionInDirection("forward")

   deleteInDirection: (direction) ->
     @responder?.deleteInDirection(direction)
     @setInputSummary(didDelete: true)

-  selectionIsInCursorTarget: ->
-    @responder?.selectionIsInCursorTarget()
-
-  extensionForFile = (file) ->
-    file.type?.match(/\/(\w+)$/)?[1]
+  @proxyMethod "responder?.expandSelectionInDirection"
+  @proxyMethod "responder?.selectionIsInCursorTarget"
+  @proxyMethod "responder?.selectionIsExpanded"
+
+extensionForFile = (file) ->
+  file.type?.match(/\/(\w+)$/)?[1]

 keyEventIsWebInspectorShortcut = (event) ->
   event.metaKey and event.altKey and not event.shiftKey and event.keyCode is 94


svg generation tweaks

svg generation tweaks

--- Skuler.coffee
+++ Skuler.coffee
@@ -274,14 +274,24 @@

 		@ # do not accumulate and return loop results (just return this)

+
 	getSVG: ->
-		lines = for strokeInfo in @strokes
+		group = lastGroup = null;
+		lines = ""
+		for strokeInfo in @strokes
 			[index, saturation, lightness, stroke] = strokeInfo
+
 			color = new SkulerColor @getColorAt(index).base, saturation, lightness
-
 			hex = Utils.toCSSHex color.calculated
-			points = stroke.join(",")
-			'<polyline stroke="'+hex+'" points="'+points+'" />\n'
+			group = '\t\t<g data-index="'+index+'" data-s="'+saturation+'" data-l="'+lightness+'" stroke="'+hex+'" >\n'
+
+			if group isnt lastGroup
+				lines += '\t\t</g>\n' if lastGroup?
+				lines += group
+				lastGroup = group
+			lines += '\t\t\t<polyline points="'+stroke.join(",")+'" />\n'
+
+		lines += '\t\t</g>\n' if lastGroup?

 		canv = @context.canvas
 		w = canv.width
@@ -290,9 +300,10 @@
 		'<?xml version="1.0"?>\n' +
 			'<svg width="'+w+'" height="'+h+'" viewPort="0 0 '+w+' '+h+'" ' +
 			'version="1.1" xmlns="http://www.w3.org/2000/svg">\n' +
-			'<g fill="none" stroke-linejoin="round" stroke-linecap="round" stroke-width="'+@context.lineWidth+'">\n' +
+			'\t<g fill="none" stroke-linejoin="round" stroke-linecap="round" stroke-width="'+@context.lineWidth+'">\n' +
 			lines +
-			'</g>\n</svg>'
+			'\t</g>\n</svg>'
+

 class SkulerSwatch


Extend and defaults are totally not the same thing.

--- test/javascripts/support/factories.js.coffee
+++ test/javascripts/support/factories.js.coffee
@@ -29,7 +29,7 @@

     baseAttrs = ETahi.FactoryAttributes[type]
     throw "No factory exists for type: #{type}" unless baseAttrs
-    _.extend(baseAttrs, recordAttrs)
+    _.defaults(recordAttrs, baseAttrs)

   setForeignKey: (model, sourceModel, options={}) ->
     keyName = options.keyName || sourceModel._rootKey


Fix cookie deleting by adding domain and past date

--- src/script/expiration/session-expiration.coffee
+++ src/script/expiration/session-expiration.coffee
@@ -24,7 +24,7 @@
   DEBUG "Totem time expired. Idle for", idleTime
   vtex.portal.stopExpiration()
   # Clean order form cookie
-  document.cookie = "checkout.vtex.com" + '=; expires=' + Date.now() + '; path=/';
+  document.cookie = "checkout.vtex.com=;expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=.#{window.location.hostname}; path=/;";
   DEBUG "Cleaned order form cookie", document.cookie.split(";")
 # Send to URL
   if url


Adjusted script to react to some more keywords.

--- src/scripts/ferengi_rules_of_acquisition.coffee
+++ src/scripts/ferengi_rules_of_acquisition.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Display a ferengi rule of acquisition.
+#   Display a ferengi rule of acquisition upon certain keywords.
 #
 # Dependencies:
 #   None
@@ -263,6 +263,10 @@
 ]

 module.exports = (robot) ->
-  robot.hear /.*(ferengi rule).*/i, (msg) ->
+
+  words = ["ferengi rule", "money", "profit", "sale", "discount", "opportunity"]
+  regex = new RegExp('(?:^|\\s)(' + words.join('|') + ')(?:\\s|\\.|\\?|!|$)', 'i');
+
+  robot.hear regex, (msg) ->
     msg.send msg.random rules


Don't show the modal when the link is clicked

--- client/admin/lib/views/stacks/credentialstatusview.coffee
+++ client/admin/lib/views/stacks/credentialstatusview.coffee
@@ -42,21 +42,6 @@
     @stalledView.addSubView @link = new kd.CustomHTMLView
       cssClass   : 'link'
       partial    : 'Credentials are not set'
-      click      : =>
-
-        modal = new CredentialSelectorModal
-          selectedCredentials: @credentials
-
-        modal.on 'ItemSelected', (credential) =>
-
-          # After adding credential, we are sharing it with the current
-          # group, so anyone in this group can use this credential ~ GG
-          {slug} = kd.singletons.groupsController.getCurrentGroup()
-          credential.shareWith {target: slug}, (err) =>
-            console.warn 'Failed to share credential:', err  if err
-            @setCredential credential
-            modal.destroy()
-

     creds = Object.keys @credentials


add comment describing bug

add comment describing bug

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -62,19 +62,23 @@
       yLower: y + DEFAULT_HEIGHT/2

   getDeleteButtonPosition: ->
+    # console.log 'getDeleteButtonPositio()'
     x: 100
     y: (@props.mark.yLower-@props.mark.yUpper)/2

   getUpperHandlePosition: ->
-    console.log 'REF: ', @props
+    # console.log 'getUpperHandlePosition()'
     x: @props.sizeRect?.props.width/2
     y: @props.mark.yUpper - @props.mark.y

   getLowerHandlePosition: ->
+    # console.log 'getLowerHandlePosition()'
     x: @props.sizeRect?.props.width/2
     y: @props.mark.yLower - @props.mark.y

   getMarkButtonPosition: ->
+    # console.log 'getMarkButtonPosition()'
+    # NOTE: this somehow doesn't receive props in the first couple renders and produces an error --STI
     x: @props.sizeRect?.props.width - 100
     y: (@props.mark.yLower-@props.mark.yUpper)/2

@@ -172,7 +176,6 @@
     </g>

   handleDrag: (e, d) ->
-    console.log 'handleDrag: ', 'disabled: ', @props.disabled, ' locked: ', @state.locked
     return if @state.locked
     return if @props.disabled
     @props.mark.y += d.y / @props.yScale


Set maximum avatars to be displayed on 5.

If there are more then 5 the last avatar will be replaced by a
icon that displays +2 or more

--- core/app/backbone/views/facts/react_fact_vote_area.coffee
+++ core/app/backbone/views/facts/react_fact_vote_area.coffee
@@ -12,12 +12,29 @@
     # react.backbone.js doesn't listen to changing models
     @model().on 'change', => @forceUpdate()

+  _opinionators: ->
+    @model()
+      .filter( (vote) => vote.get('type') == @props.type)
+
   render: ->
+    number_of_places = 5
+
+    if @_opinionators().length <= number_of_places
+      take = number_of_places
+      show_plus = false
+    else
+      take = number_of_places - 1
+      show_plus = true
+
     _div ["fact-vote-people-#{@props.type}"],
-      @model()
-        .filter( (vote) => vote.get('type') == @props.type)
+      @_opinionators()
+        .slice(0,take)
         .map (vote) ->
           ReactOpinionatorsAvatar(model: vote.user())
+
+      if show_plus
+          _span ["more-people"],
+            "+" + (@_opinionators().length - number_of_places + 1)

 FactVoteButton = React.createBackboneClass


Refactor coffee

Refactor coffee

--- app/assets/javascripts/meyasubako.coffee
+++ app/assets/javascripts/meyasubako.coffee
@@ -5,18 +5,26 @@

   setToggleEvent: ->
     $('.meyasubako__heading').on 'click', ->
-      Meyasubako.toggle()
+      container = $('.meyasubako__container')
+
+      # Switch form visibility
+      if container.is(':visible') then container.hide() else container.show()

   setSubmitEvent: ->
     $('.meyasubako__submit').on 'click', ->
+      # Return if textarea is empty
       return false unless $('.meyasubako__content').val()

+      # Disable form
       $('.meyasubako__content').attr('disabled', 'disabled')
       $('.meyasubako__submit').attr('disabled', 'disabled')

+      # Show sending message
       $('.meyasubako__message__container').fadeIn()

-      interval = setInterval ->
+      # Blink message
+      interval =
+        setInterval ->
           $('.meyasubako__message--sending').fadeOut 500, ->
             $(this).fadeIn 500
         , 1000
@@ -29,15 +37,14 @@
           content:            $('.meyasubako__content').val()

         success: ->
+          # Clear animation
           $('.meyasubako__message--sending').stop()
+
+          # Stop blinking
           clearInterval(interval)

+          # Show success message
           $('.meyasubako__message--sending').hide()
           $('.meyasubako__message--success').fadeIn()

-  @toggle: ->
-    container = $('.meyasubako__container')
-
-    if container.is(':visible') then container.hide() else container.show()
-
 window.Meyasubako = Meyasubako


Reset mask height on close

Reset mask height on close

Otherwise it will never shrink after opening long content

--- app/assets/javascripts/overlay.js.coffee
+++ app/assets/javascripts/overlay.js.coffee
@@ -29,6 +29,7 @@
     @dom.el.css('left', @dimensions.margin())
     @dom.el.css('top', @dimensions.top())
     @dom.el.width(@dimensions.width())
+    @dom.mask.height('100%')
     @dom.closeButton().css('left', @dimensions.closeButtonLeftPosition())

   show: ->


TT-21 check referrer and decide whether to close the tab (works only for chrome) or to go back in history

--- app/assets/javascripts/time_entry.coffee
+++ app/assets/javascripts/time_entry.coffee
@@ -44,9 +44,15 @@
     .done( ->

       setTimeout(
-        -> window.history.back()
+        ->
+          if isUserComingFromGithub()
+            # hacky hack hack
+            open(location, '_self').close();
+          else
+            window.history.back()
         1000
       )
+
       $alert.text("Yeah! Your time got logged.")
       $alert.addClass("alert-success in")
       # dismissAlert()
@@ -59,6 +65,13 @@
       console.error("There was an error submitting the entry:", error)
       # dismissAlert()

+  isUserComingFromGithub = ->
+
+    # unfortunately, the referrer never seems to be set when the user comes from github
+    # so, we just check if timer.scm.io is in the referrer
+    # don't close on localhost (for development)
+    return document.referrer.indexOf("timer.scm.io") == -1 and document.location.toString().indexOf("localhost") == -1
+
   dismissAlert = ->

     window.setTimeout (->


rearrange choropleth directive code

rearrange choropleth directive code

--- app/scripts/directives/choropleth.coffee
+++ app/scripts/directives/choropleth.coffee
@@ -8,6 +8,35 @@
     mappingPropertyOnData: 'id'
     valueProperty: 'value'

+  #    #############################################
+  #      Render Entry Point
+  #    #############################################
+
+  renderVisualization = (scope) ->
+    map = scope.map
+    data = scope.data
+    path = deducePathProjection(scope.projection)
+    valueProperty = scope.valueProperty
+    mappingPropertyOnMap = scope.mappingPropertyOnMap
+    mappingPropertyOnData = scope.mappingPropertyOnData
+    quantizeSteps = scope.quantizeSteps || defaults.quantizeSteps
+
+    renderMap(scope, map, path)
+
+    bounds =  path.bounds(map)
+    resizeMap(scope.svg, bounds)
+
+    if data
+      allMappingPropertiesOnMap = deduceAllAvailableMappingOnMap(map, mappingPropertyOnMap)
+      valFn = deduceValueFunction(data, valueProperty, quantizeSteps, allMappingPropertiesOnMap, mappingPropertyOnData, scope.mapId)
+      renderData(scope, data, mappingPropertyOnData, valueProperty, mappingPropertyOnMap, valFn)
+      renderLegend(scope, valFn)
+
+
+  #    #############################################
+  #      Map Rendering
+  #    #############################################
+
   # remove all paths from the svg
   # this is needed since d3 identifies by array index which is overlapping
   # across maps
@@ -23,6 +52,30 @@
     mapPaths.enter().append('path')
       .attr('d', path)

+
+  # sets the viewBox attribute on the svg element to cover the whole map
+  # dynamically adjusts the snippet height to the ratio to the viewBox
+  resizeMap = (svg, bounds) ->
+    svgHeight = bounds[1][1] - bounds[0][1]
+    svgWidth = bounds[1][0] - bounds[0][0]
+    svg.attr('viewBox', "#{bounds[0][0]} #{bounds[0][1]} #{svgWidth} #{svgHeight}")
+    ratio = $(svg.node()).width() / svgWidth
+    svg.attr('stroke-width', (1 / ratio) )
+    svg.attr('height', ratio * svgHeight)
+
+
+  deducePathProjection = (projection) ->
+    if projection
+      path = d3.geo.path().projection(eval("d3.geo.#{projection}()"))
+    else
+      path = d3.geo.path().projection(eval("d3.geo.#{defaults.projection}()"))
+
+    return path
+
+
+  #    #############################################
+  #      Data Visualization
+  #    #############################################

   renderData = (scope, data, mappingPropertyOnData, valueProperty, mappingPropertyOnMap, valFn) ->
     return if typeof data != 'object'
@@ -56,26 +109,6 @@
         mapInstance.dataPointsWithMissingRegion.push(entry)

-  # sets the viewBox attribute on the svg element to cover the whole map
-  # dynamically adjusts the snippet height to the ratio to the viewBox
-  resizeMap = (svg, bounds) ->
-    svgHeight = bounds[1][1] - bounds[0][1]
-    svgWidth = bounds[1][0] - bounds[0][0]
-    svg.attr('viewBox', "#{bounds[0][0]} #{bounds[0][1]} #{svgWidth} #{svgHeight}")
-    ratio = $(svg.node()).width() / svgWidth
-    svg.attr('stroke-width', (1 / ratio) )
-    svg.attr('height', ratio * svgHeight)
-
-
-  deducePathProjection = (projection) ->
-    if projection
-      path = d3.geo.path().projection(eval("d3.geo.#{projection}()"))
-    else
-      path = d3.geo.path().projection(eval("d3.geo.#{defaults.projection}()"))
-
-    return path
-
-
   deduceMinValue = (data, valueProperty, allMappingPropertiesOnMap, mappingPropertyOnData) ->
     minValue = d3.min data, (d) ->
       propertyOnData = d[mappingPropertyOnData] || d[defaults.mappingPropertyOnData]
@@ -90,7 +123,7 @@
         +d[valueProperty] || +d[defaults.valueProperty]

-  # for now fixed to quantize
+  # either quantize or ordinal depending on type of data
   deduceValueFunction = (data, valueProperty, quantizeSteps, allMappingPropertiesOnMap, mappingPropertyOnData, mapId) ->
     mapInstance = mapMediatorService.getUIModel(mapId)
     if mapInstance.getValueType() == 'categorical'
@@ -112,39 +145,6 @@
     return valFn

-  # gets all mapped property values that are actually on the map
-  # Either gets all from the set mappingProperty or gets all from the default
-  # no mixing of default and set property!
-  deduceAllAvailableMappingOnMap = (map, mappingPropertyOnMap) ->
-    map.features.map (mapEntry) ->
-      if mappingPropertyOnMap
-        mapEntry.properties[mappingPropertyOnMap]
-      else
-        mapEntry.properties[defaults.mappingPropertyOnMap]
-
-
-  renderVisualization = (scope) ->
-    map = scope.map
-    data = scope.data
-    path = deducePathProjection(scope.projection)
-    valueProperty = scope.valueProperty
-    mappingPropertyOnMap = scope.mappingPropertyOnMap
-    mappingPropertyOnData = scope.mappingPropertyOnData
-    quantizeSteps = scope.quantizeSteps || defaults.quantizeSteps
-
-    renderMap(scope, map, path)
-
-    bounds =  path.bounds(map)
-    resizeMap(scope.svg, bounds)
-
-    if data
-      allMappingPropertiesOnMap = deduceAllAvailableMappingOnMap(map, mappingPropertyOnMap)
-      valFn = deduceValueFunction(data, valueProperty, quantizeSteps, allMappingPropertiesOnMap, mappingPropertyOnData, scope.mapId)
-      renderData(scope, data, mappingPropertyOnData, valueProperty, mappingPropertyOnMap, valFn)
-      renderLegend(scope, valFn)
-
-
-
   #    #############################################
   #      Legend Rendering
   #    #############################################
@@ -191,6 +191,7 @@
         .attr('class', (d) -> classFn(d) )

+  # Filter out categorical values that are not shown on the map
   filterDataForCategoricalLegend = (mapInstance, valFn) ->
     data = []
     dataUsed = mapInstance.usedDataValues
@@ -201,6 +202,21 @@
           key: entry
           value: category
     data
+
+
+  #    #############################################
+  #      Utilities
+  #    #############################################
+
+  # gets all mapped property values that are actually on the map
+  # Either gets all from the set mappingProperty or gets all from the default
+  # no mixing of default and set property!
+  deduceAllAvailableMappingOnMap = (map, mappingPropertyOnMap) ->
+    map.features.map (mapEntry) ->
+      if mappingPropertyOnMap
+        mapEntry.properties[mappingPropertyOnMap]
+      else
+        mapEntry.properties[defaults.mappingPropertyOnMap]

   # Stop progress bar with a timeout to prevent running conditions
@@ -252,6 +268,11 @@
         <img class="placeholder-image" src="http://placehold.it/#{$(element).width()}x200}/F55CB7/ffffff&text=select%20this%20snippet%20and%20choose%20a%20map%20in%20the%20sidebar" />
       """)

+
+      #    #############################################
+      #      Change Listeners
+      #    #############################################
+
       scope.$watch('map', (newVal, oldVal) ->
         return unless newVal
         $placeholder = $(element).find('.placeholder-image')


MINOR removed outdated comment

MINOR removed outdated comment

--- app/static/coffee/Components/meta.coffee
+++ app/static/coffee/Components/meta.coffee
@@ -1,7 +1,3 @@
-#Move headers and genomes to meta class
-#Follow tutorial to get asych requests
-
-
 class Page
     data = {}
     meta=(response)->


Fix test

Fix test

--- apps/gallery_insights/test/routes.coffee
+++ apps/gallery_insights/test/routes.coffee
@@ -18,9 +18,9 @@
   it 'sets variable when user is not part of the gallery-insights list', ->
     routes.__set__ 'subscribed', (email, cb) -> cb false
     routes.index {}, @res
-    @res.locals.sd.MAILCHIMP_SUBSCRIBED.should.eql false
+    @res.locals.sd.MAILCHIMP_SUBSCRIBED.should.eql 'unsubscribed'

   it 'sets variable when user is part of the gallery-insights list', ->
     routes.__set__ 'subscribed', (email, cb) -> cb true
     routes.index {}, @res
-    @res.locals.sd.MAILCHIMP_SUBSCRIBED.should.eql true+    @res.locals.sd.MAILCHIMP_SUBSCRIBED.should.eql 'subscribed'


Remove debug prints.

Remove debug prints.

--- services/web/public/coffee/ide/references-search/ReferencesSearchManager.coffee
+++ services/web/public/coffee/ide/references-search/ReferencesSearchManager.coffee
@@ -31,9 +31,7 @@
 				return
 			# console.log '>> storing references keys'
 			oldKeys = @$scope.$root._references.keys
-			console.log "#{oldKeys.length} + #{newKeys.length}"
 			@$scope.$root._references.keys = _.union(oldKeys, newKeys)
-			console.log "end>> #{@$scope.$root._references.keys.length}"

 		indexReferences: (docIds, shouldBroadcast) ->
 			opts =


Make sure first change isn't hidden under toolbar

--- services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee
+++ services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee
@@ -62,7 +62,7 @@
 							$callout_el.css(top: top + line_height, height: original_top - top)

 					# Put the focused entry as close to where it wants to be as possible
-					focused_entry_top = focused_entry.scope.entry.screenPos.y
+					focused_entry_top = Math.max(focused_entry.scope.entry.screenPos.y, TOOLBAR_HEIGHT)
 					focused_entry.$box_el.css(top: focused_entry_top)
 					focused_entry.$indicator_el.css(top: focused_entry_top)
 					positionLayoutEl(focused_entry.$callout_el, focused_entry.scope.entry.screenPos.y, focused_entry_top)


static pages: do not process urls with unnecessary query params

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -306,10 +306,10 @@
         if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator") {
           set $prerender 1;
         }
-        if ($request_uri !~ "(^(\/)?$)|(\/(Pricing|About|Legal|Features)(\/|$))") {
+        if ($request_uri !~ "(^(\/)?$)|(\/(Pricing|About|Legal|Features)(\/[A-Za-z]*$|$))") {
           set $prerender 0;
         }
-        if ($args ~ "_escaped_fragment_=$|(\/(Pricing|About|Legal|Features)(\/|$))") {
+        if ($args ~ "^_escaped_fragment_=($|(\/(Pricing|About|Legal|Features)(\/[A-Za-z]*$|$)))") {
           set $prerender 1;
         }
         if ($http_user_agent ~ "Prerender") {


LEV-88 macro replacement

LEV-88 macro replacement

--- modules/levelcreator/app/assets/javascripts/level_creator/plugin_renderer.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugin_renderer.coffee
@@ -30,107 +30,8 @@
     return unless code?
     return unless @dataHandler.deferred("initialized").state() == "resolved"

-    @filter code
-    @code = @replaceMakros code
-
-
-
-
-  filter : (code) ->
-
-
-    missionData = @dataHandler.getMissionData()
-
-    # get meta info from code
-    # DESIRED_SLIDES_BEFORE_PROBLEM
-    # DESIRED_SLIDES_AFTER_PROBLEM
-    # SEGMENTS_AT_START
-    # SEGMENTS_AT_END
-
-
-    result = code.match( /DESIRED_SLIDES_BEFORE_PROBLEM\=([0-9]+)/m )
-    if result?.length is 2
-      desiredStartSlide = @slidesBeforeProblem - parseInt(result[1])
-    else
-      desiredStartSlide = 0
-
-    result = code.match( /MIN_END_SEGMENTS_AT_FIRST_SLIDE\=([0-9]+)/m )
-    if result?.length is 2
-      minEndSegmentsAtFirstSlide = parseInt(result[1])
-    else
-      minEndSegmentsAtFirstSlide = 0
-
-    result = code.match( /DESIRED_SLIDES_AFTER_PROBLEM\=([0-9]+)/m )
-    if result?.length is 2
-      desiredEndSlide = @slidesBeforeProblem + parseInt(result[1])
-    else
-      desiredEndSlide = @slidesBeforeProblem + @slidesAfterProblem
-
-    result = code.match( /MIN_END_SEGMENTS_AT_LAST_SLIDE\=([0-9]+)/m )
-    if result?.length is 2
-      minEndSegmentsAtLastSlide = parseInt(result[1]) - 1 # -1 for startSegment
-    else
-      minEndSegmentsAtLastSlide = 0
-
-    # check plausibility
-
-    if missionData.possibleEnds.length < minEndSegmentsAtFirstSlide
-      console.log "abort"
-
-
-    # apply filters
-
-    # get StartSlide - apply DESIRED_SLIDES_BEFORE_PROBLEM and MIN_SEGMENTS_AT_FIRST_SLIDE
-
-    firstStartFrame = @nmToSlide(missionData.start.firstFrame)
-
-    searchStart = Math.max(firstStartFrame, desiredStartSlide)
-
-
-    for i in [Math.floor(searchStart)...@slidesBeforeProblem] by 1
-      result = _.filter(missionData.possibleEnds, (e) =>
-        #console.log @nmToSlide(e.firstFrame) + "  - " + i + " -  " + @nmToSlide(e.lastFrame)
-        @nmToSlide(e.firstFrame) < i < @nmToSlide(e.lastFrame) ).length
-      #console.log i + " " + result
-      if result >= minEndSegmentsAtFirstSlide
-        startSlide = i
-        break
-
-
-    console.log "abort" unless startSlide?
-    console.log "Start: " + startSlide
-
-    startSlide = 0 unless startSlide?
-
-
-    lastStartFrame = @nmToSlide(missionData.start.lastFrame)
-
-    #searchStart = Math.max(firstStartFrame, desiredEndSlide)
-
-
-    for i in [Math.floor(desiredEndSlide)...Math.max(@slidesBeforeProblem, lastStartFrame)] by -1
-      result = _.filter(missionData.possibleEnds, (e) =>
-        #console.log @nmToSlide(e.firstFrame) + "  - " + i + " -  " + @nmToSlide(e.lastFrame)
-        @nmToSlide(e.firstFrame) < i < @nmToSlide(e.lastFrame) ).length
-      #console.log i + " " + result
-      if result >= minEndSegmentsAtLastSlide
-        endSlide = i
-        break
-
-
-    console.log "abort" unless endSlide?
-    console.log "end: " + endSlide
-
-    endSlide = @slidesBeforeProblem + @slidesAfterProblem unless endSlide?
-
-
-
-
-
-
-  nmToSlide : (nm) ->
-
-    (nm / @PIXEL_SIZE) + @slidesBeforeProblem
+    keyValues = @getMacroValues code
+    @code = @replaceMacros code, keyValues

   testCompile : ->
@@ -315,17 +216,135 @@
       )

-  replaceMakros : (object) ->
+  replaceMacros : (object, keyValues) ->

     json = JSON.stringify(object)

-    json = json.replace(/\$EC/g, "#{@slidesBeforeProblem}")
-    json = json.replace(/\$SBE/g, "#{@slidesBeforeProblem}")
-    json = json.replace(/\$SAE/g, "#{@slidesAfterProblem}")
-    json = json.replace(/\$LE/g, "#{@slidesAfterProblem+@slidesBeforeProblem}")
+    for key of keyValues
+
+      re = new RegExp("\\$#{key}", "g")
+      json = json.replace(re, "#{keyValues[key]}")

     jQuery.parseJSON( json )

-
-     +  getMacroValues : (code) ->
+
+
+    missionData = @dataHandler.getMissionData()
+
+    # get meta info from code
+    # DESIRED_SLIDES_BEFORE_PROBLEM
+    # DESIRED_SLIDES_AFTER_PROBLEM
+    # SEGMENTS_AT_START
+    # SEGMENTS_AT_END
+
+
+    result = code.match( /DESIRED_SLIDES_BEFORE_PROBLEM\=([0-9]+)/m )
+    if result?.length is 2
+      desiredStartSlide = @slidesBeforeProblem - parseInt(result[1])
+    else
+      desiredStartSlide = 0
+
+    result = code.match( /MIN_END_SEGMENTS_AT_FIRST_SLIDE\=([0-9]+)/m )
+    if result?.length is 2
+      minEndSegmentsAtFirstSlide = parseInt(result[1])
+    else
+      minEndSegmentsAtFirstSlide = 0
+
+    result = code.match( /DESIRED_SLIDES_AFTER_PROBLEM\=([0-9]+)/m )
+    if result?.length is 2
+      desiredEndSlide = @slidesBeforeProblem + parseInt(result[1])
+    else
+      desiredEndSlide = @slidesBeforeProblem + @slidesAfterProblem
+
+    result = code.match( /MIN_END_SEGMENTS_AT_LAST_SLIDE\=([0-9]+)/m )
+    if result?.length is 2
+      minEndSegmentsAtLastSlide = parseInt(result[1]) - 1 # -1 for startSegment
+    else
+      minEndSegmentsAtLastSlide = 0
+
+    result = code.match( /NO_START_SEGMENT_AT_LAST_SLIDE\=([a-z]+)/m )
+    if result?.length is 2
+      if result[1] is "true"
+        noStartSegmentAtLastSlide = true
+      else
+        noStartSegmentAtLastSlide = false
+    else
+      noStartSegmentAtLastSlide = false
+
+    # check plausibility
+
+    if missionData.possibleEnds.length < minEndSegmentsAtFirstSlide
+      console.log "abort"
+
+
+    # apply filters
+
+    # get StartSlide - apply DESIRED_SLIDES_BEFORE_PROBLEM and MIN_SEGMENTS_AT_FIRST_SLIDE
+
+    firstStartFrame = @nmToSlide(missionData.start.firstFrame)
+
+    searchStart = Math.max(firstStartFrame, desiredStartSlide)
+
+
+    for i in [Math.floor(searchStart)...@slidesBeforeProblem] by 1
+      result = _.filter(missionData.possibleEnds, (e) =>
+        #console.log @nmToSlide(e.firstFrame) + "  - " + i + " -  " + @nmToSlide(e.lastFrame)
+        @nmToSlide(e.firstFrame) < i < @nmToSlide(e.lastFrame) ).length
+      #console.log i + " " + result
+      if result >= minEndSegmentsAtFirstSlide
+        startSlide = i
+        break
+
+
+    console.log "abort" unless startSlide?
+    console.log "Start: " + startSlide
+
+    startSlide = 0 unless startSlide?
+
+
+    # get endSlide
+    lastStartFrame = @nmToSlide(missionData.start.lastFrame)
+
+    if noStartSegmentAtLastSlide
+      searchEnd = Math.max(@slidesBeforeProblem, lastStartFrame)
+    else
+      searchEnd = @slidesBeforeProblem
+
+    for i in [Math.floor(desiredEndSlide)...searchEnd] by -1
+      result = _.filter(missionData.possibleEnds, (e) =>
+        #console.log @nmToSlide(e.firstFrame) + "  - " + i + " -  " + @nmToSlide(e.lastFrame)
+        @nmToSlide(e.firstFrame) < i < @nmToSlide(e.lastFrame) ).length
+      #console.log i + " " + result
+      if result >= minEndSegmentsAtLastSlide
+        endSlide = i
+        break
+
+
+    console.log "abort" unless endSlide?
+    console.log "end: " + endSlide
+
+    endSlide = @slidesBeforeProblem + @slidesAfterProblem unless endSlide?
+
+
+
+    # return macro values
+    keyValues = new Object()
+
+    keyValues.START = startSlide
+    keyValues.END = endSlide
+    keyValues.LENGTH = endSlide - startSlide
+    keyValues.ERROR_CENTER = @slidesBeforeProblem
+    keyValues.DATA_START = 0
+    keyValues.DATA_END = @slidesAfterProblem+@slidesBeforeProblem
+    keyValues.DATA_ELENGTH = @slidesAfterProblem+@slidesBeforeProblem
+
+    console.log keyValues
+    keyValues
+
+
+
+  nmToSlide : (nm) ->
+
+    (nm / @PIXEL_SIZE) + @slidesBeforeProblem


--- src/app.coffee
+++ src/app.coffee
@@ -126,6 +126,19 @@
     else
       res.status(500).end()

+app.get '/api/state', (req, res) ->
+  state = app.zoo.getState()
+  id = app.zoo.getSessionId()
+  pass = app.zoo.getSessionPassword()
+  timeout = app.zoo.getSessionTimeout()
+
+  res.send
+    state: state
+    id: id.toString("hex")
+    pass: pass.toString("hex")
+    timeout: timeout
+
+
 server = app.listen 8080, ->
   LOG.info "Listening on: %s:%d", server.address().address, server.address().port
   LOG.info "Checking for the default zookeeper folder 'configs' exists"


handle ansi codes in stderr

handle ansi codes in stderr

--- src/smc-webapp/jupyter/cell-output-message.cjsx
+++ src/smc-webapp/jupyter/cell-output-message.cjsx
@@ -58,9 +58,16 @@
     mixins: [ImmutablePureRenderMixin]

     render: ->
-        # span below?  what? -- See https://github.com/sagemathinc/cocalc/issues/1958
-
{@props.message.get('text')}
-

+        value = @props.message.get('text')
+        if is_ansi(value)
+

+                <Ansi>{value}</Ansi>
+

+        else
+            # span below?  what? -- See https://github.com/sagemathinc/cocalc/issues/1958
+

+                {value}
+

 Image = rclass
     propTypes:


Show 'Discard Changes' context menu item anywhere in Unstaged Changes list

--- menus/git.cson
+++ menus/git.cson
@@ -30,7 +30,7 @@
       'command': 'github:open-file'
     }
   ]
-  '.github-UnstagedChanges .github-FilePatchListView-item': [
+  '.github-UnstagedChanges': [
     {
       'label': 'Discard Changes'
       'command': 'github:discard-changes'


Ugly fix to popup size

Ugly fix to popup size

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -16,6 +16,11 @@
       @map = new L.Map('map')
       @map.addControl(new L.Control.Distance())
       @map.setView(new L.LatLng(metadata.config.map.init.lat, metadata.config.map.init.lng,1), metadata.config.map.init.zoom)
+      @map.on 'popupopen', (e) ->
+        setTimeout((-> e.popup._updateLayout()), 100)
+        setTimeout((-> e.popup._updateLayout()), 300)
+        setTimeout((-> e.popup._updateLayout()), 700)
+        setTimeout((-> e.popup._updateLayout()), 1500)

     app = @
     $('#style_switch button').click ->


try this

try this

--- vue/tests/e2e/helpers/pageHelper.coffee
+++ vue/tests/e2e/helpers/pageHelper.coffee
@@ -80,11 +80,13 @@
       test.pause(2000)

   fillIn: (selector, value, wait) ->
+    @pause(200)
     @waitFor(selector, wait)
     test.clearValue(selector)
     test.setValue(selector, value)

   fillInAndEnter: (selector, value, wait) ->
+    @pause(100)
     @waitFor(selector, wait)
     test.clearValue(selector)
     test.setValue(selector, [value, test.Keys.ENTER])


focus tweaks

focus tweaks

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -69,14 +69,13 @@

   done: ->
     if @getInput()
-      @view.focus() # Defocus input
+      @view.focus() if @view.hasFocus() # Defocus input
       @getInput().input = false

   reset: ->
     @done()
     @clear()
     @input()
-    @focusInput()

   eval: (ed) ->
     if (input = @getInput()?.view.getModel())


Block focus-shifting when using tab-to-autocomplete.

--- app/assets/javascripts/views/tagger.coffee
+++ app/assets/javascripts/views/tagger.coffee
@@ -39,6 +39,7 @@
       when $.ui.keyCode.TAB, $.ui.keyCode.ENTER
         if @get('wantsCandidates')
           pv.willAddSelection()
+          e.preventDefault()

         if @get 'hasText'
           @clear()


check timestamps array length

check timestamps array length

--- app/coffee/DeleteQueueManager.coffee
+++ app/coffee/DeleteQueueManager.coffee
@@ -28,7 +28,7 @@
         flushProjectIfNotModified = (project_id, flushTimestamp, cb) ->
             ProjectManager.getProjectDocsTimestamps project_id, (err, timestamps) ->
                 return callback(err) if err?
-                if !timestamps?
+                if timestamps.length == 0
                     logger.log {project_id}, "skipping flush of queued project - no timestamps"
                     return cb()
                 # are any of the timestamps newer than the time the project was flushed?


Don't dump streams to log files.

Don't dump streams to log files.

--- app/coffee/FSPersistorManager.coffee
+++ app/coffee/FSPersistorManager.coffee
@@ -16,9 +16,9 @@
       callback err

   sendStream: ( location, target, sourceStream, callback = (err)->) ->
-    logger.log location:location, target:target, source:sourceStream, "sending file stream"
+    logger.log location:location, target:target, "sending file stream"
     sourceStream.on "error", (err)->
-      logger.err location:location, target:target, source:sourceStream, err:err "error on stream to send"
+      logger.err location:location, target:target, err:err "error on stream to send"
     LocalFileWriter.writeStream sourceStream, null, (err, fsPath)=>
       if err?
         logger.err  location:location, target:target, fsPath:fsPath, err:err, "something went wrong writing stream to disk"


Removing translate

Removing translate

--- app/coffee/modules/backlog/sortable.coffee
+++ app/coffee/modules/backlog/sortable.coffee
@@ -54,7 +54,7 @@
                 return

             filterError = ->
-                text = $translate.instant("BACKLOG.SORTABLE_FILTER_ERROR")
+                text = "You can't drop on backlog when filters are open"
                 $tgConfirm.notify("error", text)

             $el.sortable({


passing specs

passing specs

--- app/coffeescripts/learn/collections.coffee
+++ app/coffeescripts/learn/collections.coffee
@@ -63,10 +63,12 @@
     lastModelIndex = 0
     if (@currentAlbum().isFirstTrack(currentTrackIndex))
       if (@playlist.isFirstAlbum(currentAlbumIndex))
-        lastModelIndex = @playlist.models.length -1
+        lastModelIndex = @playlist.models.length - 1
         @set 'currentAlbumIndex': lastModelIndex
       else
         @set 'currentAlbumIndex': currentAlbumIndex - 1
+      lastTrackIndex = @currentAlbum().get('tracks').length - 1
+      @set 'currentTrackIndex': lastTrackIndex
     else
       @set 'currentTrackIndex': currentTrackIndex - 1
     @logCurrentAlbumAndTrack()


Document behaviors

Document behaviors

--- app/frontend/scripts/application.js.coffee
+++ app/frontend/scripts/application.js.coffee
@@ -22,32 +22,45 @@
     $('#songs').html(stache)

 $(document).ready () ->
+
+  # Plays music.
   $('#play').click () ->
     updateSongs("/play", "PUT")
     false

+  # Pauses music.
   $('#pause').click () ->
     updateSongs("/pause", "PUT")
     false

+  # Goes back to the previous track.
   $('#previous').click () ->
     updateSongs("/previous", "PUT")
     false

+  # Goes to the next track.
   $('#next').click () ->
     updateSongs("/next", "PUT")
     false

+  # Searches things.
   $('#search').submit () ->
     keyword = $('#search').find('input')[0].value
     updateSongs("/search?q=#{keyword}", "GET")
     false

+  # Clicking on a link with a class of `user` takes you to the user page.
+  #
+  # data-user - The Data attribute set on the link whose value is the username.
   $('.user').click () ->
     user = @text.substr(1)
     updateSongs("/user/#{user}","GET")
     false

+  # Stars this song.
+  #
+  # data-song-id - The Data attribute set on the link whose value is the
+  #                persistent ID of the song.
   $('.star').live 'click', () ->
     id = $(@).data('song-id')
     $.ajax
@@ -59,6 +72,10 @@
         alert response
     false

+  # Unstars this song.
+  #
+  # data-song-id - The Data attribute set on the link whose value is the
+  #                persistent ID of the song.
   $('.unstar').live 'click', () ->
     id = $(@).data('song-id')
     $.ajax


(fix) Fix exercise correction url.

(fix) Fix exercise correction url.

--- app/scripts/pages/students/students.coffee
+++ app/scripts/pages/students/students.coffee
@@ -55,7 +55,7 @@

   editSolution: (solution) ->
     @config.then (config) =>
-      window.open("#{config.correctorUrl}/#correction/by-solution/#{solution.id}", '_blank')
+      window.open("#{config.correctorUrl}/correction/by-solution/#{solution.id}", '_blank')

   regeneratePdf: (solution) ->
     @config.then (config) =>


update label

update label

--- lib/edit-in-new-tab.coffee
+++ lib/edit-in-new-tab.coffee
@@ -22,8 +22,8 @@
       ]
       order: 2
     indentOrigin:
-      title: "Indent Origin"
-      description: "Auto indent changes written back to origin"
+      title: "Auto-Indent Origin"
+      description: "Auto indent changes written back to the originating tab"
       type: "boolean"
       default: false
       order: 3


:new: Add EditorRegistry::forEach method

--- lib/editor-registry.coffee
+++ lib/editor-registry.coffee
@@ -14,6 +14,9 @@
       editorLinter.deactivate()
     return editorLinter

+  forEach: (callback) ->
+    @editorLinters.forEach(callback)
+
   ofTextEditor: (editor) ->
     return @editorLinters.get(editor)


update google analytics init params

update google analytics init params

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -21,7 +21,7 @@
     if sd.CURRENT_USER?.id
       googleAnalyticsParams.userId = sd.CURRENT_USER?.id

-    @ga? 'create', sd.GOOGLE_ANALYTICS_ID, 'artsy.net'
+    @ga? 'create', sd.GOOGLE_ANALYTICS_ID, googleAnalyticsParams

 module.exports.getUserAgent = ->
   window?.navigator?.userAgent


Fixed format and test view

Fixed format and test view

Signed-off-by: WebFreak001 <b0a51d41aaf8d5df1e850f1ef2ec58fb1c30319f@web.de>

--- lib/atomize-d.coffee
+++ lib/atomize-d.coffee
@@ -141,7 +141,7 @@
     if editor?.getBuffer?()? and editor?.getGrammar?().scopeName == "source.d"
       buf = editor.getBuffer()
       pos = editor.getCursorBufferPosition()
-      @getCurrentProject.dfmt.format buf.getText(), (text) =>
+      @getCurrentProject().dfmt.format buf.getText(), (text) =>
         buf.setText text
         editor.setCursorBufferPosition pos

@@ -155,7 +155,7 @@
     return project

   toggleTestView: ->
-    project = @getCurrentProject
+    project = @getCurrentProject()
     pane = atom.workspace.getActivePane()
     if project.testViewTab?
       if project.testViewTab.active


Fix deferred readiness

Fix deferred readiness

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -8,6 +8,5 @@
 p1 = App.Category.fetch()
 p2 = App.Matcher.fetch()

-$.when(p1, p2).done ->
-  Em.run.next ->
-    App.advanceReadiness()
+Ember.RSVP.all([p1, p2]).then ->
+  App.advanceReadiness()


add album list view

add album list view

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -7,8 +7,20 @@
 App.AlbumItemView = Backbone.View.extend
   tagName: 'li'
   template: _.template "<%= title %>"
-  initialize: ->
-    @model = new App.Models.Album (title: 'Giant Steps')
   render: ->
     @$el.html @template @model.attributes
     @
+
+App.AlbumsListView = Backbone.View.extend
+  el: '#featured'
+  initialize: ->
+    @listenTo @collection, 'sync', @render
+  render: ->
+    for model in @collection.models
+      itemView = new App.AlbumItemView(model: model)
+      @$el.append itemView.render().el
+
+$ ->
+  App.albums = new App.Collections.Albums
+  App.albumsListView = new App.AlbumsListView(collection: App.albums)
+  App.albums.fetch()


Change default local for JS I18n.

Change default local for JS I18n.

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -21,7 +21,7 @@
 #= require_self
 #= require wheelmap

-I18n.defaultLocale = 'de'
+I18n.defaultLocale = 'en'
 I18n.locale = $('html').attr('lang')
 I18n.fallbacks = true


Ring bell on error

Ring bell on error

Fixes #550

--- lib/cli/watch.coffee
+++ lib/cli/watch.coffee
@@ -56,6 +56,7 @@
   err = Error(err)
   if 'stack' in err then err = err.stack
   cli.emit('err', err)
+  console.log('\u0007')
   if active then server.show_error(err)

 ###*


arven map

arven map

--- app/assets/javascripts/map.js.coffee
+++ app/assets/javascripts/map.js.coffee
@@ -14,10 +14,18 @@

     cloudmade_api_key = $map.data("cloudmade-api-key")
     L.Icon.Default.imagePath = "http://leafletjs.com/dist/images"
-    L.tileLayer("http://{s}.tile.cloudmade.com/#{cloudmade_api_key}/997/256/{z}/{x}/{y}.png", {
+
+    L.tileLayer("/assets/carte_arven/{z}/{x}/{y}.png", {
+        attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade',
+        maxZoom: 5,
+        tms: true,
+        continuousWorld: true,
+        noWrap: true
+    }).addTo(map)
+    ###L.tileLayer("http://{s}.tile.cloudmade.com/#{cloudmade_api_key}/997/256/{z}/{x}/{y}.png", {
         attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade',
         maxZoom: 18
-    }).addTo(map)
+    }).addTo(map)###

     # smallPointIcon
     # middlePointIcon
@@ -113,25 +121,26 @@
       # Update only the characters that have been changed since the last fetch
       update: (characters, characters_updated_at)->
         @characters = characters
-        console.log characters, characters_updated_at
+        #console.log characters, characters_updated_at
         if characters_updated_at != @updated_at
           @updated_at = characters_updated_at
-          console.log "chars updated"
+          #console.log "chars updated"
           # A character has changed
           if characters.length == @list.length
             for new_ch, ind in characters
               old_ch = @list[ind].character
               if old_ch.id != new_ch.id
                 # Not the same character at the same position as before
+                console.log "Not the same character at the same position as before"
                 @construct_list()
                 break
               if old_ch.updated_at != new_ch.updated_at
                 # Character updated
-                console.log "ch", new_ch, "updated"
+                #console.log "ch", new_ch, "updated"
                 @list[ind].update_character(new_ch)
               else if old_ch.nodes_updated_at != new_ch.nodes_updated_at
                 # Character_node updated
-                console.log "ch nodes", new_ch, "updated"
+                #console.log "ch nodes", new_ch, "updated"
                 @list[ind].update_character_nodes(new_ch)
           else
             # not the same number of characters as before
@@ -415,6 +424,7 @@
               # Update it
               if @node.real
                 # Target and source are real
+                # TODO : two solutions : transfer all or just the main character
                 # Remove character from the source
                 @remove_character(concerned_ch_id)
                 # Update target adding a character


Remove unneeded code

Remove unneeded code

--- app/assets/javascripts/upload.coffee
+++ app/assets/javascripts/upload.coffee
@@ -367,16 +367,6 @@
       '/corrections/upload'
       '#bulk-uploadButton-button-actual'
       (xhr) ->
-        # result = JSON.parse xhr.response
-        # console.log result
-        # if result.successful.length > 0
-        #   uploaded_files = result.successful.map (file) -> file.response.body
-        #   console.log uploaded_files
-        #   hiddenInput.value = JSON.stringify(uploaded_files)
-        #   $('#upload-bulk-correction-save').prop('disabled', false)
-        #   $(metaData).empty()
-        #     .append(result.successful.length + ' ' +$(metaData).data('tr-uploads'))
-        #   $(uploadButton).hide()
       () ->
         $("#bulk-upload-area").toggle()
       'upload-bulk-correction-hidden'


Fixed order of project data loading.

Fixed order of project data loading.

--- app/coffee/controllers/issues.coffee
+++ app/coffee/controllers/issues.coffee
@@ -192,9 +192,11 @@
             regenerateTags()
             filterIssues()

-    $data.loadProject($scope)
-    $data.loadUsersAndRoles($scope).then ->
-        loadIssues()
+    # Load initial data
+    $data.loadProject($scope).then ->
+        $data.loadUsersAndRoles($scope).then ->
+            console.log $scope.constants.issueStatuses
+            loadIssues()

     $scope.updateIssueAssignation = (issue, id) ->
         issue.assigned_to = id || null
@@ -264,10 +266,12 @@
         rs.getIssueAttachments(projectId, issueId).then (attachments) ->
             $scope.attachments = attachments

-    $data.loadProject($scope)
-    $data.loadUsersAndRoles($scope).then ->
-        loadIssue()
-        loadAttachments()
+
+    # Load initial data
+    $data.loadProject($scope).then ->
+        $data.loadUsersAndRoles($scope).then ->
+            loadIssue()
+            loadAttachments()

     $scope.isSameAs = (property, id) ->
         return ($scope.issue[property] == parseInt(id, 10))


Bug#144: Now descriptions editions are processed by the wiki renderer

--- app/coffee/directives/history.coffee
+++ app/coffee/directives/history.coffee
@@ -6,7 +6,7 @@
 })

-GmHistoryDirective = ($compile, $rootScope, $i18next) ->
+GmHistoryDirective = ($compile, $rootScope, $i18next, gmWiki) ->
     restrict: "A"
     require: "?ngModel"
     link: (scope, elm, attrs, ngModel) ->
@@ -36,6 +36,10 @@
                             watchers = _.findByValues(scope.constants.users, "id", watchers_ids)
                             return _.map(watchers, "full_name").join(", ")
                         return null
+                    when "description"
+                        if value
+                            return gmWiki.render(value)
+                        return null
                     else value
             issue: (name, value) ->
                 return switch name
@@ -90,6 +94,10 @@
                             watchers = _.findByValues(scope.constants.users, "id", watchers_ids)
                             return _.map(watchers, "full_name").join(", ")
                         return null
+                    when "description"
+                        if value
+                            return gmWiki.render(value)
+                        return null
                     else value
             task: (name, value) ->
                 return switch name
@@ -122,6 +130,10 @@
                             watchers_ids = _.map(value, (v) -> parseInt(v, 10))
                             watchers = _.findByValues(scope.constants.users, "id", watchers_ids)
                             return _.map(watchers, "full_name").join(", ")
+                        return null
+                    when "description"
+                        if value
+                            return gmWiki.render(value)
                         return null
                     else value
         }
@@ -212,4 +224,4 @@
             render(ngModel.$viewValue or [])

 module = angular.module('taiga.directives.history', ['i18next'])
-module.directive("gmHistory", ['$compile', '$rootScope', '$i18next', GmHistoryDirective])
+module.directive("gmHistory", ['$compile', '$rootScope', '$i18next', 'gmWiki', GmHistoryDirective])


enhance: start the tour

enhance: start the tour

--- app/components/submission-box.coffee
+++ app/components/submission-box.coffee
@@ -1,6 +1,11 @@
 `import Ember from 'ember'`
+`import ENV from 'irene/config/environment'`

 SubmissionBoxComponent = Ember.Component.extend
   classNames: ["box"]
+  onboard: Ember.inject.service()
+
+  didInsertElement: ->
+     this.set('onboard.activeTour', ENV.TOUR.newScan)

 `export default SubmissionBoxComponent`


Last schema refactoring.

Last schema refactoring.

--- app/schemas/models/thang_type.coffee
+++ app/schemas/models/thang_type.coffee
@@ -101,7 +101,7 @@
 SoundSchema = c.sound({delay: {type: 'number'}})

 _.extend ThangTypeSchema.properties,
-  raw: c.object {title: 'Raw Vector Data'},
+  raw: c.object {title: 'Raw Vector Data', default: {shapes: {}, containers: {}, animations: {}}},
     shapes: c.object {title: 'Shapes', additionalProperties: ShapeObjectSchema}
     containers: c.object {title: 'Containers', additionalProperties: ContainerObjectSchema}
     animations: c.object {title: 'Animations', additionalProperties: RawAnimationObjectSchema}
@@ -131,18 +131,21 @@
       type: 'array'
       format: 'thang-color-group'
       items: {type: 'string'}
-  snap: c.object {title: 'Snap', description: 'In the level editor, snap positioning to these intervals.', required: ['x', 'y']},
+  snap: c.object {title: 'Snap', description: 'In the level editor, snap positioning to these intervals.', required: ['x', 'y'], default: {x: 4, y: 4}},
     x:
       title: 'Snap X'
       type: 'number'
       description: 'Snap to this many meters in the x-direction.'
-      default: 4
     y:
       title: 'Snap Y'
       type: 'number'
       description: 'Snap to this many meters in the y-direction.'
-      default: 4
   components: c.array {title: 'Components', description: 'Thangs are configured by changing the Components attached to them.', uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema  # TODO: uniqueness should be based on 'original', not whole thing
+
+ThangTypeSchema.required = ['kind']
+
+ThangTypeSchema.deafult =
+  raw: {}

 ThangTypeSchema.definitions =
   action: ActionSchema


Added more filter options in visualization profiles.

--- community/server/src/main/coffeescript/neo4j/webadmin/modules/databrowser/visualization/models/filters/PropertyFilter.coffee
+++ community/server/src/main/coffeescript/neo4j/webadmin/modules/databrowser/visualization/models/filters/PropertyFilter.coffee
@@ -83,6 +83,10 @@
       @compareMethods :
         '==' : {label : "is",    cmp : (actual, expected) -> actual == expected}
         '!=' : {label : "isn't", cmp : (actual, expected) -> actual != expected}
+        '>' : {label : ">", cmp : (actual, expected) -> actual > expected}
+        '<' : {label : "<", cmp : (actual, expected) -> actual < expected}
+        '>=' : {label : ">=", cmp : (actual, expected) -> actual >= expected}
+        '<=' : {label : "<=", cmp : (actual, expected) -> actual <= expected}

       defaults :
         'method' : 'exists'


Remove irrelevant spec

Remove irrelevant spec

--- apps/artwork/test/client/view.coffee
+++ apps/artwork/test/client/view.coffee
@@ -61,12 +61,6 @@
       @view = new @ArtworkView el: $('#artwork-page'), artist: @artist, artwork: @artwork

     describe 'when an artwork changes', ->
-      it 'updates the detail panel accordingly', ->
-        $detail = @view.$('.artwork-detail')
-        $detail.find('.artwork-buy-button').length.should.be.ok
-        @view.artwork.set 'sale_message', 'SOLD'
-        $detail.find('.artwork-buy-button').length.should.not.be.ok
-
       it 'only renders if the artwork changes', ->
         @renderDetailSpy.called.should.not.be.ok
         @view.artwork.set 'sale_message', 'SOLD'


test for if ecommerce changes on a work

test for if ecommerce changes on a work

--- apps/artwork/test/client/view.coffee
+++ apps/artwork/test/client/view.coffee
@@ -74,9 +74,14 @@
         html.should.include 'Bid'

     describe 'when an artwork changes', ->
-      it 'only renders if the artwork changes', ->
+      it 'only renders if the artwork sale_message changes', ->
         @renderDetailSpy.called.should.not.be.ok
         @view.artwork.set 'sale_message', 'SOLD'
+        @renderDetailSpy.called.should.be.ok
+
+      it 'only renders if the artwork ecommerce changes', ->
+        @renderDetailSpy.called.should.not.be.ok
+        @view.artwork.set 'ecommerce', true
         @renderDetailSpy.called.should.be.ok

     describe '#initialize', ->


* fix show success problem * change invoice option * add domain type for registration * change domain amount

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -212,7 +212,7 @@
       {createButton, registerButton} = form.buttons
       paymentController = KD.getSingleton('paymentController')
       group             = KD.getSingleton("groupsController").getCurrentGroup()
-      registerTheDomain = ->
+      registerTheDomain = =>
           KD.remote.api.JDomain.registerDomain
             domainName : domainInput.getValue()
             years      : regYears.getValue()


Working on issue #556 - SLA description only available in german - thanks to @pradorocchi!

--- app/assets/javascripts/app/models/sla.coffee
+++ app/assets/javascripts/app/models/sla.coffee
@@ -18,9 +18,9 @@
   ]

   @description = '''
-**Service-Level-Agreements**, abgekürzt **SLAs**, unterstützen Sie gegenüber Kunden gewisse zeitliche Reaktionen einzuhalten. Somit können Sie z. B. sagen Kunden sollen immer nach spätestens 8 Stunden eine Reaktion von Ihnen bekommen. Falls es zu einer drohenden Unterschreitung oder einer Unterschreitung kommt, weißt Zammad Sie auf solche Ereignisse hin.
+** Service Level Agreements **, abbreviated ** SLAs **, help you to meet certain customers' time-related responses. Thus, for example, you can say customers should always get a response from you after 8 hours at the latest. In the event of an imminent violation or a breach, Zammad will alert you to such events.

-Es können **Reaktionszeit** (Zeit zwischen Erstellung eines Tickets und erster Reaktion eines Agenten), **Aktualisierungszeit** (Zeit zwischen Nachfrage eines Kunden und Reaktion eines Agenten) und **Lösungszeit** (Zeit zwischen Erstellung und schließen eines Tickets) definiert werden.
+It can be ** response time ** (time between the creation of a ticket and the first reaction of an agent), ** update time ** (time between a customer's request and an agent's reaction) and ** solution time ** (time between creation and closing a ticket ) To be defined.

-Drohenden Unterschreitungen oder Unterschreitungen werden in einer eigenen Ansicht in den Übersichten angezeigt. Zudem können **E-Mail Benachrichtigungen** konfiguriert werden.
+Any violations are displayed in a separate view in the overviews. You can also configure ** e-mail notifications **.
 '''


escapeMarkup used for select2

escapeMarkup used for select2

--- app/assets/javascripts/app/payment.js.coffee
+++ app/assets/javascripts/app/payment.js.coffee
@@ -69,6 +69,7 @@
       data: @get('accountsTo')
       formatResult: App.Payment.paymentOptions
       formatSelection: App.Payment.paymentOptions
+      escapeMarkup: (m) -> m
     ).on('change', (event) ->
       self.setAccountTo($(this).select2('data') )
     )
@@ -103,9 +104,11 @@
       amt = '  Saldo: ' + _b.ntc(val.amount) + ''

-  ['',txt, " ", val.to_s,
-    amt, ' ',
+  ['',txt, " ", val.to_s,
+   amt, ' ',
    val.currency, ''].join('')
+
+

 App.Payment = Payment
 App.IncomePayment = IncomePayment


update zooniverse remote host url

update zooniverse remote host url

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -6,4 +6,4 @@
 #= require 'lib/proto'
 #= require 'lib/modernizr-custom.js'
 #= require 'components'
-window.projectBuilderUrl = 'https://google.com'+window.projectBuilderUrl = 'https://www.zooniverse.org/projects/haasarts/ensemble-at-yale/classify'


Set finite timeout for framestream

Set finite timeout for framestream

--- coffees/src/streaming_api.coffee
+++ coffees/src/streaming_api.coffee
@@ -3,7 +3,7 @@
 module.exports = ->
   router = express.Router()
   router.get '/framestream', (req, res) ->
-    req.socket.setTimeout Infinity
+    req.socket.setTimeout 1000*1000

     messageCount = 1
     subscriberCount = 0


MO Chart: fixes, better x-axis labels

MO Chart: fixes, better x-axis labels

--- app/assets/javascripts/d3/merit_order.coffee
+++ app/assets/javascripts/d3/merit_order.coffee
@@ -33,10 +33,7 @@
     value_x: => @gquery_x.safe_future_value()

     value_y: =>
-      if @get('key') == 'must_run'
-        1
-      else
-        @original_y_value()
+      if @get('key') == 'must_run' then 1 else @original_y_value()

     original_y_value: => @gquery_y.safe_future_value()

@@ -60,16 +57,17 @@
       margins =
         top: 10
         bottom: 40
-        left: 40
+        left: 50
         right: 10
       @margin = 50
       @width = @available_width() - (margins.left + margins.right)
       @height = 310 - (margins.top + margins.bottom)
-      @series_height = 280
+      @series_height = 260
       @x = d3.scale.linear().domain([0, 100]).range([0, @width])
       @y = d3.scale.linear().domain([0, 100]).range([0, @series_height])
       @inverted_y = @y.copy().range([@series_height, 0])
       @x_axis = d3.svg.axis().scale(@x).ticks(4).orient("bottom")
+        .tickFormat((x) => Metric.autoscale_value x, 'MW')
       @y_axis = d3.svg.axis().scale(@inverted_y).ticks(4).orient("left")
       @svg = d3.select(@container_selector())
         .append("svg:svg")
@@ -81,7 +79,7 @@
       # axis
       @svg.append("svg:g")
         .attr("class", "x_axis")
-        .attr("transform", "translate(0, #{@height})")
+        .attr("transform", "translate(0, #{@series_height})")
         .call(@x_axis)
       @svg.append("svg:g")
         .attr("class", "y_axis")
@@ -89,15 +87,15 @@
       @svg.append("svg:text")
         .text('Operating Costs [EUR/MWh]')
         .attr("x", @height / -2)
-        .attr("y", -25)
+        .attr("y", -35)
         .attr("text-anchor", "middle")
         .attr("class", "axis_label")
         .attr("transform", "rotate(270)")

       @svg.append("svg:text")
-        .text('Installed Capacity [MW]')
+        .text('Installed Capacity')
         .attr("x", @width / 2)
-        .attr("y", @series_height + 10)
+        .attr("y", @series_height + 30)
         .attr("text-anchor", "middle")
         .attr("class", "axis_label")

@@ -172,9 +170,9 @@
         )
         .attr("x", (d) => @x(d.get 'x_offset'))
         .attr("data-tooltip-text", (d) ->
-          html = "Installed capacity: #{Metric.autoscale_value(d.value_x() * 1000000, 'MW', 2)}"
-          html += "
"
-          html += "Operating costs: #{Metric.autoscale_value d.original_y_value(), 'euro', 2}"
+          html = "Installed capacity: #{Metric.autoscale_value(d.value_x() * 1000000, 'MW', 2)}
+

+                  Operating costs: #{Metric.autoscale_value d.original_y_value(), 'euro', 2}"
           if d.get('key') == 'must_run'
             html += '*
* Must run plants do not participate in merit order'
           html
@@ -185,9 +183,7 @@
   model: D3.merit_order.Node

   max_height: =>
-    _.max(@map (i) -> i.value_y())
+    d3.max @models, (i) -> i.value_y()

   total_width: =>
-    tot = 0
-    @each (i) -> tot += i.value_x()
-    tot
+    d3.sum @models, (n) -> n.value_x()


Remove warning banner

Remove warning banner

--- app/assets/javascripts/file_browse.js.coffee
+++ app/assets/javascripts/file_browse.js.coffee
@@ -19,13 +19,6 @@
   initialized = false
   $('#browse-btn').browseEverything()
     .show ->
-      alertMsg = $('<div>')
-        .html('Warning! Uploading too many files at once can lead to ingest failures.')
-        .addClass('alert')
-        .addClass('alert-danger')
-        .css('margin','3px')
-        .css('text-align','center')
-      $('.ev-body').prepend(alertMsg)
       unless $('#browse-everything input[name=workflow]').length > 0
         skip_box = $('#web_upload input[name=workflow]').closest('span')
           .clone().removeClass().css('margin-right','10px')


1 week left :P

1 week left :P

--- views/application.js.coffee
+++ views/application.js.coffee
@@ -116,7 +116,7 @@
       hours = Math.floor diff % 86400 / 3600
       minutes = Math.floor diff % 3600 / 60
       secs = Math.floor diff % 60
-      countdown.html weeks + ' weeks ' + days + ' days ' + hours + ' hours ' + minutes + ' minutes ' + secs + ' seconds'
+      countdown.html weeks + ' week ' + days + ' days ' + hours + ' hours ' + minutes + ' minutes ' + secs + ' seconds'
       setTimeout tick, 1000
     tick()


removed no longer used random number generator

--- app/assets/javascripts/initialiser.js.coffee
+++ app/assets/javascripts/initialiser.js.coffee
@@ -46,8 +46,6 @@

   initialiseProtectedCoverageViz: ($vizContainer) ->
     return false if $vizContainer.length == 0 or not Modernizr.svg?
-    rand = ->
-      Math.round(Math.random() * 100) + 1
     $vizContainer.find('.viz').each (idx, el) ->
       value = $(el).attr('data-value')
       data = [


Naming normalization

Naming normalization

--- server/packages/user/logout.coffee
+++ server/packages/user/logout.coffee
@@ -12,7 +12,7 @@
 exports.$endpoint = -> (req, fn) ->

 	# Log out.
-	req.logout().nodeify fn
+	req.logOut().nodeify fn

 # ## Implements hook `initialize`
 # Monkey patch http.IncomingMessage.prototype.logout to run our middleware,


fixed multiple search results showing up

--- app/assets/javascripts/main/paginator.coffee
+++ app/assets/javascripts/main/paginator.coffee
@@ -57,6 +57,7 @@

     @dataRetrievalPromise = $.ajax(ajaxOptions)
     @dataRetrievalPromise.then(@handleData)
+

   handleData: (responseData) =>

@@ -141,7 +142,7 @@
     for element in jsonArray
       htmlArray.push(@generateHTML(element))

-    @tbody.append(htmlArray.join(""))
+    @tbody.html(htmlArray.join(""))

   JSONcontains : (data, query) ->


slide down for export menu

slide down for export menu

--- app/assets/javascripts/settlements.js.coffee
+++ app/assets/javascripts/settlements.js.coffee
@@ -18,6 +18,8 @@
     $('#create-settlement').click -> this.createCallback
     $('.selectbox-wrapper').on 'click', =>
       this.sizeSettlement()
+    $('li.export').click ->
+      $(this).find($('ul.export-menu')).slideToggle(200)

   initRandcheck: ->
     $('.randcheck.settlement-name').click =>


put a LOT more info into the generated function names

--- lib/echo-util.coffee
+++ lib/echo-util.coffee
@@ -14,13 +14,15 @@
         gen += 1
         name

-exports.genGlobalFunctionName = (x) ->
-        name =  "__ejs_function_#{x}_#{gen}"
+exports.genGlobalFunctionName = (x, filename) ->
+        prefix = if filename? then "__ejs[#{filename}]" else "__ejs_fn"
+        name =  "#{prefix}_#{x}_#{gen}"
         gen += 1
         name

-exports.genAnonymousFunctionName = ->
-        name =  "__ejs_anonymous_#{gen}"
+exports.genAnonymousFunctionName = (filename) ->
+        prefix = if filename? then "__ejs[#{filename}]_%anon" else "__ejs_%anon"
+        name =  "#{prefix}_#{gen}"
         gen += 1
         name


add thumbnail image to search index

add thumbnail image to search index

--- api/apps/articles/model/save.coffee
+++ api/apps/articles/model/save.coffee
@@ -172,6 +172,7 @@
       featured: article.featured
       tags: article.tags
       body: sections and stripHtmlTags(sections.join(' ')) or ''
+      image_url: article.thumbnail_image
     , (error, response) ->
       console.log('ElasticsearchIndexingError: Article ' + article.id + ' : ' + error) if error
   )


TEST - updated to latest CaffeineMc module resolution

--- test/tests/CaffeineScript/Parser/Require.coffee
+++ test/tests/CaffeineScript/Parser/Require.coffee
@@ -26,7 +26,7 @@
   local:
     basic:
       "&Lib":                   "require('./Lib');"
-      "&Source":                "require('../../source');"
+      "&Source":                "require('../');"

     reservedWords:
       "&package":               "require('../../package');"


KiteSubscription: add method to create plan

--- workers/social/lib/social/models/kite.coffee
+++ workers/social/lib/social/models/kite.coffee
@@ -76,6 +76,15 @@
         as          : "owner"
       }, (err, rel) =>
         return  callback new KodingError err if err
-        @remove (err, res)->
-          return  callback err, res
+        if rel
+          @remove (err, res)->
+            return  callback err, res

+  createPlan: permit 'create kite',
+    success: (client, formData, callback)->
+      formData.tags = ["kite"]
+      JPaymentPlan  = require './payment/plan'
+      JPaymentPlan.create client.context.group, formData, (err, plan)=>
+        return  callback err if err
+        @addPlan plan, callback
+


use buildKey for prefixing name and host

--- metrics.coffee
+++ metrics.coffee
@@ -20,8 +20,8 @@

 module.exports = Metrics =
 	initialize: (_name) ->
-		name = _name
-		collectDefaultMetrics({ timeout: 5000, prefix: name + "_" })
+		name = _name + "_"
+		collectDefaultMetrics({ timeout: 5000, prefix: name+"_"})

 	registerDestructor: (func) ->
 		destructors.push func
@@ -46,7 +46,7 @@
 		key = this.sanitizeKey(key)
 		if !counters[key]
 			counters[key] = new prom.Counter({
-		  		name: key,
+		  		name: buildKey(key),
 				help: key,
 				labelNames: ['name','host']
 			})
@@ -73,7 +73,7 @@
 		key = this.sanitizeKey(key)
 		if !gauges[key]
 			gauges[key] = new prom.Gauge({
-		  		name: key,
+		  		name: buildKey(key),
 				help: key,
 				labelNames: ['name','host']
 			})
@@ -84,7 +84,7 @@
 		key = this.sanitizeKey(key)
 		if !gauges[key]
 			gauges[key] = new prom.Gauge({
-		  		name: key,
+		  		name: buildKey(key),
 				help: key,
 				labelNames: ['name','host']
 			})


guard against null and undefined keys in `\cite{}` autocomplete

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -66,12 +66,13 @@
 							}
 							if references.keys and references.keys.length > 0
 								references.keys.forEach (key) ->
-									result.push({
-										caption: "\\#{commandName}{#{previousArgsCaption}#{key}",
-										value: "\\#{commandName}{#{previousArgs}#{key}",
-										meta: "reference",
-										score: 10000
-									})
+									if !(key in [null, undefined])
+										result.push({
+											caption: "\\#{commandName}{#{previousArgsCaption}#{key}",
+											value: "\\#{commandName}{#{previousArgs}#{key}",
+											meta: "reference",
+											score: 10000
+										})
 								callback null, result
 							else
 								callback null, result


Activity: fixed the temporary fix for creating #public and #koding topics

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -5,6 +5,7 @@
     name         : 'Activity'
     searchRoute  : '/Activity?q=:text:'

+  firstFetch = yes

   constructor: (options = {}) ->

@@ -20,6 +21,13 @@
     warn 'dock.getView().show()'

     @on 'LazyLoadThresholdReached', @getView().bound 'lazyLoadThresholdReached'
+
+    # FIXME
+    # remove this once there are koding and public channels in default db setup
+    # otherwise this will continue pollute your feeds - SY
+    if firstFetch
+      {generatePassword, getRandomNumber} = KD.utils
+      KD.singletons.socialapi.message.post body: "Hello world, #{generatePassword getRandomNumber(7), yes} #koding #public", log

   post: (options = {}, callback = noop) ->
@@ -66,7 +74,6 @@
     socialapi.message.sendPrivateMessage options, callback

-  firstFetch = yes

   fetch: ({channelId, from, limit}, callback = noop) ->

@@ -74,13 +81,6 @@
     {socialapi} = KD.singletons
     {socialApiChannelId} = KD.getGroup()
     id ?= socialApiChannelId
-
-    # FIXME
-    # remove this once there are koding and public channels in default db setup
-    # otherwise this will continue pollute your feeds - SY
-    if firstFetch
-      {generatePassword, getRandomNumber} = KD.utils
-      KD.singletons.socialapi.message.post body: "Hello world, #{generatePassword getRandomNumber(7), yes} #koding #public", log

     if firstFetch and socialapi.getPrefetchedData('navigated').length > 0
       messages   = socialapi.getPrefetchedData 'navigated'


isMyNotification: export with name

isMyNotification: export with name

--- client/app/lib/util/isMyNotification.coffee
+++ client/app/lib/util/isMyNotification.coffee
@@ -1,6 +1,6 @@
 whoami = require './whoami'

-module.exports = (notification = {}) ->
+module.exports = isMyNotification = (notification = {}) ->

   { accountId } = notification.channelMessage


Don't repeat typing if we don't have any text

--- lib/operators/input.coffee
+++ lib/operators/input.coffee
@@ -16,6 +16,7 @@

   execute: ->
     if @typingCompleted
+      return unless @typedText? and @typedText.length > 0
       @undoTransaction =>
         @editor.getBuffer().insert(@editor.getCursorBufferPosition(), @typedText, true)
     else


user: allow login with username or email

--- client/landing/Login/Views/loginform.coffee
+++ client/landing/Login/Views/loginform.coffee
@@ -16,7 +16,7 @@
       inputOptions    :
         name          : "username"
         forceCase     : "lowercase"
-        placeholder   : "username"
+        placeholder   : "username or email"
         testPath      : "login-form-username"
         validate      :
           event       : 'blur'


Narrow logo so it's not as high

Narrow logo so it's not as high

--- client/skr/screens/locations/Locations.cjsx
+++ client/skr/screens/locations/Locations.cjsx
@@ -22,7 +22,7 @@
                 <LC.Input sm=6 name='name' model={@location} />
             </BS.Row>
             <BS.Row>
-                <LC.ImageSaver label='Logo' sm=4 model={@location} name='logo' />
+                <LC.ImageSaver label='Logo' sm=3 model={@location} name='logo' />
             </BS.Row>
             <BS.Row>
                 <SC.Address lg=6 model={@location.address}  />


Fix deprecations

Fix deprecations

--- lib/file-view.coffee
+++ lib/file-view.coffee
@@ -56,11 +56,11 @@

   serializeEditorState: (editor) ->
     bufferRanges: editor.getSelectedBufferRanges()
-    scrollTop: editor.getScrollTop()
+    scrollTop: atom.views.getView(editor).getScrollTop()

   deserializeEditorState: (editor, {bufferRanges, scrollTop}) ->
     editor.setSelectedBufferRanges(bufferRanges)
-    editor.setScrollTop(scrollTop)
+    atom.views.getView(editor).setScrollTop(scrollTop)

   getEditor: -> atom.workspace.getActiveTextEditor()


none existe @maxItem property but the @maxItems

--- lib/file-view.coffee
+++ lib/file-view.coffee
@@ -44,7 +44,7 @@
     @list.empty()
     @setLoading('Generating symbols\u2026')
     if tags = @cachedTags[filePath]
-      @maxItem = Infinity
+      @maxItems = Infinity
       @setItems(tags)
     else
       @generateTags(filePath)
@@ -52,5 +52,5 @@
   generateTags: (filePath) ->
     new TagGenerator(filePath, @getScopeName()).generate().done (tags) =>
       @cachedTags[filePath] = tags
-      @maxItem = Infinity
+      @maxItems = Infinity
       @setItems(tags)


Only update portal lights when they actually change

--- components/AnimatePortals.coffee
+++ components/AnimatePortals.coffee
@@ -11,10 +11,10 @@
   c.outPorts.add 'pixel',
     datatype: 'array'
   c.state = {}
-  c.second = false
+  c.sent = {}
   c.tearDown = (callback) ->
     c.state = {}
-    c.second = false
+    c.sent = {}
     callback()
   c.process (input, output) ->
     if input.hasData 'light'
@@ -25,14 +25,15 @@
     return unless input.hasData 'step'
     input.getData 'step'
     for portal, state of c.state
-      if c.second
-        value = state[1]
-      else
+      # Start with first color
+      value = state[1]
+      if value is c.sent[portal]
+        # If previous sent was first, go with second color
         value = state[2]
+      # Don't send if color isn't changing
+      continue if value is c.sent[portal]
       output.send
         pixel: [portal, value]
-    if c.second
-      c.second = false
-    else
-      c.second = true
-    output.done()+      # Store sent value
+      c.sent[portal] = value
+    output.done()


Deprecation warnings removed

Deprecation warnings removed

--- lib/rspec-tree-view.coffee
+++ lib/rspec-tree-view.coffee
@@ -224,10 +224,10 @@
   prepareKeyStrokesText: ->
     toggleSpecFileKeyBindings = atom.keymaps.findKeyBindings({command:'rspec-tree-runner:toggle-spec-file' })
     runTestsKeyBindings = atom.keymaps.findKeyBindings({command:'rspec-tree-runner:run-tests' })
-
-    toggleSpecFileKeyStroke = if (toggleSpecFileKeyBindings and toggleSpecFileKeyBindings.length > 0) then toggleSpecFileKeyBindings[0].keystroke else 'not def'
-
-    runTestsKeyStroke = if (runTestsKeyBindings and runTestsKeyBindings.length > 0) then runTestsKeyBindings[0].keystroke else 'not def'
+
+    toggleSpecFileKeyStroke = if (toggleSpecFileKeyBindings and toggleSpecFileKeyBindings.length > 0) then toggleSpecFileKeyBindings[0].keystrokes else 'not def'
+
+    runTestsKeyStroke = if (runTestsKeyBindings and runTestsKeyBindings.length > 0) then runTestsKeyBindings[0].keystrokes else 'not def'

     this.find('.toggle-file-hint').html("Press #{toggleSpecFileKeyStroke} to toggle/create")
     this.find('h3.run-tests-hint').html("Press #{runTestsKeyStroke} to run tests")


update build info while running

update build info while running

--- app/assets/javascripts/build.coffee
+++ app/assets/javascripts/build.coffee
@@ -46,6 +46,7 @@
                 $('#build-report .results').html build.report_html
                 $('#build-report .results').append ''
                 Build.styleResults()
+                Build.updateInfo()
                 Build.checkAutoscroll()
               else
                 Turbolinks.visit build_url
@@ -88,5 +89,14 @@
       $('#build-report .example_group.pending').show()
       $fails.siblings(".example.passed").show()

+  Build.updateInfo = () ->
+    if $('.build-widget p.specs_run').length == 1
+      $('.build-widget > .specs_run > .attr-value').text($('.example').length)
+      $('.build-widget > .specs_run > .attr-value').text($('.example.failed').length)
+    else
+      $('.build-widget').append('

')
+      $('.build-widget').append('

')
+      Build.updateInfo()
+

 @Build = Build


Remove unnecessary if

Remove unnecessary if

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -134,7 +134,7 @@
     editor = atom.workspaceView.getActivePaneItem()
     selectedText = editor?.getSelectedText?()
     if selectedText and selectedText.indexOf('\n') < 0
-      @findEditor.setText(editor.getSelectedText()) if editor?.getSelectedText?()
+      @findEditor.setText(editor.getSelectedText())

     @attach() if not @hasParent()
     @findEditor.focus()


server - fixed ga auth bug, calling .discover too many times

--- server/ga.coffee
+++ server/ga.coffee
@@ -1,6 +1,6 @@
 # Vendor
 Promise = require 'bluebird'
-gapi = Promise.promisifyAll require "googleapis"
+gapi = require "googleapis"
 _find = require 'lodash-node/modern/collections/find' # to be replaced w/ array.prototype.find w/ node --harmony
 gh = require './github'

@@ -19,58 +19,50 @@

 # define auth obj; used for init auth & fetches
 authClient = new gapi.auth.JWT(
-    config.ga.clientEmail,
-    config.ga.privateKeyPath,
-    null, # key as string, not needed due to key file
-    [config.ga.scopeUri]
+  config.ga.clientEmail,
+  if process.env.NODE_ENV is 'development' then config.ga.privateKeyPath else null, # key as .pem file
+  if process.env.NODE_ENV is 'production' then config.ga.privateKeyContent else null,
+  [config.ga.scopeUri]
 )

 # auth on bootstrap
 authPromise = new Promise (resolve, reject) ->
   console.log "[INFO] OAuthing w/ GA..."
-  # console.log "console.log config.ga.privateKeyPath = " + config.ga.privateKeyPath
-  # returns expires_in: 1395623939 and refresh_token: 'jwt-placeholder', not sure if 16 days or 44 yrs -_-
-  if !config.ga.privateKeyPath?
-    error = new Error "[ERROR] process.env.APP_GA_KEY_PATH mismatch or #{ config.ga.privateKeyPath }"
-    reject error
-  else
-    authClient.authorize (err, token) ->
-      if err
-        error = new Error "[ERROR] OAuth, err = #{ err }"
-        console.error error
-        reject error
-      else
-        console.info "[SUCCESS] server OAuthed w/ GA."
-        resolve token
-      return
+  authClient.authorize (err, token) ->
+    # returns expires_in: 1395623939 and refresh_token: 'jwt-placeholder', not sure if 16 days or 44 yrs -_-
+    if err
+      error = new Error "[ERROR] OAuth error; err = #{ err }"
+      reject error
+    else
+      console.info "[SUCCESS] server OAuthed w/ GA."
+      gapi.discover('analytics', 'v3').withAuthClient(authClient).execute (err, client) ->
+        if err
+          error = new Error "[ERROR] gapi.discover.execute, err = #{ err }"
+          reject error
+        else resolve client # reuse this client
+        return
+    return
   return

 fetch = (key) ->
-  ->
+  (client) -> # client returned from gapi.discover.execute
     query = queries[key]
-    promises = []
+    queryPromises = []

     query.queryObjs.forEach (queryObj) ->
       promise = new Promise (resolve, reject) ->
-        gapi.discover('analytics', 'v3').execute (err, client) ->
+        client.analytics.data.ga.get(queryObj).execute (err, result) ->
           if err
-            error = new Error "[ERROR] error with gapi.discover.execute, err = #{ err }"
+            error = new Error "[ERROR] client.analytics.data.ga.get, err = #{ err.message }"
             reject error
-          else
-            client.analytics.data.ga.get queryObj
-              .withAuthClient authClient
-              .execute (err, result) ->
-                if err
-                  error = new Error "[ERROR] error when fetching via GA API, err = #{ err }"
-                  reject error
-                else resolve result
-                return
+          else resolve result
           return
         return
-      promises.push promise
-      return
-
-    Promise.all(promises).then(query.transform).catch (err) -> console.error err; return
+      queryPromises.push promise
+      return
+
+    Promise.all(queryPromises).then query.transform
+    # err catched in cache.coffee .catch (err) -> console.error err; return

 ###
 # define queries
@@ -88,13 +80,15 @@
     }
   ]
   transform: (data) ->
-    new Promise (resolve, reject) ->
-      result = data[0].rows
-      result.forEach (d) ->
-        d[0] = if d[0].indexOf('New') != -1 then 'N' else 'E'
-        d[2] = +d[2]
-        return
-      resolve result; return
+#    console.log "transforming users"
+#    console.log data[0].rows[1]
+
+    result = data[0].rows
+    result.forEach (d) ->
+      d[0] = if d[0].indexOf('New') != -1 then 'N' else 'E'
+      d[2] = +d[2]
+      return
+    result

 queries.cmds =
   queryObjs: [
@@ -114,9 +108,11 @@
     }
   ]
   transform: (data) ->
+    # set order for display
     order = {}
     ['Install', 'Uninstall', 'Register', 'Unregister', 'Info', 'Search'].forEach (cmd, i) -> order[cmd] = i; return
-    icon =
+
+    icon = # define font awesome icons
       Install: 'download'
       Uninstall: 'trash-o'
       Register: 'pencil'
@@ -124,62 +120,60 @@
       Info: 'info'
       Search: 'search'

-    new Promise (resolve, reject) ->
-      current = data[0].rows.filter (cmd) -> cmd[0] != "(not set)"
-      prior = data[1].rows
-
-      _transform = (d) ->
-        cmdName = util.removeSlash d[0]
-        cmdName = cmdName.charAt(0).toUpperCase() + cmdName.slice 1 # Cap Case
-        d[0] = cmdName
-        d[1] = +d[1]
-        d[2] = +d[2]
-        return
-      current.forEach _transform
-      prior.forEach _transform
-
-      cmdCheck = (d) -> d[0].indexOf("ed") is -1 # and d[0] != "Searched"
-      # keep only cmds that isn't called on success; 'Searched' is deprecated
-      result = current.filter (d) -> cmdCheck d
-        .map (d) ->
-          cmd: d[0]
-          order: order[d[0]]
-          icon: icon[d[0]]
-          metrics: [
-            {metric: 'users', order: 1, current: d[1]} # ga:users
-            {metric: 'uses', order: 2, current: d[2]} # ga:pageviews
-          ]
-
-      getValue = (cmd, period, ed, valueType) ->
-        ed = if ed then 'ed' else ''
-        i = if valueType is 'users' then 1 else 2 # else pkgs
-        # catch edge case in case new cmd tracked and no prior history
-        try
-          _find(period, (d) -> d[0] is cmd.cmd + ed)[i]
-        catch error
-          0 # if not found, cmd tracking wasn't implemented
-
-      result.forEach (cmd) ->
-        # cmd with pkgs count ('-ed')
-        if ["Install", "Uninstall", "Register", "Unregister"].indexOf(cmd.cmd) != -1
-          cmd.metrics.push {
-            metric: 'pkgs', order: 3
-            current: getValue cmd, current, true, 'pkgs'
-          }
-
-        cmd.metrics.forEach (metric) ->
-          metric.prior = getValue cmd, prior, (if metric.metric is 'pkgs' then true else false), metric.metric
-          metric.delta = metric.current / metric.prior - 1
-          return
-
-        return
-
-      resolve result; return
+    current = data[0].rows.filter (cmd) -> cmd[0] != "(not set)"
+    prior = data[1].rows
+
+    _transform = (d) ->
+      cmdName = util.removeSlash d[0]
+      cmdName = cmdName.charAt(0).toUpperCase() + cmdName.slice 1 # Cap Case
+      d[0] = cmdName
+      d[1] = +d[1]
+      d[2] = +d[2]
+      return
+    current.forEach _transform
+    prior.forEach _transform
+
+    cmdCheck = (d) -> d[0].indexOf("ed") is -1 # and d[0] != "Searched"
+    # keep only cmds that isn't called on success; 'Searched' is deprecated
+    result = current.filter (d) -> cmdCheck d
+      .map (d) ->
+        cmd: d[0]
+        order: order[d[0]]
+        icon: icon[d[0]]
+        metrics: [
+          {metric: 'users', order: 1, current: d[1]} # ga:users
+          {metric: 'uses', order: 2, current: d[2]} # ga:pageviews
+        ]
+
+    getValue = (cmd, period, ed, valueType) ->
+      ed = if ed then 'ed' else ''
+      i = if valueType is 'users' then 1 else 2 # else pkgs
+      # catch edge case in case new cmd tracked and no prior history
+      try
+        _find(period, (d) -> d[0] is cmd.cmd + ed)[i]
+      catch error
+        0 # if not found, cmd tracking wasn't implemented
+
+    result.forEach (cmd) ->
+      # cmd with pkgs count ('-ed')
+      if ["Install", "Uninstall", "Register", "Unregister"].indexOf(cmd.cmd) != -1
+        cmd.metrics.push {
+          metric: 'pkgs', order: 3
+          current: getValue cmd, current, true, 'pkgs'
+        }
+
+      cmd.metrics.forEach (metric) ->
+        metric.prior = getValue cmd, prior, (if metric.metric is 'pkgs' then true else false), metric.metric
+        metric.delta = metric.current / metric.prior - 1
+        return
+
+      return
+
+    result

 queries.pkgs =
   # 'package' is a reserved word in JS
   # only want to pull pkgs w/ >= 5 installs, which is around the 3500th pkg sorted by installs
-  # hence max-results = 5000
   queryObjs: [
     { # current week
       'ids': 'ga:' + config.ga.profile
@@ -187,7 +181,7 @@
       'end-date': 'yesterday'
       'metrics': 'ga:users,ga:pageviews'
       'dimensions': 'ga:pagePathLevel2'
-      'filters': 'ga:pagePathLevel1=@installed' # =@ contains substring
+      'filters': 'ga:pagePathLevel1=@installed' # =@ contains substring, don't use url encoding '%3D@'
       'sort': '-ga:pageviews'
       'max-results': 3500
     }
@@ -197,45 +191,42 @@
       'end-date': '8daysAgo'
       'metrics': 'ga:users,ga:pageviews'
       'dimensions': 'ga:pagePathLevel2'
-      'filters': 'ga:pagePathLevel1=@installed' # =@ contains substring
+      'filters': 'ga:pagePathLevel1=@installed'
       'sort': '-ga:pageviews'
       'max-results': 3500
     }
   ]
   transform: (data) ->
-    new Promise (resolve, reject) ->
-      current = data[0].rows[..19] # TODO: from / to as arg
-      prior = data[1].rows[..29]
-
-      _transform = (d, i) ->
-        d[0] = util.removeSlash d[0]
-        d[1] = +d[1]; d[2] = +d[2]
-        d.push i + 1 # rank
-        return
-      current.forEach _transform
-      prior.forEach _transform
-
-      result = current.map (d) ->
-        bName: d[0]
-        bRank: current: d[3]
-        bUsers: current: d[1] # ga:users
-        bInstalls: current: d[2] # ga:pageviews
-
-      ghPromises = []
-      result.forEach (pkg) ->
-        priorPkg = _find prior, (d) -> d[0] is pkg.bName
-        if priorPkg?
-          pkg.bRank.prior = priorPkg[3]
-          pkg.bUsers.prior = priorPkg[1]
-          pkg.bInstalls.prior = priorPkg[2]
-        else
-          err = new Error "[ERROR] no prior period data for package #{ pkg.bName }"
-          console.error err
-        ghPromises.push gh.appendData pkg
-        return
-
-      Promise.all(ghPromises).then -> resolve result; return
-      return
+    current = data[0].rows[..19] # TODO: ranking range as arg
+    prior = data[1].rows[..29]
+
+    _transform = (d, i) ->
+      d[0] = util.removeSlash d[0]
+      d[1] = +d[1]; d[2] = +d[2]
+      d.push i + 1 # rank
+      return
+    current.forEach _transform
+    prior.forEach _transform
+
+    result = current.map (d) ->
+      bName: d[0]
+      bRank: current: d[3]
+      bUsers: current: d[1] # ga:users
+      bInstalls: current: d[2] # ga:pageviews
+
+    ghPromises = []
+    result.forEach (pkg) ->
+      priorPkg = _find prior, (d) -> d[0] is pkg.bName
+      if priorPkg?
+        pkg.bRank.prior = priorPkg[3]
+        pkg.bUsers.prior = priorPkg[1]
+        pkg.bInstalls.prior = priorPkg[2]
+      else
+        throw new Error "[ERROR] no prior period data for package #{ pkg.bName }"
+      ghPromises.push gh.appendData pkg
+      return
+
+    Promise.all(ghPromises).then -> result

 queries.geo =
   queryObjs: [
@@ -249,31 +240,29 @@
     }
   ]
   transform: (data) ->
-    new Promise (resolve) ->
-      current = data[0].rows
-      geoPromises = []
-
-      # remove (not set) country & country w/ just 1 user
-      current = current.filter (country) ->
-        country[0] != "(not set)" and +country[1] > 1
-
-      result = current.map (d) ->
-        name: d[0]
-        isoCode: geo.getCode d[0] # get ISO 3166-1 alpha-3 code
-        users: +d[1]
-
-      result.forEach (country) ->
-        geoPromise = geo.getPop(country.isoCode).then (pop) ->
-          country.density = Math.ceil(country.users / pop * 1000000)
-          return
-        # get population from world bank api then calc bower user density per 1m pop
-        geoPromises.push geoPromise
-        return
-
-      Promise.all(geoPromises).then ->
-        result.sort (a, b) -> b.density - a.density
-        resolve result
-      return
+    current = data[0].rows
+    geoPromises = []
+
+    # remove (not set) country & country w/ just 1 user
+    current = current.filter (country) ->
+      country[0] != "(not set)" and +country[1] > 1
+
+    result = current.map (d) ->
+      name: d[0]
+      isoCode: geo.getCode d[0] # get ISO 3166-1 alpha-3 code
+      users: +d[1]
+
+    result.forEach (country) ->
+      geoPromise = geo.getPop(country.isoCode).then (pop) ->
+        country.density = Math.ceil(country.users / pop * 1000000)
+        return
+      # get population from world bank api then calc bower user density per 1m pop
+      geoPromises.push geoPromise
+      return
+
+    Promise.all geoPromises
+      .call 'sort', (a, b) -> b.density - a.density
+      .then -> result

 # ==========


Fix list of users in room

Fix list of users in room

--- client/views/app/chatWindowDashboard.coffee
+++ client/views/app/chatWindowDashboard.coffee
@@ -245,12 +245,12 @@
 			totalOnline: 0
 			users: []

-		if room?.uids
+		if room?.usernames
 			# UserManager.addUser room.uids

 			filter =
-				_id:
-					$in: room?.uids
+				username:
+					$in: room.usernames

 			# unless Template.instance().showUsersOffline.get()
 			# 	filter.status = { $ne: 'offline' }


websocket stuff

websocket stuff

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -217,8 +217,7 @@

 	PRELOAD_TOLERANCE : 0.9
 	PRELOAD_RADIUS : 20
-	preloadVertices : []
-
+
 	# Use this method to let us know when you've changed your spot. Then we'll try to
 	# preload some data.
 	#
@@ -238,36 +237,38 @@

 		_.defer =>

-			_preloadVertices = @preloadVertices
-			_preloadRadius   = @PRELOAD_RADIUS
-			# Looks like `preload_vertices` hasn't been populated yet.
-			# This is what ppl call lazy initialization.
-			if _preloadVertices.length == 0
-				for z in [-1..10] by 3
-					for x in [-_preloadRadius.._preloadRadius] by 5
-						for y in [-_preloadRadius.._preloadRadius] by 5
-							_preloadVertices.push x, y, z
-
-			preloadCheckHits     = 0
-			preloadCheckCount    = 0
-			preloadCheckVertices = M4x4.transformPointsAffine matrix, _preloadVertices
-
-			for i in [0...preloadCheckVertices.length] by 3
-				x = preloadCheckVertices[i]
-				y = preloadCheckVertices[i + 1]
-				z = preloadCheckVertices[i + 2]
-
-				if x >= 0 and y >= 0 and z >= 0
-					preloadCheckCount++
-					preloadCheckHits++ if @getColor(x, y, z) > 0
-
-			if preloadCheckHits / preloadCheckCount < @PRELOAD_TOLERANCE
+			_preloadRadius = @PRELOAD_RADIUS
+
+			if @preloadTest(matrix, -_preloadRadius, -_preloadRadius, 2 * _preloadRadius) < @PRELOAD_TOLERANCE
 				@pull(matrix, (err) ->
 					done()
 					callback() if !err and callback
 				)
 			else
 				done()
+
+	preloadTest : (matrix, x0, y0, z, width, sparse = 5) ->
+
+		vertices = []
+		for x in [x0...(x0 + width)] by sparse
+			for y in [y0...(y0 + width)] by sparse
+				vertices.push x, y, z
+
+		vertices = M4x4.transformPointsAffine matrix, vertices
+
+		count = hits = 0
+		for i in [0...vertices.length] by 3
+			x = vertices[i]
+			y = vertices[i + 1]
+			z = vertices[i + 2]
+
+			if x >= 0 and y >= 0 and z >= 0
+				count++
+				hits++ if @getColor(x, y, z) > 0
+
+		hits / count
+
+

 	pull : (matrix, callback) ->
@@ -304,7 +305,7 @@
 		_WebSocket = if window['MozWebSocket'] then MozWebSocket else WebSocket

 		unless @loadSocket
-			socket = @loadSocket = new _WebSocket("ws://#{document.location.host}/binary/socket")
+			socket = @loadSocket = new _WebSocket("ws://#{document.location.host}/binary/ws")
 			socket.binaryType = 'arraybuffer'
 			socket.onclose = (code, reason) ->
 				alert("#{code}: #{reason}")
@@ -332,7 +333,7 @@
 		transmitPackage = new Float32Array(17)
 		transmitPackage[0] = socketHandle
 		transmitPackage.set(matrix, 1)
-
+
 		if socket.readyState == _WebSocket.OPEN
 			socket.send(transmitPackage)
 		else
@@ -340,7 +341,7 @@
 				socket.send(transmitPackage)
 				socket.removeEventListener("open", socketOpenCallback, false)
 			socket.addEventListener("open", socketOpenCallback, false)
-
+

 	# Now comes the implementation of our internal data structure.
 	# `cube` is the main array. It actually represents a cuboid


Now using new class name.

Now using new class name.

--- spec/coffeescripts/translator.spec.coffee
+++ spec/coffeescripts/translator.spec.coffee
@@ -14,7 +14,7 @@
       exact: '{0} there are none|{3} there is a few|{10} there is max'
       complex: '{0} none|[1,10] there is some|[25, Inf] there is lots'

-  translator = new Translator translations
+  translator = new Scribe translations

   describe 'retrieving', ->
     it 'should retrieve a translation', ->


lol ruby

lol ruby

--- plugins/modes/modes.coffee
+++ plugins/modes/modes.coffee
@@ -10,6 +10,7 @@
   m: 'c_cpp'
   md: 'markdown'
   cs: 'csharp'
+  rb: 'ruby'

 modeForLanguage = (language) ->
   console.log "modeForLanguage(#{language})"


Update to use new prefix

Update to use new prefix

--- app/coffee/Repositories/Tags.coffee
+++ app/coffee/Repositories/Tags.coffee
@@ -69,10 +69,15 @@
 				name: name
 		}, callback

-metrics.timeAsyncMethod(Tags, 'getUserTags', 'Tags.getUserTags', logger)
-metrics.timeAsyncMethod(Tags, 'createTag', 'Tags.createTag', logger)
-metrics.timeAsyncMethod(Tags, 'addProjectToTag', 'Tags.addProjectToTag', logger)
-metrics.timeAsyncMethod(Tags, 'removeProjectFromTag', 'Tags.removeProjectFromTag', logger)
-metrics.timeAsyncMethod(Tags, 'removeProjectFromAllTags', 'Tags.removeProjectFromAllTags', logger)
-metrics.timeAsyncMethod(Tags, 'deleteTag', 'Tags.deleteTag', logger)
-metrics.timeAsyncMethod(Tags, 'renameTag', 'Tags.renameTag', logger)
+
+[
+	'getUserTags',
+	'createTag',
+	'addProjectToTag',
+	'removeProjectFromTag',
+	'removeProjectFromAllTags',
+	'deleteTag',
+	'renameTag'
+].map (_method) ->
+	metrics.timeAsyncMethod(Tags, _method, 'mongo.Tags', logger)
+


Escape project name in notifications to Slack

--- plugins/slack/index.coffee
+++ plugins/slack/index.coffee
@@ -16,7 +16,8 @@

   @receiveEvent = (config, event, callback) ->
     # Build the notification title
-    title = ["#{event.trigger.message} in #{event.error.releaseStage} from <#{event.project.url}|#{event.project.name}>"]
+    projectName = event.project.name.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
+    title = ["#{event.trigger.message} in #{event.error.releaseStage} from <#{event.project.url}|#{projectName}>"]
     title.push("in #{event.error.context}")
     title.push("<#{event.error.url}|(details)>")


Use Pane.activeView instead of Pane.viewForActiveItem()

viewForActiveItem() can create a view which we don't want to do
when responding to events in the status bar. Instead we just want
to access the already existing view for the active item.

--- lib/status-bar-view.coffee
+++ lib/status-bar-view.coffee
@@ -29,7 +29,7 @@
       @updatePathText()

     @subscribe @pane, 'cursor:moved', => @updateCursorPositionText()
-    @subscribe @grammarName, 'click', => @pane.viewForActiveItem().trigger 'grammar-selector:show'
+    @subscribe @grammarName, 'click', => @pane.activeView.trigger 'grammar-selector:show'
     @subscribe @pane, 'editor:grammar-changed', => @updateGrammarText()

     if git?
@@ -59,7 +59,7 @@
     @updateCursorPositionText()

   updateGrammarText: ->
-    grammar = @pane.viewForActiveItem().getGrammar?()
+    grammar = @pane.activeView.getGrammar?()
     if not grammar? or grammar is syntax.nullGrammar
       @grammarName.hide()
     else
@@ -131,7 +131,7 @@
       @currentPath.hide()

   updateCursorPositionText: ->
-    if position = @pane.viewForActiveItem().getCursorBufferPosition?()
+    if position = @pane.activeView.getCursorBufferPosition?()
       @cursorPosition.text("#{position.row + 1},#{position.column + 1}").show()
     else
       @cursorPosition.hide()


Define `Machine::getOwner` method

Define `Machine::getOwner` method

--- client/Main/providers/machine.coffee
+++ client/Main/providers/machine.coffee
@@ -102,3 +102,10 @@
         connect    : noop
         disconnect : noop
       }
+
+
+  getOwner: ->
+
+    switch @provider
+      when 'koding'
+        return @data.credential


Social: fix bug where /Activity/:slug was giving 404s for everything

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -50,10 +50,7 @@
       handleChannel 'chat', slug

     '/:name?/Activity/:slug?' : ({params:{name, slug}, query}) ->
-      {router, appManager} = KD.singletons
-      unless slug
-      then router.openSection 'Activity', name, query
-      else createContentDisplayHandler('Activity') arguments...
+      handleChannel 'post', slug

     '/:name?/Activity' : ({params:{name, slug}, query}) ->
       {router, appManager} = KD.singletons