Treat transition like a POJO.

Treat transition like a POJO.

cause it is.

--- app/assets/javascripts/routes/task_route.js.coffee
+++ app/assets/javascripts/routes/task_route.js.coffee
@@ -31,5 +31,5 @@

   actions:
     willTransition: (transition) ->
-      unless transition.get('targetName') == 'flow_manager'
+      unless transition.targetName == @controllerFor('application').get('overlayRedirect.firstObject')
         @controllerFor('application').set('cachedModel', null)


better indication that the upload has finished

--- app/assets/javascripts/smithy/asset_uploads.coffee
+++ app/assets/javascripts/smithy/asset_uploads.coffee
@@ -25,7 +25,7 @@
       total_uploaded += e.originalEvent.detail.file.size
       progress = parseInt(total_uploaded/total_size * 100, 10)
       $progress_bar.css('width', "#{progress}%").attr('aria-valuenow', progress)
-      setTimeout((-> $progress_bar.parent().remove()), 2000) if progress >= 100
+      setTimeout((-> $progress_bar.parent().remove()), 2000) !$upload_form.find("input.uploading").length
     $(document).on "upload:failed", $upload_form, (e) ->
       $(this).addClass("upload-failed").append("
Something went wrong, please check your connection and try again
")


Limit C&E at 64 Pokemon.

Limit C&E at 64 Pokemon.

128 would require capturing 7 Pokemon, which is more than you can
even fit in a team.

--- js/app.coffee
+++ js/app.coffee
@@ -109,7 +109,7 @@
     allowedPokemon = @getAllowedPokemon()
     i = 8
     result = []
-    while i <= allowedPokemon.length
+    while i <= allowedPokemon.length and i isnt 128
       result.push i
       i *= 2
     result


Lower-cased measurement units

Lower-cased measurement units

--- app/assets/javascripts/analysis/templates/area.coffee
+++ app/assets/javascripts/analysis/templates/area.coffee
@@ -35,7 +35,7 @@

-          <%= roundToDecimals(results.sum.area, 2) %> KM2
+          <%= roundToDecimals(results.sum.area, 2) %> km2

@@ -55,9 +55,9 @@

             Habitat
-            Area KM2
+            Area km2
             Area % of Tot.
-            C-Stock KG
+            C-Stock kg

           <% _.each(results.habitats, function(attributes, key) { %>


Cleaning up Scribe initialization.

Cleaning up Scribe initialization.

--- client/publication.coffee
+++ client/publication.coffee
@@ -1110,7 +1110,6 @@

   @_scribe.use Scribe.plugins['blockquote-command']()
   @_scribe.use Scribe.plugins['link-prompt-command']()
-  @_scribe.use Scribe.plugins['curly-quotes']()
   @_scribe.use Scribe.plugins['sanitizer']
     tags:
       p: {}
@@ -1171,7 +1170,6 @@
     ###

     if @local
-      # TODO: Set privacy settings
       Meteor.call 'create-annotation', @publication._id, body, getAnnotationDefaults().access, getAnnotationDefaults().groups, (error, annotationId) =>
         return Notify.meteorError error, true if error

@@ -1318,10 +1316,9 @@
   @_rendered = true

   @_scribe = new Scribe @find('.comment-content-editor'),
-    # No block elements, they would take up too much space
+    # We want comments to be oneline, so no block elements and no <br/>
     allowBlockElements: false

-  @_scribe.use Scribe.plugins['curly-quotes']()
   @_scribe.use Scribe.plugins['sanitizer']
     tags:
       b: {}


Fix Earned Achievements Check

Fix Earned Achievements Check

--- app/views/play/level/modal/HeroVictoryModal.coffee
+++ app/views/play/level/modal/HeroVictoryModal.coffee
@@ -159,7 +159,7 @@
     for achievement in (@achievements?.models or [])
       earnedAchievement = earnedAchievementMap[achievement.id]
       if earnedAchievement
-        achievement.completedAWhileAgo = new Date().getTime() - Date.parse(earnedAchievement.get('created')) > 30 * 1000
+        achievement.completedAWhileAgo = new Date().getTime() - Date.parse(earnedAchievement.attributes.changed) > 30 * 1000
       achievement.worth = achievement.get 'worth', true
       achievement.gems = achievement.get('rewards')?.gems
     c.achievements = @achievements?.models.slice() or []


tweak to mercury uploader to use the configured csrf header.

--- vendor/assets/javascripts/mercury/uploader.js.coffee
+++ vendor/assets/javascripts/mercury/uploader.js.coffee
@@ -111,7 +111,7 @@
     xhr.open('post', Mercury.config.uploading.url, true)
     xhr.setRequestHeader('Accept', 'application/json, text/javascript, text/html, application/xml, text/xml, */*')
     xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
-    xhr.setRequestHeader('X-CSRF-Token', Mercury.csrfToken)
+    xhr.setRequestHeader(Mercury.config.csrfHeader, Mercury.csrfToken)

     @file.readAsBinaryString (result) =>
       # build the multipart post string


ComponentNode#update returns DOM node

ComponentNode#update returns DOM node

--- src/ComponentNode.coffee
+++ src/ComponentNode.coffee
@@ -20,6 +20,7 @@
     @component = old.component
     @mount = old.mount
     @component.setAttributes(@attrs)
+    @mount.dom

   destroy: (dom) ->
     @mount.unmount()


:art:

:art:

--- src/git.coffee
+++ src/git.coffee
@@ -9,14 +9,16 @@
   localAppData = env.LOCALAPPDATA
   return unless localAppData

+  githubPath = path.join(localAppData, 'GitHub')
+
   try
-    children = fs.readdirSync(path.join(localAppData, 'GitHub'))
+    children = fs.readdirSync(githubPath)
   catch error
     return

   for child in children when child.indexOf('PortableGit_') is 0
-    cmdPath = path.join(localAppData, 'GitHub', child, 'cmd')
-    binPath = path.join(localAppData, 'GitHub', child, 'bin')
+    cmdPath = path.join(githubPath, child, 'cmd')
+    binPath = path.join(githubPath, child, 'bin')
     if env.Path
       env.Path += "#{path.delimiter}#{cmdPath}#{path.delimiter}#{binPath}"
     else


remove muted from institution auto complete as some browsers are inserting it to the input value

--- services/web/public/coffee/UserDetailsUpdater.coffee
+++ services/web/public/coffee/UserDetailsUpdater.coffee
@@ -53,7 +53,7 @@

 			Institutions.search $scope.userInfoForm.institution, (err, response)->
 				$scope.institutions = _.map response.hits, (institution)->
-					"#{institution.name} (#{institution.domain})"
+					"#{institution.name} (#{institution.domain})"


renaming

renaming

--- src/Market/Market.coffee
+++ src/Market/Market.coffee
@@ -93,40 +93,40 @@
         leftOfferCurrency = leftOrder.offerCurrency
         leftBidCurrency = leftOrder.bidCurrency

-        rightMaxBidAmount = rightOrder.bidAmount
-        rightMaxOfferAmount = rightOrder.offerAmount
+        rightBidAmount = rightOrder.bidAmount
+        rightOfferAmount = rightOrder.offerAmount
         if leftOrder.fillOffer
-          leftMaxOfferAmount = leftOrder.offerAmount
-          leftMaxBidAmount = leftOrder.offerAmount.multiply(rightOrder.bidPrice)
+          leftOfferAmount = leftOrder.offerAmount
+          leftBidAmount = leftOrder.offerAmount.multiply(rightOrder.bidPrice)
         else
-          leftMaxBidAmount = leftOrder.bidAmount
-          leftMaxOfferAmount = leftMaxBidAmount.multiply(rightOrder.offerPrice)
+          leftBidAmount = leftOrder.bidAmount
+          leftOfferAmount = leftBidAmount.multiply(rightOrder.offerPrice)

-        if leftMaxOfferAmount.compareTo(rightMaxBidAmount) > 0
-          # trade the rightMaxBidAmount at the right order price
-          leftOfferReduction = rightMaxOfferAmount.multiply(rightOrder.bidPrice)
+        if leftOfferAmount.compareTo(rightBidAmount) > 0
+          # trade the rightBidAmount
+          leftOfferReduction = rightOfferAmount.multiply(rightOrder.bidPrice)
           leftBalances[leftOfferCurrency].unlock(leftOfferReduction)
-          leftBalances[leftOfferCurrency].withdraw(rightMaxBidAmount)
-          rightBalances[leftOfferCurrency].deposit(rightMaxBidAmount)
-          rightBalances[leftBidCurrency].unlock(rightMaxOfferAmount)
-          rightBalances[leftBidCurrency].withdraw(rightMaxOfferAmount)
-          leftBalances[leftBidCurrency].deposit(rightMaxOfferAmount)
+          leftBalances[leftOfferCurrency].withdraw(rightBidAmount)
+          rightBalances[leftOfferCurrency].deposit(rightBidAmount)
+          rightBalances[leftBidCurrency].unlock(rightOfferAmount)
+          rightBalances[leftBidCurrency].withdraw(rightOfferAmount)
+          leftBalances[leftBidCurrency].deposit(rightOfferAmount)
           # delete the right order
           rightBook.delete(rightOrder)
           # reduce the left order
           leftOrder.reduceOffer(leftOfferReduction)
         else
-          # trade the leftMaxOfferAmount at the right order price
+          # trade the leftOfferAmount
           leftBalances[leftOfferCurrency].unlock(leftOrder.offerAmount)
-          leftBalances[leftOfferCurrency].withdraw(leftMaxOfferAmount)
-          rightBalances[leftOfferCurrency].deposit(leftMaxOfferAmount)
-          rightBalances[leftBidCurrency].unlock(leftMaxBidAmount)
-          rightBalances[leftBidCurrency].withdraw(leftMaxBidAmount)
-          leftBalances[leftBidCurrency].deposit(leftMaxBidAmount)
+          leftBalances[leftOfferCurrency].withdraw(leftOfferAmount)
+          rightBalances[leftOfferCurrency].deposit(leftOfferAmount)
+          rightBalances[leftBidCurrency].unlock(leftBidAmount)
+          rightBalances[leftBidCurrency].withdraw(leftBidAmount)
+          leftBalances[leftBidCurrency].deposit(leftBidAmount)
           # delete the left order
           leftBook.delete(leftOrder)
           # reduce the right order
-          rightOrder.reduceBid(leftMaxOfferAmount)
+          rightOrder.reduceBid(leftOfferAmount)
           # delete the right order if now has a zero amount
           if rightOrder.bidAmount.compareTo(Amount.ZERO) == 0
             rightBook.delete(rightOrder)


[client][home] signin's remember me checkbox

--- src/frontend/home.coffee
+++ src/frontend/home.coffee
@@ -75,6 +75,11 @@
             do parent.hide
             ($ '#quickInitial').fadeIn 100

+    rememberMe.click () ->
+        ($ @).toggleClass 'checked'
+        rememberMeBox.prop 'checked', not rememberMeBox.prop 'checked'
+        rememberMeIconTick.attr 'class', (if rememberMeBox.prop 'checked' then 'icon-check' else 'icon-check-empty')
+
 ($ document).ready () =>
     do init
     displaySection quick


Speed up rendering of entry pages

Speed up rendering of entry pages

--- assets/javascripts/views/content/entry_page.coffee
+++ assets/javascripts/views/content/entry_page.coffee
@@ -27,20 +27,22 @@
   render: (content = '', fromCache = false) ->
     return unless @activated
     @empty()
+    @subview = new (@subViewClass()) @el, @entry

-    @subview = new (@subViewClass()) @el, @entry
-    @subview.render(content, fromCache)
+    $.batchUpdate @el, =>
+      @subview.render(content, fromCache)
+      @addClipboardLinks() unless fromCache
+      return

     if app.disabledDocs.findBy 'slug', @entry.doc.slug
       @hiddenView = new app.views.HiddenPage @el, @entry

     @trigger 'loaded'
-    @delay @addClipboardLinks unless fromCache
     return

   CLIPBOARD_LINK = ''

-  addClipboardLinks: =>
+  addClipboardLinks: ->
     for el in @findAllByTag('pre')
       el.insertAdjacentHTML('afterbegin', CLIPBOARD_LINK)
     return


CHANGE: if text: is null or undefined, TextElement treates it as the empty-string rather than "null" or 'undefined'

--- source/Art/Engine/Elements/Shapes/TextElement.coffee
+++ source/Art/Engine/Elements/Shapes/TextElement.coffee
@@ -42,8 +42,9 @@
       preprocess: (t) ->
         if isPlainArray t
           t.join "\n"
-        else
+        else if t?
           "#{t}"
+        else ""

   @virtualProperty
     font:


Activity: Open panes when main controller is ready

--- client/activity/lib/routehandler.coffee
+++ client/activity/lib/routehandler.coffee
@@ -6,8 +6,10 @@
   {appManager} = kd.singletons
   appManager.open 'Activity', (app) ->
     view = app.getView()
-    view.open 'topic', 'public'
-    callback view.tabs.getPaneByName 'topic-public'
+
+    kd.singleton('mainController').ready ->
+      view.open 'topic', 'public'
+      callback view.tabs.getPaneByName 'topic-public'

 handleChannel = (type, slug, callback) ->
   callback    ?= (app) -> app.getView().open type, slug


disallowing regex literals as implicit calls immediatly after ']', a rare case, I hope. Ticket #358

--- lexer.coffee
+++ lexer.coffee
@@ -505,7 +505,7 @@
 #
 # Our list is shorter, due to sans-parentheses method calls.
 NOT_REGEX: [
-  'NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE'
+  'NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']'
 ]

 # Tokens which could legitimately be invoked or indexed. A opening


Fixed Menu keys for zooming in non-OSX systems

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -119,11 +119,11 @@
         }, {
             # seee https://github.com/atom/electron/issues/1507
             label: 'Zoom In',
-            accelerator: 'Command+Plus',
+            accelerator: 'Control+=',
             click: -> action 'zoom', +0.25
         }, {
             label: 'Zoom Out',
-            accelerator: 'Command+-',
+            accelerator: 'Control+-',
             click: -> action 'zoom', -0.25
         }
     ]}


reject on read error

reject on read error

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -135,6 +135,8 @@
       reader = new FileReader()
       reader.onload = ->
         cb(new DataView(reader.result))
+      reader.onerror = (e) ->
+        df.reject('reader', e)
       reader.readAsArrayBuffer(file)

     readNext = ->


main controller: call AppManager#quitAll when we log in

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -310,6 +310,7 @@
       if firstRoute and /^\/Verify/.test firstRoute
         firstRoute = "/"

+      KD.getSingleton('appManager').quitAll()
       KD.getSingleton('router').handleRoute firstRoute or '/Activity', {replaceState: yes, entryPoint}
       KD.getSingleton('groupsController').on 'GroupChanged', =>
         new KDNotificationView


ForeignAuth: changed the auth success notification style

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -114,7 +114,9 @@
             if isUserLoggedIn
               mainController.emit "ForeignAuthSuccess.#{provider}"
               new KDNotificationView
-                title : "Thanks for linking your #{provider.capitalize()} account!"
+                title : "Your #{provider.capitalize()} account has been linked."
+                type  : "mini"
+
             else
               @afterLoginCallback err, {account, replacementToken}


Link to GitHub for each page

Link to GitHub for each page

--- js/hocr-reader.coffee
+++ js/hocr-reader.coffee
@@ -109,6 +109,8 @@
             else
               $('#page_image').append($('<h2>').text('Page image not found'))
             if page_hocr
+              page_github_url = "https://github.com/#{req.params['github_user']}/#{req.params['github_repo']}/blob/master/#{req.params['github_repo']}.book/#{page_hocr.path}"
+              $('#nav_bar').append($('<a>',class:'nav_right').attr('href',page_github_url).attr('target','_blank').text('Open page in GitHub'))
               $.ajax page_hocr.url + (if get_cookie('access_token') then "?access_token=#{get_cookie('access_token')}" else ''),
                 type: 'GET'
                 dataType: 'json'


fix(spa.html): ensure only src spa.html get's moved to client dist root

--- config/config-globs.coffee
+++ config/config-globs.coffee
@@ -107,6 +107,14 @@
 	addGlob 'dist', 'views',   ['all']
 	addGlob 'dist', 'views',   ['html']

+	# exclude spa.html
+	# ================
+	excludeSpaSrc = (type, lang) ->
+		glob.src.app.client[type][lang].push "!#{config.spa.src.path}"
+
+	excludeSpaSrc 'libs',  'all'
+	excludeSpaSrc 'views', 'html'
+
 	# cache bust
 	# ==========
 	addCacheBust = (type, lang) ->


change initial condition after formula change

--- atmospheric/001-balanced-temperature/avgtfield.coffee
+++ atmospheric/001-balanced-temperature/avgtfield.coffee
@@ -45,9 +45,9 @@

     lat = (j) -> Math.PI / 256 * (128 - j)

-    #avgt.init = (263.15 for i in [0...256])
+    avgt.init = (273.15 for i in [0...256])
     #avgt.init = (243.15 + 20 * Math.cos(lat(i)) for i in [0...256])
-    avgt.init = (303.15 for i in [0...256])
+    #avgt.init = (303.15 for i in [0...256])

     avgt.evolve =  (data, energyIn) ->
         [gland, land, gair, air] = data


Chat messages from the bot will come from chatroom@server/username not username@server, so hubot responds to himself.

--- src/adapters/xmpp.coffee
+++ src/adapters/xmpp.coffee
@@ -88,6 +88,9 @@
       message = body.getText()

       [room, from] = stanza.attrs.from.split '/'
+
+      # ignore our own messages in rooms
+      return if from == @robot.username

       # note that 'from' isn't a full JID, just the local user part
       user = @userForId from


Pinch scale bug

Pinch scale bug

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -270,7 +270,7 @@
 			event.distance = Utils.pointDistance(pointA, pointB)
 			event.scale = 1

-		if @session?.pinchStartEvent
+		if @session?.started.pinch
 			event.scale = event.distance / @session.started.pinch.distance

 		# Convert point style event properties to dom style:


Send metrics only once in 5 minutes

Send metrics only once in 5 minutes

--- server/cron/cron.coffee
+++ server/cron/cron.coffee
@@ -30,7 +30,7 @@

 jobSubmitSubmits = new Cron.CronJob("*/2 * * * * *", submitSubmits)

-jobSendMetrics = new Cron.CronJob("0 */1 * * * *", sendMetrics)
+jobSendMetrics = new Cron.CronJob("0 */5 * * * *", sendMetrics)

 export default [jobCT, jobCf, jobUpdateResults, jobUpdateBlog, jobSubmitSubmits, jobSendMetrics]


Small cleanup after removing the last listener

--- framer/EventEmitter.coffee
+++ framer/EventEmitter.coffee
@@ -35,6 +35,11 @@
 		return @ unless @[EventKey][eventName]

 		@[EventKey][eventName] = (l for l in @[EventKey][eventName] when l isnt listener)
+
+		# This is not really needed, but cleans the keys for event listeners when
+		# there are none left. Avoids confusion when looking at .listeners()
+		if not @listenersForEvent(eventName).length
+			delete @[EventKey][eventName]

 		return null


timeInputView: optionally do not pad time

--- coffee/src/views/time_input_view.coffee
+++ coffee/src/views/time_input_view.coffee
@@ -6,6 +6,10 @@

   className: @classNameExtend 'time-input-view'
+
+
+  defaults: => _.extend super,
+    padTime: true

   events: => _.extend super,
@@ -103,7 +107,7 @@

     # display timestring or '--'
     time = if @_time?
-      acorn.util.Time.secondsToTimestring @_time
+      acorn.util.Time.secondsToTimestring @_time, {padTime: @options.padTime}
     else
       '--'


Remove space

Remove space

--- src/index.cjsx
+++ src/index.cjsx
@@ -37,7 +37,7 @@

     return(
       <img
-        {... @props}
+        {...@props}
         className={"react-gravatar " + @props.className}
         src={src}
         alt={@props.email}


[bugfix] error handling in pool

[bugfix] error handling in pool

--- server/imap/pool.coffee
+++ server/imap/pool.coffee
@@ -61,8 +61,8 @@
     _getAccount: ->
         log.debug @id, "getAccount"
         Account.find @accountID, (err, account) =>
-            return @_fatal err if err
-            return @_fatal new NotFound "Account #{@accountID}" unless account
+            return @_giveUp err if err
+            return @_giveUp new NotFound "Account #{@accountID}" unless account
             @account = account
             @_deQueue()


Render the scores.

Render the scores.

--- app/draw.coffee
+++ app/draw.coffee
@@ -261,6 +261,14 @@
     .attr("x", (f) -> f.field_length / 2 - sp)
     .attr("y", (f) -> -f.field_width / 2 + sp + vPxPerLetter)

+  f.select(".left-score")
+    .attr("x", (f) -> -sp)
+    .attr("y", (f) -> -f.field_width / 2 + sp + vPxPerLetter)
+
+  f.select(".right-score")
+    .attr("x", (f) -> sp)
+    .attr("y", (f) -> -f.field_width / 2 + sp + vPxPerLetter)
+
 # in miliseconds
 max_screen_time = 100

@@ -359,6 +367,12 @@
   svg.select(".right-name").datum(right)
     .text((d) -> d.name)
     .attr("textLength", (d) -> d.name.length * hPxPerLetter)
+
+  svg.select(".left-score").datum(left)
+    .text((d) -> d.score)
+
+  svg.select(".right-score").datum(right)
+    .text((d) -> d.score)

   persistTip("#{stg2txt(referee.stage)}: #{cmd2txt(referee.command)}")
   window.referee = referee
@@ -481,6 +495,12 @@
   .classed("right-name", true)
   .attr("text-anchor", "end")
   .attr("lengthAdjust", "spacingAndGlyphs")
+svg.append("text").classed("team-name", true)
+  .classed("left-score", true)
+  .attr("text-anchor", "end")
+svg.append("text").classed("team-name", true)
+  .classed("right-score", true)
+  .attr("text-anchor", "start")

 # draw default sized field
 drawField(default_geometry_field)


fix external process message

fix external process message

--- lib/connection/messages.coffee
+++ lib/connection/messages.coffee
@@ -79,12 +79,12 @@

   connectExternal: ->
     tcp.listen().then (port) ->
-      code = "using Juno; Juno.connect(#{port})"
+      code = "using Atom; using Juno; Juno.connect(#{port})"
       msg = atom.notifications.addInfo "Connect an external process",
         detail: """
         To connect a Julia process running in the terminal,
         run the command:
-        -
+
             #{code}
         """
         dismissable: true


stop supressing left-hand whitespace in newline'd strings. Make 'em more literal.

--- lexer.coffee
+++ lexer.coffee
@@ -123,7 +123,7 @@
     return false unless string =
       @balancedToken(['"', '"'], ['#{', '}']) or
       @balancedToken ["'", "'"]
-    @interpolateString string.replace STRING_NEWLINES, " \\\n"
+    @interpolateString string.replace /\n/g, '\\\n'
     @line += count string, "\n"
     @i += string.length
     true
@@ -536,7 +536,6 @@
 # Token cleaning regexes.
 JS_CLEANER      = /(^`|`$)/g
 MULTILINER      = /\n/g
-STRING_NEWLINES = /\n[ \t]*/g
 NO_NEWLINE      = /^([+\*&|\/\-%=<>!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
 HEREDOC_INDENT  = /(\n+([ \t]*)|^([ \t]+))/g
 ASSIGNED        = /^\s*(([a-zA-Z\$_@]\w*|["'][^\r\n]+?["']|\d+)[ \t]*?[:=][^=])/


default to powershell.exe on windows instead of cmd

--- lib/connection/terminal.coffee
+++ lib/connection/terminal.coffee
@@ -34,7 +34,7 @@
     if sh?
       sh
     else if process.platform == 'win32'
-      'cmd.exe'
+      'powershell.exe'
     else
       'bash'


added handling for in-flight requests

added handling for in-flight requests

--- bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
@@ -7,7 +7,7 @@
 {logger} = require "../../common/logging"

 class TileRendererView extends PlotWidget
-
+
   get_extent: () ->
     return [@x_range.get('start'), @y_range.get('start'), @x_range.get('end'), @y_range.get('end')]

@@ -35,13 +35,13 @@
     tile_data = e.target.tile_data
     tile_data.img = e.target
     tile_data.current = true
-    @mget('tile_source').tiles[tile_data.cache_key] = tile_data
+    tile_data.loaded = true
     @request_render()

   _on_tile_cache_load: (e) =>
     tile_data = e.target.tile_data
     tile_data.img = e.target
-    @mget('tile_source').tiles[tile_data.cache_key] = tile_data
+    tile_data.loaded = true

   _on_tile_error: (e) =>
     return ''
@@ -63,15 +63,17 @@

     tile.onerror = @_on_tile_error
     tile.alt = ''
-
+
     tile.tile_data =
       tile_coords : [x, y, z]
       quadkey : @mget('tile_source').tile_xyz_to_quadkey(x, y, z)
       cache_key : @mget('tile_source').tile_xyz_to_key(x, y, z)
       bounds : bounds
+      loaded : false
       x_coord : bounds[0]
       y_coord : bounds[3]

+    @mget('tile_source').tiles[tile.tile_data.cache_key] = tile_data
     tile.src = @mget('tile_source').get_image_url(x, y, z)
     return tile

@@ -81,7 +83,7 @@
       @_set_data()
       @_map_data()
       @map_initialized = true
-
+
     # brute force way of handling resize or responsive event -------------------------------------------------------------
     if @height != @map_frame.get('height') or @width != @map_frame.get('width')
       extent = @get_extent()
@@ -190,14 +192,15 @@
       [x, y, z, bounds] = t
       if @_is_valid_tile(x, y, z)
         key = @mget('tile_source').tile_xyz_to_key(x, y, z)
-        if key of @mget('tile_source').tiles
+        tile = @mget('tile_source').tiles[key]
+        if tile? and tile.loaded == true
           cached.push(key)

         else
-
           [px, py, pz] = @mget('tile_source').get_closest_parent_by_tile_xyz(x, y, z)
           parent_key = @mget('tile_source').tile_xyz_to_key(px, py, pz)
-          if parent_key of @mget('tile_source').tiles and parent_key not in parents
+          parent_tile = @mget('tile_source').tiles[parent_key]
+          if parent_tile? and parent_tile.loaded and parent_key not in parents
             parents.push(parent_key)

           if zooming_out
@@ -205,9 +208,11 @@
             for c in children
               [cx, cy, cz, cbounds] = c
               child_key = @mget('tile_source').tile_xyz_to_key(cx, cy, cz)
+
               if child_key of @mget('tile_source').tiles
                 children.push(child_key)

+        if not tile?
           need_load.push(t)

     # draw stand-in parents ----------


switch the url around

switch the url around

--- js/app.coffee
+++ js/app.coffee
@@ -536,7 +536,7 @@

   @get '^/api/.*', (cx) => false

-  @get '^/artifacts/.*', (cx) =>
+  @get '^/gh/.*/artifacts/.*', (cx) =>
     # escape from sammy!
     location.assign cx.path


Track pageviews properly

Track pageviews properly

--- js/app.coffee
+++ js/app.coffee
@@ -351,6 +351,9 @@
 window.VM = new CircleViewModel()
 window.SammyApp = Sammy 'body', (n) ->

+    @bind 'run-route', (e, data) ->
+      mixpanel.track_pageview(data.path)
+
     # ignore forms with method ko, useful when using the knockout submit binding
     @route 'ko', '.*', ->
       false


Fix file renaming.

Fix file renaming.

--- tasks/i18n-pug.coffee
+++ tasks/i18n-pug.coffee
@@ -79,6 +79,11 @@
             # Most commonly `i18n_pug.[target]` or `i18n_pug`
             if @target.length then "#{@name}.#{@target}" else @name
         ).files
+
+        # Set files[n].orig for each files object
+        for file_obj, key in config.files
+            config.files[key] = file_obj.orig
+
         # Find the i18n settings object in the options and preserve it
         i18nConfig = config.options.i18n
         # Remove options not supported by grunt-contrib-pug from config


Adapt to new noflo-runtime

Adapt to new noflo-runtime

--- components/ProcessErrorToContext.coffee
+++ components/ProcessErrorToContext.coffee
@@ -2,21 +2,17 @@

 exports.getComponent = ->
   c = new noflo.Component
-  c.inPorts.add 'process',
-    datatype: 'string'
-  c.inPorts.add 'message',
-    datatype: 'string'
+  c.inPorts.add 'error',
+    datatype: 'object'
   c.outPorts.add 'out',
     datatype: 'object'

-  noflo.helpers.WirePattern c,
-    in: ['process', 'message']
-    out: 'out'
-    async: true
-  , (data, groups, out, callback) ->
+  c.process (input, output) ->
+    return unless input.hasData 'error'
+    error = input.getData 'error'
     ctx =
       error:
-        process: data.process
-        message: data.message
-    out.send ctx
-    do callback
+        process: error.id
+        message: error.error
+    output.sendDone
+      out: ctx


Source file updated for color catching

Source file updated for color catching

--- src/iso.coffee
+++ src/iso.coffee
@@ -320,6 +320,9 @@
       when 'rgb(123, 201, 111)', '#7bc96f' then COLORS[2]
       when 'rgb(35, 154, 59)',   '#239a3b' then COLORS[3]
       when 'rgb(25, 97, 39)',    '#196127' then COLORS[4]
+      else
+        if (fill.indexOf('#') != -1)
+          new obelisk.CubeColor().getByHorizontalColor(parseInt('0x'+fill.replace("#", "")));

   formatDateString: (dateStr, options) ->
     date = null


Start working on syncing prefs

Start working on syncing prefs

--- app/sync.coffee
+++ app/sync.coffee
@@ -1,7 +1,10 @@
-Q       = require "q"
-Auth    = require "./auth"
-User    = require "./storage"
-Log     = require "./log"
+Q       = require 'q'
+Auth    = require './auth'
+User    = require './storage'
+Log     = require './log'
+
+# Constants
+SUPPORTED_CLASSES = ['List', 'Task', 'Setting']

 # Start server
 init = ( sync_server ) ->
@@ -17,14 +20,14 @@
   io.configure ->

     # Hide irrelevant messages
-    io.set "log level", 1
+    io.set 'log level', 1

     # Prevent unauthorised access to server
-    io.set "authorization", (handshakeData, fn) ->
+    io.set 'authorization', (handshakeData, fn) ->
       uid = handshakeData.query.uid
       token = handshakeData.query.token
       if DebugMode
-        console.log "Received uid and token", uid
+        console.log 'Received uid and token', uid
       if uid? and token?
         User.checkLoginToken(uid, token).then (exists) ->
           handshakeData.uid = uid
@@ -48,13 +51,13 @@
   data =
     Task:
       completed: false
-      date: ""
-      list: "inbox"
-      name: "New Task"
-      notes: ""
+      date: ''
+      list: 'inbox'
+      name: 'New Task'
+      notes: ''
       priority: 1
     List:
-      name: "New List"
+      name: 'New List'
       tasks: []

   clone = (obj) ->
@@ -78,17 +81,17 @@

   # Socket.IO events
   events:
-    'disconnect': 'logout'
-    'fetch': 'fetch'
-    'sync': 'sync'
-    'create': 'create'
-    'update': 'update'
-    'destroy': 'destroy'
-    'info': 'info'
-    'emailList': 'emailList'
+    'disconnect' : 'logout'
+    'fetch'      : 'fetch'
+    'sync'       : 'sync'
+    'create'     : 'create'
+    'update'     : 'update'
+    'destroy'    : 'destroy'
+    'info'       : 'info'
+    'emailList'  : 'emailList'

   constructor: (@socket, uid=@socket.handshake.uid, callback) ->
-    Log "A user has connected to the server"
+    Log 'A user has connected to the server'
     # Bind socket.io events
     for event, fn of @events
       @on event, @[fn]
@@ -270,7 +273,7 @@
   # Create a new model
   create: (data, fn) =>
     [className, model] = data
-    return unless className in ["List", "Task"]
+    return unless className in SUPPORTED_CLASSES

     # Generate new id
     if className is "List" and model.id is "inbox"
@@ -303,9 +306,9 @@
   # Update existing model
   update: (data) =>
     [className, changes] = data
-    return unless className in ["List", "Task"]
+    return unless className in SUPPORTED_CLASSES
     id = changes.id
-
+
     # Check model exists on server
     if not @hasModel(className, id)
       Log "#{className} doesn't exist on server"
@@ -348,7 +351,7 @@

   # Delete existing model
   destroy: ([className, id], timestamp=Date.now()) =>
-    return unless className in ["List", "Task"]
+    return unless className in SUPPORTED_CLASSES
     model = @findModel className, id

     # Check that the model hasn't been updated after this event
@@ -360,10 +363,10 @@
         Log "Destroying Task #{taskId}"
         # TODO: Prevent server from broadcasting these changes
         #       And make the client delete the tasks
-        @destroy ["Task", taskId]
+        @destroy ['Task', taskId]

     # Remove from list
-    else if className is "Task"
+    else if className is 'Task'
       @taskRemove id, model.list

     # Replace task with deleted template
@@ -403,7 +406,7 @@
       # This code matches that list back to the task

       if type in ["create", "update"] and
-      className is "Task" and model.list.slice(0,2) is "c-"
+      className is 'Task' and model.list.slice(0,2) is "c-"

         # The list hasn't been assigned a server ID yet
         if client[model.list] is undefined


Get rid of NU C&E for now

Get rid of NU C&E for now

--- js/app.coffee
+++ js/app.coffee
@@ -105,9 +105,6 @@
     name: 'RU'
     callback: @checkTier.bind this, 'RU'
   ,
-    name: 'NU'
-    callback: @checkTier.bind this, 'NU'
-  ,
     name: 'Ubers'
     callback: @checkTier.bind this, 'Uber'
   ,


remote/stack: update helpers to use getAt

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/remote-extensions/computestack.coffee
+++ client/app/lib/remote-extensions/computestack.coffee
@@ -10,7 +10,7 @@
   getUnreadCount: -> @_revisionStatus?.status?.code or 0

-  isManaged: -> @title is 'Managed VMs'
+  isManaged: -> @getAt('title') is 'Managed VMs'

-  getOldOwner: -> @config?.oldOwner
+  getOldOwner: -> @getAt 'config.oldOwner'


Emit a signal when user subscribes.

Emit a signal when user subscribes.

--- client/landing/app/CommonViews/paymentwidgets.coffee
+++ client/landing/app/CommonViews/paymentwidgets.coffee
@@ -42,6 +42,7 @@
         @checkSubscription (status)=>
           @loader.hide()
           if status
+            @emit "subscribed"
             @widgetContent.show()
           else
             @buttonSubscribe.show()


try catching errors thrown by firebase

try catching errors thrown by firebase

--- routes/index.coffee
+++ routes/index.coffee
@@ -45,17 +45,19 @@
   msg = "submitting new user notification to firebase: "+ JSON.stringify req.body
   log msg

-  for friendId in req.body.friends
-    firebase.child('inbound').child(friendId).push {
+  try
+    for friendId in req.body.friends
+      firebase.child('inbound').child(friendId).push {
+        dts: req.body.dts
+        type: 'friendsUpdate'
+        friendId: req.body.userId
+      }, (error) => fail error if error?
+    firebase.child('inbound').child(req.body.userId).push {
       dts: req.body.dts
       type: 'friendsUpdate'
-      friendId: req.body.userId
     }, (error) => fail error if error?
-  firebase.child('inbound').child(req.body.userId).push {
-    dts: req.body.dts
-    type: 'friendsUpdate'
-  }, (error) => fail error if error?
-
-  res.send msg
+    res.send msg
+  catch error
+    fail error

 module.exports = router


Client: Activity: Feed: Deleted unnecessary lines and updated onEnter method to check nextState has postId or not.

--- client/activity/lib/routes/singlepublicchannel.coffee
+++ client/activity/lib/routes/singlepublicchannel.coffee
@@ -1,13 +1,11 @@
-kd                = require 'kd'
-ChannelThreadPane = require 'activity/components/channelthreadpane'
-ActivityFlux      = require 'activity/flux'
-getGroup          = require 'app/util/getGroup'
-changeToChannel   = require 'activity/util/changeToChannel'
-ResultStates      = require 'activity/util/resultStates'
+kd                        = require 'kd'
+ChannelThreadPane         = require 'activity/components/channelthreadpane'
+ActivityFlux              = require 'activity/flux'
+transitionToChannel       = require 'activity/util/transitionToChannel'
+SingleMessageRoute        = require './singlemessage'

 {
   thread  : threadActions,
-  channel : channelActions,
   message : messageActions } = ActivityFlux.actions

 { selectedChannelThread, channelByName } = ActivityFlux.getters
@@ -35,10 +33,10 @@

     { params, routes, location } = nextState
     { channelName } = params
+    { pathname } = location

-    if location.pathname is "/Channels/#{channelName}"
-      route = "/Channels/#{channelName}/Recent"
-      return kd.singletons.router.handleRoute route
+    unless nextState.params.postId
+      return kd.singletons.router.handleRoute "#{pathname}/Recent"

     selectedThread = kd.singletons.reactor.evaluate selectedChannelThread


Fix conflicting processor names bug

Fix conflicting processor names bug

--- client/src/app/game/toolbox/toolboxController.coffee
+++ client/src/app/game/toolbox/toolboxController.coffee
@@ -32,7 +32,7 @@
     for itemType in ["processors", "switches", "transformers", "circuits"]
       standardNames = _.keys(currentGame.standardLibrary[itemType])
       customNames = _.keys(currentGame.version[itemType])
-      $scope[itemType] = standardNames.concat(customNames).sort()
+      $scope[itemType] = _.uniq(standardNames.concat(customNames).sort(), true)
   $scope.$watch((-> currentGame.localVersion), updateItems)

   openModal = (templateUrl, dialogControllerName, model, onDone) ->


gmaps add setPoint meth

gmaps add setPoint meth

--- app/assets/javascripts/ab_admin/components/gmaps.js.coffee
+++ app/assets/javascripts/ab_admin/components/gmaps.js.coffee
@@ -52,6 +52,11 @@
       @map.setCenter(point)
       @map.setZoom(17)

+    @marker.setPosition(point)
+    @setInputs(point)
+
+  setPoint: (point) ->
+    @map.setCenter(point)
     @marker.setPosition(point)
     @setInputs(point)


add support for new homepage rails and update rails order

--- src/desktop/apps/home/queries/initial.coffee
+++ src/desktop/apps/home/queries/initial.coffee
@@ -6,12 +6,14 @@
         max_followed_gene_rails: -1,
         order: [
           ACTIVE_BIDS,
-          RECENTLY_VIEWED_WORKS
+          RECENTLY_VIEWED_WORKS,
+          SIMILAR_TO_RECENTLY_VIEWED,
+          SAVED_WORKS,
+          SIMILAR_TO_SAVED_WORKS,
+          FOLLOWED_ARTISTS,
+          FOLLOWED_GALLERIES,
           RECOMMENDED_WORKS,
-          FOLLOWED_ARTISTS,
           RELATED_ARTISTS,
-          FOLLOWED_GALLERIES,
-          SAVED_WORKS,
           LIVE_AUCTIONS,
           CURRENT_FAIRS,
           FOLLOWED_GENES,


cistrans: Remove the bit of coffee code dropping peaks with low LOD

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -132,12 +132,6 @@
            .attr("stroke", "none")
            .attr("fill", darkGray)
            .style("pointer-events", "none")
-
-  # use just the peaks with LOD > 10
-  tmp = []
-  for p in data.peaks
-    tmp.push(p) if p.lod > 10
-  data.peaks = tmp

   # maximum lod score
   maxlod = d3.max(data.peaks, (d) -> d.lod)
@@ -210,7 +204,6 @@
          .text(titletext)
          .attr("x", (left[1]+right[1])/2)
          .attr("y", top[1] - pad.top/2)
-

   # circles at eQTL peaks


Reverse regression for use of relative resources

Commit that added the regression: 5786b4bb80de85a6e6f47cd8cf7debb19bcef4f7. Original commit with the fix: ce3634cbb01efd8bdc4923a33ee3916a8db019fe

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -102,7 +102,10 @@
     </html>
     """
     iframe = document.createElement("iframe")
-    iframe.src = "data:text/html;charset=utf-8,#{encodeURI(text)}"
+    # Fix from @kwaak (https://github.com/webBoxio/atom-html-preview/issues/1/#issuecomment-49639162)
+    # Allows for the use of relative resources (scripts, styles)
+    iframe.setAttribute("sandbox", "allow-scripts allow-same-origin")
+    iframe.src = @getPath()
     @html $ iframe
     @trigger('atom-html-preview:html-changed')


Fixed highlighting of classes prepended with and underscore.

--- grammars/dart.cson
+++ grammars/dart.cson
@@ -161,7 +161,7 @@
         'name': 'constant.numeric.dart'
       }
       {
-        'match': '\\b[A-Z][a-zA-Z0-9_]+\\b'
+        'match': '\\b_?[A-Z][a-zA-Z0-9_]+\\b'
         'name': 'support.class.dart'
       }
       {


Add pattern-matching for manuscript macros

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -82,7 +82,7 @@
 		},{
 			name: "string.quoted.double.roff",
 			begin: '"(?!")'
-			end:   '(?<!")"(?!")|(?<!\\\\)$'
+			end:   '(?<!")"(?!")|(?<!\\\\)$|(?=\\\\\")'
 			beginCaptures: {0: {name: "punctuation.definition.string.begin.roff"}}
 			endCaptures:   {0: {name: "punctuation.definition.string.end.roff"}}
 			patterns: [
@@ -621,8 +621,70 @@
 	macros:
 		patterns: [
 			{include: "#man"}
+			{include: "#ms"}
 			{include: "#mm"}
 		]
+
+
+	# Manuscript macros
+	ms:
+		patterns: [{
+			name: "meta.function.${2:/downcase}.ms.macro.roff"
+			begin: "(?x) ^([.'])[ \t]*
+				(1C|2C|AB|AE|AI|AU|B1|B2|BT|BX|DA|DE|DS|EN|EQ|FE|FS|IP|KE|KF|KS|LG
+				|LP|MC|ND|NH|NL|P1|PE|PP|PS|PT|PX|QP|RP|SH|SM|TA|TC|TE|TL|TS|XA|XE
+				|XP|XS|\\[|\\])\\b"
+			end: "(?<!\\\\)(?=\n)"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.name.roff"}
+			patterns: [{include: "#params"}]
+		},{
+
+			# Deprecated macros
+			name: "invalid.deprecated.function.${2:/downcase}.ms.macro.roff"
+			begin: "^([.'])[ \t]*(De|Ds)\\b"
+			end: "(?<!\\\\)(?=\n)"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.name.roff"}
+			patterns: [{include: "#params"}]
+		},{
+
+			# Underlined text
+			name: "meta.function.ul.ms.macro.roff"
+			begin: "^([.'])[ \t]*(UL)\\b"
+			end: "(?<!\\\\)$|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.name.roff"}
+			patterns: [{
+
+				# Unquoted string
+				contentName: "markup.underline.roff"
+				begin: "\\G[ \t]*(?!\")(?=(?:[^\\s\\\\]|\\\\(?!\").)+)"
+				end:   "(?<![^\\\\]\\\\|^\\\\)(?=\\s|$)|(?=\\\\\")"
+				patterns: [include: "#escapes"]
+			},{
+
+				# Null argument
+				name: "string.quoted.double.empty.roff"
+				match: '(")(")'
+				beginCaptures: {0: {name: "punctuation.definition.string.begin.roff"}}
+				endCaptures:   {0: {name: "punctuation.definition.string.end.roff"}}
+			},{
+				# Quoted string
+				name: "string.quoted.double.roff"
+				contentName: "markup.underline.roff"
+				begin: '\\G[ \t]*(")'
+				end:   '(?<!")"(?!")|(?<!\\\\)$|(?=\\\\\")'
+				beginCaptures: 1: name: "punctuation.definition.string.begin.roff"
+				endCaptures:   0: name: "punctuation.definition.string.end.roff"
+				patterns: [include: "#escapes"]
+			}
+			{include: "#escapes"}
+			{include: "#string"}]
+		}]

 	# Memorandum macros


Autocomplete fixes, still have some issues ~

--- components/autocomplete/autocompletecontroller.coffee
+++ components/autocomplete/autocompletecontroller.coffee
@@ -206,7 +206,7 @@
     {itemDataPath,customCompare,isCaseSensitive} = @getOptions()
     suggested = JsPath.getAt data, itemDataPath
     for selectedData in @getSelectedItemData()
-      if compare?
+      if customCompare?
         alreadySelected = customCompare data, selectedData
         return yes if alreadySelected
       else
@@ -273,14 +273,14 @@
       itemValue = item.getOptions().userInput
       data = JsPath itemDataPath, itemValue

-    return no if @isItemAlreadySelected data
+    return no  if @isItemAlreadySelected data

     path = @getCollectionPath()

     itemName  = "#{name}-#{@selectedItemCounter++}"
     if form
-      collection = form.getCustomData path
-      collection = [] unless collection?
+      collection   = form.getCustomData path
+      collection or= []
       form.addCustomData path, collection
       id = itemValue.getId?()
       collection.push(


Added conversion of adjustment amount to number

--- app/assets/javascripts/sprangular/models/adjustment.coffee
+++ app/assets/javascripts/sprangular/models/adjustment.coffee
@@ -1,4 +1,7 @@
 class Sprangular.Adjustment
+  init: ->
+    @amount = Number(@amount)
+
   isPromo: ->
     @source_type == 'Spree::PromotionAction'


Added path param to Checkout.put()

Added path param to Checkout.put()

--- app/assets/javascripts/sprangular/services/checkout.coffee
+++ app/assets/javascripts/sprangular/services/checkout.coffee
@@ -51,10 +51,10 @@

       @put(params)

-    put: (params) ->
+    put: (params, path) ->
       params ||= {}

-      $http.put("/spree/api/checkouts/#{Cart.current.number}", $.param(params))
+      $http.put("/spree/api/checkouts/#{Cart.current.number}/#{path}", $.param(params))
            .success (response) ->
              Cart.load(response) unless response.error
            .error (response) ->


add a metric for the TeXLive image used on each compile

--- services/clsi/app/coffee/CompileManager.coffee
+++ services/clsi/app/coffee/CompileManager.coffee
@@ -32,7 +32,11 @@
 			injectDraftModeIfRequired (error) ->
 				return callback(error) if error?
 				timer = new Metrics.Timer("run-compile")
+				# find the image tag to log it as a metric
+				tag = request.imageName?.match(/:(.*)/)?[1] or "default"
+				tag = "other" if project_id?.match(/^[0-9a-f]{24}$/) # exclude smoke test
 				Metrics.inc("compiles")
+				Metrics.inc("compiles-with-image.#{tag}")
 				LatexRunner.runLatex request.project_id, {
 					directory: compileDir
 					mainFile:  request.rootResourcePath


Team: json fix

Team: json fix

--- client/About/team.coffee
+++ client/About/team.coffee
@@ -60,7 +60,7 @@

       ]

-    suspended     : [
+    suspended     : []

       #   username  : 'aleksey-m'
       #   title     : 'Sys Admin & node.js Developer'
@@ -97,4 +97,4 @@
       # ,
       #   username  : 'bacon'
       #   title     : 'XEO & Office Manager'
-      ]+      # ]


fixed unit test for daylight saving to work in other timezones than CEST


The returned object is inherited from Q

The returned object is inherited from Q

--- spec/arrayMethods.coffee
+++ spec/arrayMethods.coffee
@@ -1,3 +1,4 @@
+Q = require 'q'
 chai = require 'chai'
 jscov = require 'jscov'
 chaiAsPromised = require 'chai-as-promised'
@@ -33,6 +34,15 @@

+describe 'Z method', ->
+
+  it 'returns an object that is inherited from Q', ->
+    prototypeOfQ = Q.makePromise.prototype
+    x = Z(1)
+    prototypeOfQ.isPrototypeOf(x).should.eql true
+
+
+
 describe 'Q method', ->

   describe 'get', ->
@@ -49,6 +59,11 @@
       filtered = arr.get('a')
       bValue.push(3)
       filtered.should.become { b: bValue }
+
+    it 'returns an object that is inherited from Q', ->
+      prototypeOfQ = Q.makePromise.prototype
+      x = Z({ a: { b: 1 } }).get('a')
+      prototypeOfQ.isPrototypeOf(x).should.eql true


Add 0o to literal

Add 0o to literal

--- spec/coffeescript.coffee
+++ spec/coffeescript.coffee
@@ -29,7 +29,7 @@
 }`

 heredoc = """
-CoffeeScript subst test #{ 010 + 0xf / 0b10 + "nested string #{ /\n/ }"}
+CoffeeScript subst test #{ 0o010 + 0xf / 0b10 + "nested string #{ /\n/ }"}
 """

 ###


tests: Generalize heteregeous a bit

tests: Generalize heteregeous a bit

Subscribe discovery before starting participant, required for MQTT.
Will become required for AMQP when we make 'fbp' an exchange.

Also allow to specify the data to send through component.
Useful for MicroFlo, which does not support arbitrary JSON data

--- spec/heterogenous.coffee
+++ spec/heterogenous.coffee
@@ -43,11 +43,34 @@
   return startProcess args, callback

 exports.testParticipant = testParticipant = (state, name) ->
+  state.repeat = { bar: 'foo' } if typeof state.repeat == 'undefined'

   describe "#{name} participant", ->
     participant = null
+    definitions = null
+    onParticipantDiscovered = null
+
+    waitDefinition = (waitForComponent, cb) ->
+      checkAndCallback = () ->
+        console.log 'disc', definitions.length, waitForComponent, definitions
+        for def in definitions
+          if def.component == waitForComponent
+            return cb def
+      checkAndCallback()
+      onParticipantDiscovered = checkAndCallback
+
     beforeEach (done) ->
       @timeout 4000
+      definitions = []
+
+      onDiscovery = (msg) ->
+        def = msg.data
+        definitions.push def
+        state.broker.ackMessage msg
+        if typeof onParticipantDiscovered == 'function'
+          onParticipantDiscovered def, definitions
+      state.broker.subscribeParticipantChange onDiscovery
+
       participant = startForeign state.commands, name, done
     afterEach (done) ->
       participant.kill()
@@ -55,44 +78,46 @@

     describe 'when started', ->
       it 'sends definition on fbp queue', (done) ->
-        broker = state.broker

-        onDiscovery = (msg) ->
-          def = msg.data
+        waitDefinition name, (def) ->
           chai.expect(def).to.be.an 'object'
           chai.expect(def).to.have.keys ['id', 'icon', 'role', 'component', 'label', 'inports', 'outports']
-          state.broker.ackMessage msg
-          return if def.component != name
           done()
-        state.broker.subscribeParticipantChange onDiscovery

     describe 'sending data on inport queue', ->
+      @timeout 4000
       it 'repeats the same data on outport queue', (done) ->
         broker = state.broker

-        input = { bar: 'foo' }
         onReceive = (msg) ->
           broker.ackMessage msg
-          chai.expect(msg.data).to.eql input
+          chai.expect(msg.data).to.eql state.repeat
           done()

         # TODO: look up in definition
-        receiveQueue = 'test.RECEIVE'
-        inQueue = 'repeat.IN'
-        outQueue = 'repeat.OUT'
-        binding = { type: 'pubsub', src: outQueue, tgt: receiveQueue }
+        waitDefinition name, (def) ->

-        send = () ->
-          broker.sendTo 'inqueue', inQueue, input, (err) ->
+          inQueue = null
+          outQueue = null
+          for port in def.inports
+            inQueue = port.queue if port.id == 'in'
+          for port in def.outports
+            outQueue = port.queue if port.id == 'out'
+
+          receiveQueue = 'test.RECEIVE'
+          binding = { type: 'pubsub', src: outQueue, tgt: receiveQueue }
+
+          send = () ->
+            broker.sendTo 'inqueue', inQueue, state.repeat, (err) ->
+              chai.expect(err).to.not.exist
+
+          broker.createQueue 'inqueue', receiveQueue, (err) ->
             chai.expect(err).to.not.exist
-
-        broker.createQueue 'inqueue', receiveQueue, (err) ->
-          chai.expect(err).to.not.exist
-          broker.addBinding binding, (err) ->
-            chai.expect(err).to.not.exist
-            broker.subscribeToQueue receiveQueue, onReceive, (err) ->
+            broker.addBinding binding, (err) ->
               chai.expect(err).to.not.exist
-              setTimeout send, 1000 # HACK: wait for inqueue to be setup
+              broker.subscribeToQueue receiveQueue, onReceive, (err) ->
+                chai.expect(err).to.not.exist
+                setTimeout send, 1000 # HACK: wait for inqueue to be setup

 describe 'Heterogenous', ->
@@ -100,6 +125,7 @@
   g =
     broker: null
     commands: foreignParticipants
+    repeat: undefined # default

   beforeEach (done) ->
     g.broker = msgflo.transport.getBroker address


let model changes drive changes in interface

--- core/app/backbone/factlink/tooltip_definition.coffee
+++ core/app/backbone/factlink/tooltip_definition.coffee
@@ -9,22 +9,31 @@

 Backbone.Factlink ||= {}

+class HoverState extends Backbone.Model
+  defaults:
+    inTooltip: false
+    inTarget: false
+
+  hovered: ->
+    @get('inTarget') || @get('inTooltip')
+
+
 class Backbone.Factlink.TooltipDefinition
   defaults:
     closingtimeout: 500

   constructor: (options) ->
     @_options = _.defaults options, @defaults
-    @state = new Backbone.Model
-      inTooltip: false
-      inTarget: false
+    @state = new HoverState
+    @state.on 'change', => @toggleTooltip()

   render: ->
     @_options.$container.hoverIntent
       timeout: @_options.closingtimeout
       selector: @_options.selector
-      over:(e) => @_hoverTarget e, true
-      out: (e) => @_hoverTarget e, false
+      over:(e) => @state.set inTarget: true
+      out: (e) => @state.set inTarget: false
+    @$target = @_options.$container.find(@_options.selector)

   close: ->
     @removeTooltip()
@@ -34,31 +43,17 @@
     @_options.removeTooltip @_options.$container, @$target, @_$tooltip
     delete @_$tooltip

-  _hoverTarget: (e, target_is_hovered) ->
-    if @_$tooltip
-      @setTargetHover target_is_hovered
-    else if target_is_hovered
-     @$target = $(e.currentTarget)
-     @start_tooltip @_options, @$target
-
   start_tooltip: (options, $target) ->
-    @state.set('inTarget', true)
     @_$tooltip = options.makeTooltip options.$container, $target
     @_$tooltip.hoverIntent
       timeout: options.closingtimeout
-      over: => @setTooltipHover(true)
-      out: => @setTooltipHover(false)
+      over: => @state.set inTooltip: true
+      out: => @state.set inTooltip: false

-  _check: -> @removeTooltip() unless @hovered()
+  toggleTooltip: ->
+    if @_$tooltip
+      @removeTooltip() unless @state.hovered()
+    else
+      start_tooltip if @state.hovered()

-  hovered: ->
-    @state.get('inTarget') || @state.get('inTooltip')

-  setTooltipHover: (state) ->
-    @state.set('inTooltip',state)
-    @_check()
-
-  setTargetHover: (state) ->
-    @state.set('inTarget', state)
-    @_check()
-


Prettified close button

Prettified close button

--- core/app/backbone/modules/notification_center.coffee
+++ core/app/backbone/modules/notification_center.coffee
@@ -6,7 +6,7 @@
       @$el.addClass "alert alert-#{@model.get('type')}"

     template:
-      text: "{{{message}}} x"
+      text: "{{{message}}} ×"

     events:
       "click .close": -> @model.destroy()


Remove trailing whitespaces

Remove trailing whitespaces

--- client/app/Applications/Ace.kdapplication/AppController.coffee
+++ client/app/Applications/Ace.kdapplication/AppController.coffee
@@ -38,7 +38,6 @@
     else
       @bringToFront new AceView {}, file

-
   removeOpenDocument:(doc)->

     if doc
@@ -70,7 +69,7 @@

     file.on "fs.delete.finished", => @removeOpenDocument @aceViews[file.path]

-
   clearFileRecords:(view)->
     file = view.getData()
-    delete @aceViews[file.path]+    delete @aceViews[file.path]
+


client: home: keep some view usage in git history

--- client/home/lib/utilities/homeutilitiescustomerfeedback.coffee
+++ client/home/lib/utilities/homeutilitiescustomerfeedback.coffee
@@ -29,3 +29,17 @@
     {{> @switch}}

     """
+
+    # @switch  = new KodingSwitch
+    #   cssClass: 'small'
+    #   callback: (state) =>
+    #     if state
+    #     then @setClass 'on'
+    #     else @unsetClass 'on'
+
+    # {{> @switch}}
+    #

+    # Customer Feedback
+    # Enable Chatlio.com for real-time customer feedback
+    #

+    #


profile: fixed non-existing data bug, indentation

--- client/landing/app/MainApp/lazy/staticprofilecontroller.coffee
+++ client/landing/app/MainApp/lazy/staticprofilecontroller.coffee
@@ -10,15 +10,15 @@
     'github'
   ]

-  handleMap =
-    twitter :
+  handleMap   =
+    twitter   :
       baseUrl : 'https://www.twitter.com/'
-      text : 'Twitter'
-      prefix : '@'
-
-    github :
+      text    : 'Twitter'
+      prefix  : '@'
+
+    github    :
       baseUrl : 'https://www.github.com/'
-      text : 'GitHub'
+      text    : 'GitHub'

   constructor:(options,data)->
@@ -34,8 +34,8 @@
         cssClass        : 'static-content'
       showHeader        : no

-    @navLinks = {}
-    @handleLinks = {}
+    @navLinks     = {}
+    @handleLinks  = {}

     # reviving the content view. this encapsulates the listitem feed after
     # user input (type selection, more-button)
@@ -49,9 +49,9 @@
     @profileContentView.addSubView @listWrapper

     # this is the JAccount object of the static profile
-    profileUser = null
-    allowedTypes = ['CBlogPostActivity']
-    blockedTypes = []
+    profileUser   = null
+    allowedTypes  = ['CBlogPostActivity']
+    blockedTypes  = []
     currentFacets = []

     # reviving the landing page. this is needed to handle window
@@ -70,12 +70,12 @@

     @profileShowMoreView = new KDView
       lazyDomId : 'profile-show-more-wrapper'
-      cssClass : 'hidden'
+      cssClass  : 'hidden'

     profileShowMoreButton = new KDButtonView
       lazyDomId : 'profile-show-more-button'
-      title :'Show more'
-      callback:=>
+      title     : 'Show more'
+      callback  : =>
         @emit 'ShowMoreButtonClicked'
         @profileShowMoreView.hide()
         @profileShowMoreView.setHeight 0
@@ -88,17 +88,17 @@
     # adding administrative views
     profileContentWrapperView = new KDView
       lazyDomId : 'profile-content-wrapper'
-      cssClass : 'slideable'
+      cssClass  : 'slideable'

     profilePersonalWrapperView = new KDView
       lazyDomId : 'profile-personal-wrapper'
-      cssClass : 'slideable'
+      cssClass  : 'slideable'

     # reviving feed type selectors that will activate feed facets

     for type in CONTENT_TYPES
       @navLinks[type] = new StaticNavLink
-        delegate : @
+        delegate  : @
         lazyDomId : type

     @emit 'DecorateStaticNavLinks', allowedTypes
@@ -127,7 +127,8 @@
        # revive handle links

         for type in HANDLE_TYPES
-          handle = profileUser.profile.handles[type]
+          handle = profileUser.profile.handles?[type]
+          log handle
           @handleLinks[type]  = new StaticHandleLink
             delegate          : @
             attributes        :
@@ -135,7 +136,7 @@
               target          : '_blank'
             icon              :
               cssClass        : type
-            title             : "#{handleMap[type].prefix or ''}#{handle or handleMap[type].text}"
+            title             : "#{if handle then handleMap[type].prefix or '' else ''}#{handle or handleMap[type].text}"
             lazyDomId         : "profile-handle-#{type}"

         if user.getId() is KD.whoami().getId()
@@ -161,17 +162,17 @@
           showPage = user.profile.staticPage?.show

           profileAdminMessageView.addSubView disableLink = new CustomLinkView
-            title : "#{if showPage is yes then 'Disable' else 'Enable'} this Public Page"
-            cssClass : 'message-disable'
-            click : (event)=>
+            title     : "#{if showPage is yes then 'Disable' else 'Enable'} this Public Page"
+            cssClass  : 'message-disable'
+            click     : (event)=>
               event?.stopPropagation()
               event?.preventDefault()

               if user.profile.staticPage?.show is yes
-                modal =  new KDModalView
-                  cssClass : 'disable-static-page-modal'
-                  title : 'Do you really want to disable your Public Page?'
-                  content : """
+                modal           = new KDModalView
+                  cssClass      : 'disable-static-page-modal'
+                  title         : 'Do you really want to disable your Public Page?'
+                  content       : """

Disabling this feature will disable other people
                       from publicly viewing your profile. You will still be
@@ -179,17 +180,17 @@

Do you want to continue?

                     """
-                  buttons :
+                  buttons       :
                     "Disable the Public Page" :
-                      cssClass : 'modal-clean-red'
-                      callback :=>
+                      cssClass  : 'modal-clean-red'
+                      callback  : =>
                         modal.destroy()
                         user.setStaticPageVisibility no, (err,res)=>
                           if err then log err
                           disableLink.updatePartial 'Enable this Public Page'
-                    Cancel :
-                      cssClass : 'modal-cancel'
-                      callback :=>
+                    Cancel      :
+                      cssClass  : 'modal-cancel'
+                      callback  : =>
                         modal.destroy()
               else
                 user.setStaticPageVisibility yes, (err,res)=>
@@ -253,10 +254,10 @@

     @controller.on 'LazyLoadThresholdReached', =>
       appManager.tell 'Activity', 'fetchActivity',
-        originId : profileUser.getId()
-        facets : currentFacets
-        to     : @controller.itemsOrdered.last.getData().meta.createdAt
-        bypass : yes
+        originId  : profileUser.getId()
+        facets    : currentFacets
+        to        : @controller.itemsOrdered.last.getData().meta.createdAt
+        bypass    : yes
       , @bound "appendActivities"

   repositionLogoView:->
@@ -337,5 +338,5 @@
 class StaticHandleInput extends KDHitEnterInputView
   constructor:(options,data)->
     options.cssClass = 'profile-handle-customize'
-    options.defaultValue = data.profile.handles[options.service]
+    options.defaultValue = data.profile.handles?[options.service] or ''
     super options,data


oauth: fix popup blocker error msg

oauth: fix popup blocker error msg

--- client/landing/site.landing/coffee/core/oauthcontroller.coffee
+++ client/landing/site.landing/coffee/core/oauthcontroller.coffee
@@ -22,7 +22,7 @@
       newWindow  = window.open url, name, size

       unless newWindow
-        notify "Please disable your popup blocker and try again."
+        notify {message : "Please disable your popup blocker and try again."}
         return

       newWindow.onunload =->
@@ -40,5 +40,5 @@
       mainController.emit "ForeignAuthCompleted", provider

   notify = (err)->
-    message = if err then "#{err.message}" else "Something went wrong"
+    message = if err then err.message else "Something went wrong"
     new KDNotificationView title : message


Don't resolve fixtures directories using load path

--- spec/project-spec.coffee
+++ spec/project-spec.coffee
@@ -63,7 +63,7 @@
   describe ".open(path)", ->
     [fooOpener, barOpener, absolutePath, newBufferHandler, newEditSessionHandler] = []
     beforeEach ->
-      absolutePath = fsUtils.resolveOnLoadPath('fixtures/dir/a')
+      absolutePath = require.resolve('./fixtures/dir/a')
       newBufferHandler = jasmine.createSpy('newBufferHandler')
       project.on 'buffer-created', newBufferHandler
       newEditSessionHandler = jasmine.createSpy('newEditSessionHandler')
@@ -132,7 +132,7 @@
   describe ".resolve(uri)", ->
     describe "when passed an absolute or relative path", ->
       it "returns an absolute path based on the project's root", ->
-        absolutePath = fsUtils.resolveOnLoadPath('fixtures/dir/a')
+        absolutePath = require.resolve('./fixtures/dir/a')
         expect(project.resolve('a')).toBe absolutePath
         expect(project.resolve(absolutePath + '/../a')).toBe absolutePath
         expect(project.resolve('a/../a')).toBe absolutePath
@@ -144,15 +144,16 @@
   describe ".setPath(path)", ->
     describe "when path is a file", ->
       it "sets its path to the files parent directory and updates the root directory", ->
-        project.setPath(fsUtils.resolveOnLoadPath('fixtures/dir/a'))
-        expect(project.getPath()).toEqual fsUtils.resolveOnLoadPath('fixtures/dir')
-        expect(project.getRootDirectory().path).toEqual fsUtils.resolveOnLoadPath('fixtures/dir')
+        project.setPath(require.resolve('./fixtures/dir/a'))
+        expect(project.getPath()).toEqual path.dirname(require.resolve('./fixtures/dir/a'))
+        expect(project.getRootDirectory().path).toEqual path.dirname(require.resolve('./fixtures/dir/a'))

     describe "when path is a directory", ->
       it "sets its path to the directory and updates the root directory", ->
-        project.setPath(fsUtils.resolveOnLoadPath('fixtures/dir/a-dir'))
-        expect(project.getPath()).toEqual fsUtils.resolveOnLoadPath('fixtures/dir/a-dir')
-        expect(project.getRootDirectory().path).toEqual fsUtils.resolveOnLoadPath('fixtures/dir/a-dir')
+        directory = fsUtils.absolute(path.join(__dirname, 'fixtures', 'dir', 'a-dir'))
+        project.setPath(directory)
+        expect(project.getPath()).toEqual directory
+        expect(project.getRootDirectory().path).toEqual directory

     describe "when path is null", ->
       it "sets its path and root directory to null", ->
@@ -183,7 +184,7 @@
     describe "when config.core.hideGitIgnoredFiles is true", ->
       it "ignores files that are present in .gitignore if the project is a git repo", ->
         config.set "core.hideGitIgnoredFiles", true
-        project.setPath(fsUtils.resolveOnLoadPath('fixtures/git/working-dir'))
+        project.setPath(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
         paths = null
         waitsForPromise ->
           project.getFilePaths().done (foundPaths) -> paths = foundPaths
@@ -195,7 +196,7 @@
       ignoredFile = null

       beforeEach ->
-        ignoredFile = path.join(fsUtils.resolveOnLoadPath('fixtures/dir'), 'ignored.txt')
+        ignoredFile = path.join(__dirname, 'fixtures', 'dir', 'ignored.txt')
         fsUtils.writeSync(ignoredFile, "")

       afterEach ->
@@ -214,7 +215,7 @@
       ignoredFile = null

       beforeEach ->
-        ignoredFile = path.join(fsUtils.resolveOnLoadPath('fixtures/dir'), 'ignored/ignored.txt')
+        ignoredFile = path.join(__dirname, 'fixtures', 'dir', 'ignored', 'ignored.txt')
         fsUtils.writeSync(ignoredFile, "")

       afterEach ->
@@ -262,7 +263,7 @@
             range: [[2, 6], [2, 11]]

       it "works on evil filenames", ->
-        project.setPath(fsUtils.resolveOnLoadPath('fixtures/evil-files'))
+        project.setPath(path.join(__dirname, 'fixtures', 'evil-files'))
         paths = []
         matches = []
         waitsForPromise ->
@@ -294,7 +295,7 @@
         project.scan /a+/, iterator

         stdout = BufferedProcess.prototype.bufferStream.argsForCall[0][1]
-        stdout ":#{fsUtils.resolveOnLoadPath('fixtures/dir/a')}\n"
+        stdout ":#{path.join(__dirname, 'fixtures', 'dir', 'a')}\n"
         stdout "1;0 3:aaa bbb\n2;3 2:cc aa cc\n"

         expect(iterator.argsForCall[0][0]).toEqual
@@ -311,7 +312,7 @@
         [projectPath, ignoredPath] = []

         beforeEach ->
-          projectPath = fsUtils.resolveOnLoadPath('fixtures/git/working-dir')
+          projectPath = path.join(__dirname, 'fixtures', 'git', 'working-dir')
           ignoredPath = path.join(projectPath, 'ignored.txt')
           fsUtils.writeSync(ignoredPath, 'this match should not be included')


Remove the onchange handler when the view closes.

Without this, there is an accumulation of content change handlers on
jquery, n-1 which does nothing good.

--- scripts/views/workspace/content/aloha-edit.coffee
+++ scripts/views/workspace/content/aloha-edit.coffee
@@ -57,6 +57,15 @@
         @isLoaded = true
         @render()

+    # Stop auto-setting when the view closes
+    onClose: () ->
+      # This is the same as Aloha.unbind, the difference is that Aloha.unbind
+      # asks requirejs to load aloha/jquery first, causing the actual unbind
+      # to be deferred. Of course, Murhpy's Law dictates that it will only
+      # execute after we bound a new handler below, leaving us with no handler
+      # at all.
+      $(Aloha, 'body').off 'aloha-smart-content-changed.updatemodel'
+
     onRender: () ->
       # update model after the user has stopped making changes

@@ -71,7 +80,7 @@
             # Change the contents but do not update the Aloha editable area
             @model.set(@modelKey, editableBody, {internalAlohaUpdate: true})

-        Aloha.bind 'aloha-smart-content-changed', (evt, d) =>
+        Aloha.bind 'aloha-smart-content-changed.updatemodel', (evt, d) =>
           updateModel() if d.editable.obj.is(@$el) or $.contains @$el[0], d.editable.obj[0]

         # Once Aloha has finished loading enable


Server: check if api token usage is enabled for the group

--- servers/lib/server/handlers/api/createuser.coffee
+++ servers/lib/server/handlers/api/createuser.coffee
@@ -73,8 +73,8 @@

 validateData = (data, callback) ->

-  { JUser, JApiToken } = koding.models
-  { token, username }  = data
+  { JUser, JGroup, JApiToken } = koding.models
+  { token, username }          = data

   apiToken = null

@@ -83,10 +83,13 @@
     ->
       # checking if token is valid
       JApiToken.one { code : token }, (err, apiToken_) ->
+
         if err
           return callback { statusCode : 500, message : 'an error occurred' }
+
         unless apiToken_
           return callback { statusCode : 400, message : 'invalid token!' }
+
         apiToken = apiToken_
         queue.next()

@@ -95,10 +98,28 @@
       username or= "#{apiToken.group.substring(0, 4)}#{hat(32)}"
       # checking if username is available
       JUser.usernameAvailable username, (err, { kodingUser, forbidden }) ->
+
         if err
           return callback { statusCode : 500, message : 'an error occurred' }
+
         if kodingUser or forbidden
           return callback { statusCode : 400, message : 'username is not available' }
+
+        queue.next()
+
+    ->
+      # checking if apiToken is enabled for the Group
+      JGroup.one { slug : apiToken.group }, (err, group) ->
+
+        if err
+          return callback { statusCode : 500, message : 'an error occurred' }
+
+        unless group
+          return callback { statusCode : 400, message : 'group does not exist!' }
+
+        unless group.isApiTokenEnabled is true
+          return callback { statusCode : 403, message : 'api token usage is not enabled for this group' }
+
         queue.next()

     -> callback null, { apiToken, username }


PBR: Fix Hammersley 3D texture.

PBR: Fix Hammersley 3D texture.

--- engine/material_shaders/blender_cycles_pbr.coffee
+++ engine/material_shaders/blender_cycles_pbr.coffee
@@ -253,9 +253,9 @@
             pixels = new Float32Array samples*4
             for i in [0...samples]
                 phi = radical_inverse(i) * 2.0 * Math.PI
-                i2 = i<<1
-                pixels[i2] = Math.cos(phi)
-                pixels[i2+1] = Math.sin(phi)
+                i4 = i<<2
+                pixels[i4] = Math.cos(phi)
+                pixels[i4+1] = Math.sin(phi)
             lutsamples_texture = new scene.context.Texture scene,
                 formats: raw_pixels: {
                     width: samples, height: 1, pixels: pixels


Actually call the function addWithHighlight()

--- core/app/backbone/views/fact_relations/add_comment_view.coffee
+++ core/app/backbone/views/fact_relations/add_comment_view.coffee
@@ -14,7 +14,7 @@
   parseKeyDown: (e) =>
     code = e.keyCode || e.which
     @updateModel()
-    @addWithHighlight if code is 13
+    @addWithHighlight() if code is 13

   addWithHighlight: ->
     @addDefaultModel({highlight: true })


corrected port

corrected port

--- config.coffee
+++ config.coffee
@@ -40,7 +40,7 @@

   server:
     path: 'server.js'
-    port: 3335
+    port: 3333
     run: yes

   # minify: true


Refactor jquery-law script

Refactor jquery-law script

--- source/assets/javascripts/jquery-law.js.coffee
+++ source/assets/javascripts/jquery-law.js.coffee
@@ -2,7 +2,7 @@

   @start: (options={}) ->
     alert = new AlertLaw(options)
-    alert.alertOrNot()
+    alert.loadAlert()

   constructor: (options={}) ->
     @cookieUrl      = options.cookieUrl      ? '/cookies'
@@ -10,31 +10,6 @@
     @messageContent = options.messageContent ? 'En poursuivant votre navigation sur notre site, vous en acceptez l‘utilisation pour vous proposer un service personnalisé, des publicités ciblées adaptées à vos centres d’intérêts et réaliser des statistiques de visites.'
     @cookieName     = options.cookieName     ? 'cookies_law'

-  getCookie: ->
-    search      = @cookieName + "="
-    returnvalue = ""
-
-    if document.cookie.length
-      offset = document.cookie.indexOf(search)
-
-      if offset != -1
-        offset += search.length
-        end = document.cookie.indexOf(";", offset)
-        if end == -1
-           end = document.cookie.length
-
-        returnvalue = unescape(document.cookie.substring(offset, end))
-
-    return returnvalue
-
-  expirationDate: ->
-    month_number    = 13
-    current_date    = new Date()
-    expiration_date = current_date.setMonth(current_date.getMonth() + month_number)
-    expiration_date.toUTCString()
-
-  alertOrNot: ->
-    @loadAlert() if @getCookie() == ''

   buildAlert: ->
     "

@@ -44,19 +19,47 @@
       En savoir plus

"

-  loadAlert: ->
-    message = @buildAlert()
-    $('body').prepend(message)
-    $('#js-law--alert').slideDown(300)
+  cookieAlreadyAccepted: ->
+    @cookieContent() != ''

+  cookieContent: ->
+    search      = @cookieName + "="
+    startString = document.cookie.indexOf(search)
+
+    return '' if startString == -1
+
+    startString   += search.length
+    endString     = document.cookie.indexOf(';', startString)
+    endString     = if endString == -1 then endString else document.cookie.length
+    cookieContent = document.cookie.substring(startString, endString)
+    unescape(cookieContent)
+
+  defineEvents: ->
     $('#js-law--close').on 'click', (event) =>
       event.preventDefault()
       $("#js-law--alert").slideUp(300)

     $('a').on 'click', (event) =>
-      document.cookie = "#{@cookieName}=yes;expires='#{@expirationDate()}';path=/"
+      document.cookie = "#{@cookieName}=yes; expires='#{@expirationDate()}'; path=/"
+
+  displayAlert: ->
+    message = @buildAlert()
+    $('body').prepend(message)
+    $('#js-law--alert').slideDown(300)
+
+  expirationDate: ->
+    month_number = 13
+    current_date = expiration_date = new Date()
+    expiration_date.setMonth(current_date.getMonth() + month_number)
+    expiration_date.toUTCString()
+
+  loadAlert: ->
+    unless @cookieAlreadyAccepted()
+      @displayAlert()
+      @defineEvents()

 $ ->
   AlertLaw.start()
+
   # alert = new AlertLaw { cookieUrl: 'foo' }
-  # alert.alertOrNot()
+  # alert.loadAlert()


Disable unreliable test

Disable unreliable test

--- spec/server/functional/achievement.spec.coffee
+++ spec/server/functional/achievement.spec.coffee
@@ -383,7 +383,7 @@
     expect(res.statusCode).toBe 403
     done()

-  it 'recalculates for a single achievement idempotently', utils.wrap (done) ->
+  xit 'recalculates for a single achievement idempotently', utils.wrap (done) ->
     session = new LevelSession({
       permissions: simplePermissions
       creator: @admin._id


Code cleanup

Code cleanup

--- scripts/modules/featured-books/featured-books.coffee
+++ scripts/modules/featured-books/featured-books.coffee
@@ -50,22 +50,28 @@
           hoffset = $first.offset().left - $second.offset().left
           voffset = $first.offset().top - $second.offset().top

-          $container.css({'height': $container.height()+'px'})
+          if voffset
+            # Keep the container the same height during the transition
+            $container.css({'height': $container.height()+'px'})

           # Position the next book to scroll in
           $third.css({display: 'block'})
           if not voffset
             $third.css({position: 'absolute', marginLeft: hoffset*-2 + 'px', paddingLeft: '15px'})

+          # Slide the carousel
           $first.animate {marginLeft: hoffset+'px', marginTop: voffset+'px'}, 1000, () ->
             $first.insertAfter($books.last()).removeAttr('style')
             $container.removeAttr('style')

+          # Adjust the second element to the first's style
           $second.animate {paddingLeft: 0, paddingRight: '15px'}, 1000, () ->
             $second.removeAttr('style')

-          $third.animate {marginLeft: hoffset*-1+'px'}, 1000, () ->
-            $third.removeAttr('style')
+          if not voffset
+            # Adjust the third element to the second's style
+            $third.animate {marginLeft: hoffset*-1+'px'}, 1000, () ->
+              $third.removeAttr('style')

         # Start the carousel
         if @_carousel then clearInterval(@_carousel)


fix typo

fix typo

--- snippets/functions.cson
+++ snippets/functions.cson
@@ -532,7 +532,7 @@
     body: "setScreen( ${1:top}, ${2:bottom} )$3"
   "setShadowPalette()":
     prefix: "setShadowPalette()"
-    body: "setShadowPalette( ${1.startColor}, ${2:endColor}, ${3:rScale}, ${4:gScale}, ${5:bScale} )$6"
+    body: "setShadowPalette( ${1:startColor}, ${2:endColor}, ${3:rScale}, ${4:gScale}, ${5:bScale} )$6"
   "setShakeOff()":
     prefix: "setShakeOff()"
     body: "setShakeOff()$1"


Fix line_station matcher

Fix line_station matcher

--- view/coffee/creator/create_diagram_stops.coffee
+++ view/coffee/creator/create_diagram_stops.coffee
@@ -36,7 +36,7 @@
     setAutoComplete: (elem) ->
       elem.typeahead('destroy')
       design = {hint: true, highlight: true}
-      config = {name: 'stations', display: 'name', source: @matcher}
+      config = {name: 'stations', display: 'name', source: @matcher, limit: 100}
       elem.typeahead(design, config)
     setAutoCompleteAll: ->
       @setAutoComplete($('.autoCompleteStation'))
@@ -78,8 +78,7 @@
   (q, cb) ->
     equals = _.filter xs, (x) -> q == x.station.name
     if equals.length == 0
-      substrRegex = new RegExp(q, 'i')
-      matches = _.filter xs, (x) -> substrRegex.test(x.station.name)
+      matches = _.filter xs, (x) -> x.station.name.startsWith(q) or x.station.name.endsWith(q)
       cb(matches)
     else
       cb(equals)


rebase test

rebase test

--- content/gitoe-repo.coffee
+++ content/gitoe-repo.coffee
@@ -188,6 +188,7 @@
       fetch:  /^fetch: /
       checkout: /^checkout: moving from ([^ ]+) to ([^ ]+)/
       rename_remote: /^remote: renamed ([^ ]+) to ([^ ]+)/
+      rebase_finish: /^rebase (-[^ ]+)? \(finish\): returning to (.*)/
     }
     actions : {
       clone: (matched,change)->
@@ -300,6 +301,11 @@
           @span @ref_realname(matched[1]), "ref_name"
           @span " to "
           @span @ref_realname(matched[2]), "ref_name"
+        ]
+      rebase_finish: (matched, change)->
+        console.log matched
+        @li [
+          @git_command "git rebase"
         ]
     }
   }


Add whitespaces handling within tokens

Add whitespaces handling within tokens

Includes:
- Fix indentation being drawn
- Add controls over char width/height independently of the line height
- Use a single loop for rendering lines with either colors or no colors

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -83,7 +83,9 @@

   getMinimapHeight: -> @getLinesCount() * @getLineHeight()
-  getLineHeight: -> 2
+  getLineHeight: -> 3
+  getCharHeight: -> 2
+  getCharWidth: -> 1
   getLinesCount: -> @editorView.getEditor().getScreenLineCount()

   getMinimapScreenHeight: -> @minimapView.height() #/ @minimapView.scaleY
@@ -100,6 +102,7 @@
     screenRow = 0 if isNaN(screenRow)
     screenRow

+  getDefaultColor: -> @defaultColor ||= @minimapView.editorView.css('color')

   retrieveTokenColorFromDom: (token)->
     # This function insert a dummy token element in the DOM compute its style,
@@ -139,34 +142,38 @@

   drawLines: (firstRow, lastRow, offsetRow, context) ->
     lines = @editor.linesForScreenRows(firstRow, lastRow)
-    context.lineWidth = 1
     lineHeight = @getLineHeight()
-
-    if @minimapView.displayCodeHighlights
-      for line, row in lines
-        x = 0
-        y = offsetRow + row
-        for token in line.tokens
-          w = token.screenDelta
-          if not token.isOnlyWhitespace()
-            color = @getTokenColor(token)
-            context.beginPath()
-            context.strokeStyle = color
-            context.moveTo(x, y*lineHeight)
-            context.lineTo(x+w, y*lineHeight)
-            context.stroke()
-          x += w
-    else
-      context.strokeStyle = "#C0C0C0"
-      context.beginPath()
-      for line, row in lines
-        w = line.text.length
-        y = offsetRow + row
-        if w > 0
-          w0 = line.indentLevel * 2
-          context.moveTo(w0, y*lineHeight+0.5)
-          context.lineTo(w, y*lineHeight+0.5)
-      context.stroke()
+    charHeight = @getCharHeight()
+    charWidth = @getCharWidth()
+    context.lineWidth = charHeight
+    displayCodeHighlights = @minimapView.displayCodeHighlights
+
+    for line, row in lines
+      x = 0
+      y = offsetRow + row
+      for token in line.tokens
+        w = token.screenDelta
+        unless token.isOnlyWhitespace() or token.hasInvisibleCharacters
+          color = if displayCodeHighlights then @getTokenColor(token) else @getDefaultColor()
+          context.fillStyle = color
+          chars = 0
+          y0 = y*lineHeight
+          for char in token.value
+            if /\s/.test char
+              if chars > 0
+                context.fillRect(x-chars, y0, chars*charWidth, charHeight)
+                context.fill()
+              chars = 0
+            else
+              chars++
+
+            x += charWidth
+
+          if chars > 0
+            context.fillRect(x-chars, y0, chars*charWidth, charHeight)
+            context.fill()
+        else
+          x += w * charWidth

   update: =>
     return unless @editorView?
@@ -185,7 +192,7 @@
       @pendingChanges = []

     if @offscreenFirstRow?
-      @context.drawImage(@offscreenCanvas, 0, (@offscreenFirstRow-firstRow) * 2)
+      @context.drawImage(@offscreenCanvas, 0, (@offscreenFirstRow-firstRow) * @getLineHeight())
       if firstRow < @offscreenFirstRow
         @drawLines(firstRow, @offscreenFirstRow, 0, @context)
       if lastRow > @offscreenLastRow


:bug: Fix deprecated property access in render view

--- lib/minimap-render-view.coffee
+++ lib/minimap-render-view.coffee
@@ -329,7 +329,7 @@
   # Returns a {String}.
   getTokenColor: (token) ->
     #Retrieve color from cache if available
-    flatScopes = token.scopes.join()
+    flatScopes = token.scopeDescriptor.join()
     if flatScopes not of @tokenColorCache
       color = @retrieveTokenColorFromDom(token)
       @tokenColorCache[flatScopes] = color
@@ -359,7 +359,7 @@
   # Returns a {String}.
   retrieveTokenColorFromDom: (token) ->
     # This is quite an expensive operation so results are cached in getTokenColor.
-    color = @retrieveStyleFromDom(token.scopes, 'color')
+    color = @retrieveStyleFromDom(token.scopeDescriptor, 'color')
     @transparentize(color, @getTextOpacity())

   # Internal: Returns the background color for the passed-in decoration.


encoding value of text input

encoding value of text input

--- src/scripts/modules/find-content/find-content.coffee
+++ src/scripts/modules/find-content/find-content.coffee
@@ -14,7 +14,7 @@

     events:
       'click .dropdown-menu > li': 'selectSubject'
-      'keyup input': 'triggerSearch'
+      'keyup input, click .find': 'triggerSearch'

     initialize: () ->
       super()
@@ -30,7 +30,7 @@

     triggerSearch: (e) ->
       if e.keyCode is 13
-        @search($(e.currentTarget).val())
+        @search(encodeURIComponent($(e.currentTarget).val()))

     search: (query) ->
       router.navigate("search?q=#{query}", {trigger: true})


add feature check timeline

add feature check timeline

--- scripts/aipo.coffee
+++ scripts/aipo.coffee
@@ -16,6 +16,7 @@
 cronJob = require('cron').CronJob

 PATH_ACTIVITY = '/portal/media-type/html/user/h_sakaguchi_admin/page/default.psml/js_peid/P-143fe3e7fc1-10a40?action=controls.Maximize'
+PATH_TIMELINE = '/portal/media-type/html/user/h_sakaguchi_admin/page/default.psml/js_peid/P-143fe3e7fc1-10a3f?action=controls.Maximize'

 # {{{ SenpaiStorage
 setSenpaiStorage = (robot, key, val) ->
@@ -41,19 +42,46 @@
       @get PATH_ACTIVITY, null, (error, response, $) =>
         cacheActivities = getSenpaiStorage @robot, 'AIPO_ACTIVITIES'
         cacheActivities ||= []
+        dispcnt = 0
         $('.auiRowTable tbody tr').each((i, elem) =>
-          return if i > 5 or i is  0
+          return if dispcnt >= 5 or i is  0
           text = $(elem).text().replace /[\r\n]+/g, ','
           val = text.split(',')
           text = val[2] + ': ' + val[1]
           # a = $(elem).find('a')
           return if text in cacheActivities
           cacheActivities.push text
-          @send "Aipo更新通知 #{text}"
+          @send "[Aipo更新情報] #{text}"
+          dispcnt++
         )
         while cacheActivities.length > 100
           cacheActivities.shift()
-        setSenpaiStorage msg, 'AIPO_ACTIVITIES', cacheActivities
+        setSenpaiStorage @robot, 'AIPO_ACTIVITIES', cacheActivities
+
+  getTimeline: () ->
+    @login (error, response, $) =>
+      @get PATH_TIMELINE, null, (error, response, $) =>
+        cacheTimelines = getSenpaiStorage @robot, 'AIPO_TIMELINES'
+        cacheTimelines ||= []
+        dispcnt = 0
+        $('.messageContents').each((i, elem) =>
+          return if dispcnt >= 5
+          name = $(elem).find('.name').text()
+          a = $(elem).children().find('.body a')
+          #console.log name + a
+          return if a.length # 更新通知に表示されてるやつですから飛ばします
+          body = $(elem).find('.body').text().replace(/[ ]+/g, ' ').replace(/[\r\n]+/g, '')
+          text = "#{name}: #{body}"
+          # console.log text
+          val = text.split(',')
+          return if text in cacheTimelines
+          cacheTimelines.push text
+          @send "[Aipoタイムライン] #{text}"
+          dispcnt++
+        )
+        while cacheTimelines.length > 100
+          cacheTimelines.shift()
+        setSenpaiStorage @robot, 'AIPO_TIMELINES', cacheTimelines

   send: (msg) ->
     response = new @robot.Response(@robot, {user : {id : -1, name : @room}, text : "none", done : false}, [])
@@ -107,15 +135,26 @@
 module.exports = (robot) ->
   aipo = new Aipo robot, process.env.HUBOT_AIPO_SEND_ROOM, process.env.HUBOT_AIPO_BASE_URL, process.env.HUBOT_AIPO_USER, process.env.HUBOT_AIPO_PASSWORD

+  checkUpdate = () ->
+    getActivity()
+    getTimeline()
+
   getActivity = () ->
-    console.log 'start getActivity!!!'
+#    console.log 'start getActivity!!!'
     aipo.getActivity()
+
+  getTimeline = () ->
+#    console.log 'start getTimeline!!!'
+    aipo.getTimeline()

   robot.respond /aipo activity/i, (msg) ->
     getActivity()

+  robot.respond /aipo timeline/i, (msg) ->
+    getTimeline()
+
   # *(sec) *(min) *(hour) *(day) *(month) *(day of the week)
-  new cronJob('10 * * * * *', () ->
-    getActivity()
+  new cronJob('*/5 * * * * *', () ->
+    checkUpdate()
   ).start()


limit lengths of quotes when reply to comment

--- app/assets/javascripts/api/comment_features.js.coffee
+++ app/assets/javascripts/api/comment_features.js.coffee
@@ -38,6 +38,8 @@
   parent_id = $comment.data("comment-id")
   $container = $comment.closest(".juvia-container")
   text = $(".juvia-comment-pure-content", $comment).text()
+  if text.length > 300
+    text = $.trim(text).substring(0,300).trim(this).trim(this) + "..."
   lines = text.split("\n")
   i = undefined
   i = 0
@@ -45,6 +47,7 @@
     lines[i] = "> " + lines[i]
     i++
   $textarea = $("textarea", $container)
+  $textarea.val ""
   $('input[name="parent_id"]').val(parent_id);
   #var authorName = $('input[name="author_name"]', $container).val();
   authorName = $.trim($(".juvia-author-name", $comment).text())


Add version helper function

Add version helper function

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -64,6 +64,12 @@
   # Returns True if the device is running iOS, else False.
   isIOS: =>
     @os.ios
+
+  # Public: Get the version of the OS running on the device.
+  #
+  # Returns a String of the OS version.
+  osVersion: =>
+    @os.version.toString()

   # Public: Go to a specific page.
   #


Moved score calculation to server-side

Moved score calculation to server-side


Fix selection error when clicking some input elements. Fixes #209

--- src/trix/core/helpers/selection.coffee
+++ src/trix/core/helpers/selection.coffee
@@ -4,10 +4,22 @@
     selection if selection.rangeCount > 0

   getDOMRange: ->
-    Trix.getDOMSelection()?.getRangeAt(0)
+    if domRange = Trix.getDOMSelection()?.getRangeAt(0)
+      unless domRangeIsPrivate(domRange)
+        domRange

   setDOMRange: (domRange) ->
     selection = window.getSelection()
     selection.removeAllRanges()
     selection.addRange(domRange)
     Trix.selectionChangeObserver.update()
+
+# In Firefox, clicking certain <input> elements changes the selection to a
+# private element used to draw its UI. Attempting to access properties of those
+# elements throws an error.
+# https://bugzilla.mozilla.org/show_bug.cgi?id=208427
+domRangeIsPrivate = (domRange) ->
+  nodeIsPrivate(domRange.startContainer) or nodeIsPrivate(domRange.endContainer)
+
+nodeIsPrivate = (node) ->
+  not Object.getPrototypeOf(node)


Load JS on country page correctly

Load JS on country page correctly

--- app/assets/javascripts/modules/country/base.js.coffee
+++ app/assets/javascripts/modules/country/base.js.coffee
@@ -1,29 +1,31 @@
-require(['annular_sector'], (annularSector)->
-  $vizContainer = $('#protected-coverage-viz')
-  return false if $vizContainer.length == 0
+$(document).ready( ->
+  require(['annular_sector'], (annularSector)->
+    $vizContainer = $('#protected-coverage-viz')
+    return false if $vizContainer.length == 0

-  $vizContainer.find('.viz').each (idx, el) ->
-    value = $(el).attr('data-value')
-    return if typeof +value isnt 'number' or +value is isNaN
-    data = [
-      {
-        value: value
-        color: $(el).attr('data-colour')
-      }
-      {
-        value: 100 - value
-        color: '#d2d2db'
-        is_background: true
-      }
-    ]
+    $vizContainer.find('.viz').each (idx, el) ->
+      value = $(el).attr('data-value')
+      return if typeof +value isnt 'number' or +value is isNaN
+      data = [
+        {
+          value: value
+          color: $(el).attr('data-colour')
+        }
+        {
+          value: 100 - value
+          color: '#d2d2db'
+          is_background: true
+        }
+      ]

-    annularSector data, el, 160, 160
+      annularSector data, el, 160, 160
+  )
+
+  $countriesSelect = $('#countries-select')
+  return false if $countriesSelect.length == 0
+
+  $countriesSelect.select2(containerCss: {width: '200px'})
+  $countriesSelect.on 'change', (ev) ->
+    iso2 = ev.added.id
+    window.location = "/country/AR/compare/#{iso2}"
 )
-
-$countriesSelect = $('#countries-select')
-return false if $countriesSelect.length == 0
-
-$countriesSelect.select2(containerCss: {width: '200px'})
-$countriesSelect.on 'change', (ev) ->
-  iso2 = ev.added.id
-  window.location = "/country/AR/compare/#{iso2}"


Initially set brush with setBrush

Initially set brush with setBrush

--- app/assets/javascripts/player/screen_buffer.js.coffee
+++ app/assets/javascripts/player/screen_buffer.js.coffee
@@ -7,8 +7,8 @@
     @cursorX = 0
     @cursorY = 0

-    @brush = AsciiIo.Brush.create({})
-    @setCharset('us')
+    @setBrush AsciiIo.Brush.create({})
+    @setCharset 'us'

   topMargin: ->
     @scrollRegion.getTop()


Update French translations

Update French translations

--- app/assets/javascripts/uploadcare/locale/fr.js.coffee
+++ app/assets/javascripts/uploadcare/locale/fr.js.coffee
@@ -35,15 +35,28 @@
           one: 'Sélectionner une image',
           other: 'Sélectionner des images'
     dialog:
+      close: 'Fermer'
+      openMenu: 'Ouvrir le menu'
       done: 'Terminer'
       showFiles: 'Voir les fichiers'
       tabs:
         names:
+          'empty-pubkey': 'Bienvenue'
           preview: 'Avant-première'
           file: 'Fichier en local'
           url: 'Une adresse web'
-          'empty-pubkey': 'Bienvenue'
-          camera: 'Appareil photo'
+          camera: 'Caméra'
+          facebook: 'Facebook'
+          dropbox: 'Dropbox'
+          gdrive: 'Google Drive'
+          gphotos: 'Google Photos'
+          instagram: 'Instagram'
+          vk: 'VK'
+          evernote: 'Evernote'
+          box: 'Box'
+          skydrive: 'OneDrive'
+          flickr: 'Flickr'
+          huddle: 'Huddle'
         file:
           drag: 'Glissez-déposez un fichier ici'
           nodrop: 'Envoyez des fichiers depuis votre ordinateur'
@@ -58,12 +71,16 @@
           input: 'Collez le lien ici...'
           button: 'Envoi'
         camera:
+          title: 'Fichier depuis la caméra'
           capture: 'Prendre une photo'
           mirror: 'Miroir'
+          startRecord: 'Enregistrer une vidéo'
+          stopRecord: 'Arrêter'
+          cancelRecord: 'Annuler'
           retry: 'Envoyer une nouvelle demande de permission'
           pleaseAllow:
             title: 'Autorisez l\'accès à votre appareil photo'
-            text: 'Vous avez été invité à autoriser l\'accès à votre appareil photo. Pour prendre des photos avec votre appareil photo vous devez approuver cette demande.'
+            text: 'Vous avez été invité à autoriser l\'accès à votre appareil photo. Pour prendre des photos avec votre caméra vous devez approuver cette demande.'
           notFound:
             title: 'Aucun appareil photo détecté'
             text: 'Il semblerait que vous n\'ayez pas d\'appareil photo connecté à cet appareil.'
@@ -86,6 +103,9 @@
             title: 'Recadrer et ajouter cette image'
             done: 'Terminer'
             free: 'libre'
+          video:
+            title: 'Ajouter cette vidéo ?'
+            change: 'Annuler'
           error:
             default:
               title: 'Oups!'
@@ -104,10 +124,13 @@
           multiple:
             title: 'Vous avez choisi %files%'
             question: 'Voulez vous ajouter tous ces fichiers ?'
+            tooManyFiles: 'Vous avez choisi trop de fichiers. %max% est le maximum.'
+            tooFewFiles: 'Vous avez choisi %fichiers%. %min% est le minimum.'
             clear: 'Tout retirer'
             done: 'Terminer'
-            tooManyFiles: 'Vous avez choisi trop de fichiers. %max% est le maximum.'
-            tooFewFiles: 'Vous avez choisi %fichiers%. %min% est le minimum.'
+            file:
+              preview: 'Prévisualiser %file%'
+              remove: 'Supprimer %file%'

 # Pluralization rules taken from:


OX-226 Fix crosshairs

OX-226 Fix crosshairs

--- app/assets/javascripts/geometries/plane.coffee
+++ app/assets/javascripts/geometries/plane.coffee
@@ -57,10 +57,10 @@
     @crosshair          = new Array(2)
     for i in [0..1]
       crosshairGeometries[i] = new THREE.Geometry()
-      crosshairGeometries[i].vertices.push(new THREE.Vector3(-pWidth/2*i, 0, -pWidth/2*(1-i)))
-      crosshairGeometries[i].vertices.push(new THREE.Vector3( -25*i, 0,  -25*(1-i)))
-      crosshairGeometries[i].vertices.push(new THREE.Vector3( 25*i, 0, 25*(1-i)))
-      crosshairGeometries[i].vertices.push(new THREE.Vector3( pWidth/2*i, 0,  pWidth/2*(1-i)))
+      crosshairGeometries[i].vertices.push(new THREE.Vector3(-pWidth/2*i, -pWidth/2*(1-i), 0))
+      crosshairGeometries[i].vertices.push(new THREE.Vector3( -25*i,  -25*(1-i), 0))
+      crosshairGeometries[i].vertices.push(new THREE.Vector3( 25*i, 25*(1-i), 0))
+      crosshairGeometries[i].vertices.push(new THREE.Vector3( pWidth/2*i,  pWidth/2*(1-i), 0))
       @crosshair[i] = new THREE.Line(crosshairGeometries[i], new THREE.LineBasicMaterial({color: CROSSHAIR_COLORS[@planeID][i], linewidth: 1}), THREE.LinePieces)

     # create borders


exec: user ip instead of hostname by default

--- src/commands/exec.coffee
+++ src/commands/exec.coffee
@@ -14,7 +14,7 @@
     return if params.hosts? and multimatch(server.host, params.hosts).indexOf(server.host) is -1
     connection = merge {}, config.connection, server.connection
     connection.username ?= 'root'
-    connection.host ?= connection.ip or server.host
+    connection.host ?= connection.ip or server.ip or server.host
     connection.port ?= 22
     connection.private_key_location ?= '~/.ssh/id_rsa'
     connect connection, (err, ssh) ->


dont throw error for lack of travis variable

--- tasks/ember-s3.coffee
+++ tasks/ember-s3.coffee
@@ -7,8 +7,8 @@
   ENV = process.env
   # For WHAT is the purpose of uploading versions of files if the version lacks
   # presence!
-  commit = ENV.TRAVIS_COMMIT or throw new Error('set a TRAVIS_COMMIT env var')

+  commit = ENV.TRAVIS_COMMIT
   # Turn some lovely filename such as dist/1.js into 1-#{commit}.js and
   # 1-latest.js
   fileName = (file) -> file.split('/')[1]


Correctly checks for undefined in coffeescript :see_no_evil:

--- app/assets/javascripts/labels_select.js.coffee
+++ app/assets/javascripts/labels_select.js.coffee
@@ -201,7 +201,7 @@
             callback data

         renderRow: (label) ->
-          removesAll = label.id is 0 or label.id is undefined
+          removesAll = label.id is 0 or not label.id?

           selectedClass = []
           if $form.find("input[type='hidden']\


make multiple graphlinks on self behave sanely

- If a node has multiple edges on itself,
  the arcs of these edges should not have
  the same values. Instead random numbers
  are used to modify the display of such
  edges.

--- app/assets/javascripts/plugins/graph.js.coffee
+++ app/assets/javascripts/plugins/graph.js.coffee
@@ -25,6 +25,9 @@
   $.get("#{graphs_uri}?depth=#{depth}", (data) ->
       displayGraph(data)
     , "json")
+
+randomNumber = (min, max) ->
+  return Math.random() * (max - min) + min

 addClass = (selector, klass) ->
   el = $(selector)
@@ -225,6 +228,17 @@
       text((d) ->
         nodeDisplayName(d))

+    calc_offsets = (d) ->
+      d.arc_offset_a = randomNumber(55, 15) if d.arc_offset_a == undefined
+      d.arc_offset_b = randomNumber(20, 10) if d.arc_offset_b == undefined
+      if d.pos_offset_a == undefined
+        rand = Math.floor(randomNumber(1,100))
+        d.pos_offset_a = 1
+        d.pos_offset_a = 0 if (rand % 2) == 0
+      if d.pos_offset_b == undefined
+        rand = Math.floor(randomNumber(1,100))
+        d.pos_offset_b = 0
+        d.pos_offset_b = 1 if (rand % 2) == 0

     tick = ->
       node.attr("transform", (d) ->
@@ -236,12 +250,14 @@
         dx = d.target.x - d.source.x
         dy = d.target.y - d.source.y
         dr = Math.sqrt(dx*dx + dy*dy)*2
+        calc_offsets(d)
         if d.source.info.id != d.target.info.id
           "M#{d.source.x},#{d.source.y}" +
           "A#{dr},#{dr} 0 0,1 #{d.target.x},#{d.target.y}"
         else
           "M#{d.source.x},#{d.source.y-0.6}" +
-          "A-30,-15 0 1,0 #{d.target.x-4},#{d.target.y}"
+          "A-#{d.arc_offset_a},-#{d.arc_offset_b} 0 " +
+          "#{d.pos_offset_a},#{d.pos_offset_b} #{d.target.x-4},#{d.target.y}"
       )
     force.on('tick', tick)


Last Requests: Fixed domain filter

Last Requests: Fixed domain filter

--- app/scripts/controllers/last-requests.coffee
+++ app/scripts/controllers/last-requests.coffee
@@ -78,7 +78,8 @@
       @loadedRequests = null

   getRequestsInLastDay: ->
-    @LastRequestsFcty.getRequestsInLastDay(@appendDomainParams()).then @_fillRequestsInLastDay, =>
+    params = {domain_filter: @domainFilter}
+    @LastRequestsFcty.getRequestsInLastDay(params).then @_fillRequestsInLastDay, =>
       @loadedRequestsInLastDay = null

   onPageChange: (currentPage, pageSize) =>