Throw exception when get()/set() undeclared property

--- bokehjs/src/coffee/core/has_props.coffee
+++ bokehjs/src/coffee/core/has_props.coffee
@@ -145,6 +145,10 @@
       attrs = {}
       attrs[key] = value
     for own key, val of attrs
+      prop_name = key
+      if not (prop_name == "id" or @props[prop_name])
+        throw new Error("#{@type}.set('#{prop_name}'): #{prop_name} wasn't declared")
+
       if not (options? and options.defaults)
         @_set_after_defaults[key] = true
     if not _.isEmpty(attrs)
@@ -228,6 +232,9 @@
     if _.has(@_computed, prop_name)
       return @_get_prop(prop_name)
     else
+      if not (prop_name == "id" or @props[prop_name])
+        throw new Error("#{@type}.get('#{prop_name}'): #{prop_name} wasn't declared")
+
       ref_or_val = super(prop_name)
       if not resolve_refs
         return ref_or_val


Cleaning step for grunt

Cleaning step for grunt

--- client/Gruntfile.coffee
+++ client/Gruntfile.coffee
@@ -1,6 +1,11 @@
 module.exports = (grunt) ->
   grunt.initConfig(
     pkg: grunt.file.readJSON('package.json')
+
+    clean:
+      options:
+        force: true
+      dist: ['dist/', '../server/public']

     coffee:
       source:
@@ -74,5 +79,5 @@
   grunt.loadNpmTasks('grunt-contrib-copy')
   grunt.loadNpmTasks('grunt-sass')

-  grunt.registerTask('build', ['concat:source', 'coffee:source', 'handlebars', 'concat:dist', 'sass', 'concat:test', 'coffee:test', 'copy'])
+  grunt.registerTask('build', ['clean', 'concat:source', 'coffee:source', 'handlebars', 'concat:dist', 'sass', 'concat:test', 'coffee:test', 'copy'])
   grunt.registerTask('default', ['build', 'watch'])


self-compiling closures as expressions

self-compiling closures as expressions

--- nodes.coffee
+++ nodes.coffee
@@ -563,7 +563,7 @@

   wrap: (expressions, statement) ->
     func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
-    call: new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')])
+    call: new CallNode(new ValueNode(func, [new AccessorNode(new LiteralNode('call'))]), [new LiteralNode('this')])
     if statement then Expressions.wrap([call]) else call

 }
@@ -953,7 +953,7 @@
     @condition: condition
     @body:      body and body.unwrap()
     @else_body: else_body and else_body.unwrap()
-    @children:  [@condition, @body, @else_body]
+    @children:  compact [@condition, @body, @else_body]
     @tags:      tags or {}
     @multiple:  true if @condition instanceof Array
     @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert


rebuilt parser and rewrote the asserts wrapping loop in Cakefile using `do =>`

--- nodes.coffee
+++ nodes.coffee
@@ -889,14 +889,15 @@
   # Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries
   # unless `crossScope` is `true`.
   traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope
-
+
   # Automatically calls the defined function.
   do: ->
     if @bound
       @bound = no
-      new Call(new Value this, [new Accessor new Literal 'call']
-               [new Literal 'this'].concat this.params)
-    else new Call this, this.params
+      new Call new Value(this, [new Accessor new Literal 'call']),
+               [new Literal 'this'].concat this.params
+    else
+      new Call this

 #### Param


Wrapping all soaked chains that are involved in operations.

--- nodes.coffee
+++ nodes.coffee
@@ -50,7 +50,9 @@
   compile: (o) ->
     @options: merge o or {}
     @tab:     o.indent
-    del @options, 'operation' unless this instanceof ValueNode
+    unless this instanceof ValueNode or this instanceof CallNode
+      del @options, 'operation'
+      del @options, 'chain_root' unless this instanceof AccessorNode or this instanceof IndexNode
     top:      if @top_sensitive() then @options.top else del @options, 'top'
     closure:  @is_statement() and not @is_pure_statement() and not top and
               not @options.as_statement and not (this instanceof CommentNode) and
@@ -302,18 +304,17 @@
   # operators `?.` interspersed. Then we have to take care not to accidentally
   # evaluate a anything twice when building the soak chain.
   compile_node: (o) ->
-    soaked:   false
-    only:     del(o, 'only_first')
-    op:       del(o, 'operation')
-    props:    if only then @properties[0...@properties.length - 1] else @properties
-    baseline: @base.compile o
-    baseline: "($baseline)" if @base instanceof ObjectNode and @has_properties()
-    complete: @last: baseline
+    only:         del(o, 'only_first')
+    op:           del(o, 'operation')
+    props:        if only then @properties[0...@properties.length - 1] else @properties
+    o.chain_root: this unless o.chain_root
+    baseline:     @base.compile o
+    baseline:     "($baseline)" if @base instanceof ObjectNode and @has_properties()
+    complete:     @last: baseline

     for prop in props
       @source: baseline
       if prop.soak_node
-        soaked: true
         if @base instanceof CallNode and prop is props[0]
           temp: o.scope.free_variable()
           complete: "($temp = $complete)$@SOAK" + (baseline: temp + prop.compile(o))
@@ -324,8 +325,10 @@
         baseline: + part
         complete: + part
         @last: part
-
-    if op and soaked then "($complete)" else complete
+
+    del o, 'chain_root'
+
+    if op and @wrapped then "($complete)" else complete

 #### CommentNode

@@ -377,11 +380,15 @@

   # Compile a vanilla function call.
   compile_node: (o) ->
-    for arg in @args
-      return @compile_splat(o) if arg instanceof SplatNode
-    args: (arg.compile(o) for arg in @args).join(', ')
-    return @compile_super(args, o) if @is_super
-    "${@prefix()}${@variable.compile(o)}($args)"
+    o.chain_root: this unless o.chain_root
+    for arg in @args when arg instanceof SplatNode
+      compilation: @compile_splat(o)
+    unless compilation
+      args: (arg.compile(o) for arg in @args).join(', ')
+      compilation: if @is_super then @compile_super(args, o)
+      else "${@prefix()}${@variable.compile(o)}($args)"
+    del o, 'chain_root'
+    if o.operation and @wrapped then "($compilation)" else compilation

   # `super()` is converted into a call against the superclass's implementation
   # of the current function.
@@ -449,6 +456,7 @@
     this

   compile_node: (o) ->
+    o.chain_root.wrapped: or @soak_node
     proto_part: if @prototype then 'prototype.' else ''
     ".$proto_part${@name.compile(o)}"

@@ -462,6 +470,7 @@
     @soak_node: tag is 'soak'

   compile_node: (o) ->
+    o.chain_root.wrapped: @soak_node
     idx: @index.compile o
     "[$idx]"


Adopting coco-style efficient bound functions for the common case ... but not for class/prototypes.

--- nodes.coffee
+++ nodes.coffee
@@ -310,6 +310,8 @@
   compileNode: (o) ->
     code = if @isUndefined
       if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0'
+    else if @value is 'this'
+      if o.scope.method?.bound then o.scope.method.context else @value
     else if @value.reserved and "#{@value}" not in ['eval', 'arguments']
       "\"#{@value}\""
     else
@@ -1080,7 +1082,7 @@
     @params  = params or []
     @body    = body or new Block
     @bound   = tag is 'boundfunc'
-    @context = 'this' if @bound
+    @context = '_this' if @bound

   children: ['params', 'body']

@@ -1095,7 +1097,7 @@
   # a closure.
   compileNode: (o) ->
     o.scope         = new Scope o.scope, @body, this
-    o.scope.shared  = del o, 'sharedScope'
+    o.scope.shared  = del(o, 'sharedScope')
     o.indent        += TAB
     delete o.bare
     vars   = []
@@ -1122,6 +1124,11 @@
     @body.expressions.unshift exprs... if exprs.length
     o.scope.parameter vars[i] = v.compile o for v, i in vars unless splats
     @body.makeReturn() unless wasEmpty or @noReturn
+    if @bound
+      if o.scope.parent.method?.bound
+        @bound = o.scope.parent.method.context
+      else
+        o.scope.parent.assign '_this', 'this'
     idt   = o.indent
     code  = 'function'
     code  += ' ' + @name if @ctor
@@ -1129,7 +1136,6 @@
     code  += "\n#{ @body.compileWithDeclarations o }\n#{@tab}" unless @body.isEmpty()
     code  += '}'
     return @tab + code if @ctor
-    return utility('bind') + "(#{code}, #{@context})" if @bound
     if @front or (o.level >= LEVEL_ACCESS) then "(#{code})" else code

   # Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries


fixing range literals (which had gone untested) oops.

--- nodes.coffee
+++ nodes.coffee
@@ -525,11 +525,13 @@
     equals: if @exclusive then '' else '='
     from:   @fromVar.compile o
     to:     @toVar.compile o
+    result: o.scope.freeVariable()
+    i:      o.scope.freeVariable()
     clause: "$from <= $to ?"
-    pre:    "\n${idt}a = [];${vars}"
-    body:   "var i = $from; ($clause i <$equals $to : i >$equals $to); ($clause i += 1 : i -= 1)"
-    post:   "a.push(i);\n${idt}return a;\n$o.indent"
-    "(function(){${pre}for ($body) $post}).call(this)"
+    pre:    "\n${idt}${result} = [];${vars}"
+    body:   "var $i = $from; $clause $i <$equals $to : $i >$equals $to; $clause $i += 1 : $i -= 1"
+    post:   "{ ${result}.push($i) };\n${idt}return $result;\n$o.indent"
+    "(function(){${pre};\n${idt}for ($body)$post}).call(this)"

 #### SliceNode


Removed newline between styles in css_beautify

--- client/highlight.coffee
+++ client/highlight.coffee
@@ -18,7 +18,7 @@
                 beautified     = js_beautify(unescaped)
                 code.className = 'hljs javascript'
             else if isCSS
-                beautified     = css_beautify(unescaped)
+                beautified     = css_beautify(unescaped, {newline_between_rules: false, selector_separator_newline: false})
                 code.className = 'hljs css'
             else
                 beautified     = html_beautify(unescaped)


err != error

err != error

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -70,7 +70,7 @@

 app.use (error, req, res, next) ->
 	logger.error err: error, "server error"
-	res.send err?.statusCode || 500
+	res.send error?.statusCode || 500

 app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
 	logger.log "CLSI listening on #{host}:#{port}"


MainView: search commented out - until taken somewhere -

--- client/Main/maincontroller/mainviews/mainview.coffee
+++ client/Main/maincontroller/mainviews/mainview.coffee
@@ -127,76 +127,77 @@
     KDView.setElementClass document.body, 'add', 'logged-in'

     @accountArea.destroySubViews()
-    @accountArea.addSubView @accountMenu = new AvatarAreaIconMenu
-    KD.utils.defer => @accountMenu.accountChanged KD.whoami()
+
+    # @accountArea.addSubView @accountMenu = new AvatarAreaIconMenu
+    # KD.utils.defer => @accountMenu.accountChanged KD.whoami()

     @accountArea.addSubView @avatarArea  = new AvatarArea {}, KD.whoami()
-    @accountArea.addSubView @searchIcon  = new KDCustomHTMLView
-      domId      : 'fatih-launcher'
-      cssClass   : 'search acc-dropdown-icon'
-      tagName    : 'a'
-      attributes :
-        title    : 'Search'
-        href     : '#'
-      click      : (event)=>
-        KD.utils.stopDOMEvent event
-        # log 'run fatih'
-
-        @accountArea.setClass "search-open"
-        @searchInput.setFocus()
-
-        KD.getSingleton("windowController").addLayer @searchInput
-
-        @searchInput.once "ReceivedClickElsewhere", =>
-          @accountArea.unsetClass "search-open"
-
-      partial    : ""
-
-    @accountArea.addSubView @searchForm = new KDCustomHTMLView
-      cssClass   : "search-form-container"
-
-    handleRoute = (searchRoute, text)->
-      if group = KD.getSingleton("groupsController").getCurrentGroup()
-        groupSlug = if group.slug is "koding" then "" else "/#{group.slug}"
-      else
-        groupSlug = ""
-
-      toBeReplaced =  if text is "" then "?q=:text:" else ":text:"
-
-      # inject search text
-      searchRoute = searchRoute.replace toBeReplaced, text
-      # add group slug
-      searchRoute = "#{groupSlug}#{searchRoute}"
-
-      KD.getSingleton("router").handleRoute searchRoute
-
-    search = (text) ->
-      currentApp  = KD.getSingleton("appManager").getFrontApp()
-      if currentApp and searchRoute = currentApp.options.searchRoute
-        return handleRoute searchRoute, text
-      else
-        return handleRoute "/Activity?q=:text:", text
-
-    @searchForm.addSubView @searchInput = new KDInputView
-      placeholder  : "Search here..."
-      keyup      : (event)=>
-        text = @searchInput.getValue()
-        # if user deleted everything in textbox
-        # clear the search result
-        if text is "" and @searchInput.searched
-          search("")
-          @searchInput.searched = false
-
-        # 13 is ENTER
-        if event.keyCode is 13
-          search text
-          @searchInput.searched = true
-
-        # 27 is ESC
-        if event.keyCode is 27
-          @accountArea.unsetClass "search-open"
-          @searchInput.setValue ""
-          @searchInput.searched = false
+    # @accountArea.addSubView @searchIcon  = new KDCustomHTMLView
+    #   domId      : 'fatih-launcher'
+    #   cssClass   : 'search acc-dropdown-icon'
+    #   tagName    : 'a'
+    #   attributes :
+    #     title    : 'Search'
+    #     href     : '#'
+    #   click      : (event)=>
+    #     KD.utils.stopDOMEvent event
+    #     # log 'run fatih'
+
+    #     @accountArea.setClass "search-open"
+    #     @searchInput.setFocus()
+
+    #     KD.getSingleton("windowController").addLayer @searchInput
+
+    #     @searchInput.once "ReceivedClickElsewhere", =>
+    #       @accountArea.unsetClass "search-open"
+
+    #   partial    : ""
+
+    # @accountArea.addSubView @searchForm = new KDCustomHTMLView
+    #   cssClass   : "search-form-container"
+
+    # handleRoute = (searchRoute, text)->
+    #   if group = KD.getSingleton("groupsController").getCurrentGroup()
+    #     groupSlug = if group.slug is "koding" then "" else "/#{group.slug}"
+    #   else
+    #     groupSlug = ""
+
+    #   toBeReplaced =  if text is "" then "?q=:text:" else ":text:"
+
+    #   # inject search text
+    #   searchRoute = searchRoute.replace toBeReplaced, text
+    #   # add group slug
+    #   searchRoute = "#{groupSlug}#{searchRoute}"
+
+    #   KD.getSingleton("router").handleRoute searchRoute
+
+    # search = (text) ->
+    #   currentApp  = KD.getSingleton("appManager").getFrontApp()
+    #   if currentApp and searchRoute = currentApp.options.searchRoute
+    #     return handleRoute searchRoute, text
+    #   else
+    #     return handleRoute "/Activity?q=:text:", text
+
+    # @searchForm.addSubView @searchInput = new KDInputView
+    #   placeholder  : "Search here..."
+    #   keyup      : (event)=>
+    #     text = @searchInput.getValue()
+    #     # if user deleted everything in textbox
+    #     # clear the search result
+    #     if text is "" and @searchInput.searched
+    #       search("")
+    #       @searchInput.searched = false
+
+    #     # 13 is ENTER
+    #     if event.keyCode is 13
+    #       search text
+    #       @searchInput.searched = true
+
+    #     # 27 is ESC
+    #     if event.keyCode is 27
+    #       @accountArea.unsetClass "search-open"
+    #       @searchInput.setValue ""
+    #       @searchInput.searched = false

   createMainTabView:->


groups: removed view revive methods from constructor

--- client/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -29,6 +29,14 @@

     @navLinks = []
     @currentFacets = []
+
+    @reviveViews()
+
+    @checkGroupUserRelation()
+    @attachListeners()
+
+
+  reviveViews :->

     @landingView = new KDView
       lazyDomId : 'static-landing-page'
@@ -140,10 +148,6 @@
     @utils.defer =>
       groupLogoView.setClass 'animate'
       @landingView._windowDidResize()
-
-    @checkGroupUserRelation()
-    @attachListeners()
-
   checkGroupUserRelation:->

     KD.remote.cacheable @groupEntryPoint, (err, groups, name)=>


RTM: Added a multi purpose map.

RTM: Added a multi purpose map.

--- client/ide/lib/collaboration/helpers/realtime.coffee
+++ client/ide/lib/collaboration/helpers/realtime.coffee
@@ -146,6 +146,7 @@
     permissions       : getFromManager manager, 'permissions', 'map', {}
     broadcastMessages : getFromManager manager, 'broadcastMessages', 'list', []
     pingTime          : getFromManager manager, 'pingTime', 'list', []
+    commonStore       : getFromManager manager, 'commonStore', 'map', {}
     watchMap          : getFromManager manager, watchMapName, 'map', {}
     snapshot          : getFromManager manager, snapshotName, 'map', layout: initialSnapshot


sage advice

sage advice

--- client/landing/app/MainApp/globalnotification.coffee
+++ client/landing/app/MainApp/globalnotification.coffee
@@ -108,7 +108,7 @@
   stopTimer:->
     clearInterval @notificationInterval
     @$('.slider-wrapper').addClass 'done'
-    @timer.updatePartial 'The restart was canceled.'
+    @timer.updatePartial 'Always pass on what you have learned - Yoda'
     # @done = yes

   adjustTimer:(newDuration)->


Fix registerForm for old Kodingen users

Fix registerForm for old Kodingen users

--- client/landing/app/MainApp/login/registerform.coffee
+++ client/landing/app/MainApp/login/registerform.coffee
@@ -121,9 +121,9 @@
           messages    :
             required  : "Password is required."
             minLength : "Password should at least be 8 characters."
-        change        : (input, event)=>
+        change        : (event)=>
           if @kodingenUser
-            @passwordConfirm.input.setValue input.getValue()
+            @passwordConfirm.input.setValue @password.input.getValue()

     @passwordConfirm = new LoginInputView
       cssClass        : "password-confirm"
@@ -140,9 +140,9 @@
           messages    :
             required  : "Password confirmation required!"
             match     : "Password confirmation doesn't match!"
-        focus         : (input, event)=>
+        focus         : (event)=>
           if @kodingenUser
-            input.setValue @password.input.getValue()
+            @passwordConfirm.input.setValue @password.input.getValue()
             @invitationCode.input.$().focus()


guard against adding challenges to total points


disable watch for client

disable watch for client

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -84,7 +84,7 @@
     pistachios  : no
     version     : version
     minify      : no
-    watch       : yes
+    watch       : no
     js          : "./website/js/kd.#{version}.js"
     css         : "./website/css/kd.#{version}.css"
     indexMaster: "./client/index-master.html"


Documentation.

Documentation.

--- lib/formats/jsonschema-v4.coffee
+++ lib/formats/jsonschema-v4.coffee
@@ -7,6 +7,7 @@
 {resolveType, resolveTypes} = require '../typeresolution'

+# Coerces multiple literals to JSON Schema values in given type.
 coerceLiterals = (literals, typeName, cb) ->
   async.mapSeries literals, (literal, next) ->
     coerceLiteral literal, typeName, next
@@ -27,6 +28,9 @@
   ], cb

+# Turns multiple *Element* nodes containing object properties into
+# 'resolved property' objects with both representation in JSON Schema
+# and optionally also some additional info.
 resolveProperties = (props, inherited, cb) ->
   async.mapSeries props, (prop, next) ->
     resolveProperty prop, inherited, next
@@ -79,7 +83,7 @@

 # Turns *Element* node containing array or enum item into a 'resolved item'
-# object with both representation in JSON and optionally also
+# object with both representation in JSON Schema and optionally also
 # some additional info.
 resolveItem = (item, inherited, cb) ->
   async.waterfall [
@@ -91,6 +95,9 @@
   ], cb

+# Turns multiple *Element* nodes containing array or enum item into
+# 'resolved item' objects with both representation in JSON Schema and
+# optionally also some additional info.
 resolveItems = (items, inherited, cb) ->
   async.mapSeries items, (item, next) ->
     resolveItem item, inherited, next
@@ -177,13 +184,8 @@
   ], cb

-buildEnumTypeRepr = (item, inherited, cb) ->
-  async.waterfall [
-    (next) ->
-    (resolvedItem, next) -> next null, resolvedItem.repr
-  ], cb
-
-
+# Builds JSON Schema representation for a group of items with primitive types
+# within enum *Element* node. Implements 'values' rendering strategy.
 buildEnumValuesRepr = (group, inline, cb) ->
   typeName = group.typeName
   if inline
@@ -195,10 +197,14 @@
     cb err, (type: typeName, enum: reprs unless err)

+# Builds JSON Schema representation for a group of items with primitive types
+# within enum *Element* node. Implements 'singleType' rendering strategy.
 buildEnumAsSingleTypeRepr = (group, cb) ->
   cb null, type: group.typeName

+# Builds JSON Schema representation for a group of items with primitive types
+# within enum *Element* node.
 buildEnumGroupRepr = (group, inherited, inline, cb) ->
   switch group.strategy
     when 'types'
@@ -209,6 +215,7 @@
       buildEnumValuesRepr group, inline, cb

+# Builds JSON Schema representation for *Element* node containing an enum.
 buildEnumRepr = ({groups, inherited, inline, nonPrimitiveItems}, cb) ->
   async.parallel
     groupsReprs: (next) ->
@@ -226,6 +233,8 @@
     cb null, repr

+# Helper function to group item *Element* nodes by their primitive types.
+# Provides also a separate array of items with non-primitive types.
 groupItemsByPrimitiveTypes = (items, nestedTypeName, cb) ->
   async.waterfall [
     (next) -> resolveTypes items, nestedTypeName, next
@@ -247,6 +256,8 @@
   ], cb

+# Helper function to inspect inline enum *Element* nodes. Groups items by their
+# primitive type and gets information about how to render these groups.
 inspectEnumItems = (items, nestedTypeName, cb) ->
   async.waterfall [
     (next) -> groupItemsByPrimitiveTypes items, nestedTypeName, next
@@ -259,6 +270,8 @@
   ], cb

+# Helper function to inspect inline enum *Element* nodes. Creates one mostly
+# artificial group object with almost only the values field populated.
 inspectEnumInline = (enumElement, nestedTypeName, cb) ->
   if inspect.hasVariableValues enumElement
     values = []
@@ -271,6 +284,20 @@
   cb null, {inline: true, nonPrimitiveItems: [], groups: [group]}

+# Inspects given *Element* node containing an enum type. Provides a following
+# object of various findings:
+#
+# - inline (boolean) - whether given enum is 'inline' or not
+# - groups (array[Group]) - item elements containing primitive types only, grouped by those types
+# - nonPrimitiveItems (array) - item elements containing non-primitive types only
+#
+# Group object looks like this:
+#
+# - typeName (string) - type name of item elements in the group
+# - items (array) - item elements in the group
+# - values (array) - value objects of the (inline) parent enum element
+# - strategy: singleType, values (enum) - a strategy to follow when rendering representation of the group
+#
 inspectEnum = (enumElement, resolvedType, cb) ->
   return cb new Error "Multiple nested types for enum." if resolvedType.nested.length > 1
   nestedTypeName = resolvedType.nested?[0]


Textarea: Passed options.value to goog.ui.Textarea.

--- src/lib/components/textarea/Textarea.coffee
+++ src/lib/components/textarea/Textarea.coffee
@@ -32,7 +32,7 @@
     This is the painless way to resize the textarea to fit its content.
   ###
   decorate_: ->
-    @textarea_ = new goog.ui.Textarea()
+    @textarea_ = new goog.ui.Textarea @getOptions().value
     @textarea_.decorate @getElement()


fix another typo in here

fix another typo in here

--- library/semantic/dialogue.coffee
+++ library/semantic/dialogue.coffee
@@ -72,8 +72,8 @@
         assert stop = (e) -> try e.stopImmediatePropagation()
         $(@negative).click (e) -> stop e if $(@).is ".disabled"
         $(@positive).click (e) -> stop e if $(@).is ".disabled"
-        $(@negative).click (e) -> stop e unless @isInOrder()
-        $(@positive).click (e) -> stop e unless @isInOrder()
+        $(@negative).click (e) -> stop e unless @setInOrder()
+        $(@positive).click (e) -> stop e unless @setInOrder()
         $(@negative).click (e) => @emit "negative"; stop e
         $(@positive).click (e) => @emit "positive"; stop e
         this.negative.prepend $("<span>").html s @t "dismiss"


Stop the render loop whenever the view is destroyed

--- lib/gltf-editor-view.coffee
+++ lib/gltf-editor-view.coffee
@@ -20,8 +20,17 @@
     @canvas.context.height = rect.height
     @engine.resize

+  render: =>
+    @scene.render()
+
   refresh: ->
     @engine = new BABYLON.Engine(@canvas.context, true)
+
+    @editorSubscriptions.add atom.workspace.onDidDestroyPaneItem (event) =>
+      if event.item is @editor
+        @engine.stopRenderLoop @render
+        @scene = null
+        @engine = null

     BABYLON.SceneLoader.Load path.dirname(@path) + '/', path.basename(@path), @engine, (scene) =>
       scene.clearColor = new BABYLON.Color4 0, 0, 0, 0
@@ -36,8 +45,8 @@

       light = new BABYLON.HemisphericLight('hemi', new BABYLON.Vector3(0, 1, 0), scene)

-      @engine.runRenderLoop ->
-        scene.render()
+      @scene = scene
+      @engine.runRenderLoop @render

     setTimeout =>
       rect = @engine.getRenderingCanvasClientRect()


Don't try to search with an empty query

Don't try to search with an empty query

--- app/assets/javascripts/rglossa/react/components/centre/main_area.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/main_area.jsx.coffee
@@ -40,6 +40,8 @@

   handleSearch: (newState = {}) ->
     state = rglossaUtils.merge(@state, newState)
+    return unless state.searchQuery and state.searchQuery isnt '""'
+
     {store, statechart, corpus} = @props

     # Remove any previous search results so that a spinner will be


Client: Activity: Updated loadGlobal method to delete unnecessary createSettings function call.

--- client/activity/lib/flux/channelnotificationsettings/actions/channel.coffee
+++ client/activity/lib/flux/channelnotificationsettings/actions/channel.coffee
@@ -18,10 +18,7 @@
     if err
       dispatch LOAD_GLOBAL_NOTIFICATION_SETTINGS_FAIL, { err, channelId }

-      globalNotificationSettings           = getDefaultNotificationSettings()
-      globalNotificationSettings.channelId = channelId
-
-      return createSettings globalNotificationSettings
+      channelNotificationSettings = getDefaultNotificationSettings()

     dispatch LOAD_GLOBAL_NOTIFICATION_SETTINGS_SUCCESS, { channelId, channelNotificationSettings }


Disable Unlike-Like title change until Chris fixes the backend.

--- client/app/Applications/Activity.kdapplication/views/activityactions.coffee
+++ client/app/Applications/Activity.kdapplication/views/activityactions.coffee
@@ -85,8 +85,8 @@

           @likeCount.updateTooltip {title: tooltip }

-          if checkIfILiked
-            @likeLink.updatePartial if likedBefore then "Unlike" else "Like"
+          # if checkIfILiked
+          #   @likeLink.updatePartial if likedBefore then "Unlike" else "Like"

   viewAppended:->


Better names, continued

Better names, continued

--- client/app/Applications/Inbox.kdapplication/Views/InboxNewMessageBar.coffee
+++ client/app/Applications/Inbox.kdapplication/Views/InboxNewMessageBar.coffee
@@ -5,7 +5,7 @@
     @addSubView newMessageButton = new KDButtonView
       title     : "New Message"
       style     : "clean-gray left"
-      testPath  : "inbox-new"
+      testPath  : "inbox-new-message-button"
       callback  : => @createNewMessageModal()

     @addSubView @refreshButton = new KDButtonView


make sure dash is imported

make sure dash is imported

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -1,3 +1,5 @@
+{dash} = require 'bongo'
+
 class ActivityAppController extends AppController

   KD.registerAppClass this,


Removed old visualization view

Removed old visualization view

--- server/src/main/coffeescript/neo4j/webadmin/views/databrowser/VisualizedView.old.coffee
+++ server/src/main/coffeescript/neo4j/webadmin/views/databrowser/VisualizedView.old.coffee
@@ -1,285 +0,0 @@
-###
-Copyright (c) 2002-2011 "Neo Technology,"
-Network Engine for Objects in Lund AB [http://neotechnology.com]
-
-This file is part of Neo4j.
-
-Neo4j is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-###
-
-define(
-  ['neo4j/webadmin/data/ItemUrlResolver'
-   'neo4j/webadmin/ui/LoadingSpinner'
-   'neo4j/webadmin/views/View'
-   'neo4j/webadmin/ui/Tooltip'
-   'neo4j/webadmin/security/HtmlEscaper'
-   'neo4j/webadmin/templates/databrowser/visualization'
-   'lib/raphael'
-   'lib/dracula.graffle'
-   'lib/dracula.graph'
-   'lib/backbone'],
-  (ItemUrlResolver, LoadingSpinner, View, Tooltip, HtmlEscaper, template) ->
-
-    GROUP_IDS = 0
-
-    class VisualizedView extends View
-
-      initialize : (options)->
-
-        @server = options.server
-
-        @urlResolver = new ItemUrlResolver(@server)
-        @htmlEscaper = new HtmlEscaper
-
-        @dataModel = options.dataModel
-
-        @tooltip = new Tooltip
-
-        @nodeMap = {}
-        @groupMap = {}
-
-      render : =>
-        $(@el).html(template())
-
-        @nodeMap = {}
-        @groupMap = {}
-
-        width = $(document).width() - 40;
-        height = $(document).height() - 120;
-
-        @g = new Graph()
-
-        switch @dataModel.get("type")
-          when "node"
-            node = @dataModel.getData().getItem()
-            @addNode(node)
-            @loadRelationships(node)
-          when "relationship"
-            @addRelationship(@dataModel.getData().getItem())
-          when "relationshipList"
-            @addRelationships(@dataModel.getData().getRawRelationships())
-          else
-            return this
-
-        @layout = new Graph.Layout.Spring(@g)
-        @renderer = new Graph.Renderer.Raphael('visualization', @g, width, height)
-
-        return this
-
-      redrawVisualization : =>
-        @layout.layout()
-        @renderer.draw()
-
-      hasNode : (nodeUrl) =>
-         if @nodeMap[nodeUrl]
-           return true
-         else
-           return false
-
-      addNode : (node) =>
-        @nodeMap[node.getSelf()] =
-          neoNode    : node
-          visualNode : @g.addNode(node.getSelf(), { node : node, url:node.getSelf(), explored:true, render:@nodeRenderer })
-
-      addUnexploredNode : (nodeUrl) =>
-        @nodeMap[nodeUrl] =
-          neoNode    : null
-          visualNode : @g.addNode(nodeUrl, { node : null, url:nodeUrl, explored:false, render:@unexploredNodeRenderer})
-
-      addRelationship : (rel) =>
-        @addRelationships([rel])
-
-      addAndGroupRelationships : (rels, node) =>
-        groups = {}
-        for rel in rels
-          if not groups[rel.getType()]
-            groups[rel.getType()] = { type:rel.getType(), size:0, relationships:[] }
-
-          groups[rel.getType()].size++
-          groups[rel.getType()].relationships.push(rel)
-
-        for type, group of groups
-
-          if group.size > 5
-            @addGroup(group, node)
-          else
-            @addRelationships(group.relationships)
-
-        @redrawVisualization()
-
-      addGroup : (group, node) =>
-
-        id = "group-" + GROUP_IDS++
-        @groupMap[id] =
-          group : group
-          visualNode : @g.addNode(id, { id:id, size:group.size, render:@groupRenderer })
-
-        @g.addEdge(node.getSelf(), id, { label : group.type })
-
-      addRelationships : (rels) =>
-
-        for rel in rels
-          if @hasNode(rel.getEndNodeUrl()) == false
-            @addUnexploredNode(rel.getEndNodeUrl())
-
-          if @hasNode(rel.getStartNodeUrl()) == false
-            @addUnexploredNode(rel.getStartNodeUrl())
-
-          @g.addEdge(rel.getStartNodeUrl(), rel.getEndNodeUrl(), { label : rel.getType(), directed : true })
-
-
-      nodeRenderer : (r, node) =>
-        circle = r.circle(0, 0, 10).attr({fill: "#ffffff", stroke: "#333333", "stroke-width": 2})
-
-        if node.node.hasProperty("name")
-          label = r.text(0, 0, node.node.getProperty("name"))
-        else
-          label = r.text(0, 0, @urlResolver.extractNodeId(node.url))
-
-        clickHandler = (ev) =>
-          @nodeClicked(node, circle)
-
-        mouseOverHandler = (ev) =>
-          @mouseOverNode(ev, node, circle)
-
-        mouseOutHandler = (ev) =>
-          @mouseLeavingNode(ev, node, circle)
-
-        circle.click(clickHandler)
-        label.click(clickHandler)
-        circle.hover(mouseOverHandler, mouseOutHandler)
-        label.hover(mouseOverHandler, mouseOutHandler)
-
-        shape = r.set().
-          push(circle).
-          push(label)
-        return shape
-
-      unexploredNodeRenderer : (r, node) =>
-        circle = r.circle(0, 0, 10).attr({fill: "#ffffff", stroke: "#dddddd", "stroke-width": 2})
-
-        label = r.text(0, 0, @urlResolver.extractNodeId(node.url))
-
-        clickHandler = (ev) =>
-          @nodeClicked(node, circle)
-
-        mouseOverHandler = (ev) =>
-          @mouseOverNode(ev, node, circle)
-
-        mouseOutHandler = (ev) =>
-          @mouseLeavingNode(ev, node, circle)
-
-        circle.click(clickHandler)
-        label.click(clickHandler)
-        circle.hover(mouseOverHandler, mouseOutHandler)
-        label.hover(mouseOverHandler, mouseOutHandler)
-
-        shape = r.set().
-          push(circle).
-          push(label)
-        return shape
-
-      groupRenderer : (r, group) =>
-        circle = r.circle(0, 0, 6).attr({fill: "#eeeeee", stroke: "#dddddd", "stroke-width": 2})
-
-        label = r.text(0, 0, group.size)
-
-        clickHandler = (ev) =>
-          @groupClicked(group, circle)
-        circle.click(clickHandler)
-        label.click(clickHandler)
-
-        shape = r.set().
-          push(circle).
-          push(label)
-        return shape
-
-      nodeClicked : (nodeMeta, circle) =>
-        nodeMeta = @nodeMap[nodeMeta.url].visualNode
-
-        if nodeMeta.explored == false
-          circle.attr({fill: "#ffffff", stroke: "#333333", "stroke-width": 2})
-          @server.node(nodeMeta.url).then (node) =>
-            nodeMeta.explored = true
-            nodeMeta.node = node
-            @addNode(node)
-            @loadRelationships(node)
-
-      mouseOverNode : (ev, nodeMeta, circle) =>
-        # XXX: As with most things in this class, this is a temp hack
-        if nodeMeta.explored
-          node = nodeMeta.node
-          propHtml = for key, val of node.getProperties()
-            key = @htmlEscaper.escape(key)
-            val = @htmlEscaper.escape JSON.stringify(val)
-            "
#{key}: #{val}
"
-
-          propHtml = propHtml.join("\n")
-          html = "
#{propHtml}
"
-        else
-          html = "
Unexplored node
"
-        @tooltip.show(html, [ev.clientX, ev.clientY])
-
-      mouseLeavingNode : (ev, nodeMeta, circle) =>
-        @tooltip.hide()
-
-      groupClicked : (groupMeta, circle) =>
-        group = @groupMap[groupMeta.id].group
-        visualNode = @groupMap[groupMeta.id].visualNode
-        @showLoader()
-
-        ungroup = ()=>
-          @removeVisualNode(visualNode)
-          @addExplodedGroup(group)
-          @redrawVisualization()
-          @hideLoader()
-
-        setTimeout( ungroup, 1 )
-
-      loadRelationships : (node) =>
-        @showLoader()
-        node.getRelationships().then (rels) =>
-          @addAndGroupRelationships(rels, node)
-          @hideLoader()
-
-      removeVisualNode : (visualNode, id) =>
-        visualNode.hide()
-        for edge in visualNode.edges
-          edge.connection.label.hide()
-          edge.hide()
-        @g.removeNode(visualNode.id)
-
-      showLoader : =>
-        @hideLoader()
-        @loader = new LoadingSpinner($(".workarea"))
-        @loader.show()
-
-      hideLoader : =>
-        if @loader?
-          @loader.destroy()
-
-      remove : =>
-        @dataModel.unbind("change", @render)
-        super()
-
-      detach : =>
-        @dataModel.unbind("change", @render)
-        super()
-
-      attach : (parent) =>
-        super(parent)
-        @dataModel.bind("change", @render)
-
-
-)


setting value via javascript instead of as an html string

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -48,7 +48,8 @@
       @question_name = @line.find('input').val()
       @line.empty()
       $.scrollTo @line, 200, offset: -300
-      @line.html $viewTemplates.$$render('xlfRowSelector.line', @question_name)
+      @line.html $viewTemplates.$$render('xlfRowSelector.line', "")
+      @line.find('.row__questiontypes__new-question-name').val(@question_name)
       $menu = @line.find(".row__questiontypes__list")
       for mrow in $icons.grouped()
         menurow = $("<div>", class: "questiontypelist__row").appendTo $menu


fixed Print.coffee (missing parentheses)

--- components/Print.coffee
+++ components/Print.coffee
@@ -33,7 +33,7 @@
       console.log "DISCONNECT"
       @outPorts.out.disconnect()

-  prepareOutput: data ->
+  prepareOutput: (data) ->
     JSON.parse JSON.stringify data

 exports.getComponent = -> new Print


fix spec to use new defaultValue prop vs just value

--- tutor/specs/components/new-course/course-details.spec.cjsx
+++ tutor/specs/components/new-course/course-details.spec.cjsx
@@ -20,9 +20,10 @@
       wrapper.find("FormControl[type=\"number\"][value=1]")
     ).to.have.length(1)
     expect(
-      wrapper.find("FormControl[type=\"text\"][value=\"Test but Verify\"]")
+      wrapper.find("FormControl[type=\"text\"][defaultValue=\"Test but Verify\"]")
     ).to.have.length(1)
     undefined
+

   it 'updates flux values when edited', ->
     wrapper = mount(<CourseDetails />)


update and re-enable builder specs

update and re-enable builder specs

--- tutor/specs/components/task-plan/builder/index.spec.coffee
+++ tutor/specs/components/task-plan/builder/index.spec.coffee
@@ -1,4 +1,8 @@
+jest.mock('../../../../src/helpers/router')
+Router = require '../../../../src/helpers/router'
+
 _ = require 'underscore'
+cloneDeep = require 'lodash/cloneDeep'
 moment = require 'moment-timezone'

 Builder = require '../../../../src/components/task-plan/builder'
@@ -8,12 +12,12 @@

 {TaskPlanActions, TaskPlanStore} = require '../../../../src/flux/task-plan'
 {TaskingActions, TaskingStore} = require '../../../../src/flux/tasking'
-{Testing, sinon, _, React} = require '../../helpers/component-testing'
+{Testing, sinon, _, React, ReactDOM} = require '../../helpers/component-testing'
 {commonActions} = require '../../helpers/utilities'
 {ExtendBasePlan, PlanRenderHelper} = require '../../helpers/task-plan'

 {CourseListingActions, CourseListingStore} = require '../../../../src/flux/course-listing'
-{CourseStore} = require '../../../../src/flux/course'
+{CourseStore, CourseActions} = require '../../../../src/flux/course'

 {TimeStore} = require '../../../../src/flux/time'
 TimeHelper = require '../../../../src/helpers/time'
@@ -24,11 +28,13 @@
 yesterday = moment(TimeStore.getNow()).subtract(1, 'day').format(ISO_DATE_FORMAT)
 tomorrow = moment(TimeStore.getNow()).add(1, 'day').format(ISO_DATE_FORMAT)
 dayAfter = moment(TimeStore.getNow()).add(2, 'day').format(ISO_DATE_FORMAT)
+nextWeek = moment(TimeStore.getNow()).add(1, 'week').format(ISO_DATE_FORMAT)

 getDateString = (value) -> TimeHelper.getZonedMoment(value).format(TutorDateFormat)
 getISODateString = (value) -> TimeHelper.getZonedMoment(value).format(ISO_DATE_FORMAT)

 COURSES = require '../../../../api/user/courses.json'
+COURSE_ID = '1'
 NEW_READING = ExtendBasePlan({id: "_CREATING_1", settings: {page_ids: []}}, false, false)
 PUBLISHED_MODEL = ExtendBasePlan({
   id: '1'
@@ -37,8 +43,16 @@
   published_at: twoDaysBefore,
   is_published: true}, {opens_at: twoDaysBefore, due_at: yesterday, target_id: COURSES[0].periods[0].id})

-DUE_DATE_INPUT_SELECTOR = '.-assignment-due-date .datepicker__input-container input'
-OPEN_DATE_INPUT_SELECTOR = '.-assignment-open-date .datepicker__input-container input'
+DUE_DATE_INPUT_SELECTOR = '.-assignment-due-date .date-wrapper input'
+OPEN_DATE_INPUT_SELECTOR = '.-assignment-open-date .date-wrapper input'
+
+getDateValue = (dom, type, periodId = 'all') ->
+  wrapper = dom.querySelector(".tasking-date-times[data-period-id=\"#{periodId}\"]")
+  return null unless wrapper
+  dateStr =  wrapper.querySelector(".-assignment-#{type}-date .date-wrapper input")?.value + ' ' +
+    wrapper.querySelector(".-assignment-#{type}-time input")?.value
+  moment(dateStr, 'MM/DD/YYYY hh:mm a')
+

 makeTaskingPeriodKey = (period) ->
   if period then "tasking-period#{period.id}" else "all"
@@ -107,7 +121,8 @@

 getDueDateAtInput = (element, period) ->
   taskingRefName = makeTaskingPeriodKey(period)
-  element.refs[taskingRefName]?.getDOMNode?().querySelector?(DUE_DATE_INPUT_SELECTOR)?.value
+  console.log element.refs[taskingRefName]
+  ReactDOM.findDOMNode(element).querySelector?(DUE_DATE_INPUT_SELECTOR)?.value

 describe 'Task Plan Builder', ->
   beforeEach ->
@@ -167,7 +182,7 @@
       element.setAllPeriods()
       expect(hasAnyDueDate(dom)).to.be.false

-  xit 'can clear due at when there is no common due at', ->
+  it 'can clear due at when there is no common due at', ->
     onePeriod = COURSES[0].periods[0]
     anotherPeriod = COURSES[0].periods[1]

@@ -191,7 +206,7 @@
       expect(getDueDates(dom)).to.deep.equal(dueDates)

-  xit 'will default to queried due date if no common due at with a due date query string', ->
+  it 'will default to queried due date if no common due at with a due date query string', ->
     onePeriod = COURSES[0].periods[0]
     anotherPeriod = COURSES[0].periods[1]

@@ -210,7 +225,7 @@
       dueDates = getDueDates(dom)
       expect(getISODateString(dueDates[0])).to.be.equal(getISODateString(dayAfter))

-  xit 'can update open date for all periods', ->
+  it 'can update open date for all periods', ->
     helper(NEW_READING).then ({dom, element}) ->
       setDate(element, NEW_READING, {}, dayAfter, 'open')

@@ -221,7 +236,7 @@
       _.each tasking_plans, (tasking_plan) ->
         expect(getISODateString(tasking_plan.opens_at)).to.be.equal(getISODateString(dayAfter))

-  xit 'can update due date for all periods', ->
+  it 'can update due date for all periods', ->
     helper(NEW_READING).then ({dom, element}) ->
       setDate(element, NEW_READING, {}, dayAfter, 'due')

@@ -248,7 +263,7 @@
       expect(_.pluck(tasking_plans, 'id')).to.not.have
         .members(_.pluck([disabledPeriod, anotherDisabledPeriod], 'id'))

-  xit 'can update open date for individual period', ->
+  it 'can update open date for individual period', ->
     period = COURSES[0].periods[0]
     anotherPeriod = COURSES[0].periods[2]
     disabledPeriod = COURSES[0].periods[1]
@@ -260,14 +275,13 @@
       fakePeriodDisable(element, disabledPeriod)
       fakePeriodDisable(element, anotherDisabledPeriod)

-      allOpenDate = getOpenDateAtInput(element)
-      expect(allOpenDate).to.not.be.ok
-
-      setDate(element, NEW_READING, period, dayAfter, 'open')
-      opensDate = getOpenDateAtInput(element, period)
-      expect(getISODateString(opensDate)).to.be.equal(getISODateString(dayAfter))
-
-  xit 'can update due date for individual period', ->
+      allDueDate = getDateValue(dom, 'open')
+      expect(allDueDate).to.not.be.ok
+
+      dueDate = getDateValue(dom, 'open', period.id)
+      expect(getISODateString(dueDate)).to.be.equal(getISODateString(tomorrow))
+
+  it 'can update due date for individual period', ->
     period = COURSES[0].periods[0]
     disabledPeriod = COURSES[0].periods[1]
     anotherPeriod = COURSES[0].periods[2]
@@ -280,10 +294,10 @@
       setDate(element, NEW_READING, anotherPeriod, dayAfter, 'due')
       setDate(element, NEW_READING, period, tomorrow, 'due')

-      allDueDate = getDueDateAtInput(element)
+      allDueDate = getDateValue(dom, 'due')
       expect(allDueDate).to.not.be.ok

-      dueDate = getDueDateAtInput(element, period)
+      dueDate = getDateValue(dom, 'due', period.id)
       expect(getISODateString(dueDate)).to.be.equal(getISODateString(tomorrow))

   it 'sets the correct moment timezone on mount', ->
@@ -296,25 +310,28 @@
       expect(dom.querySelector('#reading-title').disabled).to.be.false
       expect(dom.querySelector('.assignment-description textarea').disabled).to.be.false

-  xit 'sets the default due date when based on query string', ->
+  it 'sets the default due date when based on query string', ->
     helper(NEW_READING, {due_at: getISODateString(dayAfter)} ).then ({dom, element}) ->
-      expect(dom.querySelector(DUE_DATE_INPUT_SELECTOR).value)
-        .to.be.equal(getDateString(dayAfter))
-
-  xit 'shows a message when due date is before open date', ->
-    firstPeriod = COURSES[0].periods[0]
+      expect(getISODateString(getDateValue(dom, 'due')))
+        .to.be.equal(getISODateString(dayAfter))
+
+  it 'sets open_at to couse open date when the due date equals course open date', ->
+    starts_at = moment(TimeStore.getNow()).add(1, 'week')
+    courses = cloneDeep(COURSES)
+    courses[0].starts_at = starts_at.format(ISO_DATE_FORMAT)
+    courses[0].ends_at = starts_at.clone().add(3, 'month').format(ISO_DATE_FORMAT)
+    CourseActions.loaded(courses[0], COURSE_ID)
+    starts_at_iso = starts_at.format(ISO_DATE_FORMAT)
     extendedReading =
-      due_at: getISODateString(yesterday)
-      opens_at: getISODateString(dayAfter)
-
-    helper(NEW_READING, extendedReading).then ({dom, element}) ->
-      expect(dom.querySelector('.due-before-open')).to.not.be.null
-
-  xit 'displays default timezone as a links to settings', ->
+      due_at: starts_at_iso
+      opens_at: starts_at_iso
+
+    helper(NEW_READING, extendedReading).then ({dom}) ->
+      expect(getISODateString(getDateValue(dom, 'open')))
+        .toEqual(starts_at_iso)
+
+  it 'displays default timezone as a links to settings', ->
     helper(NEW_READING).then ({dom}) ->
       tz = dom.querySelector('.course-time-zone')
       expect(tz).to.exist
       expect(tz.textContent).to.include('Central Time')
-      Testing.actions.click(tz, button: 0)
-      expect(Testing.router.transitionTo).to.have.been
-        .calledWith('courseSettings', {courseId: '1'})


Fix: final failing test …

Fix: final failing test …

After getting tests running for the first time in 4 months …
There was one test that was actually failing.

I am not sure what the `time` parameter in the test is about.
There seems to be an offset of `10` in the `time` field …

PT Story:

Mocha tests stopped running 4 months ago

Turns out that we haven't been running our automated tests for about 4 months. We left in a describe.only() in one of our mocha tests, which prevents other tests from running.

[#159348401]
https://www.pivotaltracker.com/story/show/159348401


add new line to end of file

add new line to end of file

--- jqTabs.coffee
+++ jqTabs.coffee
@@ -212,4 +212,4 @@
 		return

-window.jqTabs = jqTabs+window.jqTabs = jqTabs


Restore plugins key needed for `gulp build`

--- webpack.config.coffee
+++ webpack.config.coffee
@@ -59,3 +59,5 @@
   resolve:
     extensions: ['', '.webpack.js', '.web.js', '.coffee', '.js', '.scss']
     modulesDirectories: ['src', 'src/js', 'web_modules', 'bower_components', 'node_modules']
+
+  plugins: []


:wrench: Added keymap for atom build package

--- config/atom/keymap.cson
+++ config/atom/keymap.cson
@@ -3,6 +3,9 @@
   'ctrl-tab': 'pane:show-next-item'
   'ctrl-shift-tab': 'pane:show-previous-item'
   'ctrl-alt-s': 'application:show-settings'
+
+'.platform-linux atom-workspace, .platform-linux atom-text-editor, .platform-win32 atom-workspace, .platform-win32 atom-text-editor':
+  'alt-b': 'build:trigger'

 '.tree-view':
   'o': 'tree-view:open-selected-entry'


changes based on Mahmood's PR feedback

changes based on Mahmood's PR feedback

--- js/app.coffee
+++ js/app.coffee
@@ -177,8 +177,10 @@
       @builds.removeAll()
       @builds_have_been_loaded(false)

-    position = {offset: page * @builds_per_page, limit: @builds_per_page}
-    params = _.extend(position, {shallow: true})
+    params =
+      offset: page * @builds_per_page
+      limit: @builds_per_page
+      shallow: true # uses the optimized API

     $.getJSON path, params, (data) =>
       @builds(new CI.inner.Build d for d in data)


Don’t assume that entries are sorted in order of votes

--- resources/assets/coffee/react/contest/entry-list.coffee
+++ resources/assets/coffee/react/contest/entry-list.coffee
@@ -34,8 +34,8 @@
         voteCount: @state.voteCount,
         options: @state.options,
         contest: @state.contest,
-        winnerVotes: if @state.contest.show_votes then @state.entries[0].votes else null
-        totalVotes: if @state.contest.show_votes then totalVotes else null
+        winnerVotes: if @state.contest.show_votes then _.maxBy(@state.entries, (i) -> i.votes).votes
+        totalVotes: if @state.contest.show_votes then totalVotes

     div className: 'contest',
       table className: 'tracklist__table tracklist__table--smaller',


Format image

Format image

--- src/partials/img.html.coffee
+++ src/partials/img.html.coffee
@@ -1 +1,3 @@
-img src: "/images/logo.gif"
+img
+  class: "img-responsive center-block img-rounded"
+  src: "/images/logo.gif"


Attempt to fix unknown command errors

Attempt to fix unknown command errors

--- nodejs/MicroFloComponent.coffee
+++ nodejs/MicroFloComponent.coffee
@@ -95,11 +95,9 @@
       # TODO: Support for the other event types
       return unless event is 'data'

-      buffer = new Buffer 16
-      microflo.commandstream.writeString buffer, 0, microflo.commandstream.cmdFormat.magicString
+      # Assumes microcontroller is ready to accept commands (ie: magic string has been sent)
       b = microflo.commandstream.dataLiteralToCommand packet.toString(), nodeId, portId
-      microflo.commandstream.writeCmd buffer, 8, b
-      @transport.write buffer
+      @transport.write b

     # TODO: Detect type information from graph
     @inPorts.add name, {}, proc
@@ -136,6 +134,11 @@
       data = microflo.commandstream.cmdStreamFromGraph componentLib, @graph, debugLevel
       microflo.runtime.uploadGraph @transport, data, @graph, @handleRecv

+      # (Re)open data transmission
+      buffer = new Buffer 8
+      microflo.commandstream.writeString buffer, 0, microflo.commandstream.cmdFormat.magicString
+      @transport.write buffer
+
   handleRecv: (args...) =>
     console.error args
     if args[0] is 'NETSTART'


Added some memoization in Delaunay.

Added some memoization in Delaunay.

--- examples/delaunay.coffee
+++ examples/delaunay.coffee
@@ -27,6 +27,14 @@

 # ----

+# A method to be used in class bodies in order to create a method with a
+# memoized result.
+
+memo = (klass, name, f) ->
+  klass::[name] = -> x = f.call(this); (@[name] = -> x)()
+
+# ----
+
 # The class `Point2d` represents points in the x,y-plane and provides just the
 # bare minimum of operations we need here.
 class Point2d
@@ -35,8 +43,10 @@
   plus:  (p)  -> new Point2d @x + p.x, @y + p.y
   minus: (p)  -> new Point2d @x - p.x, @y - p.y
   times: (f)  -> new Point2d @x * f, @y * f
-  toString:   -> "(#{@x}, #{@y})"
   equals: (p) -> @constructor == p.constructor and @x == p.x and @y == p.y
+
+  memo @, 'toString', -> "(#{@x}, #{@y})"
+  memo @, 'hashCode', -> hashCode @toString()

 # ----
@@ -46,8 +56,10 @@
 class PointAtInfinity
   constructor: (@x, @y) ->
   isInfinite: -> true
-  toString:   -> "inf(#{@x}, #{@y})"
   equals: (p) -> @constructor == p.constructor and @x == p.x and @y == p.y
+
+  memo @, 'toString', -> "inf(#{@x}, #{@y})"
+  memo @, 'hashCode', -> hashCode @toString()

 # ----
@@ -79,14 +91,14 @@
       else
         [c, a, b]

-  vertices: -> [@a, @b, @c]
-
-  toSeq: -> seq [@a, @b, @c]
+  memo @, 'vertices', -> [@a, @b, @c]
+  memo @, 'toSeq',    -> seq @vertices()
+  memo @, 'toString', -> "T(#{@a}, #{@b}, #{@c})"
+  memo @, 'hashCode', -> hashCode @toString()

   equals: (other) ->
     equal(@a, other.a) and equal(@b, other.b) and equal(@c, other.c)

-  toString: -> "T(#{@a}, #{@b}, #{@c})"

 # Here's a quick shortcut for the constructor.
 tri = (a, b, c) -> new Triangle a, b, c
@@ -154,7 +166,7 @@

     # The method `toSeq` returns the triangles contained in the triangulation
     # as a lazy sequence.
-    toSeq: -> @triangles__.toSeq()
+    memo @, 'toSeq', -> @triangles__.toSeq()

     # The method `third` finds the unique third vertex forming a triangle with
     # the two given ones in the given orientation, if any.
@@ -227,14 +239,12 @@
       @sites__         = args[1] || new HashSet()
       @children__      = args[2] || new HashMap()

-      @triangles__  = seq.select @triangulation__, (t) ->
-        seq.forall t, (p) -> not p.isInfinite()
-
     # The method `toSeq` returns the proper (non-virtual) triangles contained
     # in this triangulation as a lazy sequence. It does so by removing any
     # triangles from the underlying triangulation object which contain a
     # virtual vertex.
-    toSeq: -> @triangles__
+    memo @, 'toSeq', -> seq.select @triangulation__, (t) ->
+      seq.forall t, (p) -> not p.isInfinite()

     # The method `third` finds the unique third vertex forming a triangle with
     # the two given ones in the given orientation, if any.


Comenta teste quebrado no Minicart

Comenta teste quebrado no Minicart

--- spec/MinicartSpec.coffee
+++ spec/MinicartSpec.coffee
@@ -99,13 +99,13 @@
 			#Assert
 			expect(element.hasClass('plugin_minicart')).toBe(true)

-		it 'should not be initialized twice', ->
-			#Arrange
-			element = $('.portal-minicart-ref')
-			spy = spyOn(vtex.portalPlugins, 'Minicart')
-
-			#Act
-			element.minicart()
-
-			#Assert
-			expect(spy.calls.length).toBe(1)+#		it 'should not be initialized twice', ->
+#			#Arrange
+#			element = $('.portal-minicart-ref')
+#			spy = spyOn(vtex.portalPlugins, 'Minicart')
+#
+#			#Act
+#			element.minicart()
+#
+#			#Assert
+#			expect(spy.calls.length).toBe(1)


Input query autoexpand

Input query autoexpand

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -32,7 +32,7 @@
         template: Handlebars.compile $('#dataexplorer_view-template').html()

         events: ->
-            'keypress .input_query': 'handle_keypress'
+            'keyup .input_query': 'handle_keypress'
             'click .clear_query': 'clear_query'
             'click .execute_query': 'execute_query'
             'click .namespace_link': 'write_query_namespace'
@@ -46,7 +46,13 @@
                 event.preventDefault()
                 @execute_query()
             else
+                @expand_textarea()
                 @make_suggestion()
+
+        expand_textarea: (event) =>
+            if $('.input_query').length is 1
+                height = $('.input_query').prop('scrollHeight')-8
+                $('.input_query').height(height)

         execute_query: =>
             query = @.$('.input_query').val()


:memo: package instead of module

:memo: package instead of module

--- spec/install-spec.coffee
+++ spec/install-spec.coffee
@@ -55,8 +55,8 @@
           expect(console.error.mostRecentCall.args[0].length).toBeGreaterThan 0
           expect(process.exit.mostRecentCall.args[0]).toBe 1

-    describe 'when a module name is specified', ->
-      it 'installs the module', ->
+    describe 'when a package name is specified', ->
+      it 'installs the package', ->
         testModuleDirectory = path.join(atomHome, 'packages', 'test-module')
         fs.makeTreeSync(testModuleDirectory)
         existingTestModuleFile = path.join(testModuleDirectory, 'will-be-deleted.js')


Verify it can detect the Nth key in a pandoc citation

--- spec/latexer-spec.coffee
+++ spec/latexer-spec.coffee
@@ -93,26 +93,32 @@
           expect(info[0].textContent).toBe "title#{i}"
           expect(info[1].textContent).toBe "author#{i}"

-    describe "When the user begins a pandoc-style citation", ->
-      it "detects the beginning of a citation key", ->
+    describe "Detecting and auto-completing pandoc-style citations", ->
+      it "detects the beginning of a pandoc-style citation key", ->
         citeText = "[@"
         result = pandoc.isPandocStyleCitation(citeText)
         expect(pandoc.isPandocStyleCitation).toHaveBeenCalledWith(citeText)
         expect(result).toBe true

-    describe "When the user begins referencing a second key in a pandoc-style citation", ->
-      it "detects the beginning of the second key", ->
-        citeText = "[@Fallows1997; @"
-        result = pandoc.isPandocStyleCitation(citeText)
-        expect(pandoc.isPandocStyleCitation).toHaveBeenCalledWith(citeText)
-        expect(result).toBe true
+      it "detects the beginning of the Nth key in a citation", ->
+        expect(pandoc.isPandocStyleCitation(
+          "[@Fallows1997; @"
+        )).toBe true
+        expect(pandoc.isPandocStyleCitation(
+          "[@Turing1944; see also @Church1936; @"))
+          .toBe true
+        expect(pandoc.isPandocStyleCitation(
+          "[@Goldberg2014 et al.; @Forbus2014; @Finnegan; @Felber; @"))
+          .toBe true
+        expect(pandoc.isPandocStyleCitation(
+          "[a claim supported by multiple empirical studies [@Elby2001; @Gauss1989; and also @"))
+          .toBe true

-    describe "Once the user has inserted a value for key", ->
-      it "Does not treat the text as beginning a key", ->
+      it "Recognizes when the user has NOT yet begun to type/edit a cite key", ->
         citeTexts = [ "[@Fallows1997; "
-            , "[ @Richards2014;"
-            , "[ @Gupta "
-            , "[@Chemler2005 and others; @Suzuki1992; ]"
+                    , "[ @Richards2014;"
+                    , "[ @Gupta "
+                    , "[@Chemler2005 and others; @Suzuki1992; "
         ]
         citeTexts.map(
           (currentValue) ->


Fixed correct ticket status icon.

Fixed correct ticket status icon.

--- app/assets/javascripts/app/controllers/navigation.js.coffee
+++ app/assets/javascripts/app/controllers/navigation.js.coffee
@@ -167,7 +167,7 @@
                 data =
                   display:    "##{ticket.number} - #{ticket.title}"
                   id:         ticket.id
-                  class:      "level-1 ticket-popover"
+                  class:      "level-#{ticket.level()} ticket-popover"
                   url:        ticket.uiUrl()
                   iconClass:  "priority"
                 area.result.push data


Don't render the bar when window.performance isn't available

/cc #9

--- app/assets/javascripts/glimpse/views/performance_bar.coffee
+++ app/assets/javascripts/glimpse/views/performance_bar.coffee
@@ -29,7 +29,7 @@
   # Create a new PerformanceBar view bound to a given element. The el and width
   # options should be provided here.
   constructor: (options={}) ->
-    @el = $('.performance-bar')
+    @el = $('#glimpse-view-performance-bar .performance-bar')
     @[k] = v for k, v of options
     @width  ?= @el.width()
     @timing ?= window.performance.timing
@@ -161,4 +161,7 @@
   , 0

 $ ->
-  renderPerformanceBar()
+  if window.performance
+    renderPerformanceBar()
+  else
+    $('#glimpse-view-performance-bar').remove()


Tests: merge multiple application deletions into a single call

Change-type: patch

--- tests/integration/models/device.spec.coffee
+++ tests/integration/models/device.spec.coffee
@@ -1818,11 +1818,20 @@
 					@deviceInfo = deviceInfo

 		afterEach ->
-			Bluebird.map [
-				@application1.id
-				@application2.id
-				@application3.id
-			], balena.models.application.remove
+			balena.pine.delete({
+				resource: 'application',
+				options: {
+					$filter: {
+						id: {
+							$in: [
+								@application1.id
+								@application2.id
+								@application3.id
+							]
+						}
+					}
+				}
+			})

 		describe 'balena.models.device.move()', ->

@@ -1876,10 +1885,19 @@
 					@deviceInfo = deviceInfo

 		afterEach ->
-			Bluebird.map [
-				@application1.id
-				@application2.id
-			], balena.models.application.remove
+			balena.pine.delete({
+				resource: 'application',
+				options: {
+					$filter: {
+						id: {
+							$in: [
+								@application1.id
+								@application2.id
+							]
+						}
+					}
+				}
+			})

 		describe 'balena.models.device.move()', ->

@@ -1917,8 +1935,16 @@
 				@devices = devices

 		after ->
-			Bluebird.props _.mapValues(@apps, 'id'),
-				balena.models.application.remove
+			balena.pine.delete({
+				resource: 'application',
+				options: {
+					$filter: {
+						id: {
+							$in: _.map(@apps, 'id')
+						}
+					}
+				}
+			})

 		describe 'balena.models.device.move()', ->


Bring some of the tests up to date

Bring some of the tests up to date

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -397,7 +397,7 @@
         keydown '/'

         # How to simulate typing?
-        editor.commandModeInputView.input[0].value = 'def'
+        editor.commandModeInputView.editor.setText 'def'
         # How do I tell the view to simulate'enter'?
         editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'

@@ -406,7 +406,7 @@
       it "loops back around", ->
         editor.setCursorBufferPosition([3, 0])
         keydown '/'
-        editor.commandModeInputView.input[0].value = 'def'
+        editor.commandModeInputView.editor.setText 'def'
         editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'

         expect(editor.getCursorBufferPosition()).toEqual [1, 0]
@@ -414,7 +414,7 @@
       describe "repeating", ->
         beforeEach ->
           keydown '/'
-          editor.commandModeInputView.input[0].value = 'def'
+          editor.commandModeInputView.editor.setText 'def'
           editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'

         describe "the n keybinding", ->
@@ -432,14 +432,14 @@
         it "composes with operators", ->
           keydown 'd'
           keydown '/'
-          editor.commandModeInputView.input[0].value = 'def'
+          editor.commandModeInputView.editor.setText 'def'
           editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'
           expect(editor.getText()).toEqual "def\nabc\ndef\n"

         it "repeats correctly with operators", ->
           keydown 'd'
           keydown '/'
-          editor.commandModeInputView.input[0].value = 'def'
+          editor.commandModeInputView.editor.setText 'def'
           editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'

           keydown '.'
@@ -448,14 +448,14 @@
     describe "when reversed as ?", ->
       it "moves the cursor backwards to the specified search pattern", ->
         keydown '?'
-        editor.commandModeInputView.input[0].value = 'def'
+        editor.commandModeInputView.editor.setText 'def'
         editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'
         expect(editor.getCursorBufferPosition()).toEqual [3, 0]

       describe "repeating", ->
         beforeEach ->
           keydown '?'
-          editor.commandModeInputView.input[0].value = 'def'
+          editor.commandModeInputView.editor.setText 'def'
           editor.commandModeInputView.trigger 'vim-mode:command-mode-input-confirm'

         describe 'the n keybinding', ->


Remove unused code in map view

Remove unused code in map view

- Remove old playback code.
- Remove unfinished code for plotting lat/lngs from separate fields.

--- frontend/coffeescript/app/viz/map_view.coffee
+++ frontend/coffeescript/app/viz/map_view.coffee
@@ -42,35 +42,14 @@
         el: '#map-viz'
         itemView: MarkerItemView

+        # Find a column that can be plotted on the map.
+        # TODO: Support lat/lngs as separate columns.
         _detect_headers: ( fields ) ->
             @map_headers = []
             @selected_header = null

-            for field in fields
-                if field.type in [ 'geopoint' ]
-                    @map_headers.push( field )
-
-                    if not @selected_header?
-                        @selected_header = field
-
-                else if field.label.length == 3
-                    if field.label.search( 'lat' ) != -1
-                        console.log(field)
-                        @map_headers.push( field )
-                    else if field.label.search( 'lng' ) != -1
-                        console.log(field)
-                        @map_headers.push( field )
-
-            # This means there were no geopoints... attempt to find an lat/lng
-            # combo between two columns
-            # TODO: Support separate lat/lngs somehow. Perhaps introduce a merging utility?
-            # if not @selected_header?
-            #     @selected_header = {}
-            #     for field in @map_headers
-            #         if field.label.search( 'lat' ) != -1
-            #             @selected_header.lat = field
-            #         else if field.label.search( 'lng' ) != -1
-            #             @selected_header.lng = field
+            for field in fields when field.type is 'geopoint'
+                @selected_header or= field

         _geopoint: ( datum ) =>
             # Ensure there is a column with geopoint data
@@ -106,18 +85,6 @@
                     continue

                 marker = L.marker( [geopoint.lat, geopoint.lng], {icon: mapIcon} )
-                #marker.data = datum.get( 'data' )
-                #marker.timestamp = new Date( datum.get( 'timestamp' ) )
-
-                # if last_marker?
-                #     day = 1000 * 60 * 60 * 24
-                #     if marker.timestamp.getTime() - last_marker.timestamp.getTime() < day
-                #         pline = [ marker.getLatLng(), last_marker.getLatLng() ]
-                #         @connections.addLayer( L.polyline( pline ) )
-
-                #     last_marker = marker
-                # else
-                #     last_marker = marker

                 html = ''
                 for key, value of datum.get( 'data' )#marker.data
@@ -129,11 +96,6 @@

                 if datum.get( 'data' ).value?
                     heatmap_value = datum.get( 'data' ).value
-
-                #heatmapData.push(
-                #    lat: geopoint[0]
-                #    lon: geopoint[1]
-                #    value: heatmap_value )

         _resize_map: () =>
             $( '#map' ).css( { 'height': ( @$el.parent().height() - 20 ) + 'px' } )
@@ -258,147 +220,3 @@

     return DataMapView
 )
-
-### OLD PLAYBACK CODE. ###
-
-# pause_playback: (event) ->
-# if @is_paused
-#     $( '#pause' ).html( "" )
-#     @is_paused = false
-
-#     # Start up playback interval if it's not already started up.
-#     if not @playback?
-#         auto = () =>
-#             @time_step() if not @is_paused
-
-#         @playback = setInterval( auto, 1000.0 / $( '#fps' ).val() )
-
-# else
-#     $( '#pause' ).html( "" )
-#     @is_paused = true
-# # resets playback
-# reset_playback: (event) ->
-#     if @playback?
-#         clearInterval( @playback )
-#         @playback = null
-
-#     # set initial lower and upper bound of our current quantum
-#     @lower_bound = @min_time
-#     @upper_bound = @min_time + @quantum
-
-#     @step_current = 0
-
-#     $( '#current_time' ).html( '' )
-
-#     $( '#progress_bar > .bar' ).width( 0 )
-
-#     $( '#pause_btn' ).html( '<icon class="icon-play">' )
-#     @is_paused = false
-
-# clear_lines: (event) ->
-#     for i of @map._layers
-#         unless @map._layers[i]._path is `undefined`
-#             try
-#                 @map.removeLayer @map._layers[i]
-#             catch e
-#                 console.log "problem with " + e + @map._layers[i]
-
-# time_step: (event) ->
-
-#     @step_current += 1
-
-#     # If we've made it past the number steps, pause the playback and
-#     # return.
-#     if @step_current > @num_steps
-#         @is_paused = true
-#         return @
-
-#     # Go through each marker in the layer and enable/disable the
-#     # marker if it meets our timestamp constraints.
-#     last_marker = undefined
-#     @markers.eachLayer( ( layer ) =>
-
-#         if @lower_bound <= layer.timestamp <= @upper_bound
-#             layer.setOpacity( 1.0 )
-#             last_marker = layer
-#         else
-#             layer.setOpacity( 0.0 )
-#     )
-
-#     # Pan to the last marker that was enabled
-#     @map.panTo( last_marker.getLatLng() ) if last_marker?
-
-#     # Calculate the percent progress we have progressed.
-#     @progress = 100 * ( @upper_bound - @min_time ) / ( @max_time - @min_time )
-
-#     # display the range of the current quantum
-#     lower_bound_str = new Date( @lower_bound )
-#     upper_bound_str = new Date( @upper_bound )
-
-#     $( '#current_time' ).html( "#{lower_bound_str} through #{upper_bound_str}" )
-#     $( '#progress_bar > .bar' ).width( "#{@progress}%" )
-
-#     @upper_bound += @quantum
-
-#     @
-
-# time_c: (event) ->
-#     val = $("#tc_input").val()
-#     con = $("input:radio[name=const]:checked").val()
-#     time = $("input:radio[name=time]:checked").val()
-#     minutes = 1000 * 60
-#     hours = minutes * 60
-#     days = hours * 24
-#     secs = undefined
-#     for i of @map._layers
-#         unless @map._layers[i]._path is `undefined`
-#             try
-#                 @map.removeLayer @map._layers[i]
-#             catch e
-#                 console.log "problem with " + e + @map._layers[i]
-#     if con is "day"
-#         secs = days
-#     else if con is "hour"
-#         secs = hours
-#     else secs = minutes  if con is "minute"
-#     _ref5 = @data.models
-#     _j = 0
-#     _len1 = _ref5.length
-
-#     while _j < (_len1 - 1)
-#         datum = _ref5[_j]
-#         parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S").parse
-#         if time is "date"
-#             try
-#                 start = Date.parse(@data.models[_j].attributes.data.Time)
-#             catch err
-#                 start = null
-#         else
-#             start = parseDate(@data.models[_j].get("timestamp"))
-#         d = Math.floor(start / secs)
-#         geopoint = datum.get("data")[@map_headers].split(" ")
-#         @pL = []
-#         @pL.push [parseFloat(geopoint[0]), parseFloat(geopoint[1])]
-#         _k = _j + 1
-#         _len1 = _ref5.length
-
-#         while _k < _len1
-#             break  if start = null
-#             datum2 = _ref5[_k]
-#             parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S").parse
-#             if time is "date"
-#                 try
-#                     start2 = Date.parse(@data.models[_k].attributes.data.Time)
-#                 catch err
-#                     start2 = null
-#             else
-#                 start2 = parseDate(@data.models[_k].get("timestamp"))
-#             d2 = Math.floor(start2 / secs)
-#             geopoint = datum2.get("data")[@map_headers].split(" ")
-#             if val >= Math.abs(d - d2)
-#                 @pL.push [parseFloat(geopoint[0]), parseFloat(geopoint[1])]
-#                 @poly = L.polyline(@pL, @pLStyle).addTo(@map)
-#             else
-#                 break
-#             _k++
-#         _j++


Change default async action timeout to 10 seconds.

--- src/Witness/_witness/Scripts/witness/AsyncAction.coffee
+++ src/Witness/_witness/Scripts/witness/AsyncAction.coffee
@@ -41,4 +41,4 @@
 			@on.failed.raise e

-AsyncAction.defaultTimeout = 1000 # milliseconds
+AsyncAction.defaultTimeout = 10000 # milliseconds


Keeps the section progress from rounding anything down to 0% or up to 100% for the Concept Coach dashboard.

--- src/components/cc-dashboard/section-progress.cjsx
+++ src/components/cc-dashboard/section-progress.cjsx
@@ -6,8 +6,14 @@
 SectionProgress = React.createClass

   render: ->
-    percent = Math.round(@props.section.completed_percentage * 100)
-    if (percent > 100) then percent = 100
+    p = @props.section.completed_percentage
+
+    percent = switch
+      when (p < 1 and p > 0.99) then 99 # Don't round to 100% when it's not 100%!
+      when (p > 0 and p < 0.01) then 1  # Don't round to 0% when it's not 0%!
+      when (p > 1) then 100             # Don't let it go over 100%!
+      else Math.round(p * 100)
+
     completedLabel = "#{percent}%"
     completedLabel = if percent is 100 then "#{completedLabel} completed" else completedLabel


Copy players array

Copy players array

--- game.coffee
+++ game.coffee
@@ -70,7 +70,7 @@

     handle_collisions: (player) ->
       pos = player.currentLinePos()
-      for p in @players
+      for p in @players.slice()
         continue if p.name == player.name
         for pos2 in p.currentLine()
           if pos2.pos.eq(pos.pos)
@@ -129,7 +129,7 @@
       @move_players(elapsed_time, new_time)

       if Game.use_collisions
-        @handle_collisions(player) for player in @players
+        @handle_collisions(player) for player in @players.slice()

       @handle_input(new_time)
       @render_game() if @browser


update

update

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -58,9 +58,16 @@
   else
     timeline.reset_timeline()

+frameTransparent = () ->
+  $('iframe').each =>
+    var url = $(this).attr("src");
+    $(this).attr("src",url+"?wmode=transparent");
+
+
 # ajax complete init
 ajaxEnd = () ->
   responsiveWindow()
+  frameTransparent()
   $("#infbtn").hide()
   clickflag = on


Fix theme preview on domain#settings

Fix theme preview on domain#settings

--- app/assets/javascripts/domain.js.coffee
+++ app/assets/javascripts/domain.js.coffee
@@ -5,6 +5,6 @@
 $ ->
   $('input[data-toggle="theme"]').live "change", (event) ->
     new_theme = $(this).val()
-    new_theme_href = "/assets/themes/#{new_theme}.css?reload=#{Math.random()}"
+    new_theme_href = "/assets/themes/#{new_theme}/all.css?reload=#{Math.random()}"
     theme_stylesheet = $('head link[href^="/assets/themes/"]')
     theme_stylesheet.attr("href", new_theme_href)


Add a missing require.

Add a missing require.

--- application/chrome/content/wesabe/download/OFXPlayer.coffee
+++ application/chrome/content/wesabe/download/OFXPlayer.coffee
@@ -1,6 +1,7 @@
 date    = require 'lang/date'
 type    = require 'lang/type'
 extend  = require 'lang/extend'
+event   = require 'util/event'
 privacy = require 'util/privacy'
 {tryCatch, tryThrow} = require 'util/try'


Node modules koding: rate limiting: use tokens according to queue.length

--- node_modules_koding/bongo/lib/expressify.coffee
+++ node_modules_koding/bongo/lib/expressify.coffee
@@ -20,16 +20,17 @@
     bongo.metrics.sendMethodMetrics { method, duration }

-  rateLimit = (id, callback) ->
+  rateLimit = (id, weight, callback) ->

     return callback no  unless rateLimitOptions.enabled

+    weight ?= 1
     # if id start with guest- use guest rules for the user
     rateLimiter.rules = if /^guest-/.test id
     then rateLimiter.convertRules guestRules
     else rateLimiter.convertRules userRules

-    rateLimiter.incr id, (err, isRateLimited) ->
+    rateLimiter.incr id, weight, (err, isRateLimited) ->
       console.log "Rate limit error occurred: #{err}"  if err
       callback isRateLimited

@@ -71,13 +72,13 @@
             console.log "bongo.fetchClient: #{sessionToken} not found in #{userArea}"
             return res.status(500).send 'An error occcurred'

-          rateLimit client.username, (isRateLimited) ->
+          rateLimit client.username, queue.length, (isRateLimited) ->
             return res.status(429).send 'Rate limit exceeded'  if isRateLimited
             process req, client, sendResponse

     else
       # use sessionToken as id for rate limiting
-      rateLimit sessionToken, (isRateLimited) ->
+      rateLimit sessionToken, queue.length, (isRateLimited) ->
         return res.status(429).send 'Rate limit exceeded'  if isRateLimited
         process req, sendResponse


Remove enter submit form suppression, was breaking rest of site

--- app/assets/javascripts/purchases.coffee
+++ app/assets/javascripts/purchases.coffee
@@ -65,16 +65,6 @@

 $(document).on "click", "#print-purchase", (event) ->
   printOrder()
-
-#########################
-# Return/Enter Escaping #
-#########################
-
-$(document).on "keyup keypress", (event) ->
-  keyCode = event.keyCode || event.which
-  if keyCode == 13
-    event.preventDefault()
-    return false

 #############################
 # Vendor selection and info #


Template rendering functionality.

Template rendering functionality.

--- app/assets/javascripts/pwn-fx.js.coffee
+++ app/assets/javascripts/pwn-fx.js.coffee
@@ -4,6 +4,97 @@
 # The author sorely misses Rails' AJAX helpers such as observe_field. This
 # library provides a replacement that adheres to the new philosophy of
 # unobtrusive JavaScript triggered by HTML5 data- attributes.
+
+
+# The class of the singleton instance that tracks all the effects on the page.
+class PwnFxClass
+  # Creates an instance that isn't aware of any effects.
+  #
+  # After defining an effect class, call registerEffect on this instance to make
+  # it aware of the effect.
+  constructor: ->
+    @effects = []
+    @effectsByName = {}
+
+  # Wires JS to elements with data-pwnfx attributes.
+  #
+  # @param [DOMElement] root the element whose content is wired; use document at
+  #                          load time
+  wire: (root) ->
+    for effect in @effects
+      attrName = effect[0]
+      effectClass = effect[1]
+      doneAttrName = "#{attrName}-done"
+      attrSelector = "[#{attrName}]"
+      for element in document.querySelectorAll(attrSelector)
+        attrValue = element.getAttribute attrName
+        continue unless attrValue
+        element.removeAttribute attrName
+        element.setAttribute doneAttrName, attrValue
+        new effectClass element, attrValue
+    null
+
+  # Registers a PwnFx effect.
+  #
+  # @param [String] attrName string following data-pwnfx- in the effect's
+  #                          attribute names
+  # @param klass the class that wraps the effect's implementation
+  registerEffect: (attrPrefix, klass) ->
+    if @effectsByName[attrPrefix]
+      raise "Effect name {attrPrefix} already registered"
+    @effects.push [attrPrefix, klass]
+
+# Singleton instance.
+PwnFx = new PwnFxClass
+
+
+# Moves an element using data-pwnfx-move.
+class PwnFxMove
+  constructor: (element, identifier) ->
+    target = document.querySelector "[data-pwnfx-move-target=\"#{identifier}\"]"
+    target.appendChild element
+
+PwnFx.registerEffect 'data-pwnfx-move', PwnFxMove
+
+
+# Renders the contents of a template into a DOM element.
+#
+# Attributes:
+#   data-pwnfx-render: identifier for the render operation
+#   data-pwnfx-render-where: insertAdjacentHTML position argument; can be
+#       beforebegin, afterbegin, beforeend, afterend; defaults to beforeend
+#   data-pwnfx-render-randomize: regexp pattern whose matches will be replaced
+#       with a random string; useful for generating unique IDs
+#   data-pwnfx-render-target: set on the element(s) receiving the rendered HTML;
+#       set to the identifier in data-pwnfx-render
+#   data-pwnfx-render-source: set on the <script> tag containing the source HTML
+#       to be rendered; set to the identifier in data-pwnfx-render
+class PwnFxRender
+  constructor: (element, identifier) ->
+    sourceSelector = "script[data-pwnfx-render-source=\"#{identifier}\"]"
+    targetSelector = "[data-pwnfx-render-target=\"#{identifier}\"]"
+    insertionPoint = element.getAttribute('data-pwnfx-render-where') ||
+                     'beforeend'
+    randomizedPatten = element.getAttribute('data-pwnfx-render-randomize')
+    if randomizedPatten
+      randomizeRegExp = new RegExp(randomizedPatten, 'g')
+    else
+      randomizeRegExp = null
+
+    onClick = (event) ->
+      source = document.querySelector sourceSelector
+      html = source.innerHTML
+      if randomizeRegExp
+        randomId = 'r' + Date.now() + '_' + Math.random()
+        html = html.replace randomizeRegExp, randomId
+      for element in document.querySelectorAll(targetSelector)
+        element.insertAdjacentHTML insertionPoint, html
+      event.preventDefault()
+      false
+    element.addEventListener 'click', onClick
+
+PwnFx.registerEffect 'data-pwnfx-render', PwnFxRender
+

 # Fires off an AJAX request (almost) every time when an element changes.
 #
@@ -64,7 +155,10 @@
       return element if element.nodeName == 'FORM'
       element = element.parentNode
     null
-
+
+PwnFx.registerEffect 'data-pwnfx-refresh-url', PwnFxRefresh
+
+
 # Shows elements conditionally, depending on whether some inputs' values match.
 #
 # Element attributes:
@@ -103,39 +197,8 @@
     element.addEventListener 'change', onChange
     element.addEventListener 'keydown', onChange
     element.addEventListener 'keyup', onChange
-
-# Moves an element using data-pwnfx-move.
-class PwnFxMove
-  constructor: (element, identifier) ->
-    targetSelector =
-    target = document.querySelector "[data-pwnfx-move-target=\"#{identifier}\"]"
-    target.appendChild element
-
-# Renders the contents of a template into a DOM element.
-#
-# Attributes:
-#   data-pwnfx-render: identifier for the render operation
-#   data-pwnfx-render-where: insertAdjacentHTML position argument; can be
-#       beforebegin, afterbegin, beforeend, afterend; defaults to beforeend
-#   data-pwnfx-render-target: set on the element(s) receiving the rendered HTML;
-#       set to the identifier in data-pwnfx-render
-#   data-pwnfx-render-source: set on the <script> tag containing the source HTML
-#       to be rendered; set to the identifier in data-pwnfx-render
-class PwnFxRender
-  constructor: (element, identifier) ->
-    sourceSelector = "script[data-pwnfx-render-source=\"#{identifier}\"]"
-    targetSelector = "[data-pwnfx-render-target=\"#{identifier}\"]"
-    insertionPoint = element.getAttribute('data-pwnfx-render-where') ||
-                     'beforeend'
-
-    onClick = (event) ->
-      source = document.querySelector sourceSelector
-      html = source.innerHTML
-      for element in document.querySelectorAll(targetSelector)
-        element.insertAdjacentHTML insertionPoint, html
-      event.preventDefault()
-      false
-    element.addEventListener 'click', onClick
+
+PwnFx.registerEffect 'data-pwnfx-confirm', PwnFxConfirm

 # Shows / hides elements when an element is clicked or checked / unchecked.
@@ -177,33 +240,11 @@
       element.addEventListener 'change', onChange
       onChange()

-
-# List of effects.
-pwnEffects = [
-  ['data-pwnfx-move', PwnFxMove],
-  ['data-pwnfx-render', PwnFxRender],
-  ['data-pwnfx-refresh-url', PwnFxRefresh],
-  ['data-pwnfx-confirm', PwnFxConfirm],
-  ['data-pwnfx-reveal', PwnFxReveal]
-]
-
-# Wires JS to elements with data-pwnfx attributes.
-#
-# @param [DOMElement] root the element whose content is wired; use document at
-#                          load time
-window.PwnFx = (root) ->
-  for effect in pwnEffects
-    attrName = effect[0]
-    effectClass = effect[1]
-    doneAttrName = "#{attrName}-done"
-    attrSelector = "[#{attrName}]"
-    for element in document.querySelectorAll(attrSelector)
-      attrValue = element.getAttribute attrName
-      continue unless attrValue
-      element.removeAttribute attrName
-      element.setAttribute doneAttrName, attrValue
-      new effectClass element, attrValue
-  null
-
-# Honor data-pwnfx attributes after the DOM is loaded.
-window.addEventListener 'load', -> window.PwnFx(document)
+PwnFx.registerEffect 'data-pwnfx-reveal', PwnFxReveal
+
+
+# Export the PwnFx instance.
+window.PwnFx = PwnFx
+
+# Wire up the entire DOM after the document is loaded.
+window.addEventListener 'load', -> PwnFx.wire(document)


Skip code fences instead of trying to remove them

--- app/assets/javascripts/task_list.coffee
+++ app/assets/javascripts/task_list.coffee
@@ -136,16 +136,8 @@
 ///

 # Used to filter out code fences from the source for comparison only.
-# http://rubular.com/r/x5EwZVrloI
-# Modified slightly due to issues with JS
-codeFencesPattern = ///
-  ^`{3}           # ```
-    (?:\s*\w+)?   # followed by optional language
-    [\S\s]        # whitespace
-  .*              # code
-  [\S\s]          # whitespace
-  ^`{3}$          # ```
-///mg
+startFencesPattern = /^`{3}.*$/
+endFencesPattern = /^`{3}$/

 # Used to filter out potential mismatches (items not in lists).
 # http://rubular.com/r/OInl6CiePy
@@ -164,11 +156,16 @@
 #
 # Returns the updated String text.
 updateTaskListItem = (source, itemIndex, checked) ->
-  clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
-    replace(itemsInParasPattern, '').split("\n")
+  clean = source.replace(/\r/g, '').replace(itemsInParasPattern, '').split("\n")
   index = 0
+  inCodeBlock = false
   result = for line in source.split("\n")
-    if line in clean && line.match(itemPattern)
+    if inCodeBlock
+      if line.match(endFencesPattern)
+        inCodeBlock = false
+    else if line.match(startFencesPattern)
+      inCodeBlock = true
+    else if line in clean && line.match(itemPattern)
       index += 1
       if index == itemIndex
         line =


cleanup

cleanup

--- app/assets/javascripts/zvg/point.coffee
+++ app/assets/javascripts/zvg/point.coffee
@@ -254,10 +254,6 @@
   series_2_label_sum: (d) ->
     d3.sum((value.values.n or 0) for value in d.values)

-  point_label_sum: (d) ->
-    console.log(d)
-    return "n = ..."
-
   series_2_label_visibility: (label) ->
     if @series_3_domain().length is 1
       ""


example of accessing args to the process

--- curator.coffee
+++ curator.coffee
@@ -1,3 +1,4 @@
+console.log process.argv
 console.log 'hello'

 #pass the command line args


Add origin servers

Add origin servers

--- d16acdn.coffee
+++ d16acdn.coffee
@@ -13,6 +13,10 @@
   'directory'
   'favicon'
 ]
+versions.set 'origin servers', [
+  {url: 'd16a.aws.af.cm', name: 'd16a'}
+  {url: 'exspresso.aws.af.cm', name: 'exspresso'}
+]

 versions.listen port, (err) ->


Specify underscore as a dependency, remove optional configuration

It's assumed that if you don't specify any URLs to ignore or Users to ignore then it'll accept everything. Also, the parser for the script catalog doesn't support the "Optional Configuration" section so this doesn't appear on the site.

--- src/scripts/http-info.coffee
+++ src/scripts/http-info.coffee
@@ -3,10 +3,9 @@
 #
 # Dependencies:
 #   "jsdom": "0.2.15"
+#   "underscore": "1.3.3"
 #
 # Configuration:
-#
-# Optional Configuration:
 #   HUBOT_HTTP_INFO_IGNORE_URLS - RegEx used to exclude Urls
 #   HUBOT_HTTP_INFO_IGNORE_USERS - Comma separated list of users to ignore
 #


[#64400282] changed message to 'no errors detected'


missing isArray

missing isArray

--- addon/utils/xdash.coffee
+++ addon/utils/xdash.coffee
@@ -1,7 +1,7 @@
 `import _ from 'lodash/lodash'`
 `import Ember from 'ember'`

-{isBlank, isPresent, computed, A} = Ember
+{isBlank, isPresent, isArray, computed, A} = Ember
 {trimRight, endsWith, isEqual, isFunction, isRegExp, isString, map, every, partial, partialRight, curry, flow} = _

 consumeEnd = (string, substr) ->


Only send notifications about temporary subscriptions to remote listeners

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -550,11 +550,12 @@
                     return cb2()

             @subscription = 'subscribed'
-            # TODO: default affiliation for temporary subscriptions?
-            defaultAffiliation = @nodeConfig.defaultAffiliation or 'none'
-            unless isAffiliationAtLeast @actorAffiliation, defaultAffiliation
-                # Less than current affiliation? Bump up to defaultAffiliation
-                @affiliation = @nodeConfig.defaultAffiliation or 'member'
+            # Temporary subscriptions always have affiliation 'none'
+            unless @req.temporary
+                defaultAffiliation = @nodeConfig.defaultAffiliation or 'none'
+                unless isAffiliationAtLeast @actorAffiliation, defaultAffiliation
+                    # Less than current affiliation? Bump up to defaultAffiliation
+                    @affiliation = @nodeConfig.defaultAffiliation or 'member'

             @checkAccessModel t, cb2
         ], (err) =>
@@ -1304,14 +1305,22 @@
             , (err) =>
                 cb2 err, moderatorListeners, otherListeners
         , (moderatorListeners, otherListeners, cb2) =>
+            # Temporary subscriptions must only by notified to remote listeners
+            localReq = @req.filter (update) ->
+                not (update.type is 'subscription' and update.temporary? and update.temporary)
+
             # Send out through backends
             if moderatorListeners.length > 0
                 for listener in moderatorListeners
-                    notification = Object.create(@req)
-                    notification.listener = listener
-                    @router.notify notification
+                    myReq = if listener.indexOf("@") >= 0 then localReq else @req
+                    if myReq.length > 0
+                        notification = Object.create(myReq)
+                        notification.node = @req.node
+                        notification.listener = listener
+                        @router.notify notification
+
             if otherListeners.length > 0
-                req = @req.filter (update) =>
+                reqFilter = (update) =>
                     switch update.type
                         when 'subscription'
                             PrivilegedOperation::filterSubscription update
@@ -1319,11 +1328,14 @@
                             PrivilegedOperation::filterAffiliation update
                         else
                             yes
-                # Any left after filtering? Don't send empty
-                # notification when somebody got banned.
-                if req.length > 0
-                    for listener in otherListeners
-                        notification = Object.create(req)
+                req = @req.filter reqFilter
+                localReq = localReq.filter reqFilter
+                for listener in otherListeners
+                    myReq = if listener.indexOf("@") >= 0 then localReq else req
+                    # Any left after filtering? Don't send empty notification
+                    # when somebody got banned.
+                    if myReq.length > 0
+                        notification = Object.create(myReq)
                         notification.node = @req.node
                         notification.listener = listener
                         @router.notify notification


Add concepts translations

Add concepts translations

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -2107,20 +2107,20 @@
     advanced_css_selectors: "Selectores CSS Avanzados"
     advanced_html_attributes: "Atributos HTML Avanzados"
     advanced_html_tags: "Etiquetas HTML Avanzados"
-#    algorithm_average: "Algorithm Average"
-#    algorithm_find_minmax: "Algorithm Find Min/Max"
-#    algorithm_search_binary: "Algorithm Search Binary"
-#    algorithm_search_graph: "Algorithm Search Graph"
-#    algorithm_sort: "Algorithm Sort"
-#    algorithm_sum: "Algorithm Sum"
+    algorithm_average: "Algoritmo Promedio"
+    algorithm_find_minmax: "Algoritmo Buscar Min/Max"
+    algorithm_search_binary: "Algoritmo Búsqueda Binaria"
+    algorithm_search_graph: "Algoritmo Búsqueda Grafica"
+    algorithm_sort: "Algoritmo Ordenar"
+    algorithm_sum: "Algoritmo Suma"
     arguments: "Argumentos"
     arithmetic: "Aritmética"
-    array_2d: "Array 2D"
-#    array_index: "Array Indexing"
-#    array_iterating: "Iterating Over Arrays"
-#    array_literals: "Array Literals"
-#    array_searching: "Array Searching"
-#    array_sorting: "Array Sorting"
+    array_2d: "Matrices Bidimencionales"
+    array_index: "Indexando Matrices"
+    array_iterating: "Iterando Sobre Matrices"
+    array_literals: "Matrices Literales"
+    array_searching: "Buscando en Matrices"
+    array_sorting: "Array Sorting"
     arrays: "Arreglos"
     basic_css_rules: "Reglas CSS Básicas"
     basic_css_selectors: "Selectores CSS Básicos"
@@ -2128,94 +2128,94 @@
     basic_html_tags: "Etiquetas HTML Básicas"
     basic_syntax: "Sintaxis Básica"
     binary: "Binario"
-#    boolean_and: "Boolean And"
-#    boolean_equality: "Boolean Equality"
-#    boolean_greater_less: "Boolean Greater/Less"
-#    boolean_logic_shortcircuit: "Boolean Logic Shortcircuiting"
-#    boolean_not: "Boolean Not"
-#    boolean_operator_precedence: "Boolean Operator Precedence"
+    boolean_and: "Booleanos Y"
+    boolean_equality: "Booleano Equidad"
+    boolean_greater_less: "Booleano Mayor/Menor que"
+    boolean_logic_shortcircuit: "Booleano Cortocircuito Lógico"
+    boolean_not: "Booleano Negación"
+    boolean_operator_precedence: "Booleano Precedencia de Operadores"
     boolean_or: "Booleano O"
     bootstrap: "Bootstrap"
     break_statements: "Sentencias Break"
     classes: "Clases"
     continue_statements: "Sentencias Continue"
-#    dom_events: "DOM Events"
-#    dynamic_styling: "Dynamic Styling"
-#    event_concurrency: "Event Concurrency"
-#    event_data: "Event Data"
-#    event_handlers: "Event Handlers"
+    dom_events: "Eventos de DOM"
+    dynamic_styling: "Estilos Dinámicos"
+    event_concurrency: "Concurrencia de Eventos"
+    event_data: "Datos de Evento"
+    event_handlers: "Manejadores de Evento"
     for_loops: "Bucle For"
-#    for_loops_nested: "Nested For Loops"
-#    for_loops_range: "For Loops Range"
+    for_loops_nested: "Bucles For Anidados"
+    for_loops_range: "Bucle For en Rangos"
     functions: "Funciones"
-#    game_ai: "Game AI"
-#    game_goals: "Game Goals"
-#    game_spawn: "Game Spawn"
+    game_ai: "IA de Juego"
+    game_goals: "Objetivos del Juego"
+    game_spawn: "Generador del Juego"
     graphics: "Gráficos"
-#    graphs: "Graphs"
-#    heaps: "Heaps"
-#    if_else_statements: "If/Else Statements"
+    graphs: "Gráfico"
+    heaps: "Montículos"
+    if_else_statements: "Sentencias If/Else"
     if_statements: "Sentencias If"
-#    if_statements_nested: "Nested If Statemnts"
-#    indexing: "Array Indexes"
-#    input_handling_flags: "Input Handling - Flags"
-#    input_handling_keyboard: "Input Handling - Keyboard"
-#    input_handling_mouse: "Input Handling - Mouse"
+    if_statements_nested: "Sentencias If Anidadas"
+    indexing: "Indizado de Arreglos"
+    input_handling_flags: "Manejo de Entradas - Flags"
+    input_handling_keyboard: "Manejo de Entradas - Keyboard"
+    input_handling_mouse: "Manejo de Entradas - Mouse"
     intermediate_css_rules: "Reglas CSS Intermedias"
     intermediate_css_selectors: "Selectores CSS Intermedios"
     intermediate_html_attributes: "Atributos HTML Intermedios"
     intermediate_html_tags: "Etiquetas HTML Intermedias"
     jquery: "jQuery"
     jquery_animations: "Animaciones jQuery"
-#    jquery_filtering: "jQuery Element Filtering"
-#    jquery_selectors: "jQuery Selectors"
-#    length: "Array Length"
-#    math_geometry: "Geometry"
+    jquery_filtering: "Filtrado de Elementos jQuery"
+    jquery_selectors: "Selectores jQuery"
+    length: "Longitud de Matrices"
+    math_geometry: "Geometría"
     math_operations: "Operaciones Matemáticas"
-#    math_trigonometry: "Trigonometry"
+    math_trigonometry: "Trigonometría"
     object_literals: "Objetos Literales"
     parameters: "Parámetros"
-#    property_access: "Accessing Properties"
-#    property_assignment: "Assigning Properties"
-#    queues: "Data Structures - Queues"
-#    reading_docs: "Reading the Docs"
+    property_access: "Acceso a Propiedades"
+    property_assignment: "Asignación de Propiedades"
+    queues: "Estructuras de Datos - Colas"
+    reading_docs: "Leyendo Documentos"
     recursion: "Recursividad"
-#    return_statements: "Return Statements"
-#    stacks: "Data Structures - Stacks"
+    return_statements: "Sentencias de Return"
+    stacks: "Estructuras de Datos - Pilas"
     strings: "Cadenas"
-#    strings_concatenation: "String Concatenation"
-#    strings_substrings: "Substring"
-#    trees: "Data Structures - Trees"
+    strings_concatenation: "Concatenación de Cadenas"
+    strings_substrings: "Subcadenas"
+    trees: "Estructuras de Datos - Arboles"
     variables: "Variables"
     vectors: "Vectores"
-#    while_condition_loops: "While Loops with Conditionals"
-#    while_loops_simple: "While Loops"
-#    while_loops_nested: "Nested While Loops"
-#    xy_coordinates: "Cartesian Coordinates"
+    while_condition_loops: "Bucles While con Condicionales"
+    while_loops_simple: "Bucles While"
+    while_loops_nested: "Bucles While Anidados"
+    xy_coordinates: "Coordenadas Cartesianas"
     advanced_strings: "Cadenas - Avanzado" # Rest of concepts are deprecated
     algorithms: "Algoritmos"
     boolean_logic: "Lógica Booleana"
-#    basic_html: "Basic HTML"
-#    basic_css: "Basic CSS"
-#    basic_web_scripting: "Basic Web Scripting"
-#    intermediate_html: "Intermediate HTML"
-#    intermediate_css: "Intermediate CSS"
-#    intermediate_web_scripting: "Intermediate Web Scripting"
-#    advanced_html: "Advanced HTML"
-#    advanced_css: "Advanced CSS"
-#    advanced_web_scripting: "Advanced Web Scripting"
+    basic_html: "HTML Básico"
+    basic_css: "CSS Básico"
+    basic_web_scripting: "Web Scripting Básico"
+    intermediate_html: "HTML Intermedio"
+    intermediate_css: "CSS Intermedio"
+    intermediate_web_scripting: "Web Scripting Intermedio"
+    advanced_html: "HTML Avanzado"
+    advanced_css: "CSS Avanzado"
+    advanced_web_scripting: "Web Scripting Avanzado"
     input_handling: "Manejo de Entradas"
     while_loops: "Bucles"
-#    place_game_objects: "Place game objects"
-#    construct_mazes: "Construct mazes"
-#    create_playable_game: "Create a playable, sharable game project"
-#    alter_existing_web_pages: "Alter existing web pages"
-#    create_sharable_web_page: "Create a sharable web page"
-#    basic_input_handling: "Basic Input Handling"
-#    basic_game_ai: "Basic Game AI"
-#    basic_javascript: "Basic JavaScript"
-#    basic_event_handling: "Basic Event Handling"
-#    create_sharable_interactive_web_page: "Create a sharable interactive web page"
+    place_game_objects: "Poner objetos en el juego"
+    construct_mazes: "Construir laberintos"
+    create_playable_game: "Crear un proyecto de juego jugable y compartible"
+    alter_existing_web_pages: "Editar página web existente"
+    create_sharable_web_page: "Crear página web compartible"
+    basic_input_handling: "Manejo de Entradas Básico"
+    basic_game_ai: "IA de Juego Básico"
+    basic_javascript: "JavaScript Básico"
+    basic_event_handling: "Manejo de Eventos Básico"
+    create_sharable_interactive_web_page: "Crear página web interactiva compartible"

   delta:
     added: "Agregado"