fixing query to not have unecessary $and operation (which slows down query, markedly)

--- lib/pages.coffee
+++ lib/pages.coffee
@@ -1,9 +1,9 @@
 @__Pages = class Pages

   settings:
-
+
     #settingName: [canBeMadeAvailableToTheClient, expectedTypes(s), defaultValue]
-
+
     dataMargin: [true, Number, 3]
     divWrapper: [true, Match.OneOf(
       Match.Optional(String)
@@ -24,9 +24,9 @@
     routerTemplate: [true, String, "pages"]
     routerLayout: [true, Match.Optional(String), undefined]
     sort: [true, Object, {}]
-
+
     # Unavailable to the client after initialization
-
+
     auth: [false, Match.Optional(Function), undefined]
     availableSettings: [false, Object, {}]
     fastRender: [false, Boolean, false]
@@ -50,27 +50,34 @@
     tableItemTemplate: [false, String, "_pagesTableItem"]
     tableTemplate: [false, String, "_pagesTable"]
     templateName: [false, Match.Optional(String), undefined] #Defaults to collection name
-
+
   # Prototype variables (shared between instances)
-
+
   _nInstances: 0
   collections: {}
   instances: {}
-
+
   methods:
-
+
     "CountPages": (sub) ->
       n = sub.get "nPublishedPages"
       return n  if n?

-      n = Math.ceil @Collection.find(
-        $and: [
+      query = {}
+
+      if sub.get('filters') and sub.get('realFilters')
+        query.$and = [
           sub.get("filters"),
           sub.get("realFilters") or {}
         ]
-      ).count() / (sub.get "perPage")
+      else if sub.get('filters')
+        query = sub.get('filters')
+      else if sub.get('realFilters')
+        query = sub.get('realFilters')
+
+      n = Math.ceil @Collection.find(query).count() / (sub.get "perPage")
       n or 1
-
+
     "Set": (k, v, sub) ->
       if !@settings[k]?
         @error "invalid-option", "Invalid option name: #{k}."
@@ -81,7 +88,7 @@

       if !@availableSettings[k] or (_.isFunction(@availableSettings[k]) and !@availableSettings[k] v, sub)
         @error "forbidden-option", "Changing #{k} not allowed."
-
+
       changes = 0
       if v?
         changes = @_set k, v, cid: sub.connection.id
@@ -89,7 +96,7 @@
         for _k, _v of k
           changes += @set _k, _v, cid: sub.connection.id
       changes
-
+
     "Unsubscribe": ->
       cid = arguments[arguments.length - 1].connection.id
       subs = {}
@@ -100,13 +107,13 @@
           delete @subscriptions[k]
       @subscriptions.length = 0
       true
-
+
   constructor: (collection, settings = {}) ->
     unless @ instanceof Meteor.Pagination
       throw new Meteor.Error "missing-new", "The Meteor.Pagination instance has to be initiated with `new`"
-
+
     # Instance variables
-
+
     @init = @beforeFirstReady = true
     @debug ?= (PAGES_DEBUG? and PAGES_DEBUG) or process?.env.PAGES_DEBUG
     @subscriptions = length: 0, order: []
@@ -122,30 +129,30 @@
     @[(if Meteor.isServer then "server" else "client") + "Init"]()
     @registerInstance()
     @
-
+
   error: (code, msg) ->
     msg = code  if !code?
     throw new Meteor.Error code, msg

   # Server initialisation
-
+
   serverInit: ->
     @setMethods()
     self = @
-
+
     # Remove the per-connection settings when a client disconnects from the server
-
+
     Meteor.onConnection (connection) =>
       connection.onClose =>
         delete @userSettings[connection.id]
-
+
     # Publish the collection that we're paginating, taking a page number as a parameter.
-
+
     Meteor.publish @id, (page) ->
       self.publish.call self, page, @
-
+
   # Client initialisation
-
+
   clientInit: ->
     @requested = {}
     @received = {}
@@ -166,9 +173,9 @@
       @reload()
     Template[@templateName].onRendered =>
       @setInfiniteTrigger() if @infinite
-
+
   #Stops all subscriptions and reloads the current page, provided it's available and @resetOnReload isn't true.
-
+
   reload: _.throttle ->
     #@log "Reloading"
     @unsubscribe()
@@ -178,7 +185,7 @@
       @sess "currentPage", false
       @sess "currentPage", p
   , 1000, trailing: false
-
+
   unsubscribe: (page, cid) ->
     return  if @beforeFirstReady
     #@log "Unsubscribing #{page} #{cid}"
@@ -207,21 +214,21 @@
       @subscriptions.order = _.without @subscriptions.order, Number page
       @subscriptions.length--
     true
-
+
   setDefaults: ->
     for k, v of @settings
       if v[2]?
         @[k] ?= v[2]
-
+
   syncSettings: (cb) ->
     S = {}
     for k, v of @settings
       if v[0]
         S[k] = @[k]
     @set S, if cb? then {cb: cb.bind(@)} else null
-
+
   # Creates server-side methods for this pagination *instance* by prefixing them with our unique id
-
+
   setMethods: ->
     nm = {}
     self = @
@@ -234,17 +241,17 @@
           r = f.apply self, arg
           r
       )(f)
-
+
     Meteor.methods nm
-
+
   # Get's the server's method name for this pagination instance
-
+
   getMethodName: (name) ->
     "#{@id}/#{name}"
-
+
   # Calls this instance's version of a given server method (first argument).
   # If the last argument is a function callback, it's bound to this instance
-
+
   call: (args...) ->
     check args, Array
     if args.length < 1
@@ -254,9 +261,9 @@
     if _.isFunction args[last]
       args[last] = args[last].bind @
     Meteor.call.apply @, args
-
+
   # Sets/gets a session variable for this instance
-
+
   sess: (k, v) ->
     return  if !Session?
     k = "#{@id}.#{k}"
@@ -264,17 +271,17 @@
       Session.set k, v
     else
       Session.get k
-
+
   # Gets a given setting
-  #
+  #
   # When there's a connection id we store this setting on a per-connection basis, otherwise we just
   # set the setting on this pagination instance

   get: (setting, connectionId) ->
     @userSettings[connectionId]?[setting] ? @[setting]
-
+
  # Sets the options for this instance
-
+
   set: (k, opts...) ->
     ch = 0
     switch opts.length
@@ -324,7 +331,7 @@
     v

   #Sanitizes all regular expressions within an object using ::sanitizeRegex()
-
+
   sanitizeRegexObj: (obj) ->
     if _.isRegExp obj
       return @sanitizeRegex obj
@@ -336,24 +343,24 @@
     obj

   # Sets a specific option
-
+
   _set: (k, v, opts = {}) ->
     check k, String
     ch = 1
-
+
     # Check that we're the server, or that we're being initialised, or that this setting can be changed
     # after initialization, or that the setting doesn't yet exist on this instance.
-
+
     if Meteor.isServer or !@[k]? or @settings[k]?[0] or opts.init
-
+
       # Check the type of the value against the @settings array
       if @settings[k]?[1]? and @settings[k]?[1] isnt true
         check v, @settings[k][1]
-
+
       @sanitizeRegexObj v
-
-      # Set the parameter on this instance (client)
-
+
+      # Set the parameter on this instance (client)
+
       oldV = @get k, opts?.cid

       return 0  if @valuesEqual v, oldV
@@ -376,20 +383,20 @@
             @userSettings[opts.cid][k] = v
         else
           @[k] = v
-
+
         opts.cb? ch
     else
       @onDeniedSetting.call @, k, v
     ch
-
+
   valuesEqual: (v1, v2) ->
     if _.isFunction v1
       _.isFunction(v2) and v1.toString() is v2.toString()
     else
       _.isEqual v1, v2
-
-  #
-
+
+  #
+
   setId: (name) ->
     if @templateName
       name = @templateName
@@ -401,15 +408,15 @@
         name = name + "2"
     @id = "pages_" + name
     @name = name
-
+
   #
-
+
   registerInstance: ->
     Pages::_nInstances++
     Pages::instances[@name] = @
-
+
   # Set the collection on which this instance operates. Creates a new one if a name is passed in.
-
+
   setCollection: (collection) ->
     if typeof collection is "object"
       Pages::collections[collection._name] = collection
@@ -420,22 +427,22 @@
         Pages::collections[collection] = @Collection
       catch e
         @Collection = Pages::collections[collection]
-        @Collection instanceof Mongo.Collection or @error "collection-inaccessible", "The '#{collection}' collection
+        @Collection instanceof Mongo.Collection or @error "collection-inaccessible", "The '#{collection}' collection
         was created outside of <Meteor.Pagination>. Pass the collection object
         instead of the collection's name to the <Meteor.Pagination> constructor."
-
+
     @setId @Collection._name
-
+
     # Create a collection based on the instance's unique id
-
+
     #@PaginatedCollection = new Mongo.Collection @id
-
+
   linkTo: (page)->
     if Router.current()?.params
       params = Router.current().params
       params.page = page
       Router.routes["#{@name}_page"].path params
-
+
   setRouter: ->
     if @router is "iron-router"
       if @route.indexOf(":page") is -1
@@ -448,12 +455,12 @@
       l = @routerLayout ? undefined
       self = @
       init = true
-
+
       Router.map ->
         unless self.infinite
-
+
           # Create a route that takes a page number
-
+
           @route "#{self.name}_page",
             path: pr
             template: t
@@ -467,10 +474,10 @@
                 self.routeSettings @
               Tracker.nonreactive =>
                 self.onNavClick page
-              @next()
-
+              @next()
+
         # Create one or more routes for the home (first) page
-
+
         if self.homeRoute
           if _.isString self.homeRoute
             self.homeRoute = [self.homeRoute]
@@ -486,46 +493,46 @@
                   self.sess "oldPage", 1
                   self.sess "currentPage", 1
                 @next()
-
+
       # If using FastRender, set it up for these routes
-
+
       if Meteor.isServer and @fastRender
         self = @
         FastRender.route pr, (params) ->
           @subscribe self.id, parseInt params.page
         FastRender.route @homeRoute, ->
-          @subscribe self.id, 1
+          @subscribe self.id, 1

   isEmpty: ->
     @isReady() and @Collection.find(_.object [["_#{@id}_i", 0]]).count() is 0
-
+
   setPerPage: ->
     @perPage = if @pageSizeLimit < @perPage then @pageSizeLimit else @perPage
-
+
   setTemplates: ->
     name = @templateName or @name
     if @table and @itemTemplate is "_pagesItemDefault"
       @itemTemplate = @tableItemTemplate
-
+
     # Create a set of template prefixed by the unique id of this pagination instance
     # The helper and events are set to those of the base versions of those templates (captured by controllers.coffee)
-
+
     for i in [@navTemplate, @pageTemplate, @itemTemplate, @tableTemplate]
       tn = @id + i
       Template[tn] = new Blaze.Template "Template.#{tn}", Template[i].renderFunction
       Template[tn].helpers _TemplateHelpers[i]
       Template[tn].events _TemplateEvents[i]
       Template[tn].helpers pagesData: @
-
-    # Set our helpers on the main template set for this pagination
-
+
+    # Set our helpers on the main template set for this pagination
+
     Template[name].helpers
       pagesData: @
       pagesNav: Template[@id + @navTemplate]
       pages: Template[@id + @pageTemplate]
-
+
   # Get the number of pages from the server
-
+
   countPages: _.throttle (cb) ->
     #@log "Counting pages"
     if !Meteor.status().connected and Package["ground:db"]?
@@ -559,13 +566,13 @@
       while @subscriptions.length >= @maxSubscriptions
         @unsubscribe @subscriptions.order[0]
       true
-
+
   # Called from the Meteor.publish call made during init, this publishes the paginated collection
   #
   # "this" will be the pagination instance
   # "page" is the page number to publish
   # "sub" is the publish handler object which (the "this" object when the function passed to Meteor.publish is called)
-
+
   publish: (page, sub) ->
     check page, Number
     check sub, Match.Where (s) ->
@@ -581,30 +588,30 @@

     # Create get and set functions for this specific connection (the settings will end up in the @userSettings,
     # stored in an object indexed under the collection id)
-
+
     get = sub.get = _.bind ((cid, k) -> @get k, cid), @, cid
     set = sub.set = _.bind ((cid, k, v) -> @set k, v, cid: cid), @, cid
-
+
     query = _.bind ((sub, get, set) ->

       delete @userSettings[cid]?.realFilters
       delete @userSettings[cid]?.nPublishedPages
-
+
       @setPerPage()
-
+
       skip = (page - 1) * get "perPage"
       skip = 0 if skip < 0
-
+
       filters = get "filters"
-
-      options =
+
+      options =
         sort: get "sort"
         fields: get "fields"
         skip: skip
         limit: get "perPage"
-
+
       # Call the authentication function if it's supplied
-
+
       if @auth?
         r = @auth.call @, skip, sub
         if !r
@@ -624,30 +631,30 @@
             options = r[1]
         else if _.isFunction r.fetch
           c = r
-
+
       if !EJSON.equals({}, filters) and !EJSON.equals(get("filters"), filters)
         set "realFilters", filters
-
+
       # Get a cursor to the base collection
       #@log "Publishing", filters, options
       c or @Collection.find filters, options
-
+
     ), @, sub, get, set

     c = query()

     #watchCollection: ->
     #c = @Collection.find()
-
+
     self = @
-
+
     # We need to call sub's added callback when a new document is added, however
     # for the purposes of pagination we also need to include the index of each document.
     #
     # Furthermore, an added document might increase the index of other documents on this page.
     #
     # We therefore need to use the observe method to handle this.
-
+
     handle = c.observe
       addedAt: _.bind ((sub, query, doc, at) ->
         return  if init
@@ -655,7 +662,7 @@
         doc["_#{@id}_p"] = page
         doc["_#{@id}_i"] = at
         id = doc._id
-        delete doc._id
+        delete doc._id
         # Add to @Collection
         query().forEach (o, i) =>
           if i is at
@@ -663,23 +670,23 @@
           else
             sub.changed @Collection._name, o._id, _.object [["_#{@id}_i", i]]
       ), @, sub, query
-
+
     # For the other cases the more efficient observeChanges will suffice...
-
+
     handle2 = c.observeChanges
       movedBefore: _.bind ((sub, query, id, before) ->
         #@log "#{id} moved before #{before}"
         query().forEach (o, i) =>
           sub.changed @Collection._name, o._id, _.object [["_#{@id}_i", i]]
       ), @, sub, query
-
+
       changed: _.bind ((sub, query, id, fields) ->
         #@log "#{id} changed"
         try
           sub.changed @Collection._name, id, fields
         catch e
       ), @, sub, query
-
+
       removed: _.bind ((sub, query, id) ->
         #@log "#{id} removed"
         try
@@ -688,9 +695,9 @@
             sub.changed @Collection._name, o._id, _.object [["_#{@id}_i", i]]
         catch e
       ), @, sub, query
-
-    # Add the documents from this query
-
+
+    # Add the documents from this query
+
     n = 0
     c.forEach (doc, index, cursor) =>
       n++
@@ -710,34 +717,34 @@
     @subscriptions[sub.connection.id][page] = sub
     @subscriptions[sub.connection.id].length++
     sub.ready()
-
-  # Sets the state of the current page as "loading" (ready = false)
-
+
+  # Sets the state of the current page as "loading" (ready = false)
+
   loading: (p) ->
     if !@fastRender and p is @currentPage()
       @sess "ready", false
-
+
   now: ->
     (new Date()).getTime()
-
+
   log: ->
     a = ["Pages: #{@name} -"]
     for i in arguments
       a.push i
     @debug and console.log.apply console, a
-
+
   logRequest: (p) ->
     @timeLastRequest = @now()
     @requesting = p
     @requested[p] = 1
-
+
   logResponse: (p) ->
     delete @requested[p]
     @received[p] = 1
-
+
   clearQueue: ->
     @queue = []
-
+
   neighbors: (page) ->
     n = []
     if @dataMargin is 0 or @maxSubscriptions < 2
@@ -751,17 +758,17 @@
       if pp > 0
         n.push pp
     n
-
+
   queueNeighbors: (page) ->
     for p in @neighbors page
       @queue.push p  if !@received[p] and !@requested[p] and p not in @queue
-
+
   paginationNavItem: (label, page, disabled, active = false) ->
     p: label
     n: page
     active: if active then "active" else ""
     disabled: if disabled then "disabled" else ""
-
+
   navigationNeighbors: ->
     page = @currentPage()
     total = @sess "totalPages"
@@ -787,7 +794,7 @@
     for i, k in n
       n[k]['_p'] = @
     n
-
+
   onNavClick: (n) ->
     if n <= @sess("totalPages") and n > 0
       Tracker.nonreactive =>
@@ -795,7 +802,7 @@
         if @received[cp]
           @sess "oldPage", cp
       @sess "currentPage", n
-
+
   setInfiniteTrigger: ->
     @scrollBoxSelector = @scrollBoxSelector || window
     @scrollBox = $(@scrollBoxSelector)
@@ -814,24 +821,24 @@

         if (@scrollBox.scrollTop() + @scrollBox[0].offsetHeight >= l)
           @sess("limit", @sess("limit") + @infiniteStep)
-
+
           ###
           if @lastPage < @sess "totalPages"
             console.log "i want page #{@lastPage + 1}"
             @sess("currentPage", @lastPage + 1)
           ###
-
+
       , @infiniteRateLimit * 1000
     ), @
-
+
   checkQueue: _.throttle ->
     #@log "Checking queue"
     cp = @currentPage()
     neighbors = @neighbors cp
-
+
     # If we haven't yet received the current page then clear all the other subscriptions and requests
     # and get the current page
-
+
     if !@received[cp]
       #@log "Haven't yet received the current page (#{cp})."
       @clearQueue()
@@ -844,9 +851,9 @@
             delete @subscriptions[k]
             @subscriptions.length--
           delete @requested[k]
-
+
     # If we do have the current page then queue the neighbours
-
+
     else if @queue.length
       #@log "Current page (#{cp}) already received."
       while @queue.length > 0
@@ -856,22 +863,22 @@
           @requestPage i
           break
   , 500
-
+
   currentPage: ->
     if Meteor.isClient and @sess("currentPage")?
       @sess "currentPage"
     else
       @_currentPage
-
+
   isReady: ->
     @sess "ready"
-
+
   ready: (p) ->
     if p is true or p is @currentPage() and Session?
       @sess "ready", true
-
+
   checkInitPage: ->
-    if @init and !@initPage
+    if @init and !@initPage
       if @router
         Router.current()?.route?.getName()
         try
@@ -883,7 +890,7 @@
     @init = false
     @sess "oldPage", @initPage
     @sess "currentPage", @initPage
-
+
   getPage: (page) ->
     if Meteor.isClient
       page = @currentPage()  if !page?
@@ -891,18 +898,18 @@
       return  if page is NaN
       total = @sess "totalPages"
       return @ready true  if total is 0
-
+
       # Request data for the page
-
+
       if page <= total
         @requestPage page
         @queueNeighbors page
         @checkQueue()
-
-      # Return the content of this page
+
+      # Return the content of this page
       #
       # The contents will be updated (as will the page) as data arrives from the server
-
+
       if @infinite
         n = @Collection.find({},
           fields: @fields
@@ -935,11 +942,11 @@
             ##########
             @requestPage @sess "currentPage"
             @countPages()
-
+
       c.fetch()
-
+
   # Subscribes to the given page
-
+
   requestPage: (page) ->
     return  if !page or @requested[page] or @received[page]
     @log "Requesting page #{page}"
@@ -976,9 +983,9 @@
         @subscriptions.length++
         #@log "Number of subscriptions: #{@subscriptions.length}"
       ), @, page
-
+
   # Called when a page has been received
-
+
   onPage: (page) ->
     @log "Received page #{page}"
     @beforeFirstReady = false


fix(thread-list): Don't rely on `zip` since it can become off-by-one forever

--- internal_packages/thread-list/lib/thread-list-data-source.coffee
+++ internal_packages/thread-list/lib/thread-list-data-source.coffee
@@ -18,13 +18,15 @@
   #    we make a single database query and load /all/ the message metadata for
   #    the new threads at once. (This is a performance optimization -it's about
   #    ~80msec faster than making 100 queries for 100 new thread ids separately.)
-  Rx.Observable.zip([
-    $threadsResultSet,
-    $threadsResultSet.flatMapLatest (threadsResultSet) =>
+  $threadsResultSet.flatMapLatest (threadsResultSet) =>
       missingIds = threadsResultSet.ids().filter (id) -> not $messagesResultSets[id]
-      return Rx.Observable.from([[]]) if missingIds.length is 0
-      Rx.Observable.fromPromise(DatabaseStore.findAll(Message, threadId: missingIds))
-  ])
+      if missingIds.length is 0
+        promise = Promise.resolve([threadsResultSet, []])
+      else
+        promise = DatabaseStore.findAll(Message, threadId: missingIds).then (messages) =>
+          Promise.resolve([threadsResultSet, messages])
+
+      return Rx.Observable.fromPromise(promise)

   # 3. when that finishes, we group the loaded messsages by threadId and create
   #    the missing observables. Creating a query subscription would normally load


add to referrers

add to referrers

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -59,7 +59,7 @@
   app.use sharify

   # Referral spam blocker
-  blocker.setReferrers ['tkpassword.com']
+  blocker.addToReferrers ['tkpassword.com']
   app.use blocker.send404

   # Development only


Mount flash middlewares

Mount flash middlewares

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -28,6 +28,8 @@
 ensureSSL = require './middleware/ensure_ssl'
 errorHandler = require "../components/error_handler"
 { notFoundError, loginError } = require('./middleware/errors')
+flash = require 'connect-flash'
+flashMiddleware = require './middleware/flash'

 # Setup sharify constants & require dependencies that use sharify data
 sharify.data =
@@ -118,6 +120,8 @@
   app.use ensureSSL

   # General helpers and express middleware
+  app.use flash()
+  app.use flashMiddleware
   app.use localsMiddleware
   app.use micrositeMiddleware
   app.use helpersMiddleware
@@ -138,6 +142,7 @@
   app.use require "../apps/auth"
   app.use require "../apps/browse"
   app.use require "../apps/feature"
+  app.use require "../apps/flash"
   app.use require "../apps/galleries"
   app.use require "../apps/tag"
   app.use require "../apps/gene"


Added documentation for two experimental functions, now that they've been officially merged.

--- lib/suite.coffee
+++ lib/suite.coffee
@@ -31,21 +31,43 @@
     # A list of suites and test runners.
     @_tests_and_suites = []

-  # (callback) -> undefined
-  _run: (callback,
-      before_alls=[], before_eachs=[], after_eachs=[], index=0,
-      report={
-        total_tests: 0
+  # (callback, before_alls=[], before_eachs=[], after_eachs=[],
+  #   index=0, report={}) -> undefined
+  #
+  # Params:
+  #   callback: A function called when the entirty of the suite is done.
+  #   before_alls: Optional. A list of before_alls to start with.
+  #   before_eachs: Optional. A list of before_eachs to start with.
+  #   after_eachs: Optional. A list of after_eachs to start with.
+  #   index: Optional. The index of @_tests_and_suites this recursive
+  #     iteration is currently at.
+  #   report: Optional. The compiled report from any tests and suites ran.
+  #
+  # Desc:
+  #   An internal function which iterates over @_tests_and_suites. It includes
+  #   optional arguments for parental pre/post hooks as well.
+  _run: (callback, before_alls=[], before_eachs=[], after_eachs=[], index=0,
+        report={
+          total_tests: 0
         }) =>

+    # If we're at the start of the iterations, append @this's pre/post
+    # hook runners to the supplied argument pre/post runners.
     if index is 0
       before_alls.push @_before_alls...
       before_eachs.push @_before_eachs...
+      # Note that the after_each's need to be added to the *beginning*
+      # of the list. This allows the following design..
+      # Their Befores > Our Befores > Test < Our Afters < Their Afters
       after_eachs[...0] = @_after_eachs

+    # Our test or suite
     item = @_tests_and_suites[index]

+    # If item does not exist, callback
     if not item?
+      # If we have one or more tests, run the after_all runners, and
+      # then callback.
       if report.total_tests > 0
         @_run_runners @_after_alls, ->
           callback report
@@ -53,18 +75,25 @@
         callback report
       return

-    before_alls_callback = (runner_reports) =>
-      before_alls = []
+    # Called after @_run_runners is finished iterating through befores.
+    # Note that these are both `before_alls` and `before_eachs`
+    befores_callback = (runner_reports) =>
+      # Since we just ran before_alls, empty it if we need to.
+      if before_alls.length > 0
+        before_alls = []
       item.run test_callback

+    # Called when our test is done.
     test_callback = (test_report) =>
       # We're just faking the report for now.
       report.total_tests += 1
       @_run_runners after_eachs, after_eachs_callback

+    # Called after `@_run_runners` is finished iterating through `after_eachs`
     after_eachs_callback = (runner_reports) =>
       @_run callback, before_alls, before_eachs, after_eachs, ++index, report

+    # Called when item is a suite, and `item._run` has finished.
     suite_callback = (suite_report) =>
       if suite_report.total_tests > 0
         before_alls = []
@@ -72,21 +101,33 @@
       @_run callback, before_alls, before_eachs, after_eachs, ++index, report

     if item instanceof Test
-      runners = []
-      runners.push before_alls...
-      runners.push before_eachs...
-      @_run_runners runners, before_alls_callback
+      befores = []
+      befores.push before_alls...
+      befores.push before_eachs...
+      @_run_runners befores, befores_callback
     else
       item._run suite_callback, before_alls[..],
         before_eachs[..], after_eachs[..]

+  # (runners, callback, index=0, reports=[]) -> undefined
+  #
+  # Params:
+  #   runners: A list of runners to iterate over and execute.
+  #   callback: The callback
+  #   index: Optional. The recursive index.
+  #   reports: Optional. The reports from each runner.
+  #
+  # Desc:
+  #   An internal function to run a list of runners, and callback when
+  #   complete.
   _run_runners: (runners, callback, index=0, reports=[]) =>
     runner = runners[index]
-
+    # If the item is null, callback with our collective reports.
     if not runner?
       callback reports
       return

+    # Run the runner!
     runner.run (report) =>
       reports.push report
       @_run_runners runners, callback, ++index, reports


debug

debug

--- lib/token.coffee
+++ lib/token.coffee
@@ -31,6 +31,7 @@
     contractAddress

   @transfer: (contractAddress, recipient, amount) ->
+    d { config }
     web3 = new Web3
     errors = {}
     unless web3.isAddress contractAddress
@@ -47,7 +48,8 @@
     Pudding.setWeb3 web3
     TokenContract.load Pudding
     tokenContract = TokenContract.at contractAddress
-    sender = config.rpc.from or throw new Error(
+    d { tokenContract }
+     sender = config.rpc.from or throw new Error(
       "please set `rpc.from` property: #{envDir}/config.json")

     Promise.resolve()


comments

comments

--- test/algebra/multiplyTests.coffee
+++ test/algebra/multiplyTests.coffee
@@ -34,8 +34,8 @@

     resultSphere = multiply contentSphere, filterSphere
     edges = resultSphere.edges
-    edges["time poem -> the"].get('weight').should.equal 25  # sample: original edges copied from content sphere
-    size(edges).should.equal size(contentSphere.edges) + 3, "expected result sphere to have only 3 more edges than content sphere, but got: #{pjson edges}"
+    edges["time poem -> the"].get('weight').should.equal 25  # check a sample: original edges are copied from content sphere
+    size(edges).should.equal size(contentSphere.edges) + 3, "expected result sphere to have 3 more edges than content sphere, but got: #{pjson edges}"

     edges["time poem -> saturn"].get('weight').should.equal 3
     edges["beauty poem -> venus"].get('weight').should.equal 2


Remove hard-coded API version from Route53 tests

--- test/services/route53.spec.coffee
+++ test/services/route53.spec.coffee
@@ -3,9 +3,11 @@

 describe 'AWS.Route53', ->

-  r53 = null
+  service = null
+  api = null
   beforeEach ->
-    r53 = new AWS.Route53()
+    service = new AWS.Route53()
+    api = service.api.apiVersion

   describe 'setEndpoint', ->
     it 'always enables SSL if no endpoint is set', ->
@@ -33,7 +35,7 @@
     it 'correctly builds the XML document', ->
       xml =
         """
-        <ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2012-12-12/">
+        <ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/#{api}/">
           <ChangeBatch>
             <Comment>comment</Comment>
             <Changes>
@@ -71,5 +73,5 @@
             }
           ]
           Comment: 'comment'
-      r53.changeResourceRecordSets params, (err, data) ->
+      service.changeResourceRecordSets params, (err, data) ->
         helpers.matchXML(this.request.httpRequest.body, xml)


add a test for selecting repo by label after a selection by id was made

currently the test only implemented selecting by label first and then selecting by id, to see that labels are deselected and by ID is selected. the other way around was missing and added with this commit

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -327,6 +327,12 @@
     expect(scope.repoSelected).toBe(allRepos1[0])
     expect(scope.repoSelectedLabel).toBe(null)

+    opts = {'query': [['repolabel',allRepos1[0].label],['outdated','true']]}
+    select_by_label = () -> scope.selectRepoLabel(allRepos1[0].label)
+    paginateResponse @httpBackend, '/v1/packages', allPackages1, select_by_label, opts
+    expect(scope.repoSelected).toBe(null)
+    expect(scope.repoSelectedLabel).toBe(allRepos1[0].label)
+
   it 'provides all repository labels (uniq)', () ->
     scope.repos = [
       {'label': 'a'}, {'label': 'a'},


Use the cloned scan parent.

Use the cloned scan parent.

--- test/unit/registrationSpec.coffee
+++ test/unit/registrationSpec.coffee
@@ -7,12 +7,14 @@
     # The mock objects.
     mock =
       scan:
+        _cls: 'Scan'
         title: 'Breast Subject 1 Session 1 Scan 1'
         registrations: [
+          _cls: 'Registration'
           protocol: 'rp1'
         ]

-    describe.only 'Unit Testing the Registration Service', ->
+    describe 'Unit Testing the Registration Service', ->
       Registration = null
       registration = null
       scan = null
@@ -26,7 +28,7 @@
         scan = _.cloneDeep(mock.scan)
         registration = scan.registrations[0]
         # Extend the test registration.
-        Registration.extend(registration, mock.scan, 1)
+        Registration.extend(registration, scan, 1)

       describe 'find', ->
         it 'should find the scan registration', ->


:closed_umbrella::truck: Updated in browser at strd6.github.io/editor

--- test/google_spreadsheet.coffee
+++ test/google_spreadsheet.coffee
@@ -1,6 +1,8 @@
 Spreadsheet = require "../lib/spreadsheet"

 describe "Google Spreadsheet wrapper", ->
-  it "loads spreadsheet from a given key", ->
+  it "loads spreadsheet from a given key", (done) ->
     Spreadsheet.load "0ArtCBkZR37MmdFJqbjloVEp1OFZLWDJ6M29OcXQ1WkE", (data) ->
-      assert.ok data.feed.author[0].name.$t is "yahivin2"
+      console.log data.feed.author[0].name.$t
+      assert data.feed.author[0].name.$t is "yahivin"
+      done()


Add user agent passthrough test for apple

--- test/passport/callbacks.coffee
+++ test/passport/callbacks.coffee
@@ -67,3 +67,11 @@
     res = { body: { error_description: 'no account linked' }, status: 403 }
     @request.end.args[0][0](null, res)
     @request.set.args[1][0]['User-Agent'].should.equal 'foo-bar-baz-ua'
+
+  it 'passes the user agent through apple signup', ->
+    @req.get.returns 'foo-bar-baz-ua'
+    cbs.apple @req, 'foo-token', 'refresh-token', 'id-token', {}
+    res = { body: { error_description: 'no account linked' }, status: 403 }
+    @request.end.args[0][0](null, res)
+    @request.set.args[1][0]['User-Agent'].should.equal 'foo-bar-baz-ua'
+


added test for service

added test for service

--- platform/src/tests/tests.coffee
+++ platform/src/tests/tests.coffee
@@ -330,30 +330,26 @@
       expect(newModel.people[0].last).toBe("bill")
       expect(newModel.people[1].last).toBe("joe")

-    it "binds across constant arrays", ->
-      oldModel = {}
-      people = [
-        { first: "bill", last: "bobson" }
-        { first: "joe", last: "johnson" }
-      ]
-
-      # make test function to spy on
-      globals.testFunction = jasmine.createSpy()
-
-      layout =
-        bind:
-          from:
-            person: people
-        children: [
-          {
-            call: "testFunction"
-            params: ["$person"]
-          }
-        ]
-
-      constants = new GE.NodeVisitorConstants(oldModel, {}, {}, {})
-      GE.visitNode(layout, constants, {})
-
-      for person in people
-        expect(globals.testFunction).toHaveBeenCalledWith(person)
-
+    it "communicates with services", ->
+      oldServiceData =
+        serviceA:
+          a: 1
+
+      actions =
+        incrementServiceData:
+          paramDefs:
+            service: ""
+          update: ->
+            expect(@params.service.a).toBe(1)
+            @params.service.a++
+
+      layout =
+        action: "incrementServiceData"
+        params:
+          service: "@service:serviceA"
+
+      constants = new GE.NodeVisitorConstants({}, oldServiceData, {}, actions)
+      results = GE.visitNode(layout, constants, {})
+      newServiceData = GE.applyPatches(results.servicePatches, oldServiceData)
+
+      expect(newServiceData.serviceA.a).toBe(2)


add failing tests for #55

add failing tests for #55

--- tests/mocha/unit/normalize.coffee
+++ tests/mocha/unit/normalize.coffee
@@ -231,7 +231,7 @@
     )
   )

-  describe('normalizeTag', ->
+  describe('normalizeTags', ->
     normalizer = null
     before( ->
       container = $('#test-container').get(0)
@@ -241,9 +241,7 @@

     attrTest = new ScribeHtmlTest(
       fn: (lineNode) ->
-        lineNode.firstChild.setAttribute('data-test', 'test')
-        lineNode.firstChild.setAttribute('width', '100px')
-        normalizer.normalizeLine(lineNode)
+        normalizer.normalizeTags(lineNode)
     )

     attrTest.run('strip extraneous attributes',
@@ -260,5 +258,20 @@
       initial:  'Color'
       expected: 'Color'
     )
+
+    attrTest.run('separate multiple styles',
+      initial: 'Color'
+      expected: 'Color'
+    )
+
+    attrTest.run('separate style from non-span',
+      initial: 'Color'
+      expected: 'Color'
+    )
+
+    attrTest.run('separate multiple styles from non-span',
+      initial: 'Color'
+      expected: 'Color'
+    )
   )
 )


removed unused requires and function

removed unused requires and function

--- test/draft4/invalid.coffee
+++ test/draft4/invalid.coffee
@@ -1,12 +1,8 @@
-fs = require "fs"
 glob = require "glob"
 assert = require "assert"
 Testify = require "testify"

 {draft4} = require("../../src/index")
-
-throws = (error, block) ->
-  assert.throws(block, error)

 helpers =
   json_types:
@@ -21,7 +17,7 @@
       for name, value of @values when !(name in names)
         value

-Testify.test "Invalid schemas", (context) ->
+Testify.test "Rejecting invalid schemas", (context) ->

   files = glob.sync "#{__dirname}/invalid/*.coffee"
   l = "#{__dirname}/invalid/".length


Remove trailing whitespace in test/error_messages.coffee

Trailing whitespace is generally considered 'bad style' and is often
linted against or even simply removed by text editors.

One of the tests in test/error_messages.coffee depended on trailing
whitespace, making the file tricky to work with for people whose
editor is configured to remove trailing whitespace. The alternative is
to use a literal "\n" and escape the line break.

--- test/error_messages.coffee
+++ test/error_messages.coffee
@@ -428,7 +428,7 @@
   assertErrorFormat '''
     ///a \\u002 0 space///
   ''', '''
-    [stdin]:1:6: error: invalid escape sequence \\u002
+    [stdin]:1:6: error: invalid escape sequence \\u002 \n\
     ///a \\u002 0 space///
          ^\^^^^^
   '''


Automatically refresh main stream every 60s

--- frontend/src/js/admin/admin.coffee
+++ frontend/src/js/admin/admin.coffee
@@ -137,6 +137,17 @@
         visualizer = new Fontana.Visualizer(container, datasource)
         visualizer.start(settings)

+    # Automatically refresh /all db. If the admin page is not
+    # displayed, the /moderated stream will not refresh.
+    fetchMessages = ->
+        refresh()
+        scheduleFetchMessages()
+
+    scheduleFetchMessages = ->
+        fetchMessagesTimer = setTimeout((=> fetchMessages()), 60000)
+
+    refresh()
+
     HTMLFontana = (settings={})->
         if visualizer
             visualizer.stop()
@@ -147,6 +158,7 @@
     # Prepare our datasource, the messages will disappear soon...
     HTMLFontana.datasource = new Fontana.datasources.HTML(container)
     HTMLFontana.datasource.getMessages()
+    scheduleFetchMessages()

     # Bindings
     $(".settings", controls).click -> toggleSettings.call(this)


Enable new createOnChange option for package sync

--- atom/config.cson
+++ atom/config.cson
@@ -60,6 +60,7 @@
   "markdown-deluxe":
     fontFamily: "Hack"
   "package-sync":
+    createOnChange: true
     forceOverwrite: true
   "plantuml-preview":
     jarLocation: "/Users/randy/bin/plantuml.jar"


Migrate atom-beautify settings

Migrate atom-beautify settings

--- atom/config.cson
+++ atom/config.cson
@@ -7,14 +7,19 @@
       totalCount: {}
     screenShake: {}
   "atom-beautify":
-    analytics: false
-    c_configPath: "/home/stephen/.uncrustify.cfg"
-    js_wrap_line_length: 120
-    language_rust_beautify_on_save: true
-    php_cs_fixer_path: "/home/stephen/.composer/vendor/bin/php-cs-fixer"
-    php_fixers: "-blankline_after_open_tag,-phpdoc_params,-return,-spaces_cast,-unused_use,ordered_use,no_blank_lines_before_namespace,multiline_spaces_before_semicolon,concat_with_spaces"
-    php_level: "symfony"
-    rust_rustfmt_path: "/home/stephen/.cargo/bin/rustfmt"
+    c:
+      configPath: "/home/stephen/.uncrustify.cfg"
+    general:
+      _analyticsUserId: "97b66e2b-19db-4d41-95dc-985c45800b5b"
+      analytics: false
+    js:
+      wrap_line_length: 120
+    php:
+      cs_fixer_path: "/home/stephen/.composer/vendor/bin/php-cs-fixer"
+      fixers: "-blankline_after_open_tag,-phpdoc_params,-return,-spaces_cast,-unused_use,ordered_use,no_blank_lines_before_namespace,multiline_spaces_before_semicolon,concat_with_spaces"
+      level: "symfony"
+    rust:
+      rustfmt_path: "/home/stephen/.cargo/bin/rustfmt"
   "atom-ctags":
     autoBuildTagsWhenActive: true
     cmd: "/usr/bin/ctags"
@@ -23,16 +28,17 @@
   "atom-material-ui":
     colors:
       abaseColor:
-        red: 47
-        green: 52
-        blue: 63
+        red: 96
+        green: 125
+        blue: 139
         alpha: 1
-      predefinedColor: "Grey"
+      predefinedColor: "Blue Grey"
     fonts: {}
     tabs:
       compactTabs: true
       tintedTabBar: true
     treeView:
+      blendTabs: true
       compactList: true
     ui:
       panelContrast: true
@@ -47,7 +53,6 @@
       "language-gfm"
       "background-tips"
       "symbols-view"
-      "atom-ctags"
       "bookmarks"
       "metrics"
     ]
@@ -65,14 +70,13 @@
     ]
     themes: [
       "atom-material-ui"
-      "peacocks-in-space-syntax"
+      "rain-syntax"
     ]
   docblockr:
     short_primitives: true
   editor:
     autoIndentOnPaste: false
     fontFamily: "Input Mono"
-    fontSize: 12
     invisibles: {}
     lineHeight: 1.75
     preferredLineLength: 120
@@ -89,9 +93,10 @@
     firstTime: true
   latex:
     alwaysOpenResultInAtom: true
+  linter: {}
   "linter-flake8": {}
   "linter-php":
-    executablePath: "/home/stephen/.phpbrew/php/php-7.0.4/bin/php"
+    executablePath: "/home/stephen/.phpbrew/php/php-7.0.6/bin/php"
   "markdown-preview":
     breakOnSingleNewline: true
     liveUpdate: false
@@ -119,8 +124,16 @@
   tabs:
     enableVcsColoring: true
     usePreviewTabs: true
+  term3:
+    colors:
+      background:
+        red: 69
+        green: 84
+        blue: 99
+        alpha: 1
   "tree-view":
     hideIgnoredNames: true
+    showOnRightSide: true
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
   wordcount:
@@ -140,3 +153,7 @@
   editor:
     preferredLineLength: 120
     softWrapHangingIndent: 0
+    tabLength: 4
+".rust.source":
+  editor:
+    preferredLineLength: 120


link terminal to current file

link terminal to current file

--- atom/config.cson
+++ atom/config.cson
@@ -41,6 +41,7 @@
         brightYellow: "#e0b15f"
     core:
       mapTerminalsTo: "Folder"
+      workingDirectory: "Active File"
     style: {}
     toggles:
       runInsertedText: false


atom vim-mode

atom vim-mode

--- atom/config.cson
+++ atom/config.cson
@@ -74,6 +74,9 @@
     diagnosticsOnFly: true
   "autocomplete-flow": {}
   "one-dark-ui": {}
+  "vim-mode":
+    useClipboardAsDefaultRegister: true
+    useSmartcaseForSearch: true
 ".css.less.source":
   editor:
     tabLength: 4


Hide VCS ignored files by default

Hide VCS ignored files by default

--- atom/config.cson
+++ atom/config.cson
@@ -132,6 +132,7 @@
     onSave: "untabify"
   "tree-view":
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false
   "white-cursor":


add cancel adaptation

add cancel adaptation

--- lib/client/javascripts/nali/collection.js.coffee
+++ lib/client/javascripts/nali/collection.js.coffee
@@ -6,7 +6,8 @@

   cloning: ->
     @subscribeTo @Model, "create.#{ @model._name.lower() }", @onModelCreated
-    @adaptations = []
+    @subscribeTo @Model, "update.#{ @model._name.lower() }", @onModelUpdated
+    @adaptations = apply: [], cancel: []
     @ordering    = {}
     @adaptCollection()
     @
@@ -18,11 +19,14 @@
     @add model if model.isCorrect @filters
     @

-  onModelUpdated: ( model ) ->
-    if model.isCorrect @filters
-      @reorder()
-      @trigger 'update.model', model
-    else @remove model
+  onModelUpdated: ( extModel, model ) ->
+    if model in @
+      if model.isCorrect @filters
+        @reorder()
+        @trigger 'update.model', model
+      else @remove model
+    else if model.isCorrect @filters
+      @add model
     @

   onModelDestroyed: ( model ) ->
@@ -37,29 +41,27 @@
           @
     @

-  adaptModel: ( model ) ->
-    adaptation.call @, model for adaptation in @adaptations
+  adaptModel: ( model, type = 'apply' ) ->
+    adaptation.call @, model for adaptation in @adaptations[ type ]
     @

-  adaptation: ( callback ) ->
-    callback.call @, model for model in @
-    @adaptations.push callback
+  adaptation: ( apply, cancel ) ->
+    apply.call @, model for model in @
+    @adaptations.apply.push apply
+    @adaptations.cancel.unshift cancel if cancel
     @

   add: ( model ) ->
     Array::push.call @, model
     @adaptModel  model
     @subscribeTo model, 'destroy', @onModelDestroyed
-    @subscribeTo model, 'update',  @onModelUpdated
     @reorder()
     @trigger 'update.length.add', model
     @trigger 'update.length', 'add', model
     @

-  indexOf: ( model ) ->
-    Array::indexOf.call @, model
-
   remove: ( model ) ->
+    @adaptModel model, 'cancel'
     Array::splice.call @, @indexOf( model ), 1
     @unsubscribeFrom model
     @reorder()
@@ -71,6 +73,9 @@
     delete @[ index ] for model, index in @
     @length = 0
     @
+
+  indexOf: ( model ) ->
+    Array::indexOf.call @, model

   sort: ( sorter ) ->
     Array::sort.call @, sorter
@@ -111,17 +116,19 @@

   show: ( viewName, insertTo, isRelation = false ) ->
     @adaptation ( model ) ->
-      view = model.view viewName
-      if isRelation
-        view.subscribeTo @, 'reset', view.hide
-      else unless @visible
-        @visible = true
-        @prepareViewToShow view
-        @hideVisibleViews()
-      else
-        @::visibleViews.push view
-      view.show insertTo
-      @inside ?= view.element[0].parentNode
+        view = model.view viewName
+        if isRelation
+          view.subscribeTo @, 'reset', view.hide
+        else unless @visible
+          @visible = true
+          @prepareViewToShow view
+          @hideVisibleViews()
+        else
+          @::visibleViews.push view
+        view.show insertTo
+        @inside ?= view.element[0].parentNode
+      , ( model ) ->
+        model.hide viewName
     @

   prepareViewToShow: ( view ) ->


unfurling test

unfurling test

--- scripts/TM-Github.coffee
+++ scripts/TM-Github.coffee
@@ -46,4 +46,4 @@
     res.reply 'http://4.bp.blogspot.com/-7g82Ltxt7wI/VLqbd1CLOyI/AAAAAAAAKcc/kisGogPKcmo/s1600/Screen%2BShot%2B2014-11-02%2Bat%2B16.49.16.png'
     res.reply 'http://2.bp.blogspot.com/-PUi2erHHCKA/VLqbmSw69pI/AAAAAAAAKeY/fWA8nqSam8k/s1600/Screen%2BShot%2B2014-11-18%2Bat%2B10.57.13.png'
     res.reply 'http://3.bp.blogspot.com/-WVlMYyq0B0g/VLqbshL9y1I/AAAAAAAAKfw/x8fqum-5gnc/s1600/Screen%2BShot%2B2014-11-18%2Bat%2B14.31.59.png'
-    res.reply 'http://1.bp.blogspot.com/-NQMcOYiwk5g/VLqbb01UNKI/AAAAAAAAKcU/IJZ5Evx5O8w/s1600/Screen%2BShot%2B2014-11-02%2Bat%2B16.44.26.png'
+    res.reply '<http://1.bp.blogspot.com/-NQMcOYiwk5g/VLqbb01UNKI/AAAAAAAAKcU/IJZ5Evx5O8w/s1600/Screen%2BShot%2B2014-11-02%2Bat%2B16.44.26.png>'


fix lastfm regex

fix lastfm regex

--- scripts/lastfm_np.coffee
+++ scripts/lastfm_np.coffee
@@ -17,13 +17,14 @@
 #   sn0opy

 getSong = (msg, usr) ->
-  user = usr ? msg.match[2]
+  user = usr ? msg.match[1]
   apiKey = process.env.HUBOT_LASTFM_APIKEY
   msg.http('http://ws.audioscrobbler.com/2.0/?')
     .query(method: 'user.getrecenttracks', user: user, api_key: apiKey, format: 'json')
     .get() (err, res, body) ->
       results = JSON.parse(body)
       if results.error
+        msg.send user
         msg.send results.message
         return
       song = results.recenttracks.track[0]


Added ability to specify a sketchclass when grabbing recordsets

--- scripts/reportTab.coffee
+++ scripts/reportTab.coffee
@@ -90,24 +90,33 @@
     _.filter results, (result) ->
       result.paramName not in ['ResultCode', 'ResultMsg']

-  recordSet: (dependency, paramName) ->
+  recordSet: (dependency, paramName, sketchClassId) ->
     unless dependency in @dependencies
       throw new Error "Unknown dependency #{dependency}"
-    dep = _.find @allResults, (result) -> result.get('name') is dependency
+    if sketchClassId
+      dep = _.find @allResults, (result) ->
+        result.get('name') is dependency and
+          result.get('sketchClass') is sketchClassId
+    else
+      dep = _.find @allResults, (result) -> result.get('name') is dependency
     unless dep
       console.log @allResults
       throw new Error "Could not find results for #{dependency}."
+    console.log 'dep', dep
     param = _.find dep.get('data').results, (param) ->
       param.paramName is paramName
     unless param
       console.log dep.get('data').results
       throw new Error "Could not find param #{paramName} in #{dependency}"
-    new RecordSet(param)
+    rs = new RecordSet(param)
+    rs.sketchClass = dep.get('sketchClass')
+    rs

   recordSets: (dependency, paramName) ->
     unless dependency in @dependencies
       throw new Error "Unknown dependency #{dependency}"
     deps = _.filter @allResults, (result) -> result.get('name') is dependency
+    console.log 'deps', deps
     unless deps.length
       console.log @allResults
       throw new Error "Could not find results for #{dependency}."
@@ -118,7 +127,9 @@
       unless param
         console.log dep.get('data').results
         throw new Error "Could not find param #{paramName} in #{dependency}"
-      params.push new RecordSet(param)
+      rs = new RecordSet(param)
+      rs.sketchClass = dep.get('sketchClass')
+      params.push rs
     return params


Fuck jimbo

Fuck jimbo

Your API broke my API

--- scripts/wikipedia.coffee
+++ scripts/wikipedia.coffee
@@ -1,93 +0,0 @@
-# Description:
-#   None
-#
-# Dependencies:
-#   "htmlparser": "1.7.6"
-#   "soupselect": "0.2.0"
-#   "underscore": "1.3.3"
-#   "underscore.string": "2.3.0"
-#
-# Configuration:
-#   None
-#
-# Commands:
-#   hubot wiki me <query> - Searches for <query> on Wikipedia.
-#
-# Author:
-#   h3h
-
-_          = require("underscore")
-_s         = require("underscore.string")
-Select     = require("soupselect").select
-HTMLParser = require "htmlparser"
-
-module.exports = (robot) ->
-  robot.respond /(wiki)( me)? (.*)/i, (msg) ->
-    wikiMe robot, msg.match[3], (text, url) ->
-      msg.send text
-      msg.send url if url
-
-wikiMe = (robot, query, cb) ->
-  articleURL = makeArticleURL(makeTitleFromQuery(query))
-
-  robot.http(articleURL)
-    .header('User-Agent', 'Hubot Wikipedia Script')
-    .get() (err, res, body) ->
-      return cb "Sorry, the tubes are broken." if err
-
-      if res.statusCode is 301
-        return cb res.headers.location
-
-      if /does not have an article/.test body
-        return cb "Wikipedia has no idea what you're talking about."
-
-      paragraphs = parseHTML(body, "p")
-
-      bodyText = findBestParagraph(paragraphs) or "Have a look for yourself:"
-      cb bodyText, articleURL
-
-# Utility Methods
-
-childrenOfType = (root, nodeType) ->
-  return [root] if root?.type is nodeType
-
-  if root?.children?.length > 0
-    return (childrenOfType(child, nodeType) for child in root.children)
-
-  []
-
-findBestParagraph = (paragraphs) ->
-  return null if paragraphs.length is 0
-
-  childs = _.flatten childrenOfType(paragraphs[0], 'text')
-  text = (textNode.data for textNode in childs).join ''
-
-  # remove parentheticals (even nested ones)
-  text = text.replace(/\s*\([^()]*?\)/g, '').replace(/\s*\([^()]*?\)/g, '')
-  text = text.replace(/\s{2,}/g, ' ')               # squash whitespace
-  text = text.replace(/\[[\d\s]+\]/g, '')           # remove citations
-  text = _s.unescapeHTML(text)                      # get rid of nasties
-
-  # if non-letters are the majority in the paragraph, skip it
-  if text.replace(/[^a-zA-Z]/g, '').length < 35
-    findBestParagraph(paragraphs.slice(1))
-  else
-    text
-
-makeArticleURL = (title) ->
-  "https://en.wikipedia.org/wiki/#{encodeURIComponent(title)}"
-
-makeTitleFromQuery = (query) ->
-  strCapitalize(_s.trim(query).replace(/[ ]/g, '_'))
-
-parseHTML = (html, selector) ->
-  handler = new HTMLParser.DefaultHandler((() ->),
-    ignoreWhitespace: true
-  )
-  parser  = new HTMLParser.Parser handler
-  parser.parseComplete html
-
-  Select handler.dom, selector
-
-strCapitalize = (str) ->
-  return str.charAt(0).toUpperCase() + str.substring(1);


find in files: move state to flux store

find in files: move state to flux store

--- salvus/page/project_search.cjsx
+++ salvus/page/project_search.cjsx
@@ -41,13 +41,14 @@

     propTypes :
         search_cb    : rtypes.func
-        set_state_cb : rtypes.func
-
-    getInitialState : ->
+        user_input   : rtypes.string
+        actions      : rtypes.object
+
+    getDefaultProps : ->
         user_input : ''

     clear_and_focus_input : ->
-        @setState(user_input : '')
+        @props.actions.setTo(user_input : '')
         @refs.project_search_input.getInputDOMNode().focus()

     clear_button : ->
@@ -57,8 +58,7 @@

     handle_change : ->
         user_input = @refs.project_search_input.getValue()
-        @setState(user_input : user_input)
-        @props.set_state_cb(user_input : user_input)
+        @props.actions.setTo(user_input : user_input)

     submit : (event) ->
         event.preventDefault()
@@ -71,7 +71,7 @@
                 autoFocus
                 type        = 'text'
                 placeholder = 'Search files in the current directory...'
-                value       = {@state.user_input}
+                value       = {@props.user_input}
                 buttonAfter = {@clear_button()}
                 onChange    = {@handle_change} />
         </form>
@@ -85,15 +85,6 @@
         most_recent_path : rtypes.string
         project_id       : rtypes.string
         search_error     : rtypes.string
-
-    shouldComponentUpdate : (nextProps) ->
-        a = @props
-        b = nextProps
-        return a.results != b.results                or
-            a.too_many_results != b.too_many_results or
-            a.project_id != b.project_id             or
-            a.search_error != b.search_error         or
-            a.most_recent_path != b.most_recent_path

     too_many_results : ->
         if @props.too_many_results
@@ -109,11 +100,11 @@
             return <Alert bsStyle='warning'>There were no results for your search</Alert>
         for i, result of @props.results
                 <ProjectSearchResultLine
-                    project_id       = @props.project_id
                     key              = {i}
                     filename         = {result.filename}
                     description      = {result.description}
-                    most_recent_path = {@props.most_recent_path} />
+                    most_recent_path = {@props.most_recent_path}
+                    actions          = {@props.actions} />

     render : ->
         results_well_styles =
@@ -135,8 +126,11 @@
         command            : rtypes.string.isRequired
         most_recent_search : rtypes.string.isRequired
         search_results     : rtypes.array
-
-    getInitialState : ->
+        info_visible       : rtypes.bool
+        search_error       : rtypes.string
+        actions            : rtypes.object
+
+    getDefaultProps : ->
         info_visible : false

     output_path : ->
@@ -145,10 +139,10 @@
         return @props.most_recent_path

     change_info_visible : ->
-        @setState(info_visible : not @state.info_visible)
+        @props.actions.setTo(info_visible : not @props.info_visible)

     get_info : ->
-        if @state.info_visible
+        if @props.info_visible
             <Alert bsStyle='info'>

@@ -181,16 +175,19 @@
     propTypes :
         checkboxes      : rtypes.object
         toggle_checkbox : rtypes.func
-        search_cb       : rtypes.func
-
-    getInitialState: ->
+        case_sensitive  : rtypes.bool
+        subdirectories  : rtypes.bool
+        hidden_files    : rtypes.bool
+        actions         : rtypes.object
+
+    getDefaultProps: ->
         case_sensitive  : false    # do not change any of these to true without also changing
         subdirectories  : false    # the "@checkbox_state = {}" below.
         hidden_files    : false

     handle_change : (name) ->
         @props.toggle_checkbox(name)
-        @setState("#{name}":not @state[name])
+        @props.actions.setTo("#{name}":not @props[name])

     render_checkbox : (name, label) ->
         <Input
@@ -198,7 +195,7 @@
             key      = {name}
             type     = 'checkbox'
             label    = {label}
-            checked  = {@state[name]}
+            checked  = {@props[name]}
             onChange = {=>@handle_change(name)} />

     render : ->
@@ -210,20 +207,20 @@
     displayName : 'ProjectSearch-ProjectSearchDisplay'

     propTypes :
-        project_id   : rtypes.string
-        current_path : rtypes.string
-
-    shouldComponentUpdate : (nextProps, nextState) ->
-        return nextProps.current_path != @props.current_path or nextProps.project_id != @props.project_id or @state != nextState
-
-    getInitialState : ->
+        project_id         : rtypes.string
+        current_path       : rtypes.string
+        user_input         : rtypes.string
+        search_results     : rtypes.array
+        search_error       : rtypes.string
+        too_many_results   : rtypes.bool
+        command            : rtypes.string
+        most_recent_search : rtypes.string
+        most_recent_path   : rtypes.string
+        actions            : rtypes.object
+
+    getDefaultProps : ->
         user_input         : ''
-        search_results     : undefined
-        search_error       : undefined
         too_many_results   : false
-        command            : undefined
-        most_recent_search : undefined
-        most_recent_path   : undefined

     componentWillMount : ->
         if not @checkbox_state?
@@ -262,9 +259,9 @@
         return cmd

     search : ->
-        query = @state.user_input
+        query = @props.user_input
         if query.trim() == ''
-            @setState
+            @props.actions.setTo
                 search_results     : []
                 search_error       : undefined
                 command            : ''
@@ -276,7 +273,7 @@
         max_results = 1000
         max_output  = 110 * max_results  # just in case

-        @setState
+        @props.actions.setTo
             search_results     : undefined
             search_error       : undefined
             command            : cmd
@@ -298,7 +295,7 @@
     process_results : (err, output, max_results, max_output, cmd) ->

         if (err and not output?) or (output? and not output.stdout?)
-            @setState(search_error : err)
+            @props.actions.setTo(search_error : err)
             return

         results = output.stdout.split('\n')
@@ -328,32 +325,35 @@
             if num_results >= max_results
                 break

-        if @state.command is cmd # only update the state if the results are from the most recent command
-            @setState
+        if @props.command is cmd # only update the state if the results are from the most recent command
+            @props.actions.setTo
                 too_many_results : too_many_results
                 search_results   : search_results

     set_user_input : (new_value) ->
-        @setState(user_input : new_value)
+        @props.actions.setTo(user_input : new_value)

     output_header : ->
-        if @state.most_recent_search? and @state.most_recent_path?
+        if @props.most_recent_search? and @props.most_recent_path?
             <ProjectSearchOutputHeader
-                most_recent_path   = {@state.most_recent_path}
-                command            = {@state.command}
-                most_recent_search = {@state.most_recent_search}
-                search_results     = {@state.search_results}
-                search_error       = {@state.search_error} />
+                most_recent_path   = {@props.most_recent_path}
+                command            = {@props.command}
+                most_recent_search = {@props.most_recent_search}
+                search_results     = {@props.search_results}
+                search_error       = {@props.search_error}
+                info_visible       = {@props.info_visible}
+                actions            = {@props.actions} />

     output : ->
-        if @state.search_results? or @state.search_error?
+        if @props.search_results? or @props.search_error?
             return <ProjectSearchOutput
                 project_id       = {@props.project_id}
-                most_recent_path = {@state.most_recent_path}
-                results          = {@state.search_results}
-                too_many_results = {@state.too_many_results}
-                search_error     = {@state.search_error} />
-        else if @state.most_recent_search?
+                most_recent_path = {@props.most_recent_path}
+                results          = {@props.search_results}
+                too_many_results = {@props.too_many_results}
+                search_error     = {@props.search_error}
+                actions            = {@props.actions}/>
+        else if @props.most_recent_search?
             # a search has been made but the search_results or search_error hasn't come in yet
             <Loading />

@@ -363,17 +363,19 @@
                 <Col sm=8>
                     <ProjectSearchInput
                         search_cb    = {@search}
-                        set_state_cb = {(new_state)=>@setState(new_state)}
-                        project_id   = {@props.project_id} />
+                        user_input   = {@props.user_input}
+                        actions      = {@props.actions} />
                     {@output_header()}
                 </Col>

                 <Col sm=4>
                     <ProjectSearchSettings
-                        project_id      = {@props.project_id}
                         checkboxes      = {@settings_checkboxes}
                         toggle_checkbox = {@toggle_checkbox}
-                        search_cb       = {@search} />
+                        case_sensitive  = {@checkbox_state.case_sensitive}
+                        subdirectories  = {@checkbox_state.subdirectories}
+                        hidden_files    = {@checkbox_state.hidden_files}
+                        actions         = {@props.actions} />
                 </Col>
             </Row>
             <Row>
@@ -390,10 +392,11 @@
         filename         : rtypes.string
         description      : rtypes.string
         most_recent_path : rtypes.string
+        actions          : rtypes.object

     click_filename : (e) ->
         e.preventDefault()
-        project_store.getActions(@props.project_id, flux).open_file
+        @props.actions.open_file
             path       : misc.path_to_file(@props.most_recent_path, @props.filename)
             foreground : misc_page.open_in_foreground(e)

@@ -432,7 +435,7 @@
         <Row>
             <Col sm=12>
                 <FluxComponent flux={flux} connectToStores={[store.name]}>
-                    <ProjectSearchDisplay project_id={project_id}/>
+                    <ProjectSearchDisplay project_id={project_id} actions={flux.getProjectActions(project_id)}/>
                 </FluxComponent>
             </Col>
         </Row>


use helper to preserve cell widths

use helper to preserve cell widths

--- app/assets/javascripts/calls.coffee
+++ app/assets/javascripts/calls.coffee
@@ -2,10 +2,10 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

+# http://stackoverflow.com/questions/1307705/jquery-ui-sortable-with-table-and-tr-width/1372954#1372954
+# http://benw.me/posts/sortable-bootstrap-tables/
+
 jQuery ->
-
-    $('#call_list td').each (index, element) ->
-      $(element).css('width', $(element).width())

     $('#call_list').sortable(
       placeholder: "ui-state-highlight"
@@ -27,13 +27,19 @@
         for child, value of children
           if value.id? then order.push(value.id)
         console.log(order)
+      helper: (e, tr) ->
+        originals = tr.children()
+        helper = tr.clone()
+        helper.children().each (index, element) ->
+          $(element).css('width', originals.eq(index).width())
+        return helper

-        $.ajax(
-          type: 'POST'
-          url: '/something/reorder_pregnancies'
-          dataType: 'json'
-          data: { order }
-        )
+#      $.ajax(
+#        type: 'POST'
+#        url: '/something/reorder_pregnancies'
+#        dataType: 'json'
+#         data: { order }
+#      )
     )


hot test

hot test

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -377,10 +377,11 @@
 			for y in [0...cubeSize[1]]
 				for x in [0...cubeSize[0]]
 					cube.push if _cube?
-						index =
+						index =
 							(x - _offset[0]) +
 							(y - _offset[1]) * _size[0] +
-							(z - _offset[2]) * _size[0] * _size[1]
+							(z - _offset[2]) * _size[0] * _size[1]
+						_cube[index]
 					else
 						null


syntax fix

syntax fix

--- scripts/pagerv2_commands.coffee
+++ scripts/pagerv2_commands.coffee
@@ -221,8 +221,9 @@

 # TODO
 #   hubot pd stfu|down [for] <duration> [because <reason>] - creates a maintenance
-  robot.respond /pd (?:stfu|down)(?: for)?\s*([0-9]+)?(?: min(?:utes)?)?(?: because (.+))?\s+$/,
-    (res) ->
+  robot.respond (
+    /pd (?:stfu|down)(?: for)?\s*([0-9]+)?(?: min(?:utes)?)?(?: because (.+))?\s+$/
+  ), (res) ->
     [ _, duration ] = res.match
     res.send "Not yet implemented"
     res.finish()


set validation: validate as fine if we have null instead of iterable

Rationale: now I can skip/abort the validation conditionally such as with the
following:

  in: ->
    if @get('country') == 'CA'
      ['ON', 'QC', 'NS', 'NB', 'MB', 'BC', 'PE', 'SK', 'AB', 'NL', 'NT', 'YT', 'NU']
    else
      null

--- packages/tower-model/shared/validator/set.coffee
+++ packages/tower-model/shared/validator/set.coffee
@@ -5,7 +5,7 @@
     value     = record.get(attribute)
     testValue = @getValue(record)

-    success = switch @name
+    success = testValue is null or switch @name
       when 'in'
         _.indexOf(testValue, value) > -1
       when 'notIn'


add command "theta --battery"  #2

add command "theta --battery"  #2

--- bin/theta.coffee
+++ bin/theta.coffee
@@ -16,6 +16,7 @@
   ['--list', 'list pictures']
   ['--id [Object ID]', 'specify picture by ID']
   ['--save [FILENAME]', 'save picture']
+  ['--battery', 'check battery level']
 ]

 parser.on 'help', ->
@@ -72,4 +73,14 @@
   theta.once 'connect', ->
     savePicture config.object_id, filename

+parser.on 'battery', ->
+  theta.connect()
+  theta.once 'connect', ->
+    theta.getBatteryLevel (err, res) ->
+      if err
+        console.error err
+        return process.exit 1
+      console.log "BatteryLevel: #{res.dataPacket.toString()}"
+      theta.disconnect()
+
 parser.parse process.argv


Don't deactivate when clicking on toolbar

--- resources/assets/coffee/forum-topic-reply.coffee
+++ resources/assets/coffee/forum-topic-reply.coffee
@@ -35,7 +35,7 @@

     $(document).on 'focus', '.js-forum-topic-reply--input', @activate
     $(document).on 'input change', '.js-forum-topic-reply--input', _.debounce(@inputChange, 500)
-    $(document).on 'blur', '.js-forum-topic-reply--input', @deactivateIfBlank
+    $(document).on 'click', @deactivateIfBlank

     $.subscribe 'stickyFooter', @stickOrUnstick

@@ -98,8 +98,15 @@
     $.publish 'stickyFooter:check'

-  deactivateIfBlank: =>
-    return unless @$input().val() == ''
+  deactivateIfBlank: (e) =>
+    return unless @available() &&
+      @getState('active') == '1' &&
+      @input[0].value == ''
+
+    $target = $(e.target)
+
+    return unless $target.closest('.js-forum-topic-reply--container').length == 0 &&
+        $target.closest('.js-forum-topic-reply--new').length == 0

     @deactivate()


Handle potential error before calling `LocalFileWriter.writeStream`.

--- services/filestore/app/coffee/FileHandler.coffee
+++ services/filestore/app/coffee/FileHandler.coffee
@@ -81,4 +81,6 @@

 	_writeS3FileToDisk: (bucket, key, opts, callback)->
 		PersistorManager.getFileStream bucket, key, opts, (err, fileStream)->
+			if err?
+				return callback(err)
 			LocalFileWriter.writeStream fileStream, key, callback


Fix smoke tests

Fix smoke tests

--- services/web/test/smoke/coffee/SmokeTests.coffee
+++ services/web/test/smoke/coffee/SmokeTests.coffee
@@ -54,7 +54,7 @@
 			expect(error, "smoke test: error in getting project").to.not.exist
 			expect(response.statusCode, "smoke test: response code is not 200 getting project").to.equal(200)
 			# Check that the project id is present in the javascript that loads up the project
-			match = !!body.match("\"project_id\":\"#{Settings.smokeTest.projectId}\"")
+			match = !!body.match("window.project_id = \"#{Settings.smokeTest.projectId}\"")
 			expect(match, "smoke test: project page html does not have project_id").to.equal true
 			done()

@@ -67,6 +67,6 @@
 			expect(error, "smoke test: error returned in getting project list").to.not.exist
 			expect(response.statusCode, "smoke test: response code is not 200 getting project list").to.equal(200)
 			expect(!!body.match("<title>Your Projects - Online LaTeX Editor ShareLaTeX</title>"), "smoke test: body does not have correct title").to.equal true
-			expect(!!body.match("
Projects
"), "smoke test: body does not have correct h1").to.equal true
+			expect(!!body.match("ProjectPageController"), "smoke test: body does not have correct angular controller").to.equal true
 			done()


[client][home] hide info message when there's an error

--- src/frontend/home.coffee
+++ src/frontend/home.coffee
@@ -49,14 +49,17 @@
     displays =
         error   : ''
         success : ''
+        info    : ''
     ok       = yes
     if do event.currentTarget.checkValidity
         target.attr 'class', 'input-success'
         displays.error   = 'none'
+        displays.info    = 'none'
         displays.success = 'inline-block'
     else
         target.attr 'class', 'input-error'
         displays.error   = 'inline-block'
+        displays.info    = 'none'
         displays.success = 'none'
         ok               = no
     for cat, disp of displays


Remove useless code.

Remove useless code.

--- src/frontend/room.coffee
+++ src/frontend/room.coffee
@@ -99,11 +99,6 @@
         $('#videos').delegate 'li.thumbnail video', 'click', () ->
             that.checkZoom this, 'thumbnailVideo'

-    # Color the tutorial button.
-    colorTutorialBtn   : () =>
-        $("#tutorialMode").css "background-color", "#00aeef"
-        $("#tutorialMode").css "box-shadow", "inset 0 1px #15DBCB"
-
     # Send bug report.
     sendReport          : () =>
         $('#sendReport').attr 'disabled', on


increase viewport size

increase viewport size

--- overrides.coffee
+++ overrides.coffee
@@ -6,7 +6,7 @@
 # add open sans font
 $('head').append("""
   <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,700,800' rel='stylesheet' type='text/css'>
-  <meta name="viewport" content="width=540, user-scalable=no">
+  <meta name="viewport" content="width=600, user-scalable=no">
 """)

 $(".readymade-site-links").append("Feedback")


some cleaning on validate_doc_update

some cleaning on validate_doc_update

--- validate_doc_update.coffee
+++ validate_doc_update.coffee
@@ -6,50 +6,26 @@
   NOW = (new Date).getTime()
   isInternal = (key) -> key[0] == '_'
   userIsAdmin = ->
-    ## normalize cloudant secObj
-    if secObj.cloudant
-      if '_writer' in secObj.cloudant.nobody
-        return true
-
-      names = []
-      for name, roles in secObj.cloudant
-        names.push name if '_writer' in roles
-
-      roles = []
-
-    else
-      if not secObj.members and not secObj.admins
-        return true
-
-      admins = secObj.admins or {}
-      members = secObj.members or {}
-      names = (admins.names or []).concat(members.names or [])
-      roles = (admins.roles or []).concat(members.roles or [])
-    ## now we have a standard "names" and a standard "roles"
-
-    if userCtx.name in names
+    if userCtx.name in secObj.admins.names
       return true

     for role in userCtx.roles
-      if role in roles
+      if role in secObj.admins.roles
         return true

+    # a quirk of the smileupps databases
+    if '_admin' in userCtx.roles
+      return true
+
+    return false

   ######### start validating

   if newDoc.where == 'here'
     # outsiders posting here

-    # check hashcash token (anti-spam)
-    if ddoc.settings and ddoc.settings.hashcash and not userIsAdmin()
-      throw unauthorized: 'hashcash token needed.' if not newDoc.hashcash
-      token = JSON.parse newDoc.hashcash
-      throw unauthorized: 'hashcash token wrong.' if not hashcash.validate token, {
-        difficulty: 20000
-        data: sha256(NOW.toString().substr(0, 8) + ddoc.settings.hashcash)
-      }
-
     # don't allow modification of docs
+    # this also covers people trying to delete docs and other attacks
     if oldDoc and not userIsAdmin()
       throw forbidden: 'Can\'t change scraps already posted.'

@@ -78,6 +54,15 @@
       if newDoc.timestamp > NOW + 60000 or newDoc.timestamp < NOW - 60000
         throw forbidden: 'timestamp is not now.'

+      # check hashcash token (anti-spam)
+      if ddoc.settings and ddoc.settings.hashcash and not userIsAdmin()
+        throw unauthorized: 'hashcash token needed.' if not newDoc.hashcash
+        token = JSON.parse newDoc.hashcash
+        throw unauthorized: 'hashcash token wrong.' if not hashcash.validate token, {
+          difficulty: 20000
+          data: sha256(NOW.toString().substr(0, 8) + ddoc.settings.hashcash)
+        }
+
       # check if the document is not marked as verified
       if newDoc.verified is not false
         throw forbidden: 'verified is not false.'


Fix layout bug caused by undefined factor

--- views/services/layout.cjsx
+++ views/services/layout.cjsx
@@ -27,6 +27,25 @@
     url = webview?.getURL?()
   catch e
     url = null
+
+    # Get factor
+  if window.layout == 'vertical'
+    cap = 200 * window.zoomLevel
+  else if window.doubleTabbed
+    cap = 450 * window.zoomLevel
+  else cap = 300 * window.zoomLevel
+  if window.layout == 'horizontal'
+    factor = Math.ceil((window.innerHeight - poiControlHeight) / 480.0 * 100) / 100.0
+    if window.innerWidth - factor * 800 < cap
+      factor = Math.ceil((window.innerWidth - cap) / 800.0 * 100) / 100.0
+  else
+    factor = Math.ceil(window.innerWidth / 800.0 * 100) / 100.0
+    if window.innerHeight - factor * 480 < cap
+      factor = Math.ceil((window.innerHeight - cap) / 480.0 * 100) / 100.0
+  if window.webviewWidth > 0.00001
+    factor = Math.max(window.webviewWidth / 800.0 * 100 / 100.0, 0.00125)
+  window.webviewFactor = factor
+
   # Autoset style
   if window.layout == 'horizontal'
     tabpaneHeight = "#{window.innerHeight / window.zoomLevel - poiControlHeight}px"
@@ -65,23 +84,7 @@
       y: bound.y
       width: parseInt(newWidth + borderX)
       height: bound.height
-  # Get factor
-  if window.layout == 'vertical'
-    cap = 200 * window.zoomLevel
-  else if window.doubleTabbed
-    cap = 450 * window.zoomLevel
-  else cap = 300 * window.zoomLevel
-  if window.layout == 'horizontal'
-    factor = Math.ceil((window.innerHeight - poiControlHeight) / 480.0 * 100) / 100.0
-    if window.innerWidth - factor * 800 < cap
-      factor = Math.ceil((window.innerWidth - cap) / 800.0 * 100) / 100.0
-  else
-    factor = Math.ceil(window.innerWidth / 800.0 * 100) / 100.0
-    if window.innerHeight - factor * 480 < cap
-      factor = Math.ceil((window.innerHeight - cap) / 480.0 * 100) / 100.0
-  if window.webviewWidth > 0.00001
-    factor = Math.max(window.webviewWidth / 800.0 * 100 / 100.0, 0.00125)
-  window.webviewFactor = factor
+
   # Fix poi-info when game size 0x0
   if webviewWidth > -0.00001 and webviewWidth < 0.00001
     $('kan-game')?.style?.display = 'none'


The y axis labels for the chart should not obscure the candles.

--- app/assets/javascripts/init.coffee
+++ app/assets/javascripts/init.coffee
@@ -2,7 +2,6 @@
   $('#candle-chart').highcharts 'StockChart',
         title: text: 'Test'
         exporting: enabled: false
-
         rangeSelector : {
           buttons : [{
               type : 'hour',
@@ -37,26 +36,26 @@
         }
         yAxis: [{
           labels: {
-              align: 'right',
-              x: -3
+              align: 'left',
+              x: 10
           },
           title: {
               text: '5 Minute Candles'
           },
-          height: '90%',
-          lineWidth: 2
+          height: '80%',
+          lineWidth: 1
         }, {
           labels: {
-              align: 'right',
-              x: -3
+              align: 'left',
+              x: 10
           },
           title: {
-              text: '24 Hr Volume'
+              text: '24 Hr Vol'
           },
           top: '90%',
-          height: '20%',
+          height: '10%',
           offset: 0,
-          lineWidth: 2
+          lineWidth: 1
         }],
         series: [ {
           type: 'candlestick'


include log message for process timeout

include log message for process timeout

--- app/coffee/ASpellWorkerPool.coffee
+++ app/coffee/ASpellWorkerPool.coffee
@@ -56,7 +56,7 @@
 			worker.idleTimer = null

 		worker.killTimer = setTimeout () ->
-			worker.kill()
+			worker.kill("spell check timed out")
 		, timeout || @MAX_REQUEST_TIME

 		worker.check words, (err, output) =>


fix create game message

fix create game message

--- app/coffee/stores/GameMixin.coffee
+++ app/coffee/stores/GameMixin.coffee
@@ -28,7 +28,7 @@

   onCreateGame: (callback) ->
     @createGameCallback = callback
-    @channel.push('game:create', @game)
+    @channel.push('game:create', {game: @game})

   onChangeGameDeckId: (deck_id) ->
     @game.deck.id = deck_id
@@ -46,4 +46,3 @@

 module.exports = GameMixin
-


Restore new video element

Restore new video element

--- app/components/subject-viewer.cjsx
+++ app/components/subject-viewer.cjsx
@@ -179,7 +179,7 @@
       when 'image'
         <img className="subject" src={src} style={SUBJECT_STYLE} onLoad={@handleLoad} />
       when 'video'
-        <video src={src} type={"#{type}/#{format}"} controls onLoad={@handleLoad}>
+        <video ref="videoPlayer" src={src} type={"#{type}/#{format}"} onCanPlayThrough={@handleLoad} onEnded={@endVideo} onTimeUpdate={@updateScrubber}>
           Your browser does not support the video format. Please upgrade your browser.
         </video>


Fix sql validations

Fix sql validations

--- app/config/endpoints_config.coffee
+++ app/config/endpoints_config.coffee
@@ -140,7 +140,6 @@
         params:
           query:
             type: "Textbox"
-            required: true
             placeholder: "Enter SQL query"
   ned:
     name: 'NED'


Don't replace the location, since it breaks the browser back button.

--- app/javascripts/controllers.coffee
+++ app/javascripts/controllers.coffee
@@ -3,14 +3,13 @@

   # The local controller helper methods.
   ctlrs.factory 'ControllerHelper', ['$location', ($location) ->
-    # Replaces the browser URL search parameters to include at most
+    # Resets the browser URL search parameters to include at most
     # a non-default project, since all other search parameters are
     # redundant.
     cleanBrowserUrl: (project) ->
       searchParams = {}
       if project != 'QIN'
         searchParams.project = project
-      $location.replace()
       $location.search(searchParams)
   ]


Fixed permissions for quizzes

Fixed permissions for quizzes

--- app/lib/collections/quizzes.coffee
+++ app/lib/collections/quizzes.coffee
@@ -7,7 +7,7 @@
 Quizzes.allow
   insert: (userId, doc) -> false

-  update: (userId, doc, fields, modifier) -> true # TODO: testing
+  update: (userId, doc, fields, modifier) -> false

   remove: (userId, doc) -> false


Track form submission with snowplow

Track form submission with snowplow

--- apps/apply/client/analytics.coffee
+++ apps/apply/client/analytics.coffee
@@ -1,4 +1,5 @@
-{ track } = require '../../../lib/analytics.coffee'
+_ = require 'underscore'
+{ track, snowplowStruct } = require '../../../lib/analytics.coffee'

 module.exports = (router) ->
   { state, form } = router
@@ -16,3 +17,4 @@

   state.on 'change:state', (state, value) ->
     track.funnel 'Submitted partner application form', state: value
+    snowplowStruct 'partner_application', 'submit', null, 'partner_application', null, _.extend({}, state.pick('mode'), form.pick('name', 'first_name', 'last_name'))


pends hanging test, will investigate

pends hanging test, will investigate

--- apps/auctions/test/template.coffee
+++ apps/auctions/test/template.coffee
@@ -39,7 +39,7 @@
       # 'How auctions work' link & Upcoming auctions
       $('.ap-upcoming-item').should.have.lengthOf 2

-  describe 'without current auctions', ->
+  xdescribe 'without current auctions', ->
     before (done) ->
       benv.setup =>
         benv.expose $: benv.require 'jquery'


Create new model class

Create new model class

--- assets/javascripts/model.js.coffee
+++ assets/javascripts/model.js.coffee
@@ -0,0 +1,57 @@
+TentStatus.Model = class Model
+  @instances: {
+    all: {}
+  }
+  @id_mapping: {}
+  @_id_counter: 0
+  @model_name: '_default'
+
+  @find: (params, callback) ->
+    if params.id && (cid = @id_mapping[@model_name]?[params.id])
+      params.cid = cid
+
+    if params.cid
+      instance = @instances.all[params.cid]
+      callback?(instance)
+      return instance
+
+    if params.id
+      @fetch(params, callback)
+
+  @fetch: (params, callback) ->
+    console.warn("You need to define #{@name}::fetch(params, callback)!")
+
+  constructor: (attributes, @options) ->
+    @generateCid()
+    @on 'change:id', @updateIdMapping
+    @parseAttribtues(attributes)
+    @trackInstance()
+
+  parseAttribtues: (attributes) =>
+    @set(k, v) for k,v of attributes
+
+  generateCid: =>
+    @cid = "#{@constructor.model_name}_#{@constructor._id_counter++}"
+
+  trackInstance: =>
+    @constructor.instances.all[@cid] = @
+    @constructor.instances[@constructor.model_name] ?= []
+    @constructor.instances[@constructor.model_name].push @cid
+
+  updateIdMapping: (new_id, old_id) =>
+    @constructor.id_mapping[@constructor.model_name] ?= {}
+    delete @constructor.id_mapping[@constructor.model_name][old_id]
+    @constructor.id_mapping[@constructor.model_name][new_id] = @cid
+
+  set: (k, v) =>
+    @fields ?= []
+    @fields.push(k) if @fields.indexOf(k) == -1
+    old_v = @[k]
+    @[k] = v
+    @trigger("change:#{k}", v, old_v) unless v == old_v
+    v
+
+  get: (k) =>
+    @[k] if @fields.indexOf(k) != -1
+
+_.extend Model::, Backbone.Events


* [injectMethods] use the new injectMethod

--- src/injectMethods.coffee
+++ src/injectMethods.coffee
@@ -7,7 +7,8 @@
   * key: the method name to inject
   * value: the new method function
 ###
-isFunction  = require('./is/type/function')
+isFunction   = require('./is/type/function')
+injectMethod = require('./injectMethod')
 module.exports = (aObject, aMethods, aOptions) ->
   replacedMethods = aOptions.replacedMethods if aOptions and aOptions.replacedMethods
   if aMethods instanceof Object
@@ -15,13 +16,7 @@
       continue if not isFunction v
       inherited = aObject[k]
       if isFunction(inherited) and not (replacedMethods and replacedMethods[k])
-        aObject[k] = ((inherited, method)->
-          return ->
-            that =
-              super: inherited
-              self: @
-            method.apply(that, arguments)
-        )(inherited, v)
+        injectMethod(aObject, k, v)
       else
         aObject[k] = v
 ###


stub header

stub header

--- src/layout/header.coffee
+++ src/layout/header.coffee
@@ -0,0 +1,2 @@
+exports.header = {
+}


#122 Added comments to GET commits.

#122 Added comments to GET commits.

--- src/octonode/repo.coffee
+++ src/octonode/repo.coffee
@@ -38,6 +38,8 @@

   # Get the commits for a repository
   # '/repos/pksunkara/hub/commits' GET
+  # - page or query object, optional - params[0]
+  # - per_page, optional             - params[1]
   commits: (params...,cb) ->
     @client.get "/repos/#{@name}/commits", params..., (err, s, b, h) ->
       return cb(err) if err


upload: Allow passing metadata

upload: Allow passing metadata

* For every uploaded file: Create an entity

--- src/routes/upload.coffee
+++ src/routes/upload.coffee
@@ -1,6 +1,7 @@
 {Form} = require 'multiparty'
 Async  = require 'async'
 Crypto = require 'crypto'
+Qs     = require 'qs'
 Fs     = require 'fs'
 CONFIG = require '../config'
 Request = require 'superagent'
@@ -14,7 +15,7 @@
 			tags: ['essential']
 			summary: "Upload a file"
 			description: "Upload a file and store it as an InfolisFile resource."
-			consumes: ['multipart/formx-data']
+			consumes: ['multipart/form-data']
 			parameters: [
 				{
 					name: 'file'
@@ -23,8 +24,40 @@
 					in: 'formData'
 				}
 				{
+					name: 'identifier'
+					type: 'string'
+					in: 'formData'
+				}
+				{
+					name: 'subjects'
+					type: 'array'
+					items: { type: 'string' }
+					in: 'formData'
+				}
+				{
+					name: 'authors'
+					type: 'array'
+					items: { type: 'string' }
+					in: 'formData'
+				}
+				{
+					name: 'language'
+					type: 'string'
+					in: 'formData'
+					enum: [
+						'en'
+						'de'
+					]
+				}
+				{
+					name: 'name'
+					type: 'string'
+					in: 'formData'
+				}
+				{
 					name: 'tags'
-					type: 'string'
+					type: 'array'
+					items: { type: 'string' }
 					in: 'formData'
 				}
 				{
@@ -67,6 +100,7 @@
 				ret = new Error("Didn't pass the 'file' upload")
 				ret.cause = [fields, files]
 				return next ret
+			entityModel = new app.schemo.models.Entity()
 			fileModel = new app.schemo.models.InfolisFile()
 			Fs.readFile fileField.path, (err, fileData) ->
 				Async.each ['md5', 'sha1'], (algo, done) ->
@@ -76,8 +110,15 @@
 					done()
 				, (err) ->
 					tags = []
-					if 'tags' of fields
-						tags = fields['tags']
+					if fields['tags']
+						tags = Qs.parse(fields['tags'][0]).tags
+					entityModel.set 'tags', tags
+					if fields['authors']
+						entityModel.set 'authors', Qs.parse(fields['authors'][0]).authors
+					if fields['subjects']
+						entityModel.set 'subjects', Qs.parse(fields['subjects'][0]).subjects
+					entityModel.set 'name', fields['name']
+					entityModel.set 'identifier', fields['identifier']
 					fileModel.set 'size', fileField['size']
 					fileModel.set 'tags', tags
 					fileModel.set 'mediaType', fields['mediaType']
@@ -96,14 +137,22 @@
 							if res2.status isnt 201
 								ret = new Error(res.text)
 								return next ret
-							fileModel.save (err, saved) ->
+							fileModel.save (err, savedFile) ->
 								if err
 									ret = new Error("Error saving file to database")
 									ret.cause = err
 									ret.status = 400
 									return next ret
-								res.status 201
-								res.header 'Location', fileModel.uri()
-								res.send '@link': fileModel.uri()
+								entityModel.set 'file', savedFile
+								entityModel.save (err, savedEntity) ->
+									if err
+										ret = new Error("Error saving entity to database")
+										ret.cause = err
+										ret.status = 400
+										return next ret
+									log.debug "Created entity: #{savedEntity}"
+									res.status 201
+									res.header 'Location', fileModel.uri()
+									res.send '@link': fileModel.uri()

 	done()


add window.INITIAL_PROPS for server-side rendering

--- src/scripts/index.coffee
+++ src/scripts/index.coffee
@@ -14,7 +14,8 @@
     action: ->
       getEntryService().fetchAll()
   ]
-  rootElement = React.createElement AppView
+  props = window.INITIAL_PROPS ? {}
+  rootElement = React.createFactory(AppView)(props)
   React.render rootElement, document.body
   router.start()
   setTimeout ->


fixed callback and null

fixed callback and null

--- src/scripts/steam.coffee
+++ src/scripts/steam.coffee
@@ -23,13 +23,11 @@
 HTMLParser = require "htmlparser"
 sanitize = require('validator').sanitize

-image = null
-
 module.exports = (robot) ->
   robot.respond /daily deal/i, (msg) ->
     getDeals msg, (responseText) ->
-      msg.send image
-      msg.send responseText
+      msg.send responseText[0]
+      msg.send responseText[1]

 getDeals = (msg, callback) ->
@@ -49,6 +47,7 @@
     finalPrice = Select(handler.dom, '.dailydeal_content .discount_final_price')[0]
     image = "#{dealObj.children[0].attribs.src.replace /[?].*/ , ''}"
     deal = "From #{sanitize(originalPrice.children[0].data).entityDecode().trim()} to #{sanitize(finalPrice.children[0].data).entityDecode().trim()} #{dealObj.attribs.href}"
+    response = [image, deal]
   else
     msg.send "No daily deal found"


:memo: Convert ThemeManager docs

:memo: Convert ThemeManager docs

--- src/theme-manager.coffee
+++ src/theme-manager.coffee
@@ -9,9 +9,32 @@
 Package = require './package'
 {File} = require 'pathwatcher'

-# Public: Handles loading and activating available themes.
+# Extended: Handles loading and activating available themes.
 #
 # An instance of this class is always available as the `atom.themes` global.
+#
+# ## Events
+#
+# ### reloaded
+#
+# Extended: Emit when all styles have been reloaded.
+#
+# ### stylesheet-added
+#
+# Extended: Emit when a stylesheet has been added.
+#
+# * `stylesheet` {StyleSheet} object that was removed
+#
+# ### stylesheet-removed
+#
+# Extended: Emit when a stylesheet has been removed.
+#
+# * `stylesheet` {StyleSheet} object that was removed
+#
+# ### stylesheets-changed
+#
+# Extended: Emit anytime any style sheet is added or removed from the editor
+#
 module.exports =
 class ThemeManager
   Emitter.includeInto(this)
@@ -98,7 +121,7 @@
         @refreshLessCache() # Update cache again now that @getActiveThemes() is populated
         @loadUserStylesheet()
         @reloadBaseStylesheets()
-        @emit('reloaded')
+        @emit 'reloaded'
         deferred.resolve()

     deferred.promise
@@ -124,7 +147,7 @@

   # Public: Set the list of enabled themes.
   #
-  # enabledThemeNames - An {Array} of {String} theme names.
+  # * `enabledThemeNames` An {Array} of {String} theme names.
   setEnabledThemes: (enabledThemeNames) ->
     atom.config.set('core.themes', enabledThemeNames)

@@ -187,9 +210,8 @@
   #
   # This supports both CSS and LESS stylsheets.
   #
-  # stylesheetPath - A {String} path to the stylesheet that can be an absolute
-  #                  path or a relative path that will be resolved against the
-  #                  load path.
+  # * `stylesheetPath` A {String} path to the stylesheet that can be an absolute
+  #   path or a relative path that will be resolved against the load path.
   #
   # Returns the absolute path to the required stylesheet.
   requireStylesheet: (stylesheetPath, type = 'bundled', htmlElement) ->


comments

comments

--- src/tip/bootstrap.coffee
+++ src/tip/bootstrap.coffee
@@ -51,7 +51,16 @@
     @tip.setPosition(step.target or false, my, at)

+# One can add more effects by hanging a function from this object, then using
+# it in the tipOptions.hideEffect or showEffect. i.e.
+#
+# @s = new Tourist.Tip.Bootstrap
+#   model: m
+#   showEffect: 'slidein'
+#
 Tourist.Tip.Bootstrap.effects =
+
+  # Move tip away from target 80px, then slide it in.
   slidein: (tip, element) ->
     OFFSETS = top: 80, left: 80, right: -80, bottom: -80


Changes next/prev conversation shortcuts on macOS

Because Cmd+Tab and Cmd+Shift+Tab are default shortcuts for switching
apps on macOS and cannot be used to change next/prev conversation.

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -23,8 +23,8 @@
     quit: { default: 'CmdOrCtrl+Q' }
     zoomin: { default: 'CmdOrCtrl+Plus' }
     # Platform specific
-    previousconversation: { default: 'Ctrl+K', darwin:'Command+Shift+Tab' }
-    nextconversation:  { default: 'Control+J', darwin:'Command+Tab' }
+    previousconversation: { default: 'Ctrl+K', darwin:'Command+K' }
+    nextconversation:  { default: 'Ctrl+J', darwin:'Command+J' }
     conversation1: { default: 'Alt+1', darwin:'Command+1' }
     conversation2: { default: 'Alt+2', darwin:'Command+2' }
     conversation3: { default: 'Alt+3', darwin:'Command+3' }


forgot about menu item

forgot about menu item

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -328,7 +328,7 @@
                     label: 'Show seen status in messages'
                     type: 'checkbox'
                     enabled: viewstate.loggedin
-                    checked: viewstate.showSeenStatus
+                    checked: viewstate.showseenstatus
                     click: -> action 'toggleshowseenstatus'
                 }
             ]


Apparently there's already `resolved` state check

--- resources/assets/coffee/react/beatmap-discussions/new-reply.coffee
+++ resources/assets/coffee/react/beatmap-discussions/new-reply.coffee
@@ -126,7 +126,7 @@

   canReopen: =>
-    @props.discussion.resolved && @props.discussion.current_user_attributes.can_reopen
+    @props.discussion.current_user_attributes.can_reopen

   canResolve: =>


air dial fix

air dial fix

--- Resources/views/preferences.coffee
+++ Resources/views/preferences.coffee
@@ -136,8 +136,8 @@

 	ac_button = Ti.UI.createButton({
 		backgroundImage:"/images/airdial/user_air_dial_ac_btn.png",
-		backgroundSelectedImage:"/airdial/air/user_air_dial_ac_btn_p.png",
-		backgroundActiveImage:"/airdial/air/user_air_dial_ac_btn_a.png",
+		backgroundSelectedImage:"/images/airdial/air/user_air_dial_ac_btn_p.png",
+		backgroundActiveImage:"/images/airdial/air/user_air_dial_ac_btn_a.png",
 		height:144,
 		width:144,
 		zIndex: 11


Handle expanding fixed

Handle expanding fixed

--- Stations/static/js/graphing.coffee
+++ Stations/static/js/graphing.coffee
@@ -8,8 +8,8 @@
 shrink = (evnt) ->
   $('#sidebar-panel').animate {width: '0px'}, 400, "swing", () ->
     $('#sidebar-panel').hide 0
-  $('#sidebar-panel').one 'click', expand
-  $('#sidebar-panel').text '>>'
+  $('#sidebar-handle').one 'click', expand
+  $('#sidebar-handle').text '>>'

 $ ->
   Highcharts.setOptions {global: { useUTC: false }}


Fix SpriteParser

Fix SpriteParser

Add esprima import

--- app/lib/sprites/SpriteParser.coffee
+++ app/lib/sprites/SpriteParser.coffee
@@ -1,4 +1,5 @@
 createjs = require 'lib/createjs-parts'
+esprima = require 'esprima'

 module.exports = class SpriteParser
   constructor: (@thangTypeModel) ->


Scene: Check for world existence when destroying it

--- engine/scene.coffee
+++ engine/scene.coffee
@@ -174,7 +174,7 @@
         for ob in @children[...]
             @remove_object ob, false
             delete @context.objects[ob.name]
-        destroy_world @world
+        if @world? then destroy_world @world

         # Reduce itself to a stub by deleting itself and copying callbacks
         stub = @context.scenes[@name] = new Scene @context


sc listでlistがカラの時に'empty'を返す

sc listでlistがカラの時に'empty'を返す

--- scripts/brainManager.coffee
+++ scripts/brainManager.coffee
@@ -51,7 +51,11 @@
     message = data.map (i) ->
         "#{urls.searchIndex(data, i.url)}: #{i.url} #{i.status}"
       .join '\n'
-    msg.send message
+
+    if message
+      msg.send message
+    else
+      msg.send "empty"

   ### Update Function ###
   robot.hear /sc[\s]+update[\s]+(\d+)[\s]+(\d+)$/, (msg) ->


Remove truncation ellipsis from thread titles below threshold

--- scripts/forum-poller.coffee
+++ scripts/forum-poller.coffee
@@ -54,7 +54,7 @@
   MESSAGE_FORMAT = "Forum Activity: :subject :url"

   truncate: (msg, length = 40) ->
-    msg.substring(0, length) + '...'
+    if msg.length > length then msg.substring(0, length) + '...' else msg

   create: (thread) ->
     message = MESSAGE_FORMAT


Probe: Calculate LoD bias as having 1 sample in WebGL1.

--- engine/probe.coffee
+++ engine/probe.coffee
@@ -34,7 +34,10 @@
             @render()

     set_lod_factor: ->
-        @lodfactor = 0.5 * Math.log( ( @size*@size / @scene.bsdf_samples ) ) / Math.log(2)
+        {bsdf_samples} = @scene
+        if not @context.is_webgl2
+            bsdf_samples = 1
+        @lodfactor = 0.5 * Math.log( ( @size*@size / bsdf_samples ) ) / Math.log(2)
         @lodfactor -= @scene.lod_bias

     render: ->


register shit to db when dropped

register shit to db when dropped

--- app/scripts/controllers/main.coffee
+++ app/scripts/controllers/main.coffee
@@ -7,8 +7,21 @@
  # # MainCtrl
  # Controller of the gummyApp
 ###
+remote = require "remote"
+app = remote.require("app")
+Datastore = remote.require "nedb"
+
 angular.module 'gummyApp'
-    .controller 'MainCtrl', ($scope, $rootScope, $trakt, $rotten) ->
+    .controller 'MainCtrl', ($scope, $rootScope, $timeout, $trakt, $rotten) ->
+
+
+        ###*
+         # Internal database
+         # @attribute db
+        ###
+        $scope.db = new Datastore
+            filename: "#{app.getPath 'home'}/Gummy/movies.db"
+            autoload: true

         ###*
@@ -30,6 +43,33 @@
          # @attribute grid
         ###
         $scope.grid = []
+
+
+        ###*
+         # Init app
+         # @method init
+        ###
+        $scope.init = () ->
+
+
+            # Add drop event to document
+            document.addEventListener "dragover", (e) -> e.preventDefault() and e.stopPropagation()
+            document.addEventListener "dragleave", (e) -> e.preventDefault() and e.stopPropagation()
+            document.addEventListener "drop", (e) ->
+                e.preventDefault()
+                e.stopPropagation()
+                $scope.drop e.dataTransfer.files
+
+
+            # Read DB
+            $scope.db.find {}, (e, movies) ->
+                if e then console.error e
+
+                # Add existing movies to main grid
+                for movie in movies
+                    $scope.add_movie movie
+
+

         ###*
@@ -55,6 +95,8 @@

             for file in files

+                console.log file
+
                 # Check for ext
                 filename = file.name.split(".")
                 ext = filename.pop()
@@ -69,15 +111,33 @@
                 $trakt
                     .search filename
                     .then (data) ->
-                        $scope.grid.push data[0]
+
+                        # Add filepath to db
+                        movie = data[0]
+                        movie.path = file.path
+
+                        # Save data to DB
+                        $scope.db.insert movie, (e, item) ->
+                            if e then return console.error e
+
+                            # Add data to main grid
+                            $scope.add_movie movie
+
+
                     .catch (e) ->
                         console.error e

-        # Add event to document
-        document.addEventListener "dragover", (e) -> e.preventDefault() and e.stopPropagation()
-        document.addEventListener "dragleave", (e) -> e.preventDefault() and e.stopPropagation()
-        document.addEventListener "drop", (e) ->
-            e.preventDefault()
-            e.stopPropagation()
-            $scope.drop e.dataTransfer.files
+        ###*
+         # Add a movie and getting rid of angular fucking digest cycle
+        ###
+        $scope.add_movie = (movie) ->
+            $timeout () ->
+                $scope.grid.push movie
+
+
+
+        #$scope.check = (filepath) ->
+
+        # Init app
+        $scope.init()


added logic for smaller refEl

added logic for smaller refEl

--- jquery-badyl.coffee
+++ jquery-badyl.coffee
@@ -1,11 +1,11 @@
 ###
 @name jquery-badyl
 @description Meet Badyl – bootstrap affix-like wheel reinvent.
-@version 1.7.53
+@version 1.7.54
 @author Se7enSky studio <info@se7ensky.com>
 ###

-###! jquery-badyl 1.7.53 http://github.com/Se7enSky/jquery-badyl###
+###! jquery-badyl 1.7.54 http://github.com/Se7enSky/jquery-badyl###

 plugin = ($) ->

@@ -53,7 +53,11 @@

 			# return if @originalHeight is 0

+			@$refEl.css height: ""
 			@badylHeight = @$refEl.height() + @config.offset * 2
+			if @badylInnerHeight > @badylHeight
+				@$refEl.css height: "#{@badylInnerHeight}px"
+				@badylHeight = @$refEl.height() + @config.offset * 2

 			@$el.replaceWith @$badylContainer = $("""<div>""")
 				.css


Link from table to new simulation landing pages

--- js/data_table.coffee
+++ js/data_table.coffee
@@ -2,7 +2,7 @@
 ---

 fname = (data, type, row) ->
-  '' + data + ''
+  '' + data + ''

 fauthor = (data, type, row) ->
   if data.email