Update test AMI

Update test AMI

--- scripts/test-instance/instance-ami.coffee
+++ scripts/test-instance/instance-ami.coffee
@@ -1,9 +1,7 @@
 module.exports  =
-  current: 'ami-4d552d27'
+  current: 'ami-9cea82fc'

   # Ordered by creation date
   list: [
-    'ami-5dd50436'
-    'ami-bf235ad5'
-    'ami-4d552d27'
+    'ami-9cea82fc'
   ]


allow modifying button style so it's not always primary

--- src/components/buttons/browse-the-book.cjsx
+++ src/components/buttons/browse-the-book.cjsx
@@ -7,12 +7,16 @@
   contextTypes:
     router: React.PropTypes.func

+  getDefaultProps: ->
+    bsStyle: 'primary'
+
   propTypes:
     courseId:  React.PropTypes.string
     chapterId: React.PropTypes.number
     sectionId: React.PropTypes.number
     page:      React.PropTypes.string
     unstyled:  React.PropTypes.bool
+    bsStyle:   React.PropTypes.string

   render: ->
     courseId = @props.courseId or @context.router.getCurrentParams().courseId
@@ -25,4 +29,4 @@
     if @props.unstyled
       <a {...linkProps}>{text}</a>
     else
-      <BS.Button bsStyle='primary' {...linkProps}>{text}</BS.Button>
+      <BS.Button bsStyle={@props.bsStyle} {...linkProps}>{text}</BS.Button>


Have editable modules.

Have editable modules.

--- app/navigation.coffee
+++ app/navigation.coffee
@@ -20,6 +20,7 @@

 @open_navigation = ->
   with_mixin [
+      VIEWS_RECORD_EDIT
       schema:  SCHEMAS["navigation"]
       records: (RECORDS["app"].find "navigation").children()
       xml:     RECORDS["app"].find "navigation"


Add tests for adding edges with missing nodes after connecting

--- spec/Network.coffee
+++ spec/Network.coffee
@@ -557,3 +557,39 @@
         chai.expect(err).to.be.an 'error'
         chai.expect(err.message).to.contain 'No component defined'
         done()
+    it 'should fail to add an edge to a missing outbound node', (done) ->
+      g = new noflo.Graph
+      g.addNode 'Repeat1', 'Split'
+      nw = new noflo.Network g
+      nw.loader = loader
+      nw.connect (err) ->
+        return done err if err
+        nw.addEdge {
+          from:
+            node: 'Repeat2'
+            port: 'out'
+          to:
+            node: 'Repeat1'
+            port: 'in'
+        }, (err) ->
+          chai.expect(err).to.be.an 'error'
+          chai.expect(err.message).to.contain 'No process defined for outbound node'
+          done()
+    it 'should fail to add an edge to a missing inbound node', (done) ->
+      g = new noflo.Graph
+      g.addNode 'Repeat1', 'Split'
+      nw = new noflo.Network g
+      nw.loader = loader
+      nw.connect (err) ->
+        return done err if err
+        nw.addEdge {
+          from:
+            node: 'Repeat1'
+            port: 'out'
+          to:
+            node: 'Repeat2'
+            port: 'in'
+        }, (err) ->
+          chai.expect(err).to.be.an 'error'
+          chai.expect(err.message).to.contain 'No process defined for inbound node'
+          done()


Tests: Generate skipped cases when no examples are present

--- spec/schemas.coffee
+++ spec/schemas.coffee
@@ -20,6 +20,7 @@
       try
         cases = lib.getExamples schemaName
       catch e
+        it.skip "does not have examples"
         return

       cases.forEach (testcase) ->


specify page_ids and courseId

specify page_ids and courseId

--- tutor/src/api/index.coffee
+++ tutor/src/api/index.coffee
@@ -95,8 +95,12 @@
   connectUpdate(CourseActions, pattern: 'courses/{id}')

   connectRead(CCDashboardActions, pattern: 'courses/{id}/cc/dashboard')
-  connectCreate(CoursePracticeActions, pattern: 'courses/{id}/practice')
   connectRead(CoursePracticeActions, pattern: 'courses/{id}/practice')
+
+  connectCreate(CoursePracticeActions,
+    url: ({courseId}) -> "courses/#{courseId}/practice"
+    data: ({courseId, query}) -> query
+  )

   connectRead(PerformanceForecast.Student.actions, pattern: 'courses/{id}/guide')
   connectRead(PerformanceForecast.Teacher.actions, pattern: 'courses/{id}/teacher_guide')


Enforce order of build tasks.

Enforce order of build tasks.

Fixes issue with race condition on build.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -6,17 +6,18 @@
 uglify = require 'gulp-uglify'
 wrap = require 'gulp-wrap-umd'

-gulp.task 'build', ->
-	sink = concat('main.js')
+gulp.task 'caffeinate', ->
+	gulp.src('src/*.coffee')
+		.pipe(coffee(bare: true)).on('error', gutil.log)
+		.pipe(gulp.dest('./tmp/build'))

-	gulp.src('lib/base64-binary.js')
-		.pipe(sink, end: false)
+	gulp.src('test/*.coffee')
+		.pipe(coffee()).on('error', gutil.log)
+		.pipe(gulp.dest('./tmp'))

-	gulp.src('src/validateSSH.coffee')
-		.pipe(coffee(bare: true)).on('error', gutil.log)
-		.pipe(sink)
-
-	sink
+gulp.task 'build', [ 'caffeinate' ], ->
+	gulp.src([ 'lib/*.js', 'tmp/build/*.js' ])
+		.pipe(concat('main.js'))
 		.pipe(wrap
 			exports: 'validateOpenSSHKey'
 		)


Use markitup-1x instead the old dependencie markitup

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -99,7 +99,7 @@
     paths.app + "vendor/jquery-flot/jquery.flot.time.js",
     paths.app + "vendor/flot-axislabels/jquery.flot.axislabels.js",
     paths.app + "vendor/jquery-textcomplete/jquery.textcomplete.js",
-    paths.app + "vendor/markitup/markitup/jquery.markitup.js",
+    paths.app + "vendor/markitup-1x/markitup/jquery.markitup.js",
     paths.app + "vendor/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js",
     paths.app + "vendor/raven-js/dist/raven.js",
     paths.app + "js/jquery.ui.git-custom.js",


Extract public names to variable

Extract public names to variable

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,6 +20,10 @@

 """

+config =
+    globalName: 'TminusLib'
+    distName: 'lib-tminus.js'
+
 # Build the full distribution, compiled and minified to a single file
 gulp.task 'build:distribution', ['clean'], ->
     return gulp.src 'src/lib.coffee',
@@ -28,8 +32,8 @@
         .pipe browserify
             transform: ['coffeeify']
             extensions: ['.coffee']
-            standalone: 'TminusLib'
-        .pipe rename 'lib-tminus.js'
+            standalone: config.globalName
+        .pipe rename config.distName
         .pipe header banner,
             pkg: pkg
         .pipe gulp.dest 'dist'


updating gulpfile

updating gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -56,7 +56,9 @@
   .pipe gulp.dest "contents/js"
   .pipe livereload();

-
+js = ()->
+  jsbabel()
+  jscoffee()

 gulp.task "newPost", ->
   knownOpts =
@@ -143,11 +145,7 @@
   css()

 gulp.task "js", ->
-  jsbabel()
-  jscoffee()
-
-gulp.task "clean_js", ->
-  del(['contents/js/babel.js','contents/js/coffee.js'])
+  js()

 gulp.task "build", ['css','js','build_wintersmith','image_min'], ->
   gutil.log gutil.colors.white.bgBlue("Build..."), "Complete"


tweak ngc

tweak ngc

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -319,7 +319,7 @@

     if utils.prod

-        $.exec "cd #{ utils.path.tmp() } ; ../node_modules/.bin/ngc",
+        $.exec "./node_modules/.bin/ngc -p #{ utils.path.tmp() }",

             (err, stdout, stderr) ->
                 $.debug stdout


Removed Gulp run deprecated method

Removed Gulp run deprecated method

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -180,8 +180,7 @@
     .pipe yml().on "error", gutil.log
     .pipe gulp.dest path.kitchensink + "/scaffolds"

-gulp.task "init", ->
-  gulp.run ["core", "standalone", "coffee", "stylus", "theme", "extensions", "docs", "kitchensink"]
+gulp.task "init", ["core", "standalone", "coffee", "stylus", "theme", "extensions", "docs", "kitchensink"]

 gulp.task "default", ->
   gulp.run ["webserver"]


Build: removed unnecessary deps

Build: removed unnecessary deps

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -14,12 +14,10 @@
 Q          = require 'q'
 fs         = require 'fs'
 http       = require 'http'
-coffee     = require 'coffee-script'
 argv       = require('minimist') process.argv
 source     = require 'vinyl-source-stream'
 gulpBuffer = require 'gulp-buffer'
 ecstatic   = require 'ecstatic'
-readdir    = require 'recursive-readdir'
 {exec}     = require 'child_process'

 pistachioCompiler = require 'gulp-pistachio-compiler'


fix return obj undefinded

fix return obj undefinded

--- src/BuscaCep.coffee
+++ src/BuscaCep.coffee
@@ -21,12 +21,13 @@

     populate: (data)->
       data = @returnConverter(data)
-      @setVal '[data-endereco-cidade-id]', data.city.id
-      @setVal '[data-endereco-cidade]', data.city.nome+'-'+data.city.uf
-      @setVal '[data-endereco-logradouro]', data.street.nome
-      @setVal '[data-endereco-bairro]', data.neighborhood.nome
+      if data.city
+        @setVal '[data-endereco-cidade-id]', data.city.id
+        @setVal '[data-endereco-cidade]', data.city.nome+'-'+data.city.uf
+      @setVal '[data-endereco-logradouro]', data.street.nome if data.street
+      @setVal '[data-endereco-bairro]', data.neighborhood.nome if data.neighborhood
       if Number(data.resultado) != 1
-        @setMessage data.mensagem
+        @setMessage data.message

     setMessage: (msg)->
       el = @find('[data-endereco-message]')


Sixth batch of Swedish translation. (#6132)

--- app/locale/sv.coco.coffee
+++ app/locale/sv.coco.coffee
@@ -1107,86 +1107,86 @@
     editor_config_behaviors_description: "Avsluta automatiskt hakparenteser, parenteser, och citat."

   about:
-#    title: "About CodeCombat - Engaging Students, Empowering Teachers, Inspiring Creation"
-#    meta_description: "Our mission is to level computer science through game-based learning and make coding accessible to every learner. We believe programming is magic and want learners to be empowered to to create things from pure imagination."
-#    learn_more: "Learn More"
+    title: "Om CodeCombat - Engagerande studenter, stärka lärare, inspirerande skapelse"
+    meta_description: "Vårt uppdrag är att planera datavetenskap genom spelbaserat lärande och göra kodning tillgänglig för alla elever. Vi tror att programmering är magiskt och vill att eleverna ska kunna skapa saker från fantasi."
+    learn_more: "Läs mer"
     main_title: "Om du vill lära dig programmera behlver du skriva (en hel del) kod."
     main_description: "PÅ CodeCombat, vårt jobb är att se till att du gör det med ett leende på läpparna."
     mission_link: "Uppdrag"
-#    team_link: "Team"
-#    story_link: "Story"
-#    press_link: "Press"
+    team_link: "Team"
+    story_link: "Berättelse"
+    press_link: "Press"
     mission_title: "Vårt uppdrag: göra programmering tillgängligt för varje elev på Jorden."
-#    mission_description_1: "Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code."
-#    mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming."
-#    team_title: "Meet the CodeCombat team"
-#    team_values: "We value open and respectful dialogue, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our GitHub contributors, because we value growth and learning in our team."
+    mission_description_1: "Programmering är magiskt. Det är förmågan att skapa saker från ren fantasi. Vi startade CodeCombat för att ge eleverna en känsla av trolldomskraft vid sina fingertoppar genom att använda kod."
+    mission_description_2: "Som vi ser, kan de också lära sig snabbare. MYCKET snabbare. Det är som att ha en konversation istället för att läsa en manual. Vi vill föra den konversationen till varje skola och till varje elev för att alla borde ha chansen att lära sig magin i programmeringen."
+    team_title: "Möt CodeCombat-teamet"
+    team_values: "Vi värdesätter en öppen och respektfull dialog där den bästa idén vinner. Våra beslut bygger på kundundersökningar och vår process är inriktad på att leverera konkreta resultat för dem. Alla medverkar, från vår VD till våra GitHub-bidragsgivare, eftersom vi värdesätter tillväxt och lärande i vårt team."
     nick_title: "Medgrundare" # {change}
-#    csm_title: "Customer Success Manager"
-#    ae_title: "Account Executive"
-#    sae_title: "Senior Account Executive"
-#    sism_title: "Senior Inside Sales Manager"
-#    shan_title: "Head of Marketing, CodeCombat Greater China"
-#    run_title: "Head of Operations, CodeCombat Greater China"
-#    lance_title: "Head of Technology, CodeCombat Greater China"
-#    zhiran_title: "Head of Curriculum, CodeCombat Greater China"
-#    yuqiang_title: "Head of Innovation, CodeCombat Greater China"
-#    swe_title: "Software Engineer"
-#    sswe_title: "Senior Software Engineer"
-#    css_title: "Customer Support Specialist"
-#    css_qa_title: "Customer Support / QA Specialist"
-#    maya_title: "Senior Curriculum Developer"
-#    bill_title: "General Manager, CodeCombat Greater China"
-#    pvd_title: "Product and Visual Designer"
-#    spvd_title: "Senior Product and Visual Designer"
-#    daniela_title: "Marketing Manager"
-#    bobby_title: "Game Designer"
-#    brian_title: "Senior Game Design Manager"
-#    stephanie_title: "Customer Support Specialist"
-#    sdr_title: "Sales Development Representative"
+    csm_title: "Kundframgångschef"
+    ae_title: "Kontoansvarig"
+    sae_title: "Senior kontoansvarig"
+    sism_title: "Senior försäljningschef"
+    shan_title: "Chef för marknadsföring, CodeCombat Kina"
+    run_title: "Verksamhetschef, CodeCombat Kina"
+    lance_title: "Teknikchef, CodeCombat Kina"
+    zhiran_title: "Chef för läroplanen, CodeCombat Kina"
+    yuqiang_title: "Innovationschef, CodeCombat Kina"
+    swe_title: "Mjukvaruingenjör"
+    sswe_title: "Senior mjukvaruingenjör"
+    css_title: "Kundtjänst specialist"
+    css_qa_title: "Kundtjänst / QA-specialist"
+    maya_title: "Senior kursplanutvecklare"
+    bill_title: "General Manager, CodeCombat Kina"
+    pvd_title: "Produkt- och visuell formgivare"
+    spvd_title: "Senior produkt- och visuell formgivare"
+    daniela_title: "Marknadsansvarig"
+    bobby_title: "Spel designer"
+    brian_title: "Senior spel designerchef"
+    stephanie_title: "Kundtjänst specialist"
+    sdr_title: "Försäljningsutvecklingsrepresentant"
     retrostyle_title: "Illustration"
     retrostyle_blurb: "RetroStyle Games"
-#    community_title: "...and our open-source community"
-#    bryukh_title: "Senior Gameplay Developer"
-#    oa_title: "Operations Associate"
-#    ac_title: "Administrative Coordinator"
-#    ea_title: "Executive Assistant"
-#    om_title: "Operations Manager"
-#    mo_title: "Manager, Operations"
-#    scd_title: "Senior Curriculum Developer"
-#    lcd_title: "Lead Curriculum Developer"
-#    vpm_title: "VP, Marketing"
-#    community_subtitle: "Over 500 contributors have helped build CodeCombat, with more joining every week!"
-#    community_description_3: "CodeCombat is a"
-#    community_description_link_2: "community project"
-#    community_description_1: "with hundreds of players volunteering to create levels, contribute to our code to add features, fix bugs, playtest, and even translate the game into 50 languages so far. Employees, contributors and the site gain by sharing ideas and pooling effort, as does the open source community in general. The site is built on numerous open source projects, and we are open sourced to give back to the community and provide code-curious players a familiar project to explore and experiment with. Anyone can join the CodeCombat community! Check out our"
-#    community_description_link: "contribute page"
-#    community_description_2: "for more info."
-#    number_contributors: "Over 450 contributors have lent their support and time to this project."
-#    story_title: "Our story so far"
-#    story_subtitle: "Since 2013, CodeCombat has grown from a mere set of sketches to a living, thriving game."
-#    story_statistic_1a: "20,000,000+"
-#    story_statistic_1b: "total players"
+    community_title: "... och vårt community med öppen källkod"
+    bryukh_title: "Senior spelutvecklare"
+    oa_title: "Operations Associate"
+    ac_title: "Administrativ koordinator"
+    ea_title: "Verkställande assistent"
+    om_title: "Verksamhetschef"
+    mo_title: "Chef, Verksamhet"
+    scd_title: "Senior kursplanutvecklare"
+    lcd_title: "Huvud kursplanutvecklare"
+    vpm_title: "VP, marknadsföring"
+    community_subtitle: "Över 500 bidragsgivare har hjälpt till att bygga CodeCombat, med fler som ansluter varje vecka!"
+    community_description_3: "CodeCombat är ett"
+    community_description_link_2: "community projekt"
+    community_description_1: "med hundratals spelare som frivilligt skapar nivåer, bidrar till vår kod för att lägga till funktioner, fixa buggar, test spela och till och med översätta spelet till 50 språk hittills. Anställda, bidragsgivare och webbplatsen får genom att dela idéer och samla insatser, liksom öppen källkod i allmänhet. Webbplatsen bygger på många open source-projekt, och vi är öppna för att ge tillbaka till samhället och ge kodnyfikna spelare ett välbekant projekt att utforska och experimentera med. Vem som helst kan gå med i CodeCombat-communityn! Kolla in vår"
+    community_description_link: "bidrag sida"
+    community_description_2: "för mer information."
+    number_contributors: "Över 450 bidragsgivare har gett sitt stöd och tid till detta projekt."
+    story_title: "Vår historia hittills"
+    story_subtitle: "Sedan 2013 har CodeCombat vuxit från bara en uppsättning skisser till ett levande, blomstrande spel."
+    story_statistic_1a: "20.000.000+"
+    story_statistic_1b: "totalt antal spelare"
     story_statistic_1c: "Har börjat sin programmeringsresa genom CodeCombat"
     story_statistic_2a: "Vi har översatt till över 50 languages — våra spelare kommer från"
     story_statistic_2b: "190+ länder"
-#    story_statistic_3a: "Together, they have written"
-#    story_statistic_3b: "1 billion lines of code and counting"
-#    story_statistic_3c: "across many different programming languages"
-#    story_long_way_1: "Though we've come a long way..."
-#    story_sketch_caption: "Nick's very first sketch depicting a programming game in action."
-#    story_long_way_2: "we still have much to do before we complete our quest, so..."
-#    jobs_title: "Come work with us and help write CodeCombat history!"
-#    jobs_subtitle: "Don't see a good fit but interested in keeping in touch? See our \"Create Your Own\" listing."
-#    jobs_benefits: "Employee Benefits"
-#    jobs_benefit_4: "Unlimited vacation"
-#    jobs_benefit_5: "Professional development and continuing education support – free books and games!"
-#    jobs_benefit_6: "Medical (gold), dental, vision, commuter, 401K"
-#    jobs_benefit_7: "Sit-stand desks for all"
-#    jobs_benefit_9: "10-year option exercise window"
-#    jobs_benefit_10: "Maternity leave: 12 weeks paid, next 6 @ 55% salary"
-#    jobs_benefit_11: "Paternity leave: 12 weeks paid"
-#    jobs_custom_title: "Create Your Own"
+    story_statistic_3a: "Tillsammans har de skrivit"
+    story_statistic_3b: "1 miljard rader kod, och mer blir det,"
+    story_statistic_3c: "på många olika programmeringsspråk"
+    story_long_way_1: "Även om vi har kommit långt..."
+    story_sketch_caption: "Nicks allra första skiss som visar ett programmeringsspel."
+    story_long_way_2: "vi har fortfarande mycket att göra innan vi slutför vårt uppdrag, så..."
+    jobs_title: "Kom och arbeta med oss och hjälp med att skriva CodeCombat-historia!"
+    jobs_subtitle: "Passar detta inte dig men är intresserad av att hålla kontakten? Se vår lista \"Skapa din egen\"."
+    jobs_benefits: "Anställningsförmåner"
+    jobs_benefit_4: "Obegränsad semester"
+    jobs_benefit_5: "Professionell utveckling och fortbildningsstöd - gratis böcker och spel!"
+    jobs_benefit_6: "Medicinsk (guld), tandvård, syn, pendlare, 401K"
+    jobs_benefit_7: "Höj och säkbart skrivbord för alla"
+    jobs_benefit_9: "10-årigt övningsfönster för optioner"
+    jobs_benefit_10: "Mammaledighet: 12 veckor betald, nästa 6 @ 55% lön"
+    jobs_benefit_11: "Pappaledighet: 12 veckor betald"
+    jobs_custom_title: "Skapa din egen"
 #    jobs_custom_description: "Are you passionate about CodeCombat but don't see a job listed that matches your qualifications? Write us and show how you think you can contribute to our team. We'd love to hear from you!"
 #    jobs_custom_contact_1: "Send us a note at"
 #    jobs_custom_contact_2: "introducing yourself and we might get in touch in the future!"


Improved code layout.

Improved code layout.

--- app/assets/javascripts/app/lib/app_init/log.coffee
+++ app/assets/javascripts/app/lib/app_init/log.coffee
@@ -1,31 +1,31 @@
 _instance = undefined
 class App.LogInclude
-  @log: ( level, args... ) ->
+  @log: (level, args...) ->
     if _instance == undefined
       _instance ?= new _Singleton
     module = @constructor.name
-    _instance.log( module, level, args )
+    _instance.log(module, level, args)

 class App.Log
-  @debug: ( module, args... ) ->
+  @debug: (module, args...) ->
     if _instance == undefined
       _instance ?= new _Singleton
-    _instance.log( module, 'debug', args )
+    _instance.log(module, 'debug', args)

-  @notice: ( module, args... ) ->
+  @notice: (module, args...) ->
     if _instance == undefined
       _instance ?= new _Singleton
-    _instance.log( module, 'notice', args )
+    _instance.log(module, 'notice', args)

-  @error: ( module, args... ) ->
+  @error: (module, args...) ->
     if _instance == undefined
       _instance ?= new _Singleton
-    _instance.log( module, 'error', args )
+    _instance.log(module, 'error', args)

-  @config: ( type, regex ) ->
+  @config: (type, regex) ->
     if _instance == undefined
       _instance ?= new _Singleton
-    _instance.config( type, regex )
+    _instance.config(type, regex)

   @timeTrack: (message) ->
     if _instance == undefined
@@ -81,14 +81,14 @@
     if window.localStorage
       window.localStorage.setItem('log_config', JSON.stringify(@currentConfig))

-  log: ( module, level, args ) ->
+  log: (module, level, args) ->
     if level is 'debug'
       return if !@currentConfigReady.module && !@currentConfigReady.content
       return if @currentConfigReady.module && !module.match(@currentConfigReady.module)
       return if @currentConfigReady.content && !args.toString().match(@currentConfigReady.content)
-    @_log( module, level, args )
+    @_log(module, level, args)

-  _log: ( module, level, args ) ->
+  _log: (module, level, args) ->
     prefixLength = 32
     prefix       = "App.#{module}(#{level})"
     if prefix.length < prefixLength


Avoid unnecessary AJAX calls in rendering dashboard and avoid popup blocking for downloading a file

--- app/assets/javascripts/vimeo_integration.js.coffee
+++ app/assets/javascripts/vimeo_integration.js.coffee
@@ -25,8 +25,7 @@
     languagesAsOptions: ->
       languages = verbalizeIt.dataContainer.languages
       langsArray = for languageCode, languageName of languages
-        "<option value=\"#{languageCode}\" data-language-name=#{languageName}>" +
-          languageName + "</option>"
+        "<option value=\"#{languageCode}\">#{languageName}</option>"
       langsArray.join("\n")

     languageChoice: ->
@@ -48,9 +47,10 @@

     reviewOrder: (task)->
       if task.fromLanguage is task.toLanguage
-        action = "Generating #{task.toLanguage} subtitles for this video"
+        action = "Generating #{@languageCodeToName task.toLanguage} subtitles for this video"
       else
-        action = "Translating this video from #{task.fromLanguage} to #{task.toLanguage}"
+        action = "Translating this video from #{@languageCodeToName task.fromLanguage}" +
+          " to #{@languageCodeToName task.toLanguage}"

       action + " will cost $#{task.cost}. " +
         " Would you like to proceed? <div class=\"btn signin-submit\" " +
@@ -155,20 +155,23 @@
       verbalizeIt.dashboardHandler.drawDashboard()

   dashboardHandler:
+    drawDashboardWithoutAjax: (message=null)->
+      $("#verbalizeit-caption-form").html(
+        verbalizeIt.templates.dashboard(message)
+      )
+
     drawDashboard: (message=null)->
       $.get "https://stagingapi.verbalizeit.com/tasks?auth_token=#{verbalizeIt.dataContainer.authToken}",
         (response)=>
           verbalizeIt.dataContainer.tasks = response
-          $("#verbalizeit-caption-form").html(
-            verbalizeIt.templates.dashboard(message)
-          )
+          @drawDashboardWithoutAjax message

   languageHandler:
     displayLanguages: ->
       $("#verbalizeit-caption-form").html(
         verbalizeIt.templates.languageChoice)

-    handleTaskSubmission: (fromLanguageCode, toLanguageCode, fromLanguage, toLanguage)->
+    handleTaskSubmission: (fromLanguageCode, toLanguageCode)->
       videoID = $('link[rel=canonical]').attr('href').split('/')[1]
       $.post "https://stagingapi.verbalizeit.com/tasks/vimeo",
         {
@@ -177,12 +180,12 @@
           source_language: fromLanguageCode,
           target_language: toLanguageCode
         }, (response)->
-          task = {
-            id: response.task_id,
-            cost: response.cost,
-            fromLanguage: fromLanguage,
-            toLanguage: toLanguage
-          }
+          task =
+            id: response.task_id
+            cost: response.cost
+            fromLanguage: fromLanguageCode
+            toLanguage: toLanguageCode
+
           verbalizeIt.taskHandler.displayTask(task)

   taskHandler:
@@ -193,11 +196,14 @@
       $("#verbalizeit-caption-form").off 'click.submitTask'
       $("#verbalizeit-caption-form").on 'click.submitTask', "#verbalizeit-submit-task", ->
         $.ajax "https://stagingapi.verbalizeit.com/tasks/#{task.id}/start?auth_token=#{verbalizeIt.dataContainer.authToken}",
-          success: ->
-            submitted = "Success! Your request will be processed within 24 hours."
-            verbalizeIt.dashboardHandler.drawDashboard(submitted)
           type:
             'PUT'
+          success: (submittedTask)->
+            submitted = "Success! Your request will be processed within 24 hours."
+            submittedTask.source_language = task.fromLanguage
+            submittedTask.target_language = task.toLanguage
+            verbalizeIt.dataContainer.tasks.push submittedTask
+            verbalizeIt.dashboardHandler.drawDashboardWithoutAjax(submitted)

   binders:
     uploadToggles: ->
@@ -225,17 +231,21 @@
     submitTaskButton: ->
       $("#verbalizeit-caption-form").on "click", "#verbalizeit-choose-language", ->
         fromLanguageCode = $("#verbalizeIt-choose-from-language").val()
-        fromLanguage = $("option[value=#{fromLanguageCode}").data("language-name")
         toLanguageCode = $("#verbalizeIt-choose-to-language").val()
-        toLanguage = $("option[value=#{toLanguageCode}").data("language-name")
         verbalizeIt.languageHandler.handleTaskSubmission(
-          fromLanguageCode, toLanguageCode, fromLanguage, toLanguage)
+          fromLanguageCode, toLanguageCode)

     downloadFileLink: ->
-      $("#verbalizeit-caption-form").on "click", ".verbalizeit-download-link", ->
+      $("#verbalizeit-caption-form").on "click", ".verbalizeit-download-link", (e)->
+        e.preventDefault()
         taskID = $(@).data("task-id")
-        $.get "https://stagingapi.verbalizeit.com/tasks/#{taskID}?auth_token=#{verbalizeIt.dataContainer.authToken}", (response)->
-          window.open(response.completed_file, "_blank")
+        $.ajax
+          url: "https://stagingapi.verbalizeit.com/tasks/#{taskID}?auth_token=#{verbalizeIt.dataContainer.authToken}"
+          async: false
+          success:  (response)->
+            window.open(response.completed_file, "_blank")
+          error: ->
+            $("#verbalizeit-caption-form").find()

   getLanguages: ->
     $.get "https://stagingapi.verbalizeit.com/api/languages", (data)->


Add four ozaria level songs

Add four ozaria level songs

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -154,6 +154,10 @@
     music: c.object {title: 'Music', description: 'Control music playing'},
       play: {title: 'Play', type: 'boolean'}
       file: c.shortString(title: 'File', enum: [
+        '/music/OzariaPhoenixLandsMusicLoop4'
+        '/music/OzariaPhoenixLandsMusicLoop3'
+        '/music/OzariaPhoenixLandsMusicLoop2b'
+        '/music/OzariaPhoenixLandsMusicLoop1b'
         '/music/OzariaCarnivalMusicLoop1',
         '/music/OzariaCarnivalMusicLoop2',
         '/music/OzariaCarnivalMusicLoop3',


buildSchoolGraph script working well

buildSchoolGraph script working well

--- scripts/buildSchoolGraph.coffee
+++ scripts/buildSchoolGraph.coffee
@@ -4,6 +4,7 @@
 mongoose = require 'mongoose'
 log = require 'winston'
 async = require 'async'
+moment = require 'moment'

 ### SET UP ###
 do (setupLodash = this) ->
@@ -19,6 +20,8 @@

 startDate = new Date 2015, 11, 1

+debugging = false
+
 query = dateCreated: {$gt: startDate}, emailLower: {$exists: true}
 selection = 'name emailLower schoolName courseInstances clans ageRange dateCreated referrer points lastIP hourOfCode preferredLanguage lastLevel'
 User.find(query).select(selection).lean().exec (err, users) ->
@@ -26,10 +29,11 @@
   log.info "Found #{usersWithSchools.length} users of #{users.length} users registered after #{startDate}."
   nextPrompt users

-nextPrompt = (users, question) ->
+nextPrompt = (users, question, userToSchool, suggestions) ->
   # We look for the next top user to classify based on the number of suggestions we can make about what the school name should be.
   sortUsers users
-  return console.log('Done.') or process.exit() unless [userToSchool, suggestions] = findUserToSchool users
+  unless userToSchool
+    return console.log('Done.') or process.exit() unless [userToSchool, suggestions] = findUserToSchool users
   question ?= formatSuggestions userToSchool, suggestions
   prompt question, (answer) ->
     answer = answer.trim()
@@ -41,7 +45,10 @@
       return finalizePrompt userToSchool, suggestions, schoolName, users
     else if answer.length < 10
       console.log "#{answer}? That's kind of short--I don't think school names and locations can be this short. What should it really be?"
-      return nextPrompt users, "> "
+      return nextPrompt users, "> ", userToSchool, suggestions
+    else unless /,.+,/.test answer
+      console.log "#{answer}? We need the full location (with two commas), like Example High School, Springfield, IL. What should it really be?"
+      return nextPrompt users, "> ", userToSchool, suggestions
     else
       return finalizePrompt userToSchool, suggestions, answer, users

@@ -79,7 +86,7 @@
       nextPrompt remainingUsers

 findNumbers = (answer, max) ->
-  numbers = (parseInt(d, 10) for d in (' ' + answer + ' ').match(/ (\d+) /g) ? [])
+  numbers = (parseInt(d, 10) for d in (' ' + answer + ' ').replace(/ /g, '  ').match(/ (\d+) /g) ? [])
   ranges = answer.match(/(\d+-\d+)/g) or []
   for range in ranges
     bounds = (parseInt(d, 10) for d in range.split('-'))
@@ -90,12 +97,14 @@
       console.log "Incorrect number #{number} higher than max: #{max}"
   numbers

-formatUser = (user) ->
+formatUser = (user, relativeToUser) ->
   # TODO: replace date string with relative time since signup compared to target user, and actually make suggestions based on students that signed up at almost the same time
-  _.values(_.pick(user, ['name', 'emailLower', 'ageRange', 'dateCreated', 'lastLevel', 'points', 'referrer', 'hourOfCode'])).join('  ')
+  props = _.pick(user, ['name', 'emailLower', 'ageRange', 'dateCreated', 'lastLevel', 'points', 'referrer', 'hourOfCode'])
+  props.dateCreated = if relativeToUser then moment(props.dateCreated).from(relativeToUser.dateCreated) else moment(props.dateCreated).fromNow()
+  _.values(props).join('  ')

 formatSuggestions = (userToSchool, suggestions) ->
-  suggestionPrompts = ("#{_.str.rpad(i + 1, 3)}  #{_.str.rpad(s.schoolName, 50)} #{s.reasons.length} #{if s.reasons.length > 1 then 'Matches' else 'Match'}: #{s.reasons.join(', ')}\tfrom user: #{formatUser(s.user)}" for s, i in suggestions).join('\n')
+  suggestionPrompts = ("#{_.str.rpad(i + 1, 3)}  #{_.str.rpad(s.schoolName, 50)} #{s.reasons.length} #{if s.reasons.length > 1 then 'Matches' else 'Match'}: #{s.reasons.join(', ')}\tfrom user: #{formatUser(s.user, userToSchool)}" for s, i in suggestions).join('\n')
   """
   What should the school for this user be?
   0    #{_.str.rpad(userToSchool.schoolName, 50)} #{formatUser(userToSchool)}
@@ -104,20 +113,24 @@
   Choose a number, type a name, enter to skip, or q to quit.
   > """

+checkedTopGroups = {}
 findUserToSchool = (users) ->
   # We find the top user from the top group that we can make the most reasoned suggestions about what the school name would be.
-  [bestTarget, bestTargetSuggestions, mostReasons] = [null, [], 0]
+  [bestTarget, bestTargetSuggestions, mostReasons, bestGroup] = [null, [], 0, null]
   for field, groups of topGroups
-    for nextLargestGroup in groups
+    for nextLargestGroup in groups when not checkedTopGroups[nextLargestGroup]
       possibleTargets = userCategories[field][nextLargestGroup]
-      schoolNames = _.uniq possibleTargets, 'schoolName'
+      schoolNames = (t.schoolName for t in _.uniq possibleTargets, 'schoolName')
       # TODO: better method to avoid showing users where everyone in the suggestion already has the same school (because we have already done this group)
-      for schoolName in schoolNames
-        if _.filter(possibleTargets, schoolName: schoolName).length > 0.5 * possibleTargets.length
+      alreadyDone = false
+      for schoolName in schoolNames when schoolName?.length > 10 and /,.+,/.test schoolName  # Long enough school name with location info (two commas)
+        sharedCount = _.filter(possibleTargets, schoolName: schoolName).length
+        if sharedCount > 0.5 * possibleTargets.length
+          console.log 'Already done', schoolName, sharedCount, possibleTargets.length, 'for', field, nextLargestGroup
           alreadyDone = true
       continue if alreadyDone
       nSamples = Math.min 15, Math.max(4, Math.floor possibleTargets.length / 20)
-      console.log 'Checking', nSamples, 'samples of', possibleTargets.length, 'players in the biggest', field, 'group:', nextLargestGroup
+      if debugging then console.log 'Checking', nSamples, 'samples of', possibleTargets.length, 'players in the biggest', field, 'group:', nextLargestGroup
       for i in [0 ... nSamples]
         target = possibleTargets[Math.floor i * possibleTargets.length / (nSamples + 1)]
         suggestions = findSuggestions target
@@ -130,26 +143,28 @@
                 when 'Name' then 30
                 when 'Referrer' then 20
                 when 'Domain' then (if getDomain(target) is 'cps.edu' then 1 else 10)
-                when 'Clans' then 0.1
+                when 'Clans' then 0.01
           sum
         ), 0
         if reasons > mostReasons
           bestTarget = target
           bestTargetSuggestions = suggestions
           mostReasons = reasons
+          bestGroup = nextLargestGroup
       break
+  checkedTopGroups[bestGroup] = true
   return [bestTarget, bestTargetSuggestions]

 findSuggestions = (target) ->
   # Look for other users with the same IP, course instances, clans, or similar school names or non-common shared email domains.
   suggestions = []
   t0 = new Date()
-  console.log '  Checking suggestions for', target.emailLower, target.schoolName, (new Date()) - t0
+  if debugging then console.log '  Checking suggestions for', target.emailLower, target.schoolName, (new Date()) - t0
   if target.lastIP
     for otherUser in (userCategories.lastIP[target.lastIP] ? []) when otherUser isnt target
       suggestions.push schoolName: otherUser.schoolName, reasons: ['IP'], user: otherUser
   for leagueType in ['courseInstances', 'clans']
-    console.log '    Now checking', leagueType, (new Date()) - t0
+    if debugging then console.log '    Now checking', leagueType, (new Date()) - t0
     if target[leagueType]?.length
       for league in target[leagueType]
         for otherUser in (userCategories[leagueType][league] ? []) when otherUser isnt target
@@ -159,7 +174,7 @@
           else
             suggestions.push schoolName: otherUser.schoolName, reasons: [reason], user: otherUser
   if target.schoolName?.length > 5
-    console.log '    Now checking schoolName', (new Date()) - t0
+    if debugging then console.log '    Now checking schoolName', (new Date()) - t0
     nameMatches = []
     for otherSchoolName in topGroups.schoolName
       score = stringScore otherSchoolName, target.schoolName, 0.8
@@ -173,7 +188,7 @@
           existingSuggestion.reasons.push reason
         else
           suggestions.push schoolName: match, reasons: [reason], user: otherUser
-  console.log '    Now checking domain', (new Date()) - t0
+  if debugging then console.log '    Now checking domain', (new Date()) - t0
   if domain = getDomain target
     for otherUser in (userCategories.domain[domain] ? []) when otherUser isnt target
       reason = "Domain"
@@ -181,7 +196,7 @@
         existingSuggestion.reasons.push reason
       else
         suggestions.push schoolName: otherUser.schoolName, reasons: [reason], user: otherUser
-  console.log '    Now checking referrer', (new Date()) - t0
+  if debugging then console.log '    Now checking referrer', (new Date()) - t0
   if referrer = getReferrer target
     for otherUser in (userCategories.referrer[referrer] ? []) when otherUser isnt target
       reason = "Referrer"
@@ -189,6 +204,7 @@
         existingSuggestion.reasons.push reason
       else
         suggestions.push schoolName: otherUser.schoolName, reasons: [reason], user: otherUser
+  if debugging then console.log '    Done checking referrer', (new Date()) - t0
   suggestions = _.sortBy suggestions, 'schoolName'
   suggestions = _.sortBy suggestions, (s) -> -s.reasons.length
   return suggestions
@@ -226,20 +242,21 @@
 getDomain = (user) ->
   return null unless domain = user.emailLower.split('@')[1]
   return null if commonEmailDomainMap[domain]
-  typo = typoCache[domain]
-  return null if typo
-  return domain if typo is false
-  typo = _.find commonEmailDomains, (commonDomain) -> stringScore(commonDomain, domain, 0.8) > 0.9
-  typoCache[domain] = Boolean(typo)
-  return null if typo
+  # Too slow? Is this actually slow?
+  #typo = typoCache[domain]
+  #return null if typo
+  #return domain if typo is false
+  #typo = _.find commonEmailDomains, (commonDomain) -> stringScore(commonDomain, domain, 0.8) > 0.9
+  #typoCache[domain] = Boolean(typo)
+  #return null if typo
   domain

-commonReferrersRegex = /(google|bing\.|yahoo|duckduckgo|jobs\.lever|code\.org|twitter|facebook|dollarclick|stumbleupon|vk\.com|playpcesor|reddit|lifehacker|favorite|bnext|freelance|taringa|blogthinkbig|graphism|inside\.com|korben|habrahabr|iplaysoft|geekbrains|playground|ycombinator|github\.com)/
+commonReferrersRegex = /(google|bing\.|yahoo|duckduckgo|jobs\.lever|code\.org|twitter|facebook|dollarclick|stumbleupon|vk\.com|playpcesor|reddit|lifehacker|favorite|bnext|freelance|taringa|blogthinkbig|graphism|inside\.com|korben|habrahabr|iplaysoft|geekbrains|playground|ycombinator|github)/
 getReferrer = (user) ->
   return null unless referrer = user.referrer?.toLowerCase().trim()
   referrer = referrer.replace /^https?:\/\//, ''
   return null if commonReferrersRegex.test referrer
-  return classCode if classCode = referrer.match(/\?_cc=(\S+)/)?[1]
+  return classCode if classCode = referrer.match(/\?_cc=(\S+)$/)?[1]
   return null if /codecombat/.test referrer
   referrer

@@ -316,7 +333,7 @@
   "virgin.net", "wanadoo.co.uk", "bt.com",

   # Domains used in Asia
-  "sina.com", "qq.com", "naver.com", "hanmail.net", "daum.net", "nate.com", "yahoo.co.jp", "yahoo.co.kr", "yahoo.co.id", "yahoo.co.in", "yahoo.com.sg", "yahoo.com.ph",
+  "sina.com", "qq.com", "naver.com", "hanmail.net", "daum.net", "nate.com", "yahoo.co.jp", "yahoo.co.kr", "yahoo.co.id", "yahoo.co.in", "yahoo.com.sg", "yahoo.com.ph", "yahoo.com.tw"

   # French ISP domains
   "hotmail.fr", "live.fr", "laposte.net", "yahoo.fr", "wanadoo.fr", "orange.fr", "gmx.fr", "sfr.fr", "neuf.fr", "free.fr",
@@ -337,4 +354,4 @@
   "hotmail.com", "gmail.com", "yahoo.com.mx", "live.com.mx", "yahoo.com", "hotmail.es", "live.com", "hotmail.com.mx", "prodigy.net.mx", "msn.com"
 ]
 commonEmailDomainMap = {}
-commonEmailDomainMap[domain] = true for domain in commonEmailDomainMap
+commonEmailDomainMap[domain] = true for domain in commonEmailDomains


Fixed host stats

Fixed host stats

--- src/api/host.coffee
+++ src/api/host.coffee
@@ -329,7 +329,7 @@
     memoryFree.push(Math.round(parseInt(n.v[memoryFreeIndex])))
     memoryUsed.push(Math.round(parseInt(n.v[memoryIndex])-(n.v[memoryFreeIndex])))
     memory.push(parseInt(n.v[memoryIndex]))
-    load.push(n.v[loadIndex])
+    load.push(if n.v[loadIndex] == 'NaN' then null else n.v[loadIndex])
     date.push(baseDate - (dateStep * (numStep - key)))
     # build the multi dimensional arrays
     $forEach cpusIndexes, (value, key) ->


keep spine up to date

keep spine up to date

--- scripts/gh-book/opf-file.coffee
+++ scripts/gh-book/opf-file.coffee
@@ -63,6 +63,9 @@
           options.doNotReparse = true
           @navModel.set 'body', @_serializeNavModel(), options

+          # if we're updating the nav the spine also probably needs to be updated
+          @_buildSpine()
+
       @tocNodes.on 'add', (model, collection, options) =>
         if not options.doNotReparse
           # Keep track of local changes if there is a remote conflict
@@ -173,6 +176,7 @@
         allContent.add(module)
         @addChild(module) # for the nav file
         @_addItem(module) # for this opf file
+

       clearTimeout(@_savingTimeout)
       @_savingTimeout = setTimeout (() =>
@@ -266,6 +270,23 @@
       @getChildren().reset([], {doNotReparse:true})
       recBuildTree(@getChildren(), $root, @navModel.id)

+    _buildSpine: ->
+
+      start = @serialize()
+      spine = @$xml.find('spine').empty()
+
+      update = (model) =>
+        if model.mediaType == XhtmlFile::mediaType
+          $('<itemref />')
+            .attr('idref', Utils.relativePath(@id, model.id))
+            .appendTo(spine)
+
+        if model.getChildren?().first()
+          model.getChildren().forEach update
+
+      @getChildren().forEach update
+
+      @_markDirty({}) if start != @serialize()

     _serializeNavModel: () ->
       $body = $(@navModel.get 'body')


Fix bug

Fix bug

--- source/assets/javascripts/main.js.coffee
+++ source/assets/javascripts/main.js.coffee
@@ -22,6 +22,8 @@
 # PUSHDOWN
 # ========

+  isPanelOpen = false
+
   pushIt = () ->
     extra = $('#extra')
     if extra.css('top') == '0px'
@@ -31,6 +33,7 @@
     return

   openPanel = () ->
+    isPanelOpen = true
     $('#panel').slideDown(1500)
     $('#banner').fadeOut(1200)
     pushIt('#extra')
@@ -38,6 +41,7 @@
     return

   closePanel = () ->
+    isPanelOpen = false
     $('#panel').slideUp(1500)
     $('#banner').fadeIn(2000)
     pushIt('#extra')
@@ -85,7 +89,7 @@
       stopTimeout()
     document.getElementById('panel').onmouseout = ->
       isMouseOver = false
-      startTimeout()
+      startTimeout() if isPanelOpen == true
     return


Remove legacy code from welcome-to-zihub

--- scripts/welcome-to-zihub.coffee
+++ scripts/welcome-to-zihub.coffee
@@ -56,7 +56,6 @@
       if user.name in robot.brain.data.nicks
         robot.logger.debug "Already know #{user.name}"
         return
-      #      robot.adapter.client.chat.postMessage(user.name, welcomeMsg(user.name))
       robot.messageRoom user.name, welcomeMsg(user.name)
       robot.messageRoom '#presentaciones', "Hola @#{user.name}"
       add_nicks user.name


Fix MCQ5a snippets

Fix MCQ5a snippets

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -72,7 +72,7 @@
   'MCQ5a':
     'prefix': 'mcq5a'
     'body': """
-      [TYPE MCQ5a][$1:true]
+      [TYPE MCQ5a][${1:true}]
       Audio(question=true): $2
       Audio(matching=true): $3
     """


CUI

CUI

- fixed "decodeUrlData": when an empty string was passed,
  the map returned contained bogus data.

--- src/base/CUI.coffee
+++ src/base/CUI.coffee
@@ -551,9 +551,16 @@
 			decode_func = (v) -> decodeURIComponent(v)

 		for part in url.split(connect)
-			pair = part.split(connect_pair)
-			key = decode_func(pair[0])
-			value = decode_func(pair[1])
+			if part.length == 0
+				continue
+
+			if part.indexOf(connect_pair) > -1
+				pair = part.split(connect_pair)
+				key = decode_func(pair[0])
+				value = decode_func(pair[1])
+			else
+				key = decode_func(part)
+				value = ""

 			if use_array
 				if not params[key]


Fix connections stuck in CLOSE_WAIT

Fix connections stuck in CLOSE_WAIT

--- server/controllers/photo.coffee
+++ server/controllers/photo.coffee
@@ -149,7 +149,13 @@

 doPipe = (req, which, download, res, next) ->

+    connectionClosed = false
+    req.on 'close', -> connectionClosed = true
+    res.on 'close', -> connectionClosed = true
+
     sharing.checkPermissionsPhoto req.photo, 'r', req, (err, isAllowed) ->
+
+        return if connectionClosed

         return next err if err

@@ -159,32 +165,21 @@
             disposition = 'attachment; filename=' + req.photo.title
             res.setHeader 'Content-disposition', disposition

-
-        # support both old style _attachment photo and new style binary photo
-        onError = (err) -> next err if err
-
         errorFile = path.join __dirname, '..', 'img', 'error.gif'

         if req.photo._attachments?[which]
             binaryPath = "/data/#{req.photo.id}/attachments/#{which}"
-            request = downloader.download binaryPath, (stream) ->
-                if stream.statusCode is 200
-                    res.on 'close', -> request.abort()
-                    stream.pipe res
-                else
-                    return res.sendFile errorFile
-
         else if req.photo.binary?[which]
             binaryPath = "/data/#{req.photo.id}/binaries/#{which}"
-            request = downloader.download binaryPath, (stream) ->
-                if stream.statusCode is 200
-                    res.on 'close', -> request.abort()
-                    stream.pipe res
-                else
-                    return res.sendFile errorFile
-
         else
             return res.sendFile errorFile
+
+        request = downloader.download binaryPath, (stream) ->
+            if stream.statusCode is 200
+                stream.pipe res
+            else
+                return res.sendFile errorFile
+        res.on 'close', -> request.abort()

         # This is a temporary hack to allow caching
         # ideally, we would do as follow :


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -43,7 +43,7 @@
   @param {Function} callback The function to be called when the specified event
   is triggered.
   ###
-  bind: (events, callback) ->
+  bind: (namespa, callback) ->
     events.split(" ").each (namespacedEvent) ->
       [event, namespace] = namespacedEvent.split(".")


Don't assume that the model always exists on the binding when applying formatters to a value.

--- src/bindings.coffee
+++ src/bindings.coffee
@@ -42,7 +42,7 @@
       args = formatter.split /\s+/
       id = args.shift()

-      formatter = if @model[id] instanceof Function
+      formatter = if @model?[id] instanceof Function
         @model[id]
       else
         @view.formatters[id]


関数名がわかりづらかったので変更。不要なコードを削除

関数名がわかりづらかったので変更。不要なコードを削除

--- src/calc-box.coffee
+++ src/calc-box.coffee
@@ -65,7 +65,7 @@
   ###
   最大辺同士を合わせた際に，対応する辺を導き出す
   ###
-  _adjustSide: (box, parcel)->
+  _getTableOfSides: (box, parcel)->
     ASIS =
       width: 'width'
       height: 'height'
@@ -78,8 +78,18 @@
       width: 'depth'
       height: 'width'
       depth: 'height'
-    # @FIXME: ねじれた位置での比較が考慮されていない。
-    # 上記3種以外にheight,depthの2辺の順番違いの条件が存在する。
+    # EXCHANGE_SIDE_1 =
+    #   width: 'width'
+    #   height: 'depth'
+    #   depth: 'height'
+    # EXCHANGE_SIDE_2 =
+    #   width: 'depth'
+    #   height: 'height'
+    #   depth: 'width'
+    # EXCHANGE_SIDE_3  =
+    #   width: 'height'
+    #   height: 'width'
+    #   depth: 'depth'

     switch box.side
       when 'width'
@@ -183,37 +193,11 @@
       if longestSideOfParcel.value <= BoxSide.value
         debug "longestSideOfParcel.side = #{longestSideOfParcel.side}"
         debug "BoxSide.side = #{BoxSide.side}"
-        table = @_adjustSide longestSideOfParcel, BoxSide
+        table = @_getTableOfSides longestSideOfParcel, BoxSide
         for k,v of table
           debug "box[#{k}]@#{@[k]} : parcel[#{v}]@#{parcel[v]}"

         @_pushParcel(parcel, table)
         break

-
     true
-
-
-    # 箱を最大限に活かすために，
-    # 箱の最短辺と荷物の長辺を比較．
-    # 入ればそれで箱の容量を減らす．
-    # 入らなければ次に短い荷物の辺を比較
-    # 以下繰り返し
-
-    # sides = @_sortSide parcel
-    # shortestSideOfBox = @_shortestSide @
-    # # 箱の最短辺と荷物の長辺を比較して
-    # for side in sides
-    #   debug "parcel[#{side.side}] = #{parcel[side.side]}"
-    #   if shortestSideOfBox.value > side.value
-    #     debug "shortestSideOfBox.side = #{shortestSideOfBox.side}"
-    #     debug "side.side = #{side.side}"
-    #     table = @_adjustSide shortestSideOfBox, side
-    #     for k,v of table
-    #       debug "box[#{k}]@#{@[k]} : parcel[#{v}]@#{parcel[v]}"
-    #
-    #     @_pushParcel(parcel, table)
-    #     break
-    #
-    #
-    # true


spec: Prevent callback of beginFrameSubscription being called twice

--- spec/api-browser-window-spec.coffee
+++ spec/api-browser-window-spec.coffee
@@ -306,8 +306,11 @@

   describe 'beginFrameSubscription method', ->
     it 'subscribes frame updates', (done) ->
+      called = false
       w.loadUrl "file://#{fixtures}/api/blank.html"
       w.webContents.beginFrameSubscription (data) ->
+        return if called
+        called = true
         assert.notEqual data.length, 0
         w.webContents.endFrameSubscription()
         done()


changed console output to be conditional debug

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -17,13 +17,15 @@
 fs     = require 'fs'
 path   = require 'path'

+console.debug = console.log if process.env.yang_debug?
+
 YANG_SPEC_SCHEMA = yaml.Schema.create [

   new yaml.Type '!require',
     kind: 'scalar'
     resolve:   (data) -> typeof data is 'string'
     construct: (data) ->
-      console.log "processing !require using: #{data}"
+      console.debug? "processing !require using: #{data}"
       require data
       #require (path.resolve pkgdir, data)

@@ -43,7 +45,7 @@
     kind: 'scalar'
     resolve:   (data) -> typeof data is 'string'
     construct: (data) =>
-      console.log "processing !yang using: #{data}"
+      console.debug? "processing !yang using: #{data}"
       (new Compiler {}).parse data
 ]

@@ -184,7 +186,7 @@
             delete extension[ext]
           map.define 'extension', name, extension
         delete schema.extension
-        console.log "[preprocess:#{map.name}] found #{extensions.length} new extension(s)"
+        console.debug? "[preprocess:#{map.name}] found #{extensions.length} new extension(s)"
         continue

       ext = map.resolve 'extension', key
@@ -210,7 +212,7 @@
             when typeof arg is 'string' and arg.length > 50
               ((arg.replace /\s\s+/g, ' ').slice 0, 50) + '...'
             else arg
-          console.log "[preprocess:#{map.name}] #{key} #{argument} " + if params? then "{ #{Object.keys params} }" else ''
+          console.debug? "[preprocess:#{map.name}] #{key} #{argument} " + if params? then "{ #{Object.keys params} }" else ''
           params ?= {}
           @preprocess params, map, ext
           try
@@ -253,14 +255,14 @@
       continue unless ext.construct instanceof Function

       unless ext.argument?
-        console.log "[compile:#{map.name}] #{key} " + if val instanceof Object then "{ #{Object.keys val} }" else val
+        console.debug? "[compile:#{map.name}] #{key} " + if val instanceof Object then "{ #{Object.keys val} }" else val
         children = @compile val, map
         output[key] = ext.construct.call map, key, val, children, output, ext
         delete output[key] unless output[key]?
       else
         for arg in (extractKeys val)
           params = if val instanceof Object then val[arg]
-          console.log "[compile:#{map.name}] #{key} #{arg} " + if params? then "{ #{Object.keys params} }" else ''
+          console.debug? "[compile:#{map.name}] #{key} #{arg} " + if params? then "{ #{Object.keys params} }" else ''
           params ?= {}
           children = @compile params, map unless key is 'specification'
           try


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -10,6 +10,7 @@

   cooldown: (name, options={}) ->
     {target, approachBy, value} = options
+
     target ||= 0
     approachBy = 1 unless approachBy?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -12,7 +12,7 @@
     player.update(1)

     player.I.health
-
+    # =>

     # Shoot Timeout
     player = GameObject()


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -26,7 +26,7 @@
 # => `velocity is {x: 2, y: 0} and position is {x: 5, y: 0}`
 </pre></code>

-@name Movable
+@name Cooldown
 @module
 @constructor
 @param {Object} I Instance variables


collections not available on import (of course)

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -83,9 +83,10 @@
     @debug = @debug_off

   @collections = []
-  while collection = Zotero.nextCollection()
-    @debug('adding collection:', collection)
-    @collections.push(@sanitizeCollection(collection))
+  if Zotero.nextCollection
+    while collection = Zotero.nextCollection()
+      @debug('adding collection:', collection)
+      @collections.push(@sanitizeCollection(collection))

 # The default collection structure passed is beyond screwed up.
 Translator.sanitizeCollection = (coll) ->


Simplification de la modification de la santé de l'avion

--- js/game/entities/Plane.coffee
+++ js/game/entities/Plane.coffee
@@ -64,8 +64,18 @@
                 fireShotSprite: new BulletFireShot @

             # Santé de l'avion
-            @health = 100
             @destroyed = no
+            health = 100
+            @get 'health', -> health
+
+            # Augmenter/réduire la santé restante
+            @set 'health', (newHealth) =>
+                if newHealth < health
+                    @parent.addChild new PlaneExplosion @parent, @centerX + @width/3, @centerY
+
+                health = newHealth
+                @destroyed = yes if health <= 0
+

             # Effet de fumée
             @smokeEffect =
@@ -200,8 +210,4 @@
                     if poule? and @hitBox.isCollidingWith(poule.hitBox)
                         @health -= 10
                         poule.explode()
-
-        # Détruire
-        explode: ->
-            @destroyed = yes
-            @parent.addChild new PlaneExplosion @parent, @centerX, @centerY+


refactor

refactor

--- src/coverage.coffee
+++ src/coverage.coffee
@@ -6,95 +6,85 @@
 wrench = require 'wrench'
 coffee = require 'coffee-script'

-reservedWords = [
-  'break'
-  'case'
-  'catch'
-  'continue'
-  'debugger'
-  'default'
-  'delete'
-  'do'
-  'else'
-  'finally'
-  'for'
-  'function'
-  'if'
-  'in'
-  'instanceof'
-  'new'
-  'return'
-  'switch'
-  'this'
-  'throw'
-  'try'
-  'typeof'
-  'var'
-  'void'
-  'while'
-  'with'
-
-  # These are not keyword according to JavaScript, but JSCoverage treats them as if they were.
-  # Just follow suit...
-  'throws'
-]
-
-isValidIdentifier = (name) ->
-  name? && (name.toString().match(/^[_a-zA-Z][_a-zA-Z0-9]*$/) || name.toString().match(/^[1-9][0-9]*$/)) && reservedWords.indexOf(name) == -1
-
-exports.rewriteSource = (code, filename) ->
-
-  injectList = []
-
-  inject = (x) ->
-    injectList.push(x.loc.start.line)
-
-    type: 'ExpressionStatement'
-    expression:
-      type: 'UpdateExpression'
-      operator: '++'
-      prefix: false
+
+# Tempting to use eval here, but I went with a more verbose solution
+evalBinaryExpression = do ->
+  methods =
+    '+': (x, y) -> x + y
+    '-': (x, y) -> x - y
+    '*': (x, y) -> x * y
+    '%': (x, y) -> x % y
+    '/': (x, y) -> x / y
+    '<<': (x, y) -> x << y
+    '>>': (x, y) -> x >> y
+    '>>>': (x, y) -> x >>> y
+  (arg1, op, arg2) ->
+    method = methods[op]
+    throw "operator not supported" if !method?
+    method(arg1, arg2)
+
+
+isValidIdentifier = do ->
+  reservedWords = [
+    'break'
+    'case'
+    'catch'
+    'continue'
+    'debugger'
+    'default'
+    'delete'
+    'do'
+    'else'
+    'finally'
+    'for'
+    'function'
+    'if'
+    'in'
+    'instanceof'
+    'new'
+    'return'
+    'switch'
+    'this'
+    'throw'
+    'try'
+    'typeof'
+    'var'
+    'void'
+    'while'
+    'with'
+
+    # These are not keyword according to JavaScript, but JSCoverage treats them as if they were.
+    # Just follow suit...
+    'throws'
+  ]
+
+
+  (name) ->
+    name? && !(name in reservedWords) && (name.toString().match(/^[_a-zA-Z][_a-zA-Z0-9]*$/) || name.toString().match(/^[1-9][0-9]*$/))
+
+
+replaceNode = (node, newVal) ->
+  props = Object.getOwnPropertyNames(node)
+  props.forEach (prop) -> delete node[prop]
+
+  Object.keys(newVal).forEach (prop) ->
+    node[prop] = newVal[prop]
+
+
+makeLiteral = (node, value) ->
+  if typeof value == 'number' && value < 0
+    replaceNode node,
+      type: 'UnaryExpression'
+      operator: '-'
       argument:
-        type: 'MemberExpression'
-        computed: true
-        property:
-          type: 'Literal'
-          value: x.loc.start.line
-        object:
-          type: 'MemberExpression'
-          computed: true
-          object:
-            type: 'Identifier'
-            name: '_$jscoverage'
-          property:
-            type: 'Literal'
-            value: filename
-
-
-  replaceNode = (node, newVal) ->
-    props = Object.getOwnPropertyNames(node)
-    props.forEach (prop) -> delete node[prop]
-
-    Object.keys(newVal).forEach (prop) ->
-      node[prop] = newVal[prop]
-
-  makeLiteral = (node, value) ->
-    if typeof value == 'number' && value < 0
-      replaceNode(node, {
-        type: 'UnaryExpression'
-        operator: '-'
-        argument: {
-          type: 'Literal'
-          value: -value
-        }
-      })
-    else
-      node.type = 'Literal'
-      node.value = value
-
-
-  ast = esprima.parse(code, { loc: true })
-
+        type: 'Literal'
+        value: -value
+  else
+    node.type = 'Literal'
+    node.value = value
+
+
+formatTree = (ast) ->
   # formatting (no difference in result, just here to give it exactly the same semantics as JSCoverage)
   # this block should be part of the comparisons in the tests; not the source
   format = true
@@ -110,51 +100,51 @@
             node.computed = false
             node.property = { type: 'Identifier', name: node.property.value }
         if node.type == 'BinaryExpression' && node.left.type == 'Literal' && node.right.type == 'Literal'
-          if node.operator == '+' && typeof node.left.value == 'string' && typeof node.right.value == 'string' ## put the "typeof" first (easier to read)
+          if typeof node.left.value == 'string' && typeof node.right.value == 'string' && node.operator == '+'
             makeLiteral(node, node.left.value + node.right.value)
             format = true
-          else if ['+', '-', '*', '%', '/', '<<', '>>', '>>>'].indexOf(node.operator) != -1 && typeof node.left.value == 'number' && typeof node.right.value == 'number' ## put the "typeof" first (easier to read)
-            makeLiteral(node, eval("#{node.left.value} #{node.operator} #{node.right.value}")) # OH NOES! Eval!
+          if typeof node.left.value == 'number' && typeof node.right.value == 'number' && node.operator in ['+', '-', '*', '%', '/', '<<', '>>', '>>>']
+            makeLiteral(node, evalBinaryExpression(node.left.value, node.operator, node.right.value))
             format = true
         if node.type == 'UnaryExpression' && node.argument.type == 'Literal'
           if node.operator == '!'
             makeLiteral(node, !node.argument.value)
             format = true
-          if ['~'].indexOf(node.operator) != -1 && typeof node.argument.value == 'number'
-            makeLiteral(node, eval("#{node.operator} #{node.argument.value}")) # OH NOES! Eval!
+          if node.operator == "~" && typeof node.argument.value == 'number'
+            makeLiteral(node, ~node.argument.value)
             format = true
         if node.type == 'ConditionalExpression' && node.test.type == 'Literal'
           if typeof node.test.value == 'string' || typeof node.test.value == 'number' || typeof node.test.value == 'boolean'
             if node.test.value
               replaceNode(node, node.consequent)
             else
-              replaceNode(node, node.alternate)
-
-
-  # injecting coverage code
-  escodegen.traverse ast,
-    enter: (node) ->
-      if ['BlockStatement', 'Program'].indexOf(node.type) != -1
-        node.body = _.flatten node.body.map (x) ->
-          if x.body && x.type == 'FunctionDeclaration' ## x.body should not be required here -- try to remove it
-            [inject(x.body), x]
-          else
-            [inject(x), x]
-      if node.type == 'SwitchCase'
-        node.consequent = _.flatten node.consequent.map (x) -> [inject(x), x]
-      if node.type == 'IfStatement'
-        ['consequent', 'alternate'].forEach (src) ->
-          if node[src]? && node[src].type != 'BlockStatement'
-            node[src] =
-              type: 'BlockStatement'
-              body: [node[src]]
-      if ['ForInStatement', 'ForStatement', 'WhileStatement', 'WithStatement', 'DoWhileStatement'].indexOf(node.type) != -1 && node.body? && node.body.type != 'BlockStatement'
-        node.body =
-          type: 'BlockStatement'
-          body: [node.body]
-
-  trackedLines = _.sortBy(_.unique(injectList), _.identity)
-
+              replaceNode(node, node.alternate)
+
+
+inject = (x, filename) ->
+  type: 'ExpressionStatement'
+  expression:
+    type: 'UpdateExpression'
+    operator: '++'
+    prefix: false
+    argument:
+      type: 'MemberExpression'
+      computed: true
+      property:
+        type: 'Literal'
+        value: x.loc.start.line
+      object:
+        type: 'MemberExpression'
+        computed: true
+        object:
+          type: 'Identifier'
+          name: '_$jscoverage'
+        property:
+          type: 'Literal'
+          value: filename
+
+
+writeFile = do ->
   sourceMappings = [
     (x) -> x.replace(/&/g, '&')
     (x) -> x.replace(/</g, '<')
@@ -164,21 +154,63 @@
     (x) -> '"' + x + '"'
   ]

-  originalSource = code.split('\n').map (line) -> sourceMappings.reduce(((src, f) -> f(src)), line)
-  originalSource = originalSource.slice(0, -1) if _.last(originalSource) == '""' # useless trimming - just to keep the semantics the same as for jscoverage
-
-  output = []
-  output.push "/* automatically generated by jscov - do not edit */"
-  output.push "if (typeof _$jscoverage === 'undefined') _$jscoverage = {};"
-  output.push "if (! _$jscoverage['#{filename}']) {"
-  output.push "  _$jscoverage['#{filename}'] = [];"
-  trackedLines.forEach (line) ->
-    output.push "  _$jscoverage['#{filename}'][#{line}] = 0;"
-  output.push "}"
-  output.push escodegen.generate(ast, { indent: "  " })
-  output.push "_$jscoverage['#{filename}'].source = [" + originalSource.join(",") + "];"
-
-  output.join('\n')
+  (originalCode, coveredCode, filename, trackedLines) ->
+
+    originalSource = originalCode.split('\n').map (line) -> sourceMappings.reduce(((src, f) -> f(src)), line)
+
+    # useless trimming - just to keep the semantics the same as for jscoverage
+    originalSource = originalSource.slice(0, -1) if _.last(originalSource) == '""'
+
+    output = []
+    output.push "/* automatically generated by jscov - do not edit */"
+    output.push "if (typeof _$jscoverage === 'undefined') _$jscoverage = {};"
+    output.push "if (! _$jscoverage['#{filename}']) {"
+    output.push "  _$jscoverage['#{filename}'] = [];"
+    trackedLines.forEach (line) ->
+      output.push "  _$jscoverage['#{filename}'][#{line}] = 0;"
+    output.push "}"
+    output.push coveredCode
+    output.push "_$jscoverage['#{filename}'].source = [" + originalSource.join(",") + "];"
+
+    output.join('\n')
+
+
+exports.rewriteSource = (code, filename) ->
+
+  injectList = []
+
+  ast = esprima.parse(code, { loc: true })
+
+  formatTree(ast)
+
+  escodegen.traverse ast,
+    enter: (node) ->
+      if node.type in ['BlockStatement', 'Program']
+        node.body = _.flatten node.body.map (x) ->
+          if x.type == 'FunctionDeclaration'
+            injectList.push(x.body.loc.start.line)
+            [inject(x.body, filename), x]
+          else
+            injectList.push(x.loc.start.line)
+            [inject(x, filename), x]
+      if node.type == 'SwitchCase'
+        node.consequent = _.flatten node.consequent.map (x) ->
+          injectList.push(x.loc.start.line)
+          [inject(x, filename), x]
+      if node.type == 'IfStatement'
+        ['consequent', 'alternate'].forEach (src) ->
+          if node[src]? && node[src].type != 'BlockStatement'
+            node[src] =
+              type: 'BlockStatement'
+              body: [node[src]]
+      if node.type in ['ForInStatement', 'ForStatement', 'WhileStatement', 'WithStatement', 'DoWhileStatement'] && node.body? && node.body.type != 'BlockStatement'
+        node.body =
+          type: 'BlockStatement'
+          body: [node.body]
+
+  trackedLines = _.sortBy(_.unique(injectList), _.identity)
+
+  writeFile(code, escodegen.generate(ast, { indent: "  " }), filename, trackedLines)

 exports.rewriteFolder = (source, target, options, callback) ->


Server: /WFGH route added

Server: /WFGH route added

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -521,9 +521,7 @@
   return true  if firstLetter.toUpperCase() is firstLetter
   return false

-app.get '/WFGH', (req, res, next)->
-
-  {JGroup} = koding.models
+

   isLoggedIn req, res, (err, loggedIn, account)->

@@ -556,6 +554,22 @@
       return res.status(200).send gravatar

     res.status(400).send body
+
+
+app.get '/WFGH/:section?', (req, res, next)->
+
+  {JGroup} = koding.models
+
+  isLoggedIn req, res, (err, loggedIn, account)->
+
+    return next()  if err
+    JGroup.render.loggedOut.kodingHome {
+      campaign    : 'hackathon'
+      bongoModels : koding.models
+      loggedIn
+      account
+    }, (err, html)->
+      res.status(200).send html


Add snippet for inserting a vanilla code-block

--- snippets/language-restructuredtext.cson
+++ snippets/language-restructuredtext.cson
@@ -14,3 +14,6 @@
   'section 3':
     'prefix': 'sss'
     'body': '${1:subsection name}\n----------------$0\n'
+  'code block':
+    'prefix': 'code'
+    'body': '.. code-block::\n\n\t$1'


Safari Date('yyy-mm-dd') parse error

Safari Date('yyy-mm-dd') parse error

--- source/spec/objects/isEqual-spec.coffee
+++ source/spec/objects/isEqual-spec.coffee
@@ -118,8 +118,8 @@
           expect(_B.isEqual new String('AAA 112'), new String('AAA 111')).to.be.false

       it 'Date', ->
-        expect(_B.isEqual new Date('2012-12-12'), new Date('2012-12-12')).to.be.true
-        expect(_B.isEqual new Date('2012-12-13'), new Date('2012-12-12')).to.be.false
+        expect(_B.isEqual new Date('2012/12/12'), new Date('2012/12/12')).to.be.true
+        expect(_B.isEqual new Date('2012/12/13'), new Date('2012/12/12')).to.be.false

       it 'RegExp', ->
         expect(_B.isEqual /abc/, /abc/).to.be.true


Fix a typo for Sally Shuttleworth

Fix a typo for Sally Shuttleworth

--- content/team.coffee
+++ content/team.coffee
@@ -56,7 +56,7 @@
       image: 'team/sally.jpg'
       name: 'Sally Shuttleworth'
       location: 'Oxford, UK'
-      description: 'Principle Investigator'
+      description: 'Principal Investigator'
     },{
       image: 'team/chris.jpg'
       name: 'Chris Lintott'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/debuggable.coffee
+++ src/debuggable.coffee
@@ -252,7 +252,7 @@

   @name toggleDebug
   @methodOf Debuggable#
-  @param {Boolean} If true is passed then debugging is enabled, if false is passed then debugging is disabled, if nothing is passed, then debug state is toggled.
+  @param {Boolean} newVal If true is passed then debugging is enabled, if false is passed then debugging is disabled, if nothing is passed, then debug state is toggled.
   ###
   toggleDebug: (newVal) ->
     if newVal?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/debuggable.coffee
+++ src/debuggable.coffee
@@ -16,7 +16,6 @@
     player.debug
       filter: 'all'

-Debuggable module
 @name Debuggable
 @module
 @constructor


Cleanup

Cleanup

--- spec/javascripts/support/store.js.coffee
+++ spec/javascripts/support/store.js.coffee
@@ -1,14 +0,0 @@
-window.build = ->
-  @params = arguments
-
-  Ember.run => Factory.build.apply(null, @params)
-
-window.create = ->
-  @params = arguments
-
-  Ember.run => Factory.create.apply(null, @params)
-
-window.find = ->
-  @params = arguments
-
-  Ember.run => Factory.find.apply(null, @params)


apiNotify was only being called on correct submissions!

--- web/coffee/problems.coffee
+++ web/coffee/problems.coffee
@@ -15,10 +15,10 @@
   .done (data) ->
     if data['status'] is 1
       loadProblems()
-      apiNotify(data)
       setTimeout( ->
         $("div[data-target='#" + input.data("pid") + "']").click()
       , 100)
+    apiNotify data

 addProblemReview = (e) ->
   e.preventDefault()


Fix typing a space in an empty editor. Fixes #81

--- src/trix/observers/mutation_observer.coffee
+++ src/trix/observers/mutation_observer.coffee
@@ -89,8 +89,8 @@
       for node in mutation.addedNodes when node.nodeType is Node.TEXT_NODE
         nodesAdded.push(node)

-    additions: (node.data for node, index in nodesAdded when node.data isnt nodesRemoved[index]?.data)
-    deletions: (node.data for node, index in nodesRemoved when node.data isnt nodesAdded[index]?.data)
+    additions: (normalizeSpaces(node.data) for node, index in nodesAdded when node.data isnt nodesRemoved[index]?.data)
+    deletions: (normalizeSpaces(node.data) for node, index in nodesRemoved when node.data isnt nodesAdded[index]?.data)

   getTextChangesFromCharacterData: ->
     characterMutations = @getMutationsByType("characterData")


review: add signature annotations

review: add signature annotations

--- workers/social/lib/social/models/messages/review/index.coffee
+++ workers/social/lib/social/models/messages/review/index.coffee
@@ -2,7 +2,7 @@

 module.exports = class JReview extends Reply

-  {ObjectId, ObjectRef, secure, dash} = require 'bongo'
+  {ObjectId, ObjectRef, secure, dash, signature} = require 'bongo'
   {Relationship}  = require 'jraphical'

   {log} = console
@@ -21,8 +21,23 @@

   @set
     sharedMethods  :
-      static       : ['fetchRelated']
-      instance     : ['delete', 'like', 'fetchLikedByes', 'checkIfLikedBefore']
+
+      static       :
+        fetchRelated:
+          (signature ObjectId, Function)
+
+      instance     :
+        delete:
+          (signature Function)
+        like:
+          (signature Function)
+        fetchLikedByes: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        checkIfLikedBefore:
+          (signature Function)
+
     sharedEvents   :
       instance     : [
         { name: 'TagsChanged' }


Adds jquery to js tests

Adds jquery to js tests

--- spec/javascripts/spec.js.coffee
+++ spec/javascripts/spec.js.coffee
@@ -1 +1,2 @@
+#=require jquery
 #=require_tree ./


test(config): get rid off warnings

test(config): get rid off warnings

These tests were using very old config syntax and relying on autocorrection. After changing the logger config, these were shown during unit tests.

--- test/unit/config.spec.coffee
+++ test/unit/config.spec.coffee
@@ -29,7 +29,7 @@
     mocks = {}
     mocks.process = exit: sinon.spy()
     mockConfigs = {
-      '/home/config1.js': wrapCfg({basePath: 'base', reporter: 'dots'}),
+      '/home/config1.js': wrapCfg({basePath: 'base', reporters: ['dots']}),
       '/home/config2.js': wrapCfg({basePath: '/abs/base'}),
       '/home/config3.js': wrapCfg({files: ['one.js', 'sub/two.js']}),
       '/home/config4.js': wrapCfg({port: 123, autoWatch: true, basePath: '/abs/base'}),
@@ -170,13 +170,13 @@

       expect(config.port).to.equal 456
       expect(config.basePath).to.equal resolveWinPath('/home/base')
+      expect(config.reporters).to.deep.equal ['dots']

       # defaults
       expect(config.files).to.deep.equal []
       expect(config.exclude).to.deep.equal [resolveWinPath('/home/config1.js')]
       expect(config.logLevel).to.exist
       expect(config.autoWatch).to.equal false
-      expect(config.reporters).to.deep.equal ['progress']
       expect(config.singleRun).to.equal false
       expect(config.browsers).to.deep.equal []
       expect(config.reportSlowerThan).to.equal 0


state change events

state change events

--- src/game_state.coffee
+++ src/game_state.coffee
@@ -52,7 +52,7 @@
   self.bind "update", ->
     I.updating = true

-    I.objects.invoke "trigger", ""
+    I.objects.invoke "trigger", "update"

     [I.objects, toRemove] = I.objects.partition (object) ->
       object.update()


Add id to catch-all listener

Add id to catch-all listener

Makes debugging easier when used along with
https://github.com/github/hubot/pull/1074

--- src/hubot-mute.coffee
+++ src/hubot-mute.coffee
@@ -82,7 +82,7 @@
       msg.send what

   # Catch-all listener to mute responses
-  robot.hear /(.*)$/i, (msg) ->
+  robot.hear /(.*)$/i, {id: "hubot-mute.catch_all"}, (msg) ->
     if mute_all is false and mute_channels.indexOf(process.env.HUBOT_MUTE_ROOM_PREFIX + msg.message.room) == -1
       return
     if msg.match[1].indexOf('mute') != -1


if user is admin, use new partner flag to show or hide 'works'

--- apps/partner/client/view.coffee
+++ apps/partner/client/view.coffee
@@ -146,8 +146,8 @@
       overview: => true
       shows: => @partner.get('displayable_shows_count') > 0
       artists: => @partner.get('partner_artists_count') > 0
-      works: => @currentUser?.isAdmin()
-      collection: => @partner.get('published_not_for_sale_artworks_count') > 0
+      works: => @currentUser?.isAdmin() and @partner.get('display_works_section')
+      collection: => @partner.get('published_not_for_sale_artworks_count') > 0 and (if @currentUser?.isAdmin() then @partner.get('display_works_section') else true)
       contact: => true
       about: => true
       articles: => true


fix media modal +/- button click

fix media modal +/- button click

--- MediaAdminBundle/Resources/public/coffee/MediaModalView.coffee
+++ MediaAdminBundle/Resources/public/coffee/MediaModalView.coffee
@@ -49,9 +49,10 @@
       currentModal.modal "hide"

   showFolder: (event) ->
-    @updateNavigation($(event.target))
-    displayLoader $(".modal-body-content", @$el)
-    GalleryLoad $(event.target).attr('id'), @options.galleryView, $(".modal-body-content", @$el)
+    if $(event.target).attr('id')
+      @updateNavigation($(event.target))
+      displayLoader $(".modal-body-content", @$el)
+      GalleryLoad $(event.target).attr('id'), @options.galleryView, $(".modal-body-content", @$el)

   updateNavigation: (node) ->
     $('.modal-body-menu nav .active', @el).removeClass("active");


Removing old config file.

Removing old config file.

--- toaster.coffee
+++ toaster.coffee
@@ -1,5 +0,0 @@
-toast 'src'
-	minify: false
-	expose: 'exports'
-	release: 'lib/toaster.js'
-	debug: 'lib/toaster-debug.js'


Autofocus home search input

Autofocus home search input

--- src/desktop/apps/home/client/index.coffee
+++ src/desktop/apps/home/client/index.coffee
@@ -74,6 +74,8 @@
     @searchBarView.on 'search:selected', @searchBarView.selectResult
     throttledScroll = _.throttle((=> @onScroll()), 100)
     $(window).on 'scroll', throttledScroll
+    @$input.focus()
+    @highlightSearch({}, false)

   onScroll: ->
     if $(window).scrollTop() > 250
@@ -83,8 +85,10 @@
       @$('#main-layout-header #main-layout-search-bar-container').removeClass('visible')
       @$('#main-layout-header').removeClass('visible')

-  highlightSearch: (e) ->
-    if $('#home-foreground #main-layout-search-bar-input').is(':focus')
+  highlightSearch: (e, onlyOnFocus = true) ->
+    if onlyOnFocus
+      $('#home-foreground #main-layout-search-bar-container').addClass('focused') if @$input.is(':focus')
+    else
       $('#home-foreground #main-layout-search-bar-container').addClass('focused')

   unhighlightSearch: (e) ->


Better function regex in data explorer

Better function regex in data explorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1474,7 +1474,7 @@
         # Extract information from the current query
         # Regex used
         regex:
-            anonymous:/^(\s)*function\(([a-zA-Z0-9,\s]*)\)(\s)*{/
+            anonymous:/^(\s)*function\s*\(([a-zA-Z0-9,\s]*)\)(\s)*{/
             loop:/^(\s)*(for|while)(\s)*\(([^\)]*)\)(\s)*{/
             method: /^(\s)*([a-zA-Z0-9]*)\(/ # forEach( merge( filter(
             row: /^(\s)*row\(/


Save the state of codemirror

Save the state of codemirror

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -433,6 +433,11 @@
             @state.history.length = 0
             window.localStorage.rethinkdb_history = JSON.stringify @state.history

+        # Save the current query (the one in codemirror) in local storage
+        save_current_query: =>
+            if window.localStorage?
+                window.localStorage.current_query = JSON.stringify @codemirror.getValue()
+
         initialize: (args) =>
             @TermBaseConstructor = r.expr(1).constructor.__super__.constructor.__super__.constructor

@@ -442,6 +447,10 @@
             # Load options from local storage
             if window.localStorage?.options?
                 @state.options = JSON.parse window.localStorage.options
+
+            # Load the query that was written in code mirror (that may not have been executed before)
+            if typeof window.localStorage?.current_query is 'string'
+                @state.current_query = JSON.parse window.localStorage.current_query

             if window.localStorage?.rethinkdb_history?
                 @state.history = JSON.parse window.localStorage.rethinkdb_history
@@ -854,6 +863,7 @@

             # Save the last query (even incomplete)
             @state.current_query = @codemirror.getValue()
+            @save_current_query()

             # Look for special commands
             if event?.which?
@@ -3726,6 +3736,7 @@
             # Set + save codemirror
             @container.codemirror.setValue @history[parseInt(id)].query
             @container.state.current_query = @history[parseInt(id)].query
+            @save_current_query()

         delete_query: (event) =>
             that = @


Fixed annotations not clearing

Fixed annotations not clearing

--- src/assembler/main-assembler.coffee
+++ src/assembler/main-assembler.coffee
@@ -31,6 +31,7 @@

   onChange = (text) ->
     if text.length == 0
+      editor.getSession().setAnnotations []
     else
       try
         assembler.assemble text


Normalize drive letter case on windows

Normalize drive letter case on windows

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -66,7 +66,11 @@
     {@resourcePath, @version, @devMode, @safeMode, @socketPath} = options

     # Normalize to make sure drive letter case is consistent on Windows
-    @resourcePath = path.normalize(@resourcePath) if @resourcePath
+    if @resourcePath
+      if process.platform is 'win32'
+        @resourcePath = @resourcePath.replace /^([a-z]):/, ([driveLetter]) ->
+          "#{driveLetter.toUpperCase()}:"
+      @resourcePath = path.normalize(@resourcePath)

     global.atomApplication = this


[dev] Passing lint

[dev] Passing lint

--- as-country-map/update_db.coffee
+++ as-country-map/update_db.coffee
@@ -46,7 +46,7 @@
           value < 1
             throw new Error('Cannot parse record file')
           for id in [start..start + value]
-            info = new Info();
+            info = new Info()
             info.id = id
             info.countryCode = countryCode
             info.registry = registry


add "No photo" to file question

add "No photo" to file question

--- app/assets/javascripts/components/questions/file_question.cjsx
+++ app/assets/javascripts/components/questions/file_question.cjsx
@@ -11,6 +11,17 @@

Upload file(s)

       <input type="file" onChange={@addFile}/>
+
+
+        <input
+          checked={@props.data.selected?.length == 0}
+          type="radio"
+          onChange={@resetFiles}
+          value={[]}
+          id={@props.data.id + "-no-photo"}
+        />
+        No photo
+

   renderFiles: =>
@@ -38,3 +49,6 @@

   deleteFile: (file) =>
     QuestionnaireStore.removeAnswer(@props.data.id, file)
+
+  resetFiles: =>
+    QuestionnaireStore.selectAnswer(@props.data.id, [])


Ajax methods added

Ajax methods added

--- Bacon.UI.coffee
+++ Bacon.UI.coffee
@@ -35,6 +35,12 @@

 Bacon.UI.ajax = (params) -> Bacon.fromPromise $.ajax(params)

+Bacon.UI.ajaxGet = (url, data, dataType) -> Bacon.UI.ajax({url, dataType, data})
+
+Bacon.UI.ajaxGetJSON = (url, data) -> Bacon.UI.ajax({ url, dataType:"json", data})
+
+Bacon.UI.ajaxPost = (url, data, dataType) -> Bacon.UI.ajax({url, dataType, data, type:"POST"})
+
 Bacon.Observable::awaiting = (response) ->
   @map(true).merge(response.map(false)).toProperty(false).skipDuplicates()


agree with the openpgp version...

agree with the openpgp version...

--- src/openpgp/ocfb.iced
+++ src/openpgp/ocfb.iced
@@ -87,7 +87,10 @@
   #-------------

   _emit_sb : (sb) ->
-    @_emit_buf sb.read_buffer_at_most @block_size
+    buf = if (deficit = @block_size - sb.rem()) > 0
+      Buffer.concat [ sb.consume_rest_to_buffer(), new Buffer(0 for i in [0...deficit]) ]
+    else sb.read_buffer @block_size
+    @_emit_buf buf

   #-------------


fixed wrong team modal user role selection

--- app/assets/javascripts/admin/views/user/team_role_modal_view.coffee
+++ app/assets/javascripts/admin/views/user/team_role_modal_view.coffee
@@ -53,7 +53,7 @@
       users = @getSelectedUsers()
       if users.length == 1
         team = _.find(users[0].get("teams"), team: teamName)
-        if team and team.role.name = roleName
+        if team and team.role.name == roleName
           return "selected"

   events :


#162852547 max photo  working for one observation

--- app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
@@ -225,20 +225,27 @@

 checkMaxPhotos = (maxPhotos, addedPhotos) ->
   if addedPhotos >= maxPhotos
-    $('.photo-upload').hide()
+    $('.photo-upload').attr('style','display:none;')
   else
-    $('.photo-upload').show()
+    $('.photo-upload').attr('style','display:block;')

 $ ->
   maxPhotos = $('#max-photos').attr("data-max-photos")
-  addedPhotos = $('.file-upload-input-button').find("img").length
+  addedPhotos = $('.photo-preview').find("img").length
   checkMaxPhotos(maxPhotos, addedPhotos)
-  $('.delete-box').on 'click', () ->
-    addedPhotos -= 1
+  $('.file-upload').on 'click', '.remove-upload', (e) ->
+    e.preventDefault()
+    $('.photo-upload').attr('style','display:block;')
+    addedPhotos = $('.photo-preview').find("img").length
+    # checkMaxPhotos(maxPhotos, addedPhotos)
+  # $('.file-upload-input-button').on 'click', () ->
+  #   addedPhotos += 1
+  #   checkMaxPhotos(maxPhotos, addedPhotos)
+
+  $('body').bind 'cloudinarydone', (e) ->
+    addedPhotos = $('.photo-preview').find("img").length
     checkMaxPhotos(maxPhotos, addedPhotos)
-  $('.file-upload-input-button').on 'click', () ->
-    addedPhotos += 1
-    checkMaxPhotos(maxPhotos, addedPhotos)
+
   # when a user removes an upload in edit, we are resetting the sortorder
   $('.delete-saved-file').on 'click', (e) ->
     e.preventDefault()


gallery.coffee - Check if player exists before call stopVideo

--- app/assets/javascripts/martian_components/components/gallery.coffee
+++ app/assets/javascripts/martian_components/components/gallery.coffee
@@ -5,7 +5,7 @@
       @el.addClass('no-touch') unless Utils.isTouchDevice()

     stopVideo: ->
-      @embeddedPlayerWrapper.data('player').stopVideo()
+      @embeddedPlayerWrapper.data('player')?.stopVideo?()

   @autoInit: ->
     $('.component-gallery').each (i, el) => new @($(el))


Update presenter specs

Update presenter specs

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -472,7 +472,7 @@

             expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
             expectStateUpdate presenter, ->
-              presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
+              presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'meta.method-call.js', 'support.function.js'], 'p', 20)
               presenter.measurementsChanged()
             expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide

@@ -866,7 +866,7 @@

             expect(getState(presenter).content.scrollWidth).toBe 10 * maxLineLength + 1
             expectStateUpdate presenter, ->
-              presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
+              presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'meta.method-call.js', 'support.function.js'], 'p', 20)
               presenter.measurementsChanged()
             expect(getState(presenter).content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide


find-and-replace:select-next binding for Linux

I noticed that Atom on Linux does not support ctrl-d out of the box like Sublime does. I guess ctrl-d on Windows is chosen because of Sublime's binding, so I thought it should be set on Linux as well.

--- keymaps/find-and-replace.cson
+++ keymaps/find-and-replace.cson
@@ -24,6 +24,7 @@
 '.platform-linux .editor':
   'f3': 'find-and-replace:find-next'
   'shift-f3': 'find-and-replace:find-previous'
+  'ctrl-d': 'find-and-replace:select-next'

   'ctrl-e': 'find-and-replace:use-selection-as-find-pattern'


definition can now be provided through parameter

--- src/coffee/directives/controller.coffee
+++ src/coffee/directives/controller.coffee
@@ -10,19 +10,22 @@
   directive('coolform', (definitionService, validatorService) ->

     l = (scope, elem, attr) ->
-      scope.definition = null
-      if scope.url then scope.definition = definitionService(scope.url).then((definition) ->
-        scope.definition = definition.form
-      )
+
+      if scope.url?
+        scope.definition = definitionService(scope.url).then((definition) ->
+          scope.definition = definition.form
+        )

       scope.$watch('definition', (v) ->
-        if !v.pages then return else validatorService(scope)
+        console.log v
+        if v? and v.pages? then validatorService(scope)
       )

     return {
       restrict: 'E'
       scope:
-        url: '@'
+        url: '@?'
+        definition: '=?'
       template: templates.controller
       link: l
       replace: true


tweak

tweak

Former-commit-id: e7120c4339f1d53caf248f71546e14cf6e1a8e61 [formerly e267b86230e4a1161603d68c38814cc9dfc96200] [formerly 3fed660a78d5ecbf812e1a42f554d8c1c648a5e8 [formerly 2ec700e2388d868c0afaed2da786f0fca2fc8661]] [formerly b87f77d9a96a63f7adacf3cf46266c86c28fb926 [formerly 8a9ea8a77749378f93906f6fa1ab03197a8310a4] [formerly 204651a820853c2f5d8f26f20622f663f0b5a955 [formerly 204651a820853c2f5d8f26f20622f663f0b5a955 [formerly bf86026ab9f3d0e021a2179e3d6e5474a78a7ae8]]]]
Former-commit-id: 64894d22edc93cbaa869265730d6fc8140c7906f [formerly e35f267738d4986e6c8c2d0f84d3cd2907b9eebd] [formerly ac323c41162322858f5a019c0b979dd95af30e56 [formerly 428b1fd7a28556eb3d583d8092a43fb354685698]]
Former-commit-id: 200778c09ad01b933d7b661eba2b77f928d6beb8 [formerly cc08f14072e096123791bb45bcc8e9ff243bf5a6]
Former-commit-id: 6839b78371aefc00874a728099290370e7e5a85e
Former-commit-id: 1be404efe44d1b54a5c6d40c6621cd9dd521d3df

--- @client/saas_landing_page.coffee
+++ @client/saas_landing_page.coffee
@@ -830,7 +830,7 @@
         """
         Travis was in a privileged position to spend time thinking about these
         issues. He was pursuing a PhD in Computer Science at the University of
-        Washington, with a research agenda grounded in the belief that we can
+        Washington, doing research grounded in the belief that we can
         improve our capacity for collective action. He was supported by a generous
         """
         A


subs-man

subs-man

--- tap_i18n_db-client.coffee
+++ tap_i18n_db-client.coffee
@@ -60,14 +60,25 @@

   return obj

+toArray = (as) ->
+  args = new Array(as.length)
+  for i in [0...as.length]
+    args[i] = as[i]
+  args
+
 TAPi18n.subscribe = (name) ->
-  args = new Array(arguments.length)
-  for i in [0..arguments.length-1]
-    args[i] = arguments[i]
+  args = toArray arguments
   args.unshift null
   TAPi18n.subscribeWithConnection.apply @, args

 TAPi18n.subscribeWithConnection = (connection, name) ->
+  args = toArray arguments
+  args.splice 1, 0, null
+  TAPi18n.subscribeWithConnectionCached.apply @, args
+
+TAPi18n.caches = {}
+
+TAPi18n.subscribeWithConnectionCached = (connection, cacheId, name) ->
 # TAPi18n.subscribe = ->
   local_session = new ReactiveDict
   local_session.set("ready", false)
@@ -83,7 +94,7 @@

   # parse arguments
   # params = Array.prototype.slice.call(arguments, shift)
-  params = Array.prototype.slice.call(arguments, 2)
+  params = Array.prototype.slice.call(arguments, 3)
   callbacks = {}
   if params.length
     lastParam = params[params.length - 1]
@@ -114,16 +125,34 @@
   subscription = null
   subscription_computation = null
   subscribe = ->
+    if cacheId
+      hash = name+'-'+cacheId
+      man = TAPi18n.caches[hash]
+      unless man?
+        Tracker.nonreactive ->
+          man = new SubsManager subscribe: _.bind connection.subscribe, connection
+          TAPi18n.caches[hash] = man
+
+          Tracker.autorun =>
+            lang = TAPi18n.getLanguage()
+            # console.log ['search lang clear']
+            man.clear()
+
     # subscription_computation, depends on TAPi18n.getLanguage(), to
     # resubscribe once the language gets changed.
     subscription_computation = Deps.autorun () ->
       lang_tag = TAPi18n.getLanguage()

-      subscription =
-        connection.subscribe.apply connection, removeTrailingUndefs [].concat(name, params, lang_tag, callbacks)
+      subargs = removeTrailingUndefs [].concat(name, params, lang_tag, callbacks)
+      if cacheId
+        # console.log [man, subargs]
+        subscription = man.subscribe.apply man, subargs
+        subscription.manager = man
+      else
+        subscription = connection.subscribe.apply connection, subargs

       # if the subscription is already ready:
-      local_session.set("ready", subscription.ready())
+      local_session.set("ready", subscription?.ready())

   # If TAPi18n is called in a computation, to maintain Meteor.subscribe
   # behavior (which never gets invalidated), we don't want the computation to


Updated: atom config

Updated: atom config

--- dotfiles/.atom/config.cson
+++ dotfiles/.atom/config.cson
@@ -7,6 +7,7 @@
       "source.ini": [
         ".buckconfig"
         ".flowconfig"
+        ".hgrc"
       ]
       "source.json": [
         ".arcconfig"
@@ -38,6 +39,7 @@
       "styleguide"
       "welcome"
       "linter"
+      "auto-update-packages"
     ]
     openEmptyEditorOnStart: false
     projectHome: "/Users/MoOx/Sync/Development"
@@ -82,6 +84,7 @@
     showIcons: false
   "tree-view":
     autoReveal: true
+    hideIgnoredNames: true
     squashDirectoryNames: true
   welcome:
     showOnStartup: false


TreeView: minor code styling

TreeView: minor code styling

--- src/components/tree/treeview.coffee
+++ src/components/tree/treeview.coffee
@@ -2,15 +2,18 @@

 module.exports = class JTreeView extends KDListView

-  constructor:(options = {},data)->
+  constructor:(options = {}, data)->

     options.animated ?= no
+    options.cssClass  = KD.utils.curry 'jtreeview expanded', options.cssClass
+
     super options, data
-    @setClass "jtreeview expanded"
+

   toggle:(callback)->

     if @expanded then @collapse callback else @expand callback
+

   expand:(callback)->

@@ -23,6 +26,7 @@
       @setClass "expanded"
       callback?()

+
   collapse:(callback)->

     if @getOptions().animated
@@ -34,17 +38,20 @@
       @unsetClass "expanded"
       callback?()

+
   mouseDown:->

-    KD.getSingleton("windowController").setKeyView @
+    KD.getSingleton("windowController").setKeyView this
     no
+

   keyDown:(event)->

     @emit "KeyDownOnTreeView", event

+
   destroy:->

-    KD.getSingleton("windowController").revertKeyView @
+    KD.getSingleton("windowController").revertKeyView this

     super


Make graphs <-> series mapping use naturalOrder too

--- app/assets/javascripts/legend.js.coffee
+++ app/assets/javascripts/legend.js.coffee
@@ -20,7 +20,7 @@
       $label = $(line.element).find("span").first()
       $label.attr "rel", line.series.name
       @graphSeries[line.series.name] =
-        series   : _.map @graphs, (graph) -> graph.series[graph.series.length - 1 - i]
+        series   : _.map @graphs, (graph) -> graph.series[i]
         disabled : no
       $label.on "click", ->
         instance.toggle $(this)


avoid address fields being reset by the widget

which now sends a change event when the address is selected

--- app/assets/javascripts/orders.js.coffee
+++ app/assets/javascripts/orders.js.coffee
@@ -15,6 +15,7 @@
     fitTextArea(@)

     widget.on "result:select", (value, item)->
+      $(@.element).data("selected", value)
       $(".hidden-address.suburb").val(item.suburb || '')
       $(".hidden-address.city_town").val(item.city || '')
       $(".hidden-address.post_code").val(item.postcode || '')
@@ -27,8 +28,8 @@
       fitTextArea(@.element)

     $(this).on "change", ->
-      clear_hidden_values()
-
+      if ($(this).data("selected") || '').trim() != $(this).val().trim()
+        clear_hidden_values()

   $("#tertiary_institution").hide() unless $(".tertiary-institution-toggle").attr('checked')


FIX: project task stats so it combines discuss and demonstrate (ie. no demonstrate in the stats)

--- src/app/common/services/unit-service.coffee
+++ src/app/common/services/unit-service.coffee
@@ -122,7 +122,8 @@
         { value: 0, type: _.trim(_.dasherize(taskService.statusKeys[5]))},
         { value: 0, type: _.trim(_.dasherize(taskService.statusKeys[6]))},
         { value: 0, type: _.trim(_.dasherize(taskService.statusKeys[7]))},
-        { value: 0, type: _.trim(_.dasherize(taskService.statusKeys[8]))}
+        # Skip demonstrate as it is not in the returned data...
+        { value: 0, type: _.trim(_.dasherize(taskService.statusKeys[9]))}
       ]
       student.progress_stats = [
         # Progress stats


activity feed: commenting out scroll up to refresh beh. for now

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -101,11 +101,14 @@
       @setFilter data.type
       @populateActivity()

-    @listController.on "scrolledToTopOfPage", =>
-      return if @isLoading
-
-      log "scrolled_up fetching activities"
-      @fetchSomeActivities()
+    # SA: commenting it out since
+    #     1. ui isn't implemeneted,
+    #     2. if the latet item can't be revived, this gets duplicate entries
+    #@listController.on "scrolledToTopOfPage", =>
+      #return if @isLoading
+
+      #log "scrolled_up fetching activities"
+      #@fetchSomeActivities()

   activitiesArrived:(activities)->
     for activity in activities when activity.bongo_.constructorName in @getFilter()


Make the easing smoother at the end

Make the easing smoother at the end

--- app/assets/javascripts/static.js.coffee
+++ app/assets/javascripts/static.js.coffee
@@ -19,8 +19,8 @@
           step: ->
             $this.text Math.ceil(@counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
           complete: ->
-            jQuery(counter: $target_count).animate { counter: (3 + $target_count) },
-              duration: 1300 + index * 600
+            jQuery(counter: $target_count).animate { counter: (40 + $target_count) },
+              duration: 400
               easing: "easeOutQuint"
               step: ->
                 $this.text Math.ceil(@counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")


Removed spinner for feeds.

Removed spinner for feeds.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -681,15 +681,6 @@
         feedbin.showNotification('Search error.');
         return

-    loadProgress: ->
-      $(document).on 'click', '[data-behavior~=feed_link]', (event, xhr) ->
-        $(@).find('.favicon').addClass('favicon-progress')
-        return
-
-      $(document).on 'ajax:complete', '[data-behavior~=feed_link]', (event, xhr) ->
-        $('.favicon-progress').removeClass("favicon-progress")
-        return
-
     savedSearch: ->
       $(document).on 'click', (event) ->
         unless $(event.target).is('[data-behavior~=save_search_link]') || $(event.target).parents('.header-form-wrap').length > 0


fix rclient check in migration metrics

fix rclient check in migration metrics

--- app/coffee/RedisMigrationManager.coffee
+++ app/coffee/RedisMigrationManager.coffee
@@ -106,7 +106,7 @@
 			@migrationClient.findQueue @queueKey, (err, rclient) =>
 				return releaseLock(err) if err?
 				# add metric for updates
-				dest = (if rclient == @rclient_new then "new" else "old")
+				dest = (if rclient == @migrationClient.rclient_new then "new" else "old")
 				metrics.count "migration", @updates_count, 1, {status: "#{@migrationClient.migration_phase}-#{dest}"}
 				multi = rclient.multi()
 				for entry in @command_list


Fix day render after cancel

Fix day render after cancel

--- app/assets/javascripts/mical.js.coffee
+++ app/assets/javascripts/mical.js.coffee
@@ -33,7 +33,7 @@
   cancelForm: ->
     $('.js-form').slideUp()
     dayIndicator = $('.header-day')
-    @_renderDay(dayIndicator.data('day'), dayIndicator.data('month'))
+    @_renderDay(dayIndicator.data('month'), dayIndicator.data('day'))

   deleteForm: ->
     if not confirm('Are you sure?')