That's the emailnotification worker.

That's the emailnotification worker.

--- workers/emailnotifications/main.coffee
+++ workers/emailnotifications/main.coffee
@@ -1,15 +1,20 @@

-{argv}         = require 'optimist'
-{mongo, email} = require argv.c
-{CronJob}      = require 'cron'
-Bongo          = require 'bongo'
+{argv}    = require 'optimist'
+{CronJob} = require 'cron'
+Bongo     = require 'bongo'
+Broker    = require 'broker'
+{Base}    = Bongo
+Emailer   = require '../social/lib/social/emailer'
+
+{mq, mongo, email} = require('koding-config-manager').load("main.#{argv.c}")
+
+broker = new Broker mq

 worker = new Bongo {
   mongo
-  root: __dirname
-  models: [
-    '../social/lib/social/models/emailnotification.coffee'
-  ]
+  mq     : broker
+  root   : __dirname
+  models : '../social/lib/social/models'
 }

 log = ->
@@ -17,15 +22,162 @@

 log "Koding E-Mail Notification Worker has started with PID #{process.pid}"

+commonHeader     = (m)-> """[Koding Bot] A new notification"""
+commonTemplate   = (m)->
+
+  action  = ''
+  preview = ''
+  switch m.event
+    when 'LikeIsAdded'
+      action = "liked your"
+    when 'ReplyIsAdded'
+      action  = "commented on your"
+      preview = """
+
+

+                    #{m.realContent.body}
+

+
+                """
+
+  """
+    Hi #{m.receiver.profile.firstName},
+
+    #{m.sender.profile.firstName} #{m.sender.profile.lastName} #{action} #{m.contentLink}.
+    #{preview}
+
+    --
+    Management
+  """
+
+prepareAndSendEmail = (notification)->
+
+  sendEmail = (details)->
+    {notification} = details
+    # log "This is the mail:", commonTemplate details
+    Emailer.send
+      To        : details.email
+      Subject   : commonHeader details
+      HtmlBody  : commonTemplate details
+    , (err, status)->
+      log "An error occured: #{err}" if err
+      notification.update $set: status: 'attempted', (err)->
+        console.error err if err
+
+  fetchSubjectContent = (contents, callback)->
+    {constructorName} = contents.subject
+    constructor       = Base.constructors[constructorName]
+    constructor.one {_id:contents.subject.id}, (err, res)->
+      if err then console.error err
+      callback err, res
+
+  fetchContent = (content, callback)->
+    {constructorName} = content
+    unless constructorName
+      callback new KodingError 'Action type wrong.'
+    else
+      constructor     = Base.constructors[constructorName]
+      {id}            = content
+      constructor.one {_id:id}, (err, res)->
+        if err then console.error err
+        callback err, res
+
+  fetchSubjectContentLink = (content, type, callback)->
+
+    contentTypeLinkMap = (link)->
+      pre = ""
+
+      JReview           : "#{pre}review"
+      JComment          : "#{pre}comment"
+      JOpinion          : "#{pre}opinion"
+      JCodeSnip         : "#{pre}code snippet"
+      JTutorial         : "#{pre}tutorial"
+      JDiscussion       : "#{pre}discussion"
+      JLinkActivity     : "#{pre}link"
+      JStatusUpdate     : "#{pre}status update"
+      JPrivateMessage   : "#{pre}private message"
+      JQuestionActivity : "#{pre}question"
+
+    if content.slug
+      callback null, contentTypeLinkMap(content.slug)[type]
+    else
+      {constructorName} = content.bongo_
+      constructor = Base.constructors[constructorName]
+      constructor.fetchRelated? content._id, (err, relatedContent)->
+        if err then callback err
+        else
+          unless relatedContent.slug?
+            constructor = \
+              Base.constructors[relatedContent.bongo_.constructorName]
+            constructor.fetchRelated? relatedContent._id, (err, content)->
+              if err then callback err
+              else
+                callback null, contentTypeLinkMap(content.slug)[type]
+          else
+            callback null, contentTypeLinkMap(relatedContent.slug)[type]
+
+  {JAccount, JEmailNotificationGG} = worker.models
+
+  {event}     = notification.data
+  contentType = notification.activity.subject.constructorName
+
+  JAccount.one {_id:notification.receiver.id}, (err, receiver)->
+    if err then callback err
+    else
+      JEmailNotificationGG.checkEmailChoice
+        event       : event
+        contentType : contentType
+        username    : receiver.profile.nickname
+      , (err, state, email)->
+        if err
+          console.error "Could not load user record"
+          callback err
+        else
+          if state not in ['instant', 'daily']
+            log 'User disabled e-mails, ignored for now.'
+            notification.update $set: status: 'postponed', (err)->
+              console.error err if err
+          else
+            # log "Trying to send it... to...", email
+            JAccount.one {_id:notification.sender}, (err, sender)->
+              if err then callback err
+              else
+                fetchSubjectContent notification.activity, \
+                (err, subjectContent)->
+                  if err then callback err
+                  else
+                    realContent = subjectContent
+                    details = {sender, receiver, event, email, \
+                               subjectContent, realContent, notification}
+                    fetchSubjectContentLink subjectContent, contentType, \
+                    (err, link)->
+                      if err then callback err
+                      else
+                        details.contentLink = link
+                        if event is 'ReplyIsAdded'
+                          fetchContent notification.activity.content, \
+                          (err, content)->
+                            if err then callback err
+                            else
+                              details.realContent = content
+                              sendEmail details
+                        else
+                          sendEmail details
+
 job = new CronJob email.notificationCron, ->

-  {JEmail} = worker.models
-  log "Running..."
+  {JEmailNotificationGG} = worker.models
+  # log "Checking for waiting queue..."

-  JEmail.someData {status: "queued"}, {email:yes}, {limit:10}, (err, cursor)->
+  JEmailNotificationGG.some {status: "queued"}, {limit:10}, (err, emails)->
     if err
-      log "Could not load email queue"
-    else cursor.toArray (err, queue)->
-      log 'E-mails found in queue:', queue
+      log "Could not load email queue!"
+    else
+      if emails.length > 0
+        log "There are #{emails.length} mail in queue."
+        for email in emails
+          prepareAndSendEmail email
+      else
+        log "E-Mail queue is empty. Yay."

-job.start()
+job.start()


 Merge branch 'master.guestActiveUsersFix' into 'master'

Master.Guest Active Users Fix

--- workers/social/lib/social/models/activeitems.coffee
+++ workers/social/lib/social/models/activeitems.coffee
@@ -59,7 +59,8 @@

     @fetchRandomUsers callback

-  @fetchRandomUsers = (callback)-> JAccount.some {}, {limit:10}, callback
+  @fetchRandomUsers = (callback)->
+    JAccount.some {"type":"registered"}, {limit:10}, callback

   @fetchRandomTopics = (callback, options)->
     group = options.group or "koding"


[worker][social][model][graph] fixed issues with merge request

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -116,7 +116,6 @@
         order by content.`meta.createdAtEpoch` DESC
         limit 20
       """
-    console.log query

     @db.query query, {}, (err, results)=>
       tempRes = []
@@ -138,7 +137,6 @@
           if groupName == "koding"
             @removePrivateContent  groupId, tempRes, callback
           else
-            console.log tempRes.length()
             callback null, tempRes
         resultData = ( result.content.data for result in results)
         objectify resultData, (objecteds)->
@@ -154,10 +152,7 @@
       order by koding.`meta.createdAtEpoch` DESC
     """

-    params =
-      itemId : itemId
-
-    @db.query query, params, (err, results) ->
+    @db.query query, {}, (err, results) ->
       if err then throw err
       resultData = []
       for result in results
@@ -183,7 +178,7 @@
       MATCH kd-[:member]->users<-[r:user]-koding
       WHERE koding.name="JApp"
       and r.createdAtEpoch < #{startDate}
-      return *
+      return users, koding, r
       order by r.createdAtEpoch DESC
       limit 20
       """
@@ -229,7 +224,7 @@
           callback err, objected

   fetchMemberFollows:(group, startDate, callback)->
-    {groupId, groupName} = group
+    {groupId} = group
     #followers
     query = """
       start koding=node:koding("id:#{groupId}")


group: bugfix: tags should be queried on the target, not the edge

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1576,20 +1576,20 @@
     [options, callback] = [callback, options]  unless callback
     options ?= {}
     { tag, tags } = options
-    if tag    then tags = [tag]
-    selector =
+    tags = [tag]  if tag and not tags
+
+    options.targetOptions ?= {}
+    options.targetOptions.options ?= {}
+    options.targetOptions.options.sort ?= sortWeight: 1
+    options.targetOptions.selector =
       if tags
       then { tags }
       else {}
-
-    options.targetOptions ?= {}
-    options.targetOptions.options ?= {}
-    options.targetOptions.options.sort ?= sortWeight: 1

     switch category
       when 'product'
-        @fetchProducts selector, options, callback
+        @fetchProducts {}, options, callback
       when 'pack'
-        @fetchPacks selector, options, callback
+        @fetchPacks {}, options, callback
       when 'plan'
-        @fetchPlans selector, options, callback
+        @fetchPlans {}, options, callback


Fix the unexistent path resolving

Fix the unexistent path resolving

--- library/gearing/watch.coffee
+++ library/gearing/watch.coffee
@@ -103,7 +103,7 @@
     # It takes care of either initial loading and registering of
     # services or the hot swapping of the services that changed.
     hotSwappingUnlink: (path) ->
-        absolute = fs.realpathSync path
+        absolute = paths.resolve path
         modules = @constructor.EXTENSIONS
         extension = paths.extname absolute
         return unless extension in modules


Renaming for consistency.

Renaming for consistency.

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -225,12 +225,12 @@
   execute: (rs) ->
     counts = rs.curr_frame().stack.splice(-@dim,@dim)
     default_val = util.initial_value @class[@dim..]
-    arrTypes = (c2t(@class[d..]) for d in [0...@dim] by 1)
+    arr_types = (c2t(@class[d..]) for d in [0...@dim] by 1)
     init_arr = (curr_dim) =>
       len = counts[curr_dim]
       if len < 0 then java_throw(rs, 'java/lang/NegativeArraySizeException',
         "Tried to init dimension #{curr_dim} of a #{@dim} dimensional #{@class.toString()} array with length #{len}")
-      type = arrTypes[curr_dim]
+      type = arr_types[curr_dim]
       if curr_dim+1 == @dim
         array = (default_val for i in [0...len] by 1)
       else


list newly created instance, including IP address

--- code/li.coffee
+++ code/li.coffee
@@ -42,13 +42,14 @@
     instance.restart ->
       process.stdout.write('restarted\n')

-# Takes a config name, and creates an instance based
-# on that config
+# Takes a config name, and creates an instance based on that config.
 # args[2] is the name of the config
 create = (args) ->
   process.stdout.write('Creating...\n')
-  Linode.create args[2], ->
-    process.stdout.write('Created!\n')
+  Linode.create args[2], (dummy_, item) ->
+    Linode.get item.name, (item) ->
+      process.stdout.write('Created!\n')
+      log_one_item item

 # args[2] is the name of the instance to destroy
 destroy = (args) ->


improved timeout class

improved timeout class

--- src/sys/timeout.coffee
+++ src/sys/timeout.coffee
@@ -6,6 +6,9 @@
   # loop - loop state (default: false)
   constructor: (@delay, @loop=false) ->
     @_dt = 0
+    @visible = false
+    @exists = true
+    @alive = true

   # Public: On end callback.
   onend: () ->
@@ -13,11 +16,15 @@

   # Public: Resets the timeout.
   reset: () ->
+    @exists = true
+    @alive = true
     @_dt = @delay
     @

   # Public: Kills the timeout.
   kill: () ->
+    @exists = false
+    @alive = false
     @_dt = 0
     @

@@ -27,6 +34,10 @@
   update: (timer) ->
     if @_dt > 0 and ((@_dt -= timer.dt) <= 0)
       @onend(@)
-      @reset() if @loop
+
+      if @loop
+        @reset()
+      else
+        @kill()

     null


Only create grammar load promise once

Only create grammar load promise once

--- src/package.coffee
+++ src/package.coffee
@@ -82,7 +82,7 @@
     @scopedProperties = []

   activate: ->
-    @grammarsPromise = @loadGrammars()
+    @grammarsPromise ?= @loadGrammars()

     unless @activationDeferred?
       @activationDeferred = Q.defer()


Turn off HoC homepage play redirect

Turn off HoC homepage play redirect

--- app/views/HomeView.coffee
+++ app/views/HomeView.coffee
@@ -14,7 +14,7 @@
     if @getQueryVariable 'hour_of_code'
       application.router.navigate "/hoc", trigger: true

-    isHourOfCodeWeek = true  # Temporary: default to /hoc flow during the main event week
+    isHourOfCodeWeek = false  # Temporary: default to /hoc flow during the main event week
     if isHourOfCodeWeek and (@isNewPlayer() or (@justPlaysCourses() and me.isAnonymous()))
       # Go/return straight to playing single-player HoC course on Play click
       @playURL = '/hoc?go=true'


Resume playing immediately on page load.

--- frontend/script.coffee
+++ frontend/script.coffee
@@ -164,23 +164,22 @@

                 @player = @playerElement.get(0)
                 @preloadPlayer = @preloadElement.get(0)
-                if @storage.currentSong
-                        try
-                                @currentSong = JSON.parse @storage.currentSong
-                        catch error
-                                @currentSong = null
-                else
-                        @currentSong = null
                 @continuePosition = 0
-                if @storage.continuePosition
-                        @lastPosition = @storage.continuePosition
-                else
-                        @lastPosition = 0
                 @preloads = {}
                 @_bind()
                 if @storage.volume
                         @setVolume @storage.volume
                 @startedPlaying = false
+                if @storage.currentSong
+                        try
+                                @currentSong = JSON.parse @storage.currentSong
+                                @lastPosition = parseInt @storage.continuePosition
+                                @resumePlaying()
+                        catch error
+                                @currentSong = null
+                else
+                        @currentSong = null
+                        @lastPosition = 0

         _bind: =>
                 @playerElement.bind "pause", =>
@@ -236,6 +235,8 @@
                 @trigger "preparePlay", song

         preload: (song, react=true) =>
+                if not song
+                        return
                 console.log "player#preload #{song.title} #{react}"
                 if song.filename of @preloads
                         console.log "player#preload #{song.title} return"


finish testing eval inputs and outputs

finish testing eval inputs and outputs

--- src/smc-hub/test/postgres-user-queries-syncstring-eval.coffee
+++ src/smc-hub/test/postgres-user-queries-syncstring-eval.coffee
@@ -20,7 +20,7 @@
 {create_accounts, create_projects, changefeed_series} = pgtest
 misc = require('smc-util/misc')

-describe 'basic use of eval_inputs table --', ->
+describe 'use of eval_inputs table --', ->
     before(setup)
     after(teardown)

@@ -174,8 +174,50 @@
                 expect(err).toEqual('postgresql error: new row for relation "eval_inputs" violates check constraint "eval_inputs_user_id_check"')
                 done()

+    it 'tests uses of eval_inputs changefeed', (done) ->
+        changefeed_id = misc.uuid()
+        t2 = new Date()
+        db.user_query
+            project_id : projects[0]
+            query      : {eval_inputs:[{string_id:string_id, time:{'>=':misc.hours_ago(4)}, user_id:null, input:null}]}
+            changes    : changefeed_id
+            cb         : changefeed_series([
+                (x, cb) ->
+                    expect(x.eval_inputs.length).toEqual(1)
+
+                    # write old input -- no update
+                    db.user_query
+                        account_id : accounts[0]
+                        query      : {eval_inputs:{string_id:string_id, time:misc.hours_ago(10), user_id:0, input:input}}
+                        cb         : (err) ->
+                            if err
+                                cb(err); return
+                            # write new input, which triggers a response
+                            db.user_query
+                                account_id : accounts[0]
+                                query      : {eval_inputs:{string_id:string_id, time:t2, user_id:0, input:input}}
+                                cb         : cb
+                (x, cb) ->
+                    expect(x).toEqual({action:'insert', new_val:{string_id:string_id, time:t2, user_id:0, input:input}})
+
+                    # modify existing input
+                    db.user_query
+                        account_id : accounts[0]              # query has deep merge semantics by default.
+                        query      : {eval_inputs:{string_id:string_id, time:t2, user_id:0, input:{foo:'bar'}}}
+                        cb         : cb
+                (x, cb) ->
+                    expect(x).toEqual({action:'update', new_val:{string_id:string_id, time:t2, user_id:0, input:misc.merge({foo:'bar'},input)}})
+
+                    db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
+                (x, cb) ->
+                    expect(x).toEqual({action:'close'})
+
+                    cb()
+            ], done)
+
+
 # NOTE: this is very similar to eval_inputs above.
-describe 'basic use of eval_outputs table --', ->
+describe 'use of eval_outputs table --', ->
     before(setup)
     after(teardown)

@@ -321,3 +363,43 @@
                 expect(err).toEqual('postgresql error: new row for relation "eval_outputs" violates check constraint "eval_outputs_number_check"')
                 done()

+    it 'tests uses of eval_outputs changefeed', (done) ->
+        changefeed_id = misc.uuid()
+        t2 = new Date()
+        db.user_query
+            account_id : accounts[0]
+            query      : {eval_outputs:[{string_id:string_id, time:{'>=':misc.hours_ago(4)}, number:null, output:null}]}
+            changes    : changefeed_id
+            cb         : changefeed_series([
+                (x, cb) ->
+                    expect(x.eval_outputs.length).toEqual(1)
+
+                    # write old output -- no update
+                    db.user_query
+                        project_id : projects[0]
+                        query      : {eval_outputs:{string_id:string_id, time:misc.hours_ago(10), number:0, output:output}}
+                        cb         : (err) ->
+                            if err
+                                cb(err); return
+                            # write new output, which triggers a response
+                            db.user_query
+                                project_id : projects[0]
+                                query      : {eval_outputs:{string_id:string_id, time:t2, number:0, output:output}}
+                                cb         : cb
+                (x, cb) ->
+                    expect(x).toEqual({action:'insert', new_val:{string_id:string_id, time:t2, number:0, output:output}})
+
+                    # modify existing output
+                    db.user_query
+                        project_id : projects[0]               # query has deep merge semantics by default.
+                        query      : {eval_outputs:{string_id:string_id, time:t2, number:0, output:{foo:'bar'}}}
+                        cb         : cb
+                (x, cb) ->
+                    expect(x).toEqual({action:'update', new_val:{string_id:string_id, time:t2, number:0, output:misc.merge({foo:'bar'},output)}})
+
+                    db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
+                (x, cb) ->
+                    expect(x).toEqual({action:'close'})
+
+                    cb()
+            ], done)


* remove noneed codes

* remove noneed codes

--- shell.coffee
+++ shell.coffee
@@ -65,7 +65,7 @@
       if not @setPath(aKeyPath)
         @_pathArray = []
       @self = @
-      parent = nut.subkey(path.dirname @path)
+      #parent = nut.subkey(path.dirname @path)
       #if parent?

       @__defineGetter__ "sublevels", ->


Removed unused lib require.

Removed unused lib require.

--- workers/sitemapgenerator/lib/sitemapgenerator/sitemapgeneratorworker.coffee
+++ workers/sitemapgenerator/lib/sitemapgenerator/sitemapgeneratorworker.coffee
@@ -3,7 +3,6 @@
 jraphical = require 'jraphical'
 {Base, race, dash, daisy} = require "bongo"
 {CronJob} = require 'cron'
-_ = require "underscore"

 NAMEPERPAGE = 50000
 GROUPPERPAGE = 5


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -64,7 +64,7 @@
   @param {Number} [duration=30] How long the effect lasts
   @param {Color} [color="transparent"] The color to fade to
   ###
-  fadeOut: (options) ->
+  fadeOut: (options={}) ->
     {alpha, color, duration} = Object.reverseMerge(options, fadeOutDefaults)

     configureFade(duration, color, 1)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -25,7 +25,7 @@

     @name centerText
     @methodOf TextScreen#
-    @param {PixieCanvas} canvas The cavans
+    @param {PixieCanvas} canvas The canvas to draw
     ###
     centerText: (canvas, text, options={}) ->
       font = options.font || I.font


Use openers ivar

Use openers ivar

--- src/project.coffee
+++ src/project.coffee
@@ -190,7 +190,7 @@
   # Returns an {EditSession}.
   open: (filePath, options={}) ->
     filePath = @resolve(filePath) if filePath?
-    for opener in @constructor.openers
+    for opener in @openers
       return resource if resource = opener(filePath, options)

     @buildEditSessionForBuffer(@bufferForPath(filePath), options)


Use setKey.

Use setKey.

--- src/client/node.coffee
+++ src/client/node.coffee
@@ -15,8 +15,8 @@
     super opts

     @opts.endpoint = ENDPOINT if ENDPOINT
-    @opts.debug = true if DEBUG
-    @key = KEY if KEY
+    @opts.debug    = true if DEBUG
+    @setKey KEY if KEY

   request: (blueprint, data = {}, key = @getKey()) ->
     opts =


removed unused scorelog

removed unused scorelog

--- src/trivia-game.coffee
+++ src/trivia-game.coffee
@@ -35,7 +35,6 @@

     @robot.brain.on 'loaded', =>
       @robot.brain.data.scores ||= {}
-      @robot.brain.data.scoreLog ||= {}

       @cache.scores = @robot.brain.data.scores


userOrgSelector: fix initial value and add email with single select

--- app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.coffee
+++ app/assets/javascripts/app/lib/app_post/user_organization_autocompletion.coffee
@@ -85,12 +85,16 @@

     return if !userId
     return if !App.User.exists(userId)
-    name = App.User.find(userId).displayName()
+    user = App.User.find(userId)
+    name = user.displayName()

     if @attribute.multiple
       # create token
       @createToken name, userId
     else
+      if user.email
+        name += " <#{user.email}>"
+
       @userSelect.val(name)

     if @callback
@@ -228,13 +232,18 @@
     App.view('generic/user_search/new_user')()

   build: =>
-    if @attribute.multiple
+    tokens = ''
+    name = ''
+    value = ''
+
+    if @attribute.multiple && @attribute.value
+      # fallback for if the value is not an array
       if typeof @attribute.value is not 'object'
         @attribute.value = [@attribute.value]
-
+
       value = @attribute.value.join ','
-      tokens = ''
-      name = ''
+
+      # create tokens
       for userId in @attribute.value
         if App.User.exists userId
           tokens += App.view('generic/token')(
@@ -245,7 +254,6 @@
           @log 'userId doesn\'t exist', userId
     else
       value = @attribute.value
-      tokens = ''
       if value
         if App.User.exists value
           name = App.User.find(value).displayName()


accessiblity touch ups when copying a course

fixes CNVS-12188

When you are migrating a course and using voice over in safari it was
really confusing because when you selected 'copy course' and did a
course search or a course select, the results would be read we everything else on the
page. This fixes that.

Test Plan
As a teacher or someone who can run a migration
Given you are using safari with voice over
When you import content into a course
And you select the content type of 'Copy a Canvas Course'
And you search for a course by its name by using the field
Then voice over should tell you there are results
And when you use the arrow keys to select one of those results
Then only the selected result should be read by voice over
And the all of the form items should not be read
----------------------------------------------------
As a teacher or someone who can run a migration
Given you are using safari with voice over
When you import content into a course
And you select the content type of 'Copy a Canvas Course'
And you select a course in the course select dropdown
And when you use the arrow keys to select one of those results
Then only the selected result should be read by voice over
And the all of the form items should not be read

Change-Id: Id2c265d900ee9676094e221fa9dd38e8a61a843a
Reviewed-on: https://gerrit.instructure.com/35240
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Product-Review: Hilary Scharton <b55b20a171673afabe1f3f421d6770a86afaa815@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/views/content_migrations/subviews/CourseFindSelectView.coffee
+++ app/coffeescripts/views/content_migrations/subviews/CourseFindSelectView.coffee
@@ -34,6 +34,15 @@
         select: @updateSelect
       @$courseSearchField.data('ui-autocomplete')._renderItem = (ul, item) ->
         $(autocompleteItemTemplate(item)).appendTo(ul)
+
+      # Accessiblity Hack. If you find a better solution please fix this. This makes it so the whole form isn't read
+      # by the screen reader every time a user selects an auto completed item.
+      $converterDiv = $('#converter')
+      @$courseSearchField.on 'focus', -> $converterDiv.attr('aria-atomic', false)
+      @$courseSearchField.on 'blur', -> $converterDiv.attr('aria-atomic', true)
+      @$courseSelect.on 'focus', -> $converterDiv.attr('aria-atomic', false)
+      @$courseSelect.on 'blur', -> $converterDiv.attr('aria-atomic', true)
+      ## hack finished ##

     toJSON: ->
       json = super


one regex not two

one regex not two

--- gulp-nav.coffee
+++ gulp-nav.coffee
@@ -30,8 +30,7 @@
   ('..' for s in start)                               # ascend out of remaining
     .concat finish                                    # descend into remaining
     .join '/'
-    .replace /\.\/$/, '.'
-    .replace /^$/, '.'
+    .replace /(^|(\.\/))$/, '.'

 resolve = (parts...) ->
   parts.reduce url.resolve


remove node require from rjs config

remove node require from rjs config

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -47,7 +47,6 @@
 			capacitor: "main"
 			signals: "../node_modules/signals/dist/signals"
 		include: ["../vendor/almond", 'main']
-		nodeRequire: require
 		out: "capacitor.js"
 		wrap:
 			startFile: "build/wrap-start.js"


Missed conversion

Missed conversion

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -45,7 +45,7 @@
   config = _.extend {}, webpackConfig

   # Don't use react-hot-loader for the production build.
-  config.entry = "./client"
+  config.entry = "./src/scripts/router"
   config.module.loaders[1] =
     { test: /\.cjsx$/, loaders: ['coffee', 'cjsx']}
   config.plugins = []


Fix zip directory as relative

Fix zip directory as relative

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -2,7 +2,7 @@
 gutil = require 'gulp-util'
 plumber = require 'gulp-plumber'
 webpack = require 'webpack'
-{join, basename} = require 'path'
+{join, basename, relative} = require 'path'
 {readFileSync, writeFileSync, existsSync, mkdirSync} = require 'fs'
 {cloneDeep} = require 'lodash'
 packager = require 'electron-packager'
@@ -93,13 +93,6 @@
       output: './dist/main.js'

 gulp.task 'publish', ->
-  github = new GitHub
-    version: "3.0.0"
-    debug: true
-  github.authenticate
-    type: 'oauth'
-    token: process.env.TOKEN
-
   Q
     .when ''
     .then ->
@@ -118,17 +111,22 @@

     .then (dirs) ->
       dirs = dirs.map (dir) ->
-        name = "#{basename dir}.zip"
-        {
-          dir, name
-          zip: "tmp/#{name}"
-        }
+        name = basename dir
+        zip = "#{name}.zip"
+        {dir, name, zip}
+
+      github = new GitHub
+        version: "3.0.0"
+        debug: true
+      github.authenticate
+        type: 'oauth'
+        token: process.env.TOKEN

       Q
         .all dirs.map ({dir, name, zip}) ->
           d = Q.defer()
-          console.log "zip #{name}"
-          zip = spawn 'zip', [zip, '-r', dir]
+          console.log "zip #{name} to #{zip}"
+          zip = spawn 'zip', [zip, '-r', name]
           zip.stdout.on 'data', (data) -> console.log data.toString 'utf8'
           zip.stderr.on 'data', (data) -> console.log data.toString 'utf8'
           zip.on 'close', (code) -> d.resolve()
@@ -165,7 +163,7 @@
           github.releases.createRelease
             owner: 'minodisk'
             repo: 'markn'
-            tag_name: 'v0.0.1' #TODO bump package.json
+            tag_name: 'v0.0.2' #TODO bump package.json
           , (err, res) ->
             throw err if err
             console.log JSON.stringify res, null, 2


fix build task dependency

fix build task dependency

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -89,13 +89,17 @@
     gutil.log e
     @emit 'end'

-gulp.task 'build', [
-  'build-json'
-  'build-html'
-  'build-resource'
-  'build-script'
-  'build-style'
-]
+gulp.task 'build', (done) ->
+  run.apply run, [
+    'build-json'
+    [
+      'build-html'
+      'build-resource'
+      'build-script'
+      'build-style'
+    ]
+    done
+  ]

 gulp.task 'build-dev', [
   'build-html-dev'


Fix Clean IssueY

Fix Clean IssueY

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -174,6 +174,8 @@
     .pipe(clean(force: true))
   gulp.src('run', read: false)
     .pipe(clean(force: true))
+  gulp.src('import', read: false)
+    .pipe(clean(force: true))

 gulp.task 'bower:install', shell.task('bower install')


Now supports .sass files (in addition to .scss)

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -36,7 +36,7 @@
 paths =
   app:
     scripts: ["app/javascripts/app.{coffee,js}", "app/javascripts/**/*.{coffee,js}"] # All .js and .coffee files, starting with app.coffee or app.js
-    styles: "app/stylesheets/**/*.{scss,css}" # css and scss files
+    styles: "app/stylesheets/**/*.{scss,sass,css}" # css and scss files
     pages: "app/pages/*.{html,jade,md,markdown}" # All html, jade,and markdown files that can be reached directly
     templates: "app/templates/**/*.{html,jade,md,markdown}" # All html, jade, and markdown files used as templates within the app
     images: "app/images/*.{png,jpg,jpeg,gif}" # All image files
@@ -115,7 +115,7 @@
 # Gather CSS files and convert scss to css
 compileCss = ->
   gulp.src(paths.app.styles)
-    .pipe(gulpif(/[.]scss$/,
+    .pipe(gulpif(/[.]scss|sass$/,
       sass({
         sourcemap: false,
         unixNewlines: true,


update favicon workflow

update favicon workflow

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -107,17 +107,26 @@
   .pipe gulp.dest('build')

 gulp.task "generate_favicons", ->
-  gulp.src("build/index.html")
-  .pipe(
-    favicons
-      files: {
-        dest: "media/graphics/web"
-        # html: "build/index.html"
-      },
-      settings: {
-        logging: true
-      })
-  .pipe gulp.dest("./")
+  return gulp.src("./build/media/graphics/favicon_base.png")
+    .pipe(
+      favicons
+        appName: "Pupil Labs"
+        appDescription: "Pupil Labs Website"
+        developerName: "Pupil Labs Dev Team"
+        developerURL: "https://github.com/pupil-labs/pupil-labs-website"
+        background: "#38ea92"
+        path: "build/media/graphics/web/favicons/"
+        display: "standalone"
+        orientation: "portrait"
+        version: 2.0
+        logging: false
+        html: "build/index.html"
+        pipeHTML: true
+        replace: true
+    )
+    .on("error", gutil.log)
+    .pipe(gulp.dest(".build/media/graphics/web/favicons/"))
+

 gulp.task "preview", ->
     wintersmith.settings.configFile = 'config.json'


(fix) Properly copies font files to dist now

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -102,6 +102,8 @@
     .pipe $.useref userefOpts
     .pipe gulp.dest '.staging/_includes'

+gulp.task 'assets', ['images', 'fonts']
+
 gulp.task 'images', ->
   # Optimize images
   gulp.src 'app/images/**/*'
@@ -115,13 +117,16 @@
     .pipe gulp.dest 'dist/images'

 gulp.task 'fonts', ->
+  gulp.src '.tmp/fonts/**/*'
+    .pipe gulp.dest 'dist/fonts'
+
+gulp.task 'wireFonts', ->
   # Copy font files obtained with bower
   bowerFiles = require 'main-bower-files'

   gulp.src bowerFiles({filter: '**/*.{eot,svg,ttf,woff,woff2}'
   }).concat 'app/fonts/**/*'
     .pipe gulp.dest '.tmp/fonts'
-    .pipe gulp.dest 'dist/fonts'

 gulp.task 'extras', ->
   # Copy all files not handled by jekyll or other gulp tasks
@@ -152,8 +157,8 @@
 # Basic entry point
 gulp.task 'build', ['clean'], ->
   # Clean first, then the rest
-  runSequence ['jekyll', 'fonts', 'extras'],
-    'images',
+  runSequence ['jekyll', 'wireFonts', 'extras'],
+    'assets',
     ['cleanStray', 'size']

 gulp.task 'size', ->
@@ -163,7 +168,7 @@

 gulp.task 'serve', ['clean'], ->
   # Clean first, then the rest
-  runSequence ['jekyll:tmp', 'fonts'], 'browsersync'
+  runSequence ['jekyll:tmp', 'wireFonts'], 'browsersync'

 gulp.task 'browsersync', ->
   # Run the web server
@@ -199,18 +204,22 @@
   # Path-specific tasks
   gulp.watch 'app/scripts/**/*.{coffee,litcoffee}', ['reloadAfterScripts']
   gulp.watch 'app/styles/**/*.scss', ['styles']
-  gulp.watch 'app/fonts/**/*', ['fonts']
-  gulp.watch 'bower.json', ['wiredep', 'fonts']
+  gulp.watch 'app/fonts/**/*', ['wireFonts']
+  gulp.watch 'bower.json', ['wiredep', 'wireFonts']

 gulp.task 'wiredep', ->
   # Insert bower dependencies into files
-  gulp.src 'app/_sass/*.scss'
+  gulp.src 'app/_sass/main.scss'
     .pipe $.debug()
     .pipe wiredep
       ignorePath: /^(\.\.\/)+/
     .pipe gulp.dest 'app/_sass'

-  gulp.src 'app/_includes/*.html'
+  gulp.src [
+    'app/_includes/footer.html'
+    'app/_includes/head.html'
+    'app/_includes/header.html'
+  ]
     .pipe $.debug()
     .pipe wiredep
       ignorePath: /^(\.\.\/)*\.\./


test that root is populated recursively

test that root is populated recursively

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -42,11 +42,13 @@
         assert.is nav.parent.title, 'Latin', titleMsg
         assert.is nav.parent.href, '.', hrefMsg
         assert.notOk nav.parent.active, notActiveMsg
+        assert.is nav.root.href, nav.parent.root.href, hrefMsg
         assert.end()
       test "Grandparent-#{label}", (assert) ->
         assert.is nav.parent.parent.title, 'Home', titleMsg
         assert.is nav.parent.parent.href, '..', hrefMsg
-        assert.notOk nav.root.active, notActiveMsg
+        assert.notOk nav.parent.parent.active, notActiveMsg
+        assert.is nav.root.href, nav.parent.parent.root.href, hrefMsg
         assert.end()
       test "Siblings-#{label}", (assert) ->
         for item, i in [
@@ -67,12 +69,14 @@
           assert.is current.href, item.href, hrefMsg
           assert.is current.active, item.active,
             if item.active then activeMsg else notActiveMsg
+          assert.is nav.root.href, current.root.href, hrefMsg
         assert.end()
       test "Children-#{label}", (assert) ->
         assert.notOk nav.children.length, 'Nav should have no children.'
         assert.end()
       test "Root-#{label}", (assert) ->
+        assert.plan 4     # don't know why, but otherwise tests exit w/o ending
         assert.is nav.root.title, 'Home', titleMsg
         assert.is nav.root.href, '..', hrefMsg
+        assert.is nav.root.href, nav.root.root.href, hrefMsg
         assert.notOk nav.root.active, notActiveMsg
-        assert.end()


fix jade space

fix jade space

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -167,7 +167,7 @@
     gulp.src('./source/tpl/[^-]*.jade')
         .pipe(plumber())
         .pipe(jade(
-            pretty: true
+            pretty: '    '
         ))
         .pipe(gulp.dest('./'))
         .pipe(reload(stream: true))
@@ -177,7 +177,7 @@
     gulp.src('./source/tpl/[^-]*.jade')
         .pipe(plumber())
         .pipe(jade(
-            pretty: true
+            pretty: '    '
         ))
         .pipe(gulp.dest('./'))


Fixed pause/unpause all alerts at once

Fixed pause/unpause all alerts at once

--- src/grafana.coffee
+++ src/grafana.coffee
@@ -313,10 +313,17 @@
     if not endpoint
       sendError 'No Grafana endpoint configured.', msg
       return
-    postGrafana endpoint, 'admin/pause-all-alerts', {'paused': paused}, (result) ->
-      robot.logger.debug result
-      if result.message
-        msg.send result.message
+    callGrafana endpoint, 'alerts', (alerts) ->
+      robot.logger.debug alerts
+      errored = 0
+      unless alerts.length > 0
+        return
+      for alert in alerts
+        postGrafana endpoint, "alerts/#{alert.id}/pause", {'paused': paused}, (result) ->
+          robot.logger.debug result
+          if result == false
+            errored = errored + 1
+      msg.send "Sucessfully tried to #{msg.match[1]} *#{alerts.length}* alerts.\n*Success: #{alerts.length - errored}*\n*Errored: #{errored}*"

   # Send a list of alerts
   sendAlerts = (alerts, response, msg) ->


support comparing tags and branches instead of just sha

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -93,7 +93,7 @@
           readme
         | tarball (/[^/]+)?
         | zipball (/[^/]+)?
-        | compare / [a-f0-9:]{40} \.{3} [a-f0-9:]{40}
+        | compare / ([^\.{3}]+) \.{3} ([^\.{3}]+)    # Can compare a tag, part of a commit sha, branch (with `/`)
         | deployments (/[0-9]+)?
         | deployments / [0-9]+ / statuses (/[0-9]+)?
         | hooks


converted to swith when

converted to swith when

--- find-unused-vars.coffee
+++ find-unused-vars.coffee
@@ -1,18 +1,6 @@
 esprima    = require 'esprima'
 nodeType   = require './node-type'
 parseRegex = require './parse-regex'
-
-
-processResults = (results, path) ->
-  for name of results
-    if results.hasOwnProperty name
-      stats = results[name]
-      if stats.declarations is 0 and stats.calls is 0
-        console.log 'Variable', name, 'undeclared', ' in ', path
-      else if stats.declarations > 1 and stats.calls is 0
-        console.log 'Variable', name, 'declared multiple times', ' in ', path
-      else if stats.calls is 0
-        console.log 'Variable', name, 'declared but not called', ' in ', path

 traverse = (node, func) ->
@@ -46,134 +34,146 @@

   traverse ast, (node) ->

-    # variable = new Variable
-    if node.type is nodeType.NewExpression
-      if checkIdentifier node.callee
-        addStatsEntry(node.callee.name);
-        variablesStats[node.callee.name].calls += 1
+    switch node.type
+      # variable = new Variable
+      when nodeType.NewExpression
+        if checkIdentifier node.callee
+          addStatsEntry node.callee.name
+          variablesStats[node.callee.name].calls += 1

-    # func (var1, var2, var3)
-    else if node.type is nodeType.FunctionExpression
-      if node.params.length > 0
-        for param in node.params
-          if checkIdentifier param
-            addStatsEntry param.name
-            variablesStats[param.name].declarations += 1
+      # func (var1, var2, var3)
+      when nodeType.FunctionExpression
+        if node.params.length > 0
+          for param in node.params
+            if checkIdentifier param
+              addStatsEntry param.name
+              variablesStats[param.name].declarations += 1

-    # variable = variable2()
-    else if node.type is nodeType.CallExpression and checkIdentifier node.callee
-      if node.callee.name != 'require'
-        addStatsEntry node.callee.name
-        variablesStats[node.callee.name].calls += 1
+      when nodeType.CallExpression
+        switch node.callee

-      if node.arguments.length > 0
-        for arg in node.arguments
-          if checkIdentifier arg
-            addStatsEntry arg.name
-            variablesStats[arg.name].calls += 1
+          # variable = variable2()
+          when nodeType.Identifier
+            if node.callee.name != 'require'
+              addStatsEntry node.callee.name
+              variablesStats[node.callee.name].calls += 1

-    else if node.type is nodeType.CallExpression and node.callee.type is nodeType.FunctionExpression
-      if node.arguments.length > 0
-        for arg in node.arguments
-          if checkIdentifier arg
-            addStatsEntry arg.name
-            variablesStats[arg.name].calls += 1
+            if node.arguments.length > 0
+              for arg in node.arguments
+                if checkIdentifier arg
+                  addStatsEntry arg.name
+                  variablesStats[arg.name].calls += 1

-    # variable.func()
-    else if node.type is nodeType.CallExpression and node.callee.type is nodeType.MemberExpression
-      if checkIdentifier node.callee.object
-        addStatsEntry node.callee.object.name
-        variablesStats[node.callee.object.name].calls += 1
+          when nodeType.FunctionExpression
+            if node.arguments.length > 0
+              for arg in node.arguments
+                if checkIdentifier arg
+                  addStatsEntry arg.name
+                  variablesStats[arg.name].calls += 1

-      if node.arguments.length > 0
-        for arg in node.arguments
-          if checkIdentifier arg
-            addStatsEntry arg.name
-            variablesStats[arg.name].calls += 1
+          # variable.func()
+          when nodeType.MemberExpression
+            if checkIdentifier node.callee.object
+              addStatsEntry node.callee.object.name
+              variablesStats[node.callee.object.name].calls += 1

-    # variable = variable2 -- to find variable2 used
-    else if node.type is nodeType.AssignmentExpression and  checkIdentifier node.right
-      addStatsEntry node.right.name
-      variablesStats[node.right.name].calls += 1
+            if node.arguments.length > 0
+              for arg in node.arguments
+                if checkIdentifier arg
+                  addStatsEntry arg.name
+                  variablesStats[arg.name].calls += 1

-    # variable = this.variable2 -- to find variable2 used
-    else if node.type is nodeType.AssignmentExpression and node.right.type is nodeType.MemberExpression
-      if node.right.object.type
-        addStatsEntry node.right.object.name
-        variablesStats[node.right.object.name].calls += 1
+      when nodeType.AssignmentExpression
+        switch node.right

-    # variable
-    else if node.type is nodeType.VariableDeclarator
-      addStatsEntry node.id.name
-      variablesStats[node.id.name].declarations += 1
+          # variable = variable2 -- to find variable2 used
+          when nodeType.Identifier
+            addStatsEntry node.right.name
+            variablesStats[node.right.name].calls += 1

-    # 'test' : variable
-    else if node.type is nodeType.Property
-      if checkIdentifier node.value
-        addStatsEntry node.value.name
-        variablesStats[node.value.name].calls += 1
+          # variable = this.variable2 -- to find variable2 used
+          when nodeType.MemberExpression
+            if node.right.object.type
+              addStatsEntry node.right.object.name
+              variablesStats[node.right.object.name].calls += 1

-    #  variable1 <= variable2
-    else if node.type is nodeType.ConditionalExpression
-      if checkIdentifier node.consequent
-        addStatsEntry node.consequent.name
-        variablesStats[node.consequent.name].calls += 1
+      # variable
+      when nodeType.VariableDeclarator
+        addStatsEntry node.id.name
+        variablesStats[node.id.name].declarations += 1

-    # variable2 = someVariable.variable1 - variable1 is used
-    else if node.type is nodeType.MemberExpression and node.property?
-      if checkIdentifier node.object
-        addStatsEntry node.object.name
-        variablesStats[node.object.name].calls += 1
+      # 'test' : variable
+      when nodeType.Property
+        if checkIdentifier node.value
+          addStatsEntry node.value.name
+          variablesStats[node.value.name].calls += 1

-    # if (variable)
-    else if node.type is nodeType.IfStatement
-      if checkIdentifier node.test
-        addStatsEntry node.test.name
-        variablesStats[node.test.name].calls += 1
+      #  variable1 <= variable2
+      when nodeType.ConditionalExpression
+        if checkIdentifier node.consequent
+          addStatsEntry node.consequent.name
+          variablesStats[node.consequent.name].calls += 1

-      if node.test.type is nodeType.UnaryExpression
-        addStatsEntry node.test.argument.name
-        variablesStats[node.test.argument.name].calls += 1
+      # variable2 = someVariable.variable1 - variable1 is used
+      when nodeType.MemberExpression
+        if node.property?
+          if checkIdentifier node.object
+            addStatsEntry node.object.name
+            variablesStats[node.object.name].calls += 1

-    # variable1 || variable2 or variable1 && variable2
-    else if node.type is nodeType.LogicalExpression
-      if checkIdentifier node.left
-        addStatsEntry node.left.name
-        variablesStats[node.left.name].calls += 1
+      # if (variable)
+      when nodeType.IfStatement
+        switch node.test

-      if checkIdentifier node.right
-        addStatsEntry node.right.name
-        variablesStats[node.right.name].calls += 1
+          when nodeType.Identifier
+            addStatsEntry node.test.name
+            variablesStats[node.test.name].calls += 1

-    # comparision two variable variable1 != null or variable1 != variable2
-    else if node.type is nodeType.BinaryExpression
-      if checkIdentifier node.left
-        addStatsEntry(node.left.name)
-        variablesStats[node.left.name].calls += 1
+          when nodeType.UnaryExpression
+            addStatsEntry node.test.argument.name
+            variablesStats[node.test.argument.name].calls += 1

-      if checkIdentifier node.right
-        addStatsEntry node.right.name
-        variablesStats[node.right.name].calls += 1
+      # variable1 || variable2 or variable1 && variable2
+      when nodeType.LogicalExpression
+        if checkIdentifier node.left
+          addStatsEntry node.left.name
+          variablesStats[node.left.name].calls += 1

-    # return variable
-    else if node.type is nodeType.ReturnStatement
-      if node.argument? and checkIdentifier node.argument
-        addStatsEntry node.argument.name
-        variablesStats[node.argument.name].calls += 1
+        if checkIdentifier node.right
+          addStatsEntry node.right.name
+          variablesStats[node.right.name].calls += 1

-    # for pistachio variables {{#(variable)}}
-    else if node.type is nodeType.Literal
-      if typeof node.value is 'string'
-        if node.value.match(parseRegex.pistachios)?
-          for val in node.value.match(parseRegex.pistachios)
-            reg = val.match parseRegex.DATA_REGEX
-            if reg?
-              data = parseRegex.getData reg[0]
-              addStatsEntry data
-              variablesStats[data].calls += 1
+      # comparision two variable variable1 != null or variable1 != variable2
+      when nodeType.BinaryExpression
+        if checkIdentifier node.left
+          addStatsEntry(node.left.name)
+          variablesStats[node.left.name].calls += 1

+        if checkIdentifier node.right
+          addStatsEntry node.right.name
+          variablesStats[node.right.name].calls += 1

-  processResults variablesStats, path
+      # return variable
+      when nodeType.ReturnStatement
+        if node.argument? and checkIdentifier node.argument
+          addStatsEntry node.argument.name
+          variablesStats[node.argument.name].calls += 1
+
+      # for pistachio variables {{#(variable)}}
+      when nodeType.Literal
+        if typeof node.value is 'string'
+          if node.value.match(parseRegex.pistachios)?
+            for val in node.value.match(parseRegex.pistachios)
+              reg = val.match parseRegex.DATA_REGEX
+              if reg?
+                data = parseRegex.getData reg[0]
+                addStatsEntry data
+                variablesStats[data].calls += 1
+
+  variablesAndPath =
+    stats : variablesStats
+    path : path
+  return variablesAndPath

 module.exports = analyzeCode


Update franchise text

Update franchise text

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -776,7 +776,7 @@

Материалы

-
Если вам нужен не функционал алгопрога, а только материалы — теоретические материалы, видеолекции, задачи и т.д. — то они доступны в том или ином виде свободно. Теоретические материалы, автором которых являюсь я, как правило, распространяются под той или иной свободной лицензией, вы можете их использовать. Она обычно указана в самом материале, если там лицензия не указана, то спросите меня. Материалы, автором которых являюсь не я, взяты с других сайтов, вы обычно тоже можете их использовать. Задачи взяты с сайта informatics.mccme.ru, этот сайт специально создан для проведения различных курсов по программированию, вы можете формировать свои курсы из доступных там задач и т.д. В частности, вообще все содержимое алгопрога есть на информатиксе: informatics.msk.ru/course/view.php?id=1135 — собственно, весь алгопрог вырос из этого моего курса на информатиксе.

+
Если вам нужен не функционал алгопрога, а только материалы — теоретические материалы, видеолекции, задачи и т.д. — то они доступны в том или ином виде свободно. Теоретические материалы, автором которых являюсь я, как правило, распространяются под той или иной свободной лицензией, вы можете их использовать. Она обычно указана в самом материале, если там лицензия не указана, то спросите меня. Материалы, автором которых являюсь не я, взяты с других сайтов, вы обычно тоже можете их использовать. Почти все задачи взяты с общедоступных сайтов informatics.mccme.ru и codeforces.com, на обоих этих сайтах вы можете формировать свои курсы из доступных там задач и т.д. (в частности, информатикс специально сделан для проведения различных курсов). Основное (хотя уже и подустаревшее) содержимое алгопрога есть на информатиксе: informatics.msk.ru/course/view.php?id=1135 — собственно, весь алгопрог вырос из этого моего курса на информатиксе.

Движок

@@ -784,7 +784,7 @@

Отдельный инстанс

-
Я могу поднять отдельный инстанс алгопрога для ваших учеников, с вашими материалами и задачами. Это требует отдельного обсуждения, но в принципе это возможно. Это будет платно, конкретная стоимость будет зависеть от необходимой работы по поддержке инстанса, необходимых доработок, и ожидаемого количества учеников. В качестве грубой предварительной оценки, если не требуется серьезных доработок, и ожидается 20-50 учеников, то стоимость отдельного инстанса будет составлять 500-1000 рублей с ученика в месяц.

+
Я могу поднять отдельный инстанс алгопрога для ваших учеников, с вашими материалами и задачами. Это требует отдельного обсуждения, но в принципе это возможно. Это будет платно, конкретная стоимость будет зависеть от необходимой работы по поддержке инстанса, необходимых доработок, и ожидаемого количества учеников. В качестве грубой предварительной оценки, если не требуется серьезных доработок, и ожидается 20-50 учеников, то стоимость отдельного инстанса будет составлять около 1000 рублей с ученика в месяц; если учеников будет меньше, то стоимость, конечно, будет повыше.

Занятия прямо на алгопроге по материалам алгопрога


fix bug in interpolation that manifested itself in discontinuous color progression at midnight, resolves #5

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -52,12 +52,12 @@
                 # kfs of opposite directions
                 if dir * kf1.direction >= 0
                     # no direction change since last kf
-                    if dir
+                    if dir is 1
                         t = (alt - kf1.altitude) / (2*max - kf1.altitude - kf2.altitude)
                     else
                         t = (kf1.altitude - alt) / (kf1.altitude + kf2.altitude - 2*min)
                 else
-                    if dir
+                    if dir is 1
                         t = (kf1.altitude + alt - 2*min) / (kf1.altitude + kf2.altitude - 2*min)
                     else
                         t = (2*max - kf1.altitude - alt) / (2*max - kf1.altitude - kf2.altitude)


less undefined css class

less undefined css class

--- resources/assets/coffee/react/beatmaps/search-filter.coffee
+++ resources/assets/coffee/react/beatmaps/search-filter.coffee
@@ -32,14 +32,17 @@

   render: =>
     div className: 'beatmapsets-search-filter',
-      span className:'beatmapsets-search-filter__header', @props.title
+      span className: 'beatmapsets-search-filter__header', @props.title

       div className: 'beatmapsets-search-filter__items',
         for option, i in @props.options
+          cssClasses = 'beatmapsets-search-filter__item'
+          cssClasses += ' beatmapsets-search-filter__item--active' if @selected(option.id)
+
           a
             key: i
             href: '#'
-            className: "beatmapsets-search-filter__item #{'beatmapsets-search-filter__item--active' if @selected(option.id)}"
+            className: cssClasses
             value: option.id
             'data-filter-value': option.id
             onClick: @select


Add package name/version to error message

--- src/install.coffee
+++ src/install.coffee
@@ -253,7 +253,8 @@
         else
           callback(null, cachePath)
     else
-      process.nextTick -> callback(new Error("Package not in cache"))
+      process.nextTick ->
+        callback(new Error("#{packageName}@#{packageVersion} is not in the cache"))

   # Is the package at the specified version already installed?
   #


PHP Class definitions

PHP Class definitions

--- snippets/php.cson
+++ snippets/php.cson
@@ -18,5 +18,51 @@
   'PHP snippets - array value':
     'prefix': 'php:arrValue'
     'body': """
-       '${1:name}' => '${2:value}',
+      '${1:name}' => '${2:value}',
     """
+
+  'PHP snippets - Class definition (Trait)':
+    'prefix': 'php:trait_class_definition'
+    'body': """<?php namespace App\Traits;
+
+      /**
+       * Trait ${1:TraitName}.
+       */
+      trait ${1:TraitName}
+      {
+          //
+      }
+
+    """
+
+  'PHP snippets - Class definition (Provider)':
+    'prefix': 'php:provider_class_definition'
+    'body': """<?php namespace App\Providers;
+
+        use Illuminate\Support\ServiceProvider;
+
+        class ${1:ClassName} extends ServiceProvider
+        {
+
+            /**
+             * Bootstrap any application services.
+             *
+             * @return void
+             */
+            public function boot()
+            {
+
+            }
+
+            /**
+             * Register any application services.
+             *
+             * @return void
+             */
+            public function register()
+            {
+              //
+            }
+
+        }
+    """


Remove unnecessary manual copying, add comments

--- lib/applyDependencies.coffee
+++ lib/applyDependencies.coffee
@@ -14,18 +14,25 @@
         dependencies[dependencyType][dependencyName] = resolver[dependencyType] dependencyName

     makeWrapper = (serviceName, serviceDef, dependencies) ->
-      wrapper = (args, done) ->
-        serviceDef args, done, dependencies
-      for key in Object.keys serviceDef
-        wrapper[key] = serviceDef[key]
+      # start with a copy of the service up until now
+      wrapper = serviceDef
+
+      # add a handle to our newly-resolved dependencies
       wrapper.dependencies = dependencies
+
+      # grab a reference to the raw service as-defined
       f = wrapper.callStack[wrapper.callStack.length-1]
+
+      # pseudo-right curry to inject the `dependencies` reference and
+      # ensure the signature is chain-friendly throughout `callStack`
       wrapper.callStack[wrapper.callStack.length-1] = (args, done) ->
         f args, done, wrapper.dependencies
+
       return wrapper

     wrapper = makeWrapper serviceName, serviceDef, dependencies
     wrappedServices[serviceName] = wrapper
+
   return wrappedServices

 module.exports = applyDependencies


Run autocompletion on buffer change with new text

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -39,9 +39,7 @@

     @editorView.on 'editor:path-changed', => @setCurrentBuffer(@editor.getBuffer())

-    if atom.config.get('autocomplete-plus.liveCompletion')
-      @editor.on 'screen-lines-changed', => @contentsModified()
-    else
+    unless atom.config.get('autocomplete-plus.liveCompletion')
       @editor.on 'contents-modified', => @contentsModified()

     @editorView.command 'autocomplete:next', => @selectNextItemView()
@@ -248,6 +246,13 @@
     @currentBuffer.on "saved", =>
       @buildWordList()

+    if atom.config.get('autocomplete-plus.liveCompletion')
+      @currentBuffer.on "changed", (e) =>
+        if e.newText.length
+          @contentsModified()
+        else
+          @cancel()
+
   ###
    * Defines which key we would like to use for filtering
   ###


minor typo

minor typo

--- fizzylogo_parser.coffee
+++ fizzylogo_parser.coffee
@@ -94,7 +94,7 @@
 #
 # basically you can add tabs within reason if you preserve
 # the logical alignment of things, i.e. if a line is still
-# "under the umbrella of" or "aligned" to the same line
+# "under the umbrella of" or "aligned to" the same line
 # then it's OK.
 #
 # It works this way:


修正新出现的日期处理问题

修正新出现的日期处理问题

--- src/core/entity.coffee
+++ src/core/entity.coffee
@@ -592,6 +592,8 @@
 						value = @_jsonToEntity(value, null, true, provider) if convert
 					else if value.hasOwnProperty("$data") or value.hasOwnProperty("data$")
 						value = @_jsonToEntity(value, null, true, provider)
+					else if value instanceof Date
+						# do nothing
 					else unless value instanceof _Entity or value instanceof _EntityList
 						value = @_jsonToEntity(value, null, false, provider)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -145,7 +145,7 @@
       for Module in modules
         if Module.isString?()

-          Module = Module.constantize()
+          Module = Module.constantize()
         self.extend Module(I, self)

       return self


Added 'hasPdf' function. One can check if a solution has a processed pdf this way.

--- src/corrections.coffee
+++ src/corrections.coffee
@@ -108,6 +108,9 @@
           rdb.error "Cannot finish solution, are you missing a result or are not authorized?"
           )).run(con)

+    hasPdf: (solutionId) ->
+      rdb.table("Solutions").get(solutionId).getField("processed").run con;
+
     getUserSolutions: (user) ->
       Groups.getGroupForUser(user).then (group) ->
         utils.toArray (rdb.table("Solutions").filter (s) -> s("group").eq(group.id)).run(con)


No method err!

No method err!

--- src/version-map.coffee
+++ src/version-map.coffee
@@ -132,7 +132,7 @@
       updatedRegistryIndexJSON = @updateRegistryIndexJSON(registryIndexBuffer, packageJSON, tag)
       @uploadRegistryIndex(updatedRegistryIndexJSON)
     .fail (err) ->
-      console.err "Could not update registry index!", err
+      console.log "Could not update registry index!", err
       err

 module.exports = VersionMap


Rename to retrieving

Rename to retrieving

--- src/Api.coffee
+++ src/Api.coffee
@@ -178,7 +178,7 @@

   downloadTodoLists: (cb) ->
-    debug "Downloading..."
+    debug "Retrieving..."
     @_request @endpoints["get_lists"], null, (err, lists) =>
       if err
         return cb err


Fixed an issue with date when requesting available trains.

--- src/app/pages/tasks/tsTaskEdit.directive.coffee
+++ src/app/pages/tasks/tsTaskEdit.directive.coffee
@@ -50,7 +50,7 @@

   scope.internalGetTrains = (train) ->
     params =
-      date: scope.departure_date
+      date: scope.departure_date.toLocaleDateString('en-US')
       departure: scope.departure_point
       destination: scope.destination_point
       train: train.toUpperCase()


Show dollars in UI.

Show dollars in UI.

--- client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -181,11 +181,11 @@
             cssClass      : "allocation"
             change        : @bound "allocationChanged"
             selectOptions : [
-              { title : "$ 10",  value : "1000" }
-              { title : "$ 20",  value : "2000" }
-              { title : "$ 30",  value : "3000" }
-              { title : "$ 50",  value : "5000" }
-              { title : "$ 100", value : "10000" }
+              { title : "$ 10",  value : "10" }
+              { title : "$ 20",  value : "20" }
+              { title : "$ 30",  value : "30" }
+              { title : "$ 50",  value : "50" }
+              { title : "$ 100", value : "100" }
             ]
           desc            :
             itemClass     : KDCustomHTMLView


translate z half of cube size

translate z half of cube size

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -125,7 +125,7 @@

   _getTransform: (deg) ->
-    "translate3d(0, 0, -#{ @size }px) rotate3d(1, 0, 0, #{ deg }deg)"
+    "translate3d(0, 0, -#{ @size / 2 }px) rotate3d(1, 0, 0, #{ deg }deg)"

   _setContent: (el, content) ->


smaller default sizes

smaller default sizes

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -20,9 +20,9 @@
 css[prop.toLowerCase()] = prefixProp prop for prop in ['Transform', 'Perspective']

 defaults =
-  size: 280
+  size: 200
   margin: 10
-  fontSize: 185
+  fontSize: 132
   perspective: 1000
   touchSensitivity: 1


fix mod in workspace onclick handler

fix mod in workspace onclick handler

--- lib/runtime/workspace.coffee
+++ lib/runtime/workspace.coffee
@@ -38,9 +38,10 @@

   onClick: (name) ->
     () =>
+      mod = if @mod == modules.follow then modules.current() else (@mod or 'Main')
       gotoSymbol
         word: name,
-        mod: @mod
+        mod: mod
       .then (symbols) =>
         return if symbols.error
         @ink.goto.goto symbols,


:art:

:art:

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -10,13 +10,13 @@
   constructor: ->
     @showIcon = atom.config.get('autocomplete-plus.defaultProvider') is 'Symbol'
     @snippetsSource =
-      snippetsByPrefixForScopes: -> {}
+      snippetsForScopes: -> {}

   setSnippetsSource: (@snippetsSource) ->

   getSuggestions: ({scopeDescriptor, prefix}) ->
     return unless prefix?.length
-    scopeSnippets = @snippetsSource.snippetsByPrefixForScopes(scopeDescriptor)
+    scopeSnippets = @snippetsSource.snippetsForScopes(scopeDescriptor)
     @findSuggestionsForPrefix(scopeSnippets, prefix)

   findSuggestionsForPrefix: (snippets, prefix) ->


Added the possibility to add paths

Added the possibility to add paths

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -26,7 +26,12 @@
   loadModule = (name, type) ->
     if hasModule and typeof root.udefine[type][name] is 'string'
       path = require 'path'
-      require path.join(process.cwd(), root.udefine[type][name])
+      prePath = do ->
+        if root.udefine.paths[type].base
+          root.udefine.paths[type].base
+        else
+          ''
+      require path.join(process.cwd(), prePath, root.udefine[type][name])
     else
       root.udefine[type][name]

@@ -101,6 +106,11 @@
     commonjs: hasModule
     browser: not hasModule

+  # Paths
+  root.udefine.paths =
+    commonjs:
+      base: undefined
+
   # Default configuration definition
   root.udefine.defaultConfig = ->
     root.udefine.globals.root or= rootExport


Stub out logger in tests

Stub out logger in tests

--- services/docstore/test/unit/coffee/MongoManagerTests.coffee
+++ services/docstore/test/unit/coffee/MongoManagerTests.coffee
@@ -12,6 +12,7 @@
 				db: @db = { docs: {}, docOps: {} }
 				ObjectId: ObjectId
 			'metrics-sharelatex': {timeAsyncMethod: sinon.stub()}
+			'logger-sharelatex': {log: ()->}
 		@project_id = ObjectId().toString()
 		@doc_id = ObjectId().toString()
 		@callback = sinon.stub()


add metric for active workers

add metric for active workers

--- services/document-updater/app/coffee/DispatchManager.coffee
+++ services/document-updater/app/coffee/DispatchManager.coffee
@@ -20,7 +20,9 @@
 					[list_name, doc_key] = result
 					[project_id, doc_id] = Keys.splitProjectIdAndDocId(doc_key)
 					# Dispatch this in the background
+					Metrics.gauge "processingUpdates", "+1"  # increments/decrements gauge with +/- sign
 					UpdateManager.processOutstandingUpdatesWithLock project_id, doc_id, (error) ->
+						Metrics.gauge "processingUpdates", "-1"
 						logger.error err: error, project_id: project_id, doc_id: doc_id, "error processing update" if error?
 					callback()

@@ -39,4 +41,4 @@
 	createAndStartDispatchers: (number) ->
 		for i in [1..number]
 			worker = DispatchManager.createDispatcher()
-			worker.run()+			worker.run()


Update path manipulations

Update path manipulations

--- src/objects/remote.install.coffee
+++ src/objects/remote.install.coffee
@@ -47,27 +47,29 @@
     extract2.rm()
   return
 .s (remote)->
-  return if 'node' == remote.dist
+  return if nodew.n == remote.dist
   f = folder install2, @ver
-  dst = file f, "#{remote.dist}.exe"
-  echo "Creating:", dst.bn 2
-  dst.cp file f, "node.exe"
+  unless (src = file f, remote.dist + nodew.x).y()
+    return
+  if (dst = file f, nodew.n + nodew.x).y()
+    return
+  echo "Alias:", dst.bn 2
+  src.cp dst
   return
 .s ->
   echo "Adjusting NPM prefix..."
   npmrc =
-  file install2, @ver, 'node_modules/npm/npmrc'
-  .open 8
+  file install2, v = @ver, 'node_modules/npm/npmrc'
+    .open 8
   npmrc.WriteLine """

     # <hack dirty src="#{PACKAGE.homepage}">
-    prefix=${USERPROFILE}\\.#{PACKAGE.mingzi}\\#{junction.$.bn()}
+    prefix=${USERPROFILE}\\#{junction.$.bn 2}
     # </hack>
     """
   npmrc.Close()
-  return
-.s ->
-  echo "Using:", v = @ver
+
+  echo "Using:", v
   junction v
   return
 .$()


Fix Issue 77: Cannot see fines summary bar for a user with zero fines

This is a hack to detour around the problem, which seems to be a bug with the
output of actor.user.fines.summary on EG 2.2.  The erroneous output is a null
object instead of data payload containing the mous object, when there are zero
fines.  More puzzling is the error shows up only for a patron account that has
been somehow modified or newly created on the EG 2.2 system. For a patron
account migrated from EG 1.6, there is no error.

To detour the problem, we detect when the output is a null object and build a
suitable data payload ourselves. Part of the payload includes the patron ID,
but we simplify by using zero value, since the software does not need it.

--- src/eg/services.coffee
+++ src/eg/services.coffee
@@ -165,9 +165,16 @@
 			o: o3
 			s: true

+		# Where there are zero fines for certain types of patrons, it's
+		# possible for the server response to be a null object instead of an
+		# mous data object, in which case, we replace the response with an mous
+		# containing zero fines.
 		'actor.user.fines.summary.authoritative':
 			i: i3
-			o: o3
+			o: (x) -> # should be o3 if all was well
+				ox = o1 x
+				ox = { __c: 'mous', __p: ['0.0', '0.0', '0.0', 0] } unless ox
+				fm.fieldmap ox
 			s: true

 		'actor.user.transactions.have_charge.fleshed':


call init on modules

call init on modules

--- src/modules.coffee
+++ src/modules.coffee
@@ -49,6 +49,8 @@

   load_module: (module_name) ->
     mod = require './' + module_name
+    mod.init?()
+    mod

   load_modules: (module_names) ->
     Q.resolve _.object module_names, _.map module_names, module.exports.load_module


fix hover state so mouseLeave properly removes hover from empty option

--- app/components/eui-popup.coffee
+++ app/components/eui-popup.coffee
@@ -66,7 +66,7 @@
         @set 'controller.highlighted', @get('content')

       mouseLeave: ->
-        @set 'controller.highlighted', null
+        @set 'controller.highlighted', undefined

 popup.reopenClass


Rename spec variables to be more legible

--- spec/dummy/spec/javascripts/validatable_form_spec.js.coffee
+++ spec/dummy/spec/javascripts/validatable_form_spec.js.coffee
@@ -4,32 +4,32 @@
   beforeEach ->
     @model = 'movie'
     @value = 'Gymkata'
-    @input = "<input data-validate='true' name='movie[title]' value='#{@value}' type='text'>"
-    @$form = $("<form data-validate-model='#{@model}'>#{@input}</form>")
-    @v = new MagicWord.ValidatableForm @$form
+    input = "<input data-validate='true' name='movie[title]' value='#{@value}' type='text'>"
+    @form_jquery = $("<form data-validate-model='#{@model}'>#{input}</form>")
+    @form = new MagicWord.ValidatableForm @form_jquery

   describe '#constructor', ->
     it 'sets the model to validate', ->
-      expect(@v.model).toBe(@model)
+      expect(@form.model).toBe(@model)

     it 'sets the default validation path', ->
-      expect(@v.validationRoute).toBe('/magic_word/validations')
+      expect(@form.validationRoute).toBe('/magic_word/validations')

     describe 'when MagicWord.enginePath has been set', ->
       beforeEach ->
         MagicWord.enginePath = 'wally_world'
-        @v = new MagicWord.ValidatableForm @$form
+        @form = new MagicWord.ValidatableForm @form_jquery

       it 'sets the given validation path', ->
-        expect(@v.validationRoute).toBe('/wally_world/validations')
+        expect(@form.validationRoute).toBe('/wally_world/validations')

   describe '#_inputs', ->
     it 'creates a new ValidatableInput for each input', ->
-      expect(@v._inputs().length).toBe(1)
+      expect(@form._inputs().length).toBe(1)

   describe '#_params', ->
     it 'serializes the form parameters', ->
-      expect(@v._params()).toBe("model=#{@model}&movie%5Btitle%5D=#{@value}")
+      expect(@form._params()).toBe("model=#{@model}&movie%5Btitle%5D=#{@value}")

   describe '#_allSuccess', ->
     describe 'when all inputs are valid', ->
@@ -37,38 +37,38 @@
         @response = { error: {} }

       it 'returns true', ->
-        expect(@v._allSuccess(@response)).toBe(true)
+        expect(@form._allSuccess(@response)).toBe(true)

     describe 'when there are invalid inputs', ->
       beforeEach ->
         @response = { error: { 'title': ["can't be blank"] } }

       it 'returns false', ->
-        expect(@v._allSuccess(@response)).toBe(false)
+        expect(@form._allSuccess(@response)).toBe(false)

   describe '#_isSuccess', ->
     beforeEach ->
-      @vInput = _.first @v._inputs()
+      @input = _.first @form._inputs()

     describe 'when input is invalid', ->
       beforeEach ->
         @response = { error: { 'title': ["can't be blank"] } }

       it 'returns success', ->
-        expect(@v._isSuccess(@vInput, @response)).toBe('error')
+        expect(@form._isSuccess(@input, @response)).toBe('error')

     describe 'when input is valid', ->
       beforeEach ->
         @response = { error: {}, success: [] }

       it 'returns success', ->
-        expect(@v._isSuccess(@vInput, @response)).toBe('success')
+        expect(@form._isSuccess(@input, @response)).toBe('success')

   describe '#_setMessages', ->
     beforeEach ->
-      @vInput = _.first @v._inputs()
-      spyOn @vInput.message, 'resetMessage'
-      @v._setMessages [@vInput], { error: {}, success: [] }
+      @input = _.first @form._inputs()
+      spyOn @input.message, 'resetMessage'
+      @form._setMessages [@input], { error: {}, success: [] }

     it 'sets the message on ValidationMessage', ->
-      expect(@vInput.message.resetMessage).toHaveBeenCalled()
+      expect(@input.message.resetMessage).toHaveBeenCalled()


don't let abstract classes be instantiated

--- library/nucleus/arche.coffee
+++ library/nucleus/arche.coffee
@@ -124,8 +124,10 @@
     # up for every archetyped class and therefore instance of class.
     constructor: (parameters...) ->
         msg = "Intercepting an %s event at the %s"
+        abs = "trying to instantiate an abstract class"
         evt = "missing the interceptor event specifier"
         imp = "missing the interceptor implementation"
+        throw new Error abs if @constructor.abstract()
         super if _.isFunction this.constructor.__super__
         assert runner = try this.constructor.configure()
         assert ids = @constructor.identify().underline


Fix impl misnomer

Fix impl misnomer

--- library/nucleus/watch.coffee
+++ library/nucleus/watch.coffee
@@ -168,9 +168,9 @@
     collectServices: (required) ->
         globals = _.values(required or {})
         exports = _.values(required.exports or {})
-        isClass = (s) -> _.isObject(s) and s.prototype
+        hasProto = (s) -> _.isObject(s) and s.prototype
         isService = (s) -> s::process? and s::matches?
-        isTyped = (s) -> isClass(s) and isService(s)
+        isTyped = (s) -> hasProto(s) and isService(s)
         unscoped = _.filter globals, isTyped
         services = _.filter exports, isTyped
         services = _.merge services, unscoped


Extract Default Modules

Extract Default Modules

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -165,7 +165,7 @@
   "Drawable"
   "Expirable"
   "Follow"
-  Metered, Movable, Rotatable, TimedEvents, Tween]
+  Metered", Movable, Rotatable, TimedEvents, Tween]

 ###*
 Construct an object instance from the given entity data.


only patch browser to avoid team changes (#6449)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/lib/LevelLoader.coco.coffee
+++ app/lib/LevelLoader.coco.coffee
@@ -333,7 +333,7 @@
     browser['version'] = $.browser.version if $.browser.version
     return if _.isEqual session.get('browser'), browser
     session.set 'browser', browser
-    session.patch()
+    session.save({browser}, {patch: true, type: 'PUT'})

   consolidateFlagHistory: ->
     state = @session.get('state') ? {}


Addition of Sentence about Volunteering

Addition of Sentence about Volunteering

--- app/pages/about/about-home.cjsx
+++ app/pages/about/about-home.cjsx
@@ -15,7 +15,7 @@

     You’ll be able to study authentic objects of interest gathered by researchers, like images of faraway galaxies, historical records and diaries, or videos of animals in their natural habitats. By answering simple questions about them, you’ll help contribute to our understanding of our world, our history, our Universe, and more.

-    With our wide-ranging and ever-expanding suite of projects, covering many disciplines and topics across the sciences and humanities, there's a place for anyone and everyone to explore, learn and have fun in the Zooniverse.
+    With our wide-ranging and ever-expanding suite of projects, covering many disciplines and topics across the sciences and humanities, there's a place for anyone and everyone to explore, learn and have fun in the Zooniverse. To volunteer with us, just go to the [Projects](../projects) page, choose one you like the look of, and get started.

     ### We accelerate important research by working together


use /help instead of /lab-how-to in markdown

--- app/pages/lab/landing-page.cjsx
+++ app/pages/lab/landing-page.cjsx
@@ -10,7 +10,7 @@
 counterpart.registerTranslations 'en',
   labLanding:
     title: "Zooniverse Project Builder"
-    content: "Anyone can build a Zooniverse project. Just upload your data and choose the tasks you want the volunteers to do. To find out more, read our [How to Build a Project documentation](/lab-how-to), or click the button below to get started."
+    content: "Anyone can build a Zooniverse project. Just upload your data and choose the tasks you want the volunteers to do. To find out more, read our [How to Build a Project documentation](/help), or click the button below to get started."
     buttons:
       getStarted: "Get started!"
       signIn: "Sign in"


disable filtering out appknox emails

disable filtering out appknox emails

--- app/routes/authenticated.coffee
+++ app/routes/authenticated.coffee
@@ -49,8 +49,9 @@
     try
       userName = user.get "username"
       accountId = user.get("email").split("@").pop().trim();
-      if accountId isnt "appknox.com"
-        analytics.login(userName,accountId)
+    #  Temporarily don't filter out appknox.com
+    #   if accountId isnt "appknox.com"
+    #     analytics.login(userName,accountId)

     trial = @get "trial"


use matches properly

use matches properly

--- tutor/src/components/performance-forecast/teacher-student.cjsx
+++ tutor/src/components/performance-forecast/teacher-student.cjsx
@@ -44,7 +44,7 @@

   renderHeading: ->
     students = Courses.get(@props.courseId).roster.students
-    selected = students.find(_.matches({role_id: @state.roleId }))
+    selected = students.find(matches({ role_id: @state.roleId }))
     return null unless selected
     name = <Name {...selected} />
     <div className='guide-heading'>


workers/social: remove chatconversation model

--- workers/social/lib/social/models/chatconversation/index.coffee
+++ workers/social/lib/social/models/chatconversation/index.coffee
@@ -1,162 +0,0 @@
-{Module} = require 'jraphical'
-
-module.exports = class JChatConversation extends Module
-
-  {ObjectId, ObjectRef, secure, signature} = require 'bongo'
-
-  { v4: createId } = require 'node-uuid'
-
-  KodingError = require '../../error'
-
-  {uniq} = require 'underscore'
-
-  @share()
-
-  @set
-    indexes         :
-      publicName    : 'unique'
-    sharedEvents    :
-      static        : []
-      instance      : ['updateInstance','notification']
-    sharedMethods   :
-      static        :
-        create:
-          (signature [String], Function)
-        fetch:
-          (signature String, Function)
-        fetchSome: [
-          (signature Function)
-          (signature Object, Function)
-        ]
-      instance      :
-        invite:
-          (signature String, Function)
-        leave:
-          (signature Function)
-    schema          :
-      publicName    : String
-      createdAt     :
-        type        : Date
-        default     : -> new Date
-      createdBy     : String
-      topic         : String
-      description   : String
-      avatar        : String
-      group         : ObjectId
-      invitees      :
-        type        : [String]
-        default     : -> []
-      participants  :
-        type        : [String]
-        default     : -> []
-      tags          : [ObjectRef]
-
-  @fetch = secure (client, publicName, callback)->
-
-    # Check if user logged in
-    {delegate} = client.connection
-    JAccount   = require '../account'
-    unless delegate instanceof JAccount
-      return callback new KodingError 'Access denied'
-
-    @one { publicName, participants: delegate.profile.nickname }, callback
-
-  @fetchSome = secure (client, options, callback)->
-
-    # Check if user logged in
-    {delegate} = client.connection
-    JAccount   = require '../account'
-    unless delegate instanceof JAccount
-      return callback new KodingError 'Access denied'
-
-    [callback, options] = [options, callback] unless callback
-    {nickname} = delegate.profile
-
-    options  or= limit: 20
-    selector   = {participants : nickname}
-
-    @some selector, options, callback
-
-  @create = secure (client, initialInvitees, callback)->
-
-    # Check if user logged in
-    {delegate} = client.connection
-    JAccount   = require '../account'
-    unless delegate instanceof JAccount
-      return callback new KodingError 'Access denied'
-
-    {nickname} = delegate.profile
-
-    initialInvitees or= []
-    initialInvitees.push nickname
-    initialInvitees = uniq initialInvitees
-
-    # Invitees check
-    if (initialInvitees.length is 1) and nickname in initialInvitees
-      return callback new KodingError 'You cannot chat with yourself'
-
-    # Make sure there is only one conversation with same participants
-    selector =
-      $and: [
-        {invitees : $all  : initialInvitees}
-        {invitees : $size : initialInvitees.length}
-      ]
-
-    @one selector, (err, conversation)->
-      return callback err  if err
-
-      # If conversation exists return the conversation
-      # and invite all invitees again
-      if conversation
-        conversation.invite client, invitee  for invitee in initialInvitees
-        return callback null, conversation
-
-      # If conversation not found, just create a new one
-      # and invite all invitees
-      conversation = new JChatConversation
-        publicName : createId()
-        createdBy  : nickname
-        invitees   : initialInvitees
-
-      conversation.save (err)->
-        if err then callback err
-        else
-          callback null, conversation
-          conversation.invite client, invitee  for invitee in initialInvitees
-
-  kick: secure (client, kickedMember, callback)->
-    # TODO: we need to implement this.
-
-  leave: secure (client, callback) ->
-    # Check if user logged in
-    {delegate} = client.connection
-    JAccount   = require '../account'
-    unless delegate instanceof JAccount
-      return callback new KodingError 'Access denied'
-
-    {nickname} = delegate.profile
-
-    @constructor.update {_id: @getId()}, {$pull: participants: nickname}, (err)->
-      callback err
-
-  invite: secure (client, invitee, callback)->
-
-    # Check if user logged in
-    {delegate} = client.connection
-    JAccount   = require '../account'
-    unless delegate instanceof JAccount
-      return callback new KodingError 'Access denied'
-
-    {nickname} = delegate.profile
-
-    unless nickname in @invitees
-      return callback? new KodingError "Access denied!"
-
-    @update $addToSet: {participants: invitee, invitees: invitee}, (err)=>
-      return console.error err  if err?
-
-      @emit 'notification', {
-        event       : 'chatRequest'
-        routingKey  : invitee
-        contents    : { invitee, @publicName }
-      }


Avoid naming conflict in routes

Avoid naming conflict in routes

--- routes.coffee
+++ routes.coffee
@@ -3,7 +3,7 @@
 auth   = require './controllers/auth'
 queues = require './controllers/queues'
 spots  = require './controllers/spots'
-app    = require './controllers/app'
+main   = require './controllers/app'

 router = express.Router()

@@ -23,11 +23,11 @@
 router.post   '/api/spots'            , spots.create
 router.delete '/api/spots/:key'       , spots.destroy

-router.get    '/'                     , app.app
-router.get    '/:key'                 , auth.logInFirst, app.app
+router.get    '/'                     , main.app
+router.get    '/:key'                 , auth.logInFirst, main.app

 module.exports = (app) ->
   # Register routers
-  app.use app.forceHTTPS
+  app.use main.forceHTTPS
   app.use '/api', auth.ensureAuthenticated
   app.use '/', router


Fix missed browsers

Fix missed browsers

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -287,6 +287,7 @@
 # CSS3 text-emphasis
 feature require('caniuse-db/features-json/text-emphasis'), (browsers) ->
   prefix 'text-emphasis',
+          browsers: browsers

 # Viewport at-rule
 feature require('caniuse-db/features-json/css-deviceadaptation'), (browsers) ->


Disable ctrl+s and cmd+s

Disable ctrl+s and cmd+s

--- src/Editor.coffee
+++ src/Editor.coffee
@@ -45,12 +45,15 @@

       handler.apply this, [event, @elem, caretOffset, textNode]

-    # TODO preventDefault on ctrl+S and cmd+S
     # TODO intelligently handle Up Arrow at the beginning of a section
     # TODO intelligently handle Down Arrow at the end of a section
     switch event.keyCode
       when 222 then callHandler @applySmartQuote
       when 189 then callHandler @applySmartEmDash
+      when 83
+        # Disable Cmd+S and Ctrl+S
+        if event.metaKey || event.ctrlKey
+          event.preventDefault()

   applySmartQuote: (event, elem, caretOffset, textNode) =>
     event.preventDefault()


Remove unused view

Remove unused view

--- app/assets/javascripts/rglossa/views/cwb/multiword_view.coffee
+++ app/assets/javascripts/rglossa/views/cwb/multiword_view.coffee
@@ -1,8 +0,0 @@
-App.CwbMultiwordView = Em.View.extend
-  templateName: 'cwb/multiword'
-
-  didInsertElement: ->
-    @$('.searchfield').first().focus()
-
-  focusOut: ->
-    @get('controller').updateQuery()


Add resolveDoc() method

Add resolveDoc() method

- Added a method to resolve related documents on the event during publication to ensure they're available.
- Refactored config() to allow calling it internally.

--- src/Events.coffee
+++ src/Events.coffee
@@ -1,12 +1,14 @@
 Events =

-  config: (args) ->
-    args = Setter.merge({}, args)
-
-    return if @_isConfig
-    setUpPubSub()
-    UserEventStats.config()
-    @_isConfig = true
+  config: (options) ->
+    unless @_options
+      options = Setter.merge {
+        resolveDoc: (collectionId, docId) -> null
+      }, options
+      @_options = options
+      setUpPubSub()
+      UserEventStats.config()
+    @_options

   parse: (arg) ->
     if Types.isString(arg)
@@ -134,6 +136,10 @@
           return if addedCount >= @reactiveLimit.get()
         return if addedMap[id]?
         @added(collectionId, id, event)
+        # Also publish any collections related to the event.
+        if event.doc?
+          doc = Events.config().resolveDoc(event.doc.collection, event.doc.id)
+          if doc then @added(event.doc.collection, event.doc.id, doc)
         addedMap[id] = event
         event._id = id
         addedCount++


Only call Directory::off when including deprecated APIs

--- src/project.coffee
+++ src/project.coffee
@@ -171,7 +171,9 @@
   #
   # * `projectPaths` {Array} of {String} paths.
   setPaths: (projectPaths) ->
-    rootDirectory.off() for rootDirectory in @rootDirectories
+    if includeDeprecatedAPIs
+      rootDirectory.off() for rootDirectory in @rootDirectories
+
     repository?.destroy() for repository in @repositories
     @rootDirectories = []
     @repositories = []
@@ -225,7 +227,7 @@
     if indexToRemove?
       [removedDirectory] = @rootDirectories.splice(indexToRemove, 1)
       [removedRepository] = @repositories.splice(indexToRemove, 1)
-      removedDirectory.off()
+      removedDirectory.off() if includeDeprecatedAPIs
       removedRepository?.destroy() unless removedRepository in @repositories
       @emit "path-changed" if includeDeprecatedAPIs
       @emitter.emit "did-change-paths", @getPaths()


Fix hardcoded tab stop in getscriptparameter snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -443,7 +443,7 @@

   'script parameter':
     'prefix': 'getscriptparameter'
-    'body': 'getSetting(\'SCRIPT\', \'custscript_xml_fields_customer\')'
+    'body': 'getSetting(\'SCRIPT\', \'custscript_${1:paramname}\')'

   # SuiteCommerce Advanced


Inbox: white flash does not occur after sending a message anymore. the reset/reinstantiation of the list is now handled after the new data arrives, not before/after.

--- client/app/Applications/Inbox.kdapplication/Controllers/InboxMessageListController.coffee
+++ client/app/Applications/Inbox.kdapplication/Controllers/InboxMessageListController.coffee
@@ -5,7 +5,6 @@
     @selectedMessages = {}

   loadMessages:(callback, continueLoading = no)->
-    @removeAllItems() if not continueLoading

     options =
       limit       : 10
@@ -14,6 +13,7 @@
     options.skip = @getItemCount() if continueLoading

     KD.whoami().fetchMail options, (err, messages)=>
+      @removeAllItems() if not continueLoading
       @loadMoreMessagesItem?.destroy()
       @instantiateListItems messages


Add $screen-md-* as tab stop placeholders

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -778,11 +778,11 @@

   'screen min width':
     'prefix': 'screenmin'
-    'body': '@media screen and (min-width: $1) {\n\t$2\n}$3'
+    'body': '@media screen and (min-width: ${1:$screen-md-min}) {\n\t$2\n}$3'

   'screen max width':
     'prefix': 'screennmax'
-    'body': '@media screen and (max-width: $1) {\n\t$2\n}$3'
+    'body': '@media screen and (max-width: ${1:$screen-md-max}) {\n\t$2\n}$3'

   '@media print':
     'prefix': 'print'


change request for cp932 encoding

change request for cp932 encoding

--- src/request.coffee
+++ src/request.coffee
@@ -1,8 +1,18 @@
 {Promise} = require 'es6-promise'
 request = require 'request'
+{Iconv} = require 'iconv'

-module.exports = (params) ->
+promisedRequest = (params) ->
   new Promise (resolve, reject) ->
     request params, (err, res) ->
       return reject(err) if err?
       resolve res
+
+module.exports = (params) ->
+  params.encoding = 'binary'
+  promisedRequest params
+  .then (res) ->
+    iconv = new Iconv 'CP932', 'UTF-8//TRANSLIT//IGNORE'
+    body = new Buffer res.body, 'binary'
+    res.body = iconv.convert(body).toString()
+    res


show basemap

show basemap

--- src/app.coffee
+++ src/app.coffee
@@ -206,7 +206,7 @@
       layerName: "Road Map"
       apiKey: bingApiKey
       bingType: "Road"
-      #active: true
+      active: true
   ,
     new app.models.BingLayer
       id: "bingAerial"


vm.set() supports PV args.

vm.set() supports PV args.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -309,6 +309,7 @@
   for param, fields of {
     'name_label'
     'name_description'
+    'PV_args'
   }
     continue unless param of params

@@ -338,6 +339,9 @@
   #
   # Note: static_min ≤ dynamic_min ≤ dynamic_max ≤ static_max
   memory: { type: 'integer', optional: true }
+
+  # Kernel arguments for PV VM.
+  PV_args: { type: 'string', optional: true }
 }

 set.resolve = {


Update documentation to begin matching atom style

--- src/bijous.coffee
+++ src/bijous.coffee
@@ -9,10 +9,10 @@
 #
 # file - The path of the file
 #
-# Examples
-#
-#   getModuleName('/modules/module1.js')
-#   # => 'module1'
+# ```coffee
+# getModuleName('/modules/module1.js')
+# # => 'module1'
+# ```
 #
 # Returns the module's name without extension
 getModuleName = (file) ->
@@ -39,6 +39,8 @@
 # Public: An asynchronous module loader. Searches out modules within a file
 # system using [Klect](https://github.com/awnist/klect) and supplies an
 # asynchronous means of initializing them.
+#
+# ## Module Definition
 #
 # Bijous modules are synonymous with klect bundles and can be retrieved and used
 # in much the same ways. What bijous adds is the ability to `load` and `require`
@@ -52,52 +54,58 @@
 # occurred, and can optionally return an object that represents the result of
 # the module.
 #
-# Examples
-#
-#   # We can assume that the following four lines describes the module being
-#   # loaded and resides in a file called *modules/mobule1.coffee*
-#   exports = module.exports = (context, modules, done) ->
-#     # do something ...
-#     done null,
-#       app: express()
-#
-#   # We can assume the rest of the code below lives in a separate file that
-#   # has access to the module specified above
-#   Bijous = require 'bijous'
-#   # Loads all modules
-#   bijous = new Bijous
-#   bijous.load (err, modules) ->
-#     # Access the results of module1
-#     console.log modules.module1
-#
-#   Bijous = require 'bijous'
-#   # Overrides the cwd option and loads all modules relative to it
-#   bijous = new Bijous
-#     cwd: '~/modules'
-#   bijous.load (err, modules) ->
-#     # Access the results of module1
-#     console.log modules.module1
-#
-#   Bijous = require 'bijous'
-#   # Overrides the bundles option and loads all modules accordingly
-#   bijous = new Bijous
-#     bundles: 'modules/!(router)'
-#   bijous.load (modules) ->
-#     # Access the results of module1
-#     console.log modules.module1
-#
-#   Bijous = require 'bijous'
-#   # Overrides the bundles option with multi bundles and loads all modules
-#   # one bundle's modules accordingly
-#   bijous = new Bijous
-#     bundles:
-#       server: 'modules/!(router)'
-#       web: ['webModules/*', 'adminModules/*']
-#   bijous.load (err, modules) ->
-#     # Access the results of module1, notice it is namespaced by the bundle
-#     # name
-#     console.log modules.server.module1
-#     console.log modules.web.module2
+# ```coffee
+# # We can assume that the following four lines describes the module being
+# # loaded and resides in a file called *modules/mobule1.coffee*
+# exports = module.exports = (context, modules, done) ->
+#   # do something ...
+#   done null,
+#     app: express()
+# ```
+#
+# ## Module Loading
+#
+# ```coffee
+# # We can assume the rest of the code below lives in a separate file that
+# # has access to the module specified above
+# Bijous = require 'bijous'
+#
+# # Loads all modules
+# bijous = new Bijous
+#
+# bijous.load (err, modules) ->
+#   # Access the results of module1
+#   console.log modules.module1
+#
+# # Overrides the cwd option and loads all modules relative to it
+# bijous = new Bijous
+#   cwd: '~/modules'
+#
+# bijous.load (err, modules) ->
+#   # Access the results of module1
+#   console.log modules.module1
+#
+# # Overrides the bundles option and loads all modules accordingly
+# bijous = new Bijous
+#   bundles: 'modules/!(router)'
+#
+# bijous.load (modules) ->
+#   # Access the results of module1
+#   console.log modules.module1
+#
+# # Overrides the bundles option with multi bundles and loads all modules
+# # one bundle's modules accordingly
+# bijous = new Bijous
+#   bundles:
+#     server: 'modules/!(router)'
+#     web: ['webModules/*', 'adminModules/*']
+#
+# bijous.load (err, modules) ->
+#   # Access the results of module1, notice it is namespaced by the bundle
+#   # name
+#   console.log modules.server.module1
+#   console.log modules.web.module2
+# ```
 class Bijous extends EventEmitter
   # Public: The default bundle configuration for klect. This configuration
   # describes how all modules are to be found. (default: `modules/*`).
@@ -109,15 +117,17 @@
   # bear it's name are not namespaced when received from the `load` callback.
   # (default: `_`)
   #
-  # Examples
-  #
-  #   Bijous = require 'bijous'
-  #   bijous = new Bijous
-  #     bundles: 'modules/!(router)'
-  #   bijous.load (err, modules) ->
-  #     # Since our bundle does not have a name the default bundle name is used
-  #     # and it's results are not namespaced
-  #     console.log modules.module1
+  # ```coffee
+  # Bijous = require 'bijous'
+  #
+  # bijous = new Bijous
+  #   bundles: 'modules/!(router)'
+  #
+  # bijous.load (err, modules) ->
+  #   # Since our bundle does not have a name the default bundle name is used
+  #   # and it's results are not namespaced
+  #   console.log modules.module1
+  # ```
   @defaultBundleName: '_'

   # Public: Instantiates a new bijous loader.
@@ -143,16 +153,18 @@
   #
   # bundle - The bundle name to use when retrieving modules. (optional)
   #
-  # Examples
-  #
-  #   Bijous = require 'bijous'
-  #   # List all modules
-  #   bijous = new Bijous
-  #     bundles:
-  #       public: 'modules/public/*'
-  #       private: 'modules/private/*'
-  #   allBundles = bijous.list()
-  #   onlyPublicBundles = bijous.list 'public'
+  # ```coffee
+  # Bijous = require 'bijous'
+  #
+  # # List all modules
+  # bijous = new Bijous
+  #   bundles:
+  #     public: 'modules/public/*'
+  #     private: 'modules/private/*'
+  #
+  # allBundles = bijous.list()
+  # onlyPublicBundles = bijous.list 'public'
+  # ```
   #
   # Returns an array of klect bundles
   list: (bundle) ->
@@ -168,17 +180,18 @@
   #
   # bundle - The bundle name to use when retrieving modules. (optional)
   #
-  # Examples
-  #
-  #   Bijous = require 'bijous'
-  #   # Require all modules
-  #   bijous = new Bijous
-  #     bundles:
-  #       public: 'modules/public/*'
-  #       private: 'modules/private/*'
-  #
-  #   allBundles = bijous.require()
-  #   onlyPublicBundles = bijous.require 'public'
+  # ```coffee
+  # Bijous = require 'bijous'
+  #
+  # # Require all modules
+  # bijous = new Bijous
+  #   bundles:
+  #     public: 'modules/public/*'
+  #     private: 'modules/private/*'
+  #
+  # allBundles = bijous.require()
+  # onlyPublicBundles = bijous.require 'public'
+  # ```
   #
   # Returns the modules exported by calling `require` on each of the bundle
   #   files; each module object contains a module's `name`, the corresponding
@@ -208,36 +221,37 @@
   #            errors that have occurred, the second contains the results of all
   #            of the loaded modules.
   #
-  # Examples
-  #
-  #   Bijous = require 'bijous'
-  #   # Loads all modules
-  #   bijous = new Bijous
-  #   bijous.load()
-  #
-  #   Bijous = require 'bijous'
-  #   # Loads only modules belonging to the module1 bundle
-  #   bijous = new Bijous
-  #     bundles:
-  #       module1: ['modules/module1']
-  #   bijous.load 'module1'
-  #
-  #   Bijous = require 'bijous'
-  #   # Loads all modules and executes a callback once all are loaded
-  #   bijous = new Bijous
-  #   bijous.load (error, results) ->
-  #     throw error if error
-  #     console.log results
-  #
-  #   Bijous = require 'bijous'
-  #   # Loads only modules belonging to the *module1* bundle and executes a
-  #   # callback once all are loaded
-  #   bijous = new Bijous
-  #     bundles:
-  #       bundle1: ['modules/module1']
-  #   bijous.load 'bundle1', (error, modules) ->
-  #     throw error if error
-  #     console.log modules.bundle1.module1
+  # ```atom
+  # Bijous = require 'bijous'
+  #
+  # # Loads all modules
+  # bijous = new Bijous
+  # bijous.load()
+  #
+  # # Loads only modules belonging to the module1 bundle
+  # bijous = new Bijous
+  #   bundles:
+  #     module1: ['modules/module1']
+  #
+  # bijous.load 'module1'
+  #
+  # # Loads all modules and executes a callback once all are loaded
+  # bijous = new Bijous
+  #
+  # bijous.load (error, results) ->
+  #   throw error if error
+  #   console.log results
+  #
+  # # Loads only modules belonging to the *module1* bundle and executes a
+  # # callback once all are loaded
+  # bijous = new Bijous
+  #   bundles:
+  #     bundle1: ['modules/module1']
+  #
+  # bijous.load 'bundle1', (error, modules) ->
+  #   throw error if error
+  #   console.log modules.bundle1.module1
+  # ```
   #
   # Returns nothing
   load: (bundle, callback) ->