Added persistent blacklist

Added persistent blacklist

--- server.coffee
+++ server.coffee
@@ -202,7 +202,7 @@
           fragdata = danmaku.fragtable[target]
           uid = if fragdata then get_ip_with_tripcode(fragdata.ip) else null
           if uid
-            danmaku.ban(uid, nick, null, null)
+            danmaku.ban(uid, nick, 'IRC Report', null)
           else if target
             self.client.say(self.chanName, "IP address '#{target}' not found.")
           else
@@ -255,16 +255,16 @@
         when 'ban'
           target = param1
           duration = parseInt(param2) || null
-          danmaku.ban(target, nick, null, duration)
+          danmaku.ban(target, nick, 'IRC Command', duration)
           return
         when 'unban'
           target = param1
           danmaku.unban(target, nick)
           return
         when 'say'
-          size = parseInt(param1) || null
-          message = param2
-          danmaku.say(message, size)
+          message = if param2 then "#{param1} #{param2}" else param1
+          danmaku.say message, '127.0.0.1',
+            color: '0,255,0,0.9'
           return
         when 'mute'
           danmaku.mute = true
@@ -346,7 +346,8 @@

     self.dbUser = process.env.OPENSHIFT_MONGODB_DB_USERNAME
     self.dbPass = process.env.OPENSHIFT_MONGODB_DB_PASSWORD
-    self.dbName = 'nodedanmaku'
+    self.dbMessageName = 'messages'
+    self.dbConfigName = 'configurations'
     self.emitter = new emitter()

   setupVariables: =>
@@ -404,12 +405,12 @@
     self.db.open (err, db) ->
       throw err if err
       if $DEBUG
-        self.db.collection(self.dbName).remove()
+        self.db.collection(self.dbMessageName).remove()
         callback()
       else
         self.db.authenticate self.dbUser, self.dbPass, (err, res) ->
           throw err if err
-          self.db.collection(self.dbName).remove()
+          self.db.collection(self.dbMessageName).remove()
           callback()

   loadStringFilter: (sensitive_word_file) =>
@@ -426,11 +427,6 @@

   stringFilter: (text) =>
     self = this
-    if self.sensitive_words.length == 0
-      sensitive_word_files = fs.readdirSync(self.sensitive_word_file_dir)
-      for sensitive_word_file in sensitive_word_files
-        self.loadStringFilter("#{self.sensitive_word_file_dir}/#{sensitive_word_file}")
-
     safe_text = text
     for sensitive_word in self.sensitive_words
       switch sensitive_word.type
@@ -456,7 +452,7 @@

   query: (last_id, callback) =>
     self = this
-    self.db.collection(self.dbName).find(
+    self.db.collection(self.dbMessageName).find(
       id:
         $gt: last_id
     ,
@@ -502,6 +498,29 @@
     if not (domain && domain.match(/^(?:[A-Za-z0-9\-\.]+\.)?phate\.(?:io|cc|us|tw|org)$/i)?)
       safe_referer = strip_hyper_links(strip_irc_colors(referer))
       throw "REFERRAL_DENIED: #{safe_referer}"
+
+  loadBlacklist: =>
+    self = this
+    self.db.collection(self.dbConfigName).find(
+      name: 'blacklist'
+    ).sort(id: 1).toArray (err, items) ->
+      throw err if err
+      return if items.length == 0
+      self.blacklist = JSON.parse(items[0].text)
+      DEBUG('Load %d blacklist items from database.', Object.keys(self.blacklist).length)
+
+  saveBlacklist: =>
+    self = this
+    criteria =
+      name: 'blacklist'
+    data =
+      name: 'blacklist'
+      text: JSON.stringify(self.blacklist)
+    options =
+      upsert: true
+    self.db.collection(self.dbConfigName).update criteria, data, options, (err, count, status) ->
+      throw err if err
+      DEBUG('Save blacklist to database. Returned %s', status)

   blacklistCheck: (ip) =>
     self = this
@@ -539,24 +558,20 @@
         count: 1
     self.last_response_uid = uid

-  sensitive_wordCheck: (text) =>
+  ignoredWordsCheck: (text) =>
     self = this
     throw 'IGNORED' if text.indexOf("\u0000") != -1

-  say: (text, size = 1.0) =>
-    self = this
-    ip = '127.0.0.1'
-    uid = get_ip_with_tripcode(ip)
+  say: (text, address, attributes = {}, callback = false) =>
+    self = this
+    uid = get_ip_with_tripcode(address)
     timestamp = (new Date()).getTime()

-    text = text.substr(0, 64)
-    text = self.stringFilter(text)
-
-    top = 0.1
-    color = '0,255,0,0.9'
-    size = size
-    weight = 'bold'
-    speed = 1.0
+    top = attributes.top || Math.random()
+    color = attributes.color || '255,255,255,0.9'
+    size = attributes.size || 1.0
+    weight = attributes.weight || 'bold'
+    speed = attributes.speed || 1.0

     item =
       id: self.last_record_id + 1
@@ -567,24 +582,45 @@
       weight: weight
       speed: speed
       uid: uid
-      ip: ip
+      ip: address
       timestamp: timestamp

-    self.db.collection(self.dbName).insert item, (result) ->
-      DEBUG('Insert id %d (%s)', self.last_record_id, ip)
+    self.db.collection(self.dbMessageName).insert item, (result) ->
+      DEBUG('Insert id %d (%s)', self.last_record_id, address)
+      callback() if callback
     self.last_record_id++
-    nodeirc.action("\u000306[#{uid}] \u000303#{text}") if not self.mute
-
-  ban: (uid, by_ = 'system', reason = 'Manual', duration = 3600) =>
-    self = this
-    timestamp = (new Date()).getTime()
+
+    if not self.mute
+      if self.metadata_updated
+        title = self.metadata.title
+        artist = self.metadata.artist
+        tags = self.metadata.tags
+        nodeirc.say("\u000306Now playing: \u000314#{artist} - #{title} (#{tags})")
+        self.metadata_updated = false
+      nodeirc.action("\u000306[#{uid}] \u000314#{text}")
+
+  system_say: (text) =>
+    self = this
+    self.say text, '127.0.0.1',
+      color: '255,0,0,0.9'
+      size: 0.5
+      speed: 0.75
+
+  ban: (uid, by_, reason, duration = 3600) =>
+    self = this
+    now_time = (new Date()).getTime()
+    timestamp = if self.blacklist[uid] then self.blacklist[uid].timestamp else now_time
+    new_timestamp = timestamp + duration * 1000
     self.blacklist[uid] =
       moderator: by_
-      timestamp: timestamp + duration * 1000
-    duration_hours = parseFloat(duration / 3600)
-    nodeirc.action("\u000304#{uid} has been banned #{duration_hours} hours. (#{reason})") if not self.mute
-
-  unban: (uid, by_ = 'system') =>
+      timestamp: new_timestamp
+    duration_hours = parseInt((new_timestamp - now_time) / 3600 / 1000)
+    if not self.mute
+      nodeirc.action("\u000304#{uid} has been banned #{duration_hours} hours. (#{reason} by #{by_})")
+      self.system_say("#{uid} has been banned #{duration_hours} hours. (#{reason})")
+    self.saveBlacklist()
+
+  unban: (uid, by_) =>
     self = this
     delete self.blacklist[uid] if self.blacklist[uid]
     nodeirc.action("\u000303#{uid} has been unbanned.") if not self.mute
@@ -620,68 +656,28 @@
       uid = get_ip_with_tripcode(ip)
       callback = params.callback
       text = params.text
-      top = params.top
-      color = params.color
-      size = params.size
-      weight = params.weight
-      speed = params.speed
-
-      loop
-        break if not text
-        text = text.substr(0, 64)
-        text = strip_hyper_links(strip_irc_colors(self.stringFilter(text)))
-
-        top = Math.random()
-        color = '255,255,255,0.9'
-        size = 1.0
-        weight = 'bold'
-        speed = 1.0
-
-        item =
-          id: self.last_record_id + 1
-          text: text
-          top: top
-          color: color.split(',')
-          size: size
-          weight: weight
-          speed: speed
-          uid: uid
-          ip: ip
-          timestamp: timestamp
-
-        try
-          self.browserCheck(req)
-          self.blacklistCheck(ip)
-          self.fragtableCheck(address) for address in forwarded_ips
-          self.sensitive_wordCheck(text)
-          self.db.collection(self.dbName).insert item, (result) ->
-            DEBUG('Insert id %d (%s)', self.last_record_id, ip)
-            res.send(jsonp_stringify(ret, callback))
-          self.last_record_id++
-          if not self.mute
-            if self.metadata_updated
-              title = self.metadata.title
-              artist = self.metadata.artist
-              tags = self.metadata.tags
-              nodeirc.say("\u000306Now playing: \u000314#{artist} - #{title} (#{tags})")
-              self.metadata_updated = false
-            nodeirc.action("\u000306[#{uid}] \u000314#{text}")
-        catch error
-          switch error
-            when 'BLACKLIST'
-              ret.code = 418
-              ret.status = 'I\'m a teapot'
-            else
-              ret.code = 500
-              ret.status = error
-          ret.data.push(item)
-          WARN('%s (%s)', error, ip)
-          res.send(jsonp_stringify(ret, callback))
-          if not self.mute && self.last_blacklist_uid != uid
-            nodeirc.action("\u000306[#{uid}]\u000301,01 #{text} \u000304(#{error})")
-          self.last_blacklist_uid = uid
-        return
-
+
+      text = text.substr(0, 64)
+      text = strip_hyper_links(strip_irc_colors(self.stringFilter(text)))
+
+      try
+        self.browserCheck(req)
+        self.blacklistCheck(ip)
+        self.fragtableCheck(address) for address in forwarded_ips
+        self.ignoredWordsCheck(text)
+        self.say(text, ip)
+      catch error
+        switch error
+          when 'BLACKLIST'
+            ret.code = 418
+            ret.status = 'I\'m a teapot'
+          else
+            ret.code = 500
+            ret.status = error
+        WARN('%s (%s)', error, ip)
+        if not self.mute && self.last_blacklist_uid != uid
+          nodeirc.action("\u000306[#{uid}]\u000301,01 #{text} \u000304(#{error})")
+        self.last_blacklist_uid = uid
       res.send(jsonp_stringify(ret, callback))

     self.routes['GET']['/poll'] = (req, res) ->
@@ -796,6 +792,10 @@

   start: =>
     self = this
+    self.loadBlacklist()
+    sensitive_word_files = fs.readdirSync(self.sensitive_word_file_dir)
+    for sensitive_word_file in sensitive_word_files
+      self.loadStringFilter("#{self.sensitive_word_file_dir}/#{sensitive_word_file}")
     self.polling()
     self.app.listen self.port, self.ipaddress, ->
       INFO('Node server started on %s:%d ...', self.ipaddress, self.port)


Fix notification when close to beacon

Fix notification when close to beacon

Notification will only be send to specific user.

--- server.coffee
+++ server.coffee
@@ -59,6 +59,7 @@
 					#send notifcation
 					Event.create
 						unit: 'inRange'
+						include: userId
 						text: 'You are in range of an enemy beacon, capture it now!'
 					found=true;
 		#Check if user is nearby an enemy beacon.
@@ -70,6 +71,7 @@
 						#send notifcation
 						Event.create
 							unit: 'nearby'
+							include: userId
 							text: 'You are ' + parseInt(dist,10)+ ' m away from an enemy beacon, capture it now!'
 						found=true;
 		#Last notification send, so that the user will not be spammed with notifications


Fixed syntax error

Fixed syntax error

--- server.coffee
+++ server.coffee
@@ -7,7 +7,7 @@
         port: process.env.PORT || 9125
         host: process.env.HOST || '127.0.0.1'

-        americano.start options
+    americano.start options

 if not module.parent
     application()


fixed coffeelint error

fixed coffeelint error

--- workers/social/lib/social/models/teamtoken.coffee
+++ workers/social/lib/social/models/teamtoken.coffee
@@ -1,11 +1,12 @@
+# coffeelint: disable=no_implicit_braces
 # JTeamInvitation is a temporary invitation token collection will be used for
 # inviting companies to use/try teams product, this file should be removed after
 # releasing teams product.
-{argv}      = require 'optimist'
+{ argv }    = require 'optimist'
 KONFIG      = require('koding-config-manager').load("main.#{argv.c}")
 jraphical   = require 'jraphical'
-shortid     = require('shortid');
-Bongo       = require "bongo"
+shortid     = require 'shortid'
+Bongo       = require 'bongo'
 Tracker     = require './tracker'
 KodingError = require '../error'
 { extend }  = require 'underscore'
@@ -19,7 +20,7 @@

   @trait __dirname, '../traits/protected'

-  {permit} = require './group/permissionset'
+  { permit } = require './group/permissionset'

   @share()

@@ -63,12 +64,12 @@
         default     : -> new Date

   markAsUsed: (callback) ->
-    operation = $set : { status: 'used' }
+    operation = { $set : { status: 'used' } }
     @update operation, callback

   validTypes: ['pending']

-  isValid:-> @status in @validTypes
+  isValid: -> @status in @validTypes

   remove$: permit 'send invitations',
     success: (client, callback) ->
@@ -80,7 +81,7 @@
       data =   {
         code      : options.code or shortid.generate()[0..3] # eg: VJPj9
         email     : options.email
-        groupName : options.groupName or "koding"
+        groupName : options.groupName or 'koding'
       }

       invite = new JTeamInvitation data
@@ -89,5 +90,5 @@
         return callback null, invite

   @byCode: (code, callback) ->
-    @one {code}, callback
+    @one { code }, callback


added callback

added callback

--- vendor/assets/javascripts/expressionui.modal.js.coffee
+++ vendor/assets/javascripts/expressionui.modal.js.coffee
@@ -33,6 +33,7 @@

         defaultButton: true
       ]
+      closeCallback: $.noop
       beforeOpen: $.noop
       afterOpen: $.noop
       beforeClose: $.noop


Align vars

Align vars

--- tests/recaptcha2.coffee
+++ tests/recaptcha2.coffee
@@ -1,5 +1,5 @@
-nock      = require "nock"
-should    = require "should"
+nock       = require "nock"
+should     = require "should"
 Recaptcha2 = require "../index.coffee"

 recaptcha2 = new Recaptcha2 siteKey: "public_site_key", secretKey: "secret_key"


fixed issue with endpont

fixed issue with endpont

--- src/model/statsd.coffee
+++ src/model/statsd.coffee
@@ -84,11 +84,11 @@
     console.log(results.Successful.length);
     console.log(results.Successful.data[0][0]);

-    var failed = results.Failed.length > 0 ? results.Failed.data[0][0] + results.Failed.data[0][1] : 0,
-      processing = results.Processing.length > 0 ? results.Processing.data[0][0] + results.Processing.data[0][1] : 0,
-      completed = results.Completed.length > 0 ? results.Completed.data[0][0] + results.Completed.data[0][1] : 0,
-      successful = results.Successful.length > 0 ? results.Successful.data[0][0] + results.Successful.data[0][1] : 0,
-      completedWErrors = results['Completed with error(s)'].length  > 0 ? results['Completed with error(s)'].data[0][0] + results['Completed with error(s)'].data[0][1] : 0;
+    var failed = 'data' in results.Failed ? results.Failed.data[0][0] + results.Failed.data[0][1] : 0,
+      processing = 'data'  in results.Processing ? results.Processing.data[0][0] + results.Processing.data[0][1] : 0,
+      completed = 'data' in results.Completed ? results.Completed.data[0][0] + results.Completed.data[0][1] : 0,
+      successful = 'data' in  results.Successful ? results.Successful.data[0][0] + results.Successful.data[0][1] : 0,
+      completedWErrors = 'data' in results['Completed with error(s)'] ? results['Completed with error(s)'].data[0][0] + results['Completed with error(s)'].data[0][1] : 0;

     data.push({


bugfix: regex date entropy

bugfix: regex date entropy

--- src/scoring.coffee
+++ src/scoring.coffee
@@ -177,8 +177,10 @@
       @lg Math.pow(char_class_bases[match.regex_name], match.token.length)
     else switch match.regex_name
       when 'recent_year'
-        # conservative estimate of year space: num years from 2000
-        year_space = parseInt(match.rx_match) - 2000
+        # conservative estimate of year space: num years from 2000.
+        # if year is close to 2000, estimate a year space of 20
+        year_space = parseInt(match.regex_match[0]) - 2000
+        year_space = Math.max year_space, 20
         @lg Math.abs year_space

   date_entropy: (match) ->


Fix package click regression

Fix package click regression

--- app/assets/javascripts/app/controllers/package.coffee
+++ app/assets/javascripts/app/controllers/package.coffee
@@ -2,7 +2,7 @@
   requiredPermission: 'admin.package'
   header: __('Packages')
   events:
-    'click .action':  'action'
+    'click .package-action':  'action'

   constructor: ->
     super


Preliminary fix to typeahead issues when user presses arrow key

--- app/assets/javascripts/modules/autocomplete.js.coffee
+++ app/assets/javascripts/modules/autocomplete.js.coffee
@@ -10,7 +10,8 @@
       remote: '/placenames?q=%QUERY'

     # When the user selects a typeahead value,
-    $placenameInputs.on 'typeahead:selected typeahead:autocompleted', (event, datum) ->
+    # TODO: Upgrade to typeahead-0.10 when available and verify that typeahead:cursorchanged triggers with arrow keys
+    $placenameInputs.on 'typeahead:selected typeahead:autocompleted typeahead:cursorchanged', (event, datum) ->
       {placename, ref, value} = datum
       searchModel.query.keywords(value)


Fix word stats count.

Fix word stats count.

--- app/assets/javascripts/pods/pods_word_stats.js.coffee
+++ app/assets/javascripts/pods/pods_word_stats.js.coffee
@@ -7,13 +7,16 @@
     for pod in pods
       words = pod.summary.toLowerCase().split(" ")
       i = 0
+      uniqueWords = {}
       while i < words.length
         word = words[i].toLowerCase()
+        if word.length > 1
+          uniqueWords[word] = true
         i++
-        if word.length > 1
-          if @wordStats[word] is `undefined`
-            @wordStats[word] = 0
-          @wordStats[word] += 1
+      for word, flag of uniqueWords
+        if @wordStats[word] is `undefined`
+          @wordStats[word] = 0
+        @wordStats[word] += 1
     for word, count of @wordStats
       @wordList.push
         word: word


Node modules koding: remove async require statement

--- node_modules_koding/bongo/lib/expressify.coffee
+++ node_modules_koding/bongo/lib/expressify.coffee
@@ -3,7 +3,6 @@
   bongo.once 'dbClientReady', callback

 module.exports = ->
-  async       = require 'async'
   { argv }    = require 'optimist'
   KONFIG      = require('koding-config-manager').load("main.#{argv.c}")
   { dash }    = require 'bongo'


Require .node file directly

Require .node file directly

--- src/onig-reg-exp.coffee
+++ src/onig-reg-exp.coffee
@@ -1,4 +1,4 @@
-{OnigScanner} = require('bindings')('onig_scanner.node')
+{OnigScanner} = require('../build/Release/onig_scanner.node')

 module.exports =
 class OnigRegExp


updated test to 409 status

updated test to 409 status

--- test/integration/contactGroupsAPITests.coffee
+++ test/integration/contactGroupsAPITests.coffee
@@ -339,7 +339,7 @@
               .set("auth-ts", authDetails.authTS)
               .set("auth-salt", authDetails.authSalt)
               .set("auth-token", authDetails.authToken)
-              .expect(403)
+              .expect(409)
               .end (err, res) ->
                 if err
                   done err


Fix for instantiating the right context for memory.

--- packages/landsofillusions/memory/context.coffee
+++ packages/landsofillusions/memory/context.coffee
@@ -17,8 +17,11 @@
   @initialize()

   @createContext: (memory) ->
-    # Here we query all inherited classes to get one that creates the context.
-    for contextClass in LOI.Memory.Context.classes when contextClass isnt LOI.Memory.Context
+    # Here we query all inherited classes (except Conversation,
+    # which is a fallback) to get one that creates the context.
+    customContextClasses = _.without LOI.Memory.Context.classes, LOI.Memory.Context, LOI.Memory.Contexts.Conversation
+
+    for contextClass in customContextClasses
       if context = contextClass.tryCreateContext memory
         # We've reached the correct context class.
         break
@@ -78,7 +81,7 @@

       # Get only the latest memory per person.
       memories = _.reverse _.sortBy memories, (memory) => memory.endTime
-      memories = _.uniqBy memories, (memory) => memory.actions[0]?.character._id
+      memories = _.uniqBy memories, (memory) => memory.actions?[0]?.character._id
       _.pull memories, undefined

       people = for memory in memories


new settings

new settings

--- app/assets/javascripts/api/crud_comment.js.coffee
+++ app/assets/javascripts/api/crud_comment.js.coffee
@@ -70,7 +70,8 @@
   idname = name.replace(" ", "-").toLowerCase()
   chat_time = hours + ":" + minutes + " " + ampm
   chat_time = options["creation_date"] if options["creation_date"]
-  chat_block = "
" + "" + name + " - " + chat_time + "" + "" + msg + "
"
+  # chat_block = "
" + "" + name + " - " + chat_time + "" + "" + msg + "
"
+  chat_block = "
" + "" + chat_time + "" + "" + msg + "
"
   if options["prepend"]
     inner.prepend chat_block
   else


add tutorial component to mark step

add tutorial component to mark step

--- app/assets/javascripts/components/mark/index.cjsx
+++ app/assets/javascripts/components/mark/index.cjsx
@@ -15,6 +15,8 @@
 DraggableModal          = require 'components/draggable-modal'
 Draggable               = require 'lib/draggable'
 {Link}                  = require 'react-router'
+YaleTutorial            = require '../yale-tutorial'
+YaleMarkTutorialText    = require '../tutorial-text/yale-mark-tutorial-text'

 module.exports = React.createClass # rename to Classifier
   displayName: 'Mark'
@@ -193,7 +195,7 @@
       waitingForAnswer = not currentAnswer

-
+      <YaleTutorial displayed={1} pages={YaleMarkTutorialText.pages} />

         { if @state.noMoreSubjectSets
             style = marginTop: "50px"


Passing data as a parameter to before_add method to enable customization

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -40,7 +40,7 @@
         file = data.files[0]
         file.unique_id = Math.random().toString(36).substr(2,16)

-        unless settings.before_add and not settings.before_add(file)
+        unless settings.before_add and not settings.before_add(file, data)
           current_files.push data
           if $('#template-upload').length > 0
             data.context = $($.trim(tmpl("template-upload", file)))


hotfix pagination count

hotfix pagination count

--- app/assets/javascripts/search/queries/base.coffee
+++ app/assets/javascripts/search/queries/base.coffee
@@ -1,6 +1,6 @@
 # TODO: gueltige werte fuer category?
 class Clarat.Search.Query.Base
-  PER_PAGE: 1
+  PER_PAGE: 20
   BASE_PRECISION: 500
   VALUES_PER_FACET: 300 # must be >= Category.Count to avoid missing categories


Make route dynamic

Make route dynamic

--- src/routes/index.coffee
+++ src/routes/index.coffee
@@ -32,11 +32,37 @@

   for entityKey, entity of req.body
     for fieldKey, field of entity
-      debug "#{entityKey} #{fieldKey} #{wageTheftReportInfo.mapping[entityKey][fieldKey]}"
       form[wageTheftReportInfo.mapping[entityKey][fieldKey]] = field

   request.post {url: wageTheftReportInfo.post_url, form: form}, (err, httpResponse, body) ->
-    return res.status(400).json({err, httpResponse, body}).end() if err?
+    if err?
+      console.log "Unsuccessfully posted wage theft: #{JSON.stringify req.body}"
+      return res.status(400).json({err, httpResponse, body}).end()
+
+    res.status(200).end()
+
+contactUsInfo =
+  post_url: "https://docs.google.com/forms/d/1FTlm-_10lrVszCE5eegwGSzpZCWenVwp7b_GfqBITs4/formResponse"
+  mapping:
+    employer:
+      name: "entry.786354004"
+    employee:
+      name: "entry.2064652974"
+      email: "entry.865525263"
+      phone: "entry.86958528"
+      comments: "entry.37898216"
+
+router.post '/contact_us', (req, res) ->
+  form = {}
+
+  for entityKey, entity of req.body
+    for fieldKey, field of entity
+      form[contactUsInfo.mapping[entityKey][fieldKey]] = field
+
+  request.post {url: contactUsInfo.post_url, form: form}, (err, httpResponse, body) ->
+    if err?
+      console.log "Unsuccessfully posted contact us: #{JSON.stringify req.body}"
+      return res.status(400).json({err, httpResponse, body}).end()

     res.status(200).end()


Date = Innaguration day

Date = Innaguration day

--- src/scripts/main.coffee
+++ src/scripts/main.coffee
@@ -6,7 +6,7 @@

 class Countdown

-  date_pat_out: (new Date(2016, 11, 8, 0, 0, 0, 0))
+  date_pat_out: (new Date("Jan 1, 2017"))

   start: ->
     @isHeOut()
@@ -25,11 +25,13 @@

   countdown: ->
     our_date = Date.now()
-    time_till_pat_out = (@date_pat_out - our_date) / 1000
+    time_till_pat_out = 0
+    if new Date(@date_pat_out) > Date.now()
+      time_till_pat_out = (@date_pat_out - our_date) / 1000

     clock = $('.flipclock').FlipClock( time_till_pat_out,
       clockFace: "DailyCounter"
-      countdown: true
+      countdown: false
     )

   patfact: ->


Added way more slam gifs and padded the original gif to show up more

--- src/scripts/slam.coffee
+++ src/scripts/slam.coffee
@@ -14,7 +14,6 @@
 #   taylor

 images = [
-  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
   "http://38.media.tumblr.com/tumblr_m825bbnJIA1qam74xo1_500.gif",
   "http://33.media.tumblr.com/tumblr_m7dpky3KDX1rof6ulo1_500.gif",
   "http://i.imgur.com/ct3PcLA.gif",
@@ -26,7 +25,151 @@
   "http://24.media.tumblr.com/tumblr_ma4vh18eP61rz79v5o2_500.gif",
   "http://massivnews.com/wp-content/uploads/2013/01/Christian-Bale-0001.gif",
   "https://38.media.tumblr.com/be1396e7b6d816233cad6deea60cff39/tumblr_nrkl22d6HW1tq4of6o1_400.gif",
-  "https://d.gr-assets.com/hostedimages/1380365332ra/717283.gif"
+  "https://d.gr-assets.com/hostedimages/1380365332ra/717283.gif",
+  "https://i.imgur.com/2YnBth.jpg",
+  "http://img1.wikia.nocookie.net/__cb20140712001317/survivor-org/images/5/59/Daaaamn.gif",
+  "http://stream1.gifsoup.com/view7/2905081/daaaamn-o.gif",
+  "http://imgur.com/xbKAw3s.gif",
+  "http://24.media.tumblr.com/eea48b8232cd44ed1be871056a487998/tumblr_mly5rsm5nL1qbipv3o1_500.gif",
+  "http://o.aolcdn.com/hss/storage/midas/c7c1534382efd62189f83a3461fd815c/200512318/andy-dwyer-gif.gif",
+  "http://i.dailymail.co.uk/i/pix/2012/11/20/lakers_gif.gif",
+  "http://i937.photobucket.com/albums/ad219/one_man_show_2010/lool.gif",
+  "http://37.media.tumblr.com/91b05eeeceff90169f57a9c67d56de19/tumblr_mt8wwkvfXg1sin6fio1_500.gif",
+  "http://i.imgur.com/PMdpq.gif",
+  "http://i.imgur.com/M1yKgjm.gif",
+  "http://wwwcache.wralsportsfan.com/asset/colleges/2015/01/23/14381597/horrified_crowd_reaction-288x209.gif",
+  "https://media.giphy.com/media/QgixZj4y3TwnS/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif",
+  "http://media1.giphy.com/media/IfaFEvfGz8CXK/giphy.gif"
   ]


Fixed dropdown type bug (#4499)

Fixed dropdown type bug (#4499)

--- bokehjs/src/coffee/models/widgets/dropdown.coffee
+++ bokehjs/src/coffee/models/widgets/dropdown.coffee
@@ -37,7 +37,7 @@
       $button.click(() => @change_input(@mget("default_value")))
       $toggle = $('<button></button>')
       $toggle.addClass("bk-bs-btn")
-      $toggle.addClass("bk-bs-btn-" + @mget("type"))
+      $toggle.addClass("bk-bs-btn-" + @mget("button_type"))
       $toggle.addClass("bk-bs-dropdown-toggle")
       $toggle.attr("data-bk-bs-toggle", "dropdown")
       $toggle.append($caret)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

   player = GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -45,7 +45,7 @@
       lastFired: I.age

   # TODO: Move this into a more core module
-  sendOrApply: (fn) ->
+  sendOrApply: (fn, args...) ->
     if typeof fn is "function"
       fn.apply(self)
     else


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -24,7 +24,7 @@
   @name every
   @methodOf TimedEvents#
   @param {Number} n Number of frames to wait before executing the callback
-  @param {Function} fn Code to execute after  of frames has passed
+  @param {Function} fn Code to execute after n frames has passed
   ###
   every: (n, fn) ->
     if I.age.mod(n) is 0


Simplify ui_component.coffee

Simplify ui_component.coffee

--- content_scripts/ui_component.coffee
+++ content_scripts/ui_component.coffee
@@ -1,17 +1,14 @@
 class UIComponent
-  iframeElement: null
-  iframePort: null
-  showing: false
-  iframeFrameId: null
-  options: null
-  shadowDOM: null
-
-  toggleIframeElementClasses: (removeClass, addClass) ->
-    @iframeElement.classList.remove removeClass
-    unless @iframeElement.classList.contains addClass
-      @iframeElement.classList.add addClass

   constructor: (iframeUrl, className, @handleMessage) ->
+    @iframeElement = null
+    @iframePort = null
+    @showing = false
+    @iframeFrameId = null
+    # TODO(philc): Make the @options object default to {} and remove the null checks.
+    @options = null
+    @shadowDOM = null
+
     DomUtils.documentReady =>
       styleSheet = DomUtils.createElement "style"
       styleSheet.type = "text/css"
@@ -30,8 +27,10 @@
       # Firefox doesn't support createShadowRoot, so guard against its non-existance.
       # https://hacks.mozilla.org/2018/10/firefox-63-tricks-and-treats/ says
       # Firefox 63 has enabled Shadow DOM v1 by default
-      @shadowDOM = shadowWrapper.attachShadow?( mode: "open" ) ?
-        shadowWrapper.createShadowRoot?() ? shadowWrapper
+      if shadowWrapper.attachShadow
+        @shadowDOM = shadowWrapper.attachShadow(mode: "open")
+      else
+        @shadowArrwap = shadowWrapper
       @shadowDOM.appendChild styleSheet
       @shadowDOM.appendChild @iframeElement
       @toggleIframeElementClasses "vimiumUIComponentVisible", "vimiumUIComponentHidden"
@@ -51,16 +50,20 @@
             { port1, port2 } = new MessageChannel
             @iframeElement.contentWindow.postMessage vimiumSecret, chrome.runtime.getURL(""), [ port2 ]
             port1.onmessage = (event) =>
-              switch event?.data?.name ? event?.data
+              eventName = null
+              if event
+                eventName = (if event.data then event.data.name) || event.data
+              switch eventName
                 when "uiComponentIsReady"
                   # If any other frame receives the focus, then hide the UI component.
                   chrome.runtime.onMessage.addListener ({name, focusFrameId}) =>
-                    if name == "frameFocused" and @options?.focus and focusFrameId not in [frameId, @iframeFrameId]
+                    if name == "frameFocused" and @options and @options.focus and
+                        not [frameId, @iframeFrameId].includes(focusFrameId)
                       @hide false
                     false # We will not be calling sendResponse.
                   # If this frame receives the focus, then hide the UI component.
                   window.addEventListener "focus", (forTrusted (event) =>
-                    if event.target == window and @options?.focus
+                    if event.target == window and @options and @options.focus
                       @hide false
                     true # Continue propagating the event.
                   ), true
@@ -69,39 +72,56 @@
                 when "setIframeFrameId" then @iframeFrameId = event.data.iframeFrameId
                 when "hide" then @hide()
                 else @handleMessage event
+            return
+          return
+        return
       if Utils.isFirefox()
           @postMessage name: "settings", isFirefox: true
+      return
+
+  toggleIframeElementClasses: (removeClass, addClass) ->
+    @iframeElement.classList.remove removeClass
+    @iframeElement.classList.add addClass
+    return

   # Post a message (if provided), then call continuation (if provided).  We wait for documentReady() to ensure
   # that the @iframePort set (so that we can use @iframePort.use()).
   postMessage: (message = null, continuation = null) ->
-    @iframePort?.use (port) ->
-      port.postMessage message if message?
-      continuation?()
+    if @iframePort
+      @iframePort.use (port) ->
+        port.postMessage message if message?
+        if continuation
+          continuation()
+        return
+    return

   activate: (@options = null) ->
     @postMessage @options, =>
       @toggleIframeElementClasses "vimiumUIComponentHidden", "vimiumUIComponentVisible"
-      @iframeElement.focus() if @options?.focus
+      if @options && @options.focus
+        @iframeElement.focus()
       @showing = true
+    return

   hide: (shouldRefocusOriginalFrame = true) ->
     # We post a non-message (null) to ensure that hide() requests cannot overtake activate() requests.
     @postMessage null, =>
-      if @showing
-        @showing = false
-        @toggleIframeElementClasses "vimiumUIComponentVisible", "vimiumUIComponentHidden"
-        if @options?.focus
-          @iframeElement.blur()
-          if shouldRefocusOriginalFrame
-            if @options?.sourceFrameId?
-              chrome.runtime.sendMessage
-                handler: "sendMessageToFrames",
-                message: name: "focusFrame", frameId: @options.sourceFrameId, forceFocusThisFrame: true
-            else
-              Utils.nextTick -> window.focus()
-        @options = null
-        @postMessage "hidden" # Inform the UI component that it is hidden.
+      return unless @showing
+      @showing = false
+      @toggleIframeElementClasses "vimiumUIComponentVisible", "vimiumUIComponentHidden"
+      if @options && @options.focus
+        @iframeElement.blur()
+        if shouldRefocusOriginalFrame
+          if @options && @options.sourceFrameId?
+            chrome.runtime.sendMessage
+              handler: "sendMessageToFrames",
+              message: name: "focusFrame", frameId: @options.sourceFrameId, forceFocusThisFrame: true
+          else
+            Utils.nextTick -> window.focus()
+      @options = null
+      @postMessage "hidden" # Inform the UI component that it is hidden.
+      return
+    return

 root = exports ? (window.root ?= {})
 root.UIComponent = UIComponent


Extract function name-finding into its own function and add support for programmatically-defined getters and setters.

--- application/chrome/content/wesabe/util/inspect.coffee
+++ application/chrome/content/wesabe/util/inspect.coffee
@@ -122,9 +122,8 @@
       frame.lineText = contents[frame.lineNumber-1]
       if frame.lineNumber < contents.length
         for i in [frame.lineNumber..0]
-          m = contents[i].match(/([_a-zA-Z]\w*)\s*:\s*function\s*\(|function\s*([_a-zA-Z]\w*)\s*\([\)]*|((?:get|set)\s+[_a-zA-Z]\w*)\(\)|\.prototype\.([_a-zA-Z]\w*)\s*=\s*function/)
-          if m
-            frame.name = m[1] || m[2] || m[3] || m[4]
+          if name = functionNameForLine(contents[i])
+            frame.name = name
             break

   if lineText = trace[0]?.lineText
@@ -270,6 +269,27 @@
    .yellow('>')
    .toString()

+functionNameForLine = (line) ->
+  # foo: function(...)
+  if match = line.match(/([_a-zA-Z]\w*)\s*:\s*function\s*\(/)
+    match[1]
+
+  # function foo(...)
+  else if match = line.match(/function\s*([_a-zA-Z]\w*)\s*\([\)]*/)
+    match[1]
+
+  # get foo() / set foo(value)
+  else if match = line.match(/((?:get|set)\s+[_a-zA-Z]\w*)\(\)/)
+    match[1]
+
+  # Bar.prototype.foo = function(...)
+  else if match = line.match(/\.prototype\.([_a-zA-Z]\w*)\s*=\s*function/)
+    match[1]
+
+  # __defineGetter__('foo', function() / __defineSetter__('foo', function(...)
+  else if match = line.match(/__define([GS]et)ter__\(['"]([_a-zA-Z]\w*)['"],\s*function/)
+    "#{match[1].toLowerCase()} #{match[2]}"
+
 wesabe.util.inspect = inspect
 wesabe.util.inspectForLog = inspectForLog
 wesabe.util.inspectorFor = inspectorFor


Refactored quotes

Refactored quotes

--- js-library/src/js/util/modal.coffee
+++ js-library/src/js/util/modal.coffee
@@ -1,6 +1,6 @@
 # The iFrame which holds the intermediate
-iFrame = $("<div />").attr
-  "id": "factlink-modal-frame"
+iFrame = $('<div />').attr
+  id: 'factlink-modal-frame'

 iFrame.hide()
 iFrame.appendTo(Factlink.el)


remove components not ready for integration

not ready for merge on this branch

--- js/src/service/SharingClient.coffee
+++ js/src/service/SharingClient.coffee
@@ -10,7 +10,6 @@
   'kryptnostic.credential-loader'
   'kryptnostic.crypto-service-loader'
   'kryptnostic.crypto-service-marshaller'
-  'kryptnostic.share-processing-service'
 ], (require) ->
   _                       = require 'lodash'
   Promise                 = require 'bluebird'
@@ -24,7 +23,6 @@
   RsaCryptoService        = require 'kryptnostic.rsa-crypto-service'
   CryptoServiceLoader     = require 'kryptnostic.crypto-service-loader'
   CryptoServiceMarshaller = require 'kryptnostic.crypto-service-marshaller'
-  ShareProcessingService  = require 'kryptnostic.share-processing-service'

   log     = Logger.get('SharingClient')

@@ -41,7 +39,6 @@
       @directoryApi            = new DirectoryApi()
       @cryptoServiceMarshaller = new CryptoServiceMarshaller()
       @cryptoServiceLoader     = new CryptoServiceLoader()
-      @shareProcessingService  = new ShareProcessingService()
       @credentialLoader        = new CredentialLoader()

     shareObject: (id, uuids) ->
@@ -96,16 +93,6 @@
         log.info('revoked access', { id, uuids })

     processIncomingShares : ->
-      Promise.resolve()
-      .then =>
-        @sharingApi.getIncomingShares()
-      .then (shares) =>
-        @shareProcessingService.processShares(shares)
-
-    registerSearchKeys : (encryptedSearchObjectKeys) ->
-      if _.isEmpty(encryptedSearchObjectKeys)
-        return Promise.resolve()
-      else
-        return @sharingApi.registerSearchKeys(encryptedSearchObjectKeys)
+      throw new Error 'unimplemented'

   return SharingClient


(PLATFORM-120) Fix spacing and linter issues

--- js/src/service/StorageClient.coffee
+++ js/src/service/StorageClient.coffee
@@ -92,7 +92,7 @@
       ))
       .then (inspections) ->
         objectKeys = []
-        _.forEach(inspections, (inspection) =>
+        _.forEach(inspections, (inspection) ->
           if inspection.isFulfilled()
             objectKeys.push(inspection.value())
           else
@@ -136,7 +136,7 @@
           )
           return result
         .catch (error) ->
-          logger.error( 'Failed to get objects')
+          logger.error('Failed to get objects')

     getChildObjects: (objectIds, parentObjectId) ->


Add custom password recovery options to JPasswordRecovery

--- workers/social/lib/social/models/passwordrecovery.coffee
+++ workers/social/lib/social/models/passwordrecovery.coffee
@@ -71,7 +71,7 @@
     else
       JUser.one {username}, (err, user)=>
         unless user then callback new KodingError "Unknown username"
-        else @create client, user.getAt('email'), callback
+        else @create client, {email: user.getAt('email')}, callback

   @recoverPasswordByEmail = secure (client, email, callback)->
     JUser = require './user'
@@ -82,12 +82,23 @@
     else
       JUser.count {email}, (err, num)=>
         unless num then callback null # pretend like everything went fine.
-        else @create client, email, callback
+        else @create client, {email}, callback

-
-  @create = secure ({connection:{delegate}}, email, callback)->
+  @create = secure ({connection:{delegate}}, options, callback)->
     JUser = require './user'
     token = createId()
+    {email} = options
+
+    defaultSubject   = @getPasswordRecoverySubject
+    defaultTextBody  = @getPasswordRecoveryMessage
+    defaultExpiresAt = new Date Date.now() + 1000 * 60 * 30 # 30 minutes
+
+    if delegate.can? 'migrate-kodingen-users'
+      {subject, textbody, expiresAt} = options
+      defaultSubject   = subject if subject
+      defaultTextBody  = textbody if textbody
+      defaultExpiresAt = expiresAt if expiresAt
+
     JUser.one {email}, (err, user)=>
       if err
         callback err
@@ -95,6 +106,7 @@
         certificate = new JPasswordRecovery {
           email
           token
+          expiresAt : defaultExpiresAt
           username  : user.getAt('username')
           status    : 'active'
         }
@@ -110,8 +122,8 @@
             Emailer.send
               From      : @getPasswordRecoveryEmail()
               To        : email
-              Subject   : @getPasswordRecoverySubject()
-              TextBody  : @getPasswordRecoveryMessage(messageOptions)
+              Subject   : defaultSubject()
+              TextBody  : defaultTextBody messageOptions
             , (err)-> callback err

   @validate = secure ({connection:{delegate}}, token, callback)->


undo change to timestore

undo change to timestore

While i think it's worth doing, doesn't belong in this change

--- tutor/src/flux/time.coffee
+++ tutor/src/flux/time.coffee
@@ -1,8 +1,7 @@
- # Store for getting the current server time.
+# Store for getting the current server time.
 # Defaults to browser time if a server request has not been made yet.

 flux = require 'flux-react'
-{ now } = require 'mobx-utils'

 {makeSimpleStore} = require './helpers'

@@ -25,7 +24,7 @@
       @setNow(date, localNow)

   exports:
-    getNow: (localNow = new Date(now(60000))) ->
+    getNow: (localNow = new Date()) ->
       shift = @_shiftMs or 0
       new Date(localNow.getTime() + shift)


Sidebar: minor styling

Sidebar: minor styling

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -328,9 +328,8 @@

   addGroupDescription: ->

-    { dock } = KD.singletons
-
-    dock.getView().addSubView new GroupDescription
+    KD.singletons.dock.getView().addSubView new GroupDescription
+


bugfix : mailbox creation

bugfix : mailbox creation

--- client/app/components/account_config_mailboxes.coffee
+++ client/app/components/account_config_mailboxes.coffee
@@ -166,12 +166,12 @@
                         allowUndefined: true
                         mailboxes: @state.mailboxesFlat
                         selectedMailboxID: @state[box].value
-                        onChange: @onMailboxChange
+                        onChange: (mailbox) => @onMailboxChange mailbox, box

     # requestChange is asynchroneous, so we need to also call setState to only
     # call onSubmit once state has really been updated
-    onMailboxChange: (mailbox) =>
+    onMailboxChange: (mailbox, box) ->
         @state[box].requestChange mailbox
         newState = {}
         newState[box] = mailbox


File submission frontend checks for filesize

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -151,9 +151,16 @@

     fd = new FormData()

+    # Sanity check of file size
+    file_too_large = false
+    max_filesize = 10*1000*1000 # 10 MB
+
     @inputs.each (index, element) ->
       if element.type is 'file'
         if element.files[0] instanceof File
+          if element.files[0].size > max_filesize
+            file_too_large = true
+            alert 'Submission aborted! Your file "' + element.files[0].name + '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
           fd.append(element.id, element.files[0])
         else
           fd.append(element.id, '')
@@ -173,7 +180,8 @@
           else
             alert(response.success)

-    $.ajaxWithPrefix("#{@url}/problem_check", settings)
+    if not file_too_large
+      $.ajaxWithPrefix("#{@url}/problem_check", settings)

   check: =>
     Logger.log 'problem_check', @answers


update Matrix code style

update Matrix code style

--- framer/Matrix.coffee
+++ framer/Matrix.coffee
@@ -1,6 +1,5 @@
 WebKitCSSMatrix::skew = (skew) ->
-	if !skew || skew == 0
-		return @
+	return @ if not skew
 	rad = skew * Math.PI / 180
 	value = Math.tan(rad)
 	m = new WebKitCSSMatrix()
@@ -9,11 +8,9 @@
 	return @multiply(m)

 WebKitCSSMatrix::point = (point = {}) ->
-	x = point.x || 0
-	y = point.y || 0
-	z = point.z || 0
+	{x, y, z} = _.defaults(point, {x:0, y:0, z:0})
 	w = @m14 * x + @m24 * y + @m34 * z + @m44
-	w = w || 1
+	w = w or 1
 	return point =
 		x: (@m11 * x + @m21 * y + @m31 * z + @m41) / w
 		y: (@m12 * x + @m22 * y + @m32 * z + @m42) / w


Enable login feature in sidebar

Enable login feature in sidebar

--- core/app/backbone/views/facts/react_discussion.coffee
+++ core/app/backbone/views/facts/react_discussion.coffee
@@ -59,8 +59,7 @@

   render: ->
     _div ['discussion'],
-      #_div ['discussion-login'],
-      #  ReactSidebarLogin()
+      ReactSidebarLogin()

       _div ['top-annotation'],
         _div ['top-annotation-text'],


Context.properties tests skeleton

Context.properties tests skeleton

--- test/contextSpec.coffee
+++ test/contextSpec.coffee
@@ -60,3 +60,7 @@
           it 'should not affect PrivateContext', ->
             expect(u(PrivateContext).keys()).toEqual []

+      describe '#properties', ->
+        describe 'with affected PrivateContext', ->
+        describe 'with empty PrivateContext', ->
+


engine data module

engine data module

--- test/engine_data.coffee
+++ test/engine_data.coffee
@@ -1,6 +1,6 @@
 module "Engine"

-test "#delay", 1, ->
+test "#data", 1, ->
   engine = Engine
     backgroundColor: false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -11,4 +11,6 @@

   player.include Flickerable

+
+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -18,6 +18,8 @@

 test "#closest", ->
   o = GameObject()
+
+  other =

   equals o.closest([]), null


Add failing test for multiple active contexts

--- test/listnr-test.coffee
+++ test/listnr-test.coffee
@@ -121,6 +121,22 @@
       triggerCombo(@el, 'a')

       assert.calledOnce(spy)
+
+    '// allow activating multiple contexts': ->
+      defSpy = @spy()
+      ctxSpy = @spy()
+
+      @listnr
+        .map('a', defSpy)
+        .addContext('context')
+        .map('b', ctxSpy)
+        .activate('default', 'context')
+
+      triggerCombo(@el, 'a')
+      triggerCombo(@el, 'b')
+
+      assert.calledOnce(defSpy)
+      assert.calledOnce(ctxSpy)

   '.reset':


Moved over to powerset for options generation

--- test/option_sets.coffee
+++ test/option_sets.coffee
@@ -1,4 +1,5 @@
 _ = require 'underscore'
+powerset = require 'powerset'

 ARG_OPTIONS =
   all: '-a'
@@ -11,16 +12,9 @@
 args = process.argv.slice(2)
 options = {}; options[key] = value in args for key, value of ARG_OPTIONS

-gen = (keys) -> results = {}; results[key] = (key in keys) for key in OPTION_KEYS; return results
-
-combos = (array) ->
-  results = [gen([])]
-  results.push(gen([key])) for key in array # start with single keys
-  array = _.clone(array)
-  while array.length
-    keys = [array.pop()]
-    results.push(gen(keys = keys.concat([item]))) for item in array
-  return results
+arrayToOptions = (keys) -> results = {}; results[key] = (key in keys) for key in OPTION_KEYS; return results

 options.all or= _.every(['none'].concat(OPTION_KEYS), (key) -> not options[key])
-module.exports = if options.all then combos(OPTION_KEYS) else [options]
+module.exports = if options.all then _.map(powerset(OPTION_KEYS), arrayToOptions) else [options]
+
+console.log module.exports


Generalized option sets

Generalized option sets

--- test/option_sets.coffee
+++ test/option_sets.coffee
@@ -1,14 +1,17 @@
+_ = require 'underscore'
+
+OPTIONS =
+  all: '-a'
+  none: '-n'
+  cache: '-c'
+  query_cache: '-q'
+  embed: '-e'

 args = process.argv.slice(2)
-options =
-  none: '-n' in args
-  cache: '-c' in args
-  query_cache: '-q' in args
-  embed: '-e' in args
-  all: '-a' in args
-options.all or= (not options.cache and not options.query_cache and not options.embed and not options.none)

-OPTIONS = ['cache', 'query_cache', 'embed']
+options = {}; options[key] = value in args for key, value of OPTIONS
+options_keys = _.keys(OPTIONS)
+
 gen = (keys) ->
   results = {}
   results[key] = true for key in keys
@@ -22,4 +25,7 @@
     (keys.push(item); results.push(gen(keys))) for item in array
   return results

-module.exports = if options.all then combos(OPTIONS) else [options]
+options.all or= _.every(_.without(options_keys, 'all'), (key) -> not options[key])
+module.exports = if options.all then combos(_.without(options_keys, 'all', 'none')) else [options]
+
+console.log module.exports


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -189,6 +189,6 @@

   engine.update()

-  equal engin
+  equal engine.

 module()


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -24,6 +24,6 @@
     width: 10
     height: 10

-  fastEnemy.
+  fastEnemy.include()

 module()


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -36,6 +36,6 @@
     width: 10
     height: 10

-  ok right
+  rig

 module()


Update mecano test to pass encoding argument

--- test/mecano.coffee
+++ test/mecano.coffee
@@ -23,7 +23,7 @@
     .on 'error', ->
       false.should.be.ok
     .on 'end', ->
-      misc.file.readFile null, "#{scratch}/chain/file", (err, content) ->
+      misc.file.readFile null, "#{scratch}/chain/file", 'utf8', (err, content) ->
         return next err if err
         content.should.eql 'helloworld'
         next()
@@ -41,7 +41,7 @@
       destination: "#{scratch}/chain/file"
       append: true
     .on 'both', ->
-      misc.file.readFile null, "#{scratch}/chain/file", (err, content) ->
+      misc.file.readFile null, "#{scratch}/chain/file", 'utf8', (err, content) ->
         return next err if err
         content.should.eql 'helloworld'
         next()


comment update

comment update

--- test/models.coffee
+++ test/models.coffee
@@ -1,4 +1,4 @@
-# see http://net.tutsplus.com/tutorials/javascript-ajax/better-coffeescript-testing-with-mocha/
+# see http://net.tutsplus.com/tutorials/javascript-ajax/better-coffeescript-testing-with-mocha/ for setup
 should = require('chai').should()
 {Track, Section, Transform, Straight, Bend, Split, Crossover, Merge} = require '../src/woodentrack'


Use local var

Use local var

--- test/random.coffee
+++ test/random.coffee
@@ -53,7 +53,7 @@
 #
 random.utf8 = utf8 = (n = 16)->
   z = for i in [1..n]
-    z = random.pick bits
+    z = pick bits
     random z.min, z.max
   String.fromCharCode.apply String, z


Check redid keys after each test

Check redid keys after each test

--- test/update.coffee
+++ test/update.coffee
@@ -34,12 +34,12 @@
       Users.update [{email: 'missing@email.com'}], (err, users) ->
         # Todo, could be "Record without identifier or unique properties
         err.message.should.eql 'Invalid record, got {"email":"missing@email.com"}'
-        next()
+        Users.clear next

     it 'should use unique index and fail because the provided value is not indexed', (next) ->
       Users.update [{username: 'missing'}], (err, users) ->
         err.message.should.eql 'Unsaved record'
-        next()
+        Users.clear next

   describe 'unique', ->


fix register handlers

fix register handlers

--- src/wx.coffee
+++ src/wx.coffee
@@ -498,10 +498,12 @@

             # 收到文本消息时，如注册了文本处理句柄，使用该句柄处理，否则响应`200`。
             when 'text'
-              async.eachSeries text_handlers, ([pattern, text_handler], callback) ->
-                if (typeof pattern is 'string' and message.content.trim() is pattern) or (typeof pattern is 'object' and match = message.content.match(pattern))
+              async.eachSeries text_handlers, ([pattern, handlers], callback) ->
+                if match = message.content.trim().match pattern
                   _(req.params).extend(match)
-                  text_handler req, res, callback
+                  async.eachSeries handlers, (handler, callback) ->
+                    handler req, res, callback
+                  , callback
                 else
                   callback()
               , ->
@@ -919,68 +921,58 @@

     # ### 注册文本消息处理句柄
     text: (args...) =>
-      switch typeof _.first(args)
-        when 'function'
-          pattern = /.*/
-        when 'string'
-          pattern = args.shift()
-
-      for handler in args
-        text_handlers.push [pattern, handler]
+      pattern = _.first(args)
+      if _.isRegExp pattern
+        pattern = args.shift()
+      else if _.isFunction pattern
+        pattern = /.*/
+      else if _.isString pattern
+        pattern = new RegExp(args.shift(), 'i')
+
+      text_handlers.push [pattern, args]
       @

     # ### 注册图片消息处理句柄
     image: (args...) =>
-      for handler in args
-        @image_handlers.push handler
+      @image_handlers = args
       @

     # ### 注册语音消息处理句柄
     voice: (args...) =>
-      for handler in args
-        @voice_handlers.push handler
+      @voice_handlers = args
       @

     # ### 注册视频消息处理句柄
     video: (args...) =>
-      for handler in args
-        @video_handlers.push handler
+      @video_handlers = args
       @

     # ### 注册地理位置处理句柄
     location: (args...) =>
-      for handler in args
-        @location_handlers.push handler
+      @location_handlers = args
       @

     # ### 注册链接处理句柄
     link: (args...) =>
-      for handler in args
-        @link_handlers.push handler
+      @link_handlers = args
       @

     # ### 注册关注与取消关注处理句柄
     subscribe   :  (args...) =>
-      for handler in args
-        @subscribe_handlers.push handler
+      @subscribe_handlers = args
       @
     unsubscribe : (args...) =>
-      for handler in args
-        @unsubscribe_handlers.push handler
+      @unsubscribe_handlers = args
       @

     # ### 注册模板消息处理句柄
     templatesendjobfinish: (args...) =>
-      for handler in args
-        @templatesendjobfinish_handlers.push handler
+      @templatesendjobfinish_handlers = args
       @

     # ### 注册点击菜单按钮处理句柄
     click: (key, args...) =>
-      unless click_handlers[key]
-        click_handlers[key] = []
-      for handler in args
-        click_handlers[key].push handler
+      click_handlers[key] = args
       @

     # ### 注册二维码处理句柄
@@ -990,10 +982,7 @@
           channel = ''
         when 'string'
           channel = args.shift().toLowerCase()
-      unless click_handlers[channel]
-        scan_handlers[channel] = []
-      for handler in args
-        scan_handlers[channel].push handler
+      scan_handlers[channel] = args
       @

     # ### 检索用户基本信息


allowing capital B in binary literals; see #2021; thanks @BrendanEich

--- lexer.coffee
+++ lexer.coffee
@@ -134,7 +134,7 @@
     return 0 unless match = NUMBER.exec @chunk
     number = match[0]
     lexedLength = number.length
-    if binaryLiteral = /0b([01]+)/.exec number
+    if binaryLiteral = /0b([01]+)/i.exec number
       number = (parseInt binaryLiteral[1], 2).toString()
     @token 'NUMBER', number
     lexedLength


experimenting with different approaches to component interaction

Former-commit-id: 7191521bf02675be8e8e3e75ce9e086fa4b5d213 [formerly ae0d9546a4c563e20ed08ffddf78d86057a7bb6f] [formerly bc31b7b3b6bee1705975a6b1d56c695943e4145f [formerly fde37788574ece73bbf4c10b9d9c6826e4cbcd1e]] [formerly 413aebfdf59d40f13aa423181bdd90402bf63a4b [formerly d459ea8a47489123530eb30cc008e57f9a513aca] [formerly 5f76253ce0bbb3fc11540647ddf193610a3e4d22 [formerly 5f76253ce0bbb3fc11540647ddf193610a3e4d22 [formerly a0d2d04497e1a520fea821cd806b16a23f401eac]]]]
Former-commit-id: 89dd76a6489fed866ef096834c1416980e68e849 [formerly 4fd98c3f95ed984b93e92c4a559a0c8da17f47fa] [formerly 25e438bedd1f3989e55b653af18acbd8d23b4d92 [formerly 07cd8253a87405e18250d0f15b5a0e5e86f5a603]]
Former-commit-id: d173bceef8720de052747fc737e59e31759d20f9 [formerly bd36677b649bcc963463ab6c0028eaffd74ae05a]
Former-commit-id: c2bd23eb954725e5334986c25dd13f49bb37275e
Former-commit-id: a8d77ecff0480af01baac5d4fae7597772a61d45

--- @client/franklin.coffee
+++ @client/franklin.coffee
@@ -22,6 +22,31 @@
 HISTOGRAM_WIDTH = 600    # Width of the slider / histogram base
 MAX_HISTOGRAM_HEIGHT = 200
 DECISION_BOARD_WIDTH = 544
+
+# React mixins
+
+##
+# We enforce strict adherence to all data required for rendering to be
+# captured in props or state for a component. Therefore, we can ensure
+# that a component should not have to update unless either its props
+# or state has changed.
+#
+# This introduces an opportunity for a particular kind of bug that occurs if a nested component
+# depends on data fetched from activeREST, like some user information. If the user
+# data is updated and that information is not present as state or props
+# on the parent, then the child component that depends on that data
+# will not be rerendered.
+#
+# One solution to this problem is to reflow from the top down whenever
+# the cache is updated by using React.forceUpdate.
+#
+# Note also that this the place that Om apparently really stands out --
+# http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/
+StrictReactComponent =
+  shouldComponentUpdate : (next_props, next_state) ->
+    !_.isEqual(next_props, @props) || !_.isEqual(next_state, @state)
+
+

 ####################
 # React Components
@@ -35,11 +60,13 @@
 #               |    /            \
 #              Point             NewPoint

+
 ##
 # Proposal
 # The mega component for a proposal.
 # Has proposal description, feelings area (slider + histogram), and reasons area
 Proposal = React.createClass
+  mixins: [StrictReactComponent]
   displayName: 'Proposal'

   ####
@@ -285,6 +312,7 @@
           valence: 'pro'
           included_points : @props.data.included_points
           onPointShouldBeRemoved : @onPointShouldBeRemoved
+          points: fetch { url: 'all_points' }

         #your reasons
         DecisionBoard
@@ -293,6 +321,7 @@
           onPointShouldBeIncluded : @onPointShouldBeIncluded
           onPointShouldBeCreated : @onPointShouldBeCreated
           toggleState: @toggleState
+          points: fetch { url: 'all_points' }

         #community cons
         CommunityPoints
@@ -301,10 +330,12 @@
           valence: 'con'
           included_points : @props.data.included_points
           onPointShouldBeRemoved : @onPointShouldBeRemoved
+          points: fetch { url: 'all_points' }

 ##
 # Histogram
 Histogram = React.createClass
+  mixins: [StrictReactComponent]

   getDefaultProps : ->
     opinions: []
@@ -378,6 +409,7 @@
 # Manages the slider and the UI elements attached to it.
 Slider = React.createClass
   displayName: 'Slider'
+  mixins: [StrictReactComponent]

   getInitialState : ->
     stance_segment : @getStanceSegmentFromSliderValue(@props.initial_stance)
@@ -475,6 +507,7 @@
 # Handles the user's list of important points in crafting state.
 DecisionBoard = React.createClass
   displayName: 'DecisionBoard'
+  mixins: [StrictReactComponent]

   componentDidMount : ->
     # make this a drop target
@@ -519,6 +552,7 @@
 # Creates NewPoint instances.
 YourPoints = React.createClass
   displayName: 'YourPoints'
+  mixins: [StrictReactComponent]

   render : ->

@@ -556,6 +590,7 @@
 # Shown in wing during crafting, in middle on results.
 CommunityPoints = React.createClass
   displayName: 'CommunityPoints'
+  mixins: [StrictReactComponent]

   componentDidMount : ->
     # Make this a drop target to facilitate removal of points
@@ -599,6 +634,7 @@
 # A single point in a list.
 Point = React.createClass
   displayName: 'Point'
+  mixins: [StrictReactComponent]

   setDraggability : ->
     # Ability to drag include this point if a community point,
@@ -638,6 +674,7 @@
 # Manages whether the user has clicked "add a new point". If they have, show new point form.
 NewPoint = React.createClass
   displayName: 'NewPoint'
+  mixins: [StrictReactComponent]

   getInitialState : ->
     editMode : false
@@ -691,6 +728,7 @@
 # Supports straight up img src, or using the CSS-embedded b64 for each user
 Avatar = React.createClass
   displayName: 'Avatar'
+  #mixins: [StrictReactComponent]

   getDefaultProps : ->
     user: -1 # defaults to anonymous user


UI/core close #44 allowing UI.Menu#hide and #show methods accept visual effects

--- ui/core/src/menu.coffee
+++ ui/core/src/menu.coffee
@@ -71,7 +71,7 @@
   # @return {Menu} this
   #
   show: ->
-    Menu.current = @constructor.current = @$super().emit('show')
+    Menu.current = @constructor.current = @$super.apply(@, arguments).emit('show')

   #
   # Making the menu element to get removed out of the DOM
@@ -80,7 +80,7 @@
   #
   hide: ->
     Menu.current = @constructor.current = null
-    @$super().emit('hide')
+    @$super.apply(@, arguments).emit('hide')

   #
   # Selects the next link on the menu


Use debug mode for dispatcher events

Use debug mode for dispatcher events

--- client/app/libs/flux/dispatcher/dispatcher.coffee
+++ client/app/libs/flux/dispatcher/dispatcher.coffee
@@ -90,7 +90,7 @@
         @param {object} payload
     ###
     dispatch: (payload) ->
-        console.log "DIS", payload.action.type, payload.action.value
+        console.debug "DIS", payload.action.type, payload.action.value
         message = 'Dispatch.dispatch(...): Cannot dispatch in the middle ' + \
                   'of a dispatch.'
         invariant(


a regression fixed

a regression fixed

--- client/landing/app/CommonViews/DNDUploader.coffee
+++ client/landing/app/CommonViews/DNDUploader.coffee
@@ -61,7 +61,7 @@
             origin  : "external"
             filename: file.name
             instance: fsFile
-            content : readEvent.target.multipleItems
+            content : readEvent.target.result
           , event, readEvent

           @reset() if file is lastFile


figuring out beginClassification issues

figuring out beginClassification issues

--- app/assets/javascripts/components/mark/index.cjsx
+++ app/assets/javascripts/components/mark/index.cjsx
@@ -41,6 +41,9 @@
     @setState
       taskKey: @props.workflow.first_task

+    console.log "finding error location: @componentWillMount"
+    console.log "this", this
+
     @beginClassification()

@@ -99,6 +102,7 @@
   handleToolComplete: (d) ->
     @handleDataFromTool(d)
     @commitClassification()
+    console.log "finding error location: @handleToolComplete"
     @beginClassification()

   # Handle user selecting a pick/drawing tool:


rename path variable

rename path variable

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -210,10 +210,10 @@
         justAppend = /\=$/.test(settings.previewProxy)
         useAmpersand = /[^\&\?]$/.test(settings.previewProxy)

-        path = if justAppend then encodedUrl else
+        queryToAppend = if justAppend then encodedUrl else
           if useAmpersand then "&url=#{encodedUrl}" else "url=#{encodedUrl}"

-        utils.normalizeUrl(settings.previewProxy) + path
+        utils.normalizeUrl(settings.previewProxy) + queryToAppend

     settings


Add page#hasClass to check whether an element has a specified DOM class.

--- application/chrome/content/wesabe/dom/page.coffee
+++ application/chrome/content/wesabe/dom/page.coffee
@@ -350,6 +350,12 @@
     wesabe.taint((wesabe.untaint(node.nodeValue) for node in textNodes).join(''))

   #
+  # Returns +true+ if +xpathOrNode+ has class +className+, +false+ otherwise.
+  #
+  hasClass: (document, xpathOrNode, className) ->
+    " #{@findStrict(document, xpathOrNode).className} ".indexOf(" #{className} ") > -1
+
+  #
   # Goes back one step in the document's window's history.
   #
   # @param document [HTMLDocument]


Actually sync follows

Actually sync follows

--- apps/home_2/components/follow_button/index.coffee
+++ apps/home_2/components/follow_button/index.coffee
@@ -16,8 +16,9 @@

   kind = if module.key is 'related_artists' then 'artist' else 'gene'
   Model = if module.key is 'related_artists' then Artist else Gene
+  followable = followableObject[module.key](module)

-  model = new Model followableObject[module.key](module)
+  model = new Model followable
   following = new Following null, kind: kind

   $el.append template()
@@ -29,3 +30,5 @@
     modelName: kind
     context_module: "Homepage rail"

+  following.syncFollows [followable.id]
+


change room name

change room name

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -67,11 +67,9 @@
 		return Session.get 'uploading'

 	roomName: ->
-		roomData = Session.get('roomData' + this._id)
-		return '' unless roomData
-
-		return RocketChat.roomTypes.getRoomName roomData?.t, roomData
-
+		project_info = ChatRoom.findOne(this._id).details
+		return project_info.name
+
 	roomTopic: ->
 		roomData = Session.get('roomData' + this._id)
 		return '' unless roomData


This fixes the "ref undefined" error in the following line: (callback) => @db.apps.checkDomain oauthio.k, ref, callback

--- plugins/server.request/server_request.coffee
+++ plugins/server.request/server_request.coffee
@@ -30,6 +30,10 @@

 	@apiRequest = (req, provider_name, oauthio, callback) =>
 		req.headers ?= {}
+		ref = fixUrl(req.headers['referer'] || req.headers['origin'] || "http://localhost");
+		urlinfos = Url.parse(ref)
+		if not urlinfos.hostname
+			return next new restify.InvalidHeaderError 'Missing origin or referer.'
 		async.parallel [
 			(callback) => @db.providers.getExtended provider_name, callback
 			(callback) => @db.apps.getKeyset oauthio.k, provider_name, callback


jupyter: copy/paste **between** different jupyter notebooks in same smc tab... works

--- src/smc-webapp/jupyter/actions.coffee
+++ src/smc-webapp/jupyter/actions.coffee
@@ -16,6 +16,9 @@

 {Actions}  = require('../smc-react')

+# Used for copy/paste.  We make a single global clipboard, so that
+# copy/paste between different notebooks works.
+global_clipboard = undefined

 ###
 The actions -- what you can do with a jupyter notebook, and also the
@@ -490,10 +493,9 @@
     # Copy all currently selected cells into our internal clipboard
     copy_selected_cells: =>
         cells = @store.get('cells')
-        clipboard = immutable.List()
+        global_clipboard = immutable.List()
         for id in @store.get_selected_cell_ids_list()
-            clipboard = clipboard.push(cells.get(id))
-        @setState(clipboard: clipboard)
+            global_clipboard = global_clipboard.push(cells.get(id))
         return

     # Cut currently selected cells, putting them in internal clipboard
@@ -544,8 +546,7 @@
         if delta == 0
             # replace, so delete currently selected
             @delete_selected_cells(false)
-        clipboard = @store.get('clipboard')
-        if not clipboard? or clipboard.size == 0
+        if not global_clipboard? or global_clipboard.size == 0
             return   # nothing more to do
         # put the cells from the clipboard into the document, setting their positions
         if not cell_before_pasted_id?
@@ -555,8 +556,8 @@
         else
             before_pos = cells.getIn([cell_before_pasted_id, 'pos'])
             after_pos  = cells.getIn([@store.get_cell_id(+1, cell_before_pasted_id), 'pos'])
-        positions = @_positions_between(before_pos, after_pos, clipboard.size)
-        clipboard.forEach (cell, i) =>
+        positions = @_positions_between(before_pos, after_pos, global_clipboard.size)
+        global_clipboard.forEach (cell, i) =>
             cell = cell.set('id', @_new_id())   # randomize the id of the cell
             cell = cell.set('pos', positions[i])
             @_set(cell, false)


feat: add messageId to serviceBus meta

feat: add messageId to serviceBus meta

--- src/sources/service.bus.source.coffee
+++ src/sources/service.bus.source.coffee
@@ -12,6 +12,7 @@
   newNotification: ({ context: { bindingData }, message }) ->
     message: _.omit message, "Sent"
     meta:
+      messageId: bindingData.messageId
       insertionTime: bindingData.enqueuedTimeUtc
       dequeueCount: bindingData.deliveryCount
       properties: bindingData.userProperties or bindingData.properties


isolateForm directive for nested angular forms, validations, fixes #9

--- static/coffee/flows/validation.coffee
+++ static/coffee/flows/validation.coffee
@@ -103,3 +103,33 @@

   require:"ngModel"
   link: link
+
+# Creates an isolated form to use for nested ng-forms
+# http://jsfiddle.net/gikoo/qNrFX/
+app.directive "isolateForm", ->
+  restrict: 'A',
+  require: '?form',
+  link: (scope, element, attrs, ctrl) ->
+      if !ctrl
+        return
+
+      # Do a copy of the controller
+      ctrlCopy = {}
+      angular.copy(ctrl, ctrlCopy)
+
+      # Get the parent of the form
+      parent = element.parent().controller('form')
+      # Remove parent link to the controller
+      parent.$removeControl(ctrl)
+
+      # Replace form controller with a "isolated form"
+      isolatedFormCtrl =
+          $setValidity: (validationToken, isValid, control) ->
+              ctrlCopy.$setValidity(validationToken, isValid, control)
+              parent.$setValidity(validationToken, true, ctrl)
+          $setDirty: ->
+              element.removeClass('ng-pristine').addClass('ng-dirty')
+              ctrl.$dirty = true
+              ctrl.$pristine = false
+
+      angular.extend(ctrl, isolatedFormCtrl)


small gtex change

small gtex change

--- static/js/controllers/index.js.coffee
+++ static/js/controllers/index.js.coffee
@@ -12,8 +12,7 @@
   # selected results is placed directly back into factory
   # this way, we can just push to next controller and everything is ready :)
   $scope.guidesFactory = new GuidesFactory()
-  $scope.guidesFactory.data.gtex_enabled
-  $scope.gtex_enabled = true
+  $scope.guidesFactory.data.gtex_enabled = true

   # Validations
   $scope.genesWarning = false
@@ -68,4 +67,4 @@
   # From pre-calculated experimental results
   $scope.waitTime = () ->
     3 + Math.max(30,($scope.guidesFactory.data.quantity - 100)) // 8 * $scope.guidesFactory.data.genes.length
-]+]


Allow repl to start even without db connection

--- repl.coffee
+++ repl.coffee
@@ -27,6 +27,11 @@
   require('./lib/models') context
   context.Factory = require('./test/lib/factory')

+  # Set up callback for error
+  context.mongoose.connection.on 'error', (err) ->
+    console.log 'Could not connect to mongo server.'
+    return
+
   # Connect to mongo
   context.mongoose.connect context.env.mongo.uri, context.env.mongo.options


Follow up 949a47a7be6cf10dc7fd38c3c5b06def6efad26d - Fixes #4040 - users customer name is displayed as id.

--- app/assets/javascripts/app/controllers/user.coffee
+++ app/assets/javascripts/app/controllers/user.coffee
@@ -49,6 +49,9 @@
       @query = query
       @delay(@search, 220, 'search')
     )
+
+    # App.User.subscribe will clear model data so we use controllerBind (#4040)
+    @controllerBind('User:create User:update User:touch User:destroy', => @delay(@search, 220, 'search'))

     # show last 20 users
     @search()
@@ -183,9 +186,6 @@
           'click': edit
     )

-    if !@subscribeId
-      @subscribeId = App.User.subscribe(=> @delay(@search, 220, 'search'))
-
   search: =>
     role_ids = []
     @$('.tab.active').each( (i,d) ->
@@ -243,8 +243,4 @@
       container: @el.closest('.content')
     )

-  release: =>
-    if @subscribeId
-      App.User.unsubscribe(@subscribeId)
-
 App.Config.set( 'User', { prio: 1000, name: __('Users'), parent: '#manage', target: '#manage/users', controller: User, permission: ['admin.user'] }, 'NavBarAdmin' )


removed sanity checks

removed sanity checks

--- app/assets/javascripts/housing_locations.js.coffee
+++ app/assets/javascripts/housing_locations.js.coffee
@@ -12,32 +12,12 @@
   popup = L.popup(minWidth: 100).setContent(""+name+"
"+location)
   marker.bindPopup(popup).openPopup()

-# Google's Geocoder
-
-displayHousingLocationMap = (address,name,map) ->
-  geocoder = new (google.maps.Geocoder)
-  coordinates = []
-  geocoder.geocode {
-    'address': address
-    'region': 'us'
-  }, (results, status) ->
-    if status == google.maps.GeocoderStatus.OK
-      coordinates[0] = results[0].geometry.location.lat()
-      coordinates[1] = results[0].geometry.location.lng()
-      addMarker coordinates,name,map
-      $('#housing-location-modal').modal()
-      popUp name,address
-    else
-      result = 'Unable to find address: ' + status
-      alert('Address not found.')
-    return
-
 $ ->
   $('.housing-location-table').DataTable({
     dom: "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>",
     pagingType: "simple_numbers",
     language: {
-      search: "",
+      search: "",
       searchPlaceholder: "Search"
     },
     "columnDefs": [
@@ -73,7 +53,7 @@
     dom: "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>",
     pagingType: "simple_numbers",
     language: {
-      search: "",
+      search: "",
       searchPlaceholder: "Search"
     },
     "columnDefs": [
@@ -124,17 +104,9 @@
     $('#housing-location-modal .modal-title').text(housing_data.name)
     name = housing_data.name
     address = housing_data.location
-    # Case where longitude and latitude exist.
-    # Remove code when all pdfs have valid lats/longs on initialize
-    if (housing_data.lat != null && housing_data.long != null)
-      console.log('retrieving coordinates using database')
-      coordinates = [housing_data.lat, housing_data.long]
-      addMarker coordinates,name,map
-      $('#housing-location-modal').modal()
-      popUp name,address
-    # call geocode on empty lat/long
-    # currently does NOT SAVE coordinates on location display.
-    else
-      console.log('calling geocode')
-      displayHousingLocationMap address,name,map
+    coordinates = [housing_data.lat, housing_data.long]
+
+    addMarker coordinates,name,map
+    $('#housing-location-modal').modal()
+    popUp name,address
   )


add s

add s

--- app/assets/javascripts/lib/maps/lodging_map.coffee
+++ app/assets/javascripts/lib/maps/lodging_map.coffee
@@ -25,7 +25,7 @@
 #   lodgingMap.render()
 #
 #
-define ['jquery','lib/utils/css_helper', 'lib/maps/map_style'], ($, CssHelper, mapStyles) ->
+define ['jquery','lib/utils/css_helper', 'lib/maps/map_styles'], ($, CssHelper, mapStyles) ->

   class LodgingMap


latency is seconds, not percent

latency is seconds, not percent

--- scripts/pipeline.coffee
+++ scripts/pipeline.coffee
@@ -17,10 +17,10 @@
     load = getLoad msg, (load) ->
       msg.send "Pipeline 2 engine CPU: "+load.engine.cpu+" %"
       msg.send "Pipeline 2 engine memory: "+load.engine.mem+" %"
-      msg.send "Pipeline 2 engine latency: "+load.engine.latency+" %"
+      msg.send "Pipeline 2 engine latency: "+load.engine.latency+" s"
       msg.send "Pipeline 2 web ui CPU: "+load.webui.cpu+" %"
       msg.send "Pipeline 2 web ui memory: "+load.webui.mem+" %"
-      msg.send "Pipeline 2 web ui latency: "+load.webui.latency+" %"
+      msg.send "Pipeline 2 web ui latency: "+load.webui.latency+" s"

   robot.respond /pipeline get (.*)/i, (msg) ->
     url = "http://localhost:8181/ws"+msg.match[1]


fill up feature should be configurable

fill up feature should be configurable

--- gem/vendor/assets/javascripts/angular-table.js.coffee
+++ gem/vendor/assets/javascripts/angular-table.js.coffee
@@ -88,16 +88,15 @@
       tr = tbody.find "tr"
       tr.attr("ng-repeat", "item in #{listName} #{filterString} | orderBy:predicate:descending")

-      tds = element.find("td")
-      tdString = ""
-      for td in tds
-        tdString += "{{item}} 	"
+      # tds = element.find("td")
+      # tdString = ""
+      # for td in tds
+      #   tdString += "{{item}} 	"

+      # fillerTr = angular.element("
#{tdString}
")
+      # fillerTr.attr("ng-repeat", "item in #{paginationName}.getFillerArray() ")

-      fillerTr = angular.element("
#{tdString}
")
-      fillerTr.attr("ng-repeat", "item in #{paginationName}.getFillerArray() ")
-
-      tbody.append(fillerTr)
+      # tbody.append(fillerTr)

       {
         post: ($scope, $element, $attributes) ->


message hash is the unique identifier

message hash is the unique identifier

--- zechat/static/peers.coffee
+++ zechat/static/peers.coffee
@@ -70,6 +70,7 @@
       return

     message = JSON.parse(zc.b64decode(packed_message))
+    message.hash = zc.message_hash(packet.data)
     peer = @app.request('peer', sender)
     peer.message_col.add(message)

@@ -103,12 +104,22 @@
   childView: zc.PeerListItemView

+class zc.MessageModel extends Backbone.Model
+
+  idAttribute: 'hash'
+
+
+class zc.MessageCollection extends Backbone.Collection
+
+  model: zc.MessageModel
+
+
 class zc.PeerModel extends Backbone.Model

   idAttribute: 'pubkey'

   initialize: ->
-    @message_col = new Backbone.Collection()
+    @message_col = new zc.MessageCollection()

 class zc.PeerList extends zc.Controller


Minor whitespacing

Minor whitespacing

--- js-library/app/js/jail_iframe/initializers/xdm.coffee
+++ js-library/app/js/jail_iframe/initializers/xdm.coffee
@@ -4,7 +4,6 @@
 Factlink.listenToWindowMessages null, Factlink.modal
 iframe.src = "#{FactlinkConfig.api}/factlink/intermediate"

-
 Factlink.remote = Factlink.createFrameProxyObject iframe.contentWindow, ['showFactlink', 'prepareNewFactlink' ]


Mark bootstrapped Posts as finalized.

Mark bootstrapped Posts as finalized.

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -47,6 +47,7 @@
         Todos.insert
           listId: list_id
           text: text
+          finalized: true
           createdAt: new Date(timestamp)

         timestamp += 1 # ensure unique timestamp.


queries refactoring

queries refactoring

--- server/query-api.coffee
+++ server/query-api.coffee
@@ -6,7 +6,7 @@

 STACK_SIMILARITY_LINES = 6

-crash_similarity_value = (crashreport) ->
+crash_similarity = (crashreport) ->

     stackframes_to_similarity_value = (stackframes) ->
         similarity = []
@@ -47,20 +47,15 @@
         return if err? then cb err, null else cb null, arr

 get_crashreport_by_id = (id, cb) ->
-        DB_CRASHREPORTS.find({"id":id}).run (err,arr) ->
-            return cb err,null if err?
-            return cb "ERROR: Crashreport not found with id:#{id}", null if arr.length == 0
-            if arr.length == 1
-                cb null, arr[0]
-            else
-                cb "ERROR: Multiple crashlogs found with id:#{id}", null
-
-get_crash_similarity_by_id = (id, cb) ->
     DB_CRASHREPORTS.find({"id":id}).one().run (err,crashreport) ->
         return cb err,null if err?
         return cb "ERROR: Crashreport not found with id:#{id}", null if not crashreport?
+        return cb null, crashreport

-        return cb null, crash_similarity_value(crashreport)
+get_crash_similarity_by_id = (id, cb) ->
+    get_crashreport_by_id id, (err, crashreport) ->
+        return if err? then cb err, null else cb null, crash_similarity(crashreport)
+

 init_query_api = (settings, app, db) ->
     DB_CRASHREPORTS = db.collection('crashreports')


adding BaseView::preRender()

adding BaseView::preRender()

--- shared/base/view.coffee
+++ shared/base/view.coffee
@@ -97,8 +97,8 @@
     "<#{@tagName}#{attrString}>#{html}</#{@tagName}>"

   render: =>
+    @_preRender()
     html = @getInnerHtml()
-    # Gross for now.
     @$el.html html
     @attachChildViews()
     @_postRender()
@@ -128,9 +128,18 @@
       @parseOptions(results)
       @render()

+  # Anything to do before rendering on the client or server.
+  # This is useful for i.e. accessing @model in the client after
+  # @hydrate() is called, but before @getTemplateData() is called.
+  _preRender: ->
+    @preRender()
+
   # Anything to do after rendering on the client.
   _postRender: ->
     @postRender()
+
+  # To be overridden by subclasses.
+  preRender: noop

   # To be overridden by subclasses.
   postRender: noop
@@ -169,6 +178,10 @@
     # with this view, and tries to load it from memory.
     @hydrate()

+    # Call preRender() so we can access things setup by @hydrate()
+    # (like @model) in i.e. @getTemplateData().
+    @_preRender()
+
     # We have to call postRender() so client-only things happen,
     # i.e. initialize slideshows, etc.
     @_postRender()


Fixed kdock notification

Fixed kdock notification

--- views/components/main/parts/kdock-panel.cjsx
+++ views/components/main/parts/kdock-panel.cjsx
@@ -153,7 +153,7 @@
             <CountdownLabel dockIndex={i}
                             completeTime={dock.completeTime}
                             isLSC={isLSC}
-                            notify={_.once @notify} />
+                            notify={if dock.completeTime > 0 then _.once(@notify) else null} />
           </div>
         </OverlayTrigger>
     }


put cursor at the end of first line in editors

(unless the interpreter tells us to put it some place other than at 0:0)

--- client/editor.coffee
+++ client/editor.coffee
@@ -206,7 +206,9 @@
   updateSize: -> cm.setSize $e.width(), $e.parent().height() - $e.position().top - 28
   open: (ee) ->
     originalValue = ee.text; cm.setValue ee.text; cm.focus()
-    cm.setCursor ee.currentRow, (ee.currentColumn || 0); cm.scrollIntoView null, $e.height() / 2
+    line = ee.currentRow; col = ee.currentColumn || 0
+    cm.setCursor line, if line || col then col else cm.getLine(0).length
+    cm.scrollIntoView null, $e.height() / 2
     for l in ee.lineAttributes?.stop then cm.setGutterMarker l, 'breakpoints', createBreakpointElement()
     return
   hasFocus: -> cm.hasFocus()


<space> picks selected `` completion

<space> picks selected `` completion

--- client/keymap.coffee
+++ client/keymap.coffee
@@ -241,9 +241,9 @@
       -> cm.showHint
         completeOnSingleClick: true
         extraKeys:
-          Backspace: (cm, m) -> m.close(); cm.execCommand 'delCharBefore'
-          Left:      (cm, m) -> m.close(); cm.execCommand 'goCharLeft'
-          Right:     (cm, m) -> m.pick()
+          Backspace: (cm, m) -> m.close(); cm.execCommand 'delCharBefore'; return
+          Left:      (cm, m) -> m.close(); cm.execCommand 'goCharLeft'; return
+          Right:     (cm, m) -> m.pick(); return
         hint: ->
           data = from: c, to: cm.getCursor(), list: bqc
           CodeMirror.on data, 'close', -> cm.setOption 'autoCloseBrackets', true; cm.setOption 'keyMap', 'dyalog'
@@ -274,7 +274,9 @@
     cm.replaceSelection Dyalog.getPrefixKey(), 'end'
     cm.showHint
       completeOnSingleClick: true
-      extraKeys: Right: (cm, m) -> m.pick()
+      extraKeys:
+        Right: (cm, m) -> m.pick(); return
+        Space: (cm, m) -> m.pick(); return
       hint: ->
         u = cm.getLine(c.line)[c.ch + 1...cm.getCursor().ch]
         a = []; for x in bqbqc when x.name[...u.length] == u then a.push x


Removed loadData properties from routes

Removed loadData properties from routes

--- client/routes.coffee
+++ client/routes.coffee
@@ -27,43 +27,36 @@
         path: '/userBadge/:id',
         key: "userBadge",
         component: UserBadgePage,
-        loadData: UserBadgePage.loadData,
     },
     {
         path: '/user/:id',
         key: "user",
         component: FullUserPage,
-        loadData: FullUserPage.loadData,
     },
     {
         path: '/table/:userList/:id',
         key: "table",
         component: TablePage
-        loadData: TablePage.loadData,
     },
     {
         path: '/dashboard',
         key: "dashboard",
         component: DashboardPage,
-        loadData: DashboardPage.loadData,
     },
     {
         path: '/solvedByWeek/:userList',
         key: "solvedByWeek",
         component: SolvedByWeekPage,
-        loadData: SolvedByWeekPage.loadData,
     },
     {
         path: '/material/:id',
         key: "material",
         component: MaterialPage,
-        loadData: MaterialPage.loadData,
     },
     {
         path: '/news',
         key: "news",
         component: FullNewsPage,
-        loadData: FullNewsPage.loadData,
     },
     {
         path: '/login',
@@ -80,7 +73,6 @@
         key: "/",
         exact: true,
         component: RootPage,
-        loadData: RootPage.loadData,
     },
     {
         component: NoMatch,


Reorganize for clarity.

Reorganize for clarity.

--- examples/dragdrop.coffee
+++ examples/dragdrop.coffee
@@ -11,12 +11,12 @@

 ImgView = Draggable View
   model: Img
+  template: -> ''
   # Cheap to completely mutate on change, no need to describe bindings, just
   # re-render entire view on any model changes.
   render: ->
     @el[k] = v for own k,v of @model
     @
-  template: -> ''

 Dropzone = Model
   defaults:
@@ -24,6 +24,7 @@

 DropzoneView = Droppable View
   model: Dropzone
+  template: -> '

'
   # Expensive to re-render entire view, so we'll declare bindings to describe
   # how to re-render on model changes.
   render: ->
@@ -53,8 +54,6 @@
   addDrop: (d) ->
     @model.drops.push d

-  template: -> '

'
-
 # Create some dropzones. Objects are used as initial state for declared models.
 # Rendering happens automatically.
 dropzone1 = DropzoneView


tests: Fix not passing broker address

tests: Fix not passing broker address

--- spec/participant.coffee
+++ spec/participant.coffee
@@ -22,7 +22,7 @@

   names = Object.keys g.commands
   names.forEach (name) ->
-    heterogenous.testParticipant g, name
+    heterogenous.testParticipant g, name, { broker: address }

 describe 'Participants', ->
   g =


wayward debug statement

wayward debug statement

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -12,8 +12,6 @@
 cacheEditor = (existingEditorView) ->
   session = atom.project.openSync()
   if existingEditorView?
-    existingEditorView.on 'textInput', ->
-      console.log "text inputting"
     existingEditorView.edit(session)
     existingEditorView.vimState = new VimState(existingEditorView)
     existingEditorView.bindKeys()


Move atom workspace creation into a beforeEach

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -1,11 +1,11 @@
 {EditorView} = require 'atom'
-atom.workspace ||= {}

 VimState = require '../lib/vim-state'

 originalKeymap = null

 beforeEach ->
+  atom.workspace ||= {}
   atom.workspace.vimState ||= {}
   atom.workspace.vimState.registers ||= {}
   atom.workspace.vimState.searchHistory ||= []


Add jasmine.buildMouseEvent

Add jasmine.buildMouseEvent

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -5,6 +5,21 @@
 require "../vendor/svg.export"

 util = require 'util'
+ObjectAssign = require 'object-assign'
+
+jasmine.buildMouseEvent = (type, properties...) ->
+  properties = ObjectAssign({bubbles: true, cancelable: true}, properties...)
+  properties.detail ?= 1
+  event = new MouseEvent(type, properties)
+  Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
+  if properties.target?
+    Object.defineProperty(event, 'target', get: -> properties.target)
+    Object.defineProperty(event, 'srcObject', get: -> properties.target)
+  if properties.pageX?
+    Object.defineProperty(event, 'pageX', get: -> properties.pageX)
+  if properties.pageY?
+    Object.defineProperty(event, 'pageY', get: -> properties.pageY)
+  event

 beforeEach ->
   jasmine.addMatchers


:art: Consume BubbleView in Linter

:art: Consume BubbleView in Linter

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -13,7 +13,7 @@
     @Subscriptions.add(@Editor.onDidSave @lint.bind(@, false))
     @Subscriptions.add(@Editor.onDidStopChanging @lint.bind(@, true)) if @Linter.LintOnFly
     @Subscriptions.add(@Editor.onDidChangeCursorPosition ({newBufferPosition}) =>
-      @Linter.View.updateBubble newBufferPosition
+      @Linter.Bubble.update newBufferPosition
     )

   lint: (OnChange) ->


Lower default volume

Lower default volume

--- resources/assets/coffee/_classes/osu-audio.coffee
+++ resources/assets/coffee/_classes/osu-audio.coffee
@@ -27,6 +27,7 @@
     $(document).on 'turbolinks:load', =>
       @player().addEventListener 'playing', @playing
       @player().addEventListener 'ended', @stop
+      @player().volume = 0.45

   play: (e) =>


Remove copyright notice from file in favor of README

--- lib/linter/flake8.coffee
+++ lib/linter/flake8.coffee
@@ -1,5 +1,3 @@
-# Copyright 2014 Daniel G. Taylor, MIT-licensed
-
 {Range, Point} = require 'atom'
 child_process = require 'child_process'
 fs = require 'fs'


fixes #2055: destructuring assignment with `new`

--- nodes.coffee
+++ nodes.coffee
@@ -759,13 +759,14 @@
   compileNode: (o) ->
     {to, from} = @range
     fromStr    = from and from.compile(o, LEVEL_PAREN) or '0'
-    compiled   = to and to.compile o, LEVEL_ACCESS
+    compiled   = to and to.compile o, LEVEL_PAREN
     if to and not (not @range.exclusive and +compiled is -1)
       toStr = ', ' + if @range.exclusive
         compiled
       else if SIMPLENUM.test compiled
-        (+compiled + 1).toString()
+        "#{+compiled + 1}"
       else
+        compiled = to.compile o, LEVEL_ACCESS
         "#{compiled} + 1 || 9e9"
     ".slice(#{ fromStr }#{ toStr or '' })"

@@ -1506,6 +1507,8 @@

   # Compile a unary **Op**.
   compileUnary: (o) ->
+    if o.level >= LEVEL_ACCESS
+      return (new Parens this).compile o
     parts = [op = @operator]
     plusMinus = op in ['+', '-']
     parts.push ' ' if op in ['new', 'typeof', 'delete'] or


fixing improperly-formatted-output of pattern matching within comprehension arguments.

--- nodes.coffee
+++ nodes.coffee
@@ -1247,7 +1247,7 @@
       svar:         scope.freeVariable()
       sourcePart:   "$svar = ${ @source.compile(o) };"
       if @pattern
-        namePart:   new AssignNode(@name, literal("$svar[$ivar]")).compile(merge o, {indent: @idt(1), top: true}) + "\n"
+        namePart:   new AssignNode(@name, literal("$svar[$ivar]")).compile(merge o, {indent: @idt(1), top: true}) + '\n'
       else
         namePart:   "$name = $svar[$ivar]" if name
       unless @object
@@ -1266,7 +1266,7 @@
       body.unshift  literal "var $index = $ivar" if index
       body:         ClosureNode.wrap(body, true)
     else
-      varPart:      "${@idt(1)}$namePart;\n" if namePart
+      varPart:      if @pattern then namePart else "${@idt(1)}$namePart;\n"
     if @object
       forPart:      "$ivar in $svar) { if (${utility('hasProp')}.call($svar, $ivar)"
     body:           body.compile(merge(o, {indent: @idt(1), top: true}))


Fix Axes instantiation from Figure. Now using SCIWIZ.Axes()

--- coffee/figure.coffee
+++ coffee/figure.coffee
@@ -26,7 +26,7 @@
         axContainer.attr('id', axesID)
         axContainer.append(axesID)

-        ax = new Axes(axesID, prop)
+        ax = new SCIWIZ.Axes(axesID, prop)

         @axes.push(ax)
         return ax
@@ -44,6 +44,7 @@
         @objects = new Array()   # render objects
         @materials = new Array() # material dictionary

+        console.log('Axes constructor')
         # renderer
         @renderer = new THREE.WebGLRenderer()
         @renderer.setSize(@properties.width, @properties.height)


Fixes #2155 -- existential assignment to a closure

--- nodes.coffee
+++ nodes.coffee
@@ -1171,6 +1171,7 @@
     o.scope.shared  = del(o, 'sharedScope')
     o.indent        += TAB
     delete o.bare
+    delete o.isExistentialEquals
     params = []
     exprs  = []
     for name in @paramNames() # this step must be performed before the others