fix for weird homepage issue

fix for weird homepage issue

Former-commit-id: 61fd77e05eb06932e2fbdd52ed1120070539a5e5 [formerly 1e3a819c9875d23b91e411c62c8147940c395b2f] [formerly 210adac55ee5ee45c619dfd36bfb845dc8aa8f15 [formerly 9f230eddb1350b0b5022cc66624e68ea507e3ccd]] [formerly 734b52a24cee5f5ebade98bc366317356db9f5a9 [formerly 539cd7f165c48e3d7f26271999430ceb4493f420] [formerly dbe431e213d06dcad338092a8793badc12107c33 [formerly dbe431e213d06dcad338092a8793badc12107c33 [formerly e7c2b29c281c5825469382293312e96545ea2502]]]]
Former-commit-id: e3f4ca9da7ef0cd556392f8c78d2a54c435df43c [formerly b8110135937cb334a5a150bbe16fa162c1eb1b08] [formerly 5f471ccb83bd8dc42a540e8cd0a995e7202157e9 [formerly 28595f7d6ca72ba108439b2fc7b36467199dd0c7]]
Former-commit-id: 6c54c5915fae5fdfec31154ac4593419d428ff81 [formerly 5ab2e884259ce69ae7836b9e177f066f3408eefc]
Former-commit-id: 22b84b3c777bf67a69451566dea41a4836db003e
Former-commit-id: 5eb0f6b7e4871b44d20ce88de63b61ffaf72d5ba

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -534,11 +534,7 @@

                   # Draw each proposal summary
                   for proposal, proposal_idx in cluster.proposals
-                    ProposalSummary
-                      key: proposal.key
-                      cluster: cluster.name
-                      columns: columns
-                      addContributors: index == 0 && proposal_idx == 0 && !browser.is_mobile
+                    @drawProposalSummary proposal, columns, index == 0 && proposal_idx == 0 && !browser.is_mobile

             # Cluster description
             if description
@@ -560,23 +556,18 @@

-# Used by the LearnDecideShare homepage
-window.ProposalSummary = ReactiveComponent
-  displayName: 'ProposalSummary'
-
-  render : ->
+  drawProposalSummary : (proposal, columns, add_contributors) ->
     subdomain = fetch('/subdomain')

-    proposal = @data()
     your_opinion = fetch(proposal.your_opinion)
-
-    hover_class = if @local.hovering_on == proposal.id then 'hovering' else ''
-
+
+
+    hovering_on = @local.hovering_on == proposal.id
     link_hover_color = focus_blue
-    cell_border = "1px solid #{if @local.hovering_on then link_hover_color else 'rgb(191, 192, 194)'}"
+    cell_border = "1px solid #{if hovering_on then link_hover_color else 'rgb(191, 192, 194)'}"

     TR
-      className: "proposal_summary " + hover_class
+      className: "proposal_summary"
       style:
         height: ''
         padding: '0 30px'
@@ -584,12 +575,12 @@
         borderLeft: cell_border
         minHeight: 60
         cursor: 'pointer'
-      onMouseEnter: => @local.hovering_on = true; save(@local)
-      onMouseLeave: => @local.hovering_on = false; save(@local)
+      onMouseEnter: => @local.hovering_on = proposal.id; save(@local)
+      onMouseLeave: => @local.hovering_on = null; save(@local)
       onClick: => loadPage "/#{proposal.slug}"

-      if @props.columns[0].heading
+      if columns[0].heading
         TD
           className: 'summary_name'
           style:
@@ -602,7 +593,7 @@
           A
             href: proposal_url(proposal)
             style:
-              color: "#{if @local.hovering_on then link_hover_color else ''}"
+              color: "#{if hovering_on then link_hover_color else ''}"
               borderBottom: '1px solid #b1afa7'

             if subdomain.name == 'livingvotersguide' && proposal.category
@@ -620,7 +611,7 @@
               'closed'

-      if @props.columns[1].heading
+      if columns[1].heading

         TD
           style:
@@ -637,7 +628,7 @@
           if !proposal.your_opinion || !your_opinion.published \
              || isNeutralOpinion(your_opinion.stance)
             style = {fontWeight: 400, color: 'rgb(158,158,158)', fontSize: 21}
-            if @local.hovering_on
+            if hovering_on
               style.color = 'black'
               style.fontWeight = 600
             SPAN style: style, '?'
@@ -657,7 +648,7 @@

               SPAN style: {color: 'rgb(166,204,70)', fontSize: 18, fontWeight: 600}, 'Yes'

-      if @props.columns[2].heading
+      if columns[2].heading

         TD
           className: 'summary_share'
@@ -704,7 +695,7 @@
                 DIV className:'point_nutshell', style: {fontSize: 15},
                   "#{proposal.top_point.nutshell[0..30]}..."

-      if @props.columns[2].heading && @props.addContributors
+      if columns[2].heading && add_contributors
         @drawContributors()


change bup_server to use direct ssh port, rather than tinc -- refactored and tested

--- salvus/bup_server.coffee
+++ salvus/bup_server.coffee
@@ -534,7 +534,7 @@
     constructor: (@project_id, @global_client) ->
         @database = @global_client.database

-    get_location_pref: (cb) =>
+    get_location_pref: (cb) =>   # db(err, server_id)
         @database.select_one
             table   : "projects"
             columns : ["bup_location"]
@@ -782,47 +782,102 @@
                     dbg("project not running anywhere, so start it somewhere")
                     @start(opts)

-
-    save: (opts) =>
-        opts = defaults opts,
-            cb : undefined
-        # if we just saved this project, return immediately -- note: THIS IS "CLIENT" side, but there is a similar guard on the actual compute node
-        if @_last_save? and misc.walltime() - @_last_save < MIN_SAVE_INTERVAL_S
-            opts.cb?(undefined)
-            return
-
-        # put this here -- we don't even want to *try* more frequently than MIN_SAVE_INTERVAL_S, in case of save bup repo being broken (?)
-        @_last_save = misc.walltime()
-
-        dbg = (m) => winston.debug("GlobalProject.save(#{@project_id}): #{m}")
-
-        need_to_save = false
-        project      = undefined
-        dc           = undefined   # data center where project currently running
-        targets      = []          # ['address:port', ...] of replication targets
-        server_id    = undefined   # id of the server on which the project is currently running
-        errors       = []
+    sync_topology: (opts) =>
+        opts = defaults opts,
+            cb        : required   # (err, {host:{dc:n, server_id:server_id, running:true or false}, targets:{'ip_address:port':target_id, ...}})
+        dbg = (m) => winston.debug("GlobalProject.sync_targets(#{opts.server_id}): #{m}")
+
+        resp = {host:{}, targets:{}}
         async.series([
             (cb) =>
                 dbg("figure out where/if project is running")
                 @get_host_where_running
                     cb : (err, s) =>
-                        server_id = s
+                        dbg("project server_id = #{s}")
+                        resp.host.server_id = s
                         if err
                             cb(err)
-                        else if not server_id?
-                            dbg("not running anywhere -- nothing to save")
+                        else if not resp.host.server_id?
+                            dbg("not running anywhere")
+                            @get_location_pref (err, s) =>
+                                resp.host.server_id = s
+                                resp.host.running = false
+                                cb(err)
+                        else
+                            resp.host.running = true
                             cb()
-                        else if @state?[server_id] == 'saving'
+            (cb) =>
+                dbg("get the data center where this project is currently running")
+                @global_client.get_data_center
+                    server_id : resp.host.server_id
+                    cb : (err, dc) =>
+                        resp.host.dc = dc
+                        dbg("sync host: #{misc.to_json(resp.host)}")
+                        cb(err)
+            (cb) =>
+                dbg("get the targets for replication")
+                @get_hosts
+                    cb : (err, t) =>
+                        v = (x for x in t when x != resp.host.server_id)
+                        dbg("sync_targets = #{misc.to_json(v)}")
+                        f = (target_id, cb) =>
+                            @global_client.get_external_ssh
+                                server_id : target_id
+                                dc        : resp.host.dc
+                                cb        : (err, addr) =>
+                                    if err
+                                        cb(err)
+                                    else
+                                        resp.targets[addr] = target_id
+                                        cb()
+                        async.map v, f, (err) =>
+                            dbg("sync_targets --> #{misc.to_json(resp.targets)}")
+                            cb(err)
+        ], (err) =>
+            if err
+                opts.cb(err)
+            else
+                opts.cb(undefined, resp)
+        )
+
+    save: (opts) =>
+        opts = defaults opts,
+            cb : undefined
+        dbg = (m) => winston.debug("GlobalProject.save(#{@project_id}): #{m}")
+        # if we just saved this project, return immediately -- note: THIS IS "CLIENT" side, but there is a similar guard on the actual compute node
+        if @_last_save? and misc.walltime() - @_last_save < MIN_SAVE_INTERVAL_S
+            dbg("we just saved this project recently")
+            opts.cb?(undefined)
+            return
+
+        # put this here -- we don't even want to *try* more frequently than MIN_SAVE_INTERVAL_S, in case of save bup repo being broken (?)
+        @_last_save = misc.walltime()
+
+
+        need_to_save = false
+        project      = undefined
+        server_id    = undefined
+        targets      = undefined
+        errors       = []
+        async.series([
+            (cb) =>
+                dbg("figure out where/if project is running")
+                @sync_topology
+                    cb : (err, resp) =>
+                        if err
+                            cb(err); return
+                        server_id = resp.host.server_id
+                        if @state?[server_id] == 'saving'
                             dbg("already saving -- nothing to do")
                             cb()
                         else
                             need_to_save = true
+                            targets = resp.targets
                             cb()
             (cb) =>
                 if not need_to_save
                     cb(); return
-                dbg("get the project itself")
+                dbg("get the project")
                 @project
                     server_id : server_id
                     cb        : (err, p) =>
@@ -830,39 +885,9 @@
             (cb) =>
                 if not need_to_save
                     cb(); return
-                dbg("get the data center where this project is currently running")
-                @get_data_center
-                    server_id : server_id
-                    cb : (err, _dc) =>
-                        dc = _dc
-                        cb(err)
-            (cb) =>
-                if not need_to_save
-                    cb(); return
-                dbg("get the targets for replication")
-                @get_hosts
-                    cb : (err, t) =>
-                        v = (x for x in t when x != server_id)
-                        dbg("sync_targets = #{misc.to_json(v)}")
-                        f = (target_id, cb) =>
-                            @get_external_ssh
-                                server_id : target_id
-                                dc        : dc
-                                cb        : (err, addr) =>
-                                    if err
-                                        cb(err)
-                                    else
-                                        targets.push(addr)
-                                        cb()
-                        async.map v, f, (err) =>
-                            dbg("sync_targets --> #{misc.to_json(targets)}")
-                            cb(err)
-            (cb) =>
-                if not need_to_save
-                    cb(); return
                 dbg("save the project and sync")
                 project.save
-                    targets : targets
+                    targets : misc.keys(targets)
                     cb      : (err, result) =>
                         r = result?.result
                         dbg("RESULT = #{misc.to_json(result)}")
@@ -875,7 +900,7 @@
                                     if x.host == '' # special case - the server hosting the project
                                         s = server_id
                                     else
-                                        s = @global_client.servers.by_host[x.host].server_id
+                                        s = targets[x.host]
                                     if not x.error?
                                         last_save[s] = r.timestamp*1000
                                     else
@@ -914,24 +939,14 @@
         errors    = []
         async.series([
             (cb) =>
-                dbg("figure out master if there is one")
-                @get_location_pref (err,s) =>
-                        server_id = s
-                        dbg("got master=#{server_id}")
-                        cb(err)
-            (cb) =>
-                dbg("lookup the servers that host this project")
-                @get_hosts
-                    cb : (err, hosts) =>
+                dbg("figure out where/if project is running")
+                @sync_topology
+                    cb : (err, resp) =>
                         if err
-                            cb(err)
-                        else
-                            targets = hosts
-                            dbg("servers=#{misc.to_json(targets)}")
-                            if not server_id? # no master (project never used), so choose one at random
-                                server_id = misc.random_choice(targets)
-                            targets = (@global_client.servers.by_id[x].host for x in targets when x!= server_id)
-                            cb()
+                            cb(err); return
+                        server_id = resp.host.server_id
+                        targets   = resp.targets
+                        cb()
             (cb) =>
                 dbg("get the project itself")
                 @project
@@ -942,7 +957,7 @@
                 dbg("do the sync")
                 tm = cassandra.now()
                 project.sync
-                    targets     : targets
+                    targets     : misc.keys(targets)
                     union       : opts.union
                     destructive : opts.destructive
                     snapshots   : opts.snapshots
@@ -962,7 +977,7 @@
                                 if x.host == '' # special case - the server hosting the project
                                     s = server_id
                                 else
-                                    s = @global_client.servers.by_host[x.host].server_id
+                                    s = targets[x.host]
                                 if not x.error?
                                     last_save[s] = tm
                                 else


Fix detached accounts

Fix detached accounts

--- app/src/models/wallet.coffee
+++ app/src/models/wallet.coffee
@@ -8,6 +8,10 @@
   @instance: undefined

   ## Global Balance management
+
+  constructor: ->
+    super
+    @_onCreateAccount = @_onCreateAccount.bind(this)

   retrieveAccountsBalances: () ->
     for account in @get('accounts')
@@ -32,6 +36,15 @@

   @initializeWallet: (callback) ->
     @instance = @findOrCreate(1, {id: 1}).save()
+    @instance._onCreateAccount()
+    ledger.database.contexts.main?.on 'insert:account', @instance._onCreateAccount
     callback?()

   @releaseWallet: () ->
+    ledger.database.contexts.main?.off 'insert:account', @instance._onCreateAccount
+
+
+  _onCreateAccount: ->
+    # Ensure every account is bound to the wallet
+    for account in Account.all()
+      account.set('wallet', this).save()


No fat arrow needed

No fat arrow needed

--- src/js/models/fact.coffee
+++ src/js/models/fact.coffee
@@ -31,7 +31,7 @@

   # This may look like some magic, but here we expose the Fact.blur/focus/click
   # methods
-  createEventHandlers: (events) =>
+  createEventHandlers: (events) ->
     @createEventHandler event_handle for event_handle in events

   createEventHandler: (event_handle) =>


1.修改模块上传时 包名 如 com.xxxx.xxx.xxx 时 压缩异常的问题

--- lib/package/package-list-view.coffee
+++ lib/package/package-list-view.coffee
@@ -128,8 +128,8 @@
     .then (result) =>
       throw new Error('login fail') unless result.result is 'true'
       console.log "upload"
-      zip(module.path, path.join os.tmpdir(), module.package.identifier).then (zipPath) =>
-        @uploadAttach zipPath + '.zip'
+      zip(module.path, os.tmpdir() + module.package.identifier + '.zip').then (zipPath) =>
+        @uploadAttach zipPath
     .then (result) =>
       console.log "validate"
       throw new Error('upload fail') unless result.result is 'success'


add startup method for splitting incident reports with case and death counts

--- collections/incidents.coffee
+++ collections/incidents.coffee
@@ -56,3 +56,28 @@
       Incidents.remove(id)
       #Meteor.call("removeOrphanedLocations", removed.userEventId, id)
       Meteor.call("updateUserEventLastModified", removed.userEventId)
+
+# Split incidents with both case and death counts into separate incidents
+if Meteor.isServer
+  Meteor.startup ->
+    incidents = Incidents.find({$and: [{cases: {$exists: true}}, {deaths: {$exists: true}}]}).fetch()
+    for incident in incidents
+      if incident.cases.length and incident.deaths.length
+        newIncident = {
+          url: incident.url
+          userEventId: incident.userEventId
+          species: incident.species
+          travelRelated: incident.travel
+          addedByUserId: incident.addedByUserId
+          addedByUserName: incident.addedByUserName
+          addedDate: incident.addedDate
+          deaths: incident.deaths
+        }
+
+        if incident.locations?.length
+          newIncident.locations = incident.locations
+        if incident.date
+          newIncident.date = incident.date
+
+        Incidents.update({_id: incident._id}, {$unset: {deaths: ""}})
+        Incidents.insert(newIncident)


Remove trailing whitespace

Remove trailing whitespace

--- services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee
+++ services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee
@@ -4,16 +4,16 @@
 sinon = require 'sinon'
 modulePath = "../../../../app/js/Features/Subscription/SubscriptionUpdater"
 assert = require("chai").assert
-ObjectId = require('mongoose').Types.ObjectId
+ObjectId = require('mongoose').Types.ObjectId

 describe "SubscriptionUpdater", ->

 	beforeEach ->
-		@recurlySubscription =
+		@recurlySubscription =
 			uuid: "1238uoijdasjhd"
 			plan:
 				plan_code: "kjhsakjds"
-		@adminUser =
+		@adminUser =
 			_id: @adminuser_id = "5208dd34438843e2db000007"
 		@otherUserId = "5208dd34438842e2db000005"
 		@allUserIds = ["13213", "dsadas", "djsaiud89"]
@@ -36,18 +36,18 @@
 		@updateStub = sinon.stub().callsArgWith(2, null)
 		@findAndModifyStub = sinon.stub().callsArgWith(2, null, @subscription)
 		@SubscriptionModel = class
-			constructor: (opts)->
+			constructor: (opts)->
 				subscription.admin_id = opts.admin_id
 				return subscription
 			@remove: sinon.stub().yields()
 		@SubscriptionModel.update = @updateStub
 		@SubscriptionModel.findAndModify = @findAndModifyStub

-		@SubscriptionLocator =
+		@SubscriptionLocator =
 			getUsersSubscription: sinon.stub()
 			getGroupSubscriptionMemberOf:sinon.stub()
-
-		@Settings =
+
+		@Settings =
 			freeTrialPlanCode: "collaborator"
 			defaultPlanCode: "personal"
 			defaultFeatures: { "default": "features" }
@@ -99,7 +99,7 @@
 	describe "_updateSubscriptionFromRecurly", ->
 		beforeEach ->
 			@FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1)
-
+
 		it "should update the subscription with token etc when not expired", (done)->
 			@SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=>
 				@subscription.recurlySubscription_id.should.equal @recurlySubscription.uuid
@@ -158,9 +158,9 @@

 		it "should add the users id to the group as a set", (done)->
 			@SubscriptionUpdater.addUserToGroup @adminUser._id, @otherUserId, =>
-				searchOps =
+				searchOps =
 					admin_id: @adminUser._id
-				insertOperation =
+				insertOperation =
 					"$addToSet": {member_ids:@otherUserId}
 				@findAndModifyStub.calledWith(searchOps, insertOperation).should.equal true
 				done()
@@ -176,9 +176,9 @@

 		it "should pull the users id from the group", (done)->
 			@SubscriptionUpdater.removeUserFromGroup @adminUser._id, @otherUserId, =>
-				searchOps =
+				searchOps =
 					admin_id:@adminUser._id
-				removeOperation =
+				removeOperation =
 					"$pull": {member_ids:@otherUserId}
 				@updateStub.calledWith(searchOps, removeOperation).should.equal true
 				done()
@@ -199,22 +199,22 @@
 			@SubscriptionLocator.getSubscription = sinon.stub().yields(null, @subscription)
 			@FeaturesUpdater.refreshFeatures = sinon.stub().yields()
 			@SubscriptionUpdater.deleteSubscription @subscription_id, done
-
+
 		it "should look up the subscription", ->
 			@SubscriptionLocator.getSubscription
 				.calledWith(@subscription_id)
 				.should.equal true
-
+
 		it "should remove the subscription", ->
 			@SubscriptionModel.remove
 				.calledWith({_id: ObjectId(@subscription_id)})
 				.should.equal true
-
+
 		it "should downgrade the admin_id", ->
 			@FeaturesUpdater.refreshFeatures
 				.calledWith(@subscription.admin_id)
 				.should.equal true
-
+
 		it "should downgrade all of the members", ->
 			for user_id in @subscription.member_ids
 				@FeaturesUpdater.refreshFeatures


Natural: slightly expand status question form

--- lib/capabilities/ControlBrowserCapability.coffee
+++ lib/capabilities/ControlBrowserCapability.coffee
@@ -36,7 +36,7 @@
       forms: [
         '(what is |describe|report )?(the )?(status|state) of( the)? <control>'
         '(what is |describe|report )?(the )?<control> (status|state)'
-        '(is|are)( the)? <control> (on|off|open|closed|locked|unlocked)']
+        '(is|are)( the)? <control> (turned |switched |shut )?(on|off|open|closed|locked|unlocked)']
       resolve: (cap, {control}) ->
         control = cap.resolveControlName(control)
         if control? then {control: control} else null


cleanup

cleanup

--- apps/home/client/view.coffee
+++ apps/home/client/view.coffee
@@ -50,7 +50,7 @@
       @show()
       @animateSplashImages()
       _.defer =>
-        @initializePopLockit() if @width > @minSupportedWidth
+        @initializePopLockit() if @browserWidth > @minSupportedWidth
         @newAnimationFrame()
     , 400

@@ -74,7 +74,7 @@
     @browserHeight = @$window.height()
     @browserWidth = @$window.width()

-    return @$content?.popLockIt 'destroy' if @width < @minSupportedWidth
+    return @$content?.popLockIt 'destroy' if @browserWidth < @minSupportedWidth

     @sizeSections()
     @documentHeight = @$document.height()


rename word_count to words

rename word_count to words

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -30,8 +30,8 @@

   update_question = (data) ->
     entry = random_question(data)
-    update_keyHandler()
-    word_count = entry.name.split(" ").length
+    update_keyHandler()
+    words = entry.name.split(" ").length
     size = entry.name.length
     $("dd#hint_stat").empty().append("This word is " + size + " letters long.")
     $("dd#hint_define").empty().append(entry['hint'])


Removed logging

Removed logging

It works now!

--- scripts/awesome.team.coffee
+++ scripts/awesome.team.coffee
@@ -207,9 +207,6 @@
           msg.send achievement.description

   robot.respond /testing/i, (msg) ->
-    console.log("Room's Team ID: " + robot.brain.data._private.team[msg.message.room]._id)
-    console.log(robot.brain.data._private.team)
-    console.log(msg.message.room)
     buttonName = "mood_" + robot.brain.data._private.team[msg.message.room]._id
     robot.emit 'slack.attachment',
       message: msg.message


fix bug in pretty uuid generation

fix bug in pretty uuid generation

--- scripts/gh-book/uuid.coffee
+++ scripts/gh-book/uuid.coffee
@@ -8,8 +8,8 @@
     return uuid() if not seed

     prefix = seed.toLowerCase()
-      .replace(' ', '-')
-      .replace(/[^a-z0-9\-]/, '') # prevent any weird characters from slipping through
+      .replace(/[ ]+/g, '-')
+      .replace(/[^a-z0-9\-]/g, '') # prevent any weird characters from slipping through

     return "#{prefix}-#{uuid()}" # add the random uuid on the back for uniqueness


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/expirable.coffee
+++ test/expirable.coffee
@@ -27,6 +27,6 @@
   5.times ->
     obj.update()

-  equals obj.I.alpha, (1 - (obj.I.age - 1 / obj.I.duration))
+  equals obj.I.alpha, (1 - (obj.I.age / obj.I.duration))

 module()


Test for EDDSA keygen

Test for EDDSA keygen

--- test/files/eddsa.iced
+++ test/files/eddsa.iced
@@ -1,4 +1,5 @@
-{unbox,KeyManager,armor} = require '../../'
+{unbox,KeyManager,armor,ecc} = require '../../'
+{burn} = require '../../lib/openpgp/burner'

 ## Keys and sigs in this file generated with GPG v2.1.6

@@ -74,4 +75,47 @@
     T.equal literals[0].toString(), msgs[i], "message #{i} was correct"
     T.assert literals[0].get_data_signer()?, "message #{i} was signed"
   cb()
-
+
+#------------------------
+
+exports.generate_key_and_sign = (T, cb) ->
+  params = { userid: "Mr Robot", ecc: true, primary: { algo: ecc.EDDSA }, subkeys: [] }
+  await KeyManager.generate params, defer err, kb, warnings
+  T.no_error err, "no errors"
+  msg = "Chancellor on brink of second bailout for banks"
+  params = { msg: msg, sign_with: kb }
+  await burn params, defer err, payload, warnings
+  T.no_error err, "no errors"
+  await unbox { armored: payload, keyfetch: kb }, defer err, literals, warnings
+  T.no_error err, "no errors"
+  T.equal literals[0].toString(), msg, "message was correct"
+  T.assert literals[0].get_data_signer()?, "message was signed"
+  cb()
+
+#------------------------
+
+exports.sign_and_exchange = (T, cb) ->
+  params = { userid: "Ms Alice", ecc: true, primary: { algo: ecc.EDDSA }, subkeys: [] }
+  await KeyManager.generate params, defer err, alice, warnings
+  T.no_error err, "no errors"
+  msg = "LOVE-LETTER-FOR-YOU.txt.vbs"
+  params = { msg: msg, sign_with: alice }
+  await burn params, defer err, signed_mail, warnings
+  T.no_error err, "no errors"
+
+  await unbox { armored: signed_mail, keyfetch: alice }, defer err, literals, warnings
+
+  T.no_error err, "no errors"
+
+  # Export public key.
+  await alice.sign {}, defer err
+  await alice.export_public {}, defer err, alice_pub
+  T.no_error err, "no errors"
+
+  # Import to separate KeyManager and try to unbox the message.
+  await KeyManager.import_from_armored_pgp { armored: alice_pub }, defer err, bobs_km
+  await unbox { armored: signed_mail, keyfetch: bobs_km }, defer err, literals, warnings
+  T.no_error err, "no errors"
+  T.equal literals[0].toString(), msg, "message was correct"
+  T.assert literals[0].get_data_signer()?, "message was signed"
+  cb()


Fix deprecated call

Fix deprecated call

Accessing PaneView via $::view() is deprecated. Use the raw DOM node or underlying model object instead raised by deprecation-cop

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -54,7 +54,7 @@
       else
         # The editor this preview was created for has been closed so close
         # this preview since a preview cannot be rendered without an editor
-        @parents('.pane').view()?.destroyItem(this)
+        atom.workspace?.paneForItem(this)?.destroyItem(this)

     if atom.workspace?
       resolve()


Fixed bugs in dropbox.file_put

Fixed bugs in dropbox.file_put

--- routes/api.coffee
+++ routes/api.coffee
@@ -133,8 +133,8 @@
     files_put: (filename, content, type) ->
       authUser = DROPBOX_FALLBACK_USER
       redis.hgetall "dropbox:#{authUser}", (err, data) ->
-        return next err if err
-        return res.json 'autentication needed' if not data?
+        return console.error  err if err
+        return console.error 'autentication needed' if not data?
         auth_dropbox = new Dropbox(
           DROPBOX_APP_KEY
           DROPBOX_APP_SECRET


updating sound script

updating sound script

--- scripts/random_noise.coffee
+++ scripts/random_noise.coffee
@@ -9,4 +9,4 @@
 module.exports = (robot) ->

   robot.hear /nicki|gruffalo|yorganic|minaj|muffin/ig, (msg) ->
-    msg.send "/play #{sounds[parseInt(sounds.length*Math.random())]}"
+    msg.sound sounds[parseInt(sounds.length*Math.random())]


Prefer Flash audio

Prefer Flash audio

--- scripts/soundManager.coffee
+++ scripts/soundManager.coffee
@@ -5,7 +5,6 @@

 window.soundManager = new SoundManager()
 window.soundManager.url = 'scripts/lib/soundmanager/swf'
-window.soundManager.preferFlash = false
 window.soundManager.html5PollingInterval = 50
 window.soundManager.beginDelayedInit()


Add a proper response

Add a proper response

--- scripts/xcode-server.coffee
+++ scripts/xcode-server.coffee
@@ -29,3 +29,7 @@
       robot.messageRoom '#publishmobileautomati', 'Built ' + app + ' ' + version + '-' + number + ' successfully. Currently available on Crashlytics for download.'
     else
       robot.messageRoom '#publishmobileautomati', 'BUILD FAILED: ' + app + ' with error ' + error
+
+    res.writeHead 204, { 'Content-Length': 0}
+
+    res.end()


Sim: Fix mirror move crash for real.

Sim: Fix mirror move crash for real.

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -693,13 +693,12 @@
 makeIdentifyMove("Miracle Eye", ["Psychic"])

 extendMove 'Mirror Move', ->
-  @hit = (battle, user, targets) ->
-    target = targets[0]
+  @hit = (battle, user, target) ->
     move = target.lastMove
     if !move? || !move.hasFlag("mirror")
       @fail(battle)
       return false
-    battle.executeMove(move, user, targets)
+    battle.executeMove(move, user, battle.getTargets(move, user))

 makeWeatherRecoveryMove 'Moonlight'
 makeWeatherRecoveryMove 'Morning Sun'


Little change.

Little change.

--- src/private/server.coffee
+++ src/private/server.coffee
@@ -10,7 +10,7 @@
 app.use '/static/js', express.static 'public/js'

 # Parsear POST headers
-app.use bodyParser.urlencoded({extended: true})
+app.use bodyParser.urlencoded({extended: false})

 app.get '/', (req, res) ->
     res.sendFile 'index.html', {root: path.join(__dirname, "../public")}


Doesn't seem to worth the extra function

--- resources/assets/coffee/_classes/blackout.coffee
+++ resources/assets/coffee/_classes/blackout.coffee
@@ -16,11 +16,7 @@
 # along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###
 class @Blackout
-  @_el: document.getElementsByClassName 'js-blackout'
-
-
-  @el: =>
-    @_el[0]
+  @el: document.getElementsByClassName 'js-blackout'

   @hide: =>
@@ -32,4 +28,4 @@

   @toggle: (state) =>
-    Fade.toggle(@el(), state) if @el()?
+    Fade.toggle(@el[0], state) if @el[0]?


payments: flesh out the payment method model

--- workers/social/lib/social/models/payment/paymentmethod.coffee
+++ workers/social/lib/social/models/payment/paymentmethod.coffee
@@ -1,9 +1,86 @@
-{Module} = require 'jraphical'
+{Module, Relationship} = require 'jraphical'

 module.exports = class JPaymentPaymentMethod extends Module
+
+  {secure} = require 'bongo'
+  {extend} = require 'underscore'
+  createId = require 'hat'
+
+  recurly = require 'koding-payment'

   @share()

   @set
-    schema      :
-      recurlyId : String
+    sharedMethods :
+      static      : [
+        'create'
+      ]
+      instance    : [
+        'associatePaymentData'
+      ]
+    schema        :
+      recurlyId   : String
+      description : String
+
+  @createPaymentMethod = secure (client, data, callback) ->
+    recurlyId = createId()
+    paymentMethod = new this { recurlyId, description: data.description }
+    paymentMethod.save (err) ->
+      return callback err  if err
+
+      paymentMethod.associatePaymentData client, data, (err) ->
+        return callback err  if err
+        callback null, paymentMethod
+
+  @create = secure (client, formData, callback) ->
+    {delegate} = client.connection
+
+    @createPaymentMethod client, formData, (err, paymentMethod) ->
+      return callback err  if err
+
+      delegate.addPaymentMethod paymentMethod, (err)->
+        callback err
+
+  associatePaymentData: secure (client, formData, callback) ->
+    JSession = require '../session'
+
+    { delegate } = client.connection
+
+    delegate.fetchUser (err, user) =>
+      return callback err  if err
+      { email, username } = user
+      { firstName, lastName } = delegate.profile
+
+      delegate.hasTarget this, 'paymentMethod', (err, hasTarget) =>
+        return callback err  if err
+        return callback message: 'Access denied!'  unless hasTarget
+
+        JSession.one clientId: client.sessionToken, (err, session) =>
+          return callback err  if err
+
+          ipAddress = session.clientIPAddress or '(Unknown IP address)'
+
+          accountData = { ipAddress, username, email, firstName, lastName }
+
+          recurly.setAccountDetailsByAccountCode @recurlyId, accountData, (err, rAccountData) =>
+            return callback err  if err
+
+            recurly.setBillingByAccountCode @recurlyId, formData, callback
+
+  fetchAssociatedPaymentData: (callback) ->
+    recurly.fetchBillingByAccountCode @recurlyId, (err, billing) =>
+      return callback err  if err
+      callback null, { @recurlyId, @description, billing }
+
+  @updatePaymentMethodByAccountCode = secure (client, recurlyId, formData, callback) ->
+    if recurlyId
+      @one { recurlyId }, (err, paymentMethod) =>
+        return callback err  if err
+        if paymentMethod
+        then paymentMethod.associatePaymentData client, formData, callback
+        else @create client, formData, callback
+    else
+      @create client, formData, callback
+
+
+


:bug: Fix deprecated call to `getPane`

:bug: Fix deprecated call to `getPane`

--- lib/mixins/view-management.coffee
+++ lib/mixins/view-management.coffee
@@ -64,7 +64,7 @@
       MinimapView ||= require '../minimap-view'

       editorId = editorView.editor.id
-      paneView = editorView.getPane()
+      paneView = editorView.getPaneView()

       view = new MinimapView(editorView)


Added package API functions

Added package API functions

--- app/api/package.coffee
+++ app/api/package.coffee
@@ -13,6 +13,52 @@
   # Keep a reference of @ in case we need it later in nested functions
   self = @

-  # TODO - package server functions
+  @create = (package) -> # TODO
+    url = ':host/api/packages' # TODO: check on this
+
+    # HTTP call details here
+    config =
+      method:   'PUT'
+      url:      _.template(url)(host: ApiConfig.host)
+      data:     JSON.stringify _.extend({}, package) # TODO
+      headers:
+        'Content-Type': 'application/json'
+        'X-Tranzit-Auth': TranzitAuthSession.user?.token
+
+    # Send request and generate a promise
+    return $http(config).then (data) ->
+      return data.data.result
+
+  @update = (package) -> # TODO
+    url = ':host/api/packages' # TODO: check on this
+
+    # HTTP call details here
+    config =
+      method:   'PUT'
+      url:      _.template(url)(host: ApiConfig.host)
+      data:     JSON.stringify _.extend({}, package) # TODO
+      headers:
+        'Content-Type': 'application/json'
+        'X-Tranzit-Auth': TranzitAuthSession.user?.token
+
+    # Send request and generate a promise
+    return $http(config).then (data) ->
+      return data.data.result
+
+  @delete = (package) -> # TODO
+    url = ':host/api/packages' # TODO: check on this
+
+    # HTTP call details here
+    config =
+      method:   'PUT'
+      url:      _.template(url)(host: ApiConfig.host)
+      data:     JSON.stringify _.extend({}, package) # TODO
+      headers:
+        'Content-Type': 'application/json'
+        'X-Tranzit-Auth': TranzitAuthSession.user?.token
+
+    # Send request and generate a promise
+    return $http(config).then (data) ->
+      return data.data.result

   return @


view past moves works, still no click handler

seems much faster on mobile now :)  we're updating the board dom
manually with jQuery, keeping our own record of the current rendered
board state so we can only update stones that have changed.

--- app/coffee/game.coffee
+++ app/coffee/game.coffee
@@ -260,8 +260,8 @@
     React.createElement NonReactBoard

   componentDidMount: ->
-    console.log 'new board mounted'
     @renderGrid()
+    @initCurrentStones()
     @renderStones()
     return

@@ -295,18 +295,43 @@

     $('#non-react-board').append $goban

+  initCurrentStones: ->
+    # we'll keep a record of what the current board looks like so that we don't
+    # have to use expensive DOM queries
+    {size} = @props
+    @currentStones = (('none' for i in [0...size]) for j in [0...size])
+
+  hasStoneChanged: (x, y, color) ->
+    @currentStones[y][x] == @currentStonesCode(color)
+
+  saveCurrentStone: (x, y, color) ->
+    @currentStones[y][x] = @currentStonesCode(color)
+
+  currentStonesCode: (color) ->
+    # we actually save some extra information besides just color in the
+    # current stones map; this function provides a string encoding that
+    # information.
+    if color is 'empty'
+      "#{color} #{@props.hoverColor}"
+    else
+      color
+
   renderStones: ->
     for rowArray, row in @props.boardState
       for color, col in rowArray
-        @setPointColor ($pointAt col, row), color
+        @setPointColor col, row, color
     @setLastPlayed()
     return

-  setPointColor: ($td, color) ->
+  setPointColor: (x, y, color) ->
+    return if @hasStoneChanged(x, y, color)
+    {hoverColor} = @props
+    $td = $pointAt x, y
     hoverClass = if @props.hoverColor \
-                 then "placement #{@props.hoverColor}" \
+                 then "placement #{hoverColor}" \
                  else ""

+    $('.placement', $td).remove()
     $('.stone', $td).remove()
     $('.territory', $td).remove()
     [stoneclass, territoryclass] = switch color
@@ -326,6 +351,8 @@
       territoryElement = document.createElement 'div'
       territoryElement.className = territoryclass
       $td.append territoryElement
+
+    @saveCurrentStone(x, y, color)

   setLastPlayed: ->
     $('.goban .last-played').remove()


resume button working

resume button working

--- app/coffee/game.coffee
+++ app/coffee/game.coffee
@@ -115,8 +115,15 @@
   scoredSgfObject = sgfObjectWithDeadStones sgfObject, boardState
   setResponseSgf smartgame.generate(scoredSgfObject)
   $('.confirm_button').prop('disabled', true)
+
   $('.pass_button').click ->
     newSgfObject = sgfObjectWithMoveAdded sgfObject
+    newSgf = smartgame.generate newSgfObject
+    setResponseSgf newSgf
+    $('#main_form').get(0).submit()
+
+  $('.resume_button').click ->
+    newSgfObject = sgfObjectWithResumeAdded sgfObject
     newSgf = smartgame.generate newSgfObject
     setResponseSgf newSgf
     $('#main_form').get(0).submit()
@@ -648,6 +655,13 @@

   return sgfObjectCopy

+sgfObjectWithResumeAdded = (sgfObject) ->
+  newNode = {TCRESUME: ''}
+  sgfObjectCopy = cloneSgfObject sgfObject
+  nodes = sgfObjectCopy.gameTrees[0].nodes
+  nodes.push newNode
+  return sgfObjectCopy
+
 # markStonesAround and helpers

 getNewDeadForToggledPoint = (state, x, y) ->


Fix isup tests

Fix isup tests

--- tests/isupTest.coffee
+++ tests/isupTest.coffee
@@ -1,6 +1,8 @@
 log = new ObjectLogger("isupTests", "debug")

 describe "isup", ->
+
+  stubCreated = null

   if Meteor.isServer
     Meteor.methods({
@@ -9,45 +11,68 @@
         return true
       "restoreStub": ->
         stubs.restore("isup.findOne")
+        return true
     })

   if Meteor.isClient
     beforeEach ->
-
+      stubCreated = false
       @url = window.location.protocol + "//" + window.location.host + "/isup"
       log.debug("url:", @url)

+    afterEach (done)->
+      try
+        log.enter 'afterEach'
+        if stubCreated isnt true
+          done()
+          return
+        log.debug "Calling restoreStub"
+        Meteor.call "restoreStub", (error, restored)=>
+          done(error) if error?
+          expect(restored).to.be.true
+          done()
+      catch ex
+        done(ex)
+      finally
+        log.return()

     it "/isup route", (done)->

       HTTP.get @url, (err, httpResponse)->
-        err = null
-        log.debug("httpResponse", httpResponse)
+        if err?
+          done(err)
+          return
         try
+          log.debug("httpResponse", httpResponse)
+          expect(httpResponse.statusCode).to.equal(200)
           expect(httpResponse.content).to.equal("yes")
-          expect(httpResponse.statusCode).to.equal(200)
+          done()
         catch ex
-          err = ex
+          done(ex)

-        done(err)
+

     it "/isup route - 404 not found", (done)->

       Meteor.call "createStub", (error, created)=>
+        if error?
+          done(error)
+          return
+        stubCreated = true
+
         expect(created).to.be.true
         log.debug("stubCreated")

         HTTP.get @url, (err, httpResponse)->
-          err = null
           try
             log.debug("httpResponse", httpResponse)
+            expect(err).to.be.ok
+            # We ignore the error afterwards, since we expect a 404 error
+            err = null

+            expect(httpResponse.statusCode).to.equal(404)
             expect(httpResponse.content).to.match(/404/)
-            expect(httpResponse.statusCode).to.equal(404)
+            done()
           catch ex
-            err = ex
+            done(ex)

-          Meteor.call("restoreStub")
-
-          done(err)
-


Drop MAX_DEPTH check from quadtree

Drop MAX_DEPTH check from quadtree

I don't recall the exact reasoning, but this works fine at arbitrary
granularity

--- app/js/quadtree.coffee
+++ app/js/quadtree.coffee
@@ -1,6 +1,5 @@
 window.QuadTree = class QuadTree
   MAX_OBJECTS: 4
-  MAX_DEPTH: 5

   nodes: null # if present: northeast, northwest, southwest, southeast

@@ -14,7 +13,7 @@
     [[left, bottom], [right, top]] = boundingBox
     return if right < @left or left >= @right or top < @bottom or bottom >= @top

-    if (@objects.length < @MAX_OBJECTS and not @nodes) or @depth is @MAX_DEPTH
+    if (@objects.length <= @MAX_OBJECTS and not @nodes)
       @objects.push [object, boundingBox]
       return


modify teacher access campaign to only CS1 (#5464)

--- app/models/User.coffee
+++ app/models/User.coffee
@@ -576,7 +576,7 @@
   useGoogleAnalytics: -> not ((features?.china ? false) or (features?.chinaInfra ? false))
   # features.china is set globally for our China server
   showChinaVideo: -> (features?.china ? false) or (features?.chinaInfra ? false)
-  canAccessCampaignFreelyFromChina: (campaignID) -> campaignID == "55b29efd1cd6abe8ce07db0d" or campaignID == "5789236960deed1f00ec2ab8" or campaignID == "578913f2c8871ac2326fa3e4"
+  canAccessCampaignFreelyFromChina: (campaignID) -> campaignID == "55b29efd1cd6abe8ce07db0d" # teacher can only access CS1 freely in China
   isCreatedByTarena: -> @get('clientCreator') == "5c80a2a0d78b69002448f545"   #ClientID of Tarena2 on koudashijie.com
   showForumLink: -> not (features?.china ? false)
   showGithubLink: -> not (features?.china ? false)


guard in case there is no user

guard in case there is no user

--- client/directives/userForm.coffee
+++ client/directives/userForm.coffee
@@ -27,7 +27,7 @@
       scope.userRoles = []
       scope.notUserRoles = []
       angular.forEach scope.roles, (role) ->
-        if scope.user.roles? and role._id in scope.user.roles
+        if scope.user?.roles? and role._id in scope.user.roles
           scope.userRoles.push role
         else
           scope.notUserRoles.push role


Fix issue #49

Fix issue #49

--- app/models/tool.coffee
+++ app/models/tool.coffee
@@ -32,6 +32,7 @@
     if @get('data_source').isExternal()
       @get('data_source').fetchData().done => @trigger 'render'
     else
+      @sourceTool().on 'destroy', => @destroy()
       @trigger 'render'

   generatePosition: ->


SA - FAQ page without login

SA - FAQ page without login

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -319,7 +319,6 @@
                   views:
                       'application_content_view':
                           templateUrl: 'views/candidature/pages/faq.html',
-                          controller: ($rootScope) -> $rootScope.loadInfos($rootScope)

         )


Added more quotes.

Added more quotes.

--- src/tucker.coffee
+++ src/tucker.coffee
@@ -48,7 +48,11 @@
   'Within your \'purview\'? Where do you think you are, some fucking regency costume drama? This is a government department, not some fucking Jane fucking Austen novel! Allow me to pop a jaunty little bonnet on your purview and ram it up your shitter with a lubricated horse cock.',
   'Fuckity bye!',
   'I\'m gonna come over there, I\'m gonna lock you in a fucking flotation tank and pump it full of sewage until you fucking drown.',
-  ''
+  'I\'ve got a to-do list here that\'s longer than a fucking Leonard Cohen song.',
+  'Do you know what\'s really fucking sad here is that I don\'t have the energy to pretend I already knew. Which is for the best, because I\'m gonna need all of my fucking energy to fucking rip all of your bodies to bits with my bare hands and sell off, yes, sell off your fucking flayed skin as a sleeping bag to a fucking normal person!',
+  'I wouldn\'t fucking piss on you if you were fucking allergic to piss, right?',
+  'Get over here. Now. Might be advisable to wear brown trousers and a shirt the colour of blood.',
+  'Enough! E-fucking-nough. You need to learn to shut your fucking cave. Right? Today, you have laid your first big fat egg of solid fuck. You took the data loss media strategy and you ate with a lump of E.coli. And then you sprayed it our of your arse at 300 mph.'
 ]

 module.exports = (robot) ->


Add debug output when sending fast 304 response

--- proxy.coffee
+++ proxy.coffee
@@ -82,6 +82,7 @@
           # proxying requests for cached files.
           if options.headers?['if-none-match'] && localDigestMatch = url.match(/etag=([^&]+[^&]?)/)
             if options.headers['if-none-match'] is decodeURIComponent(localDigestMatch[1])
+              debug "∟ Intercepted the request based on its etag param, sending a 304 response".green
               res.writeHead(304, {
                 server: 'vee'
               });


Let people trail off

Let people trail off

--- scripts/rc.coffee
+++ scripts/rc.coffee
@@ -8,7 +8,7 @@

 # Robot hooks
 module.exports = (robot) ->
-  robot.respond /(?:rc)\s+for\s([^\s]+)$/i, (msg) ->
+  robot.respond /(?:rc)\s+for\s([^\s]+)\s*$/i, (msg) ->
     who = msg.match[1].toLowerCase()
     new RCScore(who).fetch (err, info, self) =>
       {score: score, when: stamp} = info


activity: fix like interaction actor count link

--- client/Social/Activity/views/likecount.coffee
+++ client/Social/Activity/views/likecount.coffee
@@ -49,19 +49,19 @@
         name = KD.utils.getFullnameFromAccount account
         names.push "#{strong name}"

-      if actorsCount > 3
-        sep = ", "
-        andMore = "and #{actorsCount - 3} more."
-      else
-        sep = " and "
-        andMore = ""
-
       @updateTooltip names

   updateTooltip: (names) ->

     {actorsCount} = @getData().interactions.like
+
+    if actorsCount > 3
+      sep = ", "
+      andMore = "and #{actorsCount - 3} more."
+    else
+      sep = " and "
+      andMore = ""

     title =
       switch actorsCount


client: style change for upload file error

--- client/app/lib/commonviews/dnduploader.coffee
+++ client/app/lib/commonviews/dnduploader.coffee
@@ -59,14 +59,15 @@

     "

-    modal = new KDModalView
+    modal = new ContentModal
       title        : 'Upload File Error'
       width        : 500
+      cssClass     : 'content-modal'
+      content      : "
#{message}
"
       overlay      : yes
-      cssClass     : 'new-kdmodal'
-      content      : "
#{message}
"
       buttons      :
         Ok         :
+          title    : 'OK'
           style    : 'solid green medium'
           callback : -> modal.destroy()


Collaboration: Reformat start heartbeat method

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -539,7 +539,7 @@

     interval = 1000 * 15
     @sendPing() # send the first ping
-    @pingInterval =  kd.utils.repeat interval, @bound 'sendPing'
+    @pingInterval = kd.utils.repeat interval, @bound 'sendPing'

   sendPing: ->


client: add error code check to finalize collaboration

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -256,12 +256,12 @@
       kite.ping()
         .timeout(30000)
         .then =>
-          kite.reconnect()
           @listenKlientKite()
-        .catch =>
-          if @amIHost
-          then @handleCollaborationEndedForHost()
-          else @handleCollaborationEndedForParticipant()
+        .catch (err) =>
+          if err?.code is 1002
+            if @amIHost
+            then @handleCollaborationEndedForHost()
+            else @handleCollaborationEndedForParticipant()

   setWatchMap: ->


switching modes works once more

switching modes works once more

--- web-editor/app.coffee
+++ web-editor/app.coffee
@@ -12,6 +12,10 @@
 # - Show equivalent grays for colors
 #   - In order of ascending greyness ( in pairs, black and white on their own)
 #   - black,blue brown red darkgray purple orange lightblue midgray green pink cyan lightgray yellow lightgreen white
+# - save and restore ( as JSON) the "state" ( which colors are selected, etc.)
+# - bit limited, but remember changeable color for each entity and apply that color when painting on the macro
+#   - localStorage["a"] = JSON.stringify( object )
+#   - object = JSON.parse(localStorage["a"])

 # + The character set should be shown in 8 rows, 32 glyphs wide
 # + Character codes may be selected and the glyph is shown in the editor
@@ -447,8 +451,9 @@
     mode = MODE[ asset_mode][ color_mode]

     fill_out_data()
-
-    render_everything()
+    character_set.render()
+    editor.build()
+    macro.render()

   $('#upload_button').click () ->
     $('#upload_dialog').fadeIn 'fast'


Resolve conflict

Resolve conflict

--- views/index.cjsx
+++ views/index.cjsx
@@ -380,22 +380,12 @@
         <Label bsStyle="danger">{@state.warning}</Label>
       </div>
       <Tabs activeKey={@state.selectedKey} animation={false} onSelect={@handleSelectTab}>
-<<<<<<< 275b735ba593c9875c67e09222d985d8b38edcc0
-        <Tab eventKey={0} title={__ "Sortie"} ><AkashicLog indexKey={0} selectedKey={@state.selectedKey} data={@state.attackData} dataVersion={@state.dataVersion[0]} tableTab={attackTableTab} contentType={'attack'}/></Tab>
-        <Tab eventKey={1} title={__ "Expedition"} ><AkashicLog indexKey={1} selectedKey={@state.selectedKey} data={@state.missionData} dataVersion={@state.dataVersion[1]} tableTab={missionTableTab} contentType={'mission'}/></Tab>
-        <Tab eventKey={2} title={__ "Construction"} ><AkashicLog indexKey={2} selectedKey={@state.selectedKey} data={@state.createShipData} dataVersion={@state.dataVersion[3]} tableTab={createShipTableTab} contentType={'createShip'}/></Tab>
-        <Tab eventKey={3} title={__ "Development"} ><AkashicLog indexKey={3} selectedKey={@state.selectedKey} data={@state.createItemData} dataVersion={@state.dataVersion[2]} tableTab={createItemTableTab} contentType={'createItem'}/></Tab>
-        <Tab eventKey={4} title={__ "Resource"} ><AkashicResourceLog indexKey={4} selectedKey={@state.selectedKey} data={@state.resourceData} dataVersion={@state.dataVersion[4]} tableTab={resourceTableTab} mapShowFlag={@state.mapShowFlag} contentType={'resource'}/></Tab>
-        <Tab eventKey={5} title={__ "Others"} >
-=======
         <Tab eventKey={0} title={__ "Sortie"} ><AkashicLog indexKey={0} selectedKey={@state.selectedKey} tableTab={attackTableTab} contentType={CONST.typeList.attack}/></Tab>
         <Tab eventKey={1} title={__ "Expedition"} ><AkashicLog indexKey={1} selectedKey={@state.selectedKey} tableTab={missionTableTab} contentType={CONST.typeList.mission}/></Tab>
         <Tab eventKey={2} title={__ "Construction"} ><AkashicLog indexKey={2} selectedKey={@state.selectedKey} tableTab={createShipTableTab} contentType={CONST.typeList.createShip}/></Tab>
         <Tab eventKey={3} title={__ "Development"} ><AkashicLog indexKey={3} selectedKey={@state.selectedKey} tableTab={createItemTableTab} contentType={CONST.typeList.createItem}/></Tab>
-        <Tab eventKey={4} title={__ "Resource"} ><AkashicResourceLog indexKey={4} selectedKey={@state.selectedKey} tableTab={resourceTableTab} mapShowFlag={@state.mapShowFlag} contentType={'resource'}/></Tab>
-        <Tab eventKey={5} title={__ "Victory"} ><AkashicSenkaLog indexKey={5} selectedKey={@state.selectedKey} memberId={@state.memberId} tableTab={senkaTableTab}  personalShowFlag={@state.personalShowFlag} contentType={'senka'}/></Tab>
+        <Tab eventKey={4} title={__ "Resource"} ><AkashicResourceLog indexKey={4} selectedKey={@state.selectedKey} tableTab={resourceTableTab} mapShowFlag={@state.mapShowFlag} contentType={CONST.typeList.resource}/></Tab>
         <Tab eventKey={6} title={__ "Others"} >
->>>>>>> Use data-manager instead of build-in manager
           <AkashicAdvancedModule
             tableTab={
               'attack': attackTableTabEn


Reenable specs

Reenable specs

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -105,4 +105,4 @@
       failures.push "atom core" if coreSpecFailed

       grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0
-      done()
+      done(!coreSpecFailed and failedPackages.length == 0)


Simply deprecation output

Simply deprecation output

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -12,11 +12,18 @@
   packageSpecQueue = null

   logDeprecations = (label, {stderr}={}) ->
-    if process.env.JANKY_SHA1 and stderr?.indexOf('Calls to deprecated functions') isnt -1
-      grunt.log.error(label)
-      stderr = stderr.replace(/\[[^\]]+\] "/g, '')
-      stderr = stderr.replace(/source: .*$/g, '')
-      grunt.log.error(stderr)
+    return unless process.env.JANKY_SHA1
+    stderr ?= ''
+    deprecatedStart = stderr.indexOf('Calls to deprecated functions')
+    return if deprecatedStart.length is -1
+
+    grunt.log.error(label)
+    stderr = stderr.substring(deprecatedStart)
+    stderr = stderr.replace(/^\s*\[[^\]]+\]\s+/gm, '')
+    stderr = stderr.replace(/source: .*$/gm, '')
+    stderr = stderr.replace(/^"/gm, '')
+    stderr = stderr.replace(/",\s*$/gm, '')
+    grunt.log.error(stderr)

   getAppPath = ->
     contentsDir = grunt.config.get('atom.contentsDir')


Fixed wrapping issue

Fixed wrapping issue

--- app/assets/javascripts/blackmailr.js.coffee
+++ app/assets/javascripts/blackmailr.js.coffee
@@ -35,6 +35,7 @@
                     color: '#fff'
                     textAlign: 'center'
                     fontSize: 50
+                    whiteSpace: 'nowrap'
                   ).appendTo $t.data 'mask'
               $mask = $t.data 'mask'
               if s <= 0


Add explanation of UX pattern

Add explanation of UX pattern

--- examples/examples.cjsx
+++ examples/examples.cjsx
@@ -35,9 +35,10 @@
           The header on this site is a living example. When you scroll down, it
           slides out of view and slides back in when scrolling up.

-
See headroom.js for
-            the rational behind this UX pattern
-

+
Fixed headers are nice when you want persistent navigation but they
+          can also get in the way especially on mobile where screen space is scarce.
+          Using this component let's you have your persistent navigation while
+          preserving screen space when it's not needed.

Using React Headroom

It's very simple actually :)


Gallery of components not photos

Gallery of components not photos

--- examples/examples.cjsx
+++ examples/examples.cjsx
@@ -22,7 +22,7 @@

       {"""
         <ComponentGallery
-          className="photos"
+          className="example"
           margin=10
           widthHeightRatio=3/5
           targetWidth=250
@@ -60,9 +60,9 @@
       {'  '}{@state.margin}px

-
Photos

+
Components

       <ComponentGallery
-        className="photos"
+        className="example"
         margin={@state.margin}
         widthHeightRatio=3/5
         targetWidth={@state.targetWidth}>


Create WeakMap on element creation to support ‘context’ attribute change

--- src/styles-element.coffee
+++ src/styles-element.coffee
@@ -3,6 +3,7 @@
 class StylesElement extends HTMLElement
   createdCallback: ->
     @emitter = new Emitter
+    @styleElementClonesByOriginalElement = new WeakMap
     @context = @getAttribute('context') ? undefined

   attributeChangedCallback: (attrName, oldVal, newVal) ->
@@ -19,7 +20,6 @@

   attachedCallback: ->
     @subscriptions = new CompositeDisposable
-    @styleElementClonesByOriginalElement = new WeakMap
     @subscriptions.add atom.styles.observeStyleElements(@styleElementAdded.bind(this))
     @subscriptions.add atom.styles.onDidRemoveStyleElement(@styleElementRemoved.bind(this))
     @subscriptions.add atom.styles.onDidUpdateStyleElement(@styleElementUpdated.bind(this))


Fix syntax highlighting

Fix syntax highlighting

Don't break highlighting on multi-line spans (multi-line comments for
example)

--- app/assets/javascripts/dispatcher.js.coffee
+++ app/assets/javascripts/dispatcher.js.coffee
@@ -59,7 +59,7 @@

   initHighlight: ->
     $('.highlight pre code').each (i, e) ->
+      $(e).html($.map($(e).html().split("\n"), (line, i) ->
+        "" + line + ""
+      ).join("\n"))
       hljs.highlightBlock(e)
-      $(e).html($.map($(e).html().split("\n"), (line, i) ->
-        "
" + line + "
"
-      ).join("\n"))


Add comment to temporary bugfix.

Add comment to temporary bugfix.

--- app/assets/javascripts/followable.js.coffee
+++ app/assets/javascripts/followable.js.coffee
@@ -6,4 +6,5 @@

   update: (followable_id, button) ->
     $("#" + followable_id + " .js-follow").html(button)
+    # Temporary line. Waiting for issue resolution: https://github.com/consul/consul/issues/1736
     initialize_modules()


moar easy tests

moar easy tests

--- js/cs/test/spec/given_spec.coffee
+++ js/cs/test/spec/given_spec.coffee
@@ -1,15 +1,42 @@
-describe 'Setup (given)', ->
+describe 'Setup (static given)', ->
+# It's used to append events
+# does not lead to a conclusion
+# just appends to the events

-  sut = corals 0
+  sut = {}
+
+  beforeEach (done) ->
+    sut = corals 0
+    done()

   it 'value', ->
+
     sut.given 42
     .then().should.eql 42

+    sut.events.should.eql [42]
+
   it 'many values', ->
+
     sut.given 4, 42, 420
     .then().should.eql 420

+    sut.events.should.eql [4, 42, 420]
+
   it 'array', ->
+
     sut.given [42, true, 'that']
     .then().should.eql 'that'
+
+    sut.events.should.eql [42, true, 'that']
+
+  it 'many calls', ->
+
+    sut
+    .given 4, 42, 420
+    .given [42, true, 'that']
+    .then().should.eql 'that'
+
+    sut.events.should.eql [4, 42, 420, 42, true, 'that']
+
+describe 'Helpers (dynamic given)', ->


Adds better error handling to Express app

--- coffee/nasjonalturbase.api.coffee
+++ coffee/nasjonalturbase.api.coffee
@@ -6,8 +6,10 @@
 app     = express()
 turbase = require './turbase'

+debug = false
+
 # Logging
-app.use express.logger()
+# app.use express.logger()
 # Query params
 app.use express.bodyParser()
 # Error handling
@@ -23,12 +25,19 @@
     "nrk":
       "navn": "NRK"

-  req.eier = eiere[req.query.api_key]?.navn or res.end 'api_key parameter mangler eller er feil'
+  if typeof req.query.api_key is 'undefined' or typeof eiere[req.query.api_key] is 'undefined'
+    err = new Error('API Authentication Failed')
+    err.mesg = 'AuthenticationFailed'
+    err.code = 403
+    return next err
+
+  req.eier = eiere[req.query.api_key].navn

   data = req.params?.data or req.query?.data
   if data
     req.data = JSON.parse data if data
     req.data.eier = req.eier
+
   next()

 # Routing
@@ -63,8 +72,22 @@

 # Error handling
 app.use (err, req, res, next) ->
-  console.error err.stack
-  res.jsonp 500, {'err':err}
+  console.error err.stack if debug

-app.listen 4000
-console.log 'Nasjonal turbase running on port 4000'
+  code = err.code || 500
+  mesg = err.mesg || 'InternalServerError'
+
+  res.jsonp code, err: mesg
+
+if not module.parent
+  srv = app.listen 4000
+  srv.on 'close', ->
+    console.log 'closing server port...'
+    srv = app = null
+    return
+
+  console.log 'Nasjonal turbase running on port 4000'
+else
+  srv = null
+  module.exports = app
+


Add create list

Add create list

--- src/trello.coffee
+++ src/trello.coffee
@@ -100,6 +100,14 @@
       msg.reply "The following cards match your criteria" unless err
       for cards in data.cards
          msg.send " * #{cards.name} | #{cards.url}"
+
+createList = (msg, list_name, postion = top) ->
+  msg.reply "I'll get right on that!"
+  if position is "top" or position is "bottom" or position > 0
+    trello.post "/1/lists", {name: list_name, idBoard: board.id} (err, data) ->
+    msg.reply "I'm sorry I was unable to add that list. Please try again later." if err
+    msg.reply "That list has been created and be viewed here: #{data.url}" unless err
+  else msg.reply "I'm sorry I can't create a list in that position"

 moveCard = (msg, card_id, list_name) ->
@@ -186,6 +194,9 @@

   robot.respond /trello add member (\w+) (\w+)/i, (msg) ->
     addMember msg, msg.match[1], msg.match[2]
+
+  robot.respond /^trello create list ["“'‘]((.+|\n)+)["”'’] (.+)?$/i, (msg) ->
+    createList msg, msg.match[1], msg.match[3]

   robot.respond /trello help/i, (msg) ->
     msg.reply "Here are all the commands for me."


Tweak to view.

Tweak to view.

--- app/views/admin/base_view.coffee
+++ app/views/admin/base_view.coffee
@@ -1,6 +1,6 @@
-View = require 'views/kinds/RootView'
+RootView = require 'views/kinds/RootView'
 template = require 'templates/base'

-module.exports = class BaseView extends View
+module.exports = class BaseView extends RootView
   id: "base-view"
   template: template


Better logging to console

Better logging to console

--- app/scripts/Civilization/Game/Logger.coffee
+++ app/scripts/Civilization/Game/Logger.coffee
@@ -2,7 +2,7 @@
   history: []

   _log: (level, args) ->
-    console[level](Array::slice.call(args))
+    console[level].apply(console, Array::slice.call(args))

     @history.push
       level: level


Add caching to RSS routes

Add caching to RSS routes

--- apps/rss/routes.coffee
+++ apps/rss/routes.coffee
@@ -17,8 +17,8 @@
     per: 25

   Q.allSettled([
-    channel.fetch()
-    blocks.fetch()
+    channel.fetch(cache: true)
+    blocks.fetch(cache: true)
   ]).then ->
     res.set 'Content-Type', 'application/rss+xml'
     res.render 'channel',
@@ -34,10 +34,11 @@
     per: 25

   Promise.all([
-    author.fetch()
-    blocks.fetch()
+    author.fetch(cache: true)
+    blocks.fetch(cache: true)
   ])
     .then ->
+      res.set 'Content-Type', 'application/rss+xml'
       res.render 'user',
         author: author
         blocks: blocks.models
@@ -50,8 +51,9 @@
     per: 25

   Q.allSettled([
-    blocks.fetch()
+    blocks.fetch(cache: true)
   ]).then ->
+    res.set 'Content-Type', 'application/rss+xml'
     res.render 'explore',
       blocks: blocks.models
   .catch next
@@ -59,6 +61,7 @@

 @blogRSS = (req, res, next) ->
   posts.fetchAll().then (posts) ->
+    res.set 'Content-Type', 'application/rss+xml'
     res.render 'blog',
       posts: posts
   .catch next


adding user model and testing if user is author of article

--- apps/articles/client/show.coffee
+++ apps/articles/client/show.coffee
@@ -12,6 +12,8 @@
 Q = require 'q'
 imagesLoaded = require 'imagesloaded'
 embedVideo = require 'embed-video'
+CurrentUser = require '../../../models/current_user.coffee'
+

 module.exports.ArticleView = class ArticleView extends Backbone.View

@@ -23,6 +25,7 @@
     @breakCaptions()
     if $(@article.get 'lead_paragraph').text().trim() isnt ''
       @$('#articles-lead-paragraph').show()
+    @checkEditable()

   renderSlideshow: =>
     @carouselView = new CarouselView
@@ -125,6 +128,11 @@
       # Remove loading state
       $list.parent().removeClass('is-loading')

+  checkEditable: ->
+    @user = CurrentUser.orNull()
+    if @user?.id is @article.get('author_id')
+
+
 module.exports.init = ->
   article = new Article sd.ARTICLE
   slideshowArtworks = new Articles sd.SLIDESHOW_ARTWORKS


saftey

saftey

--- apps/articles/test/routes.coffee
+++ apps/articles/test/routes.coffee
@@ -85,6 +85,10 @@
         @res.render.args[0][1].vertical.get('title').should.equal 'Foo Bar'
         done()

+    it 'requests less than 100 pages!', ->
+      routes.articles @req, @res, @next
+      Backbone.sync.args[1][2].data.limit.should.be.below 100
+
   describe '#vertical', ->

     it 'renders the vertical with its articles', ->


BF-201 recursion example

BF-201 recursion example

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -7,7 +7,7 @@
 PullQueue =

   # Constants
-  PULL_DOWNLOAD_LIMIT : 10
+  PULL_DOWNLOAD_LIMIT : 100

   queue : []
   pullLoadingCount : 0
@@ -46,6 +46,9 @@
   insert : (bucket, zoomStep, priority) ->

     queue = @queue
+
+    if priority > 100 and zoomStep < 3
+      @insert([bucket[0] >> 1, bucket[1] >> 1, bucket[2] >> 1], zoomStep+1, priority-100)

     # Buckets with negative priority are not loaded
     return unless priority >= 0


#306 Shift color of active node

#306 Shift color of active node

--- app/assets/javascripts/oxalis/geometries/tree.coffee
+++ app/assets/javascripts/oxalis/geometries/tree.coffee
@@ -210,8 +210,11 @@
       isActiveNode  = isActiveNode  || @model.skeletonTracing.getActiveNodeId() == id
       isBranchPoint = isBranchPoint || @model.skeletonTracing.isBranchPoint(id)

-      if not isActiveNode
+      if isActiveNode
+        color = @shiftHex(color, 1/4)
+      else
         color = @darkenHex(color)
+
       if isBranchPoint
         color = @invertHex(color)

@@ -257,8 +260,13 @@
     ColorConverter.setHSV(new THREE.Color(), hsvColor.h, hsvColor.s, hsvColor.v).getHex()

+  shiftHex : (hexColor, shiftValue) ->
+
+    hsvColor = ColorConverter.getHSV(new THREE.Color().setHex(hexColor))
+    hsvColor.h = (hsvColor.h + shiftValue) % 1
+    ColorConverter.setHSV(new THREE.Color(), hsvColor.h, hsvColor.s, hsvColor.v).getHex()
+
+
   invertHex : (hexColor) ->

-    hsvColor = ColorConverter.getHSV(new THREE.Color().setHex(hexColor))
-    hsvColor.h = (hsvColor.h + 0.5) % 1
-    ColorConverter.setHSV(new THREE.Color(), hsvColor.h, hsvColor.s, hsvColor.v).getHex()
+    @shiftHex(hexColor, 0.5)


fixed css rounding issue

fixed css rounding issue

--- app/assets/javascripts/oxalis/view/plane_view.coffee
+++ app/assets/javascripts/oxalis/view/plane_view.coffee
@@ -160,7 +160,7 @@

   scaleTrianglesPlane : (scale) =>
     @scaleFactor = scale
-    @curWidth = WIDTH = HEIGHT = @scaleFactor * constants.VIEWPORT_WIDTH
+    @curWidth = WIDTH = HEIGHT = Math.round(@scaleFactor * constants.VIEWPORT_WIDTH)
     canvas = $("#render-canvas")
     canvas.width(2 * WIDTH + 20)
     canvas.height(2 * HEIGHT + 20)


Fixed search field blur not removing focus

Closes #18670

--- app/assets/javascripts/search_autocomplete.js.coffee
+++ app/assets/javascripts/search_autocomplete.js.coffee
@@ -171,21 +171,14 @@
     }

   bindEvents: ->
-    $(document).on 'click', @onDocumentClick
     @searchInput.on 'keydown', @onSearchInputKeyDown
     @searchInput.on 'keyup', @onSearchInputKeyUp
     @searchInput.on 'click', @onSearchInputClick
     @searchInput.on 'focus', @onSearchInputFocus
+    @searchInput.on 'blur', @onSearchInputBlur
     @clearInput.on 'click', @onClearInputClick
     @locationBadgeEl.on 'click', =>
       @searchInput.focus()
-
-  onDocumentClick: (e) =>
-    # If clicking outside the search box
-    # And search input is not focused
-    # And we are not clicking inside a suggestion
-    if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length
-      @onSearchInputBlur()

   enableAutocomplete: ->
     # No need to enable anything if user is not logged in


fix(webpack config): add missing viewport for mobile devices

--- webpack.config.coffee
+++ webpack.config.coffee
@@ -64,8 +64,8 @@
       }
     ]
     noParse:[
-       /\.min\.js/
-       path.join __dirname, 'bower_components'
+      /\.min\.js/
+      path.join __dirname, 'bower_components'
     ]
   resolve:
     extensions: ["", ".js", ".coffee"]
@@ -74,8 +74,22 @@
     use: [(require "nib")()]

 htmlPluginOptions =
+  templateContent: '''
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>Karte von Morgen</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <meta name="viewport" content="width=860, maximum-scale=1.0" />
+  </head>
+  <body>
+  </body>
+</html>
+  '''
   title   : "Karte von Morgen"
   favicon : "./src/img/favicon.ico"
+  inject: true

 if production
   plugins.push new webpack.optimize.UglifyJsPlugin()


displayed search term bug

displayed search term bug

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -33,6 +33,7 @@
                 sSearch = undefined
                 ((sSearch = data; break) if data.name == "sSearch_#{index}") for data in aoData
                 sSearch.value = filter.selected if sSearch
+                datatable.fnSettings().aoPreSearchCols[index].sSearch = filter.selected

         aoColumnDefs: aoColumnDefs
         aoColumns: datatable.data('widths')


remove console.log

remove console.log

--- app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
+++ app/assets/javascripts/components/kid_mentor_schedules.js.cjsx
@@ -142,7 +142,6 @@
   onChange: (valuesAsString) ->
     if valuesAsString? and valuesAsString.length > 0
       values = valuesAsString.split(@DELEMITER).map (id) -> parseInt id, 10
-      console.log values, limitAndRemoveFromBeginning values
       @props.onChange limitAndRemoveFromBeginning values
     else
       @props.onChange []


Patching a conflict with Foundation JS

Patching a conflict with Foundation JS

--- app/assets/javascripts/darkswarm/directives/scrollto.js.coffee
+++ app/assets/javascripts/darkswarm/directives/scrollto.js.coffee
@@ -1,6 +1,7 @@
 Darkswarm.directive "ofnScrollTo", ($location, $anchorScroll)->
   restrict: 'A'
   link: (scope, element, attrs)->
-    element.bind 'click', ->
+    element.bind 'click', (ev)->
+      ev.stopPropagation()
       $location.hash attrs.ofnScrollTo
       $anchorScroll()


fixed explorative tracing sorting

fixed explorative tracing sorting

--- app/assets/javascripts/dashboard/models/dashboard_model.coffee
+++ app/assets/javascripts/dashboard/models/dashboard_model.coffee
@@ -65,11 +65,13 @@

     exploratoryAnnotations = new Backbone.Collection()
     # Display newst first.
+    exploratoryAnnotations.comparator = (a,b) ->
+      if a.get("created") < b.get("created")
+        return 1
+      else if a.get("created") > b.get("created")
+        return -1
+      return 0
     exploratoryAnnotations.add(@get("exploratoryAnnotations"))
-    exploratoryAnnotations.comparator = (a,b) ->
-      console.log(a.get("formattedHash")," : ", a.get("created"), " < ", b.get("created"), " : ", b.get("formattedHash"))
-      return a.get("created") < b.get("created")
-    exploratoryAnnotations.sort()

     @set("exploratoryAnnotations", exploratoryAnnotations)


code style

code style

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -19,40 +19,39 @@
     exifTags = null

     if not (URL and DataView)
-      df.reject('support')
+      return df.reject('support')

-    else
-      op = ns.readJpegChunks(file)
-      op.progress (marker, view) ->
-        if marker == 0xe1
-          exifTags = ns.parseExifTags(view)
-          if exifTags?
-            exif = view.buffer
-      op.always ->
-        # start = new Date()
-        img = new Image()
-        img.onload = ->
-          # console.log('load: ' + (new Date() - start))
-          op = ns.reduceImage(img, settings)
-          op.fail df.reject
-          op.done (canvas) ->
-            # start = new Date()
-            utils.canvasToBlob canvas, 'image/jpeg', settings.quality or 0.95,
-              (blob) ->
-                if exif
-                  intro = new DataView(new ArrayBuffer(6))
-                  intro.setUint32(0, 0xffd8ffe1)
-                  intro.setUint16(4, exif.byteLength + 2)
-                  blob = new Blob([
-                    intro, exif, blob.slice(2, blob.size)
-                  ], {type: blob.type})
-                # console.log('to blob: ' + (new Date() - start))
-                df.resolve(blob)
+    op = ns.readJpegChunks(file)
+    op.progress (marker, view) ->
+      if marker == 0xe1
+        exifTags = ns.parseExifTags(view)
+        if exifTags?
+          exif = view.buffer
+    op.always ->
+      # start = new Date()
+      img = new Image()
+      img.onload = ->
+        # console.log('load: ' + (new Date() - start))
+        op = ns.reduceImage(img, settings)
+        op.fail df.reject
+        op.done (canvas) ->
+          # start = new Date()
+          utils.canvasToBlob canvas, 'image/jpeg', settings.quality or 0.95,
+            (blob) ->
+              if exif
+                intro = new DataView(new ArrayBuffer(6))
+                intro.setUint32(0, 0xffd8ffe1)
+                intro.setUint16(4, exif.byteLength + 2)
+                blob = new Blob([
+                  intro, exif, blob.slice(2, blob.size)
+                ], {type: blob.type})
+              # console.log('to blob: ' + (new Date() - start))
+              df.resolve(blob)

-        img.onerror = ->
-          df.reject('not image')
+      img.onerror = ->
+        df.reject('not image')

-        img.src = URL.createObjectURL(file)
+      img.src = URL.createObjectURL(file)

     df.promise()

@@ -65,23 +64,22 @@
     [w, h] = settings.size

     if img.width < w and img.height < h
-      df.reject('not required')
+      return df.reject('not required')

+    # start = new Date()
+
+    if img.width / w > img.height / h
+      h = img.height * w / img.width
     else
-      # start = new Date()
+      w = img.width * h / img.height

-      if img.width / w > img.height / h
-        h = img.height * w / img.width
-      else
-        w = img.width * h / img.height
+    canvas = document.createElement('canvas')
+    canvas.width = w
+    canvas.height = h
+    canvas.getContext('2d').drawImage(img, 0, 0, w, h);

-      canvas = document.createElement('canvas')
-      canvas.width = w
-      canvas.height = h
-      canvas.getContext('2d').drawImage(img, 0, 0, w, h);
-
-      # console.log('draw: ' + (new Date() - start))
-      df.resolve(canvas)
+    # console.log('draw: ' + (new Date() - start))
+    df.resolve(canvas)

     df.promise()

@@ -113,17 +111,15 @@
     df = $.Deferred()

     if not (FileReader and DataView)
-      df.reject('support')
+      return df.reject('support')

-    else
-      # start = new Date()
-      pos = 2
+    # start = new Date()
+    pos = 2

-      readToView file.slice(0, 2), (view) ->
-        if view.getUint16(0) != 0xffd8
-          return df.reject('not jpeg')
-
-        readNext()
+    readToView file.slice(0, 2), (view) ->
+      if view.getUint16(0) != 0xffd8
+        return df.reject('not jpeg')
+      readNext()

     df.promise()

@@ -136,18 +132,18 @@

     tiffOffset = 6
     if view.getUint16(tiffOffset) == 0x4949
-        littleEnd = true
+      littleEnd = true
     else if view.getUint16(tiffOffset) == 0x4d4d
-        littleEnd = false
+      littleEnd = false
     else
-        return
+      return

     if view.getUint16(tiffOffset + 2, littleEnd) != 0x002a
-        return
+      return

     entryOffset = view.getUint32(tiffOffset + 4, littleEnd)
     if entryOffset < 8
-        return
+      return
     entryOffset += tiffOffset + 2

     entries = view.getUint16(entryOffset - 2, littleEnd)


added `csi config strings` command

added `csi config strings` command

--- csi.coffee
+++ csi.coffee
@@ -215,11 +215,11 @@
 configCommands =
   requirejs: (config) ->
     join config.baseUrl || "", config.paths.csi, "require.js"
-  extra: () ->
-    # stringBundlesAsRequirejsModule()
-    ''
+  extra: () -> ''
+  strings: (config) ->
+    JSON.stringify(allStringBundles(), null, "  ")
   config: (config) ->
-    JSON.stringify config, null, "    "
+    JSON.stringify config, null, "  "
   optimizations: (config) ->
     return {} if not hasBuildProfile(argv.buildprofile)
     ext = (fname, newExt) -> fname.match(/\.(css|js)$/)[1]


disable all next tabs if current tab is invalid

--- _src/coffee/directives/form-tab/FormTab.coffee
+++ _src/coffee/directives/form-tab/FormTab.coffee
@@ -72,15 +72,15 @@
               $(element).attr('disabled', 'disabled')
         )
         angular.forEach(controls,
-        (control) ->
-          return unless (control?)
-          if (value)
-            form.$addControl(control)
-            angular.forEach(control.$error, (validity, validationToken) ->
-              form.$setValidity(validationToken, !validity, control)
-            )
-          else
-            form.$removeControl(control)
+          (control) ->
+            return unless (control?)
+            if (value)
+              form.$addControl(control)
+              angular.forEach(control.$error, (validity, validationToken) ->
+                form.$setValidity(validationToken, !validity, control)
+              )
+            else
+              form.$removeControl(control)
         )
       )

@@ -106,14 +106,18 @@
         formControls = $scope.formControls = []

         isPaneValid = () ->
-          nextPane = $scope.getNextPane()
+
           $scope.isPaneInvalid = false
           enabledElements = formElements.split(", ").join(":enabled, ") + ":enabled"
           $($element).find(enabledElements).each(() ->
             $scope.isPaneInvalid = true if ($(@).hasClass("ng-invalid"))
           )

-          nextPane.disabled = $scope.isPaneInvalid if (nextPane)
+          nextPane = $scope.getNextPane()
+          while (nextPane)
+            nextPane.disabled = $scope.isPaneInvalid if (nextPane)
+            nextPane = nextPane.getNextPane()
+
           $scope.$apply()

           return $scope.isPaneInvalid


Finally nailed down regex issue to fix duplicates. I hope.

--- src/tucker.coffee
+++ src/tucker.coffee
@@ -91,11 +91,7 @@

   robot.hear /tucker/i, (res) ->
     message = res.message.text
-    name = message.search(robot.name)
-    rn = robot.name
     re = new RegExp robot.name, 'i'
-    robot.logger.info "name match = #{name}"
-    robot.logger.info "robot name is = #{rn}"
     if message.search(re) is -1
       quote = res.random allQuotes
       if process.env.HUBOT_TUCKER_BYLINE


common: workaround for UglifyJS2 bug

common: workaround for UglifyJS2 bug

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -245,7 +245,7 @@

     if Array.isArray err
       @showError er  for er in err
-      return
+      return err.length

     if 'string' is typeof err
       message = err


Fix avatars not showing up in HUD.

Fix avatars not showing up in HUD.

Asana 54271591170319

--- app/views/play/level/ThangAvatarView.coffee
+++ app/views/play/level/ThangAvatarView.coffee
@@ -37,7 +37,7 @@
     context = super context
     context.thang = @thang
     options = @thang?.getLankOptions() or {}
-    options.async = true
+    #options.async = true  # sync builds fail during async builds, and we build HUD version sync
     context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading
     context.includeName = @includeName
     context


Changed .html to .text

Changed .html to .text

--- modules/crumbtrail/main/index.coffee
+++ modules/crumbtrail/main/index.coffee
@@ -17,7 +17,7 @@
         self.options.renderer(element, d)
       else
         @class('hx-crumbtrail-separator')
-          .html(self.options.separator).node()
+          .text(self.options.separator).node()

     @view = hx.select(@selector).view('span', 'span')
       .update update
@@ -45,4 +45,4 @@
   new Crumbtrail(selection.node(), options)
   selection

-hx.Crumbtrail = Crumbtrail+hx.Crumbtrail = Crumbtrail


Simplify finding clickable elements

Simplify finding clickable elements

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -134,37 +134,35 @@
     resultSet = []

     for element in elements
-      isClickable = false
       tagName = element.tagName.toLowerCase()
-      isClickable = (->
-        if element.hasAttribute "onclick"
-          true
-        else if element.hasAttribute "tabindex"
-          true
-        else if element.getAttribute "role" in ["button", "link"]
-          true
-        else if element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0
-          true
-        else if element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]
-          true
-        else if tagName == "a"
-          true
-        else if tagName == "img"
-          mapName = element.getAttribute "usemap"
-          if mapName
-            map = document.querySelector(mapName.replace /^#/, "")
-            areas = Array::slice.call(map.getElementsByTagName "area")
-            resultSet.concat areas
-          false
-        else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea"
-          not (element.disabled or element.readOnly)
-        else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or
-                tagName in ["button", "select"]
-          not element.disabled
-        else
-          false
-      )()
-      resultSet.push element if isClickable
+
+      # Insert area elements that provide click functionality to an img.
+      if tagName == "img"
+        mapName = element.getAttribute "usemap"
+        if mapName
+          mapName = mapName.replace(/^#/, "").replace("\"", "\\\"")
+          map = document.querySelector "map[name=\"#{mapName}\"]"
+          areas = if map then Array::slice.call(map.getElementsByTagName "area") else []
+          resultSet = resultSet.concat areas
+
+      # Check for attributes that make an element clickable regardless of its tagName.
+      if (element.hasAttribute "onclick" or
+          element.hasAttribute "tabindex" or
+          element.getAttribute "role" in ["button", "link"] or
+          element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or
+          element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"])
+        resultSet.push element
+        continue
+
+      switch tagName
+        when "a"
+          resultSet.push element
+        when "textarea", "input"
+          unless (tagName == "input" and element.getAttribute("type")?.toLowerCase() == "hidden") or
+                 element.disabled or (element.readOnly and DomUtils.isSelectable element)
+            resultSet.push element
+        when "button", "select"
+          resultSet.push element unless element.disabled

     visibleElements = []


Add a few protections to the certificate authentication js

--- app/assets/javascripts/app/application/certificate_authentication.js.coffee
+++ app/assets/javascripts/app/application/certificate_authentication.js.coffee
@@ -12,15 +12,15 @@

           # the expected response, a json with info about success or error
           if xhr.status == 200
-            if response.result == true
+            if response?.result == true
               window.location = response.redirect_to
             else
               console.log "Certificate authentication error:", response
-              mconf.Notification.addAndShow('error', response.error)
+              mconf.Notification.addAndShow('error', response?.error)

           # something went wrong, show a generic error
           else
-            error = response.error ? I18n.t('certificate_authentication.error.generic')
+            error = response?.error ? I18n.t('certificate_authentication.error.generic')
             mconf.Notification.addAndShow('error', error)
             console.log "Certificate authentication error:", xhr.statusText


UI Copy: Active Koders => Active users

UI Copy: Active Koders => Active users

--- client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
+++ client/Social/Activity/ContentDisplays/ActivityRightBlock.coffee
@@ -35,7 +35,7 @@
   constructor:(options={}, data)->

     @itemClass       = ActiveUserItemView
-    options.title    = "Active Koders"
+    options.title    = "Active users"
     options.cssClass = "active-users"

     super options, data


rendering initial dashboard view

rendering initial dashboard view

--- src/components/cc-dashboard/index.cjsx
+++ src/components/cc-dashboard/index.cjsx
@@ -1,21 +1,114 @@
 React = require 'react'
 BS = require 'react-bootstrap'
 {CoursePeriodsNav} = require '../course-periods-nav'
-{CourseStore, CourseActions} = require '../../flux/course'
+{CCDashboardStore, CCDashboardActions} = require '../../flux/cc-dashboard'
+ChapterSection = require '../task-plan/chapter-section'
 LoadableItem = require '../loadable-item'

+DashboardSectionProgress = React.createClass
+  _getPercentage: (num, total) ->
+    Math.round((num / total) * 100)
+
+  render: ->
+    total = @props.section.completed + @props.section.in_progress + @props.section.not_started
+
+    percents =
+      completed: @_getPercentage(@props.section.completed, total)
+      inProgress: @_getPercentage(@props.section.in_progress, total)
+
+    if percents.completed > 0
+      completed = <BS.ProgressBar
+        className="reading-progress-bar"
+        bsStyle="success"
+        now={percents.completed}
+        key={1} />
+
+    if percents.inProgress > 0
+      inProgress = <BS.ProgressBar
+        className="reading-progress-bar"
+        bsStyle="info"
+        now={percents.inProgress}
+        key={2} />
+
+    <BS.ProgressBar className="reading-progress-group">
+      {completed}
+      {inProgress}
+    </BS.ProgressBar>
+
+DashboardSectionPerformance = React.createClass
+  render: ->
+    percents =
+      correct: Math.round(@props.section.original_performance * 100)
+
+    percents.incorrect = 100 - percents.correct
+
+    <BS.ProgressBar className="reading-progress-group">
+      <BS.ProgressBar
+        className="reading-progress-bar progress-bar-correct"
+        now={percents.correct}
+        label="#{percents.correct}%"
+        key={1} />
+      <BS.ProgressBar
+        className="reading-progress-bar progress-bar-incorrect"
+        now={percents.incorrect}
+        key={2} />
+    </BS.ProgressBar>
+
+DashboardSection = React.createClass
+  render: ->
+    <BS.Row className="section-info-row" key={@props.section.id}>
+      <BS.Col xs={6}>
+        <ChapterSection section={@props.section.chapter_section} />
+        . {@props.section.title}
+      </BS.Col>
+      <BS.Col xs={2}>
+        <DashboardSectionProgress section={@props.section} />
+      </BS.Col>
+      <BS.Col xs={2}>
+        <DashboardSectionPerformance section={@props.section} />
+      </BS.Col>
+      <BS.Col xs={2}>
+      </BS.Col>
+    </BS.Row>
+
+DashboardChapter = React.createClass
+  renderSection: (section, index) ->
+    <DashboardSection section={section} key={index} />
+
+  render: ->
+    chapter = <BS.Row className="chapter-name-row" key={@props.chapter.id}>
+      <BS.Col xs={12}>
+        <ChapterSection section={@props.chapter.chapter_section} />
+        . {@props.chapter.title}
+      </BS.Col>
+    </BS.Row>
+
+    sections = _.map @props.chapter.pages, @renderSection
+

+      {chapter}
+      {sections}
+

+
 CCDashboard = React.createClass
   getDefaultProps: ->
     initialActivePeriod: 0

-  getInitialState: -> {}
+  getInitialState: ->
+    activePeriod = CCDashboardStore.getPeriods(@props.id)?[@props.initialActivePeriod]
+    activePeriodId: activePeriod?.id

   handlePeriodSelect: (period) ->
-    @setState({period})
+    @setState({activePeriodId: period.id})
+
+  renderChapters: (chapter, index) ->
+    <DashboardChapter chapter={chapter} key={index} />

   render: ->
     courseId = @props.id
-    periods = CourseStore.getCCPeriods(courseId)
+    periods = CCDashboardStore.getPeriods(courseId)
+    periodChapters = CCDashboardStore.getPeriodChapters(courseId, @state.activePeriodId)
+
+    chapters = _.map periodChapters, @renderChapters

     <BS.Panel className='reading-stats'>
       <CoursePeriodsNav
@@ -23,7 +116,22 @@
         initialActive={@props.initialActivePeriod}
         periods={periods}
         courseId={courseId} />
-        {@state.period?.id}
+
+      <BS.Row className="dashboard-table-header">
+        <BS.Col xs={2} xsOffset={6}>
+
Students

+
+            Completed / In progress / Not started
+
+        </BS.Col>
+        <BS.Col xs={2}>
+          Original Performance
+        </BS.Col>
+        <BS.Col xs={2}>
+          Spaced Practice Performance
+        </BS.Col>
+      </BS.Row>
+      {chapters}
     </BS.Panel>

 DashboardShell = React.createClass
@@ -34,11 +142,8 @@
     {courseId} = @context.router.getCurrentParams()

     <LoadableItem
-      store={CourseStore}
-      actions={CourseActions}
-      load={CourseActions.loadCCDashboard}
-      isLoaded={CourseStore.isCCDashboardLoaded}
-      isLoading={CourseStore.isCCDashboardLoading}
+      store={CCDashboardStore}
+      actions={CCDashboardActions}
       id={courseId}
       renderItem={-> <CCDashboard key={courseId} id={courseId} />}
     />


let change in temp fr change the current step which will change the textarea focus and scroll position

--- src/components/task-step/exercise.cjsx
+++ src/components/task-step/exercise.cjsx
@@ -176,6 +176,12 @@
   setCurrentStep: (currentStep) ->
     @setState({currentStep})

+  onFreeResponseChange: (id, tempFreeResponse) ->
+    {taskId} = @props
+    stepIndex = TaskStore.getStepIndex(taskId, id)
+    TaskStepActions.updateTempFreeResponse(id, tempFreeResponse)
+    @setCurrentStep(stepIndex)
+
   render: ->
     {id, taskId, courseId, onNextStep, onStepCompleted, goToStep} = @props
     {parts, lastPartId, isSinglePartExercise, task, currentStep} = @state
@@ -232,5 +238,5 @@
       getCurrentPanel={getCurrentPanel}
       getReadingForStep={getReadingForStep}
       setFreeResponseAnswer={TaskStepActions.setFreeResponseAnswer}
-      onFreeResponseChange={TaskStepActions.updateTempFreeResponse}
+      onFreeResponseChange={@onFreeResponseChange}
       setAnswerId={TaskStepActions.setAnswerId}/>


Firewall: Change button name for update form.

--- client/Environments/views/firewall/addfirewallrulemodal.coffee
+++ client/Environments/views/firewall/addfirewallrulemodal.coffee
@@ -13,7 +13,7 @@
         Rules                 :
           buttons             :
             Save              :
-              title           : "Save"
+              title           : if data then "Update" else "Save"
               style           : "solid green compact"
               loader          :
                 color         : "#444444"


Admin: Stacks: Remove dummy stack preview code.

--- client/admin/lib/views/stacks/onboarding/onboardingview.coffee
+++ client/admin/lib/views/stacks/onboarding/onboardingview.coffee
@@ -75,27 +75,13 @@

   createStackPreview: ->

-    @preview    = new kd.CustomHTMLView
-      cssClass : 'stack-preview'
+    @stackPreview = new kd.CustomHTMLView
+      cssClass : 'stack-preview hidden'
       partial  : """

STACK FILE PREVIEW

       """

-    @preview.addSubView new kd.CustomHTMLView
-      partial : """
-

-
1

-
2

-
3

-
4

-

-

-
provider:

-
aws:

-
access_key: '${var.access_key}'

-
secret_key: '${var.secret_key}'

-

-      """
+    @stackPreview.addSubView @stackContent = new kd.CustomHTMLView

   pistachio: ->
@@ -110,5 +96,5 @@
         {{> @nextButton}}
         {{> @skipLink}}

-      {{> @preview}}
+      {{> @stackPreview}}
     """


Remove meaningless copy-paste code from node's io/Dir.

--- application/chrome/content/wesabe/io/node/Dir.coffee
+++ application/chrome/content/wesabe/io/node/Dir.coffee
@@ -43,16 +43,6 @@

     return list

-
-  @__defineGetter__ 'profile', ->
-    getNamedDir 'ProfD'
-
-  @__defineGetter__ 'chrome', ->
-    getNamedDir 'AChrom'
-
-  @__defineGetter__ 'root', ->
-    (new @ "#{@chrome.path}/../../").normalize()
-
   @__defineGetter__ 'tmp', ->
     for tmp in ['TMPDIR', 'TMP', 'TEMP']
       return new @ process.env[tmp] if process.env[tmp]


adding the terms of service and privacy policy to auth section

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -121,10 +121,10 @@

         if auth.form == 'create account'
           email_field = ["#{t('login_as')}:", @inputBox('email', 'email@address', 'email')]
-          footer = @footerForRegistrationAndLogin()
+          footer = [@footerForRegistrationAndLogin(), @privacyAndTerms()]
         else
           email_field = ["#{t('login_as')}:", DIV style: {color: auth_text_gray, padding: '4px 8px'}, current_user.email]
-          footer = @submitButton(t('Create new account'))
+          footer = [@submitButton(t('Create new account')), @privacyAndTerms()]

         [ @headerAndBorder goal, t('Introduce Yourself'),
             @body [
@@ -175,6 +175,30 @@

       else
         throw "Unrecognized authentication form #{auth.form}"
+
+  privacyAndTerms: ->
+    DIV
+      style:
+        color: '#999'
+        marginTop: 20
+        fontSize: 14
+        textAlign: 'center'
+
+      "By creating an account, you agree to our "
+      A
+        href: '/terms_of_service'
+        target: '_blank'
+        style:
+          textDecoration: 'underline'
+        'Terms'
+      ' and that you have read our '
+      A
+        href: '/privacy_policy'
+        target: '_blank'
+        style:
+          textDecoration: 'underline'
+        'Privacy Policy'
+      '.'

   #####
   # headerAndBorder


Add a test for non-working setSource, refs #260

--- spec/ComponentLoader.coffee
+++ spec/ComponentLoader.coffee
@@ -205,36 +205,70 @@
         done()

   describe 'writing sources', ->
-    workingSource = """
-    var noflo = require('noflo');
-
-    exports.getComponent = function() {
-      var c = new noflo.Component();
-
-      c.inPorts.add('in', function(packet, outPorts) {
-        if (packet.event !== 'data') {
-          return;
-        }
-        // Do something with the packet, then
-        c.outPorts.out.send(packet.data);
-      });
-
-      c.outPorts.add('out');
-
-      return c;
-    };"""
-
-    it 'should be able to set the source', (done) ->
-      unless platform.isBrowser()
-        workingSource = workingSource.replace "'noflo'", "'./src/lib/NoFlo'"
-      l.setSource 'foo', 'RepeatData', workingSource, 'js', (err) ->
-        throw err if err
-        chai.expect(err).to.be.a 'null'
-        done()
-    it 'should be a loadable component', (done) ->
-      l.load 'foo/RepeatData', (err, inst) ->
-        chai.expect(err).to.be.a 'null'
-        chai.expect(inst).to.be.an 'object'
-        chai.expect(inst.inPorts).to.contain.keys ['in']
-        chai.expect(inst.outPorts).to.contain.keys ['out']
-        done()
+    describe 'with working code', ->
+      workingSource = """
+      var noflo = require('noflo');
+
+      exports.getComponent = function() {
+        var c = new noflo.Component();
+
+        c.inPorts.add('in', function(packet, outPorts) {
+          if (packet.event !== 'data') {
+            return;
+          }
+          // Do something with the packet, then
+          c.outPorts.out.send(packet.data);
+        });
+
+        c.outPorts.add('out');
+
+        return c;
+      };"""
+
+      it 'should be able to set the source', (done) ->
+        unless platform.isBrowser()
+          workingSource = workingSource.replace "'noflo'", "'./src/lib/NoFlo'"
+        l.setSource 'foo', 'RepeatData', workingSource, 'js', (err) ->
+          throw err if err
+          chai.expect(err).to.be.a 'null'
+          done()
+      it 'should be a loadable component', (done) ->
+        l.load 'foo/RepeatData', (err, inst) ->
+          chai.expect(err).to.be.a 'null'
+          chai.expect(inst).to.be.an 'object'
+          chai.expect(inst.inPorts).to.contain.keys ['in']
+          chai.expect(inst.outPorts).to.contain.keys ['out']
+          done()
+    describe 'with non-working code', ->
+      nonWorkingSource = """
+      var noflo = require('noflo');
+      var notFound = require('./this_file_does_not_exist.js');
+
+      exports.getComponent = function() {
+        var c = new noflo.Component();
+
+        c.inPorts.add('in', function(packet, outPorts) {
+          if (packet.event !== 'data') {
+            return;
+          }
+          // Do something with the packet, then
+          c.outPorts.out.send(packet.data);
+        });
+
+        c.outPorts.add('out');
+
+        return c;
+      };"""
+
+      it 'should be able to set the source', (done) ->
+        unless platform.isBrowser()
+          nonWorkingSource = nonWorkingSource.replace "'noflo'", "'./src/lib/NoFlo'"
+        l.setSource 'foo', 'NotWorking', nonWorkingSource, 'js', (err) ->
+          chai.expect(err).to.be.an 'object'
+          done()
+      it 'should not be a loadable component', (done) ->
+        l.load 'foo/NotWorking', (err, inst) ->
+          console.log err, inst
+          chai.expect(err).to.be.an 'object'
+          chai.expect(inst).to.be.an 'undefined'
+          done()


--- spec/api/server_spec.coffee
+++ spec/api/server_spec.coffee
@@ -17,3 +17,9 @@
                 facto()

         server api: port: process.env.API_PORT
+
+
+    it 'temporary test to check failure to call "active stub"', ipso (facto, should) ->
+
+        should.does exist: -> facto()
+