Cleanup: minor

Cleanup: minor

--- client/Main/sidebar/sidebarview.coffee
+++ client/Main/sidebar/sidebarview.coffee
@@ -27,15 +27,13 @@

     # Main Navigations
     @navController = new MainNavController
-      view           : new NavigationList
-        testPath     : 'navigation-list'
+      view           : new KDListView
         type         : "navigation"
         itemClass    : NavigationLink
         testPath     : "navigation-list"
       wrapper        : no
       scrollView     : no
     ,
-      id        : "navigation"
       title     : "navigation"
       items     : []


PrivateMessage: add request data to private message responses.

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -84,6 +84,8 @@
     # this is sent by the server when
     # response for pinned messages
     m.unreadRepliesCount = data.unreadRepliesCount
+
+    m.requestData = plain.requestData

     m.interactions    = interactions or
       like            :


Minor change to route handling

Minor change to route handling

--- client/account/lib/routehandler.coffee
+++ client/account/lib/routehandler.coffee
@@ -29,6 +29,7 @@
     when 'referrer' then kd.singletons.router.handleRoute '/'
     when 'section' then handle info, path
     when 'recover'
-      ctx.clear()
+      router = ctx
+      router.clear()
       kookies.expire 'clientId'
       global.location.href = '/Recover'


Battle scrolls to the top when turn continues.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -802,6 +802,11 @@
     @model.set('turn', turn)
     done()

+  continueTurn: (done) =>
+    @$('.battle_summary').empty().hide()
+    @$('.battle_window').scrollTop(0)
+    done()
+
   makeMove: (e) =>
     $target = $(e.currentTarget)
     moveName = $target.data('move-id')


FSHelper: PlainPath: Don't replace path with blank if it has "]" character.

--- client/app/lib/util/fs/fshelper.coffee
+++ client/app/lib/util/fs/fshelper.coffee
@@ -90,7 +90,7 @@
     extension = if extension.length is 0 then '' else extension.last
     return extension

-  @plainPath:(path)-> path.replace /^\[.*\]/, ''
+  @plainPath:(path)-> path.replace /^\[.*?\]/, ''
   @getVMNameFromPath:(path)-> (/^\[([^\]]+)\]/g.exec path)?[1]

   @getUidFromPath:(path)-> (/^\[([^\]]+)\]/g.exec path)?[1]


FavoUrite

FavoUrite

--- src/scripts/views/Classifier.coffee
+++ src/scripts/views/Classifier.coffee
@@ -58,8 +58,8 @@

-
<button>Mark as a favorite</button>

-

Favorite added!
<button>Undo</button>

+
<button>Mark as a favourite</button>

+

Favourite added!
<button>Undo</button>


Fixed errorToSlack for hubot-slack v4

Fixed errorToSlack for hubot-slack v4

--- src/helpers/logger.coffee
+++ src/helpers/logger.coffee
@@ -126,9 +126,11 @@
         else
           @error "No robot available to send message: #{msg}"
     @errorToSlack: (msg, error) ->
+      diagnosticsRoom = robot.adapter.client.rtm.dataStore.getChannelOrGroupByName 'ibizan-diagnostics'
+      diagnosticsID = diagnosticsRoom.id
       if msg
-        if robot and robot.send?
-          robot.send {room: 'ibizan-diagnostics'},
+        if robot and robot.send? and diagnosticsRoom
+          robot.send {room: diagnosticsID},
             "(#{new Date()}) ERROR: #{msg}\n#{error || ''}"
         else
           @error msg, error


tests: helpers: fakeText optional maxLength param implemented

--- client/test/lib/helpers/helpers.coffee
+++ client/test/lib/helpers/helpers.coffee
@@ -289,8 +289,12 @@
     return hashtag

-  getFakeText: ->
-    return faker.Lorem.paragraph().replace /(?:\r\n|\r|\n)/g, ''
+  getFakeText: (maxLength) ->
+
+    text = faker.Lorem.paragraph().replace(/(?:\r\n|\r|\n)/g, '')
+    text.substr(0, maxLength)  if maxLength
+
+    return text

   openFolderContextMenu: (browser, user, folderName) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -39,7 +39,7 @@

   ret = o.include M

-  equals ret,
+  equals ret, o

   equals o.test(), "my_val"
   equals o.test2, "cool"


Navigate to the query page prior to focusing the concept

--- coffee/cilantro/ui/context/item.coffee
+++ coffee/cilantro/ui/context/item.coffee
@@ -41,6 +41,7 @@
             language: flattenLanguage(@model.toJSON()).join(', ')

         clickShow: (event) ->
+            c.router.navigate('query', trigger: true)
             c.publish c.CONCEPT_FOCUS, @model.get('concept')

         clickRemove: (event) ->


Delete demo block

Delete demo block

--- app/main.cjsx
+++ app/main.cjsx
@@ -13,14 +13,6 @@
 Settings = require './pages/settings'
 UserProfile = require './pages/user-profile'
 Build = require './pages/build'
-
-DemoRouteHandler = React.createClass
-  render: ->
-

-
Params: {JSON.stringify @props.route.params}

-
Query: {JSON.stringify @props.route.query}

-
Hash: {JSON.stringify @props.route.hash}

-

 Main = React.createClass
   displayName: 'Main'


Fix test from last routing commit.

Fix test from last routing commit.

--- tests/batman/view/route_rendering_test.coffee
+++ tests/batman/view/route_rendering_test.coffee
@@ -56,6 +56,7 @@
     node: node
   view.on 'ready', ->
     urls = ($(a).attr('href') for a in view.get('node').children)
+    urls[i] = url || '' for url, i in urls
     deepEqual urls, ['#!/foo/bar', '']
     QUnit.start()
   view.get 'node'


working on requirement

working on requirement

--- src/textorum/relaxng/objects.coffee
+++ src/textorum/relaxng/objects.coffee
@@ -250,6 +250,8 @@
       else
         "notAllowed"
     require: (node) =>
+      if not node?
+        node = @childNode
       if @pattern instanceof Choice or @pattern instanceof Group or @pattern instanceof Interleave
         return @pattern.require(node)
       return this
@@ -467,13 +469,13 @@
         return @pattern2.check(node, descend)
       if @pattern2 instanceof Empty or @pattern2 instanceof EmptyNode
         return @pattern1.check(node, descend)
-      _nodelog node, "p1 group, checking pattern1 #{@pattern1} against", node, [this, @pattern1, node]
+      _nodelog node, "p1 group #{this}, checking pattern1 #{@pattern1} against", node, [this, @pattern1, node]
       p1 = @pattern1.check(node, descend)
-      if p1 instanceof NotAllowed
-        return p1
-      if p1 instanceof GoodElement
+      if p1 instanceof NotAllowed and not @pattern1.require instanceof Empty
+        return p1
+      if p1.require(node) instanceof GoodElement
         return @pattern2
-      _nodelog node, "p2 group, checking pattern2 #{@pattern2} against", node, [this, @pattern2, node]
+      _nodelog node, "p2 group #{this}, checking pattern2 #{@pattern2} against", node, [this, @pattern2, node]
       p2 = @pattern2.check(node, descend)
       if p2 instanceof NotAllowed
         # Return nullabled group
@@ -599,7 +601,7 @@
     _check: (node, descend) =>
       return @require(node)
     require: (node) =>
-      return new EmptyNode("#{this}", this)
+      return new Empty("#{this}", this)
     attrCheck: (node) =>
       error = []
       for attr in h.getNodeAttributes(node)
@@ -693,6 +695,11 @@
       @dereference()
     toString: =>
       @refname
+    require: (node) =>
+      @dereference()
+      if @pattern?
+        return @pattern.require(node)
+      return new NotAllowed("cannot find reference '#{@refname}'", this, node)
     _check: (node, descend) =>
       @dereference()
       if @pattern?
@@ -714,6 +721,8 @@
       @pattern = getPattern pattern
     toString: =>
       "#{@name} = #{@pattern}"
+    require: (node) =>
+      return @pattern.require(node)
     _check: (node, descend) =>
       @pattern.check(node, descend)
     attrCheck: (node) =>


save detected Linux file managers

save detected Linux file managers

--- lib/browse.coffee
+++ lib/browse.coffee
@@ -5,7 +5,7 @@
 {CompositeDisposable} = require 'atom'

 module.exports = BrowsePackages =
-  browsePackagesView: null
+  self: '[browse]'
   subscriptions: null
   fileManager: null
   configFile: atom.config.getUserConfigPath()
@@ -44,10 +44,15 @@
     # Get parent folder of active file
     editor = atom.workspace.getActivePaneItem()
     file = editor?.buffer.file
-    filePath = path.dirname(file?.path)
+
+    if file isnt null
+      filePath = path.dirname(file?.path)

-    # Open packages folder
-    exec "#{@fileManager} #{filePath}"
+      # Open packages folder
+      exec "#{@fileManager} #{filePath}"
+      return
+
+    atom.notifications.addWarning("atom-browse", detail: "No active file", dismissable: false)

   browseConfig: ->
     configPath = path.dirname(@configFile)
@@ -64,6 +69,12 @@
       exec "#{@fileManager} #{configPath}"

   getFileManager: ->
+    fm = atom.config.get('browse.linuxFileManager');
+
+    if typeof fm isnt 'undefined'
+        console.log "#{@self} Load from config: #{fm}"
+        return fm
+
     switch process.platform
       when "darwin"
         return "open"
@@ -77,13 +88,14 @@
         linuxFileManagers = ['xdg-open', 'gnome-open', 'kde-open', 'nautilus']

         for fm in linuxFileManagers
-          try
-            exec "which #{fm}", (error, stdout, stderr) ->
-                if error is null and stdout isnt null
-                  result = fm
+          console.log "#{@self} Trying: #{fm}"
+          exec "which #{fm}", (error, stdout, stderr) ->
+            if stdout isnt null
+              result = stdout

-          # Did we find anything yet?
-          if fm isnt null
+          if typeof result isnt 'undefined'
+            console.log "#{@self} Save to config: #{fm}"
+            atom.config.set('browse.linuxFileManager', fm);
             return fm

         atom.notifications.addWarning("atom-browse", detail: "No supported file manager detected", dismissable: true)


Support for 'label' property has been removed

--- style.coffee
+++ style.coffee
@@ -305,8 +305,6 @@
 			item.$
 		else if 'text' of item
 			item
-		else if item.label? and 'text' of item.label
-			item.label

 	updateText: ->
 		if @waiting or @children.length


Added tests for ObjectID conversion

Added tests for ObjectID conversion

--- test/mongo-watch.coffee
+++ test/mongo-watch.coffee
@@ -45,13 +45,23 @@
         should.not.exist err

   it 'pretty format should work', (done) ->
-    @watcher = new MongoWatch {format: 'pretty'}
+    @watcher = new MongoWatch {format: 'pretty', convertObjectIDs: true}
     @watcher.watch 'test.users', (event) ->
+      event.operation.should.eql 'insert'
+      should.exist event.data?.email
+      event.data._id.constructor.name.should.eql 'String'
+      event.data.email.should.eql 'graham@daventry.com'
+      done()

-      if event.operation is 'insert'
-        should.exist event.data?.email
-        event.data.email.should.eql 'graham@daventry.com'
-        done()
+    @watcher.ready =>
+      @users.insert {email: 'graham@daventry.com'}, (err, status) ->
+        should.not.exist err
+
+  it 'pretty format should optionally convert ObjectIDs', (done) ->
+    @watcher = new MongoWatch {format: 'pretty', convertObjectIDs: false}
+    @watcher.watch 'test.users', (event) ->
+      event.data._id.constructor.name.should.eql 'ObjectID'
+      done()

     @watcher.ready =>
       @users.insert {email: 'graham@daventry.com'}, (err, status) ->


add `make_request` to reduce boilerplate code

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -9,6 +9,7 @@
 _.mixin(require 'underscore.deep')

 API_BASE = 'https://api.clever.com'
+CLEVER_BASE = 'https://clever.com'

 handle_errors = (resp, body, cb) ->
   return cb?(null, resp, body) if resp.statusCode is 200
@@ -23,6 +24,17 @@
   else if auth.token?
     http_opts.headers ?= {}
     _(http_opts.headers).extend Authorization: "Bearer #{auth.token}"
+
+make_request = (opts, cb) ->
+  p = Q.defer()
+  quest opts, (err, resp, body) ->
+    handle_errors resp, body, (err, resp, body) ->
+      p.reject err if err
+      p.resolve body?.data
+      return cb?(err) if err
+      cb?(err, body?.data)
+  return cb if _.isFunction(cb)
+  p.promise

 Clever = module.exports = (auth, url_base=API_BASE, options={}) ->
   throw new Error 'Must provide auth' if not auth
@@ -300,24 +312,43 @@
       cb?(err, body?.data)
   return promise.promise

-Clever.oauth_tokens = (client_id, client_secret, optional..., cb) ->
-  promise = Q.defer()
-  owner_type = optional?.owner_type
-  owner_type ?= 'district'
-  url_base = options?.url_base
-  url_base ?= 'https://clever.com'
-  path = optional?.path
-  path ?= '/oauth/tokens'
-  opts =
-    method: 'get'
-    ca: certs
-    json: true
-    auth: "#{client_id}:#{client_secret}"
-    uri: "#{url_base}#{path}?owner_type=#{owner_type}"
-  quest opts, (err, resp, body) ->
-    handle_errors resp, body, (err, resp, body) ->
-      promise.reject err if err
-      promise.resolve body?.data
-      return cb?(err) if err
-      cb?(err, body?.data)
-  return promise.promise
+
+Clever.OAuth = class OAuth
+  @url_base: CLEVER_BASE
+  @tokens_path: '/oauth/tokens'
+  @info_path: '/oauth/tokeninfo'
+
+  @tokens: (client_id, client_secret, optional..., cb) ->
+    owner_type = optional[0]
+    owner_type ?= 'district'
+    opts =
+      method: 'get'
+      ca: certs
+      json: true
+      auth: "#{client_id}:#{client_secret}"
+      uri: "#{@url_base}#{@tokens_path}?owner_type=#{owner_type}"
+    make_request opts, cb
+
+  @token: (client_id, client_secret, code, redirect_uri, cb) ->
+    opts =
+      auth: "#{client_id}:#{client_secret}"
+      ca: certs
+      method: 'post'
+      uri: "#{@base_url}#{@tokens_path}"
+      json:
+        grant_type: 'authorization_code'
+        code: code
+        redirect_uri: redirect_uri
+    make_request opts, cb
+
+  @tokenInfo: (token, cb) ->
+    auth = {token: token} if _.isString(token)
+    auth ?= {token: token.access_token or token.token}
+    opts =
+      json: true
+      ca: certs
+      method: 'get'
+      uri: "#{@url_base}#{@info_path}"
+    apply_auth auth, opts
+    console.log opts.uri, opts.headers
+    make_request opts, cb


Added tracker to the Redmine notification plugin

Tracker is added as an optional field. If no tracker is supplied, `nil` will be sent.

--- plugins/redmine-issues/index.coffee
+++ plugins/redmine-issues/index.coffee
@@ -12,14 +12,19 @@
   # The issue priority registry api location
   @priorityUrl: (config) ->
     url.resolve config.host, "/enumerations/issue_priorities.json?key=#{config.apiKey}"
+
+  # The issue tracker registry api location
+  @trackerUrl: (config) ->
+    url.resolve config.host, "/trackers.json?key=#{config.apiKey}"

   # The issue JSON body (creation payload)
-  @payloadJson: (config, event, priorityId) ->
+  @payloadJson: (config, event, priorityId, trackerId=nil) ->
     issue:
       project_id: config.project
       subject: @title(event)
       description: @render(event)
-      priority_id: priorityId
+      priority_id: priorityId,
+      tracker_id: trackerId

   # Fetch the priority id by it's pretty name
   @fetchPriorityId: (config, priorityName, callback) ->
@@ -32,6 +37,17 @@
             return
         callback Error "Priority not found with name '#{priorityName}'"

+# Fetch the tracker id by it's pretty name
+  @fetchTrackerId: (config, trackerId, callback) ->
+    @request
+      .get @trackerUrl config
+      .end (res) ->
+        for tracker in res.body.trackers
+          if tracker.name.toLowerCase() is trackerId.toLowerCase()
+            callback null, tracker.id
+            return
+        callback Error "Tracker not found with name '#{trackerId}'"
+
   @receiveEvent = (config, event, callback) ->
     return if event?.trigger?.type == "reopened"

@@ -50,18 +66,23 @@
         url: url.resolve(config.host, "/issues/#{res.body.issue.id}")

     # Send out the request to the issues API
-    handleRequest = (priorityId) =>
+    handleRequest = (priorityId, trackerId) =>
       @request
         .post @issuesUrl(config)
         .type 'json' # Redmine JSON body
-        .send @payloadJson config, event, priorityId
+        .send @payloadJson config, event, priorityId, trackerId
         .on 'error', handleError
         .end handleCallback

-    # Fetch the priority id, and then handle the request
+    # Fetch the priority and tracker id, and then handle the request
     @fetchPriorityId config, config.priority, (err, priorityId) ->
       return callback err if err
-      handleRequest priorityId
+      unless config.tracker.nil?
+        @fetchTrackerId config, config.tracker (err, trackerId) -->
+          return callback err if err
+          handleRequest priorityId trackerId
+      else
+        handleRequest priorityId

   # Redmine flavoured message information
   # http://www.redmine.org/help/en/wiki_syntax.html


Add CoffeeScript describe and reorganize

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -10,34 +10,38 @@
 #
 # '.source.coffee':
 #   'Console log':
-#     'prefix': 'log'
-#     'body': 'console.log $1'
+#     prefix: 'log'
+#     body: 'console.log $1'
 #
-'.source.ruby':
+.source.coffee:
+  Describe:
+    prefix: 'des'
+    body: "describe '$1', ->$0"
+.source.ruby:
+  Before:
+    prefix: 'bef'
+    body: 'before { $0 }'
+  Describe:
+    prefix: 'desc'
+    body: "describe $1 do\n$0\nend"
   'FactoryGirl Build':
-    'prefix': 'fgb'
-    'body': 'FactoryGirl.build($1)$0'
+    prefix: 'fgb'
+    body: 'FactoryGirl.build($1)$0'
   'FactoryGirl Build Stubbed':
-    'prefix': 'fgbs'
-    'body': 'FactoryGirl.build_stubbed($1)$0'
+    prefix: 'fgbs'
+    body: 'FactoryGirl.build_stubbed($1)$0'
   'FactoryGirl Create':
-    'prefix': 'fgc'
-    'body': 'FactoryGirl.create($1)$0'
+    prefix: 'fgc'
+    body: 'FactoryGirl.create($1)$0'
+  It:
+    prefix: 'it'
+    body: 'it { $0 }'
   'It do':
-    'prefix': 'itd'
-    'body': "it '$1' do\n$0\nend"
-  'Describe':
-    'prefix': 'desc'
-    'body': "describe $1 do\n$0\nend"
-  'Let':
-    'prefix': 'let'
-    'body': 'let($1) { $0 }'
-  'It':
-    'prefix': 'it'
-    'body': 'it { $0 }'
-  'Subject':
-    'prefix': 'subj'
-    'body': 'subject($1) { $0 }'
-  'Before':
-    'prefix': 'bef'
-    'body': 'before { $0 }'
+    prefix: 'itd'
+    body: "it '$1' do\n$0\nend"
+  Let:
+    prefix: 'let'
+    body: 'let($1) { $0 }'
+  Subject:
+    prefix: 'subj'
+    body: 'subject($1) { $0 }'


Change while snippet default conditions; remove C#

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -4,7 +4,6 @@

 '.source.c,
  .source.cpp,
- .source.cs,
  .source.java,
  .source.js':

@@ -28,7 +27,7 @@

   'while':
     'prefix': 'while'
-    'body': "while (${1:true}) {\n\t$2\n}"
+    'body': "while (${1:condition}) {\n\t$2\n}"

   # Error handling

@@ -175,7 +174,7 @@

   'while':
     'prefix': 'while'
-    'body': "while ${1:True}:\n\t${2:pass}"
+    'body': "while ${1:condition}:\n\t${2:pass}"

   'try':
     'prefix': 'try'
@@ -247,7 +246,7 @@

   'while':
     'prefix': 'while'
-    'body': "while ( ${1:true} ) {\n\t$2\n}"
+    'body': "while ( ${1:condition} ) {\n\t$2\n}"

   'array function call':
     'prefix': 'fca'


Add tap stop at end of docstring snippet


Allow future dates in timeago

Allow future dates in timeago

Although not something that can happen in normal application usage
(without something going a bit wrong) this is a bit of an issue if you
have seeded the database with future PRs. This change of setting
resolves this issue.

--- app/assets/javascripts/bootstrap.js.coffee
+++ app/assets/javascripts/bootstrap.js.coffee
@@ -6,6 +6,8 @@
   $("[data-provide=typeahead]").typeahead
     name: $(this).attr "name"
     local: projectLanguages
+
+jQuery.timeago.settings.allowFuture = true;

 jQuery ->
   $("a[rel=popover]").popover()


added "padding" value to .x() and .y() label property

--- src/viz/methods/helpers/axis.coffee
+++ src/viz/methods/helpers/axis.coffee
@@ -32,6 +32,7 @@
     color:      "#444"
     decoration: decoration()
     family:     family()
+    padding:    3
     size:       12
     transform:  transform()
     weight:     200
@@ -81,7 +82,7 @@
   mute:  filter(true)
   padding:
     accepted: [Number]
-    value:    0.1
+    value: 0.1
   range:
     accepted: [false, Array]
     value:    false


include umobi dom

include umobi dom

--- js/umobi.main.coffee
+++ js/umobi.main.coffee
@@ -2,7 +2,9 @@
 define [
   "jquery"
   "cs!umobi.core"
+  "umobi.dom"
   "cs!umobi.page"
+  "cs!umobi.widget"
   "cs!umobi.navigation"
 ], ($, uMobi)->
   # Page Initialization


OX-120 debug

OX-120 debug

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -159,6 +159,9 @@
       unless lastPosition
         lastPosition = [0, 0, 0]

+      unless direction
+        direction = [1, 0, 0]
+
       @lastPosition = position
       @lastZoomSteps = zoomSteps.slice(0)
       @lastDirection = direction
@@ -187,19 +190,21 @@
         position[1] - lastPosition[1],
         position[2] - lastPosition[2]
       ]
-
-      directionMax = Math.abs(Math.max(newDirection[0], newDirection[1], newDirection[2]))
-      newDirectionMax = [
-          newDirection[0] / directionMax,
-          newDirection[1] / directionMax,
-          newDirection[2] / directionMax
-        ]

       direction = [
         direction[0] * 0.8 + newDirection[0] * 0.2,
         direction[1] * 0.8 + newDirection[1] * 0.2,
         direction[2] * 0.8 + newDirection[2] * 0.2
       ]
+
+      directionMax = Math.abs(Math.max(direction[0], direction[1], direction[2]))
+
+      unless directionMax = 0
+        direction = [
+          direction[0] / directionMax,
+          direction[1] / directionMax,
+          direction[2] / directionMax
+        ]

       delta_x = delta_y = delta_z = 0
       direction_x = direction_y = direction_z = 0


OX-120 debug

OX-120 debug

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -205,7 +205,7 @@
         loadDirection[1] /= directionMax;
         loadDirection[2] /= directionMax;

-      console.log loadDirection
+      console.log direction, loadDirection

       delta_x = delta_y = delta_z = 0
       direction_x = direction_y = direction_z = 0


fix resetting bar graph to zero

fix resetting bar graph to zero

--- app/assets/javascripts/pophealth.js.coffee
+++ app/assets/javascripts/pophealth.js.coffee
@@ -67,8 +67,8 @@
 		if data.population != 0 && data.denominator != 0
 			numerator_width = (data.numerator / data.population) * 100
 			denominator_width = ((data.denominator - data.numerator) / data.population) * 100
-			selector.children("div.tableBarNumerator").animate(width: "#{numerator_width}%")
-			selector.children("div.tableBarDenominator").animate(width: "#{denominator_width}%")
+		selector.children("div.tableBarNumerator").animate(width: "#{numerator_width}%")
+		selector.children("div.tableBarDenominator").animate(width: "#{denominator_width}%")
 }

 makeListsExpandable = ->


Make some functions synchronous

Make some functions synchronous

The idea was first to read the configuration asynchronously, but now
it's not, so they can be made sync

--- lib/device.coffee
+++ lib/device.coffee
@@ -12,27 +12,25 @@
     extend config, configDefaults

     # generate namespace string
-    genNS = (callback) ->
+    genNS = ->
         ns = [
             config['device']['schema']['prefix']
             config['device']['version'].split('.')[0]
             config['device']['version'].split('.')[1]
         ]
-        ns = ns.join('-')
-        callback ns
+        ns.join('-')

     # build spec version element
-    buildSpecVersion = (callback) ->
+    buildSpecVersion = ->
         major = config['device']['version'].split('.')[0]
         minor = config['device']['version'].split('.')[1]
-        specVersion = [
+        [
             { major: major }
             { minor: minor }
         ]
-        callback specVersion

     # build device element
-    buildDevice = (callback) ->
+    buildDevice = ->
         type = [
             config['device']['schema']['prefix']
             config['device']['type']
@@ -43,23 +41,20 @@
         url = config['app']['url']
         version = config['app']['version']
         udn = 'uuid:' + config['uuid']
-        d = []
-        d.push { deviceType: type }
-        d.push { friendlyName: name }
-        d.push { manufacturer: name }
-        d.push { modelName: name + ' Media Server' }
-        d.push { UDN: udn }
-        callback d
+        [
+            { deviceType: type }
+            { friendlyName: name }
+            { manufacturer: name }
+            { modelName: name + ' Media Server' }
+            { UDN: udn }
+        ]

-    genNS (xmlns) ->
-        root = xml.Element { _attr: { xmlns: xmlns } }
-        desc = xml { root: root }, { stream: true }
-        desc.pipe response
+    root = xml.Element { _attr: { xmlns: genNS() } }
+    desc = xml { root: root }, { stream: true }
+    desc.pipe response

-        process.nextTick ->
-            buildSpecVersion (specVersionEl) ->
-                root.push { specVersion: specVersionEl }
-                buildDevice (deviceEl) ->
-                    root.push { device: deviceEl }
-                    root.close()
-                    callback()
+    process.nextTick ->
+        root.push { specVersion: buildSpecVersion() }
+        root.push { device: buildDevice() }
+        root.close()
+        callback()


token input: only return relevant tokens

fixes 8816

Test plan:
* as a teacher, open canvasations
* use the address book (don't search) to compose a message to a student
  - you should see a 'add as a faculty journal entry' option
  - you should not see the 'this is a group conversation' option

Change-Id: I1c43a9a721dca0de58d91db5a40d9fa53b3c43e9
Reviewed-on: https://gerrit.instructure.com/11746
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Jon Jensen <44f878afe53efc66b76772bd845eb65944ed8232@instructure.com>

--- app/coffeescripts/widget/TokenInput.coffee
+++ app/coffeescripts/widget/TokenInput.coffee
@@ -175,7 +175,7 @@
         [$li.find('input').val(), $li.find('div').attr('title')]

     tokenValues: ->
-      input.value for input in @$tokens.find('input')
+      input.value for input in @$tokens.find("[name='#{@nodeName}[]']")

     inputKeyUp: (e) ->
       @reposition()


removed in progress stuff for travis-ci and grunt testing

--- marktab.coffee
+++ marktab.coffee
@@ -26,14 +26,12 @@
 	parse: (input) ->
 		lines = input.split("\n")
 		for line in lines
-			chars = line.split("")
+			parts = line.split(" ")
 			tabMap = {}
-			for char, i in chars
+			for part, i in parts
 				tabMapPart = {}
-				if char.match(chordStart)
+				if part.match(chordStart)
 					# chord
-					#TODO fix this up and make it work. need to increment i to chordEndIdx?
-					part = this.chomp(line, i+1, chordEnd)
 					tabMapPart = this.parseChord(part)
 				else if part.match(notePattern)
 					# note


Added identifying keys to ChoiceInput

Added identifying keys to ChoiceInput

--- lib/inputs.coffee
+++ lib/inputs.coffee
@@ -143,9 +143,8 @@
     , months

   renderDaySelector: ->
-    days = [
-      1..daysInMonth(@state.value.getMonth() + 1, @state.value.getFullYear())
-    ]
+    fullYear = @state.value.getFullYear()
+    days = [1..daysInMonth(@state.value.getMonth() + 1, fullYear)]
     dayOptions = _.map days, (day) -> option value: day, key: day, day
     return select
       className: "input-date-day"
@@ -188,6 +187,7 @@
     btnClass = @props.btnClass or ""
     div className: "group", _.map @props.choices, (title, choice) =>
       button
+        key: choice
         onClick: => @onChoiceSelect choice
         className: btnClass + " " + cx active: @state.value is choice
       , title


:art: Associate Bubble to Linter

:art: Associate Bubble to Linter

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -1,6 +1,7 @@
 Path = require 'path'
 {CompositeDisposable, Emitter} = require 'atom'
 LinterView = require './linter-view'
+Bubble = require './bubble'
 Panel = require './panel'
 PanelView = require './panel-view'
 Bottom = require './bottom'
@@ -15,6 +16,7 @@
     @Emitter = new Emitter
     @View = new LinterView this
     @Bottom = new Bottom this
+    @Bubble = new Bubble this
     @StatusBar = null
     @MessagesProject = new Map
     @ActiveEditor = atom.workspace.getActiveTextEditor()


hub/auth: missing return after early cb()

--- src/smc-hub/auth.coffee
+++ src/smc-hub/auth.coffee
@@ -263,7 +263,7 @@

         (cb) ->
             if locals.new_account_created
-                cb()
+                cb(); return
             sign_in.record_sign_in
                 ip_address    : opts.req.ip
                 successful    : true


function parsing

function parsing

--- lib/napalm.coffee
+++ lib/napalm.coffee
@@ -1,6 +1,7 @@
 NapalmView = require './napalm-view'
 {CompositeDisposable} = require 'atom'
 Acorn = require 'acorn'
+Escodegen = require 'escodegen'

 module.exports = Napalm =
   napalmView: null
@@ -17,6 +18,7 @@
     # Register command that toggles this view
     @subscriptions.add atom.commands.add 'atom-workspace', 'napalm:toggle': => @toggle()
     window.Acorn = Acorn
+    window.Escodegen = Escodegen

   deactivate: ->
     @modalPanel.destroy()
@@ -29,3 +31,25 @@
   toggle: ->
     if editor = atom.workspace.getActiveTextEditor()
       selection = editor.getSelectedText()
+      ast = Acorn.parse selection
+      if func = @findFunction(ast)
+        console.log window.func = func
+        text = Escodegen.generate(func.node)
+        console.log("module.exports = " + text)
+
+
+  findFunction: (node) ->
+    if node.type is 'FunctionDeclaration'
+      name = node.id.name
+      node.id.name = ''
+      return name: name, node: node
+    else if node.type is 'VariableDeclaration' and node.declarations.length == 1
+      declaration = node.declarations[0]
+      if declaration.type is 'VariableDeclarator' and declaration.init.type is 'FunctionExpression'
+        return name: declaration.id.name, node: declaration.init
+    else if node.type is 'Program'
+      for n in node.body
+        if result = @findFunction(n)
+          return result
+
+    return null


Fixed tests for Node < 0.11.

Fixed tests for Node < 0.11.

--- test/suite/CouchBuilder.spec.coffee
+++ test/suite/CouchBuilder.spec.coffee
@@ -1,5 +1,6 @@
 fs = require 'fs'
 path = require 'path'
+Promise = require 'bluebird'

 CouchBuilder = require '../../src/CouchBuilder'


Clean up

Clean up

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -80,11 +80,9 @@
   #   * `which`  A {Number} indicating `which` value of the event. See
   #     the docs for KeyboardEvent for more information.
   #   * `target` The target element of the event.
-  KeymapManager.buildKeydownEvent = (key, options) ->
-    keydownEvent(key, options)
-
-  KeymapManager.buildKeyupEvent = (key, options) ->
-    keyupEvent(key, options)
+  @buildKeydownEvent: (key, options) -> keydownEvent(key, options)
+
+  @buildKeyupEvent: (key, options) -> keyupEvent(key, options)

   ###
   Section: Properties
@@ -110,30 +108,30 @@
   #     is `document.body` to allow for a catch-all element when nothing is focused.
   constructor: (options={}) ->
     @[key] = value for key, value of options
-    this.watchSubscriptions = {}
-    this.clear()
-    this.enableDvorakQwertyWorkaroundIfNeeded()
+    @watchSubscriptions = {}
+    @clear()
+    @enableDvorakQwertyWorkaroundIfNeeded()

   # Public: Clear all registered key bindings and enqueued keystrokes. For use
   # in tests.
   clear: ->
-    this.emitter = new Emitter
-    this.keyBindings = []
-    this.queuedKeyboardEvents = []
-    this.queuedKeystrokes = []
-    this.bindingsToDisable = []
+    @emitter = new Emitter
+    @keyBindings = []
+    @queuedKeyboardEvents = []
+    @queuedKeystrokes = []
+    @bindingsToDisable = []

   # Public: Unwatch all watched paths.
   destroy: ->
-    this.keyboardLayoutSubscription.dispose()
-    for filePath, subscription of this.watchSubscriptions
+    @keyboardLayoutSubscription.dispose()
+    for filePath, subscription of @watchSubscriptions
       subscription.dispose()

     return

   enableDvorakQwertyWorkaroundIfNeeded: ->
-    this.keyboardLayoutSubscription = observeCurrentKeyboardLayout (layoutId) =>
-      this.dvorakQwertyWorkaroundEnabled = (layoutId?.indexOf('DVORAK-QWERTYCMD') > -1)
+    @keyboardLayoutSubscription = observeCurrentKeyboardLayout (layoutId) =>
+      @dvorakQwertyWorkaroundEnabled = (layoutId?.indexOf('DVORAK-QWERTYCMD') > -1)

   ###
   Section: Event Subscription
@@ -151,7 +149,7 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidMatchBinding: (callback) ->
-    this.emitter.on 'did-match-binding', callback
+    @emitter.on 'did-match-binding', callback

   # Public: Invoke the given callback when one or more keystrokes partially
   # match a binding.
@@ -167,7 +165,7 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidPartiallyMatchBindings: (callback) ->
-    this.emitter.on 'did-partially-match-binding', callback
+    @emitter.on 'did-partially-match-binding', callback

   # Public: Invoke the given callback when one or more keystrokes fail to match
   # any bindings.
@@ -181,7 +179,7 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidFailToMatchBinding: (callback) ->
-    this.emitter.on 'did-fail-to-match-binding', callback
+    @emitter.on 'did-fail-to-match-binding', callback

   # Invoke the given callback when a keymap file is reloaded.
   #
@@ -191,7 +189,7 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidReloadKeymap: (callback) ->
-    this.emitter.on 'did-reload-keymap', callback
+    @emitter.on 'did-reload-keymap', callback

   # Invoke the given callback when a keymap file is unloaded.
   #
@@ -201,7 +199,7 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidUnloadKeymap: (callback) ->
-    this.emitter.on 'did-unload-keymap', callback
+    @emitter.on 'did-unload-keymap', callback

   # Public: Invoke the given callback when a keymap file not able to be loaded.
   #
@@ -212,7 +210,7 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidFailToReadFile: (callback) ->
-    this.emitter.on 'did-fail-to-read-file', callback
+    @emitter.on 'did-fail-to-read-file', callback

   ###
   Section: Adding and Removing Bindings
@@ -244,18 +242,18 @@
         if normalizedKeystrokes = normalizeKeystrokes(keystrokes)
           keyBinding = new KeyBinding(source, command, normalizedKeystrokes, selector, priority)
           addedKeyBindings.push(keyBinding)
-          this.keyBindings.push(keyBinding)
+          @keyBindings.push(keyBinding)
         else
           console.warn "Invalid keystroke sequence for binding: `#{keystrokes}: #{command}` in #{source}"

     new Disposable =>
       for keyBinding in addedKeyBindings
-        index = this.keyBindings.indexOf(keyBinding)
-        this.keyBindings.splice(index, 1) unless index is -1
+        index = @keyBindings.indexOf(keyBinding)
+        @keyBindings.splice(index, 1) unless index is -1
       return

   removeBindingsFromSource: (source) ->
-    this.keyBindings = this.keyBindings.filter (keyBinding) -> keyBinding.source isnt source
+    @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source
     undefined

   ###
@@ -266,7 +264,7 @@
   #
   # Returns an {Array} of {KeyBinding}s.
   getKeyBindings: ->
-    this.keyBindings.slice()
+    @keyBindings.slice()

   # Public: Get the key bindings for a given command and optional target.
   #
@@ -283,7 +281,7 @@
   findKeyBindings: (params={}) ->
     {keystrokes, command, target, keyBindings} = params

-    bindings = keyBindings ? this.keyBindings
+    bindings = keyBindings ? @keyBindings

     if command?
       bindings = bindings.filter (binding) -> binding.command is command
@@ -321,11 +319,11 @@
     checkIfDirectory = options?.checkIfDirectory ? true
     if checkIfDirectory and fs.isDirectorySync(bindingsPath)
       for filePath in fs.listSync(bindingsPath, ['.cson', '.json'])
-        if this.filePathMatchesPlatform(filePath)
-          this.loadKeymap(filePath, checkIfDirectory: false)
+        if @filePathMatchesPlatform(filePath)
+          @loadKeymap(filePath, checkIfDirectory: false)
     else
-      this.add(bindingsPath, this.readKeymap(bindingsPath, options?.suppressErrors), options?.priority)
-      this.watchKeymap(bindingsPath, options) if options?.watch
+      @add(bindingsPath, @readKeymap(bindingsPath, options?.suppressErrors), options?.priority)
+      @watchKeymap(bindingsPath, options) if options?.watch

     undefined

@@ -341,10 +339,10 @@
   #   * `priority` A {Number} used to sort keybindings which have the same
   #     specificity.
   watchKeymap: (filePath, options) ->
-    if not this.watchSubscriptions[filePath]? or this.watchSubscriptions[filePath].disposed
+    if not @watchSubscriptions[filePath]? or @watchSubscriptions[filePath].disposed
       file = new File(filePath)
-      reloadKeymap = => this.reloadKeymap(filePath, options)
-      this.watchSubscriptions[filePath] = new CompositeDisposable(
+      reloadKeymap = => @reloadKeymap(filePath, options)
+      @watchSubscriptions[filePath] = new CompositeDisposable(
         file.onDidChange(reloadKeymap)
         file.onDidRename(reloadKeymap)
         file.onDidDelete(reloadKeymap)
@@ -356,15 +354,15 @@
   # we can't read the file cleanly, we don't proceed with the reload.
   reloadKeymap: (filePath, options) ->
     if fs.isFileSync(filePath)
-      bindings = this.readKeymap(filePath, true)
+      bindings = @readKeymap(filePath, true)

       if typeof bindings isnt "undefined"
-        this.removeBindingsFromSource(filePath)
-        this.add(filePath, bindings, options?.priority)
-        this.emitter.emit 'did-reload-keymap', {path: filePath}
+        @removeBindingsFromSource(filePath)
+        @add(filePath, bindings, options?.priority)
+        @emitter.emit 'did-reload-keymap', {path: filePath}
     else
-      this.removeBindingsFromSource(filePath)
-      this.emitter.emit 'did-unload-keymap', {path: filePath}
+      @removeBindingsFromSource(filePath)
+      @emitter.emit 'did-unload-keymap', {path: filePath}

   readKeymap: (filePath, suppressErrors) ->
     if suppressErrors
@@ -372,7 +370,7 @@
         CSON.readFileSync(filePath, allowDuplicateKeys: false)
       catch error
         console.warn("Failed to reload key bindings file: #{filePath}", error.stack ? error)
-        this.emitter.emit 'did-fail-to-read-file', error
+        @emitter.emit 'did-fail-to-read-file', error
         undefined
     else
       CSON.readFileSync(filePath, allowDuplicateKeys: false)
@@ -382,7 +380,7 @@
   # <platform> does not match the current platform, returns false. Otherwise
   # returns true.
   filePathMatchesPlatform: (filePath) ->
-    otherPlatforms = this.getOtherPlatforms()
+    otherPlatforms = @getOtherPlatforms()
     for component in path.basename(filePath).split('.')[0...-1]
       return false if component in otherPlatforms
     true
@@ -428,8 +426,8 @@
     # of the keystrokes the user has typed. When populated, the state variables
     # look something like:
     #
-    # this.queuedKeystrokes = ['ctrl-a', 'b', 'c']
-    # this.queuedKeyboardEvents = [KeyboardEvent, KeyboardEvent, KeyboardEvent]
+    # @queuedKeystrokes = ['ctrl-a', 'b', 'c']
+    # @queuedKeyboardEvents = [KeyboardEvent, KeyboardEvent, KeyboardEvent]
     #
     # Basically, this `handleKeyboardEvent` function will try to exactly match
     # the user's keystrokes to a binding. If it cant match exactly, it looks for
@@ -437,8 +435,8 @@
     # The `ctrl-a b c` binding would be partially matched:
     #
     # // The original binding: 'ctrl-a b c': 'my-sweet-command'
-    # this.queuedKeystrokes = ['ctrl-a', 'b'] // The user's keystrokes
-    # this.queuedKeyboardEvents = [KeyboardEvent, KeyboardEvent]
+    # @queuedKeystrokes = ['ctrl-a', 'b'] // The user's keystrokes
+    # @queuedKeyboardEvents = [KeyboardEvent, KeyboardEvent]
     #
     # When it finds partially matching bindings, it will put the KeymapManager
     # into a pending state via `enterPendingState` indicating that it is waiting
@@ -452,8 +450,8 @@
     # `terminatePendingState`. An extension of our last example:
     #
     # // Both of these will exit pending state for: 'ctrl-a b c': 'my-sweet-command'
-    # this.queuedKeystrokes = ['ctrl-a', 'b', 'c'] // User typed `c`. Exact match! Dispatch the command and clear state variables. Easy.
-    # this.queuedKeystrokes = ['ctrl-a', 'b', 'd'] // User typed `d`. No hope of matching, terminatePendingState(). Dragons.
+    # @queuedKeystrokes = ['ctrl-a', 'b', 'c'] // User typed `c`. Exact match! Dispatch the command and clear state variables. Easy.
+    # @queuedKeystrokes = ['ctrl-a', 'b', 'd'] // User typed `d`. No hope of matching, terminatePendingState(). Dragons.
     #
     # `terminatePendingState` is where things get crazy. Let's pretend the user
     # typed 3 total keystrokes: `ctrl-a`, `b`, then `d`. There are no exact
@@ -492,36 +490,36 @@
     #
     # Godspeed.

-    keystroke = this.keystrokeForKeyboardEvent(event)
+    keystroke = @keystrokeForKeyboardEvent(event)

     # We dont care about bare modifier keys in the bindings. e.g. `ctrl y` isnt going to work.
-    if event.type is 'keydown' and this.queuedKeystrokes.length > 0 and isBareModifier(keystroke)
+    if event.type is 'keydown' and @queuedKeystrokes.length > 0 and isBareModifier(keystroke)
       event.preventDefault()
       return

-    this.queuedKeystrokes.push(keystroke)
-    this.queuedKeyboardEvents.push(event)
-    keystrokes = this.queuedKeystrokes.join(' ')
+    @queuedKeystrokes.push(keystroke)
+    @queuedKeyboardEvents.push(event)
+    keystrokes = @queuedKeystrokes.join(' ')

     # If the event's target is document.body, assign it to defaultTarget instead
     # to provide a catch-all element when nothing is focused.
     target = if replay then document.activeElement else event.target
-    target = this.defaultTarget if event.target is document.body and this.defaultTarget?
+    target = @defaultTarget if event.target is document.body and @defaultTarget?

     # First screen for any bindings that match the current keystrokes,
     # regardless of their current selector. Matching strings is cheaper than
     # matching selectors.
-    {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates} = this.findMatchCandidates(this.queuedKeystrokes, disabledBindings)
+    {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates} = @findMatchCandidates(@queuedKeystrokes, disabledBindings)
     dispatchedExactMatch = null
-    partialMatches = this.findPartialMatches(partialMatchCandidates, target)
+    partialMatches = @findPartialMatches(partialMatchCandidates, target)

     # If any partial match *was* pending but has now failed to match, add it to
     # the list of bindings to disable so we don't attempt to match it again
     # during a subsequent event replay by `terminatePendingState`.
-    if this.pendingPartialMatches?
+    if @pendingPartialMatches?
       liveMatches = new Set(partialMatches.concat(exactMatchCandidates))
-      for binding in this.pendingPartialMatches
-        this.bindingsToDisable.push(binding) unless liveMatches.has(binding)
+      for binding in @pendingPartialMatches
+        @bindingsToDisable.push(binding) unless liveMatches.has(binding)

     hasPartialMatches = partialMatches.length > 0
     shouldUsePartialMatches = hasPartialMatches
@@ -535,7 +533,7 @@
       currentTarget = target
       eventHandled = false
       while not eventHandled and currentTarget? and currentTarget isnt document
-        exactMatches = this.findExactMatches(exactMatchCandidates, currentTarget)
+        exactMatches = @findExactMatches(exactMatchCandidates, currentTarget)
         for exactMatchCandidate in exactMatches
           if exactMatchCandidate.command is 'native!'
             shouldUsePartialMatches = false
@@ -563,7 +561,7 @@
           else
             shouldUsePartialMatches = false

-          if this.dispatchCommandEvent(exactMatchCandidate.command, target, event)
+          if @dispatchCommandEvent(exactMatchCandidate.command, target, event)
             dispatchedExactMatch = exactMatchCandidate
             eventHandled = true
             break
@@ -572,7 +570,7 @@
     # Emit events. These are done on their own for clarity.

     if dispatchedExactMatch?
-      this.emitter.emit 'did-match-binding', {
+      @emitter.emit 'did-match-binding', {
         keystrokes,
         eventType: event.type,
         binding: dispatchedExactMatch,
@@ -580,14 +578,14 @@
       }
     else if hasPartialMatches and shouldUsePartialMatches
       event.preventDefault()
-      this.emitter.emit 'did-partially-match-binding', {
+      @emitter.emit 'did-partially-match-binding', {
         keystrokes,
         eventType: event.type,
         partiallyMatchedBindings: partialMatches,
         keyboardEventTarget: target
       }
     else if not dispatchedExactMatch? and not hasPartialMatches
-      this.emitter.emit 'did-fail-to-match-binding', {
+      @emitter.emit 'did-fail-to-match-binding', {
         keystrokes,
         eventType: event.type,
         keyboardEventTarget: target
@@ -597,27 +595,27 @@
       # whose default action was prevented and no binding is matched, we'll
       # simulate the text input event that was previously prevented to insert
       # the missing characters.
-      this.simulateTextInput(event) if event.defaultPrevented and event.type is 'keydown'
+      @simulateTextInput(event) if event.defaultPrevented and event.type is 'keydown'

     # Manage the keystroke queue state. State is updated separately for clarity.

-    this.bindingsToDisable.push(dispatchedExactMatch) if dispatchedExactMatch
+    @bindingsToDisable.push(dispatchedExactMatch) if dispatchedExactMatch
     if hasPartialMatches and shouldUsePartialMatches
       enableTimeout = (
-        this.pendingStateTimeoutHandle? or
+        @pendingStateTimeoutHandle? or
         dispatchedExactMatch? or
-        characterForKeyboardEvent(this.queuedKeyboardEvents[0])?
+        characterForKeyboardEvent(@queuedKeyboardEvents[0])?
       )
       enableTimeout = false if replay
-      this.enterPendingState(partialMatches, enableTimeout)
-    else if not dispatchedExactMatch? and not hasPartialMatches and this.pendingPartialMatches?
+      @enterPendingState(partialMatches, enableTimeout)
+    else if not dispatchedExactMatch? and not hasPartialMatches and @pendingPartialMatches?
       # There are partial matches from a previous event, but none from this
       # event. This means the current event has removed any hope that the queued
       # key events will ever match any binding. So we will clear the state and
       # start over after replaying the events in `terminatePendingState`.
-      this.terminatePendingState()
+      @terminatePendingState()
     else
-      this.clearQueuedKeystrokes()
+      @clearQueuedKeystrokes()

   # Public: Translate a keydown event to a keystroke string.
   #
@@ -625,21 +623,21 @@
   #
   # Returns a {String} describing the keystroke.
   keystrokeForKeyboardEvent: (event) ->
-    keystrokeForKeyboardEvent(event, this.dvorakQwertyWorkaroundEnabled)
+    keystrokeForKeyboardEvent(event, @dvorakQwertyWorkaroundEnabled)

   # Public: Get the number of milliseconds allowed before pending states caused
   # by partial matches of multi-keystroke bindings are terminated.
   #
   # Returns a {Number}
   getPartialMatchTimeout: ->
-    this.partialMatchTimeout
+    @partialMatchTimeout

   ###
   Section: Private
   ###

   simulateTextInput: (keydownEvent) ->
-    if character = characterForKeyboardEvent(keydownEvent, this.dvorakQwertyWorkaroundEnabled)
+    if character = characterForKeyboardEvent(keydownEvent, @dvorakQwertyWorkaroundEnabled)
       textInputEvent = document.createEvent("TextEvent")
       textInputEvent.initTextEvent("textInput", true, true, window, character)
       keydownEvent.path[0].dispatchEvent(textInputEvent)
@@ -655,7 +653,7 @@
     keydownExactMatchCandidates = []
     disabledBindingSet = new Set(disabledBindings)

-    for binding in this.keyBindings when not disabledBindingSet.has(binding)
+    for binding in @keyBindings when not disabledBindingSet.has(binding)
       doesMatch = keystrokesMatch(binding.keystrokeArray, keystrokeArray)
       if doesMatch is 'exact'
         exactMatchCandidates.push(binding)
@@ -697,20 +695,20 @@
       .sort (a, b) -> a.compare(b)

   clearQueuedKeystrokes: ->
-    this.queuedKeyboardEvents = []
-    this.queuedKeystrokes = []
-    this.bindingsToDisable = []
+    @queuedKeyboardEvents = []
+    @queuedKeystrokes = []
+    @bindingsToDisable = []

   enterPendingState: (pendingPartialMatches, enableTimeout) ->
-    this.cancelPendingState() if this.pendingStateTimeoutHandle?
-    this.pendingPartialMatches = pendingPartialMatches
+    @cancelPendingState() if @pendingStateTimeoutHandle?
+    @pendingPartialMatches = pendingPartialMatches
     if enableTimeout
-      this.pendingStateTimeoutHandle = setTimeout(this.terminatePendingState.bind(this, true), this.partialMatchTimeout)
+      @pendingStateTimeoutHandle = setTimeout(@terminatePendingState.bind(this, true), @partialMatchTimeout)

   cancelPendingState: ->
-    clearTimeout(this.pendingStateTimeoutHandle)
-    this.pendingStateTimeoutHandle = null
-    this.pendingPartialMatches = null
+    clearTimeout(@pendingStateTimeoutHandle)
+    @pendingStateTimeoutHandle = null
+    @pendingPartialMatches = null

   # This is called by {::handleKeyboardEvent} when no matching bindings are
   # found for the currently queued keystrokes or by the pending state timeout.
@@ -725,11 +723,11 @@
   # disabling bindings here and there. See any spec that handles multiple
   # keystrokes failures to match a binding.
   terminatePendingState: (fromTimeout) ->
-    bindingsToDisable = this.pendingPartialMatches.concat(this.bindingsToDisable)
-    eventsToReplay = this.queuedKeyboardEvents
-
-    this.cancelPendingState()
-    this.clearQueuedKeystrokes()
+    bindingsToDisable = @pendingPartialMatches.concat(@bindingsToDisable)
+    eventsToReplay = @queuedKeyboardEvents
+
+    @cancelPendingState()
+    @clearQueuedKeystrokes()

     keyEventOptions =
       replay: true
@@ -737,13 +735,13 @@

     for event in eventsToReplay
       keyEventOptions.disabledBindings = bindingsToDisable
-      this.handleKeyboardEvent(event, keyEventOptions)
+      @handleKeyboardEvent(event, keyEventOptions)

       # We can safely re-enable the bindings when we no longer have any partial matches
-      bindingsToDisable = null if bindingsToDisable? and not this.pendingPartialMatches?
-
-    if fromTimeout and this.pendingPartialMatches?
-      this.terminatePendingState(true)
+      bindingsToDisable = null if bindingsToDisable? and not @pendingPartialMatches?
+
+    if fromTimeout and @pendingPartialMatches?
+      @terminatePendingState(true)

     return

@@ -760,7 +758,7 @@
     if document.contains(target)
       target.dispatchEvent(commandEvent)
     else
-      this.simulateBubblingOnDetachedTarget(target, commandEvent)
+      @simulateBubblingOnDetachedTarget(target, commandEvent)

     {keyBindingAborted} = commandEvent
     keyboardEvent.preventDefault() unless keyBindingAborted


Replaced building Query in OAuth2 code with the OAuthBase._buildQuery method

--- lib/oauth2.coffee
+++ lib/oauth2.coffee
@@ -35,18 +35,12 @@
 	authorize: (opts, callback) ->
 		@_createState @_provider, opts, (err, state) =>
 			return callback err if err
-
-			authorize = @_provider.oauth2.authorize
-			query = {}
-			if typeof opts.options?.authorize == 'object'
-				query = opts.options.authorize
-			hard_params = { state: state.id, callback: config.host_url + config.relbase }
-			for name, value of authorize.query
-				param = @_replaceParam value, hard_params
-				query[name] = param if param
-			url = @_replaceParam authorize.url, {}
-			url += "?" + querystring.stringify query
-			callback null, url:url, state:state.id
+			configuration = @_provider.oauth2.authorize
+			placeholderValues = { state: state.id, callback: config.host_url + config.relbase }
+			query = @_buildQuery(configuration.query, placeholderValues, opts.options?.authorize)
+			url = @_replaceParam(configuration.url, {})
+			url += "?" + querystring.stringify(query)
+			callback null, { url: url, state: state.id }

 	access_token: (state, req, response_type, callback) ->
 		# manage errors in callback
@@ -61,20 +55,18 @@
 			return callback err
 		return callback new check.Error 'code', 'unable to find authorize code' if not req.params.code

-		access_token = @_provider.oauth2.access_token
-		query = {}
-		hard_params = { code: req.params.code, state: state.id, callback: config.host_url + config.relbase }
-		for name, value of access_token.query
-			param = @_replaceParam value, hard_params
-			query[name] = param if param
+		configuration = @_provider.oauth2.access_token
+		placeholderValues = { code: req.params.code, state: state.id, callback: config.host_url + config.relbase }
+		query = @_buildQuery(configuration.query, placeholderValues)
+
 		headers = {}
-		headers["Accept"] = @_short_formats[access_token.format] || access_token.format if access_token.format
-		for name, value of access_token.headers
+		headers["Accept"] = @_short_formats[configuration.format] || configuration.format if configuration.format
+		for name, value of configuration.headers
 			param = @_replaceParam value, {}
 			headers[name] = param if param
 		options =
-			url: @_replaceParam access_token.url, {}
-			method: access_token.method?.toUpperCase() || "POST"
+			url: @_replaceParam configuration.url, {}
+			method: configuration.method?.toUpperCase() || "POST"
 			followAllRedirects: true
 			encoding: null

@@ -112,27 +104,25 @@
 					base: @_provider.baseurl
 					request: requestclone
 				result.refresh_token = response.body.refresh_token if response.body.refresh_token && response_type == "code"
-				for extra in (access_token.extra||[])
+				for extra in (configuration.extra||[])
 					result[extra] = response.body[extra] if response.body[extra]
 				for extra in (@_provider.oauth2.authorize.extra||[])
 					result[extra] = req.params[extra] if req.params[extra]
 				callback null, result

 	refresh: (token, callback) ->
-		refresh = @_provider.oauth2.refresh
-		query = {}
-		hard_params = { refresh_token: token }
-		for name, value of refresh.query
-			param = @_replaceParam value, hard_params
-			query[name] = param if param
+		configuration = @_provider.oauth2.refresh
+		placeholderValues = { refresh_token: token }
+		query = @_buildQuery(configuration.query, placeholderValues)
+
 		headers = {}
-		headers["Accept"] = @_short_formats[refresh.format] || refresh.format if refresh.format
-		for name, value of refresh.headers
+		headers["Accept"] = @_short_formats[configuration.format] || configuration.format if configuration.format
+		for name, value of configuration.headers
 			param = @_replaceParam value, { refresh_token: token }
 			headers[name] = param if param
 		options =
-			url: @_replaceParam refresh.url, {}
-			method: refresh.method?.toUpperCase() || "POST"
+			url: @_replaceParam configuration.url, {}
+			method: configuration.method?.toUpperCase() || "POST"
 			followAllRedirects: true

 		options.headers = headers if Object.keys(headers).length
@@ -186,11 +176,9 @@
 		options.url = @_replaceParam options.url, @_parameters.oauthio

 		# build query
-		options.qs = {}
-		options.qs[name] = value for name, value of req.query
-		for name, value of oauthrequest.query
-			param = @_replaceParam value, @_parameters.oauthio
-			options.qs[name] = param if param
+		presetQuery = {}
+		presetQuery[name] = value for name, value of req.query
+		options.qs = @_buildQuery(oauthrequest.query, @_parameters.oauthio, presetQuery)

 		# build headers
 		options.headers =


Add a description where relevant to the og SEO attributes

--- lib/routes.coffee
+++ lib/routes.coffee
@@ -7,7 +7,7 @@
   routeControllerNameConverter: 'upperCamelCase'
   # trackPageView: true

-seo = (title) ->
+seo = (title, description) ->
   t = []
   if title
     t.push(title)
@@ -16,13 +16,15 @@
   document.title = t
   url = document.location.href
   ogImage = 'http://dpk7qq034rxx8.cloudfront.net/img/ogImage.jpg'
+  if not description
+    description = title
   SEO.set
     title: title
     meta:
-      description: title
+      description: description
     og:
       title: title,
-      description: title
+      description: description
       url: url
       image: ogImage
     fb:
@@ -95,7 +97,7 @@
     data: ->
       proposal = Proposal.find(@params.id)
       if not proposal then return null
-      seo(proposal.title)
+      seo(proposal.title, proposal.abstract)
       {page: 'proposal', proposal: proposal}

@@ -158,7 +160,7 @@
     data: ->
       speaker = User.find(@params.id)
       if not speaker then return null
-      seo(speaker.name())
+      seo(speaker.name(), speaker.bio)
       {page: 'speaker', speaker: speaker}

   @route 'speakers',
@@ -199,7 +201,7 @@
     data: ->
       wish = Wishes.findOne(_id: @params.id)
       if not wish then return null
-      seo(wish.title)
+      seo(wish.title, wish.description)
       {page: 'wish', wish: wish}

   @route 's2013',


Bug fix

Bug fix

--- lib/schema.coffee
+++ lib/schema.coffee
@@ -27,7 +27,12 @@

   generateBelongsTo: (model_type, reverse_model_type) ->
     key = inflection.underscore(reverse_model_type.model_name)
-    throw "Schema for '#{model_type.model_name}' already has relation '#{key}'" if @raw[key]
+    if @raw[key] and not @relation(key) # not intitialized yet, intialize now
+      relation = @_parseField(key, @raw[key])
+      relation.initialize()
+      return relation
+
+    # generate new
     relation = @_parseField(key, @raw[key] = ['belongsTo', reverse_model_type, manual_fetch: true])
     relation.initialize()
     return relation


Finish lang switch, add Python

Finish lang switch, add Python

--- lib/script.coffee
+++ lib/script.coffee
@@ -6,14 +6,17 @@
   CoffeeScript:
     interpreter: "coffee"
     makeargs: (code) -> ['-e', code]
+  Python:
+    interpreter: "python"
+    makeargs: (code) -> ['-c', code]

 module.exports =

   activate: ->
     atom.project.registerOpener (uri) =>

-      interpreter = "coffee"
-      makeargs = (code) -> ['-e', code]
+      interpreter = grammarMap[@lang]["interpreter"]
+      makeargs = grammarMap[@lang]["makeargs"]

       @scriptView = new ScriptView(interpreter, makeargs) if uri is configUri

@@ -24,15 +27,15 @@
       if ! code?
         return

-      console.log("Here we go")
+      if ! editor?
+        console.log("Where's my bloody editor?")
+        return

       grammar = editor.getGrammar()
-      lang = grammar.name

-      interpreter = grammarMap[lang]["interpreter"]
-      makeargs = grammarMap[lang]["makeargs"]
+      @lang = grammar.name

-      @scriptView = new ScriptView(interpreter, makeargs)
+      console.log("Here we go")

       atom.workspaceView.open(configUri, split: 'right')
       @scriptView.runit(code)


Unintentially changed functionality. Reverting.

--- lib/sentry.coffee
+++ lib/sentry.coffee
@@ -29,7 +29,8 @@
     else
       @disable_message = "Credentials you passed in aren't complete."

-    @hostname = os.hostname()
+    _.defaults @,
+      hostname: os.hostname()

   error: (err, logger, culprit, extra = {}) =>
     unless err instanceof Error


updated

updated

--- lib/server.coffee
+++ lib/server.coffee
@@ -14,14 +14,14 @@
 kublai.use express.logger('dev')
 #kublai.use(require('less-middleware')({ src: './public/style' }))
 kublai.use express.static('./public')
-client = redis.createClient 10896,'dory.redistogo.com'
-client.auth 'c6ce6017ecd73bdbcbc23b1a1838919f'
+client = redis.createClient(10896,'dory.redistogo.com')
+client.auth('c6ce6017ecd73bdbcbc23b1a1838919f')

 #kublai.set 'json spaces',0
 kublai.get '/data/:year/:set/counties/:tables', (req,res)->
 	res.header "Access-Control-Allow-Origin", "*"
 	res.header "Access-Control-Allow-Headers", "X-Requested-With"
-	client.get req.params.tables, (err,tables)->
+	client.hget req.params.year+req.params.set,req.params.tables, (err,tables)->
 		if tables
 			res.jsonp(JSON.parse(tables))
 		else
@@ -35,7 +35,7 @@
 				qs:qs
 				json:true
 			},(e,r,b)->
-				client.set req.params.tables, JSON.stringify(b)
+				client.hset req.params.year+req.params.set,req.params.tables, JSON.stringify(b)
 				res.jsonp b
 kublai.get '/data/:view', (req,res)->
 	res.header "Access-Control-Allow-Origin", "*"
@@ -44,7 +44,7 @@
 	model = models.filter((m)->
 		return m.name==key)[0]
 	#console.log model
-	client.get key, (err,data)->
+	client.hget 'computed',key, (err,data)->
 		if data
 			res.jsonp(JSON.parse(data))
 		else
@@ -67,7 +67,7 @@
 					val = parseFloat(row[0],10)
 					o[0][row[1]+row[2]]=val
 					o[1].push(val) if val
-				client.set key, JSON.stringify(o)
+				client.hset 'computed',key, JSON.stringify(o)
 				res.jsonp o
 kublai.get '/docs/:year/:set',(req,res)->
 	request "http://www.census.gov/developers/data/#{req.params.set.slice(0,3)}_#{req.params.set.slice(-1)}yr_#{req.params.year}_var.xml",(e,r,b)->


Use the deis specified port if it exists.

--- lib/server.coffee
+++ lib/server.coffee
@@ -8,7 +8,7 @@

   constructor: ->
     @server = new Hapi.Server()
-    @server.connection(port: 4555)
+    @server.connection(process.env.PORT or 4545)
     @_names()

   _names: ->


peillis/helpapp#3455 results widget test for df:rendered event

--- test/test_widget.coffee
+++ test/test_widget.coffee
@@ -128,24 +128,29 @@
       # set up doofinder controller
       client = new doofinder.Client mock.request.hashid, mock.request.api_key,5,null,'fooserver'
       queryInputWidget = new doofinder.widgets.QueryInput '#query'
-      resultsWidget = new doofinder.widgets.Results '#results'
+      @resultsWidget = new doofinder.widgets.Results '#results'
       controller = new doofinder.Controller client
       controller.addWidget queryInputWidget
-      controller.addWidget resultsWidget
+      controller.addWidget @resultsWidget
       # the els in questionr
       @resultsContainer = @$ '#results'
       @queryEl = @$ '#query'

-    it 'display search results', (done) ->
+    it 'display search results and triggers df:rendered', (done) ->
       # when results are received, we check they're the "fake results"
       resultsContainer = @resultsContainer
+      # DOM event is first
       resultsContainer.one 'DOMSubtreeModified', () ->
         resultsContainer.find('li').length.should.be.equal 2
         resultsContainer.find('li b')[0].innerHTML.should.contain "Aironet"
+      # df:rendered event is second
+      @resultsWidget.bind 'df:rendered', (event, res) ->
+        res.results.length.should.be.equal 2
         done()
       @queryEl.val 'xill'
       # search!
       @queryEl.trigger 'keydown'
+

     it 'replaces search results', (done) ->
       resultsContainer = @resultsContainer


Dropdown times

Dropdown times

--- lib/sunset.coffee
+++ lib/sunset.coffee
@@ -3,6 +3,32 @@
 cached_themes =
   ui: []
   syntax: []
+times = [
+  { value: 0, description: '0:00 (12AM)' },
+  { value: 100, description: '1:00 (1AM)' },
+  { value: 200, description: '2:00 (2AM)' },
+  { value: 300, description: '3:00 (3AM)' },
+  { value: 400, description: '4:00 (4AM)' },
+  { value: 500, description: '5:00 (5AM)' },
+  { value: 600, description: '6:00 (6AM)' },
+  { value: 700, description: '7:00 (7AM)' },
+  { value: 800, description: '8:00 (8AM)' },
+  { value: 900, description: '9:00 (9AM)' },
+  { value: 1000, description: '10:00 (10AM)' },
+  { value: 1100, description: '11:00 (11AM)' },
+  { value: 1200, description: '12:00 (12PM)' },
+  { value: 1300, description: '13:00 (1PM)' },
+  { value: 1400, description: '14:00 (2PM)' },
+  { value: 1500, description: '15:00 (3PM)' },
+  { value: 1600, description: '16:00 (4PM)' },
+  { value: 1700, description: '17:00 (5PM)' },
+  { value: 1800, description: '18:00 (6PM)' },
+  { value: 1900, description: '19:00 (7PM)' },
+  { value: 2000, description: '20:00 (8PM)' },
+  { value: 2100, description: '21:00 (9PM)' },
+  { value: 2200, description: '22:00 (10PM)' },
+  { value: 2300, description: '23:00 (11PM)' }
+]

 module.exports = Sunset =
   config:
@@ -37,6 +63,7 @@
       minimum: 0
       maximum: 2400
       type: 'number'
+      enum: times
     when_does_it_get_light:
       title: 'When does the sun rise?'
       description: 'Use a 24hr format: 8:00PM equals 20:00, so type 2000'
@@ -44,6 +71,7 @@
       minimum: 0
       maximum: 2400
       type: 'number'
+      enum: times
     has_been_configured:
       title: 'This plugin has been configured?'
       description: 'Internal state tracking. Don\'t change this yourself unless you are ok with bugs.'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,5 +1,5 @@
 ###*
-The TimedEvents module allows arbitrary code to be executed at set intervals. This module is included by d
+The TimedEvents module allows arbitrary code to be executed at set intervals. This module is included by default

 player = GameObject()


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -9,6 +9,7 @@
 ###
 TimedEvents = (I={}) ->
   Object.reverseMerge I,
+
   ###*
   Execute fn every n frames.


fincm3.coffee: Set Yocto version to Warrior

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- fincm3.coffee
+++ fincm3.coffee
@@ -34,7 +34,7 @@
 		machine: 'fincm3'
 		image: 'resin-image'
 		fstype: 'resinos-img'
-		version: 'yocto-thud'
+		version: 'yocto-warrior'
 		deployArtifact: 'resin-image-fincm3.resinos-img'
 		compressed: true


Various stat-related bug fixes

Various stat-related bug fixes

--- app/controllers/home.coffee
+++ app/controllers/home.coffee
@@ -4,7 +4,7 @@

 REFRESH_INTERVAL = 1000 * 60 * 5

-isDev = if !!~location.host.indexOf 'demo' or +location.port > 1000 then true else false
+isDev = if !!~location.host.indexOf('demo') or +location.port > 1000 then true else false

 class HomePage extends Controller
   className: 'home-page'
@@ -13,7 +13,7 @@
   elements:
     '#user-count': 'userCount'
     '#classification-count': 'classificationCount'
-    '#totalImages': 'totalImages'
+    '#total-images': 'totalImages'
     '#percent-complete': 'percentComplete'

   constructor: ->
@@ -37,9 +37,13 @@
       total = subjectStatus.reduce (pv, cv, i, arr) ->
         pv + cv.count
       , 0
-      complete = subjectStatus[1].count # eh
+
+      complete = 0
+      for status in subjectStatus
+        continue unless status is 'complete'
+        complete = status.count

       @totalImages.html formatNumber total
-      @percentComplete.html "#{ (complete / total) * 100 }%"
+      @percentComplete.html "#{ Math.ceil(complete / total) * 100 }%"

 module.exports = HomePage


Wrap schedule in Ember.run

Wrap schedule in Ember.run

--- app/controllers/page.coffee
+++ app/controllers/page.coffee
@@ -3,8 +3,9 @@
   zoomStops: [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5]
   init: ->
     @_super()
-    Em.run.scheduleOnce 'afterRender', ->
-      $(window).trigger('resize')
+    Em.run ->
+      Em.run.scheduleOnce 'afterRender', ->
+        $(window).trigger('resize')
   nextPage: (->
       treatise = @get 'treatise'
       @get('treatise').nextPage @get('content')


Fix NG_VERSION.

Fix NG_VERSION.

--- app/javascripts/main.coffee
+++ app/javascripts/main.coffee
@@ -23,7 +23,7 @@
 # The Angular version.
 # Note: this version number must match the bower.json angularjs
 # version number.
-NG_VERSION = '#1.4.9'
+NG_VERSION = '1.4.9'

 # The Angular CDN location.
 NG_LIB = GOOGLE_LIBS + '/angularjs/' + NG_VERSION


disable adding feed while processing

disable adding feed while processing

--- app/js/classes/feeds.coffee
+++ app/js/classes/feeds.coffee
@@ -5,6 +5,7 @@
 @App.FeedsController = Ember.ArrayController.extend
 	newFeedUrl: null
 	urlPrompt: false
+	isProcessing: false
 	selectFeed: (feed)->
 		@get('content').forEach( (i)->
 			i.set('selected', false)
@@ -25,9 +26,8 @@
 	# 	)

-@App.FeedsView = Ember.View.extend {
+@App.FeedsView = Ember.View.extend
 	classNames: ['inmiddle']
-}

 @App.NewFeedView = Ember.View.extend
 	click: ->
@@ -40,25 +40,29 @@
 	keyUp: (e)->
 		url = @get('controller.newFeedUrl')
 		view = @
-		if e.keyCode == 13 && !!url
-			$.post( '/v1/feeds', {url: url} ).done (json)->
+		if e.keyCode == 13 && !!url && !@get('isProcessing')
+			@set('isProcessing', true)
+			@$('input').attr('disabled', true)
+			$.post( '/v1/feeds', {url: url} ).done (json)=>
+				json = JSON.parse(json)
+				$.get( 'v1/feeds/' + json.id ).done (json)=>
+					@set('isProcessing', false)
+					@$('input').attr('disabled', false)
 					json = JSON.parse(json)
-					$.get( 'v1/feeds/' + json.id ).done (json)->
-						json = JSON.parse(json)
-						items = json.feed.items.map((i)->
-							o = Ember.Object.create().setProperties(i)
-							o.set('selected', false)
-						)
-						feed = Ember.Object.create {
-							title: json.feed.title
-							url: url
-							created_at: new Date()
-							items: items
-							selected: false
-						}
-						view.get('controller.content').pushObject( feed )
-						view.set('controller.urlPrompt', false)
-						view.set('controller.newFeedUrl', null)
+					items = json.feed.items.map((i)->
+						o = Ember.Object.create().setProperties(i)
+						o.set('selected', false)
+					)
+					feed = Ember.Object.create {
+						title: json.feed.title
+						url: url
+						created_at: new Date()
+						items: items
+						selected: false
+					}
+					view.get('controller.content').pushObject( feed )
+					view.set('controller.urlPrompt', false)
+					view.set('controller.newFeedUrl', null)

 		# if e.keyCode == 13 && !!url
 		# 	@get('controller').newFeed {"url": encodeURIComponent(url)}


Fixed #310 by tweaking the styling to emphasize stroke over fill.

--- app/lib/surface/Mark.coffee
+++ app/lib/surface/Mark.coffee
@@ -115,14 +115,14 @@
     #@mark.cache 0, 0, diameter, diameter  # not actually faster than simple ellipse draw

   buildRadius: (range) ->
-    alpha = 0.35
+    alpha = 0.15
     colors =
       voiceRange: "rgba(0, 145, 0, #{alpha})"
       visualRange: "rgba(0, 0, 145, #{alpha})"
       attackRange: "rgba(145, 0, 0, #{alpha})"

     # Fallback colors which work on both dungeon and grass tiles
-    extracolors = [
+    extraColors = [
       "rgba(145, 0, 145, #{alpha})"
       "rgba(0, 145, 145, #{alpha})"
       "rgba(145, 105, 0, #{alpha})"
@@ -137,10 +137,8 @@

     @mark = new createjs.Shape()

-    if colors[range]?
-      @mark.graphics.beginFill colors[range]
-    else
-      @mark.graphics.beginFill extracolors[i]
+    fillColor = colors[range] ? extraColors[i]
+    @mark.graphics.beginFill fillColor

     # Draw the outer circle
     @mark.graphics.drawCircle 0, 0, @sprite.thang[range] * Camera.PPM
@@ -149,13 +147,16 @@
     if i+1 < @sprite.ranges.length
       @mark.graphics.arc 0, 0, @sprite.ranges[i+1]['radius'], Math.PI*2, 0, true

+    @mark.graphics.endFill()
+
+    strokeColor = fillColor.replace '' + alpha, '0.75'
+    @mark.graphics.setStrokeStyle 2
+    @mark.graphics.beginStroke strokeColor
+    @mark.graphics.arc 0, 0, @sprite.thang[range] * Camera.PPM, Math.PI*2, 0, true
+    @mark.graphics.endStroke()
+
     # Add perspective
     @mark.scaleY *= @camera.y2x
-
-    @mark.graphics.endStroke()
-    @mark.graphics.endFill()
-
-    return

   buildDebug: ->
     @mark = new createjs.Shape()


Move the GA tracking to the same place as the mixpanel tracking

--- js/app.coffee
+++ js/app.coffee
@@ -320,6 +320,9 @@
   @bind 'run-route', (e, data) ->
     VM.clearErrorMessage()
     mixpanel.track_pageview(data.path)
+    if window._gaq? # we dont use ga in test mode
+      window._gaq.push data.path
+

   # ignore forms with method ko, useful when using the knockout submit binding
   @route 'ko', '.*', ->
@@ -484,11 +487,6 @@

   @post '^/circumvent-sammy', (cx) -> true # dont show an error when posting

-  # Google analytics
-  @bind 'event-context-after', ->
-    if window._gaq? # we dont use ga in test mode
-      window._gaq.push @path
-
   @bind 'error', (e, data) ->
     if data? and data.error? and window.Airbrake?
       window.notifyError data


cleanup loadEditOrg

cleanup loadEditOrg

--- js/app.coffee
+++ js/app.coffee
@@ -240,15 +240,16 @@
         builds_table: 'project'

   loadEditOrg: (username, refresh) =>
-    @org_has_been_loaded(false)
-    @org().clean() if @org()
-    @org(null)
-
-    if (refresh or !@org() or (@org().name() isnt username))
-      $.getJSON "/api/v1/organization/#{username}/settings", (data) =>
-        @org(new CI.inner.Org data)
-        @org_has_been_loaded(true)
-        mixpanel.track("View Org", {"username": username})
+    if refresh
+      @org_has_been_loaded(false)
+      @org().clean() if @org()
+      @org(null)
+
+    $.getJSON "/api/v1/organization/#{username}/settings", (data) =>
+      @org().clean() if @org()
+      @org(new CI.inner.Org data)
+      @org_has_been_loaded(true)
+      mixpanel.track("View Org", {"username": username})

   loadEditOrgPage: (username, subpage) =>
     @loadEditOrg(username, false)


Fix workflow link, move to function

Fix workflow link, move to function

--- app/pages/lab/workflow.cjsx
+++ app/pages/lab/workflow.cjsx
@@ -25,6 +25,17 @@
   getInitialState: ->
     selectedTaskKey: @props.workflow.first_task
     forceReloader: 0
+
+  workflowLink: ->
+    [owner, name] = @props.project.slug.split('/')
+    # React-router completely overrides clicking on links. Unbelievable.
+    viewParams = owner: owner, name: name
+    viewQuery = workflow: @props.workflow.id
+    currentLocation = location.origin + location.pathname + location.search
+    currentLocation += if location.search is '' then '?' else '&'
+    currentLocation += "reload=#{@state.forceReloader}"
+    viewHash = @makeHref 'project-classify', viewParams, viewQuery
+    currentLocation + viewHash

   render: ->
     disabledStyle =
@@ -123,18 +134,7 @@

-            [owner, name] = @props.slug.split('/')
-            # React-router completely overrides clicking on links. Unbelievable.
-            viewParams = owner: owner, name: name
-            viewQuery = workflow: @props.workflow.id
-            currentLocation = location.origin + location.pathname + location.search
-            currentLocation += if location.search is ''
-              '?'
-            else
-              '&'
-            currentLocation += "reload=#{@state.forceReloader}"
-            viewHash = @makeHref 'project-classify', viewParams, viewQuery
-            View this workflow
+            View this workflow


add text about contacting zooniverse for complex retirement rules (#3782)

--- app/pages/lab/workflow.cjsx
+++ app/pages/lab/workflow.cjsx
@@ -386,6 +386,9 @@
             </AutoSave>

             How many people should classify each subject before it is “done”? Once a subject has reached the retirement limit it will no longer be shown to any volunteers.
+

+

+            If you'd like more complex retirement rules, please get in touch via the Contact Us page.


Paginate the comment feed

Paginate the comment feed

--- app/pages/profile/feed.cjsx
+++ app/pages/profile/feed.cjsx
@@ -3,6 +3,7 @@
 apiClient = require '../../api/client'
 talkClient = require '../../api/talk'
 Markdown = require '../../components/markdown'
+Paginator = require '../../talk/lib/paginator'

 CommentLink = React.createClass
   displayName: 'CommentLink'
@@ -108,28 +109,10 @@
           {for comment in @state.comments
             <CommentLink key={comment.id} comment={comment} />}

-          <select value={@props.query.page} disabled={meta.page_count is 1} onChange={@handlePageChange}>
-            {for page in [1..meta.page_count]
-              <option key={page}>{page}</option>}
-          </select>
+          <Paginator pageCount={meta.page_count} page={meta.page} />

       else if @state.error?

{@state.error.toString()}

       else
         null}

-
-  changeSearchString: (search = '', changes) ->
-    params = {}
-    for keyValue in search.slice(1).split('&')
-      [key, value] = keyValue.split('=')
-      params[key] = value
-    for key, value of changes
-      params[key] = value
-    "?#{([key, value].join('=') for key, value of params).join('&')}"
-
-  handlePageChange: (e) ->
-    [beforeQ, afterQ] = location.hash.split('?')
-    oldSearch = '?' + afterQ
-    newSearch = @changeSearchString(oldSearch, {page: e.target.value})
-    location.hash = beforeQ + newSearch


[2/2] Support join rooms by name (fixes #5)

Rework sequence of joining a room. Now we first get ids for all rooms.
This supports roomids or names for backwards compatibility

--- src/rocketchat.coffee
+++ src/rocketchat.coffee
@@ -39,52 +39,64 @@
 					(userid) =>
 						@robot.logger.info "Successfully Logged In"
 						joinrooms = []
+						roomids = []
 						subs = []
 						for room in rooms
 							do(room) =>
-								joinrooms.push @chatdriver.joinRoom(userid, RocketChatUser, room)
+								roomids.push  @chatdriver.getRoomId(room)

-						Q.all(joinrooms)
-							.then(
-								(res) =>
-									@robot.logger.info "all rooms joined"
-									for result, idx in res
-										@robot.logger.info "Successfully joined room: #{rooms[idx]}"
-										subs.push @chatdriver.prepMeteorSubscriptions({uid: userid, roomid: rooms[idx]})
+						Q.all(roomids).then(
+							(room_ids) =>
+								for result, index in room_ids
+									rooms[index] = result
+									joinrooms.push @chatdriver.joinRoom(userid, RocketChatUser, result)

-									Q.all(subs)
-										.then(
-											(results) =>
-												@robot.logger.info "all subscriptions ready"
-												for result, idx in results
-													@robot.logger.info "Successfully subscribed to room: #{rooms[idx]}"
+								@robot.logger.info "rid: ", room_ids
+								Q.all(joinrooms)
+									.then(
+										(res) =>
+											@robot.logger.info "all rooms joined"
+											for result, idx in res
+												@robot.logger.info "Successfully joined room: #{rooms[idx]}"
+												subs.push @chatdriver.prepMeteorSubscriptions({uid: userid, roomid: rooms[idx]})
+
+											Q.all(subs)
+												.then(
+													(results) =>
+														@robot.logger.info "all subscriptions ready"
+														for result, idx in results
+															@robot.logger.info "Successfully subscribed to room: #{rooms[idx]}"

-												@chatdriver.setupReactiveMessageList (newmsg) =>
-													if (newmsg.u._id isnt userid)  || (newmsg.t is 'uj')
-														curts = new Date(newmsg.ts.$date)
-														@robot.logger.info "Message receive callback id " + newmsg._id + " ts " + curts
-														@robot.logger.info "[Incoming] #{newmsg.u.username}: #{newmsg.msg}"
+														@chatdriver.setupReactiveMessageList (newmsg) =>
+															if (newmsg.u._id isnt userid)  || (newmsg.t is 'uj')
+																curts = new Date(newmsg.ts.$date)
+																@robot.logger.info "Message receive callback id " + newmsg._id + " ts " + curts
+																@robot.logger.info "[Incoming] #{newmsg.u.username}: #{newmsg.msg}"

-														if curts > @lastts
-															@lastts = curts
-															if newmsg.t isnt 'uj'
-																user = @robot.brain.userForId newmsg.u._id, name: newmsg.u.username, room: newmsg.rid
-																text = new TextMessage(user, newmsg.msg, newmsg._id)
-																@robot.receive text
-																@robot.logger.info "Message sent to hubot brain."
-															else   # enter room message
-																if newmsg.u._id isnt userid
-																	user = @robot.brain.userForId newmsg.u._id, name: newmsg.u.username, room: newmsg.rid
-																	@robot.receive new EnterMessage user, null, newmsg._id
+																if curts > @lastts
+																	@lastts = curts
+																	if newmsg.t isnt 'uj'
+																		user = @robot.brain.userForId newmsg.u._id, name: newmsg.u.username, room: newmsg.rid
+																		text = new TextMessage(user, newmsg.msg, newmsg._id)
+																		@robot.receive text
+																		@robot.logger.info "Message sent to hubot brain."
+																	else   # enter room message
+																		if newmsg.u._id isnt userid
+																			user = @robot.brain.userForId newmsg.u._id, name: newmsg.u.username, room: newmsg.rid
+																			@robot.receive new EnterMessage user, null, newmsg._id

-											(err) =>
-												@robot.logger.error "Unable to subscribe: #{err} Reason: #{err.reason}"
-										)
+													(err) =>
+														@robot.logger.error "Unable to subscribe: #{err} Reason: #{err.reason}"
+												)

-								(err) =>
-									@robot.logger.error "Unable to Join room: #{err} Reason: #{err.reason}"
-							)
+										(err) =>
+											@robot.logger.error "Unable to Join room: #{err} Reason: #{err.reason}"
+									)
+
+							(error) =>
+								@robot.logger.error "Unable to get room id: #{err} Reason: #{err.reason}"
+						)

 					(err) =>


Optionally pass in parameters to RequestForm service

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -84,7 +84,7 @@
   # Get an angular-dynamic-forms compatible form description from a Service
   # given a service code
   .factory 'RequestForm', (Service) ->
-    (service_code) ->
+    (service_code, params) ->
       Service.get(service_code: service_code)
         # Return a promise since dynamic-forms needs the form template in
         # scope when the controller is invoked
@@ -102,6 +102,7 @@
               label: a.description
               class: "form-control"
               wrapper: '

'
+              val: params[a.code]
             if a.datatype in ['singlevaluelist', 'multivaluelist']
               fields[a.code].multiple = a.datatype == 'multivaluelist'
               options = {}


Make all dashboards forkable

Make all dashboards forkable

--- app/views/app_header.coffee
+++ app/views/app_header.coffee
@@ -40,9 +40,7 @@
     @

   isForkable: =>
-    location.hash.split('/')[1] is 'dashboards' and
-      _.isUndefined(User.current?.dashboards?.find (dashboard) ->
-        dashboard.id is parseInt(location.hash.split('/')[2]))
+    location.hash.split('/')[1] is 'dashboards'

   onViewCurrent: =>
     @active = 'current'


fix calculation for right+bottom margins

--- line_chart.coffee
+++ line_chart.coffee
@@ -4,9 +4,9 @@
     # Margins: zero if not set or the same as the opposite margin
     # (you likely want this to keep the chart centered within the widget)
     left = @get('leftMargin') || 0
-    right = if @get('rightMargin')? then @get('rightMargin') else left
+    right = @get('rightMargin') || left
     top = @get('topMargin') || 0
-    bottom = if @get('bottomMargin')? then @get('bottomMargin') else top
+    bottom = @get('bottomMargin') || top

     container = $(@node).parent()
     # Gross hacks. Let's fix this.


test2

--- Web/App/app/modules/common/views/signin-view.coffee
+++ Web/App/app/modules/common/views/signin-view.coffee
@@ -1,6 +1,6 @@
 application = require 'application'
 vent = require 'vent'
-# test
+# test aaaa
 module.exports = class SigninView extends Backbone.Marionette.ItemView
   id: 'signin-view'
   template: require './templates/signin'


Show adjustment type.

Show adjustment type.

--- node_modules_koding/koding-payment/lib/main.coffee
+++ node_modules_koding/koding-payment/lib/main.coffee
@@ -535,6 +535,7 @@
               amount      : t.total_in_cents[0]['_']
               currency    : t.currency[0]
               description : if t.description[0].$ then "" else unescape t.description[0]
+              type        : t['$']['type']
               origin      : t.origin[0]
       callback no, adjustments


sync slideshow

sync slideshow

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -252,30 +252,53 @@
                     # TODO: Figure out what to do with "Artwork not Found" errors
                     # return callback err if err

-                    # Add artworks, images and video from the article to the post
-                    async.mapSeries article.sections, ((section, cb) ->
-                      switch section.type
-                        when 'artworks'
-                          async.mapSeries section.ids, ((id, cb2) ->
-                            request
-                              .post("#{ARTSY_URL}/api/v1/post/#{post._id}/artwork/#{id}")
-                              .set('X-Access-Token', accessToken)
-                              .end (err, res) -> cb2 (err or res.body.error), res.body
-                          ), cb
+                    # Add artworks, images and video from the slideshow to the post
+                    if article.sections[0]?.type is 'slideshow'
+                      items = article.sections[0].items
+                    else
+                      items = []
+                    async.mapSeries items, ((item, cb) ->
+                      switch item.type
+                        when 'artwork'
+                          request
+                            .post("#{ARTSY_URL}/api/v1/post/#{post._id}/artwork/#{item.id}")
+                            .set('X-Access-Token', accessToken)
+                            .end (err, res) -> cb (err or res.body.error), res.body
                         when 'image', 'video'
                           request
                             .post("#{ARTSY_URL}/api/v1/post/#{post._id}/link")
                             .set('X-Access-Token', accessToken)
-                            .send(url: section.url)
+                            .send(url: item.url)
                             .end (err, res) -> cb (err or res.body.error), res.body
                         else
                           cb()
                     ), (err) ->
                       return callback err if err
-                      request
-                        .get("#{ARTSY_URL}/api/v1/post/#{post._id}")
-                        .set('X-Access-Token', accessToken)
-                        .end (err, res) -> callback (err or res.body.error), res.body
+
+                      # Add artworks, images and video from the article to the post
+                      async.mapSeries article.sections, ((section, cb) ->
+                        switch section.type
+                          when 'artworks'
+                            async.mapSeries section.ids, ((id, cb2) ->
+                              request
+                                .post("#{ARTSY_URL}/api/v1/post/#{post._id}/artwork/#{id}")
+                                .set('X-Access-Token', accessToken)
+                                .end (err, res) -> cb2 (err or res.body.error), res.body
+                            ), cb
+                          when 'image', 'video'
+                            request
+                              .post("#{ARTSY_URL}/api/v1/post/#{post._id}/link")
+                              .set('X-Access-Token', accessToken)
+                              .send(url: section.url)
+                              .end (err, res) -> cb (err or res.body.error), res.body
+                          else
+                            cb()
+                      ), (err) ->
+                        return callback err if err
+                        request
+                          .get("#{ARTSY_URL}/api/v1/post/#{post._id}")
+                          .set('X-Access-Token', accessToken)
+                          .end (err, res) -> callback (err or res.body.error), res.body

 @destroy = (id, callback) ->
   db.articles.remove { _id: ObjectId(id) }, (err, res) ->


Kick semaphore

Kick semaphore

--- apps/articles/routes.coffee
+++ apps/articles/routes.coffee
@@ -137,3 +137,4 @@
         res.send req.body
     else
       res.status(500).send(response.errormsg)
+


Minor refactoring

Minor refactoring

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -80,6 +80,13 @@
         else
             return from

+    # Add signature at the end of the message
+    addSignature: (message, signature) ->
+        message.text += "\n\n-- \n#{signature}"
+        signatureHtml = signature.replace /\n/g, '
'
+        message.html += """
+

-- \n
#{signatureHtml}

+            """

     # Build message to put in the email composer depending on the context
     # (reply, reply to all, forward or simple message).
@@ -178,8 +185,6 @@
         message.bcc = []
         message.subject = @getReplySubject inReplyTo
         message.text = separator + @generateReplyText(text) + "\n"
-        if isSignature
-            message.text += "\n\n#{signature}"
         message.html = """
             #{COMPOSE_STYLE}

#{separator} …

@@ -187,11 +192,7 @@

             """
         if isSignature
-            signature = signature.replace /\n/g, '
'
-            message.html += """
-

-- \n
#{signature}

-            """
-
+            @addSignature message, signature

     # Build message to display in composer in case of a reply to all message:
     # * set subject automatically (Re: previous subject)
@@ -227,8 +228,6 @@

         message.subject = @getReplySubject inReplyTo
         message.text = separator + @generateReplyText(text) + "\n"
-        if isSignature
-            message.text += "\n\n#{signature}"
         message.html = """
             #{COMPOSE_STYLE}

#{separator} …

@@ -236,10 +235,7 @@

             """
         if isSignature
-            signature = signature.replace /\n/g, '
'
-            message.html += """
-

-- \n
#{signature}

-            """
+            @addSignature message, signature

     # Build message to display in composer in case of a message forwarding:
@@ -295,11 +291,10 @@
             """
         message.text = textSeparator + text
         message.html = "#{COMPOSE_STYLE}"
+
         if isSignature
-            signature = signature.replace /\n/g, '
'
-            message.html += """
-

-- \n
#{signature}

-            """
+            @addSignature message, signature
+
         message.html += """

#{htmlSeparator}

#{html}
@@ -329,15 +324,10 @@
         message.bcc = []
         message.subject = ''
         message.text = ''
+        message.html = "#{COMPOSE_STYLE}\n

"
+
         if isSignature
-            message.text += "-- \n#{signature}"
-        message.html = COMPOSE_STYLE
-        if isSignature
-            signature = signature.replace /\n/g, '
'
-            message.html += """
-

-
-- \n
#{signature}

-            """
+            @addSignature message, signature

         return message


Show green background on reviewResult page for stud only

--- client/components/ReviewResult.coffee
+++ client/components/ReviewResult.coffee
@@ -285,7 +285,10 @@
         if (!user?.paidTill)
             styles.nonpaid
         else if isPaid(user)
-            styles.paid
+            if user.userList == "stud"
+                styles.paid
+            else
+                styles.nonpaid
         else if !isMuchUnpaid(user)
             styles.unpaid
         else


ChatView: Forward collaboration related events to settings pane.

--- client/ide/views/chat/chatview.coffee
+++ client/ide/views/chat/chatview.coffee
@@ -22,6 +22,8 @@

     @on 'ReceivedClickElseWhere', @bound 'hide'

+    @on 'CollaborationStarted', => @settingsPane.emit 'CollaborationStarted'
+    @on 'CollaborationEnded',   => @settingsPane.emit 'CollaborationEnded'

   show: ->


chat: add views to chat tab view

chat: add views to chat tab view

--- client/ide/views/chat/chatview.coffee
+++ client/ide/views/chat/chatview.coffee
@@ -1,4 +1,6 @@
 class IDE.ChatView extends KDTabView
+
+  JView.mixin @prototype

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

@@ -19,12 +21,58 @@
         KD.utils.stopDOMEvent event
         @hide()

+    @createElements()
     @createLoader()

     KD.singletons.appManager.require 'Activity', @bound 'createPanes'

     @once 'CollaborationStarted',        @bound 'removeLoader'
     @once 'CollaborationNotInitialized', @bound 'removeLoader'
+
+
+  createElements: ->
+
+    channel = @getData()
+
+    header = new KDCustomHTMLView
+      tagName  : 'header'
+      cssClass : 'general-header'
+
+    header.addSubView @title = new KDCustomHTMLView
+      tagName  : 'h3'
+      cssClass : 'workspace-name'
+      partial  : 'My Workspace'
+
+    header.addSubView @chevron = @createMenu()
+
+    header.addSubView @link = new KDCustomHTMLView
+      tagName    : 'a'
+      cssClass   : 'session-link'
+      partial    : link = KD.utils.groupifyLink "IDE/#{channel.id}", yes
+      attributes : href : link
+
+    @addSubView header
+
+
+  createMenu: ->
+
+    channel = @getData()
+
+    chevron = new KDButtonViewWithMenu
+      title          : ''
+      cssClass       : 'pm-title-chevron'
+      itemChildClass : ActivityItemMenuItem
+      delegate       : this
+      menu           : @bound 'settingsMenu'
+      style          : 'resurrection'
+      callback       : (event) -> @contextMenu event
+
+
+  settingsMenu: ->
+
+    'Search'   : { callback: noop }
+    'Settings' : { callback: @bound 'showSettingsPane' }
+    'Minimize' : { callback: noop }

   createLoader: ->


Properly take floor in graduate year update (fix #179)

--- client/lib/graduateYearToClass.coffee
+++ client/lib/graduateYearToClass.coffee
@@ -4,7 +4,7 @@

 export getYear = (clas) ->
     now = new Date()
-    return (11-clas+now.getFullYear() + Math.floor(6 + now.getMonth())/12)
+    return (11-clas+now.getFullYear() + Math.floor((6 + now.getMonth())/12))

 export default getClass = (graduateDate) ->
     now = new Date()


Prevent navigation with up and down

Prevent navigation with up and down

--- client/views/app/message-popup.coffee
+++ client/views/app/message-popup.coffee
@@ -70,6 +70,10 @@
 	template.onInputKeydown = (event) =>
 		if template.open.curValue isnt true
 			return
+
+		if event.which in [38, 40]
+			event.preventDefault()
+			event.stopPropagation()

 		if event.which is 13
 			template.open.set false


Rename variables for readability and to avoid confusion

--- src/model/validations/exclusion_validator.js.coffee
+++ src/model/validations/exclusion_validator.js.coffee
@@ -4,14 +4,14 @@
   @triggers 'exlusionIn'

   constructor: (options) ->
-    @acceptableValues = options.exlusionIn
+    @unacceptableValues = options.exlusionIn
     super

   validateEach: (errors, record, key, callback) ->
     value = record.get(key)

-    for acceptableValue in @acceptableValues
-      if acceptableValue == value
+    for unacceptableValue in @unacceptableValues
+      if unacceptableValue == value
         errors.add key, @format(key, 'included_in_list')
         return callback()


remove unnecessary resolves

remove unnecessary resolves

--- src/node_modules/group-members/group-members.coffee
+++ src/node_modules/group-members/group-members.coffee
@@ -3,10 +3,6 @@
   template: require('./group-members-template.html')
   controller: require('./group-members-controller.coffee')
   resolve:
-    MembershipsStore: (MembershipsStore) ->
-      MembershipsStore
-    MembershipModel: (MembershipModel) ->
-      MembershipModel
     users: ($stateParams, UsersStore) ->
       UsersStore.all()
     memberships: ($stateParams, MembershipsStore) ->


Don't follow prev/next links without an exact word match.

Closes #617.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -788,20 +788,12 @@
       )
       .filter((a) -> wordCount(a) <= wordCount(candidateLinks[0]) + 1)

-  # try to get exact word matches first
   for linkString in linkStrings
     for candidateLink in candidateLinks
       exactWordRegex = new RegExp("\\b" + linkString + "\\b", "i")
       if (exactWordRegex.test(candidateLink.innerText))
         followLink(candidateLink)
         return true
-
-  for linkString in linkStrings
-    for candidateLink in candidateLinks
-      if (candidateLink.innerText.toLowerCase().indexOf(linkString) != -1)
-        followLink(candidateLink)
-        return true
-
   false

 findAndFollowRel = (value) ->


Fix goUp.

Fix goUp.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -299,7 +299,7 @@

   goUp: (count) ->
     url = window.location.href
-    if (url[url.length - 1] == " / ")
+    if (url[url.length - 1] == "/")
       url = url.substring(0, url.length - 1)

     urlsplit = url.split("/")


Minor refactoring.

Minor refactoring.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -436,7 +436,8 @@
         chrome.runtime.sendMessage handler: "sendMessageToFrames", message: name: "initializeTopFrame"

 # Checks if Vimium should be enabled or not in this frame.  As a side effect, it also informs the background
-# page whether this frame has the focus, allowing the background page to track the active frame's URL.
+# page whether this frame has the focus, allowing the background page to track the active frame's URL and set
+# the page icon.
 checkIfEnabledForUrl = do ->
   Frame.addEventListener "isEnabledForUrl", (response) ->
     {isEnabledForUrl, passKeys, frameIsFocused} = response
@@ -445,7 +446,8 @@
     # Initialize UI components. We only initialize these once we know that Vimium is enabled; see #1838.
     if isEnabledForUrl
       initializeTopFrame()
-      DomUtils.documentReady HUD.init.bind HUD if frameIsFocused
+      if frameIsFocused and not HUD.isReady()
+        DomUtils.documentReady HUD.init.bind HUD
     else if HUD.isReady()
       # Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load.
       HUD.hide()
@@ -502,9 +504,8 @@
   else
     HUD.showForDuration("No matches for '#{FindMode.query.rawQuery}'", 1000)

-window.performFind = -> findAndFocus()
-
-window.performBackwardsFind = -> findAndFocus(true)
+window.performFind = -> findAndFocus false
+window.performBackwardsFind = -> findAndFocus true

 getLinkFromSelection = ->
   node = window.getSelection().anchorNode


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -1,4 +1,4 @@
-asyncTest "Animation should default to first frame", ->
+asyncTest "Animation should set proper frame", ->
   animation = Animation.fromPixieId 45

   milliseconds = 300


Deprecate the use of get_defaults() upon a session login Have forgotten the original purpose of this function, and a fixme comment hints that it is probably not used anymore.

--- src/login_window.coffee
+++ src/login_window.coffee
@@ -118,7 +118,7 @@
 						$().publish 'login_event', [un]

 						# > FIXME; not sure whether this is needed anymore.
-						get_defaults()
+						#get_defaults()
 			return false

 		# Upon the user cancelling the form,