add test to authTest nodeify.

add test to authTest nodeify.

it works.

--- test/merchant.coffee
+++ test/merchant.coffee
@@ -155,6 +155,21 @@
           sandbox: true
           key: 'test'
         assert.becomes merchant.authTest(), success: true
+
+    describe 'nodeify callback', ->
+      before ->
+        wish
+          .get '/api/v1/auth_test?key=test'
+          .reply 200,
+            {"message":"","code":0,"data":{"success":true}}
+
+      it 'should callback with err and result', (done) ->
+        merchant = new Merchant
+          sandbox: true
+          key: 'test'
+        merchant.authTest (err, result) ->
+          assert.equal result.success, true
+          done()

   describe 'product', ->


Remove debug logging

Remove debug logging

--- test/surveyor.coffee
+++ test/surveyor.coffee
@@ -408,7 +408,6 @@
   it 'should prune jobs marked prunable with a negative delta', (done) ->
     rand1 = Math.floor(Math.random() * (1 << 24)).toString(16)
     rand2 = Math.floor(Math.random() * (1 << 24)).toString(16)
-    console.log rand1, rand2
     model.slaves[rand1] =
       processes:
         one:
@@ -439,7 +438,6 @@
   it 'should not prune jobs not marked prunable with a negative delta', (done) ->
     rand1 = Math.floor(Math.random() * (1 << 24)).toString(16)
     rand2 = Math.floor(Math.random() * (1 << 24)).toString(16)
-    console.log rand1, rand2
     model.slaves[rand1] =
       processes:
         one:


Agressor official French translation

Agressor official French translation

--- coffeescripts/cards-fr.coffee
+++ coffeescripts/cards-fr.coffee
@@ -425,7 +425,7 @@
             name: "Frontalier de l'espace sauvage"

         # Scum and Villainy
-        # Starviper, traduit de l'anglais
+        # Starviper
         "Prince Xizor":
             text: """Quand vous défendez, un vaisseau allié à portée 1 peut subir 1 dégât %HIT% ou %CRIT% restant à votre place."""
         "Guri":
@@ -434,7 +434,7 @@
             name: "Vigo du Soleil Noir"
         "Black Sun Enforcer":
             name: "Homme de main du Soleil Noir"
-        # Intercepteur M3-A, traduit de l'anglais
+        # Intercepteur M3-A
         "Cartel Spacer":
             name: "Astropilote du Cartel"
             ship: "Intercepteur M3-A"
@@ -447,13 +447,13 @@
         "Laetin A'shera":
             ship: "Intercepteur M3-A"
             text: "Après avoir défendu contre une attaque, si cette dernière ne vous a pas touché, vous pouvez assigner 1 marqueur d'évasion à votre vaisseau."
-        # Agressor, traduit de l'anglais
+        # Agressor
         "IG-88A":
-            text: """After you perform an attack that destroys the defender, you may recover 1 shield."""
+            text: """Après avoir effectué une attaque qui détruit le défenseur, vous pouvez récupérer 1 bouclier."""
         "IG-88B":
-            text: """Once per round, after you perform an attack that does not hit, you may perform an attack with an equipped %CANNON% secondary weapon."""
+            text: """Une fois par tour, après avoir effectué une attaque qui ne touche pas, vous pouvez effectuer une attaque avec une arme secondaire %CANON% équipée."""
         "IG-88C":
-            text: """After you perform a boost action, you may perform a free evade action."""
+            text: """Après avoir effectué une action d'accélération, vous pouvez effectuer une action d'évasion gratuite."""
         "IG-88D":
             text: """Vous pouvez exécuter la manœuvre (%SLOOPLEFT% 3) ou (%SLOOPRIGHT% 3) en utilisant le gabarit (%TURNLEFT% 3) ou (%TURNRIGHT% 3) correspondant."""
         # Firespray
@@ -485,7 +485,7 @@
             text: """À la fin de la phase d'activation, choisissez 1 vaisseau ennemi situé à portée 1-2. Jusqu'à la fin de la phase de combat, considérez la valeur de pilotage de ce vaisseau égale "0"."""
         "Spice Runner":
             name: "Trafiquant d'épice"
-        # Z-95, traduit de l'anglais
+        # Z-95
         "Black Sun Soldier":
             name: "Soldat du Soleil Noir"
             ship: "Chasseur de têtes Z-95"
@@ -831,10 +831,12 @@
         "Inertial Dampeners":
             name: "Amortisseurs inertiels"
             text: """Quand vous révélez votre cadran de manœuvre, vous pouvez défausser cette carte pour exécuter une manœuvre blanche [0%STOP%] à la place. Ensuite, recevez 1 marqueur de stress."""
-        "Dead Man's Switch": # Traduction non officielle
+        "Dead Man's Switch":
+            name: "Salve automatique"
             text: """Quand vous êtes détruit, chaque vaisseau à portée 1 subit 1 dégât."""
-        "Feedback Array": # Traduction non officielle
-            text: """Durant la phase de combat, au lieu d'effectuer des attaques, vous pouvez recevoir 1 marqueur ionique et subit 1 dégât pour choisir un vaisseau ennemi à portée 1. Ce vaisseau subit 1 dégât."""
+        "Feedback Array":
+            name: "Dispositif de retour"
+            text: """Lors de la phase de combat, au lieu d'effectuer des attaques, vous pouvez recevoir 1 marqueur ionique et subit 1 dégât pour choisir un vaisseau ennemi à portée 1. Ce vaisseau subit 1 dégât."""
         '"Hot Shot" Blaster':
             name: "Blaster \"de la mort\""
             text: """Attaque : défaussez cette carte pour attaquer 1 vaisseau (même s'il est en dehors de votre arc de tir)."""
@@ -963,7 +965,7 @@
             ship: "Intercepteur M3-A"
             text: """Intercepteur M3-A uniquement. Titre.%LINEBREAK%Votre bandeau d'amélioration gagne l'icône d'amélioration %CANNON%, %TORPEDO%, ou %MISSILE%."""
         "IG-2000":
-            text: """Aggressor uniquement.%LINEBREAK%Vous avez la capacité de pilote de chaque autre vaisseau allié avec la carte d'amélioration IG-2000 (en plus de votre propre capacité de pilote)."""
+            text: """Aggressor uniquement.%LINEBREAK%Vous avez la capacité de pilote de chaque autre vaisseau allié avec la carte d'amélioration IG-2000 (en plus de votre capacité de pilote)."""
         "BTL-A4 Y-Wing":
             name: "Y-wing BTL-A4"
             text: """Y-Wing uniquement. Titre.%LINEBREAK%Vous ne pouvez pas attaquer les vaisseaux en dehors de votre arc de tir. Après avoir effectué une attaque d'arme principale, vous pouvez immédiatement effectuer une attaque avec une arme secondaire %TURRET%."""


Made the `autoclear` timer optional

Made the `autoclear` timer optional

--- savoury.coffee
+++ savoury.coffee
@@ -32,8 +32,10 @@
     tpl_complete: "savoury__complete"
     error: "There was an error processing your request"
     tpl_error: "savoury__error"
+    autoclear: true
+    autoclearTimer: 3000

-  constructor: (options={}, @autoclear=true)->
+  constructor: (options={})->
     #<debug>
     if typeof options == 'string'
       try throw new Error() catch e
@@ -73,12 +75,16 @@
   complete: (result)->
     @setMessage(Savoury.STATE_COMPLETE, result)
     @setState(Savoury.STATE_COMPLETE)
-    if @autoclear
-      Meteor.setTimeout @reset, 3000
+    if @options.autoclear
+      Meteor.setTimeout @reset, @options.autoclearTimer

     return

   wrapMethod: (methodName, args, options, callback)->
+    if _.isFunction(options)
+      callback = options
+      options = {}
+
     @sending()
     Meteor.apply methodName, args, options, (e, r)=>
       if e?


replace 'LI' with LIST_ITEM_TAG

replace 'LI' with LIST_ITEM_TAG

--- src/core/document.coffee
+++ src/core/document.coffee
@@ -73,7 +73,7 @@
     objects = []
     list = false
     while(line)
-      if line.node.nodeName == 'LI'
+      if line.node.nodeName == dom.DEFAULT_LIST_ITEM_TAG
         if list
           list.data.push(line.getJSON())
         else
@@ -82,13 +82,13 @@
             data: [line.getJSON()]
           }
       else
-        if prevLine == 'LI' and list
+        if prevLine == dom.DEFAULT_LIST_ITEM_TAG and list
           objects.push(list)
           list = false
         objects.push(line.getJSON())
       prevLine = line.node.nodeName
       line = line.next
-    if prevLine == 'LI' and list
+    if prevLine == dom.DEFAULT_LIST_ITEM_TAG and list
       objects.push(list)
     return objects


cleanup

cleanup

--- apps/gene/client/index.coffee
+++ apps/gene/client/index.coffee
@@ -1,54 +0,0 @@
-mediator                    = require '../../../lib/mediator.coffee'
-Backbone                    = require 'backbone'
-Gene                        = require '../../../models/gene.coffee'
-CurrentUser                 = require '../../../models/current_user.coffee'
-ArtistFillwidthList         = require '../../../components/artist_fillwidth_list/view.coffee'
-{ GENE, CURRENT_USER }      = require('sharify').data
-{ Following, FollowButton } = require '../../../components/follow_button/index.coffee'
-ShareView                   = require '../../../components/share/view.coffee'
-GeneFilter                  = require './filter.coffee'
-RelatedGenesView            = require '../../../components/related_genes/view.coffee'
-
-module.exports.GeneView = class GeneView extends Backbone.View
-
-  initialize: (options) ->
-    { @user } = options
-    @followButton = new FollowButton
-      el: $('.follow-button')
-      following: if @user then new Following(null, kind: 'gene') else null
-      model: @model
-    @followButton.following?.syncFollows [@model.get('id')]
-    @shareButtons = new ShareView
-      el: @$('#gene-share-buttons')
-    @filter = new GeneFilter
-      el: @$('#gene-filter')
-      model: @model
-    @setupArtistFillwidth()
-    @setupRelatedGenes()
-
-  setupRelatedGenes: ->
-    new RelatedGenesView
-      el: @$('.main-layout-container .related-genes')
-      model: @model
-      modelName: 'gene'
-
-  setupArtistFillwidth: ->
-    if @user and not @model.isSubjectMatter()
-      @user.initializeDefaultArtworkCollection().always @renderArtistFillwidth
-    else if not @model.isSubjectMatter()
-      @renderArtistFillwidth()
-
-  renderArtistFillwidth: =>
-    @model.fetchArtists 'related', success: (artists) =>
-      new ArtistFillwidthList(
-        collection: artists
-        el: $('#gene-artists')
-        user: @user
-      ).fetchAndRender()
-
-module.exports.init = ->
-  gene = new Gene GENE
-  new GeneView
-    user: CurrentUser.orNull()
-    el: $ 'body'
-    model: gene


jupyter: make menus a bit more responsive yet

--- src/smc-webapp/jupyter/top-menubar.cjsx
+++ src/smc-webapp/jupyter/top-menubar.cjsx
@@ -33,6 +33,7 @@
         "#{name}" :
             kernels             : rtypes.immutable.List
             kernel              : rtypes.string
+            kernel_state        : rtypes.string
             has_unsaved_changes : rtypes.bool
             kernel_info         : rtypes.immutable.Map
             backend_kernel_info : rtypes.immutable.Map
@@ -41,16 +42,19 @@
             trust               : rtypes.bool
             view_mode           : rtypes.string
             toolbar             : rtypes.bool
+            cell_toolbar        : rtypes.string

     shouldComponentUpdate: (next) ->
         return next.has_unsaved_changes != @props.has_unsaved_changes or \
             next.kernels != @props.kernels or \
             next.kernel != @props.kernel or \
+            next.kernel_state != @props.kernel_state or \
             next.backend_kernel_info != @props.backend_kernel_info or \
             next.cur_id != @props.cur_id or \
             next.cells != @props.cells or \
             next.trust != @props.trust or \
             next.toolbar != @props.toolbar or \
+            next.cell_toolbar != @props.cell_toolbar or \
             next.view_mode != @props.view_mode

     propTypes :
@@ -100,17 +104,26 @@
             normal : '>view notebook normal'
             raw    : '>view notebook raw'
             json   : '>view notebook json'
+
         shownb[@props.view_mode] = {name:shownb[@props.view_mode], style:SELECTED_STYLE}

         toolbar = {name:'toggle toolbar', display:if @props.toolbar then 'Hide Toolbar' else 'Show Toolbar'}
+
+        cell_toolbars = []
+        for name in ['none', 'metadata', 'slideshow', 'attachments', 'tags']
+            item_name = ">cell toolbar #{name}"
+            if (@props.cell_toolbar ? 'none') == name
+                cell_toolbars.push({name:item_name, style:SELECTED_STYLE})
+            else
+                cell_toolbars.push(item_name)

         @render_menu
             heading : 'View'
             names : \
                 ['toggle header', toolbar, 'toggle all line numbers', '', \
-                 '<Cell Toolbar...', '>cell toolbar none', '>cell toolbar metadata', '>cell toolbar slideshow', '>cell toolbar attachments', '>cell toolbar tags', '', \
+                 '<Cell Toolbar...'].concat(cell_toolbars).concat(['', \
                  'zoom in', 'zoom out', '', \
-                 "<Show Notebook as...", shownb.normal, shownb.raw, shownb.json]
+                 "<Show Notebook as...", shownb.normal, shownb.raw, shownb.json])

     render_insert: ->
         @render_menu
@@ -157,7 +170,7 @@

     render_kernel: ->
         items = @render_kernel_items()
-        names = ['interrupt kernel', 'confirm restart kernel', 'confirm restart kernel and clear output', \
+        names = ["#{if @props.kernel_state != 'busy' then '<' else ''}interrupt kernel", 'confirm restart kernel', 'confirm restart kernel and clear output', \
                  'confirm restart kernel and run all cells', '', \
                  '<Change kernel...'].concat(items)


Proper fix of spec. Give it a meaningful title.

--- src/spec/integration/import.spec.coffee
+++ src/spec/integration/import.spec.coffee
@@ -514,15 +514,15 @@
         expect(_.size result.body.results).toBe 1
         p = result.body.results[0].masterData.staged
         expect(p.name.en).toBe 'mySeoProdcut'
-        expect(p.metaTitle.en).toBe 'x' # I would actually expect ''
-        expect(p.metaDescription.en).toBe 'y'
-        expect(p.metaKeywords.en).toBe 'z'
-        done()
-      .fail (err) ->
-        done(_.prettify err)
-      .done()
-
-    it 'should fail a partial update of SEO attribute', (done) ->
+        expect(p.metaTitle.en).toBe 'a' # I would actually expect ''
+        expect(p.metaDescription.en).toBe 'b'
+        expect(p.metaKeywords.en).toBe 'changed'
+        done()
+      .fail (err) ->
+        done(_.prettify err)
+      .done()
+
+    it 'should not update SEO attribute if not all 3 headers are present', (done) ->
       csv =
         """
         productType,variantId,sku,name,metaTitle,metaDescription,metaKeywords


Add tests for mixin initialization logic

--- test/spec/basics.coffee
+++ test/spec/basics.coffee
@@ -1,21 +1,90 @@
+Model = null
+
+# Backbone.Model.extend() and Backbone.ComputedFields.mixin are used only in these tests, to show what exactly we do.
+# In other tests 'clazz()' shortcut will be used to create new class with already attached mixin
+
 describe "mixing", ->

-  Model = null
-  methods = ["initialize", "get", "set"]
+  methods = ["get", "set"]

-  # Backbone.Model.extend() and Backbone.ComputedFields.mixin are used only in this test, to show what exactly we do.
-  # In other tests 'clazz()' shortcut will be used to create new class with already attached mixin
-  beforeEach ->
-    Model = Backbone.Model.extend()
+  beforeEach -> Model = Backbone.Model.extend()

   it "should override methods in prototype", ->
-    Backbone.ComputedFields.mixin Model::
+    mixin Model::
     for method in methods
       expect(Model::[method]).is.not.equal Backbone.Model::[method], "Method #{method} is not overridden in prototype"

   it "should override methods in particular instance", ->
     model = new Model
-    Backbone.ComputedFields.mixin model
+    mixin model
     for method in methods
       expect(model[method]).is.not.equal Model::[method], "Method '#{method}' is not overridden in instance"
       expect(Model::[method]).is.equal Backbone.Model::[method], "Method '#{method}' is overridden in prototype"
+
+
+
+describe "initializing", ->
+
+  propName = "_computedFieldsDependenciesMap"
+
+  describe "when initializing with non-empty computed config", ->
+    beforeEach ->
+      Model = Backbone.Model.extend
+        computed:
+          answer:
+            get: -> 42
+
+    it "should create dependencies config in prototype when attached to prototype", ->
+      mixin Model::, yes
+      expect(Model::).to.have.ownProperty propName
+
+    it "still should create dependencies config in prototype, even when attached to instance", ->
+      model = new Model
+      mixin model, yes
+      expect(Model::).to.have.ownProperty propName
+      expect(model).to.not.have.ownProperty propName
+
+    it "should create dependencies config in instance when instance have own computed config", ->
+      model = new Model
+      model.computed =
+        question:
+          get: -> "The meaning of life?"
+
+      mixin model, yes
+      expect(model).to.have.ownProperty propName
+      expect(Model::).to.not.have.ownProperty propName
+
+    it "by default, should init dependencies config lazy - only after first model creation", ->
+      mixin Model::
+      expect(Model::).to.not.have.ownProperty propName
+      model = new Model
+      expect(Model::).to.have.ownProperty propName
+      expect(model).to.not.have.ownProperty propName
+
+    it "should initialize dependencies config only once", ->
+      mixin Model::, yes
+      config1 = Model::[propName]
+      model = new Model
+      config2 = model[propName]
+      expect(config1).is.an "object"
+      expect(config1).is.equal config2, "Dependencies config was rewritten after first initialization"
+
+  describe "when initializing with empty computed config", ->
+
+    beforeEach -> Model = Backbone.Model.extend()
+
+    it "should NOT create dependencies config in prototype", ->
+      mixin Model::, yes
+      expect(Model::).to.not.have.ownProperty propName
+
+    it "should NOT create dependencies config nowhere", ->
+      model = new Model
+      mixin model, yes
+      expect(model).to.not.have.ownProperty propName
+      expect(Model::).to.not.have.ownProperty propName
+
+    it "should NOT lazy create dependencies config nowhere", ->
+      mixin Model::
+      model = new Model
+      expect(Model::).to.not.have.ownProperty propName
+      expect(model).to.not.have.ownProperty propName


Initial Helpers spec covering #_initDebugMixin.

--- tests/helpers.coffee
+++ tests/helpers.coffee
@@ -0,0 +1,37 @@
+define [
+  'phaser'
+  'underscore'
+  'app/helpers'
+], (Phaser, _, Helpers) ->
+
+  DebugMixin = Helpers.DebugMixin
+
+  describe 'Helpers.DebugMixin', ->
+    obj = null
+
+    beforeEach ->
+      class SomeObject
+      _.extend SomeObject::, DebugMixin
+      obj = new SomeObject()
+
+    describe '#_initDebugMixin', ->
+      it 'initializes debug-related flags', ->
+        completedInit = obj._initDebugMixin()
+        expect(obj.debugging).toBeDefined()
+        expect(obj.tracing).toBeDefined()
+        expect(completedInit).toBe no
+
+      it 'creates #debugTextItems key-value store for info for Phaser.Utils.Debug', ->
+        obj._initDebugMixin()
+        expect(obj.debugTextItems).toEqual {}
+
+      it 'sets up gui (folder) if provided one', ->
+        gui = { add: -> }
+        spyOn(gui, 'add').and.returnValue jasmine.createSpyObj('control', ['onFinishChange'])
+
+        completedInit = obj._initDebugMixin gui
+        expect(obj.gui).toBe gui
+        expect(gui.add).toHaveBeenCalledWith obj, 'debugging'
+        expect(gui.add).toHaveBeenCalledWith obj, 'tracing'
+        expect(completedInit).toBe yes
+


handle multiple style in single span case

--- src/normalizer.coffee
+++ src/normalizer.coffee
@@ -183,17 +183,22 @@
         if attrName == 'style'
           attrVal = node.getAttribute(attrName)
           styles = attrVal.split(';')
+          first = true
           _.each(styles, (styleStr) ->
             [style, value] = styleStr.split(':')
             if style? and value?
               style = style.replace(/^\s\s*/, '').replace(/\s\s*$/, '')  # Trim
               value = value.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
-              _.any(rendererStyles, (rules, selector) ->
+              _.each(rendererStyles, (rules, selector) ->
                 [tagName, className] = selector.split('.')
                 if tagName == 'span' and rules[style] == value
-                  node.classList.add(className)
-                  return true
-                return false
+                  if first
+                    node.classList.add(className)
+                    first = false
+                  else
+                    span = lineNode.ownerDocument.createElement('span')
+                    span.classList.add(className)
+                    Scribe.DOM.wrap(span, node)
               )
           )
         node.removeAttribute(attrName)


Let's let apps decide on this

Let's let apps decide on this

--- src/browser/appwindow.coffee
+++ src/browser/appwindow.coffee
@@ -24,7 +24,6 @@
     windowOpts =
       width: 800
       height: 600
-      'auto-hide-menu-bar': process.platform is 'win32'
       title: options.title ? "You Should Set options.title"
       'web-preferences':
         'subpixel-font-scaling': true


Make writing CSON grammars slightly more bearable


Account for tab expansion when determining the character width

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -221,6 +221,11 @@

       while bufferColumn <= bufferLineLength
         character = bufferLine[bufferColumn]
+        if character is '\t'
+          distanceToNextTabStop = @tabLength - (screenColumn % @tabLength)
+          characterWidth = @ratioForCharacter(' ') * distanceToNextTabStop
+        else
+          characterWidth = @ratioForCharacter(character)
         previousCharacter = bufferLine[bufferColumn - 1]
         foldEndBufferPosition = folds[bufferRow]?[bufferColumn]
         inTrailingWhitespace = bufferColumn >= trailingWhitespaceStartBufferColumn
@@ -279,7 +284,7 @@
               })
               tokensScreenExtent = screenColumn

-        if character? and ((screenLineWidth + @ratioForCharacter(character)) > @softWrapColumn)
+        if character? and ((screenLineWidth + characterWidth) > @softWrapColumn)
           if lastWordStartBufferColumn > lastWrapBufferColumn and not lastWordEndsLeadingWhitespace
             wrapScreenColumn = lastWordStartScreenColumn
             wrapBufferColumn = lastWordStartBufferColumn


run: separate run_docker_wrapper

run: separate run_docker_wrapper

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -322,20 +322,25 @@
         done
     }

-    function runMongoDocker() {
+    function runMongoDocker () {
         docker run -d -p 27017:27017 --name=mongo koding/mongo:2016-10-11
     }

-    function runPostgresqlDocker() {
+    function runPostgresqlDocker () {
         docker run -d -p 5432:5432 --name=postgres koding/postgres
         waitPostgresReady
     }

+    function run_docker_wrapper () {
+      if [[ `uname` == 'Darwin' ]]; then
+        command -v boot2docker >/dev/null 2>&1 && boot2docker up
+        command -v docker-machine >/dev/null 2>&1 && docker-machine start default || echo 1
+      fi
+    }
+
     function build_services () {

-      if [[ `uname` == 'Darwin' ]]; then
-        boot2docker up
-      fi
+      run_docker_wrapper

       echo "Stopping services: $SERVICES"
       docker stop $SERVICES
@@ -370,9 +375,8 @@

     function services () {

-      if [[ `uname` == 'Darwin' ]]; then
-        boot2docker up
-      fi
+      run_docker_wrapper
+
       EXISTS=$(docker inspect --format="{{ .State.Running }}" $SERVICES 2> /dev/null)
       if [ $? -eq 1 ]; then
         echo ""


inputview: Added min-height and max-height to autogrow calculation

--- src/components/inputs/inputview.coffee
+++ src/components/inputs/inputview.coffee
@@ -378,6 +378,8 @@
         borderRight   : $input.css 'border-right'
         borderBottom  : $input.css 'border-bottom'
         borderLeft    : $input.css 'border-left'
+        minHeight     : $input.css 'minHeight'
+        maxHeight     : $input.css 'maxHeight'
         paddingTop    : $input.css 'padding-top'
         paddingRight  : $input.css 'padding-right'
         paddingBottom : $input.css 'padding-bottom'


Changed ids to include select.

Changed ids to include select.

--- src/DOM.coffee
+++ src/DOM.coffee
@@ -31,9 +31,9 @@
         },
         'COLORPICKER':{
             'CONTAINER':'wolfcage-colorpicker',
-            'ACTIVE_HEX':'wolfcage-colorpicker-active-hex',
-            'BORDER_HEX':'wolfcage-colorpicker-border-hex',
-            'INACTIVE_HEX':'wolfcage-colorpicker-inactive-hex'
+            'ACTIVE_HEX':'wolfcage-colorpicker-select-active-hex',
+            'BORDER_HEX':'wolfcage-colorpicker-select-border-hex',
+            'INACTIVE_HEX':'wolfcage-colorpicker-select-inactive-hex'
         },
         'TABS':{
             'CONTAINER':'wolfcage-tab-container'


feat(json): added base64 encoding

feat(json): added base64 encoding

--- src/datahandlers/json.coffee
+++ src/datahandlers/json.coffee
@@ -1,8 +1,8 @@
 class JSONHandler
   load: (data) ->
-    JSON.parse data
+    JSON.parse atob data

   save: (data) ->
-    JSON.stringify data
+    btoa ko.toJSON data

 module.exports = JSONHandler if module?


(feature) Refactor to allow inheritance.

--- src/exercise_list.coffee
+++ src/exercise_list.coffee
@@ -1,4 +1,4 @@
-module.exports = (ko, config) ->
+module.exports = (ko) ->
   class ExerciseViewModel
     constructor: (data) ->
       @id = data.id
@@ -12,18 +12,21 @@
       @formattedDueDate = ko.computed => @dueDate().toLocaleDateString()
       @isOld = ko.computed => @dueDate().getTime() < Date.now()

-    show: => config.show @id
+    show: => @show @id

   class OverviewPageViewModel
     constructor: ->
       @exercises = ko.observableArray()

-      config.getExercises (exercises) =>
-        @exercises exercises.map( (e) -> new ExerciseViewModel(e)).sort (a, b) ->
+      @getExercises (exercises) =>
+        @exercises exercises.map((e) => @createExerciseViewModel(e)).sort (a, b) ->
           if a.isOld()
             return if b.isOld() then 0 else 1
           else
             return if b.isOld() then 1 else 0

-  viewModel: OverviewPageViewModel
-  template: config.html
+    createExerciseViewModel: (e) -> new ExerciseViewModel(e)
+
+
+  ExerciseViewModel: ExerciseViewModel
+  OverviewPageViewModel: OverviewPageViewModel


Track loading/done loading status

Track loading/done loading status

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -3,6 +3,7 @@
 _ = require 'underscore'
 {TocStore} = require './toc'
 {makeSimpleStore} = require './helpers'
+LOADING = 'loading'

 EXERCISE_TAGS =
   TEKS: 'teks'
@@ -31,6 +32,7 @@

 ExerciseConfig =
   _exercises: []
+  _asyncStatus: null

   FAILED: -> console.error('BUG: could not load exercises')

@@ -39,11 +41,12 @@
     @_exerciseCache = []

   load: (courseId, pageIds) -> # Used by API
+    @_asyncStatus = LOADING

   loaded: (obj, courseId, pageIds) ->
     key = pageIds.toString()
+    delete @_asyncStatus
     return if @_exercises[key] and @_HACK_DO_NOT_RELOAD
-
     @_exercises[key] = obj.items
     _exerciseCache = []
     _.each obj.items, (exercise) ->
@@ -55,6 +58,8 @@
   HACK_DO_NOT_RELOAD: (bool) -> @_HACK_DO_NOT_RELOAD = bool

   exports:
+    isLoading: -> @_asyncStatus is LOADING
+
     isLoaded: (pageIds) ->
       !!@_exercises[pageIds.toString()]

@@ -69,6 +74,7 @@
         tag?.chapter_section
       exercises = _.sortBy(@_exercises[pageIds.toString()], byChapterSection)
       _.groupBy(exercises, byChapterSection)
+

     getExerciseById: (exercise_id) ->
       @_exerciseCache[exercise_id]


isFileSync -> existsSync

isFileSync -> existsSync

--- src/apm.coffee
+++ src/apm.coffee
@@ -22,14 +22,14 @@
     appFolder = path.dirname(apmFolder)
     if path.basename(apmFolder) is 'apm' and path.basename(appFolder) is 'app'
       asarPath = "#{appFolder}.asar"
-      if fs.isFileSync(asarPath)
+      if fs.existsSync(asarPath)
         return process.nextTick -> callback(asarPath)

     apmFolder = path.resolve(__dirname, '..', '..', '..')
     appFolder = path.dirname(apmFolder)
     if path.basename(apmFolder) is 'apm' and path.basename(appFolder) is 'app'
       asarPath = "#{appFolder}.asar"
-      if fs.isFileSync(asarPath)
+      if fs.existsSync(asarPath)
         return process.nextTick -> callback(asarPath)

     switch process.platform
@@ -40,7 +40,7 @@
           callback("#{appLocation}/Contents/Resources/app.asar")
       when 'linux'
         appLocation = '/usr/local/share/atom/resources/app.asar'
-        unless fs.isFileSync(appLocation)
+        unless fs.existsSync(appLocation)
           appLocation = '/usr/share/atom/resources/app.asar'
         process.nextTick -> callback(appLocation)
       when 'win32'


Adjust intensity to magnitude conversion

--- src/app.coffee
+++ src/app.coffee
@@ -159,12 +159,12 @@
           if isNaN parseFloat feature.properties.magnitude
             switch feature.properties.magnitude
               when "I" then mag = 1
-              when "II" then mag = 2
+              when "II" then mag = 3
               when "III" then mag = 3
               when "IV" then mag = 4
-              when "V" then mag = 5
-              when "VI" then mag = 6
-              when "VII" then mag = 7
+              when "V" then mag = 4
+              when "VI" then mag = 5
+              when "VII" then mag = 6

           else
             mag = parseFloat feature.properties.magnitude


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -23,6 +23,7 @@
       I.currentState = requestedState

       I.currentState.trigger "enter", previousState
+

       requestedState = null


Several corrections to API calls. - Tie api calls to specific version numbers - Change create-frame, parse-setup, parse to POST

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -46,7 +46,8 @@
   requestWithOpts = (path, opts, go) ->
     doGet (composePath path, opts), go

-  encodeArray = (array) -> "[#{join (map array, encodeURIComponent), ','}]"
+  encodeArrayForGet = (array) -> "[#{encodeURIComponent join (map array, (element) -> "\"#{element}\""), ','}]"
+  encodeArrayForPost = (array) -> "[#{join (map array, (element) -> "\"#{element}\""), ','}]"

   encodeObject = (source) ->
     target = {}
@@ -56,10 +57,11 @@

   requestInspect = (key, go) ->
     opts = key: encodeURIComponent key
-    requestWithOpts '/Inspect.json', opts, go
+    requestWithOpts '/1/Inspect.json', opts, go

   requestCreateFrame = (opts, go) ->
-    requestWithOpts '/2/CreateFrame.json', (encodeObject opts), go
+    #requestWithOpts '/2/CreateFrame.json', (encodeObject opts), go
+    doPost '/2/CreateFrame.json', opts, go

   requestFrames = (go) ->
     doGet '/3/Frames.json', (error, result) ->
@@ -83,7 +85,7 @@
         go null, head result.frames

   requestJobs = (go) ->
-    doGet '/3/Jobs.json', (error, result) ->
+    doGet '/2/Jobs.json', (error, result) ->
       if error
         go new Flow.Error 'Error fetching jobs', error
       else
@@ -92,7 +94,7 @@
   requestJob = (key, go) ->
     #opts = key: encodeURIComponent key
     #requestWithOpts '/Job.json', opts, go
-    doGet "/3/Jobs.json/#{encodeURIComponent key}", (error, result) ->
+    doGet "/2/Jobs.json/#{encodeURIComponent key}", (error, result) ->
       if error
         go new Flow.Error "Error fetching job '#{key}'", error
       else
@@ -102,7 +104,7 @@
     opts =
       src: encodeURIComponent path
       limit: limit
-    requestWithOpts '/Typeahead.json/files', opts, go
+    requestWithOpts '/2/Typeahead.json/files', opts, go

   requestImportFiles = (paths, go) ->
     tasks = map paths, (path) ->
@@ -112,26 +114,25 @@

   requestImportFile = (path, go) ->
     opts = path: encodeURIComponent path
-    requestWithOpts '/ImportFiles.json', opts, go
+    requestWithOpts '/2/ImportFiles.json', opts, go

   requestParseSetup = (sources, go) ->
-    encodedPaths = map sources, encodeURIComponent
     opts =
-      srcs: "[#{join encodedPaths, ','}]"
-    requestWithOpts '/ParseSetup.json', opts, go
+      srcs: encodeArrayForPost sources
+    doPost '/2/ParseSetup.json', opts, go

   requestParseFiles = (sourceKeys, destinationKey, parserType, separator, columnCount, useSingleQuotes, columnNames, deleteOnDone, checkHeader, go) ->
     opts =
-      hex: encodeURIComponent destinationKey
-      srcs: encodeArray sourceKeys
+      hex: destinationKey
+      srcs: encodeArrayForPost sourceKeys
       pType: parserType
       sep: separator
       ncols: columnCount
       singleQuotes: useSingleQuotes
-      columnNames: encodeArray columnNames
+      columnNames: encodeArrayForPost columnNames
       checkHeader: checkHeader
       delete_on_done: deleteOnDone
-    requestWithOpts '/Parse.json', opts, go
+    doPost '/2/Parse.json', opts, go

   patchUpModels = (models) ->
     for model in models
@@ -159,16 +160,16 @@
         go error, head patchUpModels result.models

   requestModelBuilders = (go) ->
-    doGet "/2/ModelBuilders.json", go
+    doGet "/3/ModelBuilders.json", go

   requestModelBuilder = (algo, go) ->
-    doGet "/2/ModelBuilders.json/#{algo}", go
+    doGet "/3/ModelBuilders.json/#{algo}", go

   requestModelInputValidation = (algo, parameters, go) ->
-    doPost "/2/ModelBuilders.json/#{algo}/parameters", parameters, go
+    doPost "/3/ModelBuilders.json/#{algo}/parameters", parameters, go

   requestModelBuild = (algo, parameters, go) ->
-    doPost "/2/ModelBuilders.json/#{algo}", parameters, go
+    doPost "/3/ModelBuilders.json/#{algo}", parameters, go

   requestPredict = (modelKey, frameKey, go) ->
     doPost "/3/Predictions.json/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", {}, (error, result) ->
@@ -223,16 +224,16 @@
     go null, Flow.LocalStorage.write type, id, obj

   requestCloud = (go) ->
-    doGet '/3/Cloud.json', go
+    doGet '/1/Cloud.json', go

   requestTimeline = (go) ->
-    doGet '/3/Timeline.json', go
+    doGet '/2/Timeline.json', go

   requestProfile = (depth, go) ->
-    doGet "/3/Profiler.json?depth=#{depth}", go
+    doGet "/2/Profiler.json?depth=#{depth}", go

   requestStackTrace = (go) ->
-    doGet '/3/JStack.json', go
+    doGet '/2/JStack.json', go

   requestRemoveAll = (go) ->
     doGet '/3/RemoveAll.json', go


seekToTime todo added

seekToTime todo added

--- src/foundation-player.coffee
+++ src/foundation-player.coffee
@@ -105,6 +105,7 @@
       @updateButtonPlay()
       @
     seekToTime: (time) ->
+      # TODO: `time` should be parsable MM:SS string.
       @audio.currentTime = time
       @updatePlayedProgress()
       @updateTimeStatuses()


No need for optimist anymore

No need for optimist anymore

--- src/cli.coffee
+++ src/cli.coffee
@@ -1,9 +1,10 @@
 #!/usr/bin/env phantomjs

-_ = require "lodash"
 system = require "system"
-opt = require "optimist-phantomjs"
-opt.usage """
+usage = ->
+	console.log help()
+help = -> """
+
 phantomtask <URL> [tasks] [options]

 Tasks:
@@ -13,13 +14,14 @@

     -n, --name <taskname>    Name this task.
     -o, --options <json>     Pass options to the task. (JSON string)
+
+Options:
+    -h, --help               Display help.
+    -p, --parallel           Run tasks in parallel.
+    -d, --debug              Output debug info.
+    -i, --inject <script>    Inject script(s) into the page.
+
 """
-
-opt.alias    "i", "inject"
-opt.describe "i", "Inject script(s) into the page."
-opt.alias    "p", "parallel"
-opt.boolean  "p"
-opt.describe "p", "Run tasks in parallel."

 options = {}
 src = null
@@ -47,7 +49,6 @@
 					i--
 					return [path, label, options]

-
 	while i < total
 		switch args[i]
 			when "-t", "--task"
@@ -56,6 +57,9 @@
 			when "-i", "--inject"
 				i++
 				scripts.push args[i]
+			when "-h", "--help"
+				usage()
+				phantom.exit 0
 			when "-p", "--parallel"
 				options.parallel = yes
 			when "-d", "--debug"
@@ -65,11 +69,13 @@
 		i++

 unless src
-	console.error "Missing URL."
-	opt.showHelp()
+	console.error """
+
+	Error: No URL provided.
+	"""
+	usage()
 	phantom.exit 1

-console.log "src: #{src}"
 PhantomTask = require "./phantomtask"
 task = new PhantomTask options


add security tests and update old tests

add security tests and update old tests

--- tests/recaptcha2.coffee
+++ tests/recaptcha2.coffee
@@ -36,27 +36,25 @@

   describe "validate", ->
     describe "when there is a valid frontend captcha response", ->
-      it "resolves as successful", (done)->
+      it "resolves as successful", ()->
         postData = response: "valid_captcha_response", remoteip: "127.0.0.1", secret: "secret_key"
         scope = nock("https://www.google.com")
         .post("/recaptcha/api/siteverify", postData).reply 200, RECAPTCHA_RESPONSE_OK
         recaptcha2.validate("valid_captcha_response", "127.0.0.1")
         .then (response)->
           response.should.eql true
-          done()
         .catch (error)->
           should.not.exist error

     describe "when there is an empty frontend captcha response", ->
-      it "rejects", (done)->
+      it "rejects", ()->
         recaptcha2.validate("")
         .catch (errors)->
           should.exist errors
           errors.should.eql ['missing-input-response']
-          done()

     describe "when there is an invalid frontend captcha response", ->
-      it "rejects", (done)->
+      it "rejects", ()->
         postData = response: "invalid_captcha_response", remoteip: "127.0.0.1", secret: "secret_key"
         scope = nock("https://www.google.com")
         .post("/recaptcha/api/siteverify", postData).reply 200, RECAPTCHA_RESPONSE_ERROR
@@ -64,10 +62,9 @@
         .catch (errors)->
           should.exist errors
           errors.should.eql ['invalid-input-response', "invalid-input-secret"]
-          done()

     describe "when there is a request error", ->
-      it "rejects", (done)->
+      it "rejects", ()->
         postData = response: "valid_captcha_response", remoteip: "127.0.0.1", secret: "secret_key"
         scope = nock("https://www.google.com")
         .post("/recaptcha/api/siteverify", postData).replyWithError 500
@@ -75,18 +72,16 @@
         .catch (errors)->
           should.exist errors
           errors.should.eql ['request-error', "Error: 500"]
-          done()

   describe "validateRequest", ->
     describe "when there is a valid frontend captcha response", ->
-      it "resolves as successful", (done)->
+      it "resolves as successful", ()->
         postData = response: "valid_captcha_response", remoteip: "127.0.0.1", secret: "secret_key"
         scope = nock("https://www.google.com")
         .post("/recaptcha/api/siteverify", postData).reply 200, RECAPTCHA_RESPONSE_OK
         recaptcha2.validateRequest({body: {'g-recaptcha-response': "valid_captcha_response"}}, "127.0.0.1")
         .then (response)->
           response.should.eql true
-          done()
         .catch (error)->
           should.not.exist error

@@ -123,12 +118,24 @@
         recaptcha2.translateErrors(errors).should.eql readableErrors

   describe "formElement", ->
-    describe "when there is a given htlm class", ->
+    describe "when there is a given html class", ->
       it "returns a div with the given class", ->
-        div = '

'
-        recaptcha2.formElement("test-class").should.eql div
+        div = '

'
+        recaptcha2.formElement("test-class class2").should.eql div

-    describe "when there is no given htlm class", ->
+    describe "when there is no given html class", ->
       it "returns a div with the default class", ->
         div = '

'
         recaptcha2.formElement().should.eql div
+
+  describe "security tests", ->
+    attackVectorClass='" OnErRor=alert(123) b="'
+    attackVectorSecretKey='" OnErRor=alert(321) b="'
+    insecureRecaptcha = new Recaptcha2 siteKey: attackVectorSecretKey, secretKey: "secret_key"
+    describe "html class name is present", ->
+      it "returns a div with the escaped class", ->
+        insecureRecaptcha.formElement(attackVectorClass).should.not.containEql attackVectorClass
+
+    describe "when sitekey is malicious", ->
+      it "returns a div with the escaped sitekey", ->
+        insecureRecaptcha.formElement().should.not.containEql attackVectorSecretKey


add route to retrieve all todo tasks

add route to retrieve all todo tasks

--- config/routes.coffee
+++ config/routes.coffee
@@ -2,13 +2,14 @@
     #    map.get '/', 'tasks#index'

     map.get '/tasks/', 'tasks#all'
+    map.get '/tasks/todo', 'tasks#todo'
     map.get '/tasks/archives', 'tasks#all-archives'
     map.get '/todolists/:listId/tasks', 'tasks#todo'
     map.get '/todolists/:listId/tasks/archives', 'tasks#archives'
     map.post '/todolists/:listId/tasks', 'tasks#create'
-    map.get '/todolists/:listId/tasks/:id/', 'tasks#show'
-    map.put '/todolists/:listId/tasks/:id/', 'tasks#update'
-    map.del '/todolists/:listId/tasks/:id/', 'tasks#destroy'
+    map.get '/todolists/:listId/tasks/:id', 'tasks#show'
+    map.put '/todolists/:listId/tasks/:id', 'tasks#update'
+    map.del '/todolists/:listId/tasks/:id', 'tasks#destroy'

     map.get '/todolists', 'todolists#all'
     map.post '/todolists', 'todolists#create'


Testing file creation/update/deletion in watch mode

--- tests/unit/polvo.coffee
+++ tests/unit/polvo.coffee
@@ -7,6 +7,7 @@
 base = path.join __dirname, '..', 'mocks', 'basic'
 files =
   pack: path.join base, 'package.json'
+  app: path.join base, 'src', 'app.coffee'
   config: path.join base, 'polvo.yml'
   js: path.join base, 'public', 'app.js'
   css: path.join base, 'public', 'app.css'
@@ -28,8 +29,16 @@
 pack = '{"name": "basic"}'

 clear_cache = ->
-  mod = require.resolve '../../lib/utils/plugins'
-  delete require.cache[mod]
+  mods = [
+    '../../lib/utils/plugins'
+    '../../lib/core/compiler'
+    '../../lib/core/file'
+    '../../lib/core/files'
+  ]
+
+  for m in mods
+    mod = require.resolve m
+    delete require.cache[mod]

 describe '[polvo]', ->

@@ -42,6 +51,7 @@
     fs.writeFileSync files.config, config

   afterEach ->
+    clear_cache()

   after ->
     fs.unlinkSync files.config if fs.existsSync files.config
@@ -68,9 +78,10 @@

     outs.should.equal 1
     errors.should.equal 0
+    clear_cache()

-  it 'should compile project without any surprises', ->
+  it 'should compile app without any surprises', ->
     errors = outs = 0
     checker = /✓ public\/app\.(js|css).+$/m

@@ -100,7 +111,8 @@
       err:(msg) -> errors++
       nocolor: true

-    compile = polvo options, stdio
+    fs.writeFileSync files.pack, pack
+    release = polvo options, stdio
     errors.should.equal 0

   it 'version should be printed properly with `polvo -v`', ->
@@ -114,7 +126,58 @@
         outs++
         version.should.equal require('../../package.json').version

-    compile = polvo options, stdio
+    fs.writeFileSync files.pack, pack
+    version = polvo options, stdio

     outs.should.equal 1
-    errors.should.equal 0+    errors.should.equal 0
+
+  it 'should start app app and perform crate/change/delete files events in watch mode', (done)->
+    @timeout 4000
+
+    errors = outs = 0
+    checkers = [
+      /✓ public\/app\.js.+/
+      /✓ public\/app\.css.+/
+
+      /\• src\/app.coffee/
+      /✓ public\/app\.js/
+
+      /\- src\/app.coffee/
+      /✓ public\/app\.js/
+
+      /\+ src\/app\.coffee/
+      /✓ public\/app\.js/
+    ]
+
+    options = watch: 'true', base: base
+    stdio =
+      out:(msg) ->
+        checkers.shift().test(msg).should.be.true
+        if checkers.length is 0
+          polvo.close()
+          errors.should.equal 0
+          done()
+      err:(msg) -> errors++
+      nocolor: true
+
+    fs.writeFileSync files.pack, pack
+    backup = fs.readFileSync files.app
+
+    polvo = require '../../lib/polvo'
+    start = polvo options, stdio
+
+    # editing
+    new setTimeout ->
+      fs.appendFileSync files.app, '\n\na = 1\n'
+    , 1000
+
+    # deleting
+    new setTimeout ->
+      fs.unlinkSync files.app
+    , 2000
+
+    # creating
+    new setTimeout ->
+      fs.writeFileSync files.app, backup
+    , 3000


Fixing the datepicker, so that it will select the date when selecting year from dropdown..


do not show profile pages for team subdomains

--- servers/lib/server/handlers/profile.coffee
+++ servers/lib/server/handlers/profile.coffee
@@ -1,4 +1,5 @@
-{ error_404 }          = require './../helpers'
+{ error_404, isMainDomain } = require './../helpers'
+
 koding                 = require './../bongo'
 { generateFakeClient } = require "./../client"
 Crawler                = require './../../crawler'
@@ -7,9 +8,11 @@

   bongoModels                 = koding.models
   { JName }                   = bongoModels
-  { params }                  = req
+  { params, headers }         = req
   { name, section, slug }     = params
   { path, loggedIn, account } = options
+
+  return next()  unless isMainDomain req

   JName.fetchModels name, (err, result)->


set isempty off when adding children tags

--- src/xml.coffee
+++ src/xml.coffee
@@ -47,6 +47,7 @@

         @pending.push tag if tag.closed is no
         @emit 'add', this, tag
+        @isempty = no
         tag.emit? 'close', tag if tag.closed
         callback?.call(this, tag)


update: Don't show zeroes use blank

update: Don't show zeroes use blank

--- app/assets/javascripts/staffplans/show/views/work_week_list_view.js.coffee
+++ app/assets/javascripts/staffplans/show/views/work_week_list_view.js.coffee
@@ -59,8 +59,8 @@
         workWeek = @workWeekByCweekAndYear dateObject.mweek, dateObject.year

         if workWeek?
-          dateObject.actual_hours = workWeek.get('actual_hours')
-          dateObject.estimated_hours = workWeek.get('estimated_hours')
+          dateObject.actual_hours =    (workWeek.get('actual_hours') or '')
+          dateObject.estimated_hours = (workWeek.get('estimated_hours') or '')
           dateObject.cid = workWeek.cid

         dateObject
@@ -113,6 +113,7 @@
       .offset(offset)

   hideRowFiller: (event) ->
+    @zeroToBlank event # XXX Can't bind multiple methods to the same event via Backbone :(
     event.currentTarget.type = "text"
     @_lastFocused = event.currentTarget
     @_rowFillerTimer = setTimeout =>
@@ -127,6 +128,10 @@
     $(@_lastFocused).parent().nextAll().find('input[data-work-week-input]')
       .val(@_lastFocused.value)
       .trigger('change')
+
+  zeroToBlank: (event) ->
+    if (event.currentTarget.value == '0')
+      event.currentTarget.value = ''

   queueUpdateOrCreate: (event) ->
     window.clearTimeout event.currentTarget.timeout


rough draft work on groups and routes

rough draft work on groups and routes

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -4,15 +4,18 @@

   tree = {}

-  getHashFragment =(url)-> url.substr url.indexOf '#'
+  getHashFragment =(url)-> url.substr 1 + url.indexOf '#'

   handleNotFound =(route)=> @handleNotFound? route

-  changeRoute =(frag)->
+  changeRoute =(frag, rewriteHashFragment=no)->
     node = tree
     params = {}
-    frag = frag.split('/')
+    frag = frag.split '/'
     frag.shift() # first edge is garbage like '' or '#!'
+
+    path = frag.join '/'
+    history.pushState path, KDRouter.getTitle(path), path

     for edge in frag
       if node[edge]
@@ -22,7 +25,7 @@
         if param?
           params[param.name] = edge
           node = param
-        else handleNotFound frag.join('/')
+        else handleNotFound frag.join '/'

     listeners = node[listenerKey]
     if listeners?.length
@@ -30,16 +33,18 @@

   window.addEventListener 'popstate', (event)->
     console.log event
-    console.log location
+    changeRoute event.state, no  if event.state?

   window.addEventListener 'hashchange', (event)->
-    changeRoute getHashFragment(event.newURL)
+    changeRoute getHashFragment(event.newURL), yes
+
+  @getTitle =(path)-> path

   @init =-> changeRoute location.hash.substr 1 if location.hash.length

-  @handleNotFound =-> log "The route #{route} was not found!"
+  @handleNotFound =(route)-> log "The route #{route} was not found!"

-  @handleRoute =(route)-> changeRoute route
+  @handleRoute =(route)-> changeRoute route, no

   @addRoutes =(routes)->
     @addRoute route, listener for own route, listener of routes


code quality

code quality

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -53,6 +53,9 @@

   handleNotFound:(route)-> log "The route #{route} was not found!"

+  routeWithoutThisEdge =(route, edge)->
+    "/#{route.slice(0, i).concat(route.slice i + 1).join '/'}"
+
   addRoute:(route, listener)->
     @routes[route] = listener
     node = @tree
@@ -62,10 +65,7 @@
       len = edge.length-1
       if '?' is edge.substr len # then this is an "optional edge".
         # recursively alias this route without this optional edge:
-        routeWithoutThisEdge = "/#{
-          route.slice(0, i).concat(route.slice i + 1).join '/'
-        }"
-        @addRoute routeWithoutThisEdge, listener
+        @addRoute routeWithoutThisEdge(route, edge), listener
         edge = edge.substr 0, len # get rid of the "?" from the route
       if /^:/.test edge
         node[':'] or= name: edge.substr 1


fix obsolete document options

fix obsolete document options

--- src/tex-writer.coffee
+++ src/tex-writer.coffee
@@ -153,8 +153,8 @@
   write "% IN-DOCUMENT CONFIGURATION"
   ### TAINT use default configuration *.ptv file ###
   ### TAINT make more general; ATM can only decide on boolean ###
-  S.configuration[ 'document/geometry/show/cmgrid'      ]?= false
-  S.configuration[ 'document/geometry/show/mmgrid'      ]?= false
+  S.configuration[ 'document/geometry/show/textgrid'    ]?= false
+  S.configuration[ 'document/geometry/show/papergrid'   ]?= false
   S.configuration[ 'document/geometry/show/linebands'   ]?= false
   S.configuration[ 'document/geometry/show/columns'     ]?= false
   S.configuration[ 'document/geometry/show/baselines'   ]?= false


header, section & footer organism autorender with constructor


Modify multi line reply function

Modify multi line reply function

--- coffee/BabyFtpd.coffee
+++ coffee/BabyFtpd.coffee
@@ -34,7 +34,7 @@
           for i in [0..replys.length-1]
             if i is (replys.length - 1)
               replyData += status.toString() + " "
-            else if replys[i].substring(0, i).match(/[0-9]/)
+            else if replys[i].match(/^[0-9]/)
               replyData += "  "
             replyData += replys[i] + "\r\n"
         @write(replyData, callback)


Use correct ObjectID before saving object

--- coffee/document.coffee
+++ coffee/document.coffee
@@ -61,6 +61,7 @@
   errors   = []
   message  = ''

+  req.body._id = req.doc._id
   req.body.tilbyder = req.usr
   req.body.endret = new Date().toISOString()
   req.body.checksum = createHash('md5').update(JSON.stringify(req.body)).digest('hex')


open modal if logged out

open modal if logged out

--- apps/auction/client/index.coffee
+++ apps/auction/client/index.coffee
@@ -7,6 +7,7 @@
 openSpecialistModal = require '../../../components/simple_contact/specialist_feedback.coffee'
 ConfirmRegistrationModal = require '../../../components/credit_card/client/confirm_registration.coffee'
 AuctionArtworksView = require '../../../components/auction_artworks/view.coffee'
+mediator = require '../../../lib/mediator.coffee'
 setupClocks = require './clocks.coffee'
 EmailRegistrationView = require './email_registration.coffee'
 attachCTA = require './cta.coffee'
@@ -18,7 +19,7 @@
   feature = new Feature FEATURE
   auction = new Auction AUCTION
   artworks = new Artworks ARTWORKS
-  user = new CurrentUser(USER)
+  user = new CurrentUser(USER) if USER

   # If we are on the confirm-registration path then pop up a modal
   # Page is otherwise unchanged
@@ -55,3 +56,10 @@
   $('.js-specialist-contact-link').click (e) ->
     e.preventDefault()
     openSpecialistModal()
+
+  $('.js-register-button').click (e) ->
+    return if user
+    e.preventDefault()
+    mediator.trigger 'open:auth',
+      mode: 'register'
+      redirectTo: $(e.target).attr('href')


add a way to deal with load to an error message

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -1683,7 +1683,7 @@

                 if err
                     if not @public_access
-                        alert_message(type:"error", message:"Problem reading file listing for '#{path}' -- #{misc.trunc(err,100)}; email help@sagemath.com (include the id #{@project.project_id}).")
+                        alert_message(type:"error", message:"Problem reading file listing for '#{path}' -- #{misc.trunc(err,100)}; email help@sagemath.com (include the id #{@project.project_id}). If the system is heavily loaded enter your credit card under billing and request a $7/month membership to move your project(s) to a members-only server, or wait until the load is lower.", timeout:15)
                         @current_path = []
                     cb?(err)
                 else


project log breakage mentioned in previous commit was a 1-character typo (fixed here).

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -204,7 +204,7 @@
                 cb?()

     connected: () =>
-        return @sync_server?  # only defined once we have connection.
+        return @dsync_server?  # only defined once we have connection.

     live: (s) =>
         if s?


Fix whitespace, delete old code

Fix whitespace, delete old code

--- frontend/coffeescript/app/webform/views.coffee
+++ frontend/coffeescript/app/webform/views.coffee
@@ -432,80 +432,60 @@

           return null

-        switch_question: ( next_index, forward ) ->
-
-            #TODO: if in group, test relevance/constraint for all children
-
-            # Does the current active question pass our constraints?
-            if forward
-                if not @passes_question_constraints()
-                    return @
-
-            previous_question = document.flat_fields[@currentQuestionIndex]
-            if not previous_question
-              @_display_form_buttons( @currentQuestionIndex, current_question )
+        switch_question: ( next_index, advancing ) ->
+          #TODO: if in group, test relevance/constraint for all children
+
+          # Does the current active question pass our constraints?
+          if advancing
+            return unless @passes_question_constraints()
+
+          previous_question = document.flat_fields[@currentQuestionIndex]
+          if not previous_question
+            @_display_form_buttons( @currentQuestionIndex, current_question )
+            return
+
+          if previous_question.type is 'group' and next_index isnt -1
+            #TODO: check if group is field-list or not first
+            if advancing
+              next_index = next_index + previous_question.children.length
+
+          current_question = document.flat_fields[next_index]
+          if not advancing
+            #is current question within group
+            group = @get_group_for_question(current_question)
+            if group
+              next_index = next_index - group.children.length
+              current_question = document.flat_fields[next_index]
+
+          if not current_question
+            @_display_form_buttons( @currentQuestionIndex, current_question )
+            return
+
+          #Is this question relevant?  Or, is this question an equation?
+          if (current_question.bind and current_question.bind.calculate != null) or not
+            XFormConstraintChecker.isRelevant( current_question, @queryStringToJSON($( ".form" ).serialize()) )
+              # If its a calculation, calculate it!
+              $("#" + current_question.name).val _performCalcluate(current_question.bind.calculate)  if current_question.bind and current_question.bind.calculate
+
+              # Switch to the next question!
+              if advancing
+                next_index += 1 if next_index < @numberOfQuestions
+              else
+                next_index -= 1 if next_index > 0
+
+              @switch_question( next_index, advancing )
               return

-            if previous_question.type == 'group' and next_index != -1
-              #TODO: check if group is field-list or not first
-              if forward
-                next_index = next_index + previous_question.children.length
-
-            # Question to switch to
-            #switch_question_key = $( element ).data( 'key' )
-
-            # Check constraints of this question before continuing
-            #question_index = -1
-            # form_info = _.find( @input_fields, ( child ) ->
-            #     @currentQuestionIndex += 1
-            #     return child.name == switch_question_key
-            # )
-            current_question = document.flat_fields[next_index]
-            if not forward
-              #is current question within group
-              group = @get_group_for_question(current_question)
-              if group
-                next_index = next_index - group.children.length
-                current_question = document.flat_fields[next_index]
-
-            if not current_question
-              @_display_form_buttons( @currentQuestionIndex, current_question )
-              return
-
-            #Is this question relevant?  Or, is this question an equation?
-            if (current_question.bind and current_question.bind.calculate != null) or not
-              XFormConstraintChecker.isRelevant( current_question, @queryStringToJSON($( ".form" ).serialize()) )
-                # If its a calculation, calculate it!
-                $("#" + current_question.name).val _performCalcluate(current_question.bind.calculate)  if current_question.bind and current_question.bind.calculate
-
-                # Switch to the next question!
-                if forward
-                    if next_index < @numberOfQuestions
-                        next_index += 1
-                else
-                    if next_index > 0
-                        next_index -= 1
-
-                @switch_question( next_index, forward )
-                return
-
-            if @toggle_question(current_question, false)
-              @toggle_question(previous_question, true)
-
-            # If there is a query to a previous answer, display that answer
-            # subsequent = undefined
-            # if (form_info.title and subsequent = form_info.title.indexOf("${")) isnt -1
-            #   end_subsequent = form_info.title.indexOf("}", subsequent)
-            #   subsequent_st = form_info.title.substring(subsequent + 2, end_subsequent)
-            #   switch_question[0].innerHTML = switch_question[0].innerHTML.replace(/\${.+}/, $("#" + subsequent_st).val())
-
-            @currentQuestionIndex = next_index
-            #Start the Geopoint display if geopoint
-            #_geopointDisplay()  if form_info.bind isnt `undefined` and form_info.bind.map isnt `undefined`
-
-            @_display_form_buttons( @currentQuestionIndex, current_question )
-
-            @
+          if @toggle_question(current_question, false)
+            @toggle_question(previous_question, true)
+
+          @currentQuestionIndex = next_index
+          #Start the Geopoint display if geopoint
+          #_geopointDisplay()  if form_info.bind isnt `undefined` and form_info.bind.map isnt `undefined`
+
+          @_display_form_buttons( @currentQuestionIndex, current_question )
+
+          @

         next_question: () ->


sshkey: url is added into main.dev.coffe

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -521,6 +521,10 @@
             location    : "~ /sitemap(.*).xml"
             proxyPass   : "http://socialapi/sitemap$1.xml"
           }
+          {
+            location    : "~ /api/social/sshkey"
+            proxyPass   : "http://socialapi/sshkey$1$is_args$args"
+          }
         ]

     dailyemailnotifier  :


Node: Change req prefix to require because of conflict with core snippet

--- snippets/node.cson
+++ snippets/node.cson
@@ -3,7 +3,7 @@
     prefix: "moduleExports"
     body: "module.exports = ${1:name};"
   "require module":
-    prefix: "req"
+    prefix: "require"
     body: '''
     ${1:const} ${2:module} = require('${3:${2:module}}');$0
     '''


Treat new content the same as existing content when saving

--- scripts/gh-book/app.coffee
+++ scripts/gh-book/app.coffee
@@ -141,12 +141,8 @@
       ret = null
       switch method
         when 'read' then ret = readFile(path, model.isBinary)
-        when 'update' then ret = writeFile(path, model.serialize(), 'Editor Save', model.isBinary)
-        when 'create'
-          # Create an id if this model has not been saved yet
-          id = _uuid()
-          model.set 'id', id
-          ret = writeFile(path, model.serialize(), model.isBinary)
+        when 'update' then ret = writeFile(path, model.serialize(), 'Editor Update', model.isBinary)
+        when 'create' then ret = writeFile(path, model.serialize(), 'Editor Create', model.isBinary)
         else throw "Model sync method not supported: #{method}"

       ret.done (value) => options?.success?(value)


Added hack to load the workspace

Added hack to load the workspace

--- scripts/gh-book/app.coffee
+++ scripts/gh-book/app.coffee
@@ -79,8 +79,15 @@
   # Custom routes to configure the Github User and Repo from the browser
   class GithubRouter extends Backbone.Router
     routes:
+      '':                                 'justStart'
       'repo/:repoUser/:repoName':         'configRepo'
       'repo/:repoUser/:repoName/:branch': 'configRepo'
+
+    justStart: () ->
+      # HACK: Another async require so we don't start fetching prematurely.
+      require ['cs!controllers/routing'], (controller) ->
+        # Open the workspace
+        controller.workspace()

     configRepo: (repoUser, repoName, branch='master') ->
       session.set
@@ -88,10 +95,7 @@
         'repoName': repoName
         'branch': branch

-      # HACK: Another async require so we don't start fetching prematurely.
-      require ['cs!controllers/routing'], (controller) ->
-        # Open the workspace
-        controller.workspace()
+      @justStart()

   new GithubRouter()
   Backbone.history.start() if not Backbone.History.started


Adjust commit link text

Adjust commit link text

--- scripts/github-hook.coffee
+++ scripts/github-hook.coffee
@@ -121,7 +121,7 @@
     {creator, sha, environment} = deployment

     """
-    [#{creator.login}](#{creator.html_url}) started a deployment of [#{sha[0..8]}](#{repository.html_url}/commits/#{sha}) in [#{repository.name}](#{repository.html_url}) to #{environment}
+    [#{creator.login}](#{creator.html_url}) started a deployment of [#{sha[0..8]}](#{repository.html_url}/commit/#{sha}) in [#{repository.name}](#{repository.html_url}) to #{environment}
     """

   formatDeploymentStatus = (event) ->


Add user name and user link to paymant notify

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -697,9 +697,9 @@
         await userPrivate.upsert()
         try
             receipt = await addIncome("Оплата занятий на algoprog.ru", +userPrivate.price)
-            notify "Добавлен чек (#{req.body.OrderId}, #{userPrivate.price}р.): " + makeReceiptLink(receipt)
+            notify "Добавлен чек (#{req.body.OrderId}, #{userPrivate.price}р.):\n#{user.name}: http://algoprog.ru/user/#{userId}\n" + makeReceiptLink(receipt)
         catch e
-            notify "Ошибка добавления чека (#{req.body.OrderId}, #{userPrivate.price}р.): " + e
+            notify "Ошибка добавления чека (#{req.body.OrderId}, #{userPrivate.price}р.):\n#{user.name}: http://algoprog.ru/user/#{userId}\n" + e
             receipt = "---"
         logger.info("paymentNotify #{req.body.OrderId}: ok, new paidTill: #{newPaidTill}, receipt: #{receipt}")
         payment.processed = true


Log failed downloads

Log failed downloads

--- server/lib/download.coffee
+++ server/lib/download.coffee
@@ -26,4 +26,4 @@
             logger.info e.message
             await sleep(delay)
             delay *= 2
-    throw "Can't download"
+    throw "Can't download " + href


Fixed: typo

Fixed: typo

--- server/lib/importer.coffee
+++ server/lib/importer.coffee
@@ -26,7 +26,7 @@

             msg = localization.t localizationKey, name: model.name
             notification.createOrUpdatePersistent notificationSlug,
-                app: 'konnector'
+                app: 'konnectors'
                 text: msg
                 resource:
                     app: 'konnectors'


improving player bidding

improving player bidding

--- server/publications.coffee
+++ server/publications.coffee
@@ -1 +1,2 @@
 Meteor.publish 'footballer', -> Current.find()
+Meteor.publish 'teams', -> Teams.find()


[master] debug

[master] debug

--- server/wechat/index.coffee
+++ server/wechat/index.coffee
@@ -43,11 +43,11 @@
     next()
   .done()

-rpcCall = (tag, payload, sendReponse) ->
+rpcCall = (tag, payload, sendResponse) ->
   if tag == 'GET'
-    sendReponse "echo #{payload.request}"
+    sendResponse "echo #{payload.request}"
   else
-    sendReponse "invalid tag #{tag}"
+    sendResponse "invalid tag #{tag}"

 rpcHandler = (req, res, next) ->
   console.dir req.body
@@ -67,19 +67,21 @@
         </xml>"
       res.send 200, ret
       next()
-    content parsed.xml['Content'][0]
+    content = parsed.xml['Content'][0]
     separatorIndex = content.indexOf ' '
     if separatorIndex < 1
+      logger.debug 'hh'
       sendResponse 'invalid request'
     else
+      logger.debug 'hc'
       tag = content[..separatorIndex - 1]
       request = content[separatorIndex + 1..]
       payload =
-        toUser: toUser
-        fromUser: fromUser
+        toUser: fromUser
+        fromUser: toUser
         createTime: createTime
         request: request
-      rpcCall tag, payload, sendReponse
+      rpcCall tag, payload, sendResponse
   .done()

 exports.authenticationHandler = authenticationHandler


next quick

next quick

--- controllers/api.coffee
+++ controllers/api.coffee
@@ -1,4 +1,3 @@
-
 pos = require('pos');
 tagger = new pos.Tagger();
 _ = require('lodash')


fix: The login button is missing or invisible or obstructed at tigard.chlk.it

Former-commit-id: 6c35ded25f02162efb0a929d52b89c11bdfbed9c [formerly 7528b08935e73c946b0bc87759f3b9d08266f7ee] [formerly aab2421bf5270e8d16315d6aa0c2d1db0fd713cb [formerly 70185e2691ed6484c943a35faf6cb17c0729d862]] [formerly 0b1a86466b19d1226b693feb5688f4df643c1e02 [formerly 4c965b79155a4f5e4e29c2ac377be3eb2be29a51] [formerly 8b6f90f41a4291a3eff23f4ea4f876ed39143ba6 [formerly 8b6f90f41a4291a3eff23f4ea4f876ed39143ba6 [formerly 819379cf3684cbc131d9acba74af7f3b67b6d642]]]]
Former-commit-id: e33f7618b470d3eb6dd3a48db60e4a47d5bb89ac [formerly 10bbe2f25bc774d18c46da506b34f00d7f42296f] [formerly 8efaae155bc1d1a88d494b7d4bfc444fca70cf90 [formerly 7568a583f8292a9582bf29fc77b3f8ac60018b81]]
Former-commit-id: de97e7e09baca6fc7834d00c1f0f2fc98525e814 [formerly 43bb24af9a906f8b0662446c8f023d36a47cd150]
Former-commit-id: 3e96966060d4a0ac21ba413724258888f00f1823
Former-commit-id: 798313380bfe71209b1f3410e96a450d87af7358

--- @client/swapables.coffee
+++ @client/swapables.coffee
@@ -1001,6 +1001,7 @@
       style:
         _.extend(
           position: 'absolute'
+          zIndex: 1
           right: 50
           top: 17,
           _.clone(@props.style))


only append @draft if version isn't present

--- src/api.coffee
+++ src/api.coffee
@@ -69,7 +69,8 @@

 start = ->
   apiHelper ExerciseActions, ExerciseActions.load, ExerciseActions.loaded, 'GET', (id) ->
-    url: "/api/exercises/#{id}@draft"
+    id = if id.indexOf("@") is -1 then "#{id}@draft" else id
+    url: "/api/exercises/#{id}"

   apiHelper ExerciseActions, ExerciseActions.save, ExerciseActions.saved, 'PUT', (id) ->

@@ -106,7 +107,10 @@
     httpMethod: 'DELETE'

   apiHelper VocabularyActions, VocabularyActions.load, VocabularyActions.loaded, 'GET', (id) ->
-    url: "/api/vocab_terms/#{id}@draft"
+
+    id = if id.indexOf("@") is -1 then "#{id}@draft" else id
+
+    url: "/api/vocab_terms/#{id}"

   apiHelper VocabularyActions, VocabularyActions.create, VocabularyActions.created, 'POST', (id, obj) ->
     obj = VocabularyStore.get(id)
@@ -120,13 +124,13 @@

     vocabId = if id.indexOf("@") is -1 then id else id.split("@")[0]

-    url:"/api/exercises/#{vocabId}@draft"
+    url:"/api/vocab_terms/#{vocabId}@draft"
     httpMethod: 'PUT'
     payload: obj

   apiHelper VocabularyActions, VocabularyActions.publish, VocabularyActions.published, 'PUT', (id) ->
     obj = VocabularyStore.get(id)
-    debugger
+
     url: "/api/vocab_terms/#{obj.uid}/publish"
     httpMethod: 'PUT'
     payload: obj


call the callback on other operating systems with null input

--- src/apm.coffee
+++ src/apm.coffee
@@ -57,6 +57,8 @@
           asarPaths = glob.sync(pattern, null) # [] | a sorted array of locations with the newest version being last
           asarPath = asarPaths.pop()
         return process.nextTick -> callback(asarPath)
+      else
+        return process.nextTick -> callback('')

   getReposDirectory: ->
     process.env.ATOM_REPOS_HOME ? path.join(@getHomeDirectory(), 'github')


Add correct propType to relay onLoad event from image closes #2

--- src/index.cjsx
+++ src/index.cjsx
@@ -15,7 +15,8 @@
     ]).isRequired
     checkIfRetinaImgExists: React.PropTypes.bool
     retinaImageSuffix: React.PropTypes.string
-    handleOnLoad: React.PropTypes.func
+    handleOnLoad: React.PropTypes.func # Deprecated.
+    onLoad: React.PropTypes.func

   getDefaultProps: ->
     checkIfRetinaImgExists: true
@@ -90,6 +91,10 @@

   handleOnLoad: (e) ->
     # Customers of component might care when the image loads.
+    if @props.onLoad?
+      @props.onLoad(e)
+    # handleOnLoad was in an earlier version (wrong name) and will be removed
+    # at the next major release.
     if @props.handleOnLoad?
       @props.handleOnLoad(e)


groups: show tabs when super admin

groups: show tabs when super admin

--- client/landing/app/Applications/Groups.kdapplication/Views/groupview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupview.coffee
@@ -142,7 +142,7 @@

   decorateUponRoles:(roles)->

-    if "admin" in roles
+    if "admin" in roles or KD.checkFlag 'super-admin'
       @tabView.showHandleContainer()

   privateGroupOpenHandler: GroupsAppController.privateGroupOpenHandler


Map#removeEntity accepts multiple entities

--- src/map.coffee
+++ src/map.coffee
@@ -24,10 +24,10 @@
 		@timeManager.add entities...
 		@

-	removeEntity: (e) ->
-		e.map = null
-		_.pull @entities, e
-		@timeManager.remove e
+	removeEntity: (entities...) ->
+		e.map = null for e in entities
+		_.pull @entities, entities...
+		@timeManager.remove entities...
 		@

 	entitiesAt: (x, y, f) ->


Components: DeleteWorkspaceWidget: Add guide link

--- client/app/lib/components/sidebarmachineslistitem/deleteworkspacewidget.coffee
+++ client/app/lib/components/sidebarmachineslistitem/deleteworkspacewidget.coffee
@@ -56,9 +56,9 @@

         Related Guides

+          className='DeleteWorkspace-Understading'
+          href='http://learn.koding.com/guides/getting-started/workspaces/'>
           Understanding Workspaces


Fix announcements for some guilds

Fix announcements for some guilds

--- modules/announcements.coffee
+++ modules/announcements.coffee
@@ -22,7 +22,8 @@
         return if await Core.modules.isDisabledForGuild guild, @
         s = await Core.settings.getForGuild(guild)
         l = Core.locales.getLocale(s.locale)
-        c = s.commandChannel or guild.defaultChannel
+        c = guild.defaultChannel
+        try c = guild.channels.get(s.commandChannel) if s.commandChannel
         c.send embed: {
           title
           description: message


Use more efficient imports with latest es-is

--- src/ref.coffee
+++ src/ref.coffee
@@ -1,6 +1,7 @@
-import objectAssign         from 'es-object-assign'
-import {isNumber, isObject} from 'es-is'
-import observable           from 'riot-observable'
+import isNumber     from 'es-is/number'
+import isObject     from 'es-is/object'
+import objectAssign from 'es-object-assign'
+import observable   from 'riot-observable'

 nextId = do ->
   ids = 0


NumberAnimation bug fixed

NumberAnimation bug fixed

--- impl/base/level1/animation/number.coffee
+++ impl/base/level1/animation/number.coffee
@@ -54,17 +54,17 @@
 		# if data.isIntegerProperty is true and progress isnt 1
 		# 	val = round val

-		if val is val and target # isNaN hack
+		if val is val and target and (property = anim._property) # isNaN hack
 			if not running or anim._updateProperty or not data.propertySetter
 				anim._updatePending = true
 				# when animation has been started in a signal which stopped propagation
 				if not running and target[data.internalPropertyName] is val
 					target[data.internalPropertyName] = data.from
-				target[anim._property] = val
+				target[property] = val
 				anim._updatePending = false

 				# force impl update, because setter won't update if nothing change in data
-				if progress is 1 and data.propertySetter and target[anim._property] is val
+				if progress is 1 and data.propertySetter and target[property] is val
 					impl[data.propertySetter].call target, val
 			else
 				impl[data.propertySetter].call target, val


Fix simpleEditor HTML entities escaping

Fix simpleEditor HTML entities escaping

--- www/webapp/static/webapp/js/app/views/partials/controls/simpleEditor.coffee
+++ www/webapp/static/webapp/js/app/views/partials/controls/simpleEditor.coffee
@@ -26,6 +26,7 @@
       ]
       placeholder: @get('placeholder')
       allowedContent: 'b i u br'
+      entities: false
       forcePasteAsPlainText: true
       fillEmptyBlocks: false
       autoParagraph: false


Update settings.cson

Update settings.cson

Changed some english texts and spellings.

--- def/de/settings.cson
+++ def/de/settings.cson
@@ -44,9 +44,9 @@
     {_label: "Choose a Theme", value: "Ein Thema auswählen"}
     {_label: "Installed Themes", value: "Installierte Themen"}
     {_label: "Install Packages", value: "Paket installieren"}
-    {_label: "Featured Packages", value: "Präsentierte Pakete"}
+    {_label: "Featured Packages", value: "Vorgestellte Pakete"}
     {_label: "Install Themes", value: "Themen installieren"}
-    {_label: "Featured Themes", value: "Präsentierte Themen"}
+    {_label: "Featured Themes", value: "Vorgestellte Themen"}
     {_label: "Available Updates", value: "Verfügbare Updates"}
   ]
   subSectionHeadings: [
@@ -64,11 +64,11 @@
     notes: [
       {
         _id: 'core-settings-note'
-        html: 'Dies sind Atom\'s kern welche das Verhalten ohne Verbindung zum editieren von Texten beeinflussen. Individuelle Pakete könnten eigene Einstellungen besitzen, welche sich in der jeweiligen Paket-Karte in der Paket Liste befinden.'
+        html: 'Dies sind Atom\'s Kerneinstellungen welche aber nicht das Verhalten zum Editieren von Texten beeinflussen (zu finden unter Editor). Individuelle Pakete könnten eigene Einstellungen besitzen, welche sich in dem jeweiligen Paket-Einstellungsdialog in der Liste aller installierten Pakete befinden.'
       }
       {
         _id: 'editor-settings-note'
-        html: 'Diese Einstellungen hängen mit dem editieren von Text zusammen. Einige davon, können auf Basis der jeweiligen Sprache übergangen werden. Prüfe die jeweiligen Spracheinstellungen in der jeweiligen Paket-Karte in der Paket Liste.'
+        html: 'Diese Einstellungen hängen mit dem Editieren von Text zusammen. Einige davon können auf Basis der jeweiligen Sprache übergangen werden. Prüfe die jeweiligen Spracheinstellungen in dem jeweiligen Paket-Einstellungsdialog in der Liste aller installierten Pakete.'
       }
     ]
     controls: [
@@ -79,8 +79,8 @@
       }
       {
         _id: 'core.audioBeep'
-        title: "Audio Beep"
-        desc: "Löse den System-Beep aus, wenn eine spezielle Aktion nicht ausgeführt werden kann oder keine Ergebnisse existieren."
+        title: "Systemton auslösen"
+        desc: "Löse den Systemton (System-Beep) aus, wenn eine spezielle Aktion nicht ausgeführt werden kann oder keine Ergebnisse existieren."
       }
       {
         _id: 'core.automaticallyUpdate'
@@ -90,12 +90,12 @@
       {
         _id: 'core.autoHideMenuBar'
         title: "Menü automatisch verbergen"
-        desc: "Menü automatisch verbergen und zum einblenden Alt drücken. Dies wird nur unter unterstütz unter Windows & Linux."
+        desc: "Menü automatisch verbergen und zum einblenden Alt drücken. Dies wird nur unter Windows & Linux unterstützt."
       }
       {
         _id: 'core.closeDeletedFileTabs'
-        title: "Close Deleted File Tabs"
-        desc: "Close corresponding editors when a file is deleted outside Atom."
+        title: "Schließe Reiter, wenn Datei gelöscht wurde"
+        desc: "Schließe den zugehörigen Reiter, sobald die entsprechende Datei im System gelöscht wurde."
       }
       {
         _id: 'core.closeEmptyWindows'
@@ -120,7 +120,7 @@
       {
         _id: 'core.followSymlinks'
         title: "Folge Symlinks"
-        desc: "Folge beim suchen von Dateien und beim öffnen von Ordnern mit dem fuzzy Finder symbolischen Verknüpfungen."
+        desc: "Folge beim Suchen von Dateien und beim Öffnen von Ordnern mit dem fuzzy Finder symbolischen Verknüpfungen."
       }
       {
         _id: 'core.ignoredNames'
@@ -134,12 +134,12 @@
       }
       {
         _id: 'core.packagesWithKeymapsDisabled'
-        title: "Pakete mit deaktivierten Keymaps"
+        title: "Pakete mit deaktivierten Tastaturkürzeln"
       }
       {
         _id: 'core.projectHome'
-        title: "Projekt Home"
-        desc: "Verzeichnis wo Projekte gespeichert werden. Pakete welche mittels dem Paket-Generator erstellt werden werden dort standardmäßig abgelegt."
+        title: "Projekt Heimverzeichnis"
+        desc: "Verzeichnis in dem Projekte gespeichert werden. Pakete welche mittels dem Paket-Generator erstellt werden, werden dort standardmäßig abgelegt."
       }
       {
         _id: 'core.reopenProjectMenuCount'
@@ -148,31 +148,31 @@
       }
       {
         _id: 'core.restorePreviousWindowsOnStart'
-        title: "Letzte Fenster beim Start wieder herstellen"
-        desc: "When checked restores the last state of all Atom windows when started from the icon or atom by itself from the command line; otherwise a blank environment is loaded."
+        title: "Letzte Fenster beim Start wiederherstellen"
+        desc: "Wenn aktiviert, wird der letzte Stand aller Atom-Fenster bei Start über das Icon wiederhergestellt. Gilt auch für den Start von atom über die Kommandozeile. Andernfalls wird Atom mit einer neuen Umgebung gestartet."
         select:
-          no: "no"
-          yes: "yes"
-          always: "always"
+          no: "Nein"
+          yes: "Ja"
+          always: "Immer"
       }
       {
         _id: 'core.telemetryConsent'
         title: "Sende Telemetrie-Daten an das Atom Team"
-        desc: "Dem Senden und der Verwendung von statistischen Daten sowie Ausnahmemeldungen an das Atom Team zustimmen um mitzuhelfen das Produkt zu verbessern."
+        desc: "Dem Senden und der Verwendung von statistischen Daten sowie Ausnahmemeldungen an das Atom Team zustimmen. Dient der Produkt um mitzuhelfen das Produkt zu verbessern."
         select:
           limited: "Limitierte anonyme Nutzer-Statistiken, Ausnahmen und Fehlerberichte"
           no: "Keine Telemetrie-Daten senden"
-          undecided: "Unentschlossen (Atom will ask again next time it is launched)"
+          undecided: "Unentschlossen (Atom fragt beim nächsten Start erneut nach)"
       }
       {
         _id: 'core.useProxySettingsWhenCallingApm'
-        title: "Use Proxy Settings When Calling APM"
-        desc: "Use detected proxy settings when calling the apm command-line tool."
+        title: "Nutze die Proxy-Einstellungen, wenn APM aufgerufen wird"
+        desc: "Nutze die erkannten Proxy-Einstellungen, wenn das apm Kommandozeilenwerkzeug aufgerufen wird."
       }
       {
         _id: 'core.useCustomTitleBar'
-        title: "Benutze Benutzerspezifische Titel-Leiste"
-        desc: "Use custom, theme-aware title bar.
Note: This currently does not include a proxy icon.
This setting will require a relaunch of Atom to take effect."
+        title: "Benutze benutzerspezifische Titel-Leiste"
+        desc: "Nutze eigene, Theme-gebundene Titel-Leiste.
Hinweis: Das Proxy-Icon ist davon ausgenommen.
Damit diese Einstellung Wirkung hat, muss Atom neu gestartet werden."
       }
       {
         _id: 'core.warnOnLargeFileLimit'
@@ -191,12 +191,12 @@
       }
       {
         _id: 'editor.autoIndentOnPaste'
-        title: "Automatisches einrücken beim einfügen"
+        title: "Automatisches einrücken beim Einfügen"
         desc: "Automatisch eingefügten Text einrücken, basierend auf der Einrückung der vorausgehenden Zeile."
       }
       {
         _id: 'editor.backUpBeforeSaving'
-        title: "Back Up vor dem Speichern"
+        title: "Sicherung vor dem Speichern"
         desc: ""
       }
       {
@@ -206,12 +206,12 @@
       }
       {
         _id: 'editor.fontFamily'
-        title: "Font Familie"
-        desc: "Der Name der Font Familie welche als Editor-Text verwendet wird."
+        title: "Schrifttypfamilie"
+        desc: "Der Name der Schrifttypfamilie welche als Editor-Text verwendet wird."
       }
       {
         _id: 'editor.fontSize'
-        title: "Font Größe"
+        title: "Schrifttypgröße"
         desc: "Höhe des Textes im Editor in Pixel."
       }
       {
@@ -237,7 +237,7 @@
       {
         _id: 'editor.lineHeight'
         title: "Zeilen Höhe"
-        desc: "Höhe von Editor-Zeilen, als Multiplikator der Font-Größe."
+        desc: "Höhe von Editor-Zeilen, als Multiplikator der Schrifttypgröße."
       }
       {
         _id: 'editor.nonWordCharacters'
@@ -261,8 +261,8 @@
       }
       {
         _id: 'editor.showCursorOnSelection'
-        title: "Show Cursor On Selection"
-        desc: "Show cursor while there is a selection."
+        title: "Zeige Mauszeiger bei Textmarkierungen"
+        desc: "Zeige den Mauszeiger auch dann an, wenn Text markiert wird."
       }
       {
         _id: 'editor.showIndentGuide'
@@ -271,13 +271,13 @@
       }
       {
         _id: 'editor.showInvisibles'
-        title: "Zeige Unsichtbare"
-        desc: "Zeige Platzhalter für unsichtbbare Zeichen wie Tabs, Leerzeichen oder Zeilenumbrüche."
+        title: "Zeige Unsichtbare Zeichen"
+        desc: "Zeige Platzhalter für unsichtbare Zeichen wie Tabs, Leerzeichen oder Zeilenumbrüche."
       }
       {
         _id: 'editor.showLineNumbers'
-        title: "Zeige Zeilen Nummern"
-        desc: "Zeige Zeilen Nummern im Editor-Spaltenzwischenraum."
+        title: "Zeige Zeilennummern"
+        desc: "Zeige Zeilennummern im Editor-Spaltenzwischenraum."
       }
       {
         _id: 'editor.softTabs'
@@ -287,22 +287,22 @@
       {
         _id: 'editor.softWrap'
         title: "Soft Wrap"
-        desc: "Bricht Zeilen um, welche die Breite des Fensters überschreiten. Wenn Soft Wrap anhand bevorzugten Zeilenlänge aktiviert ist, wird die Zeile anhand der definierten maximalen Anzahl an Zeichen in der Bevorzugten Zeilenlänge Einstellung umgebrochen."
+        desc: "Bricht Zeilen um, welche die Breite des Fensters überschreiten wird. Wenn Soft Wrap anhand bevorzugten Zeilenlänge aktiviert ist, wird die Zeile anhand der definierten maximalen Anzahl an Zeichen in der Bevorzugten Zeilenlänge Einstellung umgebrochen."
       }
       {
         _id: 'editor.softWrapAtPreferredLineLength'
         title: "Soft Wrap anhand bevorzugter Zeilenlänge"
-        desc: "Anstelle eines Zeilenumbruches an der Fensterbreite, wird die Zeile bei der Anzahl der Zeichen umgebrochen welche in Bevorzugte Zeilenlänge Einstellung defininiert ist. Dies hat nur einen Effekt, wenn die Soft Wrap Einstellung global oder für die aktuelle Sprache aktiviert ist. Beachte: Wenn du die Wrap-Hilfe (vertikale Linie) verbergen willst kannst du das Paket wrap-guide de-aktivieren."
+        desc: "Anstelle eines Zeilenumbruches an der Fensterbreite, wird die Zeile bei der Anzahl der Zeichen umgebrochen, welche in Bevorzugte Zeilenlänge Einstellung defininiert ist. Dies hat nur einen Effekt, wenn die Soft Wrap Einstellung global oder für die aktuelle Sprache aktiviert ist. Beachte: Wenn du die Wrap-Hilfe (vertikale Linie) verbergen willst kannst du das Paket wrap-guide de-aktivieren."
       }
       {
         _id: 'editor.softWrapHangingIndent'
         title: "Soft Wrap fliegende Einrückung"
-        desc: "Wenn Soft Wrap aktiviert ist, definiert dies die Anzahl an Zeichen die als zusätzliche Einrückung verwendet wird."
+        desc: "Wenn Soft Wrap aktiviert ist, definiert dies die Anzahl an Zeichen, die als zusätzliche Einrückung verwendet wird."
       }
       {
         _id: 'editor.tabLength'
         title: "Tab Länge"
-        desc: "Anzahl von Leerzeichen welche verwendet werden um ein Tab darzustellen."
+        desc: "Anzahl von Leerzeichen, welche verwendet werden, um einen Tab darzustellen."
       }
       {
         _id: 'editor.tabType'
@@ -316,8 +316,8 @@
       }
       {
         _id: 'editor.zoomFontWhenCtrlScrolling'
-        title: "Zoom Font beim Strg scrollen"
-        desc: "Ändere die Font-Größe wenn bei gedrückter Strg-Taste mit der Maus nach oben oder unten gescrollt wird."
+        title: "Ändere die Schriftgröße, wenn mit Strg + Mausrad gescrollt wird"
+        desc: "Ändere die Schriftgröße wenn bei gedrückter Strg-Taste mit der Maus nach oben oder unten gescrollt wird."
       }
       {
         _id: 'system.windows.file-handler'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -34,7 +34,7 @@
       equals obj, obj2
     else if index is 2
       equals obj, obj3
-    else if
+    else if index is 3

   equals indexSum, 3


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -8,7 +8,7 @@
     x: 0
     y: 50

-  equals engine.find('.findable')leng,
+  equals engine.find('.findable').length,

 module()


Fix tests

Fix tests

Accidentally committed tests with `.only`

--- test/simulation-test.coffee
+++ test/simulation-test.coffee
@@ -223,7 +223,7 @@
           @simulation.run()
           @nodeB.currentValue.should.equal 20

-        it.only "should scale between output's min and max", ->
+        it "should scale between output's min and max", ->
           @nodeB.min = 50
           @nodeB.max = 100
           @simulation.run()
@@ -239,7 +239,7 @@
           @simulation.run()
           @nodeB.currentValue.should.equal 20

-        it.only "should scale between output's min and max", ->
+        it "should scale between output's min and max", ->
           @nodeA.min = 0
           @nodeA.max = 50
           @simulation.run()


Added some pending tests

Added some pending tests

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -747,6 +747,18 @@
 			# Only after we set a color a shadow should be drawn
 			layer.shadowColor = null
 			layer.style.boxShadow.should.equal "rgba(0, 0, 0, 0) 10px 10px 10px 10px"
+
+		it "should add multiple shadows by passing an array into the shadows property"
+		it "should be able to access shadow properties through properties"
+		it "should change the shadow when a shadow property is changed"
+		it "should remove a shadow when a shadow property is set to null"
+		it "should keep the rest of the shadows when a shadow property is removed"
+		it "should animate shadows through a shadow property"
+		it "should should change all shadows when shadowColor, shadowX, shadowY are changed"
+		it "should create a shadow1 if a shadowColor when there aren't any shadows"
+		it "should copy shadows if you copy a layer"
+
+	describe "Events", ->

 		it "should remove all events", ->
 			layerA = new Layer


refs #23 CompetitiveList: store boolean if match has been automatically rated through and more improvements for @letWinnerWinMatchesAgainstCompetitorsWhichLoseAgainstLoser.

--- app/assets/javascripts/voluntary/dom_manipulation/competitive_list.js.coffee
+++ app/assets/javascripts/voluntary/dom_manipulation/competitive_list.js.coffee
@@ -19,9 +19,7 @@

   @start: () ->
     matchesAlreadyExist = false
-
-    if window.matches == undefined
-      window.matches = []
+    window.matches ||= []

     if window.matches.length > 0
       matchesAlreadyExist = true
@@ -60,40 +58,32 @@
       )

       if notExistingCompetitors.length > 0
-        window.Voluntary.DomManipulation.CompetitiveList.removeMatch(match)
+        window.Voluntary.DomManipulation.CompetitiveList.removeItemFromArray(window.matches, match)
       else
-        if window.competitorsOfCompetitor[match['competitors'][0]] == undefined
-          window.competitorsOfCompetitor[match['competitors'][0]] = []
-
+        window.competitorsOfCompetitor[match['competitors'][0]] ||= []
         window.competitorsOfCompetitor[match['competitors'][0]].push match['competitors'][1]
-
-        if window.competitorsOfCompetitor[match['competitors'][1]] == undefined
-          window.competitorsOfCompetitor[match['competitors'][1]] = []
-
+        window.competitorsOfCompetitor[match['competitors'][1]] ||= []
         window.competitorsOfCompetitor[match['competitors'][1]].push match['competitors'][0]

         unless match['winner_competitor'] == undefined
           loserId = window.Voluntary.DomManipulation.CompetitiveList.otherCompetitorOfMatch(match, match['winner_competitor'])
           window.Voluntary.DomManipulation.CompetitiveList.incrementMatchesWon(match['winner_competitor'], loserId)

-  @removeMatch: (match) ->
+  @removeItemFromArray: (array, item) ->
     list = []

-    $.each window.matches, (index, workingMatch) ->
-
-      unless JSON.stringify(workingMatch) == JSON.stringify(match)
-        list.push workingMatch
-
-    window.matches = list
+    $.each array, (index, workingItem) ->
+      unless JSON.stringify(workingItem) == JSON.stringify(item)
+        list.push newItem
+
+    return list

   @generateMatches: () ->
     $.each window.competitors, (index, competitor_id) ->
-      if window.competitorsOfCompetitor[competitor_id] == undefined
-        window.competitorsOfCompetitor[competitor_id] = []
+      window.competitorsOfCompetitor[competitor_id] ||= []

       $.each window.competitors, (index, otherCompetitorId) ->
-        if window.competitorsOfCompetitor[otherCompetitorId] == undefined
-          window.competitorsOfCompetitor[otherCompetitorId] = []
+        window.competitorsOfCompetitor[otherCompetitorId] ||= []

         if competitor_id != otherCompetitorId && $.inArray(otherCompetitorId, window.competitorsOfCompetitor[competitor_id]) == -1
           window.matches.push { competitors: [competitor_id, otherCompetitorId] }
@@ -154,20 +144,36 @@
   @letWinnerWinMatchesAgainstCompetitorsWhichLoseAgainstLoser: (winnerId, loserId) ->
     $.each window.matches, (index, match) ->
       matchOfWinner = $.inArray(winnerId, match['competitors'])
-      matchCompetitorsWhichIncludeLoser = null
-
-      unless matchOfWinner == -1
-        matchCompetitorsWhichIncludeLoser = jQuery.map(match['competitors'], (c) ->
-          if $.inArray(c, window.Voluntary.DomManipulation.CompetitiveList.defeatedCompetitorsByCompetitor(loserId)) > -1
-            return c
-        )
-
-      if matchOfWinner > -1 && matchCompetitorsWhichIncludeLoser.length > 0
-        window.matches[index]['winner_competitor'] = winnerId
-
+      return true if matchOfWinner == -1
+
+      matchCompetitorsWhichIncludeLoser = jQuery.map(match['competitors'], (c) ->
+        if $.inArray(c, window.Voluntary.DomManipulation.CompetitiveList.defeatedCompetitorsByCompetitor(loserId)) > -1
+          return c
+      )
+
+      return true if match['winner_competitor'] == winnerId || matchCompetitorsWhichIncludeLoser.length == 0
+
+      otherLoserId = window.Voluntary.DomManipulation.CompetitiveList.otherCompetitorOfMatch(match, winnerId)
+
+      window.matchesWon[winnerId] ||= 0
+      window.matchesWon[winnerId] = window.matchesWon[winnerId] + 1
+
+      unless match['winner_competitor'] == undefined
+        window.Voluntary.DomManipulation.CompetitiveList.changeCompetitorsComparisonResult(winnerId, otherLoserId)
+
+      window.Voluntary.DomManipulation.CompetitiveList.appointWinnerOfMatch(index, winnerId, loserId, match['winner_competitor'] == undefined)
+      window.matches[index]['winner_competitor'] = winnerId
+      console.log 'auto_winner through @letWinnerWinMatchesAgainstCompetitorsWhichLoseAgainstLoser: ' + JSON.stringify([winnerId, loserId])
+      window.matches[index]['auto_winner'] = true
+
+  @changeCompetitorsComparisonResult: (winnerId, loserId) ->
+    window.matchesWon[loserId] = window.matchesWon[loserId] - 1
+    window.outmatchedCompetitorsByCompetitor[winnerId] ||= []
+    window.outmatchedCompetitorsByCompetitor[winnerId] = window.Voluntary.DomManipulation.CompetitiveList.removeItemFromArray(window.outmatchedCompetitorsByCompetitor[winnerId], loserId)
+    window.defeatedCompetitorsByCompetitor[otherLoserId] = window.Voluntary.DomManipulation.CompetitiveList.removeItemFromArray(window.outmatchedCompetitorsByCompetitor[loserId], winnerId)
+
   @defeatedCompetitorsByCompetitor: (competitorId) ->
-    if window.defeatedCompetitorsByCompetitor[competitorId] == undefined
-      window.defeatedCompetitorsByCompetitor[competitorId] = []
+    window.defeatedCompetitorsByCompetitor[competitorId] ||= []

     return window.defeatedCompetitorsByCompetitor[competitorId]

@@ -182,15 +188,15 @@
     winnerId = parseInt($("input[name='winner']:checked").val())
     loserId = window.Voluntary.DomManipulation.CompetitiveList.otherCompetitorOfMatch(window.currentMatch, winnerId)

-    window.Voluntary.DomManipulation.CompetitiveList.appointWinnerOfMatch(window.currentMatchIndex, winnerId, loserId)
+    window.Voluntary.DomManipulation.CompetitiveList.appointWinnerOfMatch(window.currentMatchIndex, winnerId, loserId, true)

     unless window.Voluntary.DomManipulation.CompetitiveList.nextMatch()
       window.Voluntary.DomManipulation.CompetitiveList.sortByMostMatchesWon()
       $('#bootstrap_modal').modal('hide')

-  @appointWinnerOfMatch: (matchIndex, winnerId, loserId) ->
+  @appointWinnerOfMatch: (matchIndex, winnerId, loserId, decrementMatchesLeft) ->
     window.matches[matchIndex]['winner_competitor'] = winnerId
-    window.matchesLeft = window.matchesLeft - 1
+    window.matchesLeft = window.matchesLeft - 1 if decrementMatchesLeft

     window.Voluntary.DomManipulation.CompetitiveList.incrementMatchesWon(winnerId, loserId)
     window.Voluntary.DomManipulation.CompetitiveList.letWinnerWinMatchesAgainstCompetitorsWhichLoseAgainstLoser(winnerId, loserId)
@@ -201,28 +207,30 @@
       window.Voluntary.DomManipulation.CompetitiveList.letCompetitorWinMatchAgainstOtherCompetitor(competitorId, loserId)

   @outmatchedCompetitorsByCompetitor: (competitorId) ->
-    if window.outmatchedCompetitorsByCompetitor[competitorId] == undefined
-      window.outmatchedCompetitorsByCompetitor[competitorId] = []
+    window.outmatchedCompetitorsByCompetitor[competitorId] ||= []

     return window.outmatchedCompetitorsByCompetitor[competitorId]

   @letCompetitorWinMatchAgainstOtherCompetitor: (winnerId, loserId) ->
     $.each window.matches, (index, match) ->
-      if $.inArray(winnerId, match['competitors']) > -1 && $.inArray(loserId, match['competitors']) > -1 && window.matches[index]['winner_competitor'] != winnerId
-        window.Voluntary.DomManipulation.CompetitiveList.appointWinnerOfMatch(index, winnerId, loserId)
-
-        return
+      if $.inArray(winnerId, match['competitors']) > -1 && $.inArray(loserId, match['competitors']) > -1 && match['winner_competitor'] != winnerId
+        console.log 'auto_winner through @letCompetitorWinMatchAgainstOtherCompetitor: ' + JSON.stringify([winnerId, loserId])
+        window.matches[index]['auto_winner'] = true
+        window.Voluntary.DomManipulation.CompetitiveList.changeCompetitorsComparisonResult(winnerId, loserId) unless match['winner_competitor']  == undefined
+        window.Voluntary.DomManipulation.CompetitiveList.appointWinnerOfMatch(index, winnerId, loserId, match['winner_competitor']  == undefined)
+
+        return false

   @incrementMatchesWon: (winnerId, loserId) ->
-    if window.matchesWon[winnerId] == undefined
-      window.matchesWon[winnerId] = 0
-
+    window.matchesWon[winnerId] ||= 0
     window.matchesWon[winnerId] = window.matchesWon[winnerId] + 1

     window.Voluntary.DomManipulation.CompetitiveList.defeatedCompetitorsByCompetitor(winnerId)
+    window.defeatedCompetitorsByCompetitor[winnerId] ||= []
     window.defeatedCompetitorsByCompetitor[winnerId].push loserId

     window.Voluntary.DomManipulation.CompetitiveList.outmatchedCompetitorsByCompetitor(loserId)
+    window.outmatchedCompetitorsByCompetitor[loserId] ||= []
     window.outmatchedCompetitorsByCompetitor[loserId].push winnerId

   @cancelTournament: ->
@@ -231,12 +239,8 @@

   @sortByMostMatchesWon: ->
     $.each window.competitors, (index, competitorId) ->
-      count = 0
-
-      unless window.matchesWon[competitorId] == undefined
-        count = window.matchesWon[competitorId]
-
-      $('#competitor_' + competitorId).data('wins', count)
+      window.matchesWon[competitorId] ||= 0
+      $('#competitor_' + competitorId).data('wins', window.matchesWon[competitorId])

     $wrapper = $('.competitive_list')


reset widget

reset widget

--- client/app/Applications/Activity.kdapplication/widgets/tutorialwidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/tutorialwidget.coffee
@@ -51,7 +51,7 @@

             @inputTutorialEmbedLink.setValue @sanitizeUrls @inputTutorialEmbedLink.getValue()

-            url = @inputTutorialEmbedLink.getValue()
+            url = @inputTutorialEmbedLink.getValue().trim()

             if /^((http(s)?\:)?\/\/)/.test url
               # parse this for URL
@@ -170,6 +170,7 @@
     @removeCustomData "activity"
     @inputDiscussionTitle.setValue ''
     @inputContent.setValue ''
+    @inputTutorialEmbedShowLink.setValue off
     @embedBox.resetEmbedAndHide()
     super