atom whitespace config

atom whitespace config

--- atom/config.cson
+++ atom/config.cson
@@ -6,6 +6,10 @@
     'atom-dark-ui'
     'base16-tomorrow-dark-theme'
   ]
+'whitespace':
+  'ensureSingleTrailingNewline': true
+  'removeTrailingWhitespace': true
+  'ignoreWhitespaceOnCurrentLine': false
 'exception-reporting':
   'userId': '2849baab-b308-ebf1-8b8d-e7152846e14b'
 'welcome':


atom: fix inability to see or manipulate space indentation directly

--- atom/config.cson
+++ atom/config.cson
@@ -40,11 +40,12 @@
 		telemetryConsent: "limited"
 		themes: [
 			"contrast-ui"
-			"monokai-blackboard"
+			"monokai-dark"
 		]
 	"dark-flat-ui":
 		accentColor: "green"
 	editor:
+		atomicSoftTabs: false
 		fontSize: 16
 		invisibles: {}
 		showIndentGuide: true


Remove Python environment variables from Atom init

These environment variables are no longer necessary to set on Atom
initialization since I switched from the atom-script package to the
OS X Terminal for running my Python scripts.

--- atom/init.coffee
+++ atom/init.coffee
@@ -24,7 +24,3 @@
 process.env.PATH = '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
 if process.env.VIRTUAL_ENV_NAME
   activateVirtualenv()
-# Ensure that Python correctly outputs Unicode characters
-process.env.PYTHONIOENCODING = 'utf_8'
-# Prevent Python from generating bytecode (.pyc, .pyo) files
-process.env.PYTHONDONTWRITEBYTECODE = '1'


for loops instead

for loops instead

--- apps/articles/client/show.coffee
+++ apps/articles/client/show.coffee
@@ -92,21 +92,19 @@

       # Resize each img, maintaining aspect ratio, until the row fits the
       # width of the container
-      i = 0
-      until widthDiff() < 1
+      for i in [0..999]
         for img in imgs
           resizeHeight img, dir
           break if widthDiff() < 1
-        break if i += 1 > 999
+        break if widthDiff() < 1

       # Resize down to accomodate padding
-      i = 0
       totalWhitespace = imgs.length * 30
-      until imgsWidth() <= $list.width() - totalWhitespace
+      for i in [0..999]
         for img in imgs
           resizeHeight img, -1
           break if imgsWidth() <= $list.width() - totalWhitespace
-        break if i += 1 > 999
+        break if imgsWidth() <= $list.width() - totalWhitespace

       # Round off sizes
       for img in imgs


add profile.uploadedImage to the users publication

--- server/publications.coffee
+++ server/publications.coffee
@@ -3,6 +3,7 @@
     roles: 1
     'profile.editing': 1
     'profile.name': 1
+    'profile.uploadedImage': 1
     'profile.role': 1
     'profile.submitted': 1
     'services.google.picture': 1


Remove unused component

Remove unused component

--- src/components/qa/book.cjsx
+++ src/components/qa/book.cjsx
@@ -1,29 +0,0 @@
-React = require 'react'
-
-{EcosystemsStore} = require '../../flux/ecosystems'
-
-LoadableItem = require '../loadable-item'
-{ReferenceBookActions, ReferenceBookStore} = require '../../flux/reference-book'
-ReferenceBookWrapper = require './reference-book-wrapper'
-
-Book = React.createClass
-
-  contextTypes:
-    router: React.PropTypes.func
-
-  render: ->
-    {courseId} = @context.router.getCurrentParams()
-    book = EcosystemsStore.getBook(id: parseInt(courseId, 10))
-
-

-
{book.title}

-
-      <LoadableItem
-        id={courseId}
-        store={ReferenceBookStore}
-        actions={ReferenceBookActions}
-        renderItem={ -> <ReferenceBookWrapper {...book} /> }
-      />
-

-
-module.exports = Book


Instead of rendering getting started page, just redirect to it

It would be nice to allow to just render getting started page, but
because of the way we manage layouts, it's hard to get it running
without weird bugs popping up now and then. This should be easier to
achieve once the templates are cleaned up to use better laout
management.

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -60,7 +60,7 @@
         return true

     renderNoOwnedRepos: ->
-      @render('no_owned_repos', outlet: 'main', into: 'application')
+      @transitionTo('getting_started')

     renderFirstSync: ->
       @renderFirstSync()
@@ -122,12 +122,11 @@
   setupController: ->
     $('body').attr('id', 'home')
     @container.lookup('controller:repos').activate()
-    @container.lookup('controller:application').connectLayout 'home'
+    @container.lookup('controller:application').connectLayout 'simple'
     @_super.apply(this, arguments)

   renderTemplate: ->
     @render 'top', outlet: 'top'
-    @render 'repos',   outlet: 'left'
     @_super.apply(this, arguments)

 Travis.FirstSyncRoute = Travis.Route.extend


Delete useless modulo

Delete useless modulo

--- app/annotation/annotation.cjsx
+++ app/annotation/annotation.cjsx
@@ -122,7 +122,7 @@

       i = @state.zoomImageCurrent
       if e.keyCode is 37
-        i = (i - 1) % @props.previews.length
+        i -= 1
         if i < 0
           i = @props.previews.length - 1
       else


Delete conversation.coffee

Delete conversation.coffee

--- scripts/conversation.coffee
+++ scripts/conversation.coffee
@@ -1,31 +0,0 @@
-var Conversation = require('hubot-conversation');
-module.exports = function (robot) {
-
-    var switchBoard = new Conversation(robot);
-
-    robot.respond(/clean the house/, function (msg) {
-        var dialog = switchBoard.startDialog(msg);
-
-        msg.reply('Sure, where should I start? Kitchen or Bathroom');
-        dialog.addChoice(/kitchen/i, function (msg2) {
-            msg2.reply('On it boss!');
-        });
-        dialog.addChoice(/bathroom/i, function (msg2) {
-            msg.reply('Do I really have to?');
-            dialog.addChoice(/yes/, function (msg3) {
-                msg3.reply('Fine, Mom!');
-            })
-        });
-    });
-
-    robot.respond(/jump/, function (msg) {
-        var dialog = switchBoard.startDialog(msg);
-        msg.reply('Sure, How many times?');
-
-        dialog.addChoice(/([0-9]+)/i, function (msg2) {
-            var times = parseInt(msg2.match[1], 10);
-            for (var i = 0; i < times; i++) {
-                msg.emote("Jumps"); //We can use the original message too.
-            }
-        });
-});


consolidate MKTScript events

consolidate MKTScript events

--- src/tex-writer.coffee
+++ src/tex-writer.coffee
@@ -1793,17 +1793,34 @@
     #.......................................................................................................
     return null

+
+#-----------------------------------------------------------------------------------------------------------
+@MKTX.$consolidate_mktscript_events = ( S ) ->
+  # remark      = MD_READER._get_remark()
+  collector   = []
+  first_meta  = null
+  return $ ( event, send ) =>
+    if select event, '.', 'mktscript'
+      [ type, name, text, meta, ] = event
+      first_meta                 ?= meta
+      collector.push text
+    else
+      # debug '83726', collector
+      if collector.length > 0
+        send [ '.', 'mktscript', ( collector.join '\n' ), ( copy first_meta ), ]
+        first_meta        = null
+        collector.length  = 0
+      send event
+
 #-----------------------------------------------------------------------------------------------------------
 @MKTX.$mktscript = ( S ) =>
   return PIPEDREAMS.$async ( event, send, end ) =>
     #.......................................................................................................
     if event?
-      if select event, '.', [ 'mktscript', 'mktscript-trim', ]
+      if select event, '.', 'mktscript'
         [ type, name, mktscript, meta, ]   = event
-        trim        = ( name is 'mktscript-trim' )
         send stamp event
         tex_source  = await ( promisify @tex_from_md.bind @ ) mktscript, { bare: yes, }
-        tex_source  = tex_source.trim() if trim
         send [ 'tex', tex_source, ]
         send.done()
       else
@@ -1908,6 +1925,7 @@
     .pipe @MKTX.SH.$spawn                                   S
     .pipe @MKTX.CALL.$call_await                            S
     .pipe @MKTX.CALL.$call_stream                           S
+    .pipe @MKTX.$consolidate_mktscript_events               S
     .pipe @MKTX.$mktscript                                  S
     # .pipe D.$observe ( event ) -> info ( CND.grey '--------->' ), ( CND.blue event[ 0 ] + event[ 1 ] )
     # .pipe D.$observe ( event ) -> info '23993', ( CND.grey '--------->' ), jr event


Yet another very minor cleanup

Yet another very minor cleanup

--- src/touchclick.coffee
+++ src/touchclick.coffee
@@ -21,10 +21,10 @@
     $targetEl = $ target
     $touchclickEl = $targetEl.closest "*[data-touchclick='true']"

-    if ($touchclickEl.length)
-      return $touchclickEl
-
-    return $targetEl
+    if $touchclickEl.length
+      $touchclickEl
+    else
+      $targetEl

   touchstart = (e) ->
     $touchclickEl = getTouchclickEl e.target
@@ -59,7 +59,7 @@
     $touchclickEl.removeClass activeClass

   events = (type) ->
-    $el = $(this)
+    $el = $ this

     if window.navigator.msPointerEnabled
       $el[type] "MSPointerDown", touchstart


activity: fix other browsers are not effected with own like/unlike action bug

--- client/activity/lib/views/activitylikelink.coffee
+++ client/activity/lib/views/activitylikelink.coffee
@@ -15,8 +15,6 @@

     super options, data

-    { @isInteracted } = data.interactions.like
-
     @update()

     data
@@ -26,7 +24,8 @@

   setLikeState: (state) ->
-    @isInteracted = state
+    { like } = @getData().interactions
+    like.isInteracted = state
     @update()

@@ -56,9 +55,11 @@

   update: ->

+    { isInteracted } = @getData().interactions.like
+
     @setTemplate @pistachio()

-    if @isInteracted
+    if isInteracted
     then @setClass 'liked'
     else @unsetClass 'liked'

@@ -72,5 +73,6 @@

   pistachio: ->

-    "#{if @isInteracted then "Unlike" else "Like"}"
+    { isInteracted } = @getData().interactions.like
+    "#{if isInteracted then "Unlike" else "Like"}"


Log error as it is

Log error as it is

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -202,7 +202,7 @@
     remote.api.JWorkspace.createDefault machine.uid, (err, workspace) ->

       if err
-        console.error "User Environment  Data Provider: #{err}"
+        console.error "User Environment  Data Provider:", err

       callback err, workspace


Add toBeShown and toBeHiding matchers for Jasmine

To reduce redundancy in tests

--- cms/static/coffee/spec/views/feedback_spec.coffee
+++ cms/static/coffee/spec/views/feedback_spec.coffee
@@ -1,3 +1,10 @@
+beforeEach ->
+    @addMatchers
+        toBeShown: ->
+            @actual.hasClass("is-shown") and not @actual.hasClass("is-hiding")
+        toBeHiding: ->
+            @actual.hasClass("is-hiding") and not @actual.hasClass("is-shown")
+
 describe "CMS.Views.Alert as base class", ->
     tpl = readFixtures('alert.underscore')

@@ -19,7 +26,7 @@
     it "renders the template", ->
         view = new CMS.Views.Alert({model: @model})
         expect(view.$(".action-close")).toBeDefined()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
+        expect(view.$('.wrapper')).toBeShown()
         expect(view.$el).toContainText(@model.get("title"))
         expect(view.$el).toContainText(@model.get("message"))

@@ -30,8 +37,7 @@
         view.$(".action-close").click()

         expect(CMS.Views.Alert.prototype.hide).toHaveBeenCalled()
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

 describe "CMS.Views.Prompt", ->
     tpl = readFixtures('prompt.underscore')
@@ -114,63 +120,52 @@
     it "a minShown view should not hide too quickly", ->
         view = new CMS.Views.Notification({model: @model, minShown: 1000})
         expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()

         # call hide() on it, but the minShown should prevent it from hiding right away
         view.hide()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()

         # wait for the minShown timeout to expire, and check again
         @clock.tick(1001)
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

     it "a maxShown view should hide by itself", ->
         view = new CMS.Views.Notification({model: @model, maxShown: 1000})
         expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()

         # wait for the maxShown timeout to expire, and check again
         @clock.tick(1001)
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

     it "a minShown view can stay visible longer", ->
         view = new CMS.Views.Notification({model: @model, minShown: 1000})
         expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()

         # wait for the minShown timeout to expire, and check again
         @clock.tick(1001)
         expect(CMS.Views.Notification.prototype.hide).not.toHaveBeenCalled()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()

         # can now hide immediately
         view.hide()
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

     it "a maxShown view can hide early", ->
         view = new CMS.Views.Notification({model: @model, maxShown: 1000})
         expect(CMS.Views.Notification.prototype.show).toHaveBeenCalled()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()

         # wait 50 milliseconds, and hide it early
         @clock.tick(50)
         view.hide()
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

         # wait for timeout to expire, make sure it doesn't do anything weird
         @clock.tick(1000)
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

     it "a view can have both maxShown and minShown", ->
         view = new CMS.Views.Notification({model: @model, minShown: 1000, maxShown: 2000})
@@ -178,17 +173,13 @@
         # can't hide early
         @clock.tick(50)
         view.hide()
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()
         @clock.tick(1000)
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()

         # show it again, and let it hide automatically
         view.show()
         @clock.tick(1050)
-        expect(view.$('.wrapper')).toHaveClass("is-shown")
-        expect(view.$('.wrapper')).not.toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeShown()
         @clock.tick(1000)
-        expect(view.$('.wrapper')).not.toHaveClass("is-shown")
-        expect(view.$('.wrapper')).toHaveClass("is-hiding")
+        expect(view.$('.wrapper')).toBeHiding()


added support for inline templates

added support for inline templates

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -91,9 +91,11 @@
 				# handle template
 				if typeof(exported.template) == 'string' && exported.template.match(/\./)
 					export_path = "#{module_path}/.crystal/template/#{exported.template}"
-					if not fs.existsSync export_path
-						throw new Error "Unknown export path (#{export_path}) for export (#{export_name}) in module (#{module_name})"
-					module_config.exports[export_name].template = fs.readFileSync(export_path, 'utf8')
+					if fs.existsSync export_path
+						template = fs.readFileSync(export_path, 'utf8')
+					else
+						template = exported.template
+					module_config.exports[export_name].template = template

 				# handle transformer
 				if typeof(exported.transformer) == 'string' && exported.transformer.match(/\./)


Remove shouldComponentUpdate hook

Remove shouldComponentUpdate hook

Will be replaced by manual update logic anyway.

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/cursor-component.coffee
+++ src/cursor-component.coffee
@@ -12,6 +12,3 @@
     WebkitTransform = "translate(#{left}px, #{top}px)"

     div className: 'cursor', style: {height, width, WebkitTransform}
-
-  shouldComponentUpdate: (newProps) ->
-    not isEqualForProperties(newProps, @props, 'pixelRect', 'defaultCharWidth')


[BETA] For WebSQL Support

[BETA] For WebSQL Support

--- src/device.module.db.coffee
+++ src/device.module.db.coffee
@@ -7,13 +7,6 @@
 ###

 Device.DB = do(dvc = Device) ->
-  ERROR = lng.Constants.ERROR
-  CONFIG =
-    name: "lungo_db"
-    version: "1.0"
-    size: 65536
-    schema: []
-
   db = null

   ###
@@ -21,13 +14,20 @@
   @method init
   @param {object} Configuration for the Database
   ###
-  init = (db_config) ->
-    CONFIG = lng.Core.mix(CONFIG, db_config)
-    db = openDatabase(CONFIG.name, CONFIG.version, CONFIG.name, CONFIG.size)
+  init = (name, version, size, schema) ->
+    db = openDatabase name, version, name, size
     if db
-      _createSchema()
+      schema_len = schema.length
+      return unless schema_len
+      i = 0
+      while i < schema_len
+        current = schema[i]
+        _regenerateTable current
+        _createTable current.name, current.fields
+        i++
+
     else
-      throw new Error(ERROR.DATABASE)
+      throw new Error "[ERROR] Device.DB: DB unnecesible"

   ###
@@ -96,26 +96,12 @@
   @param {Function} Callback when the process is complete
   ###
   execute = (sql, callback) ->
-    lng.Core.log 1, "lng.Data.Sql >> " + sql
     db.transaction (transaction) ->
       transaction.executeSql sql, [], ((transaction, rs) ->
         _callbackResponse callback, rs
       ), (transaction, error) ->
         transaction.executedQuery = sql
         _throwError.apply null, arguments
-
-
-  _createSchema = ->
-    schema = CONFIG.schema
-    schema_len = schema.length
-    return  unless schema_len
-    i = 0
-
-    while i < schema_len
-      current = schema[i]
-      _regenerateTable current
-      _createTable current.name, current.fields
-      i++

   _createTable = (table, fields) ->
     sql_fields = ""
@@ -156,7 +142,7 @@
     execute "INSERT INTO " + table + " (" + fields + ") VALUES (" + values + ")"

   _throwError = (transaction, error) ->
-    throw new Error(ERROR.DATABASE_TRANSACTION + error.code + ": " + error.message + " \n Executed query: " + transaction.executedQuery)
+    throw new Error("[ERROR] Device.DB " + error.code + ": " + error.message + " \n Executed query: " + transaction.executedQuery)

   init: init
   select: select


Tune water splashes

Tune water splashes

--- app/scripts/components/entities/WaterSplash.coffee
+++ app/scripts/components/entities/WaterSplash.coffee
@@ -7,36 +7,37 @@
       @requires 'WebGL, Color, Tween'

   waterSplash: (attr) ->
+    #console.log 'splash', attr.size
     options =
-      maxParticles: 150
-      size: attr.size / 2
+      maxParticles: 5 * attr.size
+      size: attr.size / 1.5
       sizeRandom: 4
-      speed: attr.size / 7
+      speed: attr.size / 20
       speedRandom: 0.2
       # Lifespan in frames
-      lifeSpan: attr.size
+      lifeSpan: attr.size / 3
       lifeSpanRandom: 7
       # Angle is calculated clockwise: 12pm is 0deg, 3pm is 90deg etc.
-      angle: 0
+      angle: 350
       angleRandom: 20
-      startColour: [50, 50, 170, 1]
+      startColour: [40, 40, 170, 1]
       startColourRandom: [0, 0, 0, 0]
-      endColour: [255, 255, 255, 0.1]
+      endColour: [205, 205, 205, 0.1]
       endColourRandom: [30, 30, 30, 0.1]
       # Only applies when fastMode is off, specifies how sharp the gradients are drawn
       sharpness: 10
       sharpnessRandom: 5
       # Random spread from origin
-      spread: attr.size
+      spread: attr.size / 3
       # How many frames should this last
       #duration: -1
       duration: 900 / Crafty.timer.FPS()
       # Will draw squares instead of circle gradients
       #fastMode: false,
       fastMode: no
-      gravity: { x: 0, y: 0.15 }
+      gravity: { x: 0, y: 0.25 }
       # sensible values are 0-3
-      jitter: 1
+      jitter: 0
     cleanupDelay = (options.lifeSpan + options.lifeSpanRandom) * Crafty.timer.FPS()

     if @explosionMode is 'particles'


Adding in line about how to get your allowance uploaded (#3460)

* Adding in line about how to get your allowance uploaded

Adding in a line about how to contact the Zooniverse team to get your subject upload allowance increased. I had gotten an email from an astronomer interested in using the Zooniverse project builder, but didn't know how to get the allowance increased, so adding a line here like what is on the policies page asking the project owner to contact the Zoonvierse team so it's clear. Not sure if I did the href correctly.

* shorter sentence

shorter sentence

* more edits

shorted sentence

* fixed " to '

* ensure the link renders as html

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -131,14 +131,17 @@
   subjectLimitMessage: (project_subject_count, user) ->
     "The project has " + project_subject_count + " uploaded subjects. " +
     "You have uploaded " + user.uploaded_subjects_count + " subjects from an " +
-    "allowance of " + user.subject_limit + ". Your uploaded subject count is the tally of all subjects (including those deleted) that your account has uploaded through the project builder or Zooniverse API."
+    "allowance of " + user.subject_limit + ". Your uploaded subject count is the tally of all subjects " +
+    "(including those deleted) that your account has uploaded through the project builder or Zooniverse API."

   render: ->

{@props.subjectSet.display_name} #{@props.subjectSet.id}

A subject is a unit of data to be analyzed. A subject can include one or more images that will be analyzed at the same time by volunteers. A subject set consists of a list of subjects (the “manifest”) defining their properties, and the images themselves.

Feel free to group subjects into sets in the way that is most useful for your research. Many projects will find it’s best to just have all their subjects in 1 set, but not all.

-
{@subjectLimitMessage(@props.project.subjects_count, @props.user)}

+

+        {@subjectLimitMessage(@props.project.subjects_count, @props.user)} Please  contact us to request changes to your allowance.
+

We strongly recommend uploading subjects in batches of 500 - 1,000 at a time.

       <form onSubmit={@handleSubmit}>


key error

key error

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -281,7 +281,7 @@
     {title: 'Toutes', sortby: {'campaign__is_current_setup':'true', "unselected": 'false'}, count:0 },
     {title: 'Refusées', sortby: {'campaign__is_current_setup':'true', "unselected":'true'}, count:0 },
     {title: 'Non finalisées', sortby: {'campaign__is_current_setup':'true', "unselected": 'false', "application_completed": 'false'}, count:0},
-    {title: 'En attente de validation', sortby: {'campaign__is_current_setup':'true', "unselected": 'false', "application_completed":'true', "application_complete": 'fasle'}, count:0},
+    {title: 'En attente de validation', sortby: {'campaign__is_current_setup':'true', "unselected": 'false', "application_completed":'true', "application_complete": 'false'}, count:0},
     {title: 'Visées', sortby: {'campaign__is_current_setup':'true', "unselected": 'false', "application_complete":'true'}, count:0},
     {title: 'Entretien : liste d\'attente', sortby: {'campaign__is_current_setup':'true', "unselected": 'false', "wait_listed_for_interview":'true'}, count:0},
     {title: 'Entretien : Selectionnés', sortby: {'campaign__is_current_setup':'true', "unselected": 'false', "selected_for_interview":'true'}, count:0},


candidatures export infos fix

candidatures export infos fix

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -352,7 +352,7 @@
         row = []
         for col in line.querySelectorAll("td, th")
             t = $(col).text().replace(/\s\s+/g,' ')
-            # t = $(col).text().replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n|

|
|
|
)/g, ' ')
+            t = $(col).text().replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n|

|
|
|
)/g, ' ')
             row.push(t)
         csv.push(row.join(";"))


set radio values to Casey

set radio values to Casey

--- app/scripts/robot/radio.coffee
+++ app/scripts/robot/radio.coffee
@@ -34,7 +34,7 @@
         value: num
         })

-    radioInit = (radioAddr = "0013A2004090CAA3", portPath = "/dev/tty.usbserial-A700eETt") ->
+    radioInit = (radioAddr = "0013A20040A580C4", portPath = "/dev/ttyUSB0") ->
       SerialPort = requireNode("serialport").SerialPort
       serialPort = new SerialPort(portPath, baudrate: 57600, false)

@@ -46,7 +46,7 @@
       radio = requireNode('kyleradio')
       _ndl3Radio = new radio.Radio()
       _ndl3Radio.connectXBee(radioAddr, serialPort)
-
+
     return {
       init: ->
         unless _init
@@ -92,4 +92,4 @@
           .replace(/chname/, channel)
         return true
     }
-])+])


Minor

Minor

--- src/chromeExtension/components/020-story.cjsx
+++ src/chromeExtension/components/020-story.cjsx
@@ -343,7 +343,7 @@
       <Severity/>
       <Indent level={level}/>
       <CaretOrSpace/>
-      <Icon icon="copy"/>
+      <Icon icon="copy" disabled style={{color: 'currentColor'}}/>
       <ColoredText text={msg}/>
       <Time
         t={record.tLastRepetition}


Fix error if not violation

Fix error if not violation

--- app/static/coffee/views.coffee
+++ app/static/coffee/views.coffee
@@ -624,11 +624,12 @@
                     el: el
                 view.render()

-            @minOffset = @$el.find('.js-first-violation').offset().top
-            @minOffset -= @$el.find('.js-violations-menu').height()
-            $(window).scroll $.proxy @onScroll,  @
-            @$el.find('pre').addClass 'prettyprint'
-            prettyPrint()
+            if @$el.find('.js-first-violation').length
+                @minOffset = @$el.find('.js-first-violation').offset().top
+                @minOffset -= @$el.find('.js-violations-menu').height()
+                $(window).scroll $.proxy @onScroll,  @
+                @$el.find('pre').addClass 'prettyprint'
+                prettyPrint()

         onScroll: ->
             if $(window).scrollTop() > @minOffset


Stepped dashboard ok date

Stepped dashboard ok date

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -16,14 +16,14 @@
     tableNames = (await Promise.all(tableNamePromises)).map((table) -> table.name)
     res.table.tables = tableNames
     return res
-
+
 expandResults = (results) ->
     promises = []
     for result in results
         promises.push(expandResult(result))
     res = await Promise.all(promises)
     return res
-
+
 runDashboardQuery = (key, query, result) ->
     limit = 20
     if key == "ok"
@@ -42,20 +42,19 @@
     for r in cfr
         result["cf"].push(expandCfResult(r))
     result["cf"] = await Promise.all(result["cf"])
-
+
 export default dashboard = () ->
-    queries =
+    queries =
         # remember that months start from 0
-        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 7, 12)}},
+        ok: {ok: 1, lastSubmitTime: {$gt: new Date(2017, 7, 18)}},
         wa: {solved: 0, ok: 0, ignored: 0, attempts: {$gt: 0}},
         ig: {ignored: 1},
         ac: {solved: 1}
     result = {}
     promises = []
     for key, query of queries
-        query["total"] = 1
+        query["total"] = 1
         promises.push(runDashboardQuery(key, query, result))
     promises.push(runCfQuery(result))
     await Promise.all(promises)
     return result
-


Make dependents a property

Make dependents a property

--- src/property.coffee
+++ src/property.coffee
@@ -80,17 +80,6 @@
     else
       @get()

-  # Find all properties which are dependent upon this one
-  dependents: ->
-    deps = []
-    findDependencies = (name) =>
-      for property in @object._s_properties
-        if property.name not in deps and name in property.localDependencies
-          deps.push(property.name)
-          findDependencies(property.name)
-    findDependencies(@name)
-    deps
-
   registerGlobal: ->
     unless @object["_s_glb_" + @name]
       def @object, "_s_glb_" + @name, value: true, configurable: true
@@ -99,7 +88,7 @@
         globalDependencies[subname].push({ @object, subname, name, type, dependency: @name })

   trigger: ->
-    names = [@name].concat(@dependents())
+    names = [@name].concat(@dependents)
     changes = {}
     changes[name] = @object[name] for name in names
     @object.changed?.trigger?(changes)
@@ -115,6 +104,17 @@

   one: (fun) ->
     @event.one(fun)
+
+  # Find all properties which are dependent upon this one
+  def @prototype, "dependents", get: ->
+    deps = []
+    findDependencies = (name) =>
+      for property in @object._s_properties
+        if property.name not in deps and name in property.localDependencies
+          deps.push(property.name)
+          findDependencies(property.name)
+    findDependencies(@name)
+    deps

   def @prototype, "listeners", get: ->
     @event.listeners


More debug output

More debug output

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -59,6 +59,13 @@
         logger.log "Compare submits ", @_id, other._id, @comments.length > 0, @outcome == "AC" or @outcome == "IG" or @outcome == "DQ", @force, @user == other.user, @problem == other.problem, outcomeType(@outcome) == outcomeType(other.outcome), @source.replace("\r", "") == other.source.replace("\r", ""), Math.abs(@time - other.time)
         logger.log "s1=`#{Buffer.from(@source)}` `#{Buffer.from(@source.replace("\r", ""))}`"
         logger.log "s2=`#{Buffer.from(other.source)}`, `#{Buffer.from(other.source.replace("\r", ""))}`"
+        s1 = @source.replace("\r", "")
+        s2 = other.source.replace("\r", "")
+        logger.log "s1,s2.length=", s1.length, s2.length
+        for i in [0..s1.length - 1]
+            if s1.charAt(i) != s2.charAt(i)
+                print("!!!", i)
+                break
     if @comments.length > 0
         return false
     if @outcome == "AC" or @outcome == "IG" or @outcome == "DQ"


Debugging

Debugging

--- src/reactive.coffee
+++ src/reactive.coffee
@@ -874,6 +874,8 @@
         toAdd = toNodes(contents)
         (elt.appendChild node) for node in toAdd
         console.log("    -updateSVGContents firstChild #{elt.firstChild}")
+        console.log("    -updateSVGContents childNodes #{elt.childNodes}")
+        console.log("    -updateSVGContents childNodes[0] #{elt.childNodes[0]}")
       else if _.isString(contents)
         updateSVGContents(elt, [contents])
       else


Fix for auth modal not updating redirect when switching modes (#2211)

--- src/desktop/components/auth_modal/view.coffee
+++ src/desktop/components/auth_modal/view.coffee
@@ -43,24 +43,14 @@
     mode = mode: options.mode if options.mode
     @state = new State mode

-    currentPath = location.pathname
-    postLoginPath = @redirectTo or currentPath
-    postSignupPath = @redirectTo or '/personalize'
-    if (options.mode is 'signup' or options.mode is 'register') and !@destination
-      @destination = currentPath unless @redirectTo
-
     @templateData = _.extend {
       context: @context
       signupIntent: @signupIntent
       copy: @renderCopy(options.copy)
-      redirectTo: switch @state.get 'mode'
-        when 'login' then postLoginPath
-        when 'signup' then postSignupPath
-        when 'register' then postSignupPath
-        else @redirectTo or '/'
+      redirectTo: @currentRedirectTo()
     }, options?.userData

-    @listenTo @state, 'change:mode', @reRender
+    @listenTo @state, 'change:mode', @updateTemplateAndRender
     @listenTo @state, 'change:mode', @logState
     @on 'rerendered', @initializeMailcheck

@@ -72,6 +62,24 @@

     Cookies.set 'postSignupAction', JSON.stringify(@afterSignUpAction) if @afterSignUpAction
     Cookies.set('destination', @destination, expires: 60 * 60 * 24) if @destination
+
+  currentRedirectTo: ->
+    currentPath = location.pathname
+    postLoginPath = @redirectTo or currentPath
+    postSignupPath = @redirectTo or '/personalize'
+    mode = @state.get 'mode'
+    if (mode is 'signup' or mode is 'register') and !@destination
+      @destination = currentPath unless @redirectTo
+
+    switch mode
+      when 'login' then postLoginPath
+      when 'signup' then postSignupPath
+      when 'register' then postSignupPath
+      else @redirectTo or '/'
+
+  updateTemplateAndRender: ->
+    @templateData.redirectTo = @currentRedirectTo()
+    @reRender()

   initializeMailcheck: ->
     if @state.get('mode') is 'register'


making AppView server-safe

making AppView server-safe

--- shared/base/app_view.coffee
+++ shared/base/app_view.coffee
@@ -1,6 +1,6 @@
 BaseView = require('./view')

-hasPushState = window.pushState?
+hasPushState = window?.pushState?

 module.exports = class AppView extends BaseView
   el: 'body'


switched line charts back to white background because of font display bug with google charts

--- public/dreamcatcher/controllers/charts_controller.coffee
+++ public/dreamcatcher/controllers/charts_controller.coffee
@@ -27,9 +27,9 @@
     chart.draw(data, {
       title: "#{json.data['title']}",
       width: 600, height: 300,
-      backgroundColor: 'black',
-      legendTextStyle: {color: 'white'},
-      titleTextStyle: {color: 'white'}
+      backgroundColor: 'white',
+      legendTextStyle: {color: 'black'},
+      titleTextStyle: {color: 'black'}
     })


enh: made geometry generation more snappy, but not perfect yet

--- 3d/lines-app.coffee
+++ 3d/lines-app.coffee
@@ -82,7 +82,7 @@
     i = 0

     #We must process all tubes in 75% of the chunk flyby time
-    minTimePerTube = 10 #100 tubes/second
+    minTimePerTube = 1000/30 #100 parts/second
     chunkFlybyTime = @chunkLen / stepsPerMs

     #How many pieces to split blueprint to
@@ -91,17 +91,27 @@
     nPieces = Math.min(nPieces, blueprint.length)

     tubesPerPart = Math.ceil(blueprint.length / nPieces) | 0
+    nPieces = Math.ceil(blueprint.length / tubesPerPart) | 0
     processingDelay = completionTime / nPieces
-
+
+    timeStart = Date.now()
+
     processPart = =>
+      if i + tubesPerPart < blueprint.length-1
+        setTimeout processPart, processingDelay
+
       for j in [0...Math.min(blueprint.length-1-i, tubesPerPart)] by 1
         tubeBp = blueprint[i]
         tubeGeom = @createTube tubeBp
         tube = new THREE.Mesh tubeGeom, @materials[i]
         chunk.add tube
         i+=1
-      if i < blueprint.length-1
-        setTimeout processPart, processingDelay
+
+      #if i < blueprint.length-1
+      #  setTimeout processPart, processingDelay
+      #else
+      #  dt = Date.now() - timeStart
+      #  console.log "Expected completion time: #{completionTime}, actual: #{dt}"

     processPart()
     #console.log "Received chunk!"


edit saml profile form

edit saml profile form

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -8,6 +8,7 @@
 # - reset password
 # - verify email
 # - edit profile
+# - edit saml profile
 #
 # Each of these screens has some differences. We try to keep these differences
 # clearly documented in the render method so that the method doesn't get
@@ -29,7 +30,7 @@

   auth = fetch 'auth'

-  if auth.form && auth.form == 'edit profile'
+  if auth.form && auth.form in ['edit profile', 'edit saml profile']
     loadPage '/'

   reset_key auth
@@ -159,6 +160,23 @@
             SPAN style: {color: 'green'}, t("Updated successfully")
         ]

+      # The EDIT SAML PROFILE form
+      # SAML users are directed here after log in.
+      # We don't render an enclosing header and border,
+      # and add feedback when the user is updated.
+      when 'edit saml profile'
+        if avatar_field = @avatarInput()
+          avatar_field = ["#{t('pic_prompt')}:", avatar_field]
+
+        [ @headerAndBorder null, t('Your Profile'),
+            @body [
+              ["#{t('name_prompt')}:", @inputBox('name', t('full_name'))],
+              avatar_field].concat @userQuestionInputs()
+          @submitButton(t('Update'))
+          if @local.saved_successfully
+            SPAN style: {color: 'green'}, t("Updated successfully")
+        ]
+
       # The RESET PASSWORD form
       when 'reset password'
         [ @headerAndBorder null, t('Reset Your Password'),
@@ -235,7 +253,7 @@
                 position: 'relative'
               task

-            if auth.form != 'edit profile'
+            if auth.form not in ['edit profile', 'edit saml profile']

               SPAN
                 style:
@@ -500,7 +518,7 @@
         padding: '5px 10px'
         fontSize: if browser.is_mobile then 36 else 18
         display: 'inline-block'
-      value: if auth.form == 'edit profile' then @local[name] else null
+      value: if auth.form in ['edit profile','edit saml profile'] then @local[name] else null
       name: "user[#{name}]"
       key: "#{name}_inputBox"
       placeholder: placeholder
@@ -651,7 +669,7 @@
     auth = fetch('auth')

     if auth.ask_questions && auth.form in \
-          ['edit profile', 'create account', 'create account via invitation']
+          ['edit profile', 'create account', 'create account via invitation', 'edit saml profile']
       questions = customization('auth_questions')
     else
       questions = []
@@ -770,10 +788,10 @@
       current_user.trying_to = auth.form

       save current_user, =>
-        if auth.form in ['create account', 'edit profile']
+        if auth.form in ['create account', 'edit profile', 'edit saml profile']
           ensureCurrentUserAvatar()

-        if auth.form == 'edit profile'
+        if auth.form in ['edit profile', 'edit saml profile']
           @local.saved_successfully = current_user.errors.length == 0 && (@local.errors or []).length == 0

         # Once the user logs in, we will stop showing the log-in screen


removed helpers

removed helpers

--- scripts/slackbot-examples.coffee
+++ scripts/slackbot-examples.coffee
@@ -11,21 +11,12 @@
   get_username = (response) ->
     "@#{response.message.user.name}"

-  # helper method to get ID of sender of the message
-  get_user_id = (response) ->
-    response.message.user.id
-
   # helper method to get channel of originating message
   get_channel = (response) ->
     if response.message.room == response.message.user.name
       "@#{response.message.room}"
     else
       "##{response.message.room}"
-
-  # helper method to get ID of channel of the originating message
-  get_channel_id = (response) ->
-    get_channel.slice(1)
-

   ###
   # basic example of a fully qualified command


use warn instead of console.warn

use warn instead of console.warn

--- client/app/MainApp/routes.coffee
+++ client/app/MainApp/routes.coffee
@@ -1,13 +1,12 @@
 do ->
   mainController = KD.getSingleton 'mainController'

-
   handleRoute =(groupId, route)->
-    console.log 'invoking a route by group id...'
+    log 'invoking a route by group id...'

   notFound =(route)->
     KDRouter.addRoute route, ->
-      console.warn "Contract warning: shared route #{route} is not implemented."
+      warn "Contract warning: shared route #{route} is not implemented."

   routes =


fixme

fixme

--- src/template.coffee
+++ src/template.coffee
@@ -147,7 +147,7 @@
         # write it back so builder can use it to instantiate a new tag
         @xml.Tag = @xml.opts.Tag = ExtendedTag

-        ##
+        ## FIXME replace this with new checker middleware api when possible
         # add self closing tag behavior
         # some of the html tags dont need a closing tag
         end_tag = Tag::end


test that builder can be add to a tag

test that builder can be add to a tag

--- src/test/xml.coffee
+++ src/test/xml.coffee
@@ -481,4 +481,25 @@
             @on('end', done)

-
+    inception: (æ) ->
+        xml = new Builder
+        xml.on 'end', ->
+            æ.deepEqual copyarr(results.add),   ['add',   "root", "childA", "childB", "childC"]
+            æ.deepEqual copyarr(results.close), ['close', "root", "childA", "childC", "childB"]
+            æ.done()
+
+        results = add:['add'], close:['close']
+
+        xml.on 'add', (par, el) ->
+            results.add.push el.name
+        xml.on 'close', (el) ->
+            results.close.push el.name
+
+        sub = new Builder
+        root = xml.tag('root').add(sub).end()
+        sub.$tag('childA')
+        sub.tag('childB').tag('childC').up().end()
+        sub.end()
+        xml.end()
+
+


oops~! my bad, tried calling Array#some in IE FFS

--- src/timeline.coffee
+++ src/timeline.coffee
@@ -123,7 +123,7 @@

     out = []

-    for i in arr when not out.some((item) -> test item, i)
+    for i in arr when not some.call(out, (item) -> test item, i)
         out.push(i)

     out


Fixed Shark.term bug #70

Fixed Shark.term bug #70

--- public/scripts/backbone-schedule/app.coffee
+++ public/scripts/backbone-schedule/app.coffee
@@ -7,7 +7,7 @@
 	initialize = () ->
 		Shark.terms = new Terms(CS.terms)
 		Shark.school = new School(CS.school)
-		Shark.term = Shark.terms.get (Shark.school.get 'currentTerm')._id
+		Shark.term = Shark.terms.get Shark.school.get 'currentTerm'
 		Router.initialize @

   #Return an object with the intialize method


add a sparse mongo index for finding packs

--- services/track-changes/app/coffee/MongoManager.coffee
+++ services/track-changes/app/coffee/MongoManager.coffee
@@ -353,6 +353,8 @@
 		db.docHistory.ensureIndex { doc_id: 1, v: 1 }, { background: true }
 		# For finding all updates that affect a project (getProjectUpdates meta.end_ts < before
 		db.docHistory.ensureIndex { project_id: 1, "meta.end_ts": 1 }, { background: true }
+		# For finding all packs that affect a project (use a sparse index so only packs are included)
+		db.docHistory.ensureIndex { project_id: 1, "pack.0.meta.end_ts": 1, "meta.end_ts": 1}	, { background: true, sparse: true }
 		# For finding updates that don't yet have a project_id and need it inserting
 		db.docHistory.ensureIndex { doc_id: 1, project_id: 1 }, { background: true }
 		# For finding project meta-data


Parse body from github correctly, add logging

--- server/controllers/deploy.coffee
+++ server/controllers/deploy.coffee
@@ -5,9 +5,15 @@
   return refParts[refParts.length-1]

 exports.index = (req, res) ->
-  unless getBranchFromRef(req.body.ref) is "deploy"
+  parsedPayload = JSON.parse(req.body.payload)
+
+  console.log "Got deploy message from #{parsePayload.ref}"
+  unless getBranchFromRef(parsedPayload.ref) is "deploy"
+    console.log "Ignoring, only deploys on pushes from deploy"
     return res.send 500, "Only commits from deploy branch are accepted"

-  cp.fork('bin/setup.coffee')
+  console.log "Forking deploy.coffee"
+  cp.fork('bin/deploy.coffee')
+  console.log "Forked!"

   res.send 200


is not is preferred to != in coffeescript

--- server/controllers/deploy.coffee
+++ server/controllers/deploy.coffee
@@ -6,7 +6,7 @@
 tagRefersToServer = (tag, serverTags) ->
   deploymentTags = tag.split('-')[0]
   deploymentTags = deploymentTags.split(',')
-  return _.intersection(deploymentTags, serverTags).length != 0
+  return _.intersection(deploymentTags, serverTags).length is not 0

 getIpFromRequest = (req) ->
   req.headers['x-real-ip'] or req.connection.remoteAddress


<controller> Driver - More explicit error message for delete.

--- server/controllers/driver.coffee
+++ server/controllers/driver.coffee
@@ -83,6 +83,6 @@
 module.exports.delete = (req, res) ->
 	req.driver.destroy (err) ->
 		if err?
-			res.send error: 'Server error while deleting Driver', 500
+			res.send error: err, 500
 		else
 			res.send success: true, 200


no need to log full page contents.

no need to log full page contents.

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -476,7 +476,6 @@
       #if e then return res.e e
       if status is 404
         res.send(page, status)
-      log page
       # Using Coffee-Scripts implicit returns we assign page.story to the
       # result of a list comprehension by way of a switch expression.
       try
@@ -515,7 +514,6 @@
         page.journal.push({type: "fork", site: action.fork})
         delete action.fork
       page.journal.push(action)
-      log page
       pagehandler.put(req.params[0], page, (e) ->
         if e then return res.e e
         res.send('ok')


Include code language in parent email

Include code language in parent email

--- server/users/user_handler.coffee
+++ server/users/user_handler.coffee
@@ -264,6 +264,9 @@
         address: email
       email_data:
         name: req.user.get('name') or ''
+    if codeLanguage = req.user.get('aceConfig.language')
+      codeLanguage = codeLanguage[0].toUpperCase() + codeLanguage.slice(1)
+      emailParams['email_data']['codeLanguage'] = codeLanguage
     sendwithus.api.send emailParams, (err, result) =>
       if err
         log.error "sendwithus one-time email error: #{err}, result: #{result}"


Removed timeout die fix on timesheet.

Removed timeout die fix on timesheet.

--- js/datetime/timesheet/timesheet.coffee
+++ js/datetime/timesheet/timesheet.coffee
@@ -425,12 +425,6 @@
         _item = @createTimeSheetItem( _value )
         _items.push( _item ) if _item

-  #Delete timesheet item after 300ms. Prevent flashing
-  _delTimeSheetItem:( _item ) ->
-    setTimeout( (->
-      _item.die()
-    ), 300 )
-
   ###
   # =Description
   # Create a new timeSheetItems if it hasn't been done already,
@@ -441,7 +435,7 @@
     @timeSheetItems = [] unless @timeSheetItems?
     if @timeSheetItems.length > 0
       for _timeSheetItem in @timeSheetItems
-        @_delTimeSheetItem( _timeSheetItem )
+        _timeSheetItem.die()
       @timeSheetItems = []

   # finds the index in the array which contains most sequential items


[BUGFIX] Infinite loop fixed

[BUGFIX] Infinite loop fixed

In some cases...

Grid layout does not correctly compute initial column width correctly.

Calling `.adjustLayout` on `didInsertElement` event can fix the problem or cause an infinite loop.

This patch seems to address both scenarios.

--- addon/components/ella-grid.coffee
+++ addon/components/ella-grid.coffee
@@ -123,10 +123,10 @@
     Compute listing dimensions after addition to DOM to ensure column widths
     are calculated and rendered correctly.

-    @method _didInsertElement
+    @method __didInsertElement
   ###
-  _didInsertElement: Ember.on('didInsertElement', ->
-    @adjustLayout()
+  __didInsertElement: Ember.on('didInsertElement', ->
+    Ember.run.once(@, 'adjustLayout')
   )

 `export default EllaGridComponent`


Updated machine model.

Updated machine model.

--- brainfuck.coffee
+++ brainfuck.coffee
@@ -91,8 +91,8 @@
 # ## Machine Model
 environment = (0 for i in [0...100]) # Originally 30000 instead of 100
 pointer = 0
-output = (string) -> console.log string
-input = prompt
+output = (byte) -> console.log String.fromCharCode(byte)
+input = () -> prompt "Input byte value: "

 # ## Evaluate

@@ -116,10 +116,9 @@
     when DEC_BYTE
       environment[pointer]--
     when OUTPUT_BYTE
-      output(String.fromCharCode(environment[pointer]))
+      output environment[pointer]
     when INPUT_BYTE
-      byteValue = input("Input byte value: ")
-      environment[pointer] = byteValue
+      environment[pointer] = input()

 isCommand = (child) -> typeof child is 'string'
 isBlock = (child) -> typeof child is 'object'
@@ -135,7 +134,7 @@

 # We presume no one in their right mind would want to type brainfuck directly
 # into the interpreter, but rather specify the path of a file to execute.
-filename = prompt("Enter path to input program: ")
+filename = prompt("Enter path of input program: ")
 program = fs.readFileSync(filename, 'utf8')

 log "-*- Input Program -*-"


Set the type inside the directive.

Set the type inside the directive.

--- app/assets/asset/pdfViewer.coffee
+++ app/assets/asset/pdfViewer.coffee
@@ -10,6 +10,7 @@
       objectTag = document.createElement('object')
       objectTag.setAttribute('data', $scope.asset.downloadRoute(true))
       objectTag.setAttribute('width', '80%')
+      objectTag.setAttribute('type', $scope.asset.format.mimetype)
       pdfElement.appendChild(objectTag)
       return


Environments: error handling

Environments: error handling

--- client/app/Applications/Environments.kdapplication/views/domains/domainsroutingview.coffee
+++ client/app/Applications/Environments.kdapplication/views/domains/domainsroutingview.coffee
@@ -85,28 +85,28 @@
     options         = {hostnameAlias}

     domain.bindVM options, (err)=>
+      listItem.hideLoader()
       unless err
-        listItem.hideLoader()
         @disconnectedVMController.removeItem listItem
         @connectedVMController.addItem vm
         domain.hostnameAlias.push hostnameAlias
       else
-        notifyError err
+        notifyError err.message or err

   unbindVM:(listItem)->
-    domain  = @getData()
-    vm      = listItem.getData()
-    {hostnameAlias}  = vm
-    options = {hostnameAlias}
+    domain          = @getData()
+    vm              = listItem.getData()
+    {hostnameAlias} = vm
+    options         = {hostnameAlias}

     domain.unbindVM options, (err)=>
+      listItem.hideLoader()
       unless err
-        listItem.hideLoader()
         @connectedVMController.removeItem listItem
         @disconnectedVMController.addItem vm
         domain.hostnameAlias.splice domain.hostnameAlias.indexOf(hostnameAlias), 1
       else
-        notifyError err
+        notifyError err.message or err

   fetchVms:->


remove read in context

remove read in context

Former-commit-id: c65afb8764932f71b2eecf4f9c77b0fb9e5640b6 [formerly f86eeba7186950a82dbf97547bf9689e5903e417] [formerly 1af9cc34bd669c0e9563c675751caa08f08805f1 [formerly cad29df6198749e5e1a16f064f71110a695d555e]] [formerly 6717930cc645b62ba04d1ad623acf823589b25a5 [formerly 79a05f7bcf762173dd9b9cef0828741bac906d22] [formerly af8e9f7c6b6498acdaca8d61664f8a21849425b4 [formerly af8e9f7c6b6498acdaca8d61664f8a21849425b4 [formerly c8b8165c7daba3590a3daf01a13881b87b951687]]]]
Former-commit-id: 50b52513b16b32222ac0ec0b7d56d20510b602bc [formerly 67cdef0d2553f8bc2900680ac508b36069ec2567] [formerly 5af8072bd5ac535945ef72e3007213dee9c87803 [formerly 960d1cf0caea7dc5eabb69214c4cfed78e412753]]
Former-commit-id: 44aef702bd04538be7e5736f7c45bd1cdd05887c [formerly ba48f10e4995b680fb6e52d0ff5c66c2f67be1c3]
Former-commit-id: 3857ad1cf421dcf4b7187fb3151fb3b8f70e8424
Former-commit-id: 8ad533ea4d9374514a7503e7c160191aa62f352e

--- @client/admin.coffee
+++ @client/admin.coffee
@@ -576,14 +576,6 @@
           DIV style:{fontSize: 12, marginLeft: 73},
             "by #{author.name}"

-            if (item.status != 0 && item.status != 2 && class_name != 'Proposal') || class_name == 'Comment'
-              [SPAN style: {fontSize: 8, padding: '0 4px'}, " • "
-              A
-                target: '_blank'
-                href: "/#{proposal.slug}/?selected=#{point.key}"
-                style: {textDecoration: 'underline'}
-                'Read in context']
-
             if !moderatable.hide_name && !@local.messaging
               [SPAN style: {fontSize: 8, padding: '0 4px'}, " • "
               A


spec for BaseObject

spec for BaseObject

--- spec/javascripts/object_model/base_object_spec.js.coffee
+++ spec/javascripts/object_model/base_object_spec.js.coffee
@@ -1,10 +1,35 @@
-# include brancusi/object_model/base_object
+#= require brancusi/object_model/base_object

 describe 'brancusi.BaseObject', ->

+  class FooModule
+    @bar: 'bar'
+    baz: 'baz'
+
   describe '.include', ->
+    it "invokes brancusi.include on the calling class, passing the given module", ->
+      spyOn(brancusi, 'include')
+
+      class Foo extends brancusi.BaseObject
+        @include FooModule

+      expect(brancusi.include).toHaveBeenCalledWith(Foo, FooModule)
+
   describe '.extend', ->
+    it "invokes brancusi.extend on the calling class, passing the given module", ->
+      spyOn(brancusi, 'extend')
+
+      class Foo extends brancusi.BaseObject
+        @extend FooModule
+
+      expect(brancusi.extend).toHaveBeenCalledWith(Foo, FooModule)

   describe '#decorate', ->
-
+    it "invokes brancusi.decorate on the calling instance, passing the given decorator and arguments", ->
+      spyOn(brancusi, 'decorate')
+
+      foo = new (class extends brancusi.BaseObject)
+      foo.decorate(FooModule, 'arg1')
+
+      expect(brancusi.decorate).toHaveBeenCalledWith(foo, FooModule, 'arg1')
+


Remove comments

Remove comments

--- spec/views/cherry-pick-select-branch-view-spec.coffee
+++ spec/views/cherry-pick-select-branch-view-spec.coffee
@@ -5,8 +5,6 @@
 describe "CherryPickSelectBranch view", ->
   beforeEach ->
     @view = new CherryPickSelectBranch(repo, ['head1', 'head2'], 'currentHead')
-    # spyOn(git, 'cmd').andCallFake ->
-      # Promise.reject 'blah'

   it "displays a list of branches", ->
     expect(@view.items.length).toBe 2


Remove redundant line.

Remove redundant line.

This lines was never needed.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -623,7 +623,6 @@
   unregisterFrame: unregisterFrame
   frameFocused: handleFrameFocused
   nextFrame: (request) -> BackgroundCommands.nextFrame 1, request.frameId
-  mainFrame: BackgroundCommands.mainFrame
   updateScrollPosition: handleUpdateScrollPosition
   copyToClipboard: copyToClipboard
   pasteFromClipboard: pasteFromClipboard


Remote: fetchName: no need to re-fetch group instance

--- client/app/lib/remote.coffee
+++ client/app/lib/remote.coffee
@@ -1,3 +1,4 @@
+debug             = (require 'debug') 'remote'
 globals           = require 'globals'
 kd                = require 'kd'
 kookies           = require 'kookies'
@@ -24,8 +25,16 @@
     fetchName      : do ->
       cache = {}
       (nameStr, callback) ->
+        debug 'fetchName called with', nameStr
+
         if cache[nameStr]?
-          return callback null, cache[nameStr], name
+          debug 'found in cache, returning'
+          return callback null, cache[nameStr]
+
+        if nameStr is (group = globals.currentGroup).slug
+          debug 'currentGroup is requested, reviving'
+          return callback null, [ bongoInstance.revive group ]
+
         @api.JName.one { name: nameStr }, (err, name) =>
           if err then return callback err
           else unless name?
@@ -58,6 +67,7 @@
           async.parallel queue, =>
             @emit 'modelsReady'
             cache[nameStr] = models
+            debug 'models ready', err, models, name
             callback err, models, name
     mq: null


enable change parent scope object

enable change parent scope object

--- coffee/directives/uzHover.coffee
+++ coffee/directives/uzHover.coffee
@@ -6,10 +6,10 @@

       element.on 'mouseenter', ->
         scope.$apply ->
-          scope[attrs.uzHover] = true
+          scope.$eval(attrs.uzHover + " = true")

       element.on 'mouseleave', ->
         scope.$apply ->
-          scope[attrs.uzHover] = false
+          scope.$eval(attrs.uzHover + " = false")

   }


fix: minor fixes

fix: minor fixes

--- app/components/file-header.coffee
+++ app/components/file-header.coffee
@@ -254,12 +254,7 @@
       appAction = @get "manualscan.appAction"

       loginRequired =  @get "manualscan.loginRequired"
-
       userRoles = @get "manualscan.userRoles"
-      userRoles.forEach (userRole) ->
-        userRole.userRole.forEach (role) ->
-          delete role.id
-
       vpnRequired =  @get "manualscan.vpnRequired"
       vpnAddress = @get "manualscan.vpnDetails.address"
       vpnPort = @get "manualscan.vpnDetails.port"


typo in French locale

typo in French locale

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -404,7 +404,7 @@
       "contact create success"   : "%{contact} a été ajouté(e) à vos contacts"
       "contact create error"     : "L'ajout à votre carnet d'adresses a
                                         échoué : {error}"
-      "message contact creation"    : "Voulez vous ajouter  %{contact} à votre carnet d'adresse ?"
+      "message contact creation"    : "Voulez vous ajouter %{contact} à votre carnet d'adresse ?"

       # GMail security
       "gmail security tile": "Sécurité Gmail"


fixing a GoofyMistake™

fixing a GoofyMistake™

back to previous version for line #77

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -74,7 +74,7 @@
     "ask for assistance":"Demandez de l'aide"
     "logout":"déconnexion"
     "welcome to your cozy":"Bienvenue sur votre Cozy !"
-    "you have no apps":"Vous n'avez pas encore d'applications, vous devriez en installer."
+    "you have no apps":"Vous n'avez pas encore d'applications, vous devriez"
     "configure":"configurer "
     "app store":"App store"
     "configuration":"Configuration"


Remove jQuery from tests exercising the `squashedDirectoryName` option

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2543,13 +2543,13 @@

         rootDirPath = treeView.roots[0].getPath()
         expect(rootDirPath).toBe(rootDir)
-        zetaDirPath = $(treeView.roots[0].entries).find('.directory:contains(zeta):first')[0].getPath()
+        zetaDirPath = findDirectoryContainingText(treeView.roots[0], 'zeta').getPath()
         expect(zetaDirPath).toBe(zetaDir)

       it "does not squash a file in to a DirectoryViews", ->
-        zetaDir = $(treeView.roots[0].entries).find('.directory:contains(zeta):first')
-        zetaDir[0].expand()
-        zetaEntries = [].slice.call(zetaDir[0].children[1].children).map (element) ->
+        zetaDir = findDirectoryContainingText(treeView.roots[0], 'zeta')
+        zetaDir.expand()
+        zetaEntries = [].slice.call(zetaDir.children[1].children).map (element) ->
           element.innerText

         expect(zetaEntries).toEqual(["zeta.txt"])
@@ -2557,11 +2557,11 @@
       it "squashes two dir names when the first only contains a single dir", ->
         if path.sep is '\\'
           # First escape the backslashes for Coffeescript, then escape them for jQuery
-          betaDir = $(treeView.roots[0].entries).find(".directory:contains(alpha\\\\beta):first")
+          betaDir = findDirectoryContainingText(treeView.roots[0], "alpha\\\\beta")
         else
-          betaDir = $(treeView.roots[0].entries).find(".directory:contains(alpha#{path.sep}beta):first")
-        betaDir[0].expand()
-        betaEntries = [].slice.call(betaDir[0].children[1].children).map (element) ->
+          betaDir = findDirectoryContainingText(treeView.roots[0], "alpha#{path.sep}beta")
+        betaDir.expand()
+        betaEntries = [].slice.call(betaDir.children[1].children).map (element) ->
           element.innerText

         expect(betaEntries).toEqual(["beta.txt"])
@@ -2569,19 +2569,19 @@
       it "squashes three dir names when the first and second only contain single dirs", ->
         if path.sep is '\\'
           # First escape the backslashes for Coffeescript, then escape them for jQuery
-          epsilonDir = $(treeView.roots[0].entries).find(".directory:contains(gamma\\\\delta\\\\epsilon):first")
+          epsilonDir = findDirectoryContainingText(treeView.roots[0], "gamma\\\\delta\\\\epsilon")
         else
-          epsilonDir = $(treeView.roots[0].entries).find(".directory:contains(gamma#{path.sep}delta#{path.sep}epsilon):first")
-        epsilonDir[0].expand()
-        epsilonEntries = [].slice.call(epsilonDir[0].children[1].children).map (element) ->
+          epsilonDir = findDirectoryContainingText(treeView.roots[0], "gamma#{path.sep}delta#{path.sep}epsilon")
+        epsilonDir.expand()
+        epsilonEntries = [].slice.call(epsilonDir.children[1].children).map (element) ->
           element.innerText

         expect(epsilonEntries).toEqual(["theta.txt"])

       it "does not squash a dir name when there are two child dirs ", ->
-        lambdaDir = $(treeView.roots[0].entries).find('.directory:contains(lambda):first')
-        lambdaDir[0].expand()
-        lambdaEntries = [].slice.call(lambdaDir[0].children[1].children).map (element) ->
+        lambdaDir = findDirectoryContainingText(treeView.roots[0], "lambda")
+        lambdaDir.expand()
+        lambdaEntries = [].slice.call(lambdaDir.children[1].children).map (element) ->
           element.innerText

         expect(lambdaEntries).toEqual(["iota", "kappa"])
@@ -2589,20 +2589,20 @@
       describe "when a squashed directory is deleted", ->
         it "un-squashes the directories", ->
           jasmine.attachToDOM(workspaceElement)
-          piDir = $(treeView.roots[0].entries).find(".directory:contains(omicron#{path.sep}pi):first")[0]
+          piDir = findDirectoryContainingText(treeView.roots[0], "omicron#{path.sep}pi")
           treeView.focus()
           treeView.selectEntry(piDir)
           spyOn(atom, 'confirm').andCallFake (dialog) ->
             dialog.buttons["Move to Trash"]()
           atom.commands.dispatch(treeView.element, 'tree-view:remove')

-          omicronDir = $(treeView.roots[0].entries).find(".directory:contains(omicron):first span")[0]
-          expect(omicronDir.title).toEqual("omicron")
+          omicronDir = findDirectoryContainingText(treeView.roots[0], "omicron")
+          expect(omicronDir.header.textContent).toEqual("omicron")

       describe "when a file is created within a directory with another squashed directory", ->
         it "un-squashes the directories", ->
           jasmine.attachToDOM(workspaceElement)
-          piDir = $(treeView.roots[0].entries).find(".directory:contains(omicron#{path.sep}pi):first")[0]
+          piDir = findDirectoryContainingText(treeView.roots[0], "omicron#{path.sep}pi")
           expect(piDir).not.toBeNull()
           # omicron is a squashed dir, so searching for omicron would give us omicron/pi instead
           omicronPath = piDir.getPath().replace "#{path.sep}pi", ""
@@ -2610,18 +2610,18 @@
           fs.writeFileSync(sigmaFilePath, "doesn't matter")
           treeView.updateRoots()

-          omicronDir = $(treeView.roots[0].entries).find(".directory:contains(omicron):first span")[0]
-          expect(omicronDir.title).toEqual("omicron")
+          omicronDir = findDirectoryContainingText(treeView.roots[0], "omicron")
+          expect(omicronDir.header.textContent).toEqual("omicron")
           omicronDir.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-          piDir = $(treeView.roots[0].entries).find(".directory:contains(omicron) .entries .directory:contains(pi) span")[0]
-          expect(piDir.title).toEqual("pi")
-          sigmaFile = $(treeView.roots[0].entries).find(".directory:contains(omicron) .entries .file:contains(sigma) span")[0]
-          expect(sigmaFile.title).toEqual("sigma.txt")
+          piDir = findDirectoryContainingText(omicronDir, "pi")
+          expect(piDir.header.textContent).toEqual("pi")
+          sigmaFile = findFileContainingText(omicronDir, "sigma.txt")
+          expect(sigmaFile.fileName.textContent).toEqual("sigma.txt")

       describe "when a directory is created within a directory with another squashed directory", ->
         it "un-squashes the directories", ->
           jasmine.attachToDOM(workspaceElement)
-          piDir = $(treeView.roots[0].entries).find(".directory:contains(omicron#{path.sep}pi):first")[0]
+          piDir = findDirectoryContainingText(treeView.roots[0], "omicron#{path.sep}pi")
           expect(piDir).not.toBeNull()
           # omicron is a squashed dir, so searching for omicron would give us omicron/pi instead
           omicronPath = piDir.getPath().replace "#{path.sep}pi", ""
@@ -2629,25 +2629,33 @@
           fs.makeTreeSync(rhoDirPath)
           treeView.updateRoots()

-          omicronDir = $(treeView.roots[0].entries).find(".directory:contains(omicron):first span")[0]
-          expect(omicronDir.title).toEqual("omicron")
+          omicronDir = findDirectoryContainingText(treeView.roots[0], "omicron")
+          expect(omicronDir.header.textContent).toEqual("omicron")
           omicronDir.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
-          piDir = $(treeView.roots[0].entries).find(".directory:contains(omicron) .entries .directory:contains(pi) span")[0]
-          expect(piDir.title).toEqual("pi")
-          rhoDir = $(treeView.roots[0].entries).find(".directory:contains(omicron) .entries .directory:contains(rho) span")[0]
-          expect(rhoDir.title).toEqual("rho")
+          piDir = findDirectoryContainingText(omicronDir, "pi")
+          expect(piDir.header.textContent).toEqual("pi")
+          rhoDir = findDirectoryContainingText(omicronDir, "rho")
+          expect(rhoDir.header.textContent).toEqual("rho")

       describe "when a directory is reloaded", ->
         it "squashes the directory names the last of which is same as an unsquashed directory", ->
-          muDir = $(treeView.roots[0].entries).find('.directory:contains(mu):first')
-          muDir[0].expand()
-          muEntries = Array.from(muDir[0].children[1].children).map (element) -> element.innerText
+          muDir = findDirectoryContainingText(treeView.roots[0], "mu")
+          muDir.expand()
+          muEntries = Array.from(muDir.children[1].children).map (element) -> element.innerText
           expect(muEntries).toEqual(["nu#{path.sep}xi", "xi"])

-          muDir[0].expand()
-          muDir[0].reload()
-          muEntries = Array.from(muDir[0].children[1].children).map (element) -> element.innerText
+          muDir.expand()
+          muDir.reload()
+          muEntries = Array.from(muDir.children[1].children).map (element) -> element.innerText
           expect(muEntries).toEqual(["nu#{path.sep}xi", "xi"])
+
+    findDirectoryContainingText = (element, text) ->
+      directories = Array.from(element.querySelectorAll('.entries .directory'))
+      directories.find((directory) -> directory.header.textContent is text)
+
+    findFileContainingText = (element, text) ->
+      files = Array.from(element.querySelectorAll('.entries .file'))
+      files.find((file) -> file.fileName.textContent is text)

   describe "Git status decorations", ->
     [projectPath, modifiedFile, originalFileContent] = []


FIX: Ensure that campuses are loaded on start

--- src/app/config/root-controller/root-controller.coffee
+++ src/app/config/root-controller/root-controller.coffee
@@ -3,7 +3,10 @@
 #
 # The Doubtfire root application controller
 #
-.controller("AppCtrl", (TeachingPeriod) ->
+.controller("AppCtrl", (TeachingPeriod, campusService) ->
+
+  # Ensure that campuses are loaded
+  campusService.query().subscribe()

   # Ensure that teaching periods are loaded
   TeachingPeriod.query()


skip summary screen for tutorial lightcurve (resolves #195)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -564,10 +564,13 @@
     @continueButton.hide()

   showSummaryScreen: ->
-    @hideMarkingButtons()
-    @nextSubjectButton.show()
-    @setGuestObsContent()
-    @classifySummary.fadeIn(150)
+    if @classification.subject.id is 'TUTORIAL_SUBJECT'
+      @onClickNextSubject()
+    else
+      @hideMarkingButtons()
+      @nextSubjectButton.show()
+      @setGuestObsContent()
+      @classifySummary.fadeIn(150)
     # @finishSubject()

   checkSupplementalTutorial: ->


stop simulations for now

stop simulations for now

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -542,7 +542,8 @@
     @sim_count ||= 0
     @sim_count +=1

-    @sim_rate = 0.20
+    # @sim_rate = 0.20
+    @sim_rate = 0.0

     sim_roll = Math.random()
     # console.log "sim roll #{sim_roll} rate #{@sim_rate}"


Restore parens.

Restore parens.

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -715,7 +715,7 @@

     if @shouldShowTraining()
       app.api.get("projects/asteroid/groups/#{TRAINING_SUBJECT_GROUP}/subjects").then (subjects) ->
-        Subject.current.destroy
+        Subject.current.destroy()
         subject = new Subject subjects[0]
         queued = Subject.instances.pop()
         Subject.instances.unshift queued


finalizing changes

finalizing changes

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -20,6 +20,9 @@

 MAIN_SUBJECT_GROUP = "5417014a3ae7400bda000001"
 SIMULATION_GROUP   = "5417014b3ae7400bda000002"
+
+USERS_OPT_IN = ['a', 'b', 'c', 'g', 'h', 'i']
+USERS_OPT_OUT = ['d', 'e', 'f', 'j', 'k', 'l']

 class Classifier extends BaseController
   className: 'classifier'
@@ -78,7 +81,7 @@
     # if window.matchMedia("(min-device-width: 320px)").matches and window.matchMedia("(max-device-width: 480px)").matches
     #   location.hash = "#/verify"

-    Subject.group = "5417014a3ae7400bda000001"
+    Subject.group = MAIN_SUBJECT_GROUP

     @loggedOutClassificationCount = 0

@@ -115,7 +118,9 @@
     @supplementalTutorial = new Tutorial
       parent: window.classifier.el.children()[0]
       steps: supplementalTutorialSteps.steps
-    @injectMiniCourseView()
+
+    @supplementalTutorial.createElement "div.foo", @supplementalTutorial.el.children[2]
+    console.log 'FOO ', @supplementalTutorial.el

     # mini course
     @course = new MiniCourse
@@ -185,82 +190,53 @@
   onChangeMiniCourseOption: ->
     console.log 'onChangeMiniCourseOption(): '
     return unless User.current?
-
-    @courseEnabled = !@courseEnabled
-
-    if @courseEnabled
+    showMiniCourse = $("[name='mini-course-option']").prop 'checked'
+    console.log "onChangeMiniCourseOption(): SHOW MINI COURSE = ", showMiniCourse
+
+    if showMiniCourse
+      console.log 'onChangeMiniCourseOption(): YES, MINI COURSE!'
       User.current?.setPreference 'course', 'yes'
-      console.log '  > unchecking course-opt-out'
-      $("[name='course-opt-out']").prop 'checked', false
-    else
+      @courseEnabled = true
+    else
+      console.log 'onChangeMiniCourseOption(): BOO! No mini course.'
+      # $("[name='course-opt-out']").prop x'checked', true
       User.current?.setPreference 'course', 'no'
-      console.log '  > checking course-opt-out'
-      $("[name='course-opt-out']").prop 'checked', true
+      @courseEnabled = false

     clickEvent =
-      event: 'courseEnabled'
+      event: 'course-enabled'
       value: @courseEnabled
       timestamp: (new Date).toUTCString()
     @recordedClickEvents.push clickEvent

+    console.log 'COURSE_ENABLED: ', @courseEnabled
+
   onChangeCourseOptOut: ->
     console.log 'onChangeCourseOptOut(): '
     return unless User.current?
-    opt_out = $("[name='course-opt-out']").prop 'checked'
-    if opt_out
+
+    optOut = $("[name='course-opt-out']").prop 'checked'
+    console.log "onChangeCourseOptOut(): SHOW MINI COURSE = ", !optOut
+
+    if optOut
+      console.log 'onChangeCourseOptOut(): BOO! No mini course.'
       User.current?.setPreference 'course', 'no'
-      console.log '  > setting course pref to NO'
-      @courseEnabled = false # TODO: needs work!
-    else
+      @courseEnabled = false
+    else
+      console.log 'onChangeCourseOptOut(): YES, MINI COURSE!'
       User.current?.setPreference 'course', 'yes'
-      console.log '  > setting course pref to YES'
       @courseEnabled = true

     clickEvent =
-      event: 'courseOptedOut'
-      value: opt_out
+      event: 'course-opted-out'
+      value: optOut
       timestamp: (new Date).toUTCString()
     @recordedClickEvents.push clickEvent

-  # onChangeMiniCourseOption: ->
-  #   console.log 'onChangeMiniCourseOption(): '
-  #   return unless User.current?
-  #   courseOption = User.current.preferences.planet_hunter.course
-
-  #   # toggle course option
-  #   if courseOption is 'yes'
-  #     courseOption = 'no'
-  #     # $("[name='course-opt-out']").prop 'checked', true
-  #   else
-  #     courseOption = 'yes'
-  #     $("[name='course-opt-out']").prop 'checked', false
-
-  #   clickEvent =
-  #     event: 'miniCourseOptionChanged'
-  #     value: courseOption
-  #     timestamp: (new Date).toUTCString()
-  #   @recordedClickEvents.push clickEvent
-
-  #   User.current?.setPreference 'course', courseOption
-
-  # CODE FOR PROMPT (NOT CURRENTLY BEING USED)
-  # onChangeCourseInterval: ->
-  #   # console.log 'VALUE: ', @el.find('#course-interval').val()
-  #   defaultValue = 5
-  #   value = +@el.find('#course-interval').val()
-
-  #   # console.log 'VALUE IS NUMBER: ', (typeof value)
-
-  #   # validate integer values
-  #   unless (typeof value is 'number') and (value % 1 is 0) and value > 0 and value < 100
-  #     value = defaultValue
-  #     @el.find('#course-interval').val(value)
-  #   else
-  #     # console.log 'SETTING VALUE TO: ', value
-  #     @course.setRate value
+    console.log 'COURSE_ENABLED: ', @courseEnabled

   onChangeCourseIntervalViaSupTut: ->
-    # console.log 'onChangeCourseIntervalViaSupTut(): '
+    console.log 'onChangeCourseIntervalViaSupTut(): '
     defaultValue = 5
     value = +@el.find('#course-interval-sup-tut').val()

@@ -269,12 +245,12 @@
       value = defaultValue
       @el.find('#course-interval-sup-tut').val(value)
     else
-      # console.log 'SETTING VALUE TO: ', value
+      console.log 'SETTING VALUE TO: ', value

     @course.setRate value

     clickEvent =
-      event: 'courseIntervalChanged'
+      event: 'course-interval-changed'
       value: value
       timestamp: (new Date).toUTCString()
     @recordedClickEvents.push clickEvent
@@ -350,13 +326,13 @@
       @course.setRate 5 # set default

     # SET MINI-COURSE DEFAULT OPT-IN/OUT PREFS
-    if @splitDesignation in ['a', 'b', 'c', 'g', 'h', 'i'] # users must opt in
+    if @splitDesignation in USERS_OPT_IN
       @courseEnabled = false
       User.current.setPreference 'course', 'no'
       # initialize checkboxes
       $("[name='course-opt-out']").prop 'checked', true
       $("[name='mini-course-option']").prop 'checked', false
-    else if @splitDesignation in ['d', 'e', 'f', 'j', 'k', 'l'] # users must opt out
+    else if @splitDesignation in USERS_OPT_OUT
       @courseEnabled = true
       User.current.setPreference 'course', 'yes'
       # initialize checkboxes
@@ -479,12 +455,12 @@
     else
       @spottersGuide.slideDown()
       $("html, body").animate scrollTop: @spottersGuide.offset().top - 20, 500
-      clickEvent = { event: 'guideActivated', timestamp: (new Date).toUTCString() }
+      clickEvent = { event: 'spotters-guide-pressed', timestamp: (new Date).toUTCString() }
       @recordedClickEvents.push clickEvent
     @guideShowing = !@guideShowing

   onClickTutorial: ->
-    clickEvent = { event: 'tutorialClicked', timestamp: (new Date).toUTCString() }
+    clickEvent = { event: 'tutorial-clicked', timestamp: (new Date).toUTCString() }
     @recordedClickEvents.push clickEvent
     @canvasGraph.marks.destroyAll() # clear previous marks
     @updateButtons()
@@ -638,11 +614,23 @@

         # prompt user to opt in/out of mini course
         if @course.count is 3
-
-          # check box only if mini-course enabled
-          $('.mini-course-option').prop 'checked', @courseEnabled
-
-  injectMiniCourseView: ->
+          @renderSupTutPrompt()
+          if @allowCustomCourseInterval
+            @el.find('.allow-custom-interval').show()
+            @el.find('.disallow-custom-interval').hide()
+          else
+            @el.find('.allow-custom-interval').hide()
+            @el.find('.disallow-custom-interval').show()
+
+          if @courseEnabled
+            @el.find('[name="mini-course-option"]').prop 'checked', true
+            @el.find('[name="course-opt-out"]').prop 'checked', false
+          else
+            @el.find('[name="mini-course-option"]').prop 'checked', false
+            @el.find('[name="course-opt-out"]').prop 'checked', true
+
+  renderSupTutPrompt: ->
+    console.log 'rendering supplemental tutorial prompt...'
     newElement = document.createElement('div')
     newElement.setAttribute 'class', "supplemental-tutorial-option-container"
     newElement.setAttribute 'style', "padding: 20px;"
@@ -797,7 +785,7 @@

     @updateZoomButton(@canvasGraph.zoomLevel)
     @showZoomMessage(@magnification[@canvasGraph.zoomLevel])
-    @recordedClickEvents.push { event: 'clickedZoomLevel'+@canvasGraph.zoomLevel, timestamp: (new Date).toUTCString() }
+    @recordedClickEvents.push { event: 'clicked-zoom-level'+@canvasGraph.zoomLevel, timestamp: (new Date).toUTCString() }

   updateZoomButton: (zoomLevel) ->
     if zoomLevel is 2


Don't offer to push empty files

Don't offer to push empty files

--- components/CheckSyncState.coffee
+++ components/CheckSyncState.coffee
@@ -255,6 +255,7 @@
         addToConflict 'component', matching[0], remoteComponent, operations

       localOnly = data.project.components.filter (localComponent) ->
+        return false unless localComponent.code.length
         notPushed = true
         for remoteComponent in objects.components
           notPushed = false if localComponent.sha is remoteComponent.sha
@@ -281,6 +282,7 @@
         addToConflict 'spec', matching[0], remoteSpec, operations

       localOnly = data.project.specs.filter (localSpec) ->
+        return false unless localSpec.code.length
         notPushed = true
         for remoteSpec in objects.specs
           notPushed = false if localSpec.sha is remoteSpec.sha


wire up next-frame button to the frame display

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -37,12 +37,15 @@
   className: 'classifier'
   template: require '../views/classifier'
   marks: []
+  currentFrameIdx = 0 # keeps track of current (zero-indexed) frame
+
   events:
     'click button[name="play-frames"]'    : 'onClickPlay'
     'click button[name="invert"]'         : 'onClickInvert'
     'click button[name="finish-marking"]' : 'onClickFinishMarking'
     'click button[name="four-up"]'        : 'onClickFourUp'
     'click button[name="flicker"]'        : 'onClickFlicker'
+    'click button[name="next-frame"]'     : 'onClickNextFrame'
     # 'click input[name="current-frame"]'   : 'onClickRadioButton'
     'change input[name="current-frame"]'  : 'onChangeFrameSlider'

@@ -59,6 +62,7 @@
     'button[name="flicker"]'        : 'flickerButton'
     'button[name="four-up"]'        : 'fourUpButton'
     'button[name="finish-marking"]' : 'finishButton'
+    'button[name="next-frame"]'     : 'nextFrame'

     'keydown': (e) ->
       return if @el.hasClass 'playing'  # disable while playing
@@ -82,7 +86,8 @@
     # @setClassification @classification  # ...

     @invert = false
-    @setCurrentFrameIdx 0
+
+    @currentFrameIdx = 0

     window.classifier = @

@@ -262,6 +267,13 @@
     @flickerButton.attr 'disabled', true
     @fourUpButton.attr 'disabled', false

+  onClickNextFrame: ->
+    console.log "next frame...: " + @currentFrameIdx
+    @currentFrameIdx++
+    @setCurrentFrameIdx(@currentFrameIdx)
+    @activateFrame(@currentFrameIdx)
+    console.log @currentFrameIdx
+
   # onClickRadioButton: ->
   #   for i in [0...@frameRadioButtons.length]
   #     if @frameRadioButtons[i].checked
@@ -293,6 +305,7 @@
     # @markingSurfaceList.enable()

   activateFrame: (@active) ->
+    console.log "activating frame: " + @active
     @active = modulus +@active, @classification.subject.location.standard.length
     @showFrame(@active)


Remove unused "native" toolbar element attribute

--- src/trix/elements/trix_toolbar_element.coffee
+++ src/trix/elements/trix_toolbar_element.coffee
@@ -27,12 +27,6 @@
     if @innerHTML is ""
       @appendChild(cloneFragment(Trix.config.toolbar.content))

-    if @hasAttribute("native")
-      if Trix.NativeToolbarController
-        @toolbarController = new Trix.NativeToolbarController this
-      else
-        throw "Host application must implement Trix.NativeToolbarController"
-    else
-      @toolbarController = new Trix.ToolbarController this
+    @toolbarController = new Trix.ToolbarController this

     triggerEvent("trix-element-attached", onElement: this)


Don't check for badge conditions if no user exists.

--- app/controllers/interfaces.coffee
+++ app/controllers/interfaces.coffee
@@ -46,7 +46,7 @@
       userFetch = User.fetch()

       userFetch.done =>
-        User.current.badges = badges # sigh
+        User.current.badges = badges if User.current # sigh
         @archive.checkBadges()

     @classification.send done


Clone BLANK_STUDENT to prevent accidental changes

--- src/components/course-administration/add-student.cjsx
+++ src/components/course-administration/add-student.cjsx
@@ -41,7 +41,7 @@
   bindEvent: 'created'

   getInitialState: ->
-    BLANK_STUDENT
+    _.clone(BLANK_STUDENT)

   performUpdate: ->
     this.refs.overlay.hide()
@@ -49,7 +49,7 @@
       period_id: @props.period.id
     )
     RosterActions.create(@props.courseId, student)
-    @setState(BLANK_STUDENT)
+    @setState(_.clone(BLANK_STUDENT))

   renderForm: ->
     <BS.Popover className='teacher-add-student-form'


Fix router, closes #49

Fix router, closes #49

--- client/helpers/router.coffee
+++ client/helpers/router.coffee
@@ -2,7 +2,7 @@
   layoutTemplate: "layout"
   notFoundTemplate: "notFound"
   loadingTemplate: "loading"
-  renderTemplates:
+  yieldTemplates:
     footer:
       to: "footer"
     header:


Routes: Update ActiveMachineStore

Routes: Update ActiveMachineStore

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -63,6 +63,7 @@

   { machine, workspace, username, channelId } = data
   selectWorkspaceOnSidebar data
+  actions.setSelectedMachineId machine._id

   appManager = kd.getSingleton 'appManager'
   ideApps    = appManager.appControllers.IDE


Missed requiring the EmptyEvents component

--- src/components/student-dashboard/all-events-by-week.cjsx
+++ src/components/student-dashboard/all-events-by-week.cjsx
@@ -1,6 +1,7 @@
 React  = require 'react'
 BS     = require 'react-bootstrap'
 moment = require 'moment'
+EmptyPanel  = require './empty-panel'
 EventsPanel = require './events-panel'
 {StudentDashboardStore} = require '../../flux/student-dashboard'
 _ = require 'underscore'
@@ -26,4 +27,4 @@
     if _.any(weeks)
       <div>{_.map(weeks, @renderWeek)}</div>
     else
-      <EmptyPanel>No Upcoming Events</EmptyPanel>
+      <EmptyPanel>No past tasks</EmptyPanel>


Added getMilestone and getMilestones tests for resources

--- test/unit/services/resourceServiceSpec.coffee
+++ test/unit/services/resourceServiceSpec.coffee
@@ -92,6 +92,10 @@
         httpBackend.whenGET('http://localhost:8000/api/v1/roles?project=100').respond(400)
         httpBackend.whenPOST('http://localhost:8000/api/v1/roles?', {"test": "test", "project": 1}).respond(200)
         httpBackend.whenPOST('http://localhost:8000/api/v1/roles?', {"test": "bad", "project": 1}).respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/milestones?project=1').respond(200, [{"user_stories": [{"test": "test"}]}])
+        httpBackend.whenGET('http://localhost:8000/api/v1/milestones?project=100').respond(400)
+        httpBackend.whenGET('http://localhost:8000/api/v1/milestones/1?project=1').respond(200, {"user_stories": [{"test": "test"}]})
+        httpBackend.whenGET('http://localhost:8000/api/v1/milestones/100?project=1').respond(400)

     describe 'resource service', ->
         afterEach ->
@@ -336,3 +340,25 @@
             promise = resource.createRole(1, {"test": "bad"})
             promise.should.be.rejected
             httpBackend.flush()
+
+        it 'should allow to get all the milestones of a project', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/milestones?project=1')
+            promise = resource.getMilestones(1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/milestones?project=100')
+            promise = resource.getMilestones(100)
+            promise.should.be.rejected
+            httpBackend.flush()
+
+        it 'should allow to get one milestone', inject (resource) ->
+            httpBackend.expectGET('http://localhost:8000/api/v1/milestones/1?project=1')
+            promise = resource.getMilestone(1, 1)
+            httpBackend.flush()
+            promise.should.be.fullfilled
+
+            httpBackend.expectGET('http://localhost:8000/api/v1/milestones/100?project=1')
+            promise = resource.getMilestone(1, 100)
+            promise.should.be.rejected
+            httpBackend.flush()


Specify that algoprog chat is mostly in Russian

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

+7(910)794-32-07**

Telegram: @pkalinin

Telegram channel with important announcements: @algoprog_news (mainly in Russian).

-
Telegram chat for all algoprog students: Algoprog-chat.
+
Telegram chat for all algoprog students (mostly in Russian): Algoprog-chat.
         To join, you should first specify your telegram id in your algoprog profile.

My contact details can be freely distributed for any questions related to the course or
@@ -552,7 +552,7 @@
         (or write /start), the bot will send you a message with your id.

This will allow you to:

-
join the telegram chat for all algoprog students,

+
join the telegram chat for all algoprog students (mostly in Russian),

receive notifications about counted/ignored solutions, and about comments on solutions, to do this, start a chat with the algoprog bot @algoprog_bot.


Tracking hit box adjacent sides

Tracking hit box adjacent sides

--- src/javascript/game/systems/map_physics_system.coffee
+++ src/javascript/game/systems/map_physics_system.coffee
@@ -57,6 +57,12 @@
       top: []
       bottom: []

+    adjacent =
+      left: null
+      right: null
+      top: null
+      bottom: null
+
     # Apply & restrict VERTICAL movement
     box.moveY(vy * @dt())

@@ -64,12 +70,19 @@
     if hits.top.length > 0
       s = hits.top[0]
       box.setY(s.y+s.height - box.topOffset)
+      adjacent.top = hits.top
     else
-      hits.bottom = tileSearchHorizontal(grid, tileWidth,tileHeight,box.bottom, box.left, box.right-1)
-      # console.log "tileSearchHorizontal(grid, tileWidth,tileHeight,box.bottom, box.left, box.right-1)", tileWidth,tileHeight,box.bottom, box.left, box.right-1
+      hits.bottom = tileSearchHorizontal(grid, tileWidth,tileHeight,Math.ceil(box.bottom), box.left, box.right-1)
       if hits.bottom.length > 0
         s = hits.bottom[0]
-        box.setY(s.y - box.bottomOffset)
+        box.setY(s.y - box.bottomOffset-1)
+        adjacent.bottom = hits.bottom
+
+    unless adjacent.top?
+      adjacent.top = tileSearchHorizontal(grid, tileWidth,tileHeight, box.top-1, box.left, box.right-1)
+    unless adjacent.bottom?
+      adjacent.bottom = tileSearchHorizontal(grid, tileWidth,tileHeight,Math.ceil(box.bottom+1), box.left, box.right-1)
+

     # Step 2: apply & restrict horizontal movement
     box.moveX(vx * @dt())
@@ -78,11 +91,19 @@
     if hits.left.length > 0
       s = hits.left[0]
       box.setX(s.x+s.width - box.leftOffset)
+      adjacent.left = hits.left
     else
-      hits.right = tileSearchVertical(grid, tileWidth,tileHeight,box.right, box.top, box.bottom-1)
+      hits.right = tileSearchVertical(grid, tileWidth,tileHeight, Math.ceil(box.right), box.top, box.bottom-1)
       if hits.right.length > 0
+        console.log "hits.right",hits.right
         s = hits.right[0]
-        box.setX(s.x - box.rightOffset)
+        box.setX(s.x - box.rightOffset-1)
+        adjacent.right = hits.right
+
+    unless adjacent.left?
+      adjacent.left = tileSearchVertical(grid, tileWidth,tileHeight,box.left-1, box.top, box.bottom-1)
+    unless adjacent.right?
+      adjacent.right = tileSearchVertical(grid, tileWidth,tileHeight, Math.ceil(box.right+1), box.top, box.bottom-1)

     # Update position and hit_box components
     @updateComp position.set('x', box.x).set('y', box.y)
@@ -98,6 +119,12 @@
     hitBoxJS.touching.bottom = hits.bottom.length > 0
     hitBoxJS.touchingSomething = hitBoxJS.touching.left or hitBoxJS.touching.right or hitBoxJS.touching.top or hitBoxJS.touching.bottom

+    hitBoxJS.adjacent = {}
+    hitBoxJS.adjacent.top = adjacent.top.length > 0
+    hitBoxJS.adjacent.bottom = adjacent.bottom.length > 0
+    hitBoxJS.adjacent.left = adjacent.left.length > 0
+    hitBoxJS.adjacent.right = adjacent.right.length > 0
+
     @updateComp hitBox.merge(hitBoxJS)

     # Update velocity if needed based on running into objects:


Fix parse boxparams with negative coords

--- client/utils/boxparam.coffee
+++ client/utils/boxparam.coffee
@@ -1,6 +1,6 @@
 L = require("leaflet")

-RE = /(?:box\=)([0-9\.\,\|\d]+)/
+RE = /(?:box\=)([0-9\.\-\,\|\d]+)/

 matches = (str)->
   RE.test(str)


remove debugging

remove debugging

--- app/controllers/base_panel.coffee
+++ app/controllers/base_panel.coffee
@@ -3,7 +3,6 @@
 class BasePanel extends Panel
   activate: ->
     super
-    console.log 'trigger activateTab', @tab
     Spine.trigger 'activateTab', @tab

 module.exports = BasePanel


fix: destroy ghost marks appropriately; code cleanup

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -203,10 +203,11 @@

   updateGhostMark: (mark) ->
     for ghostMark in [ @el.find(".ghost-mark")... ]
-      ghostMark.remove() if ghostMark.getAttribute 'from-frame' is mark.frame and ghostMark.getAttribute 'from-asteroid' is @currAsteroid.id
-
+      if parseInt(ghostMark.getAttribute 'from-frame') is (parseInt mark.frame) \
+        and parseInt(ghostMark.getAttribute 'from-asteroid') is parseInt @currAsteroid.id
+          ghostMark.remove()
+
   onDestroyMark: (mark) =>
-    console.log 'mark destroyed'
     console.log 'mark.frame = ', mark.frame
     @destroyMarksInFrame mark.frame
     @updateIconsForDestroyMark mark.frame
@@ -216,14 +217,11 @@

   onCreateTool: (tool) =>
     surfaceIndex = +@markingSurfaceList.indexOf tool.surface
-    console.log 'tool created on surface', surfaceIndex

     if @asteroidMarkedInFrame[surfaceIndex]
-      console.log 'clearing'
       @currAsteroid.clearSightingsInFrame surfaceIndex
       @destroyMarksInFrame(surfaceIndex)
     else
-      console.log 'pushing'
       @el.find(".asteroid-frame-complete-#{surfaceIndex}").prop 'checked', true
       @asteroidMarkedInFrame[surfaceIndex] = true
     @updateIconsForCreateMark(surfaceIndex)
@@ -232,7 +230,6 @@
       @doneButton.prop 'disabled', false

     console.log @asteroidMarkedInFrame
-
     tool.controls.controller.setMark(surfaceIndex, @currAsteroid.id)

   onChangeFrameSlider: =>
@@ -273,8 +270,7 @@
   loadFrames: =>
     @destroyFrames()
     subject_info = @classification.subject.location
-    numImages = subject_info.standard.length
-    for i in [0...numImages]
+    for i in [0...@numFrames]
       frame_id = "frame-id-#{i}"
       frameImage =
         @markingSurfaceList[i].addShape 'image',


organize code some more; rename variable whenToDisplayTips

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -90,7 +90,7 @@
     ifFaved: false

     # classification counts at which to display supplementary tutorial
-    @whenToDisplayTips = [1, 2, 3] # TODO: don't forget to add 4 after beta version
+    @supTutIntervals = [1, 2, 3] # TODO: don't forget to add 4 after beta version
     @splitDesignation = null

     @known_transits = ''
@@ -115,6 +115,7 @@
     @supplementalTutorial = new Tutorial
       parent: window.classifier.el.children()[0]
       steps: supplementalTutorialSteps.steps
+    @injectMiniCourseView()

     # mini course
     @course = new MiniCourse
@@ -572,7 +573,7 @@
       @course.launch()

     if @splitDesignation in ['a', 'b', 'c', 'd', 'e', 'f']
-      @whenToDisplayTips = [3]
+      @supTutIntervals = [3]
     @checkSupplementalTutorial()

     @sendClassification()
@@ -629,7 +630,7 @@
       @classifySummary.fadeIn(150)

   checkSupplementalTutorial: ->
-    for classification_count in @whenToDisplayTips
+    for classification_count in @supTutIntervals
       if @course.count is classification_count
         # console.log "*** DISPLAY SUPPLEMENTAL TUTOTIAL # #{classification_count} *** "
         @supplementalTutorial.first = "displayOn_" + classification_count.toString()
@@ -637,30 +638,32 @@

         # prompt user to opt in/out of mini course
         if @course.count is 3
-          @createMiniCoursePrompt()

           # check box only if mini-course enabled
           $('.mini-course-option').prop 'checked', @courseEnabled

-  createMiniCoursePrompt: ->
+  injectMiniCourseView: ->
     newElement = document.createElement('div')
     newElement.setAttribute 'class', "supplemental-tutorial-option-container"
     newElement.setAttribute 'style', "padding: 20px;"
     newElement.innerHTML = """
-      <input class=\"mini-course-option\" name=\"mini-course-option\" type=\"checkbox\"></input>
-

+      <input class="mini-course-option" name="mini-course-option" type="checkbox"></input>
+

+
+

+
Launch mini-course every

+          <input type="number" id="course-interval-sup-tut" class="course-interval" name="course-interval-sup-tut" value="5" />
+
 classifications!

+

+
+

+
Yes, I want to learn more!

+

+
+

     """
     # inject custom element into zootorial
     @supplementalTutorial.container.getElementsByClassName('zootorial-content')[0].appendChild(newElement)
-
-    if @allowCustomCourseInterval
-      $('#course-opt-in-label').html """
-
Launch mini-course every

-          <input type="number" id="course-interval-sup-tut" class="course-interval" name="course-interval-sup-tut" value="5"></input>
-
 classifications!

-      """
-    else
-      $('#course-opt-in-label').html "Yes, I want to learn more!"

   sendClassification: ->
     if User.current?


increase bottom padding of checkbox

increase bottom padding of checkbox

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -488,7 +488,7 @@

         newElement = document.createElement('div')
         newElement.setAttribute 'class', "supplemental-tutorial-option-container"
-        newElement.setAttribute 'style', "float: 1right; padding: 5px;"
+        newElement.setAttribute 'style', "float: 1right; padding-top: 20px;"
         newElement.innerHTML = """
           <input class=\"supplemental-option\" type=\"checkbox\"></input>
           <label>Do not show tips in the fiture.</label>


Fixed an error wizard sprites were having.

--- app/lib/surface/WizardLank.coffee
+++ app/lib/surface/WizardLank.coffee
@@ -81,7 +81,6 @@
     shouldUpdate = not _.isEqual(newColorConfig, @options.colorConfig)
     @options.colorConfig = $.extend(true, {}, newColorConfig)
     if shouldUpdate
-      @setUpLank()
       @playAction(@currentAction) if @currentAction

   onLankSelected: (e) ->


On Shabbat Ereb YT and on 1st day of YT, we specify the time for Arbit as the earliest opinion for Set Hakochabim

--- source/javascripts/mincha.coffee
+++ source/javascripts/mincha.coffee
@@ -183,9 +183,9 @@
     else recent_hadlakat_nerot
   arbit: -> @_arbit ?= switch
     when @hebrew_date.isErebYomKippur() then minutes_before_event(@sunset, 55)
-    when @hebrew_date.isErebShabbat() || (@tonight_is_yom_tob() && !@hebrew_date.isShabbat()) then null
+    when @hebrew_date.isErebShabbat() || (@hebrew_date.isErebYomTob() && !@hebrew_date.isShabbat()) then null
     when @hebrew_date.isYomKippur() || (@hebrew_date.isShabbat() && !@tonight_is_yom_tob()) then @arbit_on_mosaei_shabbat()
-    when @yom_tob_that_we_can_pray_at_plag() then @plag()
+    when @has_hadlakat_nerot_after_set_hakochabim() then (if (@yom_tob_that_we_can_pray_at_plag() && !@hebrew_date.isShabbat()) then @plag() else @set_hakochabim_geonim())
     when @hebrew_date.is2ndDayOfYomTob() && !@hebrew_date.isErebShabbat() then minutes_before_event(@zmanim.set_hakochabim, 10)
     else @sunset
   hadlakat_nerot: -> if @has_hadlakat_nerot_after_set_hakochabim() then moment(@zmanim.set_hakochabim) else moment(@sunset).subtract(19, 'minutes')


fix work_mem for linux

fix work_mem for linux

--- source/javascripts/pgtune.coffee
+++ source/javascripts/pgtune.coffee
@@ -102,7 +102,7 @@
       }[@dbType]
       # work_mem is assigned any time a query calls for a sort, or a hash, or any other structure that needs a space allocation, which can happen multiple times per query. So you're better off assuming max_connections * 2 or max_connections * 3 is the amount of RAM that will actually use in reality. At the very least, you need to subtract shared_buffers from the amount you're distributing to connections in work_mem.
       # The other thing to consider is that there's no reason to run on the edge of available memory. If you do that, there's a very high risk the out-of-memory killer will come along and start killing PostgreSQL backends. Always leave a buffer of some kind in case of spikes in memory usage. So your maximum amount of memory available in work_mem should be ((RAM - shared_buffers) / 2 / (max_connections * 3)).
-      workMem = (memoryInKB - gConfig['shared_buffers']) / (gConfig['max_connections'] * 3) / 2
+      workMem = (memoryInKB - gConfig['shared_buffers']) / (gConfig['max_connections'] * 3)
       gConfig['work_mem'] = {
         web: Math.floor(workMem),
         oltp: Math.floor(workMem),