Fix TIE Defender Maarek.

Fix TIE Defender Maarek.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -777,7 +777,8 @@
         "Baron of the Empire":
             name: "Barón del Imperio"
             ship: "Prototipo de TIE Avanzado"
-        "Maarek Stele (Defensor TIE)":
+        "Maarek Stele (TIE Defender)":
+            name: "Maarek Stele (Defensor TIE)"
             text: """Cuando tu ataque inflija una carta de Daño boca arriba al defensor, en vez de eso roba 3 cartas de Daño, elige 1 de ellas a tu elección y luego descarta las otras."""
             ship: "Defensor TIE"
         "Countess Ryad":


Use Pane::setActiveItem internally so observers are invoked

--- src/pane.coffee
+++ src/pane.coffee
@@ -68,7 +68,7 @@
     @emitter = new Emitter

     @items = Sequence.fromArray(compact(params?.items ? []))
-    @activeItem ?= @items[0]
+    @setActiveItem(@items[0]) unless @getActiveItem()?

     @subscribe @items.onEach (item) =>
       if typeof item.on is 'function'
@@ -214,7 +214,7 @@
     @items.splice(index, 0, item)
     @emit 'item-added', item, index
     @emitter.emit 'did-add-item', {item, index}
-    @activeItem ?= item
+    @setActiveItem(item) unless @getActiveItem()?
     item

   # Public: Adds the given items to the pane.
@@ -236,7 +236,7 @@
     return if index is -1
     if item is @activeItem
       if @items.length is 1
-        @activeItem = undefined
+        @setActiveItem(undefined)
       else if index is 0
         @activateNextItem()
       else


:art:

:art:

--- src/atom.coffee
+++ src/atom.coffee
@@ -550,10 +550,7 @@
   startEditorWindow: ->
     @installUncaughtErrorHandler()

-    {safeMode} = @getLoadSettings()
-
     CommandInstaller = require './command-installer'
-
     commandInstaller = new CommandInstaller(@getVersion())
     commandInstaller.installAtomCommand false, (error) ->
       console.warn error.message if error?
@@ -561,7 +558,6 @@
       console.warn error.message if error?

     @config.load()
-
     @themes.loadBaseStylesheets()

     @setBodyPlatformClass()
@@ -575,7 +571,7 @@

     @packages.activate()
     @keymaps.loadUserKeymap()
-    @requireUserInitScript() unless safeMode
+    @requireUserInitScript() unless @getLoadSettings().safeMode

     @menu.update()
     @disposables.add @config.onDidChange 'core.autoHideMenuBar', ({newValue}) =>


revert

revert

--- src/auth.coffee
+++ src/auth.coffee
@@ -11,7 +11,6 @@
 #  on every page load we will check if we return from login
   # catch FB message
   window.onmessage = (msg) ->
-      console.log(msg.data)
       if (msg.origin == Settings.BASE_URL)
         if msg.data.indexOf("srndp-ready") != -1
           # call serendip ready


Fix Model.find

Fix Model.find

--- src/marbles/model.js.coffee
+++ src/marbles/model.js.coffee
@@ -7,7 +7,7 @@
   @_id_counter: 0
   @model_name: '_default'

-  @buildIdMappingScope: (params) =>
+  @buildIdMappingScope: (params) ->
     scope = []
     for key in @id_mapping_scope
       return null unless params.hasOwnProperty(key)
@@ -20,6 +20,7 @@
       params.cid = cid

     if params.cid && instance = @instances.all[params.cid]
+      options?.success?(instance)
       return instance

     if params.id && (!options.hasOwnProperty('fetch') || options.fetch) && (!params.hasOwnProperty('fetch') || params.fetch)


lazy lazy loading

lazy lazy loading

--- src/menu/NewGameMenu.coffee
+++ src/menu/NewGameMenu.coffee
@@ -8,7 +8,6 @@
 PlayerRacerController = require 'racer/PlayerRacerController'
 MenuCameraController = require 'camera/MenuCameraController'
 CameraController = require 'camera/CameraController'
-MainMenu = require 'menu/MainMenu'
 IO = require 'core/IO'

 class NewGameMenu extends ListMenu
@@ -30,7 +29,7 @@
       new MenuOption("Race", 20, 280, =>
         console.log "this should start a race"),
       new MenuOption("Back", 20, 380, =>
-        console.log MainMenu
+        MainMenu = require 'menu/MainMenu'
         @game.addEntity(new MainMenu())
         @destroy())
       ]


Added missing EMAIL_REGEX.

Added missing EMAIL_REGEX.

--- lib/match.coffee
+++ lib/match.coffee
@@ -2,6 +2,9 @@
 UNMISTAKABLE_CHARS = '23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz'
 INVALID_ID_CHARS_REGEX = new RegExp "[^#{ UNMISTAKABLE_CHARS }]"
 INVALID_SHA256_CHARS_REGEX = new RegExp '[^a-f0-9]'
+
+# Regular expression for matching e-mail addresses.
+EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

 Match.PositiveNumber = Match.Where (x) ->
   check x, Number


fix response status check

fix response status check

--- app/assets/javascripts/autotolk/locales.js.coffee
+++ app/assets/javascripts/autotolk/locales.js.coffee
@@ -17,6 +17,6 @@

 translate = (original, to, textarea) ->
   $.get '/translate', {original: original, to: to}, ((response) ->
-    if response.status == 200
+    if response.status >= 200 && response.status < 400
       textarea.innerText = response.translated
   ), 'json'


Quantity.smartScale should not scale to invalid unit

Prevents attempted smart-scaling from '#' to 'k#', 'M#', etc. If the
unit does not exist, smartScale will return the value in the original
unit.

--- app/assets/javascripts/lib/models/quantity.coffee
+++ app/assets/javascripts/lib/models/quantity.coffee
@@ -129,7 +129,10 @@
       _.find(POWERS, (x) -> x.multiple is multiple)

     if power is @unit.power then this else
-      @to("#{ power.prefix }#{ @unit.base.name }")
+      try
+        @to("#{ power.prefix }#{ @unit.base.name }")
+      catch
+        this

   # Public: Formats the quantity as a human-readable string, using the
   # localisation options given as parameters, and defined in the unit.


renable linting

renable linting

--- tutor/src/flux/new-course.coffee
+++ tutor/src/flux/new-course.coffee
@@ -62,6 +62,7 @@
 # used by api
 # coffeelint: disable=no_empty_functions
   clone: ->
+# coffeelint: enable=no_empty_functions
   create: ->
     @_local['newlyCreatedCourse'] = null


Unique names for testing collections.

Unique names for testing collections.

Otherwise names could conflict with some other collections in other
packages when testing whole project.

--- tests.coffee
+++ tests.coffee
@@ -1,5 +1,5 @@
-Users = new Meteor.Collection 'Users'
-Posts = new Meteor.Collection 'Posts'
+Users = new Meteor.Collection "Users_#{ Random.id() }"
+Posts = new Meteor.Collection "Posts_#{ Random.id() }"

 intersectionObjects = (array, rest...) ->
   _.filter _.uniq(array), (item) ->


Fixes Hull-Access-Tken revocation in remote

--- src/remote/services/hull.coffee
+++ src/remote/services/hull.coffee
@@ -3,21 +3,21 @@
   API_PATH_REGEXP = /^\/?api\/v1\//

   (app)->
+    # Safari hack: Safari doesn't send response tokens for remote exchange
     accessToken = app.config.access_token
     headers = { 'Hull-App-Id': app.config.appId }
-    headers['Hull-Access-Token'] = accessToken if accessToken?

     identified = false
     originalUserId = app.config.data?.me?.id

     identify = (me) ->
       return unless me
+      identified = !!me.id?
+      return unless identified
       analytics = require('analytics')
       signInCount = me.stats?.sign_in_count || 0

-      if identified && signInCount == 1
-        analytics.alias(me.id)
-        identified = true
+      analytics.alias(me.id) if identified && signInCount == 1

       ident = _.pick(me, 'name', 'email', 'id', 'picture')
       ident.created = me.created_at
@@ -37,6 +37,7 @@
     hullHandler = (options, success, error) ->
       url = normalizePath(options.path)

+      handler.headers['Hull-Access-Token'] = accessToken
       promise = handler.handle
         url: url
         type: options.method
@@ -46,7 +47,7 @@
         identify(h.response) if url == '/api/v1/me'

         if accessToken && originalUserId && originalUserId != h.headers['Hull-User-Id']
-          accessToken = null
+          accessToken = undefined

         h.provider = 'hull'

@@ -75,6 +76,7 @@
       doTrack(eventName, req.params)
       req.params.event ?= eventName
       req.params = { t: btoa(JSON.stringify(req.params)) }
+      handler.headers['Hull-Access-Token'] = accessToken
       promise = handler.handle
         url: normalizePath 't'
         type: req.method || 'post'
@@ -101,9 +103,7 @@

       analytics.initialize(analyticsSettings)

-      if app.config.data.me?.id?
-        identified = true
-        identify(app.config.data.me)
+      identify(app.config.data.me) if app.config.data.me?

       doTrack("hull.app.init")
       app.core.routeHandlers.hull = hullHandler


Remove unnecessary sorting, add utility function for testing

--- src/models.coffee
+++ src/models.coffee
@@ -374,7 +374,10 @@
             lang = p13n.getLanguage()
             res = _.filter @get('connections'), (c) ->
                 (c.section_type in sections) and lang of c.name
-            _.sortBy res, (c) -> c.order  # TODO: not strictly necessary?
+
+        _isOrdered: (coll) ->
+            not _.find coll, (el, i, coll) ->
+                coll[i].order >= (coll[i+1]?.order or Number.MAX_VALUE)

         toJSON: (options) ->
             data = super()


Fetch required data for search results.

Fetch required data for search results.

--- src/models.coffee
+++ src/models.coffee
@@ -626,7 +626,8 @@
             opts.data =
                 q: query
                 language: p13n.getLanguage()
-                only: 'name,location,root_services'
+                only: 'unit.name,service.name,unit.location,unit.root_services'
+                include: 'unit.accessibility_properties,service.ancestors,unit.services'
             city = p13n.get('city')
             if city
                 opts.data.municipality = city


Fix lint warnings

Fix lint warnings

--- src/prep.coffee
+++ src/prep.coffee
@@ -189,22 +189,25 @@
             log.warn "Missing rev: #{JSON.stringify was, null, 2}"
             callback new Error 'Missing rev'
         else
-            doc.docType = 'file'
-            doc.lastModification ?= new Date
-            if doc.lastModification is 'Invalid date'
-                doc.lastModification = new Date
-            @buildId doc
-            @buildId was
-            docIgnored = @ignore.isIgnored doc
-            wasIgnored = @ignore.isIgnored was
-            if side is 'local' and docIgnored and wasIgnored
-                callback()
-            else if side is 'local' and docIgnored
-                @merge.deleteFile side, was, callback
-            else if side is 'local' and wasIgnored
-                @merge.addFile side, doc, callback
-            else
-                @merge.moveFile side, doc, was, callback
+            @doMoveFile side, doc, was, callback
+
+    doMoveFile: (side, doc, was, callback) ->
+        doc.docType = 'file'
+        doc.lastModification ?= new Date
+        if doc.lastModification is 'Invalid date'
+            doc.lastModification = new Date
+        @buildId doc
+        @buildId was
+        docIgnored = @ignore.isIgnored doc
+        wasIgnored = @ignore.isIgnored was
+        if side is 'local' and docIgnored and wasIgnored
+            callback()
+        else if side is 'local' and docIgnored
+            @merge.deleteFile side, was, callback
+        else if side is 'local' and wasIgnored
+            @merge.addFile side, doc, callback
+        else
+            @merge.moveFile side, doc, was, callback

     # Expectations:
     #   - the new folder path is present and valid
@@ -225,22 +228,25 @@
             log.warn "Missing rev: #{JSON.stringify was, null, 2}"
             callback new Error 'Missing rev'
         else
-            doc.docType = 'folder'
-            doc.lastModification ?= new Date
-            if doc.lastModification is 'Invalid date'
-                doc.lastModification = new Date
-            @buildId doc
-            @buildId was
-            docIgnored = @ignore.isIgnored doc
-            wasIgnored = @ignore.isIgnored was
-            if side is 'local' and docIgnored and wasIgnored
-                callback()
-            else if side is 'local' and docIgnored
-                @merge.deleteFolder side, was, callback
-            else if side is 'local' and wasIgnored
-                @merge.addFolder side, doc, callback
-            else
-                @merge.moveFolder side, doc, was, callback
+            @doMoveFolder side, doc, was, callback
+
+    doMoveFolder: (side, doc, was, callback) ->
+        doc.docType = 'folder'
+        doc.lastModification ?= new Date
+        if doc.lastModification is 'Invalid date'
+            doc.lastModification = new Date
+        @buildId doc
+        @buildId was
+        docIgnored = @ignore.isIgnored doc
+        wasIgnored = @ignore.isIgnored was
+        if side is 'local' and docIgnored and wasIgnored
+            callback()
+        else if side is 'local' and docIgnored
+            @merge.deleteFolder side, was, callback
+        else if side is 'local' and wasIgnored
+            @merge.addFolder side, doc, callback
+        else
+            @merge.moveFolder side, doc, was, callback

     # Expectations:
     #   - the file path is present and valid


Fixed broken URL to grunt-parker GitHub page in parker-report.md

--- src/parker.coffee
+++ src/parker.coffee
@@ -113,7 +113,7 @@
                 aLogFileLines.push ""
                 aLogFileLines.push "* * *"
                 aLogFileLines.push ""
-                aLogFileLines.push "Last generated: #{ grunt.template.today() } by [grunt-packer](https://github.com/leny/grunt-packer)."
+                aLogFileLines.push "Last generated: #{ grunt.template.today() } by [grunt-parker](https://github.com/leny/grunt-parker)."
                 aLogFileLines.push ""

             grunt.file.write oOptions.file, aLogFileLines.join "\n"


Push all async parsing of wrapper VAST inside a closure to work on the correct ad object

--- src/parser.coffee
+++ src/parser.coffee
@@ -50,40 +50,41 @@

             for ad in response.ads
                 continue unless ad.nextWrapperURL?
-
-                if parentURLs.length >= 10 or ad.nextWrapperURL in parentURLs
-                    # Wrapper limit reached, as defined by the video player.
-                    # Too many Wrapper responses have been received with no InLine response.
-                    VASTUtil.track(ad.errorURLTemplates, ERRORCODE: 302)
-                    response.ads.splice(response.ads.indexOf(ad), 1)
-                    complete()
-                    break
-
-                if ad.nextWrapperURL.indexOf('://') == -1
-                    # Resolve relative URLs (mainly for unit testing)
-                    baseURL = url.slice(0, url.lastIndexOf('/'))
-                    ad.nextWrapperURL = "#{baseURL}/#{ad.nextWrapperURL}"
-
-                @_parse ad.nextWrapperURL, parentURLs, (err, wrappedResponse) =>
-                    if err?
-                        # Timeout of VAST URI provided in Wrapper element, or of VAST URI provided in a subsequent Wrapper element.
-                        # (URI was either unavailable or reached a timeout as defined by the video player.)
-                        VASTUtil.track(ad.errorURLTemplates, ERRORCODE: 301)
+                do (ad) =>
+                    if parentURLs.length >= 10 or ad.nextWrapperURL in parentURLs
+                        # Wrapper limit reached, as defined by the video player.
+                        # Too many Wrapper responses have been received with no InLine response.
+                        VASTUtil.track(ad.errorURLTemplates, ERRORCODE: 302)
                         response.ads.splice(response.ads.indexOf(ad), 1)
-                    else if not wrappedResponse?
-                        # No Ads VAST response after one or more Wrappers
-                        VASTUtil.track(ad.errorURLTemplates, ERRORCODE: 303)
-                        response.ads.splice(response.ads.indexOf(ad), 1)
-                    else
-                        response.errorURLTemplates = response.errorURLTemplates.concat wrappedResponse.errorURLTemplates
-                        index = response.ads.indexOf(ad)
-                        response.ads.splice(index, 1)
-                        for wrappedAd in wrappedResponse.ads
-                            wrappedAd.errorURLTemplates = ad.errorURLTemplates.concat wrappedAd.errorURLTemplates
-                            wrappedAd.impressionURLTemplates = ad.impressionURLTemplates.concat wrappedAd.impressionURLTemplates
-                            response.ads.splice index, 0, wrappedAd
-
-                    complete()
+                        complete()
+                        return
+
+                    if ad.nextWrapperURL.indexOf('://') == -1
+                        # Resolve relative URLs (mainly for unit testing)
+                        baseURL = url.slice(0, url.lastIndexOf('/'))
+                        ad.nextWrapperURL = "#{baseURL}/#{ad.nextWrapperURL}"
+
+                    @_parse ad.nextWrapperURL, parentURLs, (err, wrappedResponse) =>
+                        if err?
+                            # Timeout of VAST URI provided in Wrapper element, or of VAST URI provided in a subsequent Wrapper element.
+                            # (URI was either unavailable or reached a timeout as defined by the video player.)
+                            VASTUtil.track(ad.errorURLTemplates, ERRORCODE: 301)
+                            response.ads.splice(response.ads.indexOf(ad), 1)
+                        else if not wrappedResponse?
+                            # No Ads VAST response after one or more Wrappers
+                            VASTUtil.track(ad.errorURLTemplates, ERRORCODE: 303)
+                            response.ads.splice(response.ads.indexOf(ad), 1)
+                        else
+                            response.errorURLTemplates = response.errorURLTemplates.concat wrappedResponse.errorURLTemplates
+                            index = response.ads.indexOf(ad)
+                            response.ads.splice(index, 1)
+                            for wrappedAd in wrappedResponse.ads
+                                wrappedAd.errorURLTemplates = ad.errorURLTemplates.concat wrappedAd.errorURLTemplates
+                                wrappedAd.impressionURLTemplates = ad.impressionURLTemplates.concat wrappedAd.impressionURLTemplates
+                                response.ads.splice index, 0, wrappedAd
+
+                        delete ad.nextWrapperURL
+                        complete()

             complete()


Use screen width for reseting the inital offset starting position of the target page to be transitioned.

Also make transtion time out the same. This stops an overlapping bug.

--- js/flight.coffee
+++ js/flight.coffee
@@ -92,14 +92,16 @@
   slide: (targetPage, currentPage) ->

     targetPage.style.display = "-webkit-box"
+
+    screenWidth = window.innerWidth +  'px';

     if @back
-      @translate(targetPage, "X", "-100%", "0ms")
+      @translate(targetPage, "X", "-" + screenWidth, "0ms")
       window.setTimeout =>
         @translate(currentPage, "X", "100%")
       , 10
     else
-      @translate(targetPage,"X","100%", "0ms")
+      @translate(targetPage,"X", screenWidth, "0ms")
       window.setTimeout =>
         @translate(currentPage, "X", "-100%")
       , 10
@@ -107,7 +109,7 @@
     window.setTimeout =>
       @translate(targetPage, "X", "0%" )
       @back = false
-    , 5
+    , 10

   # Private: Perform a slide from bottom transition.
   #


Convert to function

Convert to function

--- resources/assets/coffee/react/_components/beatmap-icon.coffee
+++ resources/assets/coffee/react/_components/beatmap-icon.coffee
@@ -19,27 +19,21 @@
 {div} = React.DOM
 el = React.createElement

-@BeatmapIcon = React.createClass
-  mixins: [React.addons.PureRenderMixin]
+@BeatmapIcon = (props) ->
+  beatmap = props.beatmap

-  getDefaultProps: ->
-    showTitle: true
+  difficultyRating =
+    if props.overrideVersion?
+      props.overrideVersion
+    else
+      BeatmapHelper.getDiffRating beatmap.difficulty_rating

-  render: ->
-    beatmap = @props.beatmap
+  showTitle = (props.showTitle ? true) && !props.overrideVersion?

-    difficultyRating =
-      if @props.overrideVersion?
-        @props.overrideVersion
-      else
-        BeatmapHelper.getDiffRating beatmap.difficulty_rating
+  mode = if beatmap.convert then 'osu' else beatmap.mode

-    showTitle = @props.showTitle && !@props.overrideVersion?
-
-    mode = if beatmap.convert then 'osu' else beatmap.mode
-
-    div
-      className: "beatmap-icon beatmap-icon--#{difficultyRating} beatmap-icon--#{@props.modifier}"
-      title: beatmap.version if showTitle
-      div className: 'beatmap-icon__shadow'
-      el Icon, name: "extra-mode-#{mode}"
+  div
+    className: "beatmap-icon beatmap-icon--#{difficultyRating} beatmap-icon--#{props.modifier}"
+    title: beatmap.version if showTitle
+    div className: 'beatmap-icon__shadow'
+    el Icon, name: "extra-mode-#{mode}"


Export option, options

Export option, options

--- src/sake.coffee
+++ src/sake.coffee
@@ -32,6 +32,8 @@
 export {
   install
   invoke
+  option
+  options
   parallel
   running
   serial


Better handling of missing elements

Better handling of missing elements

--- src/snok.coffee
+++ src/snok.coffee
@@ -40,13 +40,15 @@
                     self._examine item

     _examine: (item) =>
+        item.guid = item.link if not item.guid
         return if item.guid in @checkedItems

         @checkedItems.shift() if @checkedItems.length > @MAX_SAVED_ITEMS
         @checkedItems.push item.guid

         containsTrigger = false
-        text = item.title.toLowerCase() + item.description?.toLowerCase() # Not every item has description
+        text = item.title?.toLowerCase() or ''
+        text += item.description?.toLowerCase() or ''

         for trigger in @triggers
             if text.includes trigger


sets up mongoose with mongo url

sets up mongoose with mongo url

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -6,7 +6,8 @@

 { APP_URL, API_URL, NODE_ENV, SESSION_SECRET,
 SESSION_COOKIE_MAX_AGE, SESSION_COOKIE_KEY,
-COOKIE_DOMAIN, GOOGLE_ANALYTICS_ID, SENDGRID_API_KEY } = config = require "../config"
+COOKIE_DOMAIN, GOOGLE_ANALYTICS_ID, SENDGRID_API_KEY,
+MONGO_URL } = config = require "../config"

 path = require 'path'
 stylus = require "stylus"
@@ -24,7 +25,9 @@
 sharify = require 'sharify'
 sendgrid  = require('sendgrid')(SENDGRID_API_KEY) if SENDGRID_API_KEY

-router = express.Router()
+mongoose = require 'mongoose'
+mongoose.connect MONGO_URL
+User = require '../db/models/user.coffee'

 module.exports = (app) ->


Discoverede a bug that i foolishly failed to mention in the last commit. The arrays are now copied when submitted to the next recursive function. All tests now pass.

--- lib/suite.coffee
+++ lib/suite.coffee
@@ -74,7 +74,7 @@
     if item instanceof Suite
       # The item is a Suite. Tell it to build it's own session, so we can
       # combine it with ours.
-      item_session = item._build_session befores, before_eachs, after_eachs
+      item_session = item._build_session befores[..], before_eachs[..], after_eachs[..]

       if item_session.length > 0
         # If the item_session has any items in it, then it will have appended


sync: don't call a queue callback twice

sync: don't call a queue callback twice

Some time ago async was made a little stricter by forbidding queue and
forEach worker callbacks from being called
twice ([commit](https://github.com/caolan/async/commit/bb43829d29e91c8f314704876293ae3f7b22c82d)),
which absolutely makes sense.

However in some (rare) cases it is possible for the sync progress to do
such a thing: after a subscriptions sync, if a remote node has no local
listeners left, it needs to be purged. But the current code will cause
the callback to be called after this purge as well as after the normal
sync.

This commit fixes this by making sure that the callback can only be
called once.

--- src/sync.coffee
+++ src/sync.coffee
@@ -114,10 +114,10 @@
                 if err
                     logger.error "Cannot get node listeners: #{err.stack or err}"

-                unless listeners? and listeners.length > 0
+                if listeners? and listeners.length > 0
+                    cb2()
+                else
                     t.purgeRemoteNode @node, cb2
-
-            cb2()

     # TODO: none left? remove whole node.
     writeResults: (t, results, cb) ->


Adding a count down for the extruder heat up.

--- src/tegh.coffee
+++ src/tegh.coffee
@@ -109,6 +109,7 @@
     new ServiceSelector().on "select", @_onServiceSelect
     @_sensors = {}
     @_targetTemp = {}
+    @_targetTempEta = {}

   _onServiceSelect: (service) =>
     clear()
@@ -119,6 +120,7 @@
       .on("job_upload_progress_changed", @_renderProgressBar)
       .on("sensor_changed", @_onSensorChanged)
       .on("target_temp_changed", @_onTargetTempChanged)
+      .on("target_temp_progress_changed", @_onTargetTempProgressChanged)
       .on("job_progress_changed", @_onJobChanged)
       .on("ack", @_onAck)
       .on("construct_error", @_onError)
@@ -133,6 +135,10 @@
     @_targetTemp[@_sensorMap[k]] = v for k, v of data
     @cli.render()

+  _onTargetTempProgressChanged: (data) =>
+    @_targetTempEta[@_sensorMap[k]] = v for k, v of data
+    @cli.render()
+
   _onSensorChanged: (data) =>
     @_sensors[data.name] = data.value
     @cli.render()
@@ -182,7 +188,10 @@
   _lHeader: ->
     fields = []
     for k, v of @_sensors
-      fields.push "#{k.capitalize()}: #{v} / #{@_targetTemp[k]||0}\u00B0C"
+      s = "#{k.capitalize()}: #{v} / #{@_targetTemp[k]||0}\u00B0C"
+      if @_targetTempEta[k]? and @_targetTempEta[k].eta > 0
+        s+= " (#{@_targetTempEta[k].eta.round(1)} seconds)"
+      fields.push s
     fields.join("  ")

   _rHeader: ->


backend pod: remove image pull dependency

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -444,6 +444,7 @@

       if [ "$param" == "start" ]; then
         # Dependency on a hypervisor will be dropped off in the future
+        command -v docker           >/dev/null 2>&1 || { echo >&2 "I require docker but it's not installed.  Aborting."; exit 1; }
         command -v VirtualBox       >/dev/null 2>&1 || { echo >&2 "I require VirtualBox but it's not installed.  Aborting."; exit 1; }
         command -v minikube         >/dev/null 2>&1 || { echo >&2 "I require a Kubernetes cluster. To install minikube: \
                 (curl -Lo minikube curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.20.0/minikube-$(uname | awk '{print tolower($0)}')-amd64 && chmod +x minikube && mv minikube /usr/local/bin/)"; exit 1;}
@@ -452,8 +453,20 @@
         command -v helm             >/dev/null 2>&1 || { echo >&2 "I require helm but it's not installed. To install helm: \
                 (curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh && chmod 700 get_helm.sh && ./get_helm.sh)"; exit 1; }

-        minikube start
-        helm init
+        # minikube must be deleted and recreated if a disk resize is needed
+        minikube start --disk-size 50g
+
+        export MINIKUBE_IP=$(minikube ip)
+        eval $(minikube docker-env)
+
+        docker image inspect baseinstall:v1 >/dev/null 2>&1 && echo yes || echo no >/tmp/response.log 2>&1
+
+        export IMAGE_RESPONSE=$(tail -1 /tmp/response.log)
+
+        if [[ "$IMAGE_RESPONSE" == "no" ]]; then
+          echo 'base installation docker image for koding was not found, building the image:\n'
+          docker build -t baseinstall:v1 -f Dockerfile-k8s-base .
+        fi

         export TILLER_POD_NAME=$(kubectl get pods -n kube-system -l app=helm  -o jsonpath="{.items[0].metadata.name}")
         k8s_health_check $TILLER_POD_NAME kube-system 5 120


Close mobile navbar when a link is clicked

--- js/melvin.coffee
+++ js/melvin.coffee
@@ -7,6 +7,14 @@

   # Fade in navbar
   timeoutID = window.setTimeout delayedFadeIn, "1500"
+
+  # Close expanded navbar when an item is clicked
+  $("#js-navbar .name a, #js-navbar .top-bar-section a").click ->
+    # Snippet taken from foundation.topbar.js:215
+    topbar = $(".top-bar, [data-topbar]")
+    topbar.css "height", ""
+    topbar.removeClass "expanded"
+    topbar.find("li").removeClass "hover"

   # Add jQuery Address handler for navbar links
   $("#js-navbar a").address ->


update tests: new event syntax, empty tag removal fails


whitelist mode and filter when parsing

whitelist mode and filter when parsing

--- resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
+++ resources/assets/coffee/_classes/beatmap-discussion-helper.coffee
@@ -20,6 +20,9 @@
   @DEFAULT_BEATMAP_ID: '-'
   @DEFAULT_MODE: 'timeline'
   @DEFAULT_FILTER: 'total'
+
+  @MODES = ['timeline', 'general', 'generalAll']
+  @FILTERS = ['deleted', 'hype', 'mapperNotes', 'mine', 'pending', 'praises', 'resolved', 'total']

   @discussionMode: (discussion) ->
     if discussion.beatmap_id?
@@ -155,8 +158,8 @@
       beatmapsetId: if isFinite(beatmapsetId) then beatmapsetId
       beatmapId: if isFinite(beatmapId) then beatmapId
       # empty path segments are ''
-      mode: mode || @DEFAULT_MODE
-      filter: filter || @DEFAULT_FILTER
+      mode: if _.includes(@MODES, mode) then mode else @DEFAULT_MODE
+      filter: if _.includes(@FILTERS, filter) then filter else @DEFAULT_FILTER

     if url.hash[1] == '/'
       discussionId = parseInt(url.hash[2..], 10)


helpers for extracting data from package.json

--- src/helpers/helpers-special.coffee
+++ src/helpers/helpers-special.coffee
@@ -1,6 +1,6 @@
 module.exports.register = (Handlebars, options) ->

-  # Node deps
+  # Nodejs libs.
   fs = require 'fs'

   # npm deps
@@ -18,8 +18,41 @@
   Handlebars.registerHelper 'jsfiddle', (id, tabs) ->
     tabs   = "result,js,html,css"  if Utils.isUndefined(tabs)
     result = '<iframe width="100%" height="300" src="http://jsfiddle.net/' + id + '/embedded/' + tabs + '/presentation/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>'
-    new Handlebars.SafeString(result)
+    Utils.safeString(result)

+
+  ###
+  Package.json: get the current version from package.json
+  Usage: {{ pkg-name [pkg] }}
+  ###
+  Handlebars.registerHelper "pkg-name", (pkg) ->
+    if Utils.isUndefined(pkg)
+      pkg = Utils.readJSON("package.json")
+    else
+      pkg = Utils.readJSON(pkg)
+    name = pkg.name
+
+  ###
+  Version: get the current version from package.json
+  Usage: {{ pkg-version [pkg] }}
+  ###
+  Handlebars.registerHelper "pkg-version", (pkg) ->
+    if Utils.isUndefined(pkg)
+      pkg = Utils.readJSON("package.json")
+    else
+      pkg = Utils.readJSON(pkg)
+    version = pkg.version
+
+  ###
+  Version: get the current version from package.json
+  Usage: {{ pkg-description [pkg] }}
+  ###
+  Handlebars.registerHelper "pkg-description", (pkg) ->
+    if Utils.isUndefined(pkg)
+      pkg = Utils.readJSON("package.json")
+    else
+      pkg = Utils.readJSON(pkg)
+    description = pkg.description

   ###
   Gist: Downloads and embeds public GitHub Gists by
@@ -30,7 +63,7 @@
     id = Handlebars.Utils.escapeExpression(id)
     file = ""  if Utils.isUndefined(file)
     result = '<script src="https://gist.github.com/' + id + '.js"></script>'
-    new Handlebars.SafeString(result)
+    Utils.safeString(result)

   ###
@@ -44,7 +77,19 @@
     file = Utils.readSync(file)
     language = ""  if Utils.isUndefined(language)
     result = '``` ' + language + '\n' + file + '\n```'
-    new Handlebars.SafeString(result)
+    Utils.safeString(result)
+
+
+  ###
+  Format Phone Number
+  from: http://blog.teamtreehouse.com/handlebars-js-part-2-partials-and-helpers
+  Helper function to output a formatted phone number
+  Usage: {{formatPhoneNumber phoneNumber}}
+  ###
+  Handlebars.registerHelper "formatPhoneNumber", (phoneNumber) ->
+    phoneNumber = phoneNumber.toString()
+    "(" + phoneNumber.substr(0, 3) + ") " + phoneNumber.substr(3, 3) + "-" + phoneNumber.substr(6, 4)
+

   @


Don't show autocompile for users signed up after release date

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -250,9 +250,16 @@
 						return cb(null, true)
 			showAutoCompileOnboarding: (cb) ->
 				cb = underscore.once(cb)
-				if (!user_id?)
+				if !user_id?
 					return cb()
+				timestamp = user_id.toString().substring(0,8)
+				userSignupDate = new Date( parseInt( timestamp, 16 ) * 1000 )
+				if userSignupDate > new Date("2017-10-05")
+					# Don't show for users who registered after it was released
+					return cb(null, false)
+				timeout = setTimeout cb, 500
 				AnalyticsManager.getLastOccurance user_id, "shown-autocompile-onboarding", (error, event) ->
+					clearTimeout timeout
 					if error?
 						return cb(null, false)
 					else if event?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -19,6 +19,7 @@

       else
         f = Easing[data.easing](data.start, data.end)
+        I.age - data.startTime
         I[property] = f()

   tween: (duration, properties) ->


Added rudimentary tweening, wut!

Added rudimentary tweening, wut!

--- src/tween.coffee
+++ src/tween.coffee
@@ -36,7 +36,7 @@

   @name tween
   @methodOf Tween#
-  @param {Number} [duration=30] How long the effect lasts
+  @param {Number} duration How long the effect lasts
   @param {Number} [frequency=3] The number of frames in between opacity changes
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###


Eliminate variable by directly reading title

--- src/components/task-step/index.cjsx
+++ src/components/task-step/index.cjsx
@@ -39,13 +39,12 @@
   render: ->
     {id, taskId} = @props
     {type} = TaskStepStore.get(id)
-    {ecosystem_title} = TaskStore.get(taskId)

     Type = getStepType(type)
     <div>
       <Type {...@props}/>
       <SpyModeContent className='task-ecosystem-info'>
-        TaskId: {taskId}, StepId: {id}, Ecosystem: {ecosystem_title}
+        TaskId: {taskId}, StepId: {id}, Ecosystem: {TaskStore.get(taskId).ecosystem_title}
       </SpyModeContent>
     </div>


Use scale if set

Use scale if set

--- bokehjs/src/coffee/models/canvas/cartesian_frame.coffee
+++ bokehjs/src/coffee/models/canvas/cartesian_frame.coffee
@@ -45,8 +45,10 @@
   _get_scales: (scale, ranges, frame_range) ->
     scales = {}
     for name, range of ranges
-      if range.type == 'Range1d' or range.type == "DataRange1d"
-        s = if scale? then scale else new LinearScale()
+      if scale?
+        s = scale
+      else if range.type == 'Range1d' or range.type == "DataRange1d"
+        s = new LinearScale()
       else if range.type == "FactorRange"
         s = new CategoricalScale()
       else


Rewrite CartesianFrame.{x,y}_mappers not to use get()

--- bokehjs/src/coffee/models/canvas/cartesian_frame.coffee
+++ bokehjs/src/coffee/models/canvas/cartesian_frame.coffee
@@ -18,8 +18,8 @@
     x_ranges: () -> @_get_ranges('x')
     y_ranges: () -> @_get_ranges('y')

-    x_mappers: () -> @_get_mappers('x', @x_ranges, @h_range)
-    y_mappers: () -> @_get_mappers('y', @y_ranges, @v_range)
+    x_mappers: () -> @_get_mappers(@x_mapper_type, @x_ranges, @h_range)
+    y_mappers: () -> @_get_mappers(@y_mapper_type, @y_ranges, @v_range)

     mapper: () ->
       new GridMapper.Model({
@@ -79,20 +79,20 @@
         ranges[name] = range
     return ranges

-  _get_mappers: (dim, ranges, frame_range) ->
+  _get_mappers: (mapper_type, ranges, frame_range) ->
     mappers = {}
     for name, range of ranges
       if range.type == "Range1d" or range.type == "DataRange1d"
-        if @get("#{dim}_mapper_type") == "log"
-          mapper_type = LogMapper.Model
+        if mapper_type == "log"
+          mapper_model = LogMapper.Model
         else
-          mapper_type = LinearMapper.Model
+          mapper_model = LinearMapper.Model
       else if range.type == "FactorRange"
-        mapper_type = CategoricalMapper.Model
+        mapper_model = CategoricalMapper.Model
       else
         logger.warn("unknown range type for range '#{name}': #{range}")
         return null
-      mappers[name] = new mapper_type({
+      mappers[name] = new mapper_model({
         source_range: range
         target_range: frame_range
       })


Added print_r until for debugging arrays

--- src/utils.coffee
+++ src/utils.coffee
@@ -44,4 +44,13 @@
     if elem.children
       print elem.children, attr, depth+1, out

-  return out.join "\n" +  return out.join "\n"
+
+print_r = exports.print_r = (arr, attr = "name") ->
+  out = "[ "
+
+  attributes = arr.map (elem) ->
+    return elem[attr]
+  out += attributes.join ", "
+  out += " ]"
+  return out


Remove the workaround for net.connect delay.

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -38,10 +38,6 @@
     if (not fs.existsSync socketPath) or options.test
       createAtomApplication()
       return
-
-    # The net.connect is slow in atom-shell for now, use this workaround until
-    # atom/atom-shell#159 is fixed.
-    process.activateUvLoop()

     client = net.connect {path: socketPath}, ->
       client.write JSON.stringify(options), ->


:fire: unused method

:fire: unused method

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -449,9 +449,6 @@
     if states.length > 0 or allowEmpty
       @storageFolder.store('application.json', states)

-  hasEditorWindows: ->
-    @windows.some (window) -> not window.isSpec
-
   loadState: ->
     if (states = @storageFolder.load('application.json'))?.length > 0
       for state in states


point to production polyfills

point to production polyfills

--- src/utils/load-polyfills.coffee
+++ src/utils/load-polyfills.coffee
@@ -46,5 +46,5 @@
   return new Promise (resolve, reject)->
     return resolve() if allGood
     file = if (config.debug) then "polyfill.js" else "polyfill.js"
-    url = "//polyfills.ngrok.com/v1/#{file}?features=#{_.keys(polyfills).join(",")}"
+    url = "//hull-polyfills.heroku.com/v1/#{file}?features=#{_.keys(polyfills).join(",")}"
     return scriptLoader({ src:url, document:config.document}).then(shimHTMLImports).then(resolve, reject)


Fall back to empty object if project has no dependencies

Some projects have no dependencies, but only devDependencies. In these projects, brunch fails because of
```
/var/www/jimdo/prod/node_modules/brunch/lib/watch.js:403
    plugins = loadDeps(Object.keys(json.dependencies));
                              ^
TypeError: Object.keys called on non-object
  at Function.keys (native)
  at loadPackages (/var/www/jimdo/prod/node_modules/brunch/lib/watch.js:403:31)
  at /var/www/jimdo/prod/node_modules/brunch/lib/watch.js:422:19
  at /var/www/jimdo/prod/node_modules/brunch/lib/helpers.js:498:16
  at /var/www/jimdo/prod/node_modules/brunch/lib/helpers.js:457:14
  at /var/www/jimdo/prod/node_modules/brunch/node_modules/read-components/index.js:240:16
  at /var/www/jimdo/prod/node_modules/brunch/node_modules/read-components/index.js:54:14
  at Object.cb [as oncomplete] (fs.js:168:19)
```

--- src/watch.coffee
+++ src/watch.coffee
@@ -331,7 +331,7 @@
             requireModule depPath, dependency
           catch error
             throw new Error "You probably need to execute `npm install` to install brunch plugins. #{error}"
-  plugins = loadDeps(Object.keys json.dependencies)
+  plugins = loadDeps(Object.keys json.dependencies or {})
   devPlugins = loadDeps(Object.keys(json.devDependencies or {}), true)
   plugins.concat(devPlugins.filter((_) -> _?))


Use is_root for labels

Use is_root for labels

--- src/world.coffee
+++ src/world.coffee
@@ -180,7 +180,7 @@
     klasses = [klasses] unless _.isArray(klasses)
     for klass in klasses
       starter.push klass
-    if @up.labels? then @up.labels(starter) else starter
+    if @is_root() then starter else @up.labels(starter)

   toString: -> "World_#{@label()}"


init params no dataset

init params no dataset

--- src/client/script-tag-config.coffee
+++ src/client/script-tag-config.coffee
@@ -65,7 +65,7 @@
   transform = param.transform || (o)-> o
   value = _.reduce(keys, (ret, k)->
     if ret == null
-      value = transform((el.getAttribute(dasherize(k)) || el.getAttribute(k) || el.dataset[k]), key)
+      value = transform((el.getAttribute(dasherize(k)) || el.getAttribute(k)), key)
       valid = value? && (!param.validation  || param.validation(value, k))
       ret = value if valid
     ret


Refactored function and variable names in Observer to help clarify what is going on.

--- src/coffee/Deft/mvc/Observer.coffee
+++ src/coffee/Deft/mvc/Observer.coffee
@@ -6,45 +6,19 @@
 # @private
 Ext.define( 'Deft.mvc.Observer',

-	###
-
-	target = "propName"
-	events = // is object
-		event1: "handler1" // is string or function
-		event2: "handler2 // is string or function"
-
-	target = "propName2.prop"
-	events = // is object
-		event3: // is object
-			fn: "handler3"
-			scope: @
-			options:
-				option1: "value"
-		event4: [ "handler4", "handler5" ] //is array
-
-	if (map.indexOf('.') > -1) {
-    map = map.split('.', 2);
-    var v = n[map[0]][map[1]];
-	} else {
-	    var v = n[map];
-	}
-	###
 	constructor: ( config ) ->
 		@listeners = []

-		#TODO: Remove instance variables for target, host, and events, and pull these from the config object for use below.
 		host = config?.host
 		target = config?.target
 		events = config?.events

-		#TODO: handle nested property value for target
-
 		if host and target and ( @isPropertyChain( target ) or host?[ target ]?.isObservable )
-			for eventName, fn of events
-				fnReference = @locateFunctionReference( host, target, fn )
-				if fnReference
-					fnReference.target.on( eventName, fnReference.fn, host )
-					@listeners.push( { targetName: target, target: fnReference.target, event: eventName, fn: fnReference.fn, scope: host } )
+			for eventName, handler of events
+				references = @locateReferences( host, target, handler )
+				if references
+					references.target.on( eventName, references.handler, host )
+					@listeners.push( { targetName: target, target: references.target, event: eventName, handler: references.handler, scope: host } )
 					Deft.Logger.log( "Created observer on '#{ target }' for event '#{ eventName }'." )
 					console.log( "Created observer on '#{ target }' for event '#{ eventName }'." )
 				else
@@ -57,23 +31,26 @@

 		return @

+
 	isPropertyChain: ( target ) ->
 		return Ext.isString( target ) and target.indexOf( '.' ) > -1

-	locateFunctionReference: ( host, target, handler ) ->
+
+	locateReferences: ( host, target, handler ) ->
 		handlerHost = host

 		if @isPropertyChain( target )
-			parseResults = @parsePropertyChain( host, target )
-			host = parseResults.host
-			target = parseResults.target
+			propertyChain = @parsePropertyChain( host, target )
+			host = propertyChain.host
+			target = propertyChain.target

 		if Ext.isFunction( handler )
-			return { target: host[ target ], fn: handler  }
+			return { target: host[ target ], handler: handler  }
 		else if Ext.isFunction( handlerHost[ handler ] )
-			return { target: host[ target ], fn: handlerHost[ handler ]  }
+			return { target: host[ target ], handler: handlerHost[ handler ]  }
 		else
 			return null
+

 	parsePropertyChain: ( host, target ) ->
 		if Ext.isString( target )
@@ -90,10 +67,11 @@
 		else
 			return null

+
 	destroy: ->
 		for listenerData in @listeners
 			Deft.Logger.log( "Removing observer on '#{ listenerData.targetName }' for event '#{ listenerData.event }'." )
-			listenerData.target.un( listenerData.event, listenerData.fn, listenerData.scope )
+			listenerData.target.un( listenerData.event, listenerData.handler, listenerData.scope )
 		@listeners = []
 		return
 )


Rename viewClass to getViewClasses

Rename viewClass to getViewClasses

--- src/select-list-view.coffee
+++ src/select-list-view.coffee
@@ -24,7 +24,7 @@
 module.exports =
 class SelectListView extends View
   @content: ->
-    @div class: @viewClass(), =>
+    @div class: @getViewClasses(), =>
       @subview 'editorView', new EditorView(mini: true)
       @div class: 'error-message', outlet: 'error'
       @div class: 'loading', outlet: 'loadingArea', =>
@@ -32,7 +32,7 @@
         @span class: 'badge', outlet: 'loadingBadge'
       @ol class: 'list-group', outlet: 'list'

-  # Public: Get the classes to set on the root view element.
+  # Public: Get the CSS classes to set on the root view element.
   #
   # Subclasses may override this method but should call `super` to get the
   # default classes.
@@ -40,12 +40,12 @@
   # ### Example
   # ```coffee
   # class MySelectListView extends SelectListView
-  #   @viewClass: ->
+  #   @getViewClasses: ->
   #     "#{super} my-view-class"
   # ```
   #
   # Return a {String} of space-separated CSS classes.
-  @viewClass: -> 'select-list'
+  @getViewClasses: -> 'select-list'

   maxItems: Infinity
   scheduleTimeout: null


Support static methods

Support static methods

--- src/server/bootstrap.coffee
+++ src/server/bootstrap.coffee
@@ -137,24 +137,41 @@

         # Iterate over all changed classes and detect if a method was deleted.
         for newClassName in changedClasses
-          oldMethods = _.keys oldApp[newClassName].prototype
-          newMethods = _.keys global.App[newClassName].prototype
+          oldClass = oldApp[newClassName]
+          newClass = global.App[newClassName]
+
+          oldMethods = _.keys oldClass.prototype
+          newMethods = _.keys newClass.prototype

           # If this is the case, delete the method from the existing instances
           for currentMethod in oldMethods when currentMethod not in newMethods
-            console.log "Remove method #{methodName}"
-            delete oldApp[newClassName].prototype[currentMethod]
+            delete oldClass::[currentMethod]
+
+          oldMethods = _.keys oldClass
+          newMethods = _.keys newClass
+
+          # Do the same with static methods
+          for currentMethod in oldMethods when currentMethod not in newMethods
+            delete oldClass[currentMethod]

           # Replace every old prototype method with the new version
-          for methodName, method of global.App[newClassName].prototype
-            console.log "Replace method #{methodName}"
-            oldApp[newClassName].prototype[methodName] = method
-
-          # TODO: Not working with fat arrow functions yet
+          for methodName in _.keys newClass.prototype
+            oldClass::[methodName] = newClass[methodName]
+
+          # Do the same with static methods
+          for methodName in _.keys global.App[newClassName]
+            oldClass[methodName] = newClass[methodName]
+
+          # FIXME: fat arrow functions don't seem to work.
+          # I have no solution how to replace the bound methods, as they
+          # are bound per instance when instantiating and I have no way to
+          # access every instance
+          #
+          # Reference: http://stackoverflow.com/a/13687261/9535

         global.App = oldApp

-        callback null, file if callback
+        return callback null, file if callback

   initControllers: (cb) ->


Fix typo.

Fix typo.

--- src/server.coffee
+++ src/server.coffee
@@ -6,7 +6,7 @@
 join    = path.join
 dirname = path.dirname

-existsSync = requrie './utils'
+existsSync = require './utils'

 exports.createServer = (opts) ->
   app = express.createServer()


add filters;

add filters;

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -1,4 +1,17 @@

+
+class SiriusUtils
+  @is_function: (a) ->
+    Object.prototype.toString.call(a) is '[object Function]'
+
+  @is_string: (a) ->
+    Object.prototype.toString.call(a) is '[object String]'
+
+  @is_array: (a) ->
+    Object.prototype.toString.call(a) is '[object Array]'
+
+  @camelize: (str) ->
+    str.charAt(0).toUpperCase() + str.slice(1)
 ###
   RoutePart is a parser for string route representation
 ###
@@ -63,6 +76,87 @@
     @args = args
     true

+
+
+class ControlFlow
+  # obj is a object with controller\action\before\after\data properties
+  # required:
+  #   controller must be a Object
+  #   action is a string
+  # before\after might be given as string or find in controller as before_action and after_action methods
+  # before\after might be a function
+  # data is a string for element (id\class\data-*\...) event routes, otherwise it's a null
+  # if it's a function, then before\after\data is a null or empty function ? TODO
+  constructor: (params) ->
+    @controller = params['controller'] || throw "Params must contain a Controller"
+
+
+    act = params['action']
+
+    @action = if SiriusUtils.is_string(act)
+                @controller[act]
+              else if SiriusUtils.is_function(act)
+                act
+              else
+                throw err("Action")
+
+    if !SiriusUtils.is_function(@action) && !SiriusUtils.is_string(@action)
+      throw "Action must be string or function"
+
+    ###
+      extract from `params` before or after function
+      when it's a function then return function
+      when it's a string it's find by `string` in controller
+        when given not a function raise error
+        otherwise return this method from controller
+      if it's not a function or string throw error
+      otherwise find in controller by *_given_action if found a function return it
+        else raise error
+      by end it's return empty function
+    ###
+    extract = (property) =>
+      p = params[property]
+      k = @controller["#{property}_#{act}"]
+      err = (a) ->
+        "#{a} action must be string or function"
+      if SiriusUtils.is_string(p)
+        t = @controller[p]
+        throw err(SiriusUtils.camelize(property)) if !SiriusUtils.is_function(t)
+        t
+      else if SiriusUtils.is_function(p)
+        p
+      else if p
+        throw err(SiriusUtils.camelize(property))
+      else if k
+        throw err(SiriusUtils.camelize(property)) if !SiriusUtils.is_function(k)
+        k
+      else
+        ->
+
+    @before = extract('before')
+    @after  = extract('after')
+
+    @data = params['data'] || null
+
+  # e is a event need extract event target
+
+  handle_event: (e, args...) ->
+    @before.call(null)
+
+    #when e defined it's a Event, otherwise it's call from url_routes
+    if e
+      if @data
+        @action.apply(null, [e])#FIXME add data
+      else
+        @action.apply(null, [e])
+    else
+      @action.apply(null, args)
+
+    @after.call(null)
+
+
+
+#TODO make it as function
 ###
   Main Object.

@@ -96,41 +190,26 @@
     # @event application:hashchange [current_url, prev_url]
     # @event application:404 when url not found
     # @event application:run after running
-    create: (routes, fn) ->
+    create: (routes, fn = ->) ->
       current = prev = window.location.hash

       is_f = (f) ->
         Object.prototype.toString.call(f) == '[object Function]'

-
-      #TODO: add filters: before\after\wrap
-      #[Controller, "method"] => Controller[method]
-      a2f = (a) ->
-        throw "#{a} must be array or function" if Object.prototype.toString.call(a) isnt '[object Array]'
-        throw "#{a} must contain two elements: Controller and method" if a.length != 2
-        [controller, action] = a
-        throw "Controller must be a Object" if typeof controller isnt 'object'
-        throw "Action must be a String" if Object.prototype.toString.call(action) isnt '[object String]'
-        f = controller[action]
-        throw "Action must be a Function" if Object.prototype.toString.call(f) isnt '[object Function]'
-        f
-
       for url, action of routes when url.indexOf("#") != 0 && url.toString() != "404"
         do (url, action) =>
-          action = if is_f(action) then action else a2f(action)
+          action = if is_f(action) then action else new ControlFlow(action) #FIXME
           z = url.match(/^([a-zA-Z:]+)(\s+)?(.*)?/)
           event_name = z[1]
           selector   = z[3] || document #when it a custom event: 'custom:event' for example
-          SiriusApplication.adapter.bind(selector, event_name, action)
+          SiriusApplication.adapter.bind(selector, event_name, action.handle_event || action)

       # for cache change obj[k, v] to array [[k,v]]
       array_of_routes = for url, action of routes when url.toString() != "404"
         do (url, action) ->
           url    = new RoutePart(url)
-          action = if is_f(action) then action else a2f(action)
+          action = if is_f(action) then action else new ControlFlow(action) #FIXME
           [url, action]
-
-      empty = () ->

       window.onhashchange = (e) =>
         prev = current
@@ -147,16 +226,23 @@
             r = f.match(current)
             if r && !result
               result = true
-              part[1].apply(null, f.args)
+              f = part[1]
+              if f.handle_event
+                f.handle_event(null, f.args)
+              else
+                f.apply(null, f.args)
               return
-

         #when no results, then call 404 or empty function
         if !result
           SiriusApplication.adapter.fire(document, "application:404", current, prev)
-          (if routes['404'] then a2f(routes['404']) else empty)(current)
-
-      (fn || empty)()
+          #FIXME
+          r404 = routes['404']
+          if r404
+            z = new ControlFlow(r404)
+            z.handle_event(null, current)
+
+      fn()

   log: false
   #adapter for application @see adapter documentation


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -57,7 +57,7 @@
     @param {Number} y Position on the y axis to draw the sprite
     @param {Number} width How far to tile the sprite on the x-axis
     @param {Number} height How far to tile the sprite on the y-axis
-    @param {String} repeat
+    @param {String} repeat Repeat options. Can be
     ###
     fill: (canvas, x, y, width, height, repeat="repeat") ->
       pattern = canvas.createPattern(image, repeat)


add org support

add org support

--- src/trello.coffee
+++ src/trello.coffee
@@ -29,33 +29,71 @@
 ensureConfig = (out) ->
   out "Error: Trello app key is not specified" if not process.env.HUBOT_TRELLO_KEY
   out "Error: Trello token is not specified" if not process.env.HUBOT_TRELLO_TOKEN
-  out "Error: Trello board ID is not specified" if not process.env.HUBOT_TRELLO_BOARD
-  return false unless (process.env.HUBOT_TRELLO_KEY and process.env.HUBOT_TRELLO_TOKEN and process.env.HUBOT_TRELLO_BOARD)
+  out "Error: Trello organization ID is not specified" if not process.env.HUBOT_TRELLO_ORGANIZATION
+  return false unless (process.env.HUBOT_TRELLO_KEY and process.env.HUBOT_TRELLO_TOKEN and process.env.HUBOT_TRELLO_ORGANIZATION)
   true

 ##############################
 # API Methods
 ##############################

+showBoards = (msg) ->
+  msg.reply "Sure thing boss. I'll list the boards for you."
+  ensureConfig msg.send
+  trello.get "/1/organizations/#{process.env.HUBOT_TRELLO_ORGANIZATION}/boards", (err, data) ->
+    msg.reply "There was an error reading the list of boards" if err
+    msg.send "* #{board.id} - #{board.name}" for board in data unless err and data.length == 0
+
+showLists = (msg) ->
+  msg.reply "Looking up the lists for #{msg.room}, one sec."
+  ensureConfig msg.send
+  trello.get "/1/organizations/#{process.env.HUBOT_TRELLO_ORGANIZATION}/boards", (err, data) ->
+    msg.reply "There was an error reading the list of boards" if err
+    for board in data
+      msg.send "#{board.id} - #{board.name}"
+      if board.name.toLowerCase() == msg.room.toLowerCase()
+        trello.get "/1/boards/#{board.id}/lists", (err, data) ->
+          msg.reply "There was an error reading the lists" if err
+          msg.send "* #{list.name}" for list in data unless err and data.length == 0
+        break
+
 createCard = (msg, list_name, cardName) ->
   msg.reply "Sure thing boss. I'll create that card for you."
   ensureConfig msg.send
-  id = lists[list_name.toLowerCase()].id
-  trello.post "/1/cards", {name: cardName, idList: id}, (err, data) ->
-    msg.reply "There was an error creating the card" if err
-    msg.reply "OK, I created that card for you. You can see it here: #{data.url}" unless err
+  trello.get "/1/organizations/#{process.env.HUBOT_TRELLO_ORGANIZATION}/boards", (err, data) ->
+    msg.reply "There was an error reading the list of boards" if err
+    for board in data
+      msg.send "#{board.id} - #{board.name}"
+      if board.name.toLowerCase() == msg.room.toLowerCase()
+        trello.get "/1/boards/#{board.id}/lists", (err, data) ->
+          msg.reply "There was an error reading the lists" if err
+          for list in data
+            if list.name.toLowerCase() == list_name.toLowerCase()
+              trello.post "/1/cards", {name: cardName, idList: list.id}, (err, data) ->
+                msg.reply "There was an error creating the card" if err
+                msg.reply "OK, I created that card for you. You can see it here: #{data.url}" unless err
+              break
+        break

 showCards = (msg, list_name) ->
   msg.reply "Looking up the cards for #{list_name}, one sec."
   ensureConfig msg.send
-  id = lists[list_name.toLowerCase()].id
-  msg.send "I couldn't find a list named: #{list_name}." unless id
-  if id
-    trello.get "/1/lists/#{id}", {cards: "open"}, (err, data) ->
-      msg.reply "There was an error showing the list." if err
-      msg.reply "Here are all the cards in #{data.name}:" unless err and data.cards.length == 0
-      msg.send "* [#{card.shortLink}] #{card.name} - #{card.shortUrl}" for card in data.cards unless err and data.cards.length == 0
-      msg.reply "No cards are currently in the #{data.name} list." if data.cards.length == 0 and !err
+  trello.get "/1/organizations/#{process.env.HUBOT_TRELLO_ORGANIZATION}/boards", (err, data) ->
+    msg.reply "There was an error reading the list of boards" if err
+    for board in data
+      msg.send "#{board.id} - #{board.name}"
+      if board.name.toLowerCase() == msg.room.toLowerCase()
+        trello.get "/1/boards/#{board.id}/lists", (err, data) ->
+          msg.reply "There was an error reading the lists" if err
+          for list in data
+            if list.name.toLowerCase() == list_name.toLowerCase()
+              trello.get "/1/lists/#{list.id}", {cards: "open"}, (err, data) ->
+                msg.reply "There was an error showing the list." if err
+                msg.reply "Here are all the cards in #{data.name}:" unless err and data.cards.length == 0
+                msg.send "* [#{card.shortLink}] #{card.name} - #{card.shortUrl}" for card in data.cards unless err and data.cards.length == 0
+                msg.reply "No cards are currently in the #{data.name} list." if data.cards.length == 0 and !err
+              break
+        break

 moveCard = (msg, card_id, list_name) ->
   ensureConfig msg.send
@@ -109,4 +147,3 @@
     msg.send " *  shows * [<card.shortLink>] <card.name> - <card.shortUrl>"
     msg.send " *  trello move <card.shortlink> \"<ListName>\""
     msg.send " *  trello list lists"
-


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -14,6 +14,6 @@
   obj.bind 'destroy', ->
     destroySpy = true

-  obj.
+  obj.I.x

 module()


no duplicate entries in install script

no duplicate entries in install script

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -83,11 +83,12 @@
                 "\tcd `dirname $this`"
                 "done"
                 "dir=`pwd`"
+                "# install party"
             ]
             islastdir = no
             for pkgname,pkgs of @skip
                 src.push("echo 'install #{pkgname} …'")
-                for pkgpath in pkgs
+                for pkgpath in nub(pkgs)
                     parentpkgpath = path.join(pkgpath, "../..")
                     unless pkgpath isnt @dirname and parentpkgpath isnt @dirname
                         src.push("npm install #{pkgname}")


Expose Tooltips on the client

Expose Tooltips on the client

--- tooltips.coffee
+++ tooltips.coffee
@@ -45,6 +45,12 @@
 vertically = ($el, $reference) ->
 	[$reference.offset().top, $reference.outerHeight(), $el.outerHeight()]

+# Exports
+
+Tooltips =
+	set: setTooltip
+	get: getTooltip
+	hide: hideTooltip

 # Template helpers


use null instead of err in callbacks on success

--- services/document-updater/app/coffee/LockManager.coffee
+++ services/document-updater/app/coffee/LockManager.coffee
@@ -46,11 +46,11 @@
 						return callback(err) if err? # error freeing lock
 						callback null, false # tell caller they didn't get the lock
 				else
-					callback err, true, lockValue
+					callback null, true, lockValue
 			else
 				metrics.inc "doc-blocking"
 				profile.log("doc is locked").end()
-				callback err, false
+				callback null, false

 	getLock: (doc_id, callback = (error, lockValue) ->) ->
 		startTime = Date.now()
@@ -81,10 +81,10 @@
 			exists = parseInt exists
 			if exists == 1
 				metrics.inc "doc-blocking"
-				callback err, false
+				callback null, false
 			else
 				metrics.inc "doc-not-blocking"
-				callback err, true
+				callback null, true

 	releaseLock: (doc_id, lockValue, callback)->
 		key = keys.blockingKey(doc_id:doc_id)
@@ -99,4 +99,4 @@
 				return callback(new Error("tried to release timed out lock"))
 			else
 				profile.log("unlockScript:ok").end()
-				callback(err,result)
+				callback(null,result)


RequirementsParser: take custom variables into account

--- client/admin/lib/views/stacks/requirementsparser.coffee
+++ client/admin/lib/views/stacks/requirementsparser.coffee
@@ -45,6 +45,10 @@
       # And this one for userInputs which will be asked to user when they
       # want to build their stacks which created from the stack template
       /\$\{var\.(userInput)\_(.*?)\}/g
+
+      # This is for custom data which will be asked from the admin
+      # These variables needed to provide in custom variables section
+      /\$\{var\.(custom)\_(.*?)\}/g
     ]

     requirements = {}


Use existential operator

Use existential operator

--- src/jasmine-http-server-spy.coffee
+++ src/jasmine-http-server-spy.coffee
@@ -6,12 +6,12 @@

 doneResolveHandler = (done) ->
     return (result) ->
-        done() if done
+        done?()
         return result

 doneRejectHandler = (done) ->
     return (err) ->
-        done.fail(err) if done and done.fail
+        done?.fail?(err)
         throw err

 getRequestObject = (req) ->


static pages: linked .pdf and .ai anchors on about page

--- client/landing/site.landing/coffee/about/AppView.coffee
+++ client/landing/site.landing/coffee/about/AppView.coffee
@@ -63,7 +63,7 @@

             Koding Logo

-            .pdf .ai
+            .pdf .ai


Access data attributes with .attr(), not .data() to prevent caching issues.

--- src/data.coffee
+++ src/data.coffee
@@ -36,31 +36,31 @@
   add: (e) ->
     e.preventDefault()
     $this = jQuery(this)
-    CartJS.Core.addItem $this.data('cartAdd'), $this.data('cartQuantity')
+    CartJS.Core.addItem $this.attr('data-cart-add'), $this.attr('data-cart-quantity')

   # Handler for [data-cart-remove] click events.
   remove: (e) ->
     e.preventDefault()
     $this = jQuery(this)
-    CartJS.Core.removeItem $this.data('cartRemove')
+    CartJS.Core.removeItem $this.attr('data-cart-remove')

   # Handler for [data-cart-remove-id] click events.
   removeById: (e) ->
     e.preventDefault()
     $this = jQuery(this)
-    CartJS.Core.removeItemById $this.data('cartRemoveId')
+    CartJS.Core.removeItemById $this.attr('data-cart-remove-id')

   # Handler for [data-cart-update] click events.
   update: (e) ->
     e.preventDefault()
     $this = jQuery(this)
-    CartJS.Core.updateItem $this.data('cartUpdate'), $this.data('cartQuantity')
+    CartJS.Core.updateItem $this.attr('data-cart-update'), $this.attr('data-cart-quantity')

   # Handler for [data-cart-update-id] click events.
   updateById: (e) ->
     e.preventDefault()
     $this = jQuery(this)
-    CartJS.Core.updateItemById $this.data('cartUpdateId'), $this.data('cartQuantity')
+    CartJS.Core.updateItemById $this.attr('data-cart-update-id'), $this.attr('data-cart-quantity')

   # Handler for [data-cart-clear] click events.
   clear: (e) ->
@@ -70,7 +70,7 @@
   # Handler for [data-cart-toggle] change events.
   toggle: (e) ->
     $input = jQuery(this)
-    id = $input.data('cartToggle')
+    id = $input.attr('data-cart-toggle')
     if $input.is(':checked')
       CartJS.Core.addItem(id)
     else
@@ -79,7 +79,7 @@
   # Handler for [data-cart-toggle-attribute] change events.
   toggleAttribute: (e) ->
     $input = jQuery(this)
-    attribute = $input.data('cartToggleAttribute')
+    attribute = $input.attr('data-cart-toggle-attribute')
     CartJS.Core.setAttribute(attribute, if $input.is(':checked') then 'Yes' else '')

   # Handle for [data-cart-submit] submit events.
@@ -114,4 +114,4 @@
     # Render the context to elements as needed.
     jQuery('[data-cart-render]').each ()->
       $this = jQuery(this)
-      $this.text context[$this.data('cartRender')]
+      $this.text context[$this.attr('data-cart-render')]


Remove duplicate method

Remove duplicate method

--- src/turbolinks/visit.coffee
+++ src/turbolinks/visit.coffee
@@ -91,9 +91,6 @@
   shouldIssueRequest: ->
     @action is "advance" or not @hasSnapshot()

-  hasSnapshot: ->
-    @controller.hasSnapshotForLocation(@location)
-
   saveSnapshot: ->
     unless @snapshotSaved
       @controller.saveSnapshotForLocation(@previousLocation)


that was just not true

that was just not true

--- src/ui/views/convadd.coffee
+++ src/ui/views/convadd.coffee
@@ -2,11 +2,7 @@
 {throttle} = require '../util'
 chilledaction = throttle 1500, action

-unique = (obj) ->
-  # unfortunately entities resulting from a search do not have
-  # an unique id associated therefore we use serialized obkect
-  # to compare them
-  JSON.stringify obj
+unique = (obj) -> obj.id.chat_id or obj.id.gaia_id

 module.exports = view (models) ->
   {convsettings} = models


fixes #240: treat unrecognised extensions as JS; support JSON

--- src/documents/lib/command.js.coffee
+++ src/documents/lib/command.js.coffee
@@ -117,21 +117,20 @@
 # Compile a path, which could be a script or a directory. If a directory
 # is passed, recursively compile all '.js' extension source files in it
 # and all subdirectories.
-compilePath = (source, topLevel, base) ->
+compilePath = (source, topLevel = yes) ->
   fsUtil.stat source, (err, stats) ->
     throw err if err and err.code isnt 'ENOENT'
     if err?.code is 'ENOENT' # file does not exist
       if topLevel and source[-3..] isnt '.js'
         # retry with extension '.js' added
-        source = "#{source}.coffee"
-        return compilePath source, topLevel, base
+        source = "#{source}.js"
+        return compilePath source, topLevel
       if topLevel
         console.error "File not found: #{source}"
         process.exit 1
       return

     if stats.isDirectory()
-      # watchDir source, base if opts.watch
       fsUtil.readdir source, (err, files) ->
         throw err if err and err.code isnt 'ENOENT'
         return if err?.code is 'ENOENT'
@@ -139,8 +138,8 @@
         # sources[index..index] = (pathUtil.join source, file for file in files)
         # sourceCode[index..index] = files.map -> null
         for file in files when not hidden file
-          compilePath (pathUtil.join source, file), no, base
-    else if topLevel or pathUtil.extname(source) is '.js'
+          compilePath (pathUtil.join source, file), no
+    else if topLevel or (pathUtil.extname source) in ['.js', '.json']
       compileScript source

 # Test if file is hidden (starts with a dot)
@@ -152,6 +151,8 @@
   try
     console.log "#### ---- #{fname}" if options.verbose
     code = fsUtil.readFileSync fname
+    if '.json' is pathUtil.extname fname
+      code = "(#{code})"
     compiled_code = js2coffee.build(code.toString(),options)
     console.log compiled_code
   catch err


:new: Add specs for the workaround

:new: Add specs for the workaround

--- spec/package-deps-spec.coffee
+++ spec/package-deps-spec.coffee
@@ -32,6 +32,24 @@
       expect(view.progress.value).toBe(1)
       view.advance()
       expect(view.progress.value).toBe(2)
+
+    it 'works without notifications', ->
+      {View} = require('../lib/view')
+      view = new View('some-package', ['linter'])
+      view.show()
+      advanceClock(50)
+      expect(view.element).not.toBe(null)
+      expect(view.element.tagName).toBe('DIV')
+
+    it 'works with notifications', ->
+      {View} = require('../lib/view')
+      view = new View('some-package', ['linter'])
+
+      waitsForPromise ->
+        atom.packages.activatePackage('notifications').then ->
+          view.show()
+          advanceClock(50)
+          expect(view.element.tagName).not.toBe('div')

   describe 'install', ->
     it 'works', ->


Fix typo in app.coffee

Fix typo in app.coffee

Forgot the = sign, whoops.

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -27,7 +27,7 @@

-ready ->
+ready = ->
   triviaInit()
   $('table.nav .menu-btn a').click ->
     $('table.nav .items, table.nav .search').toggleClass('isOpen')


:white_check_mark: for ir and ar diff

:white_check_mark: for ir and ar diff

And spec has bug for ‘gear” part but no effect since !!! is not concept
of upper/lower

--- spec/range-marker-spec.coffee
+++ spec/range-marker-spec.coffee
@@ -49,25 +49,42 @@
       expect(vimState.hasRangeMarkers()).toBe(false)

     describe "basic behavior", ->
-      it "create-range-marker create range marker", ->
-        keystroke('g m i w')
-        ensureRangeMarker length: 1, text: ['ooo']
-        keystroke('j .')
-        ensureRangeMarker length: 2, text: ['ooo', 'xxx']
-      it "marked range can use as target of operator by `a r`", ->
-        keystroke('g m i w j . 2 j g m i p') # Mark 2 inner-word and 1 inner-paragraph
-        ensureRangeMarker length: 3, text: ['ooo', 'xxx', "ooo xxx ooo\nxxx ooo xxx\n"]
-        ensure 'g U a r',
-          text: """
-          OOO xxx ooo
-          XXX ooo xxx
+      describe "create-range-marker", ->
+        it "create-range-marker create range marker", ->
+          keystroke('g m i w')
+          ensureRangeMarker length: 1, text: ['ooo']
+          keystroke('j .')
+          ensureRangeMarker length: 2, text: ['ooo', 'xxx']
+      describe "inner-range-marker", ->
+        it "apply operator only to cursor contained range-marker", ->
+          keystroke('g m i w j . 2 j g m i p') # Mark 2 inner-word and 1 inner-paragraph
+          ensureRangeMarker length: 3, text: ['ooo', 'xxx', "ooo xxx ooo\nxxx ooo xxx\n"]
+          ensure 'g U i r',
+            text: """
+            ooo xxx ooo
+            xxx ooo xxx

-          OOO XXX OOO
-          XXX OOO XXX
+            OOO XXX OOO
+            XXX OOO XXX

-          ooo xxx ooo
-          xxx ooo xxx\n
-          """
+            ooo xxx ooo
+            xxx ooo xxx\n
+            """
+      describe "a-range-marker", ->
+        it "apply operator to across all range-markers", ->
+          keystroke('g m i w j . 2 j g m i p') # Mark 2 inner-word and 1 inner-paragraph
+          ensureRangeMarker length: 3, text: ['ooo', 'xxx', "ooo xxx ooo\nxxx ooo xxx\n"]
+          ensure 'g U a r',
+            text: """
+            OOO xxx ooo
+            XXX ooo xxx
+
+            OOO XXX OOO
+            XXX OOO XXX
+
+            ooo xxx ooo
+            xxx ooo xxx\n
+            """

     describe "select-all-in-range-marker", ->
       it "select all instance of cursor word only within marked range", ->
@@ -78,7 +95,7 @@
         expect(editor.getSelections()).toHaveLength(6)
         keystroke 'c'
         editor.insertText '!!!'
-        ensure 'g U a r',
+        ensure
           text: """
           !!! xxx !!!
           xxx !!! xxx


Spec: Non Editor tabs open permanently

Spec: Non Editor tabs open permanently

--- spec/sublime-tabs-spec.coffee
+++ spec/sublime-tabs-spec.coffee
@@ -4,7 +4,7 @@
 SublimeTabBarView         = require '../lib/sublime-tab-bar-view'
 SublimeTabView            = require '../lib/sublime-tab-view'

-describe "Sublime Tabs Package", ->
+describe 'Sublime Tabs Package', ->
   beforeEach: ->
     atom.workspaceView = new WorkspaceView

@@ -48,41 +48,68 @@
     atom.deserializers.remove(TestView)

   describe 'Temporary Tabs', ->
-    it 'adds a temp class when opening a file', ->
-      editor2 = null
+    describe 'Opening a new tab', ->
+      it 'adds a temp class when opening a file', ->
+        editor2 = null

-      waitsForPromise ->
-        atom.project.open('sample.txt').then (o) -> editor2 = o
+        waitsForPromise ->
+          atom.project.open('sample.txt').then (o) -> editor2 = o

-      runs ->
-        pane.activateItem(editor2)
-        expect(tabBar.tabForItem(editor2)).toHaveClass 'temp'
+        runs ->
+          pane.activateItem(editor2)
+          expect(tabBar.tabForItem(editor2)).toHaveClass 'temp'

-    it 'makes the tab permanent when dbl clicking the tab', ->
-      editor2 = null

-      waitsForPromise ->
-        atom.project.open('sample.txt').then (o) -> editor2 = o

-      runs ->
-        pane.activateItem(editor2)
-        tabBar.tabForItem(editor2).trigger 'dblclick'
-        expect(tabBar.tabForItem(editor2)).not.toHaveClass 'temp'
+      describe 'when there is an temp tab already', ->
+        it 'will replace an existing temporary tab', ->
+          editor2 = null
+          editor3 = null

-    it 'will replace an existing temporary tab', ->
-      editor2 = null
-      editor3 = null
+          waitsForPromise ->
+            atom.project.open('sample.txt').then (o) ->
+              editor2 = o
+              pane.activateItem(editor2)
+              atom.project.open('sample2.txt').then (o) ->
+                editor3 = o
+                pane.activateItem(editor3)

-      waitsForPromise ->
-        atom.project.open('sample.txt').then (o) ->
-          editor2 = o
-          pane.activateItem(editor2)
-          atom.project.open('sample2.txt').then (o) ->
-            editor3 = o
-            pane.activateItem(editor3)
+          runs ->
+            expect(editor2.isDestroyed()).toBe true
+            expect(editor3.isDestroyed()).toBe false
+            expect(tabBar.tabForItem(editor2)).not.toExist()
+            expect(tabBar.tabForItem(editor3)).toHaveClass 'temp'

-      runs ->
-        expect(editor2.isDestroyed()).toBe true
-        expect(editor3.isDestroyed()).toBe false
-        expect(tabBar.tabForItem(editor2)?).toBe false
-        expect(tabBar.tabForItem(editor3)).toHaveClass 'temp'
+        it 'makes the tab permanent when dbl clicking the tab', ->
+          editor2 = null
+
+          waitsForPromise ->
+            atom.project.open('sample.txt').then (o) -> editor2 = o
+
+          runs ->
+            pane.activateItem(editor2)
+            tabBar.tabForItem(editor2).trigger 'dblclick'
+            expect(tabBar.tabForItem(editor2)).not.toHaveClass 'temp'
+
+      describe 'when opening views that do not contain an editor', ->
+        editor2 = null
+        settingsView = null
+
+        beforeEach ->
+          waitsForPromise ->
+            atom.project.open('sample.txt').then (o) ->
+              editor2 = o
+              pane.activateItem(editor2)
+
+          waitsForPromise ->
+            atom.packages.activatePackage('settings-view').then ->
+              atom.workspaceView.open('atom://config').then (o) ->
+                settingsView = o
+                pane.activateItem(settingsView)
+
+        it 'creates a permanent tab', ->
+          expect(tabBar.tabForItem(settingsView)).toExist()
+          expect(tabBar.tabForItem(settingsView)).not.toHaveClass 'temp'
+
+        it 'replaces an existing temp tab', ->
+          expect(tabBar.tabForItem(editor2)).not.toExist()


автоматически прокладывается маршрут до ближайшего места от текущего

--- app/assets/javascripts/map.coffee
+++ app/assets/javascripts/map.coffee
@@ -17,11 +17,7 @@
       navigator.geolocation.getCurrentPosition (position) ->
         current_location = new google.maps.LatLng(position.coords.latitude, position.coords.longitude)
         map.google_map.setCenter current_location
-
-        console.log(map.current_location)
         map.current_location = current_location
-
-        console.log(map.current_location)

         current_loc_marker_options =
           position: current_location
@@ -44,7 +40,7 @@
       suppressMarkers: true
     directions_renderer = new google.maps.DirectionsRenderer(directions_renderer_options)

-    end_point = new google.maps.LatLng(places.objects[0].lat, places.objects[0].lng)
+    end_point = new google.maps.LatLng(places.closest.lat, places.closest.lng)

     directions_service_options =
       origin: start_point
@@ -59,10 +55,8 @@
     )

   init: ->
-    #запуск карты, центрование на текущем месте и отображение всех мест
+    #инициализация карты
     map.google_map = new google.maps.Map(@.dom, @.options)
-
-

 places =
   #параметры мест
@@ -80,7 +74,7 @@
       icon: 'assets/places/car.png'

   render:(place) ->
-    #отображение мест на карте, принимает json объект места
+    #отображение места на карте, принимает json объект места
     position = new google.maps.LatLng(place.lat, place.lng)
     place_marker_options =
       position: position
@@ -89,7 +83,7 @@
     marker = new google.maps.Marker(place_marker_options)

     place_infobox = new InfoBox(infobox_options)
-    infobox_content = "" + place.type + ": " + place.name + " расстояние: " + place.distance
+    infobox_content = "
" + place.type + ": " + place.name + "

Расстояние: " + place.distance.toFixed(1) + " км
"

     google.maps.event.addListener marker, 'mouseover', ->
       place_infobox.setContent(infobox_content)
@@ -104,6 +98,7 @@
     x * Math.PI / 180

   get_distance:(place) ->
+    #добавляет значение расстояния от места до текущего положения, принимает json объект места
     cur_loc_lat = map.current_location.lat()
     cur_loc_lng = map.current_location.lng()
     earth_radius = 6371
@@ -116,6 +111,9 @@
     c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
     place.distance = earth_radius * c

+    if places.closest == undefined || places.closest.distance > place.distance
+      places.closest = place
+

 infobox_options =
   #параметры отображения окна инфы при ховере поместу


Only warn about missing controller context when calling bindTo

--- src/views/base/index.coffee
+++ src/views/base/index.coffee
@@ -96,16 +96,7 @@
     @options = options

   _bindControllerEvents: ->
-
-    if context = @controller?.context
-      context.on 'destroy', @destroy
-    else
-      console.warn '''
-        This view has not been provided with an associated context
-        (usually the Window within which the view is displayed).
-        This means we won't be able to automatically destroy the
-        view when the context is destroyed.
-      '''
+    @controller?.context?.on 'destroy', @destroy

   # Internal: Creates the view if it doesn't already exist.
   #
@@ -205,6 +196,17 @@

   bindTo: (model, eventName, callback) =>

+    unless @bindings
+
+      unless @controller?.context
+
+        console.warn """
+          This view (#{@viewName}) has not been provided with an associated context
+          (usually the Window within which the view is displayed).  This means we
+          won't be able to automatically destroy the view when the context is destroyed,
+          and the events being bound will likely cause a memory leak.
+        """
+
     @bindings or= []

     model.on eventName, callback, @


cleanup ws

cleanup ws

--- src/views/base/index.coffee
+++ src/views/base/index.coffee
@@ -118,14 +118,12 @@

     @application.decorators.decorate @

-
   ###
   ###

   setChild: (name, child) ->
     child.set "parent", parent
     @set "sections.#{name}", child
-

   ###
   ###


Optimized room cleanup

Optimized room cleanup

--- src/tunnel.coffee
+++ src/tunnel.coffee
@@ -30,7 +30,7 @@
     disconnect: (cn, roomId, clientId)->
         cn.on 'close', =>
             delete @rooms[roomId][clientId]
-            @cleanupRooms()
+            delete @rooms[roomId] if _.isEmpty @rooms[roomId]

     message: (cn, data)->
         mes = null
@@ -53,7 +53,7 @@

         clearTimeout cn._startTO
         @disconnect cn, roomId, clientId
-        @response cn, clientId, roomId, '/hello'
+        @response cn, null, 'tunnel', '/created', room: roomId, client: clientId

     tunnelMessage: (cn, mes, data)->
         return if not mes.host or not @rooms[mes.host] or not @rooms[mes.host][mes.auth]
@@ -82,9 +82,5 @@
         hash.update _.uniqueId prefix + new Date().getTime()
         return hash.digest('hex').slice(0, 8)

-    cleanupRooms: ->
-        for room of @rooms
-            delete @rooms[room] if _.isEmpty @rooms[room]
-

 module.exports = Tunnel


Add K2C4 group

Add K2C4 group

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -25,7 +25,8 @@
 K2_0_SUBJECT_GROUP = "547d05ce415ac13139000001"
 K2_1_SUBJECT_GROUP = "54f4c5ab8f165b6e85000001"
 K2_2_SUBJECT_GROUP = "55db0eca05cd210084000001"
-MAIN_SUBJECT_GROUP = K2_2_SUBJECT_GROUP
+K2_4_SUBJECT_GROUP = "5628a593eaad4a0122000001"
+MAIN_SUBJECT_GROUP = K2_4_SUBJECT_GROUP
 SIMULATION_GROUP   = "5417014b3ae7400bda000002"

 USERS_OPT_IN = ['a', 'b', 'c', 'g', 'h', 'i']


Action para salvar um novo lançamento (ainda não testado)

--- app/controllers/lancamento.coffee
+++ app/controllers/lancamento.coffee
@@ -1,5 +1,7 @@
 module.exports = (app) ->
-
+	Organizador = app.models.organizador
+	Lancamento = app.models.lancamento
+
 	LancamentoController =
 		index: (req, res) ->
 			res.render 'lancamento/index'
@@ -10,6 +12,13 @@
 		salvar: (req, res) ->
 			lancamento = req.body.lancamento

-			# Salvar
+			organizador = new Organizador

-			res.render 'lancamento/index'+			novoLancamento = organizador.criar new Date(),
+				lancamento.finalidade, lancamento.detalhesDaCompra, lancamento.valor
+
+			console.log novoLancamento
+
+			Lancamento.create novoLancamento, (erro, lancamento) ->
+				res.redirect '/' if erro
+				res.redirect '/lancamento'


adding a comment updates users timestamp

--- workers/social/lib/social/models/messages/post.coffee
+++ workers/social/lib/social/models/messages/post.coffee
@@ -343,40 +343,63 @@
       comment = new replyType body: comment
       comment.sign(delegate).save (err)=>
         return callback err if err
+        daisy queue = [
+          ()->
+            delegate.addContent comment, (err)->
+              return callback err if err
+              queue.next()
+          ()->
+            delegate.updateMetaModifiedAt (err)->
+              return callback err if err
+              queue.next()
+          ()=>
+            @addComment comment, flags: {isLowQuality: exempt}, (err, docs)=>
+              return callback err if err
+              queue.docs = docs
+              queue.next()
+          ()=>
+            Relationship.count {sourceId: @getId(),as:'reply'}, (err, count_)=>
+              queue.relationshipCount = count_
+              return callback err if err
+              queue.next()
+          ()=>
+            @update $set: repliesCount: relationshipCount, (err)=>
+              return callback err if err
+              queue.next()
+          ()=>
+            @fetchActivityId (err, id)->
+              return callback err if err
+              CActivity.update {_id: id}, $set: 'sorts.repliesCount': relationshipCount, (err)->
+                return callback err if err
+                queue.next()
+          ()=>
+            @fetchOrigin (err, origin)=>
+              return callback err if err
+              unless exempt
+                @emit 'ReplyIsAdded', {
+                  origin
+                  subject       : ObjectRef(@).data
+                  actorType     : 'replier'
+                  actionType    : 'reply'
+                  replier       : ObjectRef(delegate).data
+                  reply         : ObjectRef(comment).data
+                  repliesCount  : relationshipCount
+                  relationship  : queue.docs[0]
+                }
+              queue.next()
+          ()=>
+            @follow client, emitActivity: no, (err)->
+              return callback err if err
+              queue.next()
+          ()=>
+            @addParticipant delegate, 'commenter', (err)->
+              return callback err if err
+              queue.next()
+          ()->
+            callback null, comment
+        ]

-        delegate.addContent comment, (err)-> log 'error adding content to delegate with err', err if err
-
-        delegate.update $set: 'meta.modifiedAt': new Date, ->
-
-        @addComment comment, flags: {isLowQuality: exempt}, (err, docs)=>
-          return callback err if err
-          return callback null, comment if exempt
-
-          Relationship.count {sourceId: @getId(),as:'reply'}, (err, count)=>
-            return callback err if err
-            @update $set: repliesCount: count, (err)=>
-              return callback err if err
-              callback null, comment
-
-              @fetchActivityId (err, id)-> CActivity.update {_id: id}, {
-                  $set: 'sorts.repliesCount': count
-                }, (err)-> log err if err
-
-              @fetchOrigin (err, origin)=>
-                return console.log "Couldn't fetch the origin" if err
-                unless exempt
-                  @emit 'ReplyIsAdded', {
-                    origin
-                    subject       : ObjectRef(@).data
-                    actorType     : 'replier'
-                    actionType    : 'reply'
-                    replier       : ObjectRef(delegate).data
-                    reply         : ObjectRef(comment).data
-                    repliesCount  : count
-                    relationship  : docs[0]
-                  }
-                @follow client, emitActivity: no, (err)->
-                @addParticipant delegate, 'commenter', (err)-> #TODO: what should we do with this error?
+

   # TODO: the following is not well-factored.  It is not abstract enough to belong to "Post".
   # for the sake of expedience, I'll leave it as-is for the time being.


Fixes the problem where the embed frame gets wrapped around interactives when it shouldn't.  This makes the condition based on the type of step instead of trying to figure out what it is based on the URL.

--- src/components/task-step/step-with-reading-content.cjsx
+++ src/components/task-step/step-with-reading-content.cjsx
@@ -42,11 +42,14 @@
   getCnxId: ->
     TaskStepStore.getCnxId(@props.id)

+  shouldExcludeFrame: ->
+    (@props.stepType == 'interactive')
+
   render: ->
     {id, stepType} = @props
     {content_html} = TaskStepStore.get(id)
     <div className={"#{stepType}-step"}>
-      <ArbitraryHtmlAndMath className={"#{stepType}-content"} html={content_html} />
+      <ArbitraryHtmlAndMath className={"#{stepType}-content"} html={content_html} shouldExcludeFrame={@shouldExcludeFrame} />
     </div>


Add jade helper to check if translation exists.

--- src/jade.coffee
+++ src/jade.coffee
@@ -88,6 +88,7 @@
                 ['t', i18n.t]
                 ['tAttr', @tAttr]
                 ['tAttrHasLang', @tAttrHasLang]
+                ['translationExists', i18n.exists]
                 ['phoneI18n', @phoneI18n]
                 ['staticPath', @staticPath]
                 ['humanDateRange', @humanDateRange]


Add support for multiline descriptions

Add support for multiline descriptions

--- src/jira.coffee
+++ src/jira.coffee
@@ -43,7 +43,7 @@
               user = JSON.parse body
               reporter = user[0] if user and user.length is 1
             finally
-              quoteRegex = /`(.*?)`/
+              quoteRegex = /`{1,3}([^]*?)`{1,3}/
               labelsRegex = /#\S+\s?/g
               labels = ["triage"]
               message = msg.match[1]
@@ -89,19 +89,19 @@
                     msg.send "<@#{msg.message.user.id}> Unable to create ticket: #{error}"
                     console.log "statusCode:", res.statusCode, "error:", error, "err:", err, "body:", body

-    robot.respond /story (.+)/i, (msg) ->
+    robot.respond /story ([^]+)/i, (msg) ->
       room = msg.message.room
       project = projects[room]
       return msg.reply "Stories must be submitted in one of the following project channels:" + (" <\##{team}>" for team, key of projects) if not project
       report project, "Story / Feature", msg

-    robot.respond /bug (.+)/i, (msg) ->
+    robot.respond /bug ([^]+)/i, (msg) ->
       room = msg.message.room
       project = projects[room]
       return msg.reply "Bugs must be submitted in one of the following project channels:" + (" <\##{team}>" for team, key of projects) if not project
       report project, "Bug", msg

-    robot.respond /task (.+)/i, (msg) ->
+    robot.respond /task ([^]+)/i, (msg) ->
       room = msg.message.room
       project = projects[room]
       return msg.reply "Tasks must be submitted in one of the following project channels:" + (" <\##{team}>" for team, key of projects) if not project


UserEnvironmentDataProvider: Added get method.

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -12,6 +12,9 @@

       globals.userEnvironmentData = data
       callback data
+
+
+  get: -> return globals.userEnvironmentData

   revive: ->


Now, clicking on any descendant of a carouseling post will stop the carouseling, preserving the lineage of the post in question.

--- client/coffeescript/templates/post_wrapper.coffee
+++ client/coffeescript/templates/post_wrapper.coffee
@@ -53,7 +53,11 @@

     'click': (event) ->
       parent = Session.get('carousel_parent')
-      if parent?._id == @parent_id
+      ancestors = [(cur = @).parent_id]
+      while cur?.parent_id?
+        cur = Posts.findOne( _id: cur.parent_id )
+        ancestors.push(cur.parent_id)
+      if parent._id in ancestors
         Meteor.clearInterval(Session.get('carousel_handle'))
         Session.set('carousel_handle', null)
   }


Usage tests - wip

Usage tests - wip

--- test/unit/08-Usage.coffee
+++ test/unit/08-Usage.coffee
@@ -21,18 +21,18 @@
       @robot.receive msg, -> deferred.resolve() # resolve as callback
       return deferred.promise
     # same username in two rooms should be recognised as same user
-    @nimaInFoo = new User 1,
+    @nimaInA = new User 1,
       name: 'nima'
-      room: '#foo'
-    @pemaInFoo = new User 2,
+      room: '#A'
+    @pemaInA = new User 2,
       name: 'pema'
-      room: '#foo'
-    @nimaInBar = new User 1,
+      room: '#A'
+    @nimaInB = new User 1,
       name: 'nima'
-      room: '#bar'
-    @pemaInBar = new User 2,
+      room: '#B'
+    @pemaInB = new User 2,
       name: 'pema'
-      room: '#bar'
+      room: '#B'

   beforeEach ->
     # prepare robot to store every message
@@ -55,80 +55,76 @@
     _.invoke @spy, 'restore' # restore all the methods
     @robot.shutdown()

-  context 'scene type "user"', ->
+  context 'knock knock test - user scene', ->

     beforeEach ->
+      unmute = mute()
       @scene = @playbook.scene 'user'
+      @robot.hear /knock/, (res) =>
+        @dialogue = @scene.enter res
+        @dialogue.send "Who's there?"
+        @dialogue.branch /.*/, (res) =>
+          @dialogue.send "#{ res.match[0] } who?"
+          @dialogue.branch /.*/, "Hello #{ res.match[0] }"
+      unmute()

     afterEach ->
       unmute = mute()
       @scene.exitAll()
       unmute()

-    context 'enter dialogue and another user responds to prompt', ->
+    context 'Nima begins in A, continues in B, Pema tries in both', ->

       beforeEach ->
         unmute = mute()
-        @robot.hear /knock/, (res) =>
-          @dialogue = @scene.enter res
-          @dialogue.send "Who's there?"
-          @dialogue.branch /.*/, (res) =>
-            @dialogue.send "#{ res.match[0] } who?"
-            @dialogue.branch /.*/, "hahaha, good one."
-
-        @send @nimaInFoo, 'knock knock' # who's there?
-        .then => @send @pemaInFoo, 'Pema' # ignored
-        .then => @send @nimaInFoo, 'Nima' # Nima who?
-        .then => @send @nimaInFoo, 'Nima in Foo' # haha
+        @send @nimaInA, 'knock knock' # ...Who's there?
+        .then => @send @pemaInA, 'Pema in A' # ...ignored
+        .then => @send @nimaInB, 'Nima in B' # ...Nima in B who?
+        .then => @send @pemaInB, 'Pema in B' # ...ignored
         .then -> unmute()

-      it 'responds to first user only', ->
+      it 'responds to Nima in both, ignores Pema in both', ->
         @messages.should.eql [
-          [ '#foo', 'nima', 'knock knock' ],
-          [ '#foo', 'hubot', 'Who\'s there?' ],
-          [ '#foo', 'pema', 'Pema' ],
-          [ '#foo', 'nima', 'Nima' ],
-          [ '#foo', 'hubot', 'Nima who?' ],
-          [ '#foo', 'nima', 'Nima in Foo' ],
-          [ '#foo', 'hubot', 'hahaha, good one.' ]
+          [ '#A', 'nima', 'knock knock' ],
+          [ '#A', 'hubot', 'Who\'s there?' ],
+          [ '#A', 'pema', 'Pema in A' ],
+          [ '#B', 'nima', 'Nima in B' ],
+          [ '#A', 'hubot', 'Nima in B who?' ],
+          [ '#B', 'pema', 'Pema in B' ]
         ]

-    context 'enter dialogue and continue in another room', ->
+  context 'scene type "room"', ->
+
+    beforeEach ->
+      @scene = @playbook.scene 'room'
+
+    afterEach ->
+      unmute = mute()
+      @scene.exitAll()
+      unmute()
+
+    context 'Nima begins in A, continues in B, Pema responds in A', ->

       beforeEach ->
         unmute = mute()
-        @robot.hear /knock/, (res) =>
-          @dialogue = @scene.enter res
-          @dialogue.send "Who's there?"
-          @dialogue.branch /.*/, (res) =>
-            @dialogue.send "Hi #{ res.match[0] }"
-
-        @send @nimaInFoo, 'knock knock' # who's there?
-        .then => @send @pemaInFoo, 'Pema in Foo' # ignored
-        .then => @send @nimaInBar, 'Nima in Bar' # Nima who? (in other room)
-        .then => @send @pemaInBar, 'Pema in Bar' # ignored
+        @send @nimaInA, 'knock knock' # ...Who's there?
+        .then => @send @pemaInA, 'Pema' # ...Pema who?
+        .then => @send @pemaInB, 'Pema in B' # ...Pema who?
+        .then => @send @nimaInB, 'No it\'s Nima' # No it's Nima who?
+        .then => @send @pemaInB, 'Hey!?' # ...ignored
         .then -> unmute()

-      it 'responds to the first user only', ->
+      it 'responds to Nima or Pema in A, ignores in B', ->
         @messages.should.eql [
-          [ '#foo', 'nima', 'knock knock' ],
-          [ '#foo', 'hubot', 'Who\'s there?' ],
-          [ '#foo', 'pema', 'Pema in Foo' ],
-          [ '#bar', 'nima', 'Nima in Bar' ],
-          [ '#foo', 'hubot', 'Hi Nima in Bar' ],
-          [ '#bar', 'pema', 'Pema in Bar' ]
+          [ '#A', 'nima', 'knock knock' ],
+          [ '#A', 'hubot', 'Who\'s there?' ],
+          [ '#A', 'pema', 'Pema' ],
+          [ '#A', 'hubot', 'Pema who?' ],
+          [ '#B', 'pema', 'Pema in B' ],
+          [ '#A', 'nima', 'No it\'s Nima' ],
+          [ '#A', 'hubot', 'No it\'s Nima who?' ],
+          [ '#A', 'pema', 'Hey!?' ]
         ]
-
-
-  # context 'scene type "room"', ->
-  #
-  #   beforeEach ->
-  #
-  #   it 'responds to the first user in the room', ->
-  #
-  #   it 'responds to other users in the room', ->
-  #
-  #   it 'does not respond in other rooms', ->
   #
   # context 'scene type "userRoom"', ->
   #


event update: use computed values

event update: use computed values

--- src/main.coffee
+++ src/main.coffee
@@ -268,7 +268,7 @@
             event.allDay = false
           else
             # Set start time
-            event.start = moment(event.start).hour(m[1]).minute(m[2]).format()
+            event.start = moment(event.start).hour(start_hour).minute(start_min).format()
             event.allDay = false
           event.title = m[3]
           delta_title_save event, update_event_if_ok


logoutFromFB

logoutFromFB

--- src/main.coffee
+++ src/main.coffee
@@ -48,7 +48,17 @@
       ).promise()
     logoutFromFB : () ->
       SRNDP_FB_IFRAME.contentWindow.postMessage("srndp-logout-fb",Settings.BASE_URL)
-      @logout()
+      that = @
+      return $.Deferred(
+        () ->
+          that.logout().done(
+            (resp) =>
+              @resolve(resp)
+          ).fail(
+            (err) =>
+              @reject(err)
+          )
+      ).promise()
     activate : () ->
       return $.Deferred(
         () ->


Remove unneeded backgroundColor

Remove unneeded backgroundColor

--- src/main-process/atom-window.coffee
+++ src/main-process/atom-window.coffee
@@ -26,11 +26,6 @@
     options =
       show: false
       title: 'Atom'
-      # Add an opaque backgroundColor (instead of keeping the default
-      # transparent one) to prevent subpixel anti-aliasing from being disabled.
-      # We believe this is a regression introduced with Electron 0.37.3, and
-      # thus we should remove this as soon as a fix gets released.
-      backgroundColor: "#fff"
       webPreferences:
         # Prevent specs from throttling when the window is in the background:
         # this should result in faster CI builds, and an improvement in the


using userId() instead of user._id, it was causing some problems fetching the colleciton

--- client/views/backgrounds/backgroundListing.coffee
+++ client/views/backgrounds/backgroundListing.coffee
@@ -6,7 +6,7 @@
   needs: ->
     Needs.find backgroundId: @_id
   needsIOwn: ->
-    Needs.find userId: Meteor.user._id
+    Needs.find userId: Meteor.userId()
   isEditingProject: ->
     Session.get('editingProject') is @_id
   editFlagForNeeds: ->


Read languages from config, support i18n.exists.

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -23,14 +23,13 @@
             return 'en-gb'
         return lang

-    SUPPORTED_LANGUAGES = ['fi', 'en', 'sv']
+    SUPPORTED_LANGUAGES = appSettings.supported_languages
     LOCALSTORAGE_KEY = 'servicemap_p13n'
     CURRENT_VERSION = 1
     LANGUAGE_NAMES =
         fi: 'suomi'
         sv: 'svenska'
         en: 'English'
-    FALLBACK_LANGUAGES = ['en', 'fi']

     ACCESSIBILITY_GROUPS = {
         senses: ['hearing_aid', 'visually_impaired', 'colour_blind'],
@@ -121,7 +120,7 @@
             @deferred = i18n.init
                 lng: @getLanguage()
                 resGetPath: appSettings.static_path + 'locales/__lng__.json'
-                fallbackLng: FALLBACK_LANGUAGES
+                fallbackLng: []

             #TODO: This should be moved to a more appropriate place (and made nicer)
             i18n.addPostProcessor "fixFinnishStreetNames", (value, key, options) ->