Bring over comments from JS to CoffeeScript source by hand. This breaks a number of unit tests.

--- src/control.coffee
+++ src/control.coffee
@@ -1,7 +1,53 @@
+###
+QuickUI
+Version 0.8.9
+Modular web control framework
+http:#quickui.org/
+
+Copyright (c) 2009-2012 Jan Miksovsky
+Licensed under the MIT license.
+###
+
+###
+Control JSON routines.
+###
+
+###
+Create the control from the given JSON. This will be of three forms.
+The first form creates a control:
+
+ {
+     control: "MyButton",
+     id: "foo",
+     content: "Hello, world."
+ }
+
+The special "control" property determines the class of the control.
+The second form creates a plain HTML element:
+
+ {
+     html: "<div/>",
+     id: "foo"
+ }
+
+The html can be any HTML string supported by jQuery. It can also be an HTML
+tag singleton without braces: e.g., "div" instead of needing "<div>" or
+"<div/>". In normal jQuery, a tag like "div" would be a selector, and would
+not create an element. But in the context of creating controls, it seems
+more useful to interpret this to create an element of the indicated type.
+
+The "id" property determines the ID *andhas the side effect of creating
+an element reference so the logical parent can find that control later.
+The remainder of the JSON properties are invoked as setters on the new
+control.
+
+The third form is any other JSON dictionary object, returned as is.
+###
 evaluateControlJson = (json, logicalParent) ->
+  # Get the first key in the JSON.
   for firstKey of json
     break
-  return json  if firstKey isnt "html" and firstKey isnt "control"
+  return json  if firstKey isnt "html" and firstKey isnt "control"  # Regular object, return as is.
   reservedKeys = {}
   reservedKeys[firstKey] = true
   stripped = copyExcludingKeys(json, reservedKeys)
@@ -9,11 +55,12 @@
   control = undefined
   if firstKey is "html"
     html = json.html
-    html = "<" + html + ">"  if /^\w+$/.test(html)
+    html = "<" + html + ">"  if /^\w+$/.test(html)              # HTML tag singleton. Map tag like "div" to "<div>".
     control = Control(html).properties(properties)
   else
     control = Control.getClass(json.control).create(properties)
   if json.id
+    # Create an element reference function on the parent's class.
     logicalParentClass = logicalParent.constructor
     elementReference = "$" + json.id
     unless logicalParentClass::[elementReference]
@@ -21,80 +68,211 @@
         @referencedElement elementReference, elements
     logicalParent.referencedElement elementReference, control
   control
+
+###
+For each key in the given JSON object, evaluate its value.
+
+If the JSON is a scalar value (e.g., a string) or array, this will implicitly
+be taken as a content property. E.g., a json argument of "Hello" would have
+the same as { content: "Hello" }.
+###
 evaluateControlJsonProperties = (json, logicalParent) ->
+  # Scalar value or array; take this as the content property.
   json = content: json  unless $.isPlainObject(json)
   properties = {}
   for key of json
     properties[key] = evaluateControlJsonValue(json[key], logicalParent)
   properties
+
+
+###
+Determine the value of the given JSON object found during the processing
+of control JSON.
+
+If the supplied json is a JavaScript object, it will be treated as a control
+and created from that object's properties.
+If it's an array, its items will be mapped to their values using this same
+function.
+Otherwise, the object is returned as is.
+
+The "logical parent" is the control whose JSON defined the elements being
+created. The logical parent for a given element may not be the element's
+immediate parent in the DOM; it might be higher up.
+###
 evaluateControlJsonValue = (value, logicalParent) ->
   result = undefined
   if $.isArray(value)
+        # Recursively process each member of the array.
     result = []
     i = 0

     while i < value.length
       item = value[i]
       itemValue = evaluateControlJsonValue(item, logicalParent)
-      itemValue = itemValue[0]  if itemValue instanceof jQuery
+      itemValue = itemValue[0]  if itemValue instanceof jQuery            # When adding jQuery object to array, just add their element.
+
       result.push itemValue
       i++
   else if $.isPlainObject(value)
+        # Process JSON sub-dictionary.
     result = evaluateControlJson(value, logicalParent)
   else
+        # Return other types of values as is.
     result = value
   result
+
+###
+Utilities
+###
+
+###
+Return a copy of the given object, skipping the indicated keys.
+Keys should be provided as a dictionary with true values. E.g., the dictionary
+{ a: true, b: true } specifies that keys "a" and "b" should be excluded
+from the result.
+###
 copyExcludingKeys = (obj, excludeKeys) ->
   copy = {}
   for key of obj
     copy[key] = obj[key]  unless excludeKeys[key]
   copy
+
+###
+Return true if the document body contains the indicated element, or if
+if the element *isthe document body.
+###
 isElementInDocument = (element) ->
   !!document.body and (document.body is element or $.contains(document.body, element))
+
+###
+Find which class implements the given method, starting at the given
+point in the class hierarchy and walking up.
+
+At each level, we enumerate all class prototype members to search for a
+function identical to the method we're looking for.
+
+Returns the class that implements the function, and the name of the class
+member that references it. Returns null if the class was not found.
+###
 findMethodImplementation = (methodFn, classFn) ->
+    # See if this particular class defines the function.
   prototype = classFn::
   for key of prototype
     if prototype[key] is methodFn
+            # Found the function implementation.
+            # Check to see whether it's really defined by this class,
+            # or is actually inherited.
       methodInherited = (if classFn.superclass then prototype[key] is classFn.superclass::[key] else false)
       unless methodInherited
+                # This particular class defines the function.
         return (
           classFn: classFn
           fnName: key
         )
+    # Didn't find the function in this class.
+    # Look in parent classes (if any).
   (if classFn.superclass then findMethodImplementation(methodFn, classFn.superclass) else null)
+
+###
+QuickUI "control" jQuery extension to create and manipulate
+controls via a regular jQuery instance.
+
+Usage:
+
+$( element ).control()
+  Returns the control that was created on that element.
+
+$( element ).control( { content: "Hello" } );
+  Sets the content property of the control at this element.
+
+$( element ).control( MyControlClass );
+  Creates a new instance of MyControlClass around the element(s).
+
+$( element ).control( MyControlClass, { content: "Hello" } );
+  Creates new control instance(s) and sets its (their) content property.
+
+NOTE: the forms that create new control instances may return a jQuery array
+of elements other than the ones which were passed in. This occurs whenever
+the control class wants a different root tag than the tag on the supplied
+array of elements.
+###
 $.fn.control = (arg1, arg2) ->
   if arg1 is `undefined`
+        # Return the controls bound to these element(s), cast to the correct class.
     $cast = Control(this).cast(jQuery)
     (if ($cast instanceof Control) then $cast else null)
   else if $.isFunction(arg1)
+        # Create a new control around the element(s).
     controlClass = arg1
     properties = arg2
     controlClass.createAt this, properties
   else
+        # Set properties on the control(s).
     Control(this).cast().properties arg1

+###
+Selector for ":control": reduces the set of matched elements to the ones
+which are controls.
+
+With this, $foo.is(":control") returns true if at least one element in $foo
+is a control, and $foo.filter(":control") returns just the controls in $foo.
+###
 $.expr[":"].control = (elem) ->
   controlClass = Control(elem)._controlClass()
   (if controlClass then controlClass is Control or controlClass:: instanceof Control else false)

+###
+Control subclass of jQuery.
+This is used as the base class for all QuickUI controls.
+###
 Control = $.sub()
 $.extend Control,
+
+  ###
+  Given an array of functions, repeatedly invoke them as a chain.
+
+  This function allows the compact definition of getter/setter functions
+  for Control classes that are delegated to aspects of the control or
+  elements within its DOM.
+
+  For example:
+
+      MyControl.prototype.foo = Control.chain( "$foo", "content" );
+
+  will create a function foo() on all MyControl instances that sets or gets
+  the content of the elements returned by the element function $foo(),
+  which in turn likely means any element with id "#foo" inside the control.
+
+  The parameters to chain are the names of functions that are invoked in
+  turn to produce the result. The last parameter may be an optional side
+  effect function that will be invoked whenever the chain function is
+  invoked as a setter.
+
+  The function names passed as parameters may also define an optional
+  string-valued parameter that will be passed in. So chain( "css/display" )
+  creates a curried setter/getter function equivalent to css("display", value ).
+  ###
   chain: ->
+        # Check for a side effect function as last parameter.
     args = arguments
     sideEffectFn = undefined
     if $.isFunction(args[args.length - 1])
+            # Convert arguments to a real array in order to grab last param.
       args = [].slice.call(arguments)
       sideEffectFn = args.pop()
+        # Identify function names and optional parameters.
     functionNames = []
     functionParams = []
     i = 0
     length = args.length

     while i < length
+            # Check for optional parameter.
       parts = arguments[i].split("/")
       functionNames[i] = parts.shift()
       functionParams[i] = parts
       i++
+        # Generate a function that executes the chain.
     chain = (value) ->
       result = this
       i = 0
@@ -103,6 +281,7 @@
       while i < length
         fn = result[functionNames[i]]
         params = functionParams[i]
+                    # Invoke last function as setter.
         params = params.concat([ value ])  if value isnt `undefined` and i is length - 1
         if fn is `undefined`
           message = "Control class \"" + @className() + "\" tried to chain to an undefined getter/setter function \"" + functionNames[i] + "\"."
@@ -110,31 +289,84 @@
         result = fn.apply(result, params)
         i++
       if value is `undefined`
+                # Chain invoked as getter.
         result
       else
+                    # Carry out side effect.
         sideEffectFn.call this, value  if sideEffectFn
         this

+  ###
+  A class' "classHierarchy" member reflects the names of all classes
+  in its class hierarchy, from most specific to most general class.
+  This member is automatically built by Control.subclass().
+
+  Example: If a control class Foo has superclasses Bar and Control,
+  this member will be "Foo Bar Control".
+  ###
   classHierarchy: "Control"
+
+  ###
+  Each control class knows its own name.
+  We'd prefer to use "name" for this, but something (the browser? jQuery?)
+  creates a "name" property on jQuery instances that's unchangeable.
+  ###
   className: "Control"
+
+  ###
+  Create an instance of this control class around a specific element (or
+  multiple instances around a set of elements).
+  ###
   create: (properties) ->
     @createAt null, properties

+  ###
+  Create instance(s) of this control class around the given target(s).
+
+  If the tag associated with the given class differs from the tag on the
+  target(s), a new element (or set of elements) will be created and used to
+  replace the existing element(s). E.g., if one creates a button-based
+  control on a div, the exiting div will get replaced with a button element.
+  This will work for any existing element other than the document body,
+  which will of course be left as a body element. Event handlers or CSS
+  classes on the old element(s) will not be transferred to the new one(s).
+
+  If properties are supplied, they will be set on the new controls.
+  If the properties argument is a single string, it will be passed to
+  the controls' content() property.
+  ###
   createAt: (target, properties) ->
     defaultTarget = "<" + @::tag + "/>"
+        # Instantiate the control class.
     $controls = undefined
     oldContents = undefined
     if target is null
+            # Create a default element.
       $controls = this(defaultTarget)
       oldContents = []
     else
+            # Grab the existing contents of the target elements.
       $controls = this(target)
       oldContents = $controls._significantContents()
       existingTag = $controls[0].nodeName.toLowerCase()
+                # Tags don't match; replace with elements with the right tag.
       $controls = @_replaceElements($controls, this(defaultTarget))  if existingTag isnt @::tag.toLowerCase() and existingTag isnt "body"
+            # Plain string becomes control content
     properties = content: properties  if typeof properties is "string"
+            # Save a reference to the controls' class.
+
+            # Apply all class names in the class hierarchy as style names.
+            # This lets the element pick up styles defined by those classes.
+
+            # Render controls as DOM elements.
+
+            # Pass in the target's old contents (if any).
+
+            # Set any requested properties.
     $controls._controlClass(this).addClass(@classHierarchy).render().propertyVector("content", oldContents).properties properties
+        # Apply generic style if class supports that.
     $controls.generic true  if @genericIfClassIs is @className
+        # Tell the controls they're ready.
     i = 0
     length = $controls.length

@@ -143,9 +375,16 @@
       i++
     $controls

+  ###
+  Given a value, returns a corresponding class:
+  - A string value returns the global class with that string name.
+  - A function value returns that function as is.
+  - An object value returns a new anonymous class created from that JSON.
+  ###
   getClass: (value) ->
     classFn = undefined
     if value is null or value is ""
+            # Special cases used to clear class-valued properties.
       classFn = null
     else if $.isFunction(value)
       classFn = value
@@ -156,9 +395,18 @@
       throw "Unable to find a class called \"" + value + "\"."  unless classFn
     classFn

+    # Return true if the given element is a control.
   isControl: (element) ->
     Control(element).control() isnt `undefined`

+  ###
+  Return a function that applies another function to each control in a
+  jQuery array.
+
+  If the inner function returns a defined value, then the function is
+  assumed to be a property getter, and that result is return immediately.
+  Otherwise, "this" is returned to permit chaining.
+  ###
   iterator: (fn) ->
     ->
       args = arguments
@@ -169,24 +417,37 @@
           iteratorResult = result
           false

-      (if (iteratorResult isnt `undefined`) then iteratorResult else this)
-
+  # Getter
+
+  # Method or setter
+        (if (iteratorResult isnt `undefined`) then iteratorResult else this)
+
+  ###
+  Generic factory for a property getter/setter.
+  ###
   property: (sideEffectFn, defaultValue, converterFunction) ->
     backingPropertyName = "_property" + Control.property._symbolCounter++
     (value) ->
       result = undefined
       if value is `undefined`
+                # Getter
         result = @data(backingPropertyName)
         (if (result is `undefined`) then defaultValue else result)
       else
+                # Setter. Allow chaining.
         @eachControl (index, $control) ->
           result = (if (converterFunction) then converterFunction.call($control, value) else value)
           $control.data backingPropertyName, result
           sideEffectFn.call $control, result  if sideEffectFn

+  ###
+  Create a subclass of this class.
+  ###
   subclass: (json) ->
     superclass = this
     newClass = superclass.sub()
+        # $.sub uses $.extend to copy properties from super to subclass, so
+        # we have to blow away class properties that shouldn't have been copied.
     delete newClass._initializeQueue

     if json
@@ -194,7 +455,13 @@
         newClass.extend
           className: json.className
           classHierarchy: json.className + " " + superclass.classHierarchy
-      newClass.genericIfClassIs = json.className  if json.genericSupport
+      if json.genericSupport
+        newClass.genericIfClassIs = json.className
+        ###
+             Create a copy of the JSON that doesn't include the reserved keys
+             (which go on the class). We use the remaining JSON values as
+             "inherited" on the class' prototype for use by render().
+        ###
       inherited = copyExcludingKeys(json,
         className: true
         genericSupport: true
@@ -205,20 +472,32 @@
         inherited: inherited
         tag: json.tag
     else
+            # Clear copied or inherited values.
       newClass.className = `undefined`
       newClass::inherited = null
     newClass

+  ###
+  An element may have been added to the document; see if it's a control that's
+  waiting to be notified of that via Control.prototype.inDocument().
+  ###
   _checkForElementInsertion: (event) ->
     callbacksReady = []
     i = 0
     while i < Control._elementInsertionCallbacks.length
       element = Control._elementInsertionCallbacks[i].element
       if isElementInDocument(element)
+                # The element has been inserted into the document. Move it
+                # into the list of callbacks which we can now invoke.
         callbacksReady = callbacksReady.concat(Control._elementInsertionCallbacks[i])
+                # Now remove it from the pending list. We remove it from the list
+                # *beforewe invoke the callback -- because the callback
+                # itself might do DOM manipulations that trigger more DOM
+                # mutation events.
         Control._elementInsertionCallbacks.splice i, 1
       else
         i++
+            # No more elements to wait for.
     Control._stopListeningForElementInsertion()  if Control._elementInsertionCallbacks.length is 0
     i = 0
     while i < callbacksReady.length
@@ -228,36 +507,77 @@
       callback.call $control
       i++

+  ###
+  The name of the data element used to store a reference to an element's control class.
+  ###
   _controlClassData: "_controlClass"
+    # Controls waiting (via an inDocument call) to be added to the body.
   _elementInsertionCallbacks: []
+    # True if Control is currently listening for DOM insertions.
   _listeningForElementInsertion: false
+    # True if we can rely on DOM mutation events to detect DOM changes.
   _mutationEvents: ->
+        # Since the only QuickUI-supported browser that doesn't support
+        # mutation events is IE8, we just look for that. If need be, we
+        # could programmatically detect support using something like
+        # Diego Perini's NWMatcher approach.
     not $.browser.msie or parseInt($.browser.version) >= 9

+  ###
+  Replace the indicated existing element(s) with copies of the indicated
+  replacement element. During this operation, preserve element IDs.
+
+  This is used if, say, we need to convert a bunch of divs to buttons.
+  ###
   _replaceElements: ($existing, $replacement) ->
+        # Gather the existing IDs.
     ids = $existing.map((index, element) ->
       $(element).prop "id"
     )
     $new = $replacement.replaceAll($existing)
+        # Put IDs onto new elements.
     $new.each (index, element) ->
       id = ids[index]
       $(element).prop "id", ids[index]  if id and id.length > 0

     $new

+  ###
+  Start listening for insertions of elements into the document body.
+
+  On modern browsers, we use the DOMNodeInserted mutation event (which, as of
+  7/2011, had better browser coverage than DOMNodeinDocument).
+  Mutation events are slow, but the only way to reliably detect insertions
+  that are created outside the document and then later added in. The events
+  aren't used when we can avoid it, and the event is unbound once all known
+  pending controls have been added.
+
+  IE8 doesn't support mutation events, so we have to set up our own polling
+  interval to check to see whether a control has been added. Again, we avoid
+  doing this at all costs, but this apparently is the only way we can
+  determine when elements have been added in IE8.
+  ###
   _startListeningForElementInsertion: ->
     if Control._mutationEvents()
+            # Use mutation events.
       if document.body
+                # The document's ready for us to wire up mutation event handlers.
         jQuery("body").on "DOMNodeInserted", Control._checkForElementInsertion
         Control._listeningForElementInsertion = true
       else unless Control._deferredElementInsertionListening
+                # We can't sink events yet, so things get messy. We have to
+                # queue up a ready() callback that can wire up the events.
         jQuery("body").ready ->
+                    # Check pending callbacks.
           Control._checkForElementInsertion()
+                    # If any callbacks are left, start listening.
           Control._startListeningForElementInsertion()  if Control._elementInsertionCallbacks.length > 0
           Control._deferredElementInsertionListening = false

+                # Make note that we've already deferred, so we don't do it again.
         Control._deferredElementInsertionListening = true
     else
+            # Use timer
       self = this
       Control._elementInsertionInterval = window.setInterval(->
         self._checkForElementInsertion()
@@ -271,27 +591,59 @@
       window.clearInterval Control._elementInsertionInterval
       Control._elementInsertionInterval = null

+###
+More factories for getter/setters of various types.
+###
 $.extend Control.property,
+    # A boolean property.
   bool: (sideEffectFn, defaultValue) ->
+                # Convert either string or bool to bool.
     Control.property sideEffectFn, defaultValue, convertToBool = (value) ->
       String(value) is "true"

+  ###
+  A class-valued property.
+  This accepts either a function (the class) or a class name as a string.
+  ###
   class: (sideEffectFn, defaultValue) ->
     Control.property sideEffectFn, defaultValue, Control.getClass

+  # A date-valued property. Accepts a JavaScript date or parseable date string.
   date: (sideEffectFn, defaultValue) ->
     Control.property sideEffectFn, defaultValue, convertToDate = (value) ->
       (if (value instanceof Date or not value?) then value else new Date(Date.parse(value)))

+  # An integer property.
   integer: (sideEffectFn, defaultValue) ->
     Control.property sideEffectFn, defaultValue, parseInt

+  # Used to generate symbols to back new properties.
   _symbolCounter: 0

+###
+Control instance methods.
+###
 $.extend Control::,
+
+  ###
+  Get/set whether the indicated class(es) are applied to the elements.
+  This effectively combines $.hasClass() and $.toggleClass() into a single
+  getter/setter.
+  ###
   applyClass: (classes, value) ->
     (if (value is `undefined`) then @hasClass(classes) else @toggleClass(classes, String(value) is "true"))

+  ###
+  Return the array of elements cast to their closest JavaScript class ancestor.
+  E.g., a jQuery $(".foo") selector might pick up instances of control classes
+  A, B, and C. If B and C are subclasses of A, this will return an instance of
+  class A. So Control(".foo").cast() does the same thing as A(".foo"), but without
+  having to know the type of the elements in advance.
+
+  The highest ancestor class this will return is the current class, even for plain
+  jQuery objects, in order to allow Control methods (like content()) to be applied to
+  the result.
+  ###
   cast: (defaultClass) ->
     defaultClass = defaultClass or @constructor
     setClass = undefined
@@ -303,46 +655,127 @@
       elementClass = $element._controlClass() or defaultClass
       setClass = elementClass  if setClass is `undefined` or ( setClass:: ) instanceof elementClass
       i++
-    setClass = setClass or defaultClass
+    setClass = setClass or defaultClass  # In case "this" had no elements.
     setClass this

+  ###
+  See if the control's size has changed since the last time we checked and,
+  if so, trigger the sizeChanged event.
+
+  A control can use this method to let layout-performing ancestors know
+  that the control has changed its size, in case the ancestor will now
+  need to update the layout.
+  ###
   checkForSizeChange: ->
     @trigger "sizeChanged"  if @_updateSavedSize()
     this

+  ###
+  The set of classes on the control's element.
+  If no value is supplied, this gets the current list of classes.
+  If a value is supplied, the specified class name(s) are *added*
+  to the element. This is useful for allowing a class to be added
+  at design-time to an instance, e.g., <Foo class="bar"/>. The
+  resulting element will end up with "bar" as a class, as well as
+  the control's class hierarchy: <div class="Foo Control bar">.
+  ###
   class: (classList) ->
     (if (classList is `undefined`) then @attr("class") else @toggleClass(classList, true))

+  ###
+  The name of the control's class.
+  This shorthand is useful for quickly getting the name in the debugger
+  when one is holding a reference to a control.
+  ###
   className: ->
     @constructor.className

+  ###
+  Get/set the content of an HTML element.
+
+  Like $.contents(), but you can also set content, not just get it.
+  You can set content to a single item, an array of items, or a set
+  of items listed as parameters. Setting multiple items at a time
+  is an important case in compiled QuickUI markup. Input elements
+  are also handled specially: their value (val) is their content.
+
+  This function attempts to return contents in a canonical form, so
+  that setting contents with common parameter types is likely to
+  return those values back in the same form. If there is only one
+  content element, that is returned directly, instead of returning
+  an array of one element. If the element being returned is a text node,
+  it is returned as a string.
+
+  Usage:
+   $element.content("Hello")              # Simple string
+   $element.content(["Hello", "world"])   # Array
+   $element.content("Hello", "world")     # Parameters
+   Control("<input type='text'/>").content("Hi")   # Sets text value
+
+  This is used as the default implementation of a control's content
+  property. Controls can override this behavior.
+  ###
   content: (value) ->
     if value is `undefined`
+            # Getting contents. Just process first element.
       $element = @nth(0)
       result = undefined
       if $element.isInputElement()
+                # Return input element value.
         result = $element.val()
       else
+                # Return HTML contents in a canonical form.
         resultContainsStrings = false
         result = $element.contents().map((index, item) ->
           if item.nodeType is 3
+                        # Return text as simple string
             resultContainsStrings = true
             item.nodeValue
           else
             item
         )
+                    # Return the single string instead of an array.
         result = result[0]  if resultContainsStrings and result.length is 1
       result
     else
+            # Setting contents.
+
+            # Cast arguments to an array.
+
+                 # set of parameters
+
+                 # convert jQuery object to array
+
+                 # single array parameter
+                 # singleton parameter
       array = (if (arguments.length > 1) then arguments else (if value instanceof jQuery then value.get() else (if $.isArray(value) then value else [ value ])))
       @each (index, element) ->
         $element = Control(element)
         if $element.isInputElement()
+                    # Set input element value.
           $element.val value
         else
+                    # Set HTML contents.
+
+                    # We're about to blow away the contents of the element
+                    # via $empty(), but the new content value might actually
+                    # already be deeper in the element's existing content.
+                    # To ensure that data, etc., get preserved, first detach
+                    # the existing contents.
           $element.children().detach()
+                    # Use apply() to feed array to $.append() as params.
           $element.empty().append.apply $element, array

+  ###
+  The control's culture.
+  If jQuery Globalize is present, this defaults to the current culture.
+  This can be overridden on a per-control basis, e.g., for testing purposes.
+
+  Control classes can override this method to respond immediately to an
+  explicit change in culture. They should invoke their base class' culture
+  method, do whatever work they want (if the culture parameter is defined),
+  then return the result of the base class call.
+  ###
   culture: (culture) ->
     cultureDataMember = "_culture"
     controlCulture = undefined
@@ -354,6 +787,10 @@
       @data cultureDataMember, controlCulture
       this

+  ###
+  Execute a function once for each control in the array.
+  Inside the function, "this" refers to the single control.
+  ###
   eachControl: (fn) ->
     i = 0
     length = @length
@@ -365,28 +802,76 @@
       i++
     this

+  ###
+  Sets/gets whether the control is showing a generic appearance.
+  We use a property with a side-effect, rather than a binding to
+  applyClass/generic, in order for the default value to be "undefined".
+  ###
   generic: Control.property.bool((generic) ->
     @applyClass "generic", generic
   )
+
+  ###
+  Sets the generic property to true if the control's most specific
+  class (i.e., its constructor) is the indicated class. Controls that
+  want to define a generic appearance should invoke this in their
+  initialize handler, passing in the control class.
+
+  The assumption is that subclasses want to define their own appearance,
+  and therefore do *notwant an inherited generic appearance. E.g.,
+  a class Foo could ask for generic appearance, but a subclass of Foo
+  called Bar will not get the generic appearance unless it calls this
+  function via this.genericIfClassIs(Bar).
+  ###
   genericIfClassIs: (classFn) ->
     @eachControl (index, $control) ->
       $control.generic true  if $control.constructor is classFn and $control.generic() is `undefined`

+    # Allow controls have an element ID specified on them in markup.
   id: (id) ->
     @attr "id", id

+  ###
+  Invoked when the control has finished rendering.
+  Subclasses can override this to perform their own post-rendering work
+  (e.g., wiring up events).
+  ###
   initialize: ->

+  ###
+  A (new) control is asking to have a callback invoked when the control
+  has been inserted into the document body. This is useful because
+  the initialize event is often invoked before the control has actually
+  been added to the document, and therefore doesn't have height or width,
+  doesn't have any externally-imposed styles applied to it, etc.
+
+  If the control is already in the document, the callback is executed
+  immediately.
+
+  If no callback function is supplied, this returns true if all controls
+  are in the document, and false if not.
+
+  Callbacks will be invoked in reserve document order to ensure that
+  the callback of a child will be invoked before the callback of a parent.
+  This lets the parent proceed knowing that its children have already had
+  the chance to set themselves up.
+  ###
   inDocument: (callback) ->
     if callback is `undefined`
+                # The empty condition is defined as being *notin the document.
       return false  if @length is 0
+            # See if controls are in document.
       i = 0

       while i < @length
-        return false  unless isElementInDocument(this[i])
+        return false  unless isElementInDocument(this[i])                     # At least one control is not in the document.
+
         i++
+            # All controls are in the document.
       true
     else
+            # Invoke callback immediately for controls already in document,
+            # queue a callback for those which are not.
       callbacks = []
       i = 0

@@ -394,8 +879,10 @@
         $element = @nth(i)
         element = $element[0]
         if isElementInDocument(element)
+                    # Element already in document
           callback.call $element
         else
+                    # Add element to the list we're waiting to see inserted.
           callbacks.push
             element: element
             callback: callback
@@ -405,10 +892,36 @@
         Control._startListeningForElementInsertion()  unless Control._listeningForElementInsertion
       this

+  ###
+  Return true if the (first) element is an input element (with a val).
+  Note that buttons are not considered input elements, because typically
+  when one is setting their contents, one wants to set their label, not
+  their "value".
+  ###
   isInputElement: ->
     inputTags = [ "input", "select", "textarea" ]
     (if @length is 0 then false else ($.inArray(this[0].nodeName.toLowerCase(), inputTags) >= 0))

+  ###
+  Apply the indicated JSON to the control. Each key in the JSON will
+  invoke the corresponding setter function in a function chain. E.g.,
+  the JSON dictionary
+
+       {
+           foo: "Hello",
+           bar: "World"
+       }
+
+  will invoke this.foo("Hello").bar("World").
+
+  If a dictionary value is itself a JSON object, it will be reconstituted
+  into HTML, or controls, or an array.
+
+  This is similar to properties(), but that function doesn't attempt any
+  processing of the values.
+
+  The logicalParent parameter is intended for internal use only.
+  ###
   json: (json, logicalParent) ->
     logicalParent = logicalParent or this
     i = 0
@@ -420,11 +933,22 @@
       control.properties properties
       i++

+  ###
+  Experimental function like eq, but faster because it doesn't manipulate
+  the selector stack.
+  ###
   nth: (index) ->
     @constructor this[index]

+  ###
+  Save or retrieve an element associated with the control using the
+  given key. For a collection of controls, the getter maps the collection
+  to a collection of the corresponding elements.
+  ###
   referencedElement: (key, elements) ->
     if elements is `undefined`
+            # Map a collection of control instances to the given element
+            # defined for each instance.
       elements = []
       i = 0
       length = @length
@@ -434,6 +958,12 @@
         elements.push element  if element isnt `undefined`
         i++
       $result = Control(elements).cast()
+            # To make the element function $.end()-able, we want to call
+            # jQuery's public pushStack() API. Unfortunately, that call
+            # won't allow us to both a) return a result of the proper class
+            # AND b) ensure that the result of calling end() will be of
+            # the proper class. So, we directly set the internal prevObject
+            # member used by end().
       $result.prevObject = this
       $result
     else
@@ -445,13 +975,30 @@
         i++
       this

+  ###
+  Rendering a control lets each class in the control class' hierarchy,
+  starting at the *top*. Each class' "inherited" settings are passed to
+  property setters on that class' superclass. That is, each class defines
+  itself in the semantics of its superclass.
+  ###
   render: render = ->
     classFn = @constructor
     if classFn isnt Control
       superclass = classFn.superclass
+                # Superclass renders first.
+
+                # Apply the class' settings using superclass's setters.
       superclass(this).render().json @inherited, this
     this

+  ###
+  Invoke the indicated setter functions on the control to
+  set control properties. E.g.,
+
+     $c.properties( { foo: "Hello", bar: 123 } );
+
+  is shorthand for $c.foo( "Hello" ).bar( 123 ).
+  ###
   properties: (properties) ->
     for propertyName of properties
       if this[propertyName] is `undefined`
@@ -461,9 +1008,17 @@
       this[propertyName].call this, value
     this

+  ###
+  Get/set the given property on multiple elements at once. If called
+  as a getter, an array of the property's current values is returned.
+  If called as a setter, that property of each element will be set to
+  the corresponding defined member of the values array. (Array values
+  which are undefined will not be set.)
+  ###
   propertyVector: (propertyName, values) ->
     propertyFn = this[propertyName]
     if values is `undefined`
+            # Getter
       results = []
       i = 0
       length = @length
@@ -473,6 +1028,7 @@
         i++
       results
     else
+            # Setter
       i = 0
       length1 = @length
       length2 = values.length
@@ -482,100 +1038,252 @@
         i++
       this

+  ### Control has no settings that need to be applied on render.###
   inherited: null
+
+  ###
+  Sets/gets the style of matching elements.
+  This lets one specify a style attribute in QuickUI markup for a control instance;
+  the style will apply to the control's root element.
+  ###
   style: (style) ->
     @attr "style", style

+  ###
+  The tabindex of the control.
+  ###
   tabindex: (tabindex) ->
     @attr "tabindex", tabindex

+  ###
+  Replace this control with an instance of the given class and properties.
+  Unlike a normal Control.create() call, existing control contents are
+  *not* preserved. Event handlers, however, remain attached;
+  use a separate call to $.off() to remove them if desired.
+
+  If preserveClasses is true, the existing class hierarchy will be left
+  on the "class" attribute, although the class "Control" will remain the
+  rightmost class. Suppose the class hierarchy looks like
+       class="Foo Control"
+  If we're switching to class Bar, the hierarchy will end up like
+       class="Bar Foo Control"
+
+  TODO: This function have evolved to overlap quite a bit with $.control().
+  The latter's ability to preserve element content in Control.createAt()
+  perhaps should be deprecated. Callers could rely on transmute() if they
+  need to preserve existing content.
+  ###
   transmute: (newClass, preserveContent, preserveClasses, preserveEvents) ->
     classFn = Control.getClass(newClass)
     oldContents = (if preserveContent then @_significantContents() else null)
     oldClasses = (if preserveClasses then @prop("class") else null)
+        # Reset everything.
     @empty().removeClass().removeData()
     @off()  unless preserveEvents
     $controls = classFn.createAt(this)
     $controls.propertyVector "content", oldContents  if oldContents
-    $controls.removeClass("Control").addClass(oldClasses).addClass "Control"  if oldClasses
+    $controls.removeClass("Control").addClass(oldClasses).addClass "Control"  if oldClasses       # Ensures Control ends up rightmost
     $controls

+  ###
+  By default, the root tag of the control will be a div.
+  Control classes can override this: <Control name="Foo" tag="span">
+  ###
   tag: "div"
+
+  ###
+  The current version of QuickUI.
+  ###
   quickui: "0.8.9"
+
+  ###
+  Toggle the element's visibility.
+  Like $.toggle(), but if no value is supplied, the current visibility is returned
+  (rather than toggling the element's visibility).
+  ###
   visibility: (value) ->
     (if (value is `undefined`) then @is(":visible") else @toggle(String(value) is "true"))

+  ###
+  Get/set the reference for the class for these control(s).
+  ###
   _controlClass: (classFn) ->
     (if classFn then @data(Control._controlClassData, classFn) else @data(Control._controlClassData))

+  ###
+  Return the control's "significant" contents: contents which contain
+  at least one node that's something other than whitespace or comments.
+  If an element has no significant contents, return null for that element.
+  ###
   _significantContents: ->
+        # Use base implementation of content().
     contents = Control(this).propertyVector("content")
     significantContents = $.map(contents, (content) ->
-      return content  if $.trim(content).length > 0  if typeof content is "string"
+      return content  if $.trim(content).length > 0  if typeof content is "string"                     # Found significant text
+            # Content is an array
       i = 0
       length = content.length

       while i < length
         c = content[i]
         if typeof c is "string"
-          return content  if $.trim(c).length > 0
-        else return content  if c.nodeType isnt 8
+          return content  if $.trim(c).length > 0                 # Found significant text
+        # Comment node
+        else return content  if c.nodeType isnt 8                     # Found some real element
         i++
       null
     )
-    significantContents
-
+    significantContents    # Didn't find anything significant
+
+  ###
+  Call a function of the same name in a superclass.
+
+  E.g., if A is a superclass of B, then:
+
+       A.prototype.calc = function ( x ) {
+           return x 2;
+       }
+       B.prototype.calc = function ( x ) {
+           return this._super( x ) + 1;
+       }
+
+       var b = new B();
+       b.calc( 3 );         # = 7
+
+  This assumes a standard prototype-based class system in which all classes have
+  a member called "superclass" pointing to their parent class, and all instances
+  have a member called "constructor" pointing to the class which created them.
+
+  This routine has to do some work to figure out which class defined the
+  calling function. It will have to walk up the class hierarchy and,
+  if we're running in IE, do a bunch of groveling through function
+  definitions. To speed things up, the first call to _super() within a
+  function creates a property called "_superFn" on the calling function;
+  subsequent calls to _super() will use the memoized answer.
+
+  Some prototype-based class systems provide a _super() function through the
+  use of closures. The closure approach generally creates overhead whether or
+  not _super() will ever be called. The approach below adds no overhead if
+  _super() is never invoked, and adds minimal overhead if it is invoked.
+  This code relies upon the JavaScript .caller method, which many claims
+  has slow performance because it cannot be optimized. However, "slow" is
+  a relative term, and this approach might easily have acceptable performance
+  for many applications.
+  ###
   _super: _super = ->
+        # Figure out which function called us.
+
+                     # Modern browser
+
+                       # IE9 and earlier
     callerFn = (if (_super and _super.caller) then _super.caller else arguments.callee.caller)
     return `undefined`  unless callerFn
+        # Have we called super() within the calling function before?
     superFn = callerFn._superFn
     unless superFn
+            # Find the class implementing this method.
       classInfo = findMethodImplementation(callerFn, @constructor)
       if classInfo
         classFn = classInfo.classFn
         callerFnName = classInfo.fnName
+                # Go up one level in the class hierarchy to get the superfunction.
         superFn = classFn.superclass::[callerFnName]
+                # Memoize our answer, storing the value on the calling function,
+                # to speed things up next time.
         callerFn._superFn = superFn
-    (if superFn then superFn.apply(this, arguments) else `undefined`)
-
+    (if superFn then superFn.apply(this, arguments) else `undefined`)  # Invoke superfunction
+
+  ###
+  Compare element's current size with its previously recorded size.
+  If the size has not changed, return false. If the size has changed,
+  update the recorded size and return true.
+  ###
   _updateSavedSize: ->
     previousSize = @data("_size") or {}
     size =
       height: @height()
       width: @width()

-    return false  if size.height is previousSize.height and size.width is previousSize.width
+    return false  if size.height is previousSize.height and size.width is previousSize.width            # Size hasn't changed.
+
+        # Size changed; record the new size.
     @data "_size", size
     true

+###
+Layout event.
+
+Elements can use the layout event if they want to perform custom layout when
+their size changes. The layout contract is weak: it's generally triggered
+when the window size changes AND the element's size has actually changed in
+response.
+
+The layout event will *notautomatically fire if the element's size has
+changed in response to other activity, such as a child element growing in
+size. Supporting that generally would require setting up an expensive poll
+interval. However, a contained element that want to let their containers
+know about changes in the contained element's size can do so by triggering
+a layout event that will bubble up to the container.
+###
 $.event.special.layout =
+  ###
+  Add a layout event handler.
+  ###
   add: (handleObj) ->
+        # Add the element to the set of element being tracked.
     layout = $.event.special.layout
     layout._trackedElements = layout._trackedElements.add(this)
+        # Send an initial layout event when the element is in the document.
     Control(this).inDocument ->
+       ###
+             Directly invoke the handler instead of triggering the event.
+             If add() is invoked on an element that's already in the document,
+             inDocument() will fire immediately, which means the handler won't
+             be wired up yet.
+        ###
       handler = handleObj.handler
       event = new jQuery.Event("layout")
       handler.call this, event

+  ###
+  Handle the layout event.
+  ###
   handle: (event) ->
     control = Control(this)
-    return  unless control.inDocument()
-    return  unless control.checkForSizeChange()
+    return  unless control.inDocument()            # Not currently in document; no need for layout.
+
+    return  unless control.checkForSizeChange()     # Size hasn't actually changed; no need for layout.
     event.handleObj.handler.apply this, arguments

+  ###
+  Called the first time the layout event is added to an element.
+  ###
   setup: ->
+        # Are we already handling the window resize event?
     layout = $.event.special.layout
     unless layout._trackingResizeEvent
+            # Start handling window resize.
       $(window).resize ->
         layout._windowResized()

       layout._trackingResizeEvent = true

+  ###
+  The last layout event handler for an element has been removed.
+  ###
   teardown: ->
+        # Remove the control from the set of controls being tracked.
     $.event.special.layout._trackedElements = $.event.special.layout._trackedElements.not(this)

+    # The set of elements receiving layout events.
   _trackedElements: $()
+
+  ###
+  The window has been resized.
+  ###
   _windowResized: ->
+        # Trigger layout event for all elements being asked.
     $.event.special.layout._trackedElements.trigger "layout"

+# Expose Control class as a global.
 window.Control = Control


More methods in the shapeways client

More methods in the shapeways client

--- fabricator/shapeways.coffee
+++ fabricator/shapeways.coffee
@@ -1,4 +1,5 @@
 OAuth = require('oauth')
+fs = require('fs');

 API_HOST = "http://api.shapeways.com"

@@ -38,30 +39,91 @@
         callback { oauth_access_token, oauth_access_token_secret }
     )

-  postModel: (file, oauth_access_token, oauth_access_token_secret, callback) ->
+  getMaterials: (oauth_access_token, oauth_access_token_secret, callback) ->
+    @oa.get(
+      API_HOST+"/materials/v1",
+      oauth_access_token,
+      oauth_access_token_secret,
+      (error, data, response) ->
+        if error
+          callback error, null
+        if response is undefined
+          callback ('error: ' + response), null
+        callback null, JSON.parse(data)
+    )
+
+  getPrice: (options, oauth_access_token, oauth_access_token_secret, callback) ->
+    @oa.post(
+      API_HOST+"/price/v1",
+      oauth_access_token,
+      oauth_access_token_secret,
+      JSON.stringify(options),
+      (error, data, response) ->
+        if error
+          callback error, null
+        else
+          callback null, JSON.parse(data)
+    )
+
+  getModel: (modelId, oauth_access_token, oauth_access_token_secret, callback) ->
+    @oa.get(
+      API_HOST+"/models/"+modelId+"/v1",
+      oauth_access_token,
+      oauth_access_token_secret,
+      (error, data, response) ->
+        if error
+          callback error, null
+        else
+          callback null, JSON.parse(data)
+    )
+
+  postModel: (file, params, oauth_access_token, oauth_access_token_secret, callback) ->
+      # TODO: check if file already is posted to Shapeways through the API.
       model_upload = fs.readFile file, (err, fileData) =>
         fileData = encodeURIComponent fileData.toString('base64')
-
-        upload = JSON.stringify {
-          file: fileData,
-          fileName: file,
-          hasRightsToModel: 1,
-          acceptTermsAndConditions: 1,
-          isPublic: false,
-          isForSale: true,
-          isDownloadable: true
-        }
-
+        params.file = fileData;
+        params.fileName = file;
         @oa.post(
           API_HOST+"/models/v1",
           oauth_access_token,
           oauth_access_token_secret,
-          upload,
+          JSON.stringify(params),
           (error, data, response) ->
             if error
-              callback err, null
+              callback error, null
             else
-              callback null, data
+              callback null, JSON.parse(data)
         )

+  updateModel: (modelId, params, oauth_access_token, oauth_access_token_secret, callback) ->
+    @oa.put(
+      API_HOST+"/models/"+modelId+"/info/v1",
+      oauth_access_token,
+      oauth_access_token_secret,
+      JSON.stringify(params),
+      (error, data, response) ->
+        if error
+          callback error, null
+        else
+          callback null, JSON.parse(data)
+    )
+
+  addToCart: (modelId, materialId, quantity, oauth_access_token, oauth_access_token_secret, callback) ->
+    params = {
+      "modelId": modelId,
+      "materialId": materialId,
+      "quantity": quantity
+    }
+    @oa.post(
+      API_HOST+"/orders/cart/v1",
+      oauth_access_token,
+      oauth_access_token_secret,
+      JSON.stringify(params),
+      (error, data, response) ->
+        if error
+          callback error, null
+        else
+          callback null, JSON.parse(data)
+    )
+
 module.exports = ShapewaysClient


Fix memoize helper

Fix memoize helper

--- src/trix/utilities/helpers.coffee
+++ src/trix/utilities/helpers.coffee
@@ -4,8 +4,11 @@
   defer: (fn) ->
     setTimeout fn, 1

-  memoize: (fn) -> ->
-    this["_memos#{memos++}"] ?= fn.apply(this, arguments)
+  memoize: (fn) ->
+    memo = memos++
+    ->
+      @memos ?= {}
+      @memos[memo] ?= fn.apply(this, arguments)

   capitalize: (string) ->
     string.charAt(0).toUpperCase() + string.substring(1)


Enable audible notifications

Enable audible notifications

--- src/ui/views/notifications.coffee
+++ src/ui/views/notifications.coffee
@@ -59,6 +59,7 @@
             message: text
             wait: true
             sender: 'com.github.yakyak'
+            sound: true
         , (err, res) -> if res?.trim() == 'Activate'
           action 'appfocus'
           action 'selectConv', c


Render has_many using list view.

Render has_many using list view.

--- js/templates/src/template.coffee
+++ js/templates/src/template.coffee
@@ -142,9 +142,9 @@

 		relationship: (entity, rel_name, relationship, view = "view") ->
 			if relationship.type is "has_many"
-				rel = $()
+				rel = find("..#{entity._type()}.#{rel_name}.#{view}");
 				rels = entity[rel_name]()
-				rel.append(render.entity(ent)) for ent in rels
+				rel.append(render.entity(ent, 'list')) for ent in rels
 				return rel
 			else
 				return render.entity(entity[rel_name]())


Remove debug statement

Remove debug statement

--- src/ext/components/model-input.coffee
+++ src/ext/components/model-input.coffee
@@ -338,7 +338,6 @@
   _exception = signal null
   _algorithms = signal []
   _algorithm = signal null
-  lift _algorithm, (a) -> debug "ALGO CHANGED TO " + a
   _canCreateModel = lift _algorithm, (algorithm) -> if algorithm then yes else no

   _modelForm = signal null


Added show_on_startup config item

Added show_on_startup config item

--- lib/atomatigit.coffee
+++ lib/atomatigit.coffee
@@ -16,6 +16,12 @@
       type: 'string'
       default: ''
       order: 2
+    show_on_startup:
+      title: 'Show on Startup'
+      description: 'Check this if you want atomatigit to show up when Atom is loaded'
+      type: 'boolean'
+      default: false
+      order: 3

   repo: null
   repoView: null


Fix Attachment not correctly cleaning up listener

--- lib/attachment.coffee
+++ lib/attachment.coffee
@@ -30,7 +30,7 @@
     @tether.destroy()
     document.body.removeChild @root
     document.removeEventListener 'mousedown', @handleEvent
-    document.removeEventListener 'focus', @handleEvent
+    document.removeEventListener 'focus', @handleEvent, true

   handleEvent: (e) =>
     node = e.target


Wrap tests in document ready.

Wrap tests in document ready.

--HG--
branch : qunit

--- src/js/collection-editor-tests.coffee
+++ src/js/collection-editor-tests.coffee
@@ -1,117 +1,118 @@
-mock_access_token_params =
-  access_token: 'nonsense'
-  expires_in: 3600
-  token_type: 'Bearer'
+$(document).ready ->
+  mock_access_token_params =
+    access_token: 'nonsense'
+    expires_in: 3600
+    token_type: 'Bearer'

-test "hello test", ->
-  ok( 1 == 1, "Passed!" )
+  test "hello test", ->
+    ok( 1 == 1, "Passed!" )

-test "URN construction", ->
-  equal( cite_urn('namespace','collection','row'), 'urn:cite:namespace:collection.row' )
-  equal( cite_urn('namespace','collection','row','version'), 'urn:cite:namespace:collection.row.version' )
+  test "URN construction", ->
+    equal( cite_urn('namespace','collection','row'), 'urn:cite:namespace:collection.row' )
+    equal( cite_urn('namespace','collection','row','version'), 'urn:cite:namespace:collection.row.version' )

-module "cookie functions"
+  module "cookie functions"

-test "values set by set_cookie should be readable by get_cookie", ->
-  set_cookie 'cookie_test', 'test value', 60
-  equal( get_cookie('cookie_test'), 'test value' )
-  delete_cookie 'cookie_test'
+  test "values set by set_cookie should be readable by get_cookie", ->
+    set_cookie 'cookie_test', 'test value', 60
+    equal( get_cookie('cookie_test'), 'test value' )
+    delete_cookie 'cookie_test'

-test "values deleted by delete_cookie should return null", ->
-  set_cookie 'delete_cookie_test', 'test value', 60
-  delete_cookie 'delete_cookie_test'
-  equal( get_cookie('delete_cookie_test'), null )
+  test "values deleted by delete_cookie should return null", ->
+    set_cookie 'delete_cookie_test', 'test value', 60
+    delete_cookie 'delete_cookie_test'
+    equal( get_cookie('delete_cookie_test'), null )

-asyncTest "cookies set by set_cookie should expire", ->
-  expect(2)
-  set_cookie 'expire_cookie_test', 'test value', 1
-  equal( get_cookie('expire_cookie_test'), 'test value' )
-  setTimeout ->
-    equal( get_cookie('expire_cookie_test'), null )
-    start()
-  , 1001
+  asyncTest "cookies set by set_cookie should expire", ->
+    expect(2)
+    set_cookie 'expire_cookie_test', 'test value', 1
+    equal( get_cookie('expire_cookie_test'), 'test value' )
+    setTimeout ->
+      equal( get_cookie('expire_cookie_test'), null )
+      start()
+    , 1001

-module "access token cookies",
-  setup: ->
-    delete_cookie 'access_token'
-  teardown: ->
-    delete_cookie 'access_token'
-    $.mockjaxClear()
+  module "access token cookies",
+    setup: ->
+      delete_cookie 'access_token'
+    teardown: ->
+      delete_cookie 'access_token'
+      $.mockjaxClear()

-test "access token cookie should not be written for invalid access tokens", ->
-  expect(2)
-  equal( get_cookie('access_token'), null, 'cookie not set at test start' )
-  $.mockjax
-    url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?*'
-    contentType: 'text/json'
-    responseText:
-      error: "invalid_token"
-    status: 400
-  stop()
-  set_access_token_cookie mock_access_token_params, ->
-    equal( get_cookie('access_token'), null )
-    start()
+  test "access token cookie should not be written for invalid access tokens", ->
+    expect(2)
+    equal( get_cookie('access_token'), null, 'cookie not set at test start' )
+    $.mockjax
+      url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?*'
+      contentType: 'text/json'
+      responseText:
+        error: "invalid_token"
+      status: 400
+    stop()
+    set_access_token_cookie mock_access_token_params, ->
+      equal( get_cookie('access_token'), null )
+      start()

-test "access token cookie should be written for valid access tokens", ->
-  expect(2)
-  equal( get_cookie('access_token'), null, 'cookie not set at test start' )
-  $.mockjax
-    url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?*'
-    contentType: 'text/json'
-    responseText:
-      audience: 'nonsense'
-      user_id: 'nonsense'
-      scope: 'nonsense'
-      expires_in: 3600
-    status: 200
-  stop()
-  set_access_token_cookie mock_access_token_params, ->
-    equal( get_cookie('access_token'), mock_access_token_params['access_token'] )
-    start()
+  test "access token cookie should be written for valid access tokens", ->
+    expect(2)
+    equal( get_cookie('access_token'), null, 'cookie not set at test start' )
+    $.mockjax
+      url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?*'
+      contentType: 'text/json'
+      responseText:
+        audience: 'nonsense'
+        user_id: 'nonsense'
+        scope: 'nonsense'
+        expires_in: 3600
+      status: 200
+    stop()
+    set_access_token_cookie mock_access_token_params, ->
+      equal( get_cookie('access_token'), mock_access_token_params['access_token'] )
+      start()

-module "author name"
-  setup: ->
-    set_cookie 'access_token', 'nonsense', 3600
-    delete_cookie 'author_name'
-    $('.container').append $('<input>').attr('id','Author').attr('value','')
-  teardown: ->
-    delete_cookie 'access_token'
-    delete_cookie 'author_name'
-    $('#Author').remove()
-    $.mockjaxClear()
+  module "author name"
+    setup: ->
+      set_cookie 'access_token', 'nonsense', 3600
+      delete_cookie 'author_name'
+      $('.container').append $('<input>').attr('id','Author').attr('value','')
+    teardown: ->
+      delete_cookie 'access_token'
+      delete_cookie 'author_name'
+      $('#Author').remove()
+      $.mockjaxClear()

-test "set_author_name should pull from cookie when available", ->
-  equal( $('#Author').attr('value'), '', 'author empty at start')
-  set_cookie 'author_name', 'Test User', 60
-  set_author_name()
-  equal( $('#Author').attr('value'), 'Test User', 'author set')
+  test "set_author_name should pull from cookie when available", ->
+    equal( $('#Author').attr('value'), '', 'author empty at start')
+    set_cookie 'author_name', 'Test User', 60
+    set_author_name()
+    equal( $('#Author').attr('value'), 'Test User', 'author set')

-test "set_author_name with a successful AJAX call should set the cookie and populate the UI", ->
-  expect(3)
-  equal( $('#Author').attr('value'), '', 'author empty at start')
-  $.mockjax
-    url: 'https://www.googleapis.com/oauth2/v1/userinfo?*'
-    contentType: 'text/json'
-    status: 200
-    responseText:
-      name: 'AJAX User'
-  stop()
-  set_author_name ->
-    equal( get_cookie('author_name'), 'AJAX User' )
-    equal( $('#Author').attr('value'), 'AJAX User' )
-    start()
+  test "set_author_name with a successful AJAX call should set the cookie and populate the UI", ->
+    expect(3)
+    equal( $('#Author').attr('value'), '', 'author empty at start')
+    $.mockjax
+      url: 'https://www.googleapis.com/oauth2/v1/userinfo?*'
+      contentType: 'text/json'
+      status: 200
+      responseText:
+        name: 'AJAX User'
+    stop()
+    set_author_name ->
+      equal( get_cookie('author_name'), 'AJAX User' )
+      equal( $('#Author').attr('value'), 'AJAX User' )
+      start()

-test "set_author_name with an unsuccessful AJAX call should do nothing", ->
-  expect(3)
-  equal( $('#Author').attr('value'), '', 'author empty at start')
-  $.mockjax
-    url: 'https://www.googleapis.com/oauth2/v1/userinfo?*'
-    contentType: 'text/json'
-    responseText:
-      error: "invalid_token"
-    status: 400
-  stop()
-  set_author_name ->
-    equal( get_cookie('author_name'), null )
-    equal( $('#Author').attr('value'), '' )
-    start()
+  test "set_author_name with an unsuccessful AJAX call should do nothing", ->
+    expect(3)
+    equal( $('#Author').attr('value'), '', 'author empty at start')
+    $.mockjax
+      url: 'https://www.googleapis.com/oauth2/v1/userinfo?*'
+      contentType: 'text/json'
+      responseText:
+        error: "invalid_token"
+      status: 400
+    stop()
+    set_author_name ->
+      equal( get_cookie('author_name'), null )
+      equal( $('#Author').attr('value'), '' )
+      start()


[BaseView] always call ::attachChildViews() from ::_postRender()

--- shared/base/view.coffee
+++ shared/base/view.coffee
@@ -104,7 +104,6 @@
     # when calling getHtml() (server), let's make sure it also
     # happens during render() (client).
     @$el.attr @getAttributes()
-    @attachChildViews()
     @_postRender()
     @

@@ -140,6 +139,7 @@

   # Anything to do after rendering on the client.
   _postRender: ->
+    @attachChildViews()
     @postRender()

   # To be overridden by subclasses.


improved js API

improved js API

--- vendor/assets/javascripts/aced-api.js.coffee
+++ vendor/assets/javascripts/aced-api.js.coffee
@@ -1,27 +1,41 @@
 (

   ($) ->
-    $.fn.aced_init = (options) ->
-      $.extend {
-        id: 'aced_editor'
-      }, options
+    methods = {
+      init: (options) ->
+        unless options['id']
+          if @.attr('id').length > 0
+            options['id'] = @.attr 'id'
+          else
+            options['id'] = 'aced_editor'

-      @.attr 'id', options['id']
+        @.attr 'id', options['id']

-      editor = ace.edit options['id']
-      @.data 'aced_editor', editor
+        editor = ace.edit options['id']
+        @.data 'aced_editor', editor

-      delete options['id']
-      @.aced_conf options
+        delete options['id']
+        @.aced_conf options
+      ,
+      conf: (options) ->
+        unless @.data('aced_editor')
+          $.error "Element isn't ace editor"
+          return @

-    $.fn.aced_conf = (options) ->
-      return @ unless @.data('aced_editor')
+        editor = @.data('aced_editor')
+        if options['theme']
+          editor.setTheme "ace/theme/#{options['theme']}"
+        if options['mode']
+          editorMode = require("ace/mode/#{options['mode']}").Mode
+          editor.getSession().setMode(new editorMode())
+    }

-      editor = @.data('aced_editor')
-      if options['theme']
-        editor.setTheme "ace/theme/#{options['theme']}"
-      if options['mode']
-        editorMode = require("ace/mode/#{options['mode']}").Mode
-        editor.getSession().setMode(new editorMode())
-
+    $.fn.aced = (method) ->
+      if methods[method]
+        return methods[ method ].apply @, Array.prototype.slice.call( arguments, 1 )
+      else if typeof method == 'object' or not method
+        return methods.init.apply @, arguments
+      else
+        $.error 'No method error'
+
 )(jQuery)


Correction API GET consommable

Correction API GET consommable

--- postgreAPI/api.coffee
+++ postgreAPI/api.coffee
@@ -175,20 +175,19 @@
   search = ""
   param = []
   if req.query.search
-    search = """WHERE nom LIKE $1::text """
+    search = """AND nom LIKE $1::text """
     param = ["%#{req.query.search}%"]

   db().bind({}).then (connection) ->
     @connection = connection
     #  mdp_super, , mdp AS pass_hash
     connection.client.query """
-      SELECT DISTINCT nom, qte_frigo AS frigo, commentaire,
-        (SELECT prix_adh FROM "public"."groupeV"
-        WHERE "public"."groupeV"."groupe_id" = "public"."groupe"."id"
-        ORDER BY "public"."groupeV".date DESC LIMIT 1) AS prix
+      SELECT DISTINCT nom, MAX(prix_adh) AS prix
       FROM "public"."groupe"
-      INNER JOIN stockgroupe on stockgroupe.groupe_id = "public"."groupe".id
+      INNER JOIN "public"."groupeV" on "public"."groupeV".groupe_id = "public"."groupe".id
+      WHERE actif = true
       #{search}
+      GROUP BY "public"."groupe".id
       ORDER BY prix
     """, param
   .then (consommables) ->


update description

update description

--- lib/main.coffee
+++ lib/main.coffee
@@ -37,7 +37,7 @@
       order: 3
     manageDependencies:
       title: "Manage Dependencies"
-      description: "When enabled, this will automatically install third-party dependencies"
+      description: "When enabled, third-party dependencies will be installed automatically"
       type: "boolean"
       default: true
       order: 4


Tweak scrolling.

Tweak scrolling.

--- foreground.coffee
+++ foreground.coffee
@@ -18,7 +18,7 @@
     Scroller.init()
     Common.installListener window, "keydown", @keyDownHandler = (event) => @onKeyDown event
     Common.installListener window, "keyup", @KeyUpHandler = (event) => @onKeyUp event
-    Common.installListener window, "scroll", @scrollHandler = (event) => @onScroll event
+    Common.installListener window, "scroll", @scrollHandler = => @onScroll()
     chrome.runtime.sendMessage name: "icon", show: @config.selectors?

     @selectElement element false if element
@@ -30,27 +30,23 @@
     return unless Common.isActive()
     return if event.repeat

-    action = @eventToAction event
-
-    if action == "enter"
-      element = @element ? document.activateElement
-      if not element and @config.activeSelector
-        element = @querySelector document, @config.activeSelector
-      return unless element
-      return unless Common.isInViewport element
-
-      @activateElement element, event
-
-    else if action in [ "up", "down" ]
-      return if @config.nativeJK
-      @performUpDown action
-
-    else
-      return
+    switch action = @eventToAction event
+      when "enter"
+        element = @element ? document.activateElement
+        element ?= @querySelector document, @config.activeSelector if @config.activeSelector
+        return unless element and Common.isInViewport element
+
+        @activateElement element, event
+
+      when "up", "down"
+        return if @config.nativeJK
+        @performUpDown action
+
+      else
+        return

     event.preventDefault()
     event.stopImmediatePropagation()
-
     false

   onKeyUp: (event) ->
@@ -65,9 +61,8 @@

     if (@element and newIndex == oldIndex) or not elements[newIndex]
       element = @element ? document.body
-      amount = 100
-      amount *= -1 if action == "up"
-      Scroller.scrollBy element, "y", amount, true
+      amount = 100 * if action == "up" then -1 else 1
+      Scroller.scrollBy (@element ? document.body), "y", amount, true
     else
       # Sometimes, we just scroll to the previously selected element, which feels better from a UX
       # perspective.


crossbrowser: safari page load issue fixed

--- core/KD.coffee
+++ core/KD.coffee
@@ -40,10 +40,14 @@
 KD.warn  = warn  = noop
 KD.error = error = noop

+
 unless window.event?
-  # warn when the global "event" property is accessed.
-  Object.defineProperty window, "event", get:->
-    KD.warn "Global \"event\" property is accessed. Did you forget a parameter in a DOM event handler?"
+  try
+    # warn when the global "event" property is accessed.
+    Object.defineProperty window, "event", get:->
+      KD.warn "Global \"event\" property is accessed. Did you forget a parameter in a DOM event handler?"
+  catch e
+    log "we fail silently!", e

 @KD = $.extend (KD), do ->
   # private member for tracking z-indexes


It's better to use default behavior for loginRequired notification

--- core/KD.coffee
+++ core/KD.coffee
@@ -104,7 +104,6 @@

     if KD.whoami() instanceof KD.remote.api.JGuest
       new KDNotificationView
-        type     : 'growl'
         title    : 'Access denied!'
         content  : errMsg or 'You must log in to perform this action!'
         duration : 3000


Removed logging output of linter

Removed logging output of linter

--- lib/dub-linter.coffee
+++ lib/dub-linter.coffee
@@ -41,7 +41,6 @@
           stderr: (data) ->
             output += data
           exit: (code) ->
-            console.log output
             lines = output.split("\n");
             obj = []
             for line in lines


Better array visualization, #166

Better array visualization, #166

Example :
> objTest: Object { ... }
> testArr: Array [ 16 ]

--- lib/Components/CallStackPane.coffee
+++ lib/Components/CallStackPane.coffee
@@ -42,7 +42,6 @@
   eventEmitter = new EventEmitter()

   builder =
-    # helper: move to debugger?
     loadProperties: (ref) ->
       log "builder.loadProperties #{ref}"
       _debugger
@@ -67,7 +66,14 @@
               instance.properties[idx].value = value
             return instance.properties

-    # helper: move to debugger?
+    loadArrayLength: (ref) ->
+      _debugger
+      .lookup(ref)
+      .then (instance) ->
+        _debugger.lookup(instance.properties[0].ref)
+      .then (result) ->
+        result.value
+
     loadFrames: () ->
       log "builder.loadFrames"
       _debugger.fullTrace()
@@ -99,9 +105,21 @@
         when 'function'
           TreeViewItem("#{name} : function() { ... }", handlers: handlers)
         when 'object'
-          decorate = (title) -> (state) -> if state.isOpen then title else "#{title} { ... }"
           ref = value.value.ref || value.value.handle
-          TreeView(decorate("#{name} : #{className}"), (() => builder.loadProperties(ref).map(builder.property)), handlers: handlers)
+          isArray = className is "Array"
+          (if isArray then builder.loadArrayLength(ref) else Promise.resolve(0)).then (len) ->
+            decorate =
+              (title) ->
+                (state) ->
+                  if state.isOpen
+                    title
+                  else
+                    if isArray
+                      "#{title} [ #{len} ]"
+                    else
+                      "#{title} { ... }"
+
+            TreeView(decorate("#{name} : #{className}"), (() => builder.loadProperties(ref).map(builder.property)), handlers: handlers)

     frame: (frame) ->
       log "builder.frame #{frame.script.name}, #{frame.script.line}"
@@ -214,7 +232,8 @@
       _debugger.eval(state.expression())
       .then (result) =>
         ref = { name: state.expression(), value: result }
-        t = builder.value(ref, { dblclick: () => state.editMode.set(true) })
+        builder.value(ref, { dblclick: () => state.editMode.set(true) })
+      .then (t) =>
         state.value.set([t])
         return state
       .catch (error) =>
@@ -293,5 +312,4 @@

 exports.cleanup = () ->
   for remove in listeners
-    log "removing listener"
     remove()


add another task'

add another task'


fix bug with badge data getting lost

fix bug with badge data getting lost

--- src/ingress.coffee
+++ src/ingress.coffee
@@ -117,21 +117,26 @@
 ]

 module.exports = (robot) ->
-  badges = {}
+  badges =
+    add: (user, badgeName) ->
+      userBadges = robot.brain.data.ingressBadges[user.id] ?= []
+      userBadges.push ":#{badgeName}:"
+    forUser: (user) ->
+      robot.brain.data.ingressBadges[user.id] ?= []

   sayBadges = (a) ->
     badgeReq = for kind, amt of a
       Array(amt+1).join ":#{kind}:"

-  robot.brain.on "loaded", ->
-    badges = robot.brain.data.ingressBadges ?= {}
+  robot.brain.on 'loaded', ->
+    robot.brain.data.ingressBadges ?= {}

   robot.respond /AP\s+(?:to|(?:un)?til)\s+L?(\d{1,2})/i, (msg) ->
     [lv, lvl] = [msg.match[1], levels[msg.match[1]]]
     if lvl.badges?
       badgeReq = sayBadges lvl.badges
     msg.reply "You need #{lvl.ap} AP#{if badgeReq? then ' ' + badgeReq.join ' ' else ''}
-        to reach L#{lv}#{if lv > 15 then ' (hang in there!)' else ''}"
+ to reach L#{lv}#{if lv > 15 then ' (hang in there!)' else ''}"

   robot.respond /AP all/i, (msg) ->
     lvls = for lv, lvl of levels
@@ -149,10 +154,8 @@
     else
       who = robot.brain.userForName who

-    userBadges = badges[who.id] ?= []
-
     if badgeName in badgeList
-      userBadges.push ":#{badgeName}:"
+      badges.add who, badgeName
       if who.name == msg.envelope.user.name
         msg.reply "congrats on earning the :#{badgeName}: badge!"
       else
@@ -168,7 +171,7 @@
     else
       who = robot.brain.userForName who

-    userBadges = badges[who.id] = []
+    userBadges = badges.forUser who
     you = if who? and who.name == msg.envelope.user.name then true else false
     whowhat = "#{if you then 'You have' else who.name + ' has'}"


change to vertical-layout

change to vertical-layout

--- content/gitoe-draw.coffee
+++ content/gitoe-draw.coffee
@@ -122,8 +122,8 @@

   draw_group: (sha1,layer,pos)->
     group_pos = {
-      left : @constant.padding_left + layer * @constant.outer_width
-      top  : @constant.padding_top + pos * @constant.outer_height
+      left : @constant.padding_left + pos * @constant.outer_width
+      top  : @constant.padding_top  + layer * @constant.outer_height
     }
     if group_pos.left > @canvas_width
       @canvas_inc_width()
@@ -149,16 +149,16 @@
       # the line
       coord_sets = []
       coord_sets.push [
-        - @constant.inner_width / 2
         0
-        - (@constant.inner_width / 2 + @constant.commit_handle)
+        -  @constant.box_style.height / 2
         0
+        - (@constant.box_style.height / 2 + @constant.commit_handle )
       ] # handle
       coord_sets.push [
-        - (@constant.inner_width / 2 + @constant.commit_handle)
         0
-        -(layer - p.layer) * @constant.outer_width + @constant.inner_width/2
-        -(pos - p.pos) * @constant.outer_height
+        - (@constant.box_style.height / 2 + @constant.commit_handle )
+        -(pos - p.pos) * @constant.outer_width
+        -(layer - p.layer) * @constant.outer_height + @constant.box_style.height/2
       ] # the path
       for coords in coord_sets
         paths.push(new fabric.Line coords, @constant.path_style)


hdp_select: accept string argument

hdp_select: accept string argument

--- lib/hdp_select.coffee
+++ lib/hdp_select.coffee
@@ -13,6 +13,7 @@
 ###

 module.exports = (options, callback) ->
+  options.name = options.argument if options.argument?
   options.version ?= 'latest'
   if options.version and options.version not in ['latest', 'current']
     options.store['hdp_select.version.default'] = options.version


fixed typo

fixed typo

--- source/core/output.coffee
+++ source/core/output.coffee
@@ -51,7 +51,7 @@
   refresh: ->
     dom_node = @el[0]
     @_render()
-    @bindEvents()
+    @bindEvents?()
     dom_node.parentNode.replaceChild @el[0], dom_node

   ###


update guide.coffee

update guide.coffee

Added bushpigs to red river hogs, per @mimiarandjelovic's request

--- app/lib/guide.coffee
+++ app/lib/guide.coffee
@@ -205,7 +205,7 @@
       header: 'red river hog'
       subHeader: 'Potamochoerus porcus'
       description: '''
-
Pudgy pig-like animal notable for its bright red fur and its curled, pointed, elfin ears with white ear-tufts. The large muzzle is black with two small tusks and white markings around the eyes. Longer fur on flanks and underbelly. Has a line of spiky blonde hair down the spine.  Seen both at night and during the day.

+
Pudgy pig-like animal notable for its bright red fur and its curled, pointed, elfin ears with white ear-tufts. The large muzzle is black with two small tusks and white markings around the eyes. Longer fur on flanks and underbelly. Has a line of spiky blonde hair down the spine.  Seen both at night and during the day.  Bushpigs, close relatives of red river hogs, should also be marked using this classifications.

       '''
       confusions: ['giant forest hog', 'warthog']
       exampleImages: ["./assets/guide/red-river-hog-1.jpg", "./assets/guide/red-river-hog-2.jpg", "./assets/guide/red-river-hog-3.jpg"]


Fixed some commented translations

Fixed some commented translations

--- app/locale/cs.coffee
+++ app/locale/cs.coffee
@@ -254,7 +254,7 @@
 #    tome_cast_button_run: "Run"
 #    tome_cast_button_running: "Running"
 #    tome_cast_button_ran: "Ran"
-#    tome_submit_button: "Odeslat"
+    tome_submit_button: "Odeslat"
 #    tome_reload_method: "Reload original code for this method" # Title text for individual method reload button.
     tome_select_method: "Vybrat metodu"
     tome_see_all_methods: "Vybrat všechny metody, které mohou být upraveny" # Title text for method list selector (shown when there are multiple programmable methdos).
@@ -280,8 +280,8 @@
     tip_scrub_shortcut: "Ctrl+[ a Ctrl+] pro přetočení a rychlý přesun."
     tip_guide_exists: "Klikněte na průvode uvnitř herního menu (nahoře na stránce), pro užitečné informace."
     tip_open_source: "CodeCombat je 100% open source!"
-#    tip_beta_launch: "CodeCombat spustil svoji beta verzi v Říjnu, 2013."
-#    tip_think_solution: "Myslete na řešení, ne na problém."
+    tip_beta_launch: "CodeCombat spustil svoji beta verzi v Říjnu, 2013."
+    tip_think_solution: "Myslete na řešení, ne na problém."
 #    tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
 #    tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
 #    tip_debugging_program: "If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra"


Removed more wizard i18n stuff

Removed more wizard i18n stuff

I am not sure about line 278.

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -291,7 +291,6 @@
     tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
     tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
     tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
-    customize_wizard: "Customize Wizard"

   game_menu:
     inventory_tab: "Inventory"
@@ -504,8 +503,6 @@
     job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job."
     sample_profile: "See a sample profile"
     view_profile: "View Your Profile"
-    wizard_tab: "Wizard"
-    wizard_color: "Wizard Clothes Color"

   keyboard_shortcuts:
     keyboard_shortcuts: "Keyboard Shortcuts"
@@ -526,7 +523,6 @@
     toggle_pathfinding: "Toggle pathfinding overlay."
     beautify: "Beautify your code by standardizing its formatting."
     maximize_editor: "Maximize/minimize code editor."
-    move_wizard: "Move your Wizard around the level."

   community:
     main_title: "CodeCombat Community"
@@ -872,7 +868,6 @@
     versions: "Versions"
     items: "Items"
     heroes: "Heroes"
-    wizard: "Wizard"
     achievement: "Achievement"
     clas: "CLAs"
     play_counts: "Play Counts"


Update copy

Update copy

- Update Jane’s title
- Mark bulk_assign copy as changed

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -953,7 +953,7 @@
     sean_title: "Territory Manager"
     liz_title: "Territory Manager"
     david_title: "Customer Success Manager"
-    jane_title: "Partnership Associate"
+    jane_title: "Partnership Manager" # {change}
     retrostyle_title: "Illustration"
     retrostyle_blurb: "RetroStyle Games"
     jose_title: "Music"
@@ -1522,7 +1522,7 @@
     copy_class_url: "Copy Class URL"
     class_join_url_blurb: "You can also post this unique class URL to a shared webpage."
     add_students_manually: "Invite Students by Email"
-    bulk_assign: "Select course"
+    bulk_assign: "Select course" # {change}
     assigned_msg_1: "{{numberAssigned}} students were assigned {{courseName}}."
     assigned_msg_2: "{{numberEnrolled}} licenses were applied."
     assigned_msg_3: "You now have {{remainingSpots}} available licenses remaining."


try to fix regular expression interpolation

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -1710,7 +1710,7 @@
     teacher_quest_teach_strings_step2: "Positionnez le sélecteur de niveaux des professeurs sur la page [Guide des cours](/teachers/"+"cou"+"rses)pour prévisualiser __True Names__."
 #    teacher_quest_teach_loops: "Keep your students in the loop about `loops`."
 #    teacher_quest_teach_loops_step1: "Get 75% of at least one class through __Fire Dancing__."
-#    teacher_quest_teach_loops_step2: "Use the __Loops Activity__ in the [CS1 Curriculum guide](/teachers/resources/cs1) to reinforce this concept."
+    teacher_quest_teach_loops_step2: "Utilisez la __Loops Activity__ dans le [guide du Curriculum CS1]("+'''/teachers/resources/cs1'''+") to reinforce this concept."
      teacher_quest_teach_variables: "Faites-le varier avec les `variables`."
 #    teacher_quest_teach_variables_step1: "Get 75% of at least one class through __Known Enemy__."
 #    teacher_quest_teach_variables_step2: "Encourage collaboration by using the [Pair Programming Activity](/teachers/resources/pair-programming)."
@@ -1719,9 +1719,9 @@
 #    teacher_quest_kithgard_gates_100_step2: "Guide students to think through hard problems using the [Engineering Cycle Worksheet](http://files.codecombat.com/docs/resources/EngineeringCycleWorksheet.pdf)."
 #    teacher_quest_wakka_maul_100: "Prepare to duel in Wakka Maul."
 #    teacher_quest_wakka_maul_100_step1: "Get 75% of at least one class to __Wakka Maul__."
-#    teacher_quest_wakka_maul_100_step2: "See the [Arena Guide](/teachers/resources/arenas) in the [Resource Hub](/teachers/resources) for tips on how to run a successful arena day."
+#    teacher_quest_wakka_maul_100_step2: "See the [Arena Guide]("+'''/teachers/resources/arenas'''+") in the [Resource Hub]("+'''/teachers/resources'''+") for tips on how to run a successful arena day."
 #    teacher_quest_reach_gamedev: "Explore new worlds!"
-#    teacher_quest_reach_gamedev_step1: "[Get licenses](/teachers/licenses) so that your students can explore new worlds, like Game Development and Web Development!"
+#    teacher_quest_reach_gamedev_step1: "[Get licenses]("+'''/teachers/licenses'''+") so that your students can explore new worlds, like Game Development and Web Development!"
     teacher_quest_done: "Voulez-vous que vos étudiants apprennent encore plus à coder? Restez en contact avec nos [spécialistes scolaires](mailto:schools@codecombat.com) aujourd'hui!"
 #    teacher_quest_keep_going: "Keep going! Here's what you can do next:"
 #    teacher_quest_more: "See all quests"
@@ -2175,7 +2175,7 @@
     advanced_html_attributes: "Attributs HTML avancés"
     advanced_html_tags: "Balises HTML avancées"
 #    algorithm_average: "Algorithm Average"
-#    algorithm_find_minmax: "Algorithm Find Min/Max"
+    algorithm_find_minmax: "Algorithme de recherche Min"+'''/'''+"Max"
 #    algorithm_search_binary: "Algorithm Search Binary"
 #    algorithm_search_graph: "Algorithm Search Graph"
 #    algorithm_sort: "Algorithm Sort"
@@ -2198,7 +2198,7 @@
     boolean_and: "Opérateur booléen ET"
 #    boolean_inequality: "Boolean Inequality"
     boolean_equality: "Opérateur booléen EGAL"
-    boolean_greater_less: "Opérateur booléen PLUS GRAND/PLUS PETIT"
+    boolean_greater_less: "Opérateur booléen PLUS GRAND"+'''/'''+"PLUS PETIT"
 #    boolean_logic_shortcircuit: "Boolean Logic Shortcircuiting"
     boolean_not: "Opérateur booléen NON"
 #    boolean_operator_precedence: "Boolean Operator Precedence"


Update ru.coffee. New "Teachers" block

Update ru.coffee. New "Teachers" block

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -576,37 +576,37 @@
     retrostyle_blurb: "RetroStyle Games"

   teachers:
-    title: "CodeCombat для учителей" # {change}
-#    intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages."
-#    intro_2: "No experience required!"
-#    free_title: "How much does it cost?"
-#    free_1: "CodeCombat Basic is FREE! There are 70+ free levels which cover every concept."
-#    free_2: "A monthly subscription provides access to video tutorials and extra practice levels."
-#    teacher_subs_title: "Teachers get free subscriptions!"
-#    teacher_subs_1: "Please contact"
-#    teacher_subs_2: "to setup a free monthly subscription."
-#    sub_includes_title: "What is included in the subscription?"
-#    sub_includes_1: "In additional to the 70+ free levels, students with a monthly subscription get access to these additional features:"
-#    sub_includes_2: "40+ practice levels"
-#    sub_includes_3: "Video tutorials"
-#    sub_includes_4: "Premium email support"
-#    sub_includes_5: "7 new heroes with unique skills to master"
-#    sub_includes_6: "3500 bonus gems every month"
-#    who_for_title: "Who is CodeCombat for?"
-#    who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
-#    who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
-#    material_title: "How much material is there?"
-#    material_1: "Approximately 8 hours of free content, and an additional 14 hours of subscriber content."
-#    concepts_title: "What concepts are covered?"
-#    how_much_title: "How much does a monthly subscription cost?"
-#    how_much_1: "A"
-#    how_much_2: "monthly subscription"
-#    how_much_3: "costs $9.99, and can be cancelled anytime."
-#    how_much_4: "Additionally, we provide discounts for larger groups:"
-#    group_discounts_1: "We also offer group discounts for bulk subscriptions."
+    title: "CodeCombat: Информация для учителей"
+    intro_1: "CodeCombat - это онлайн игра, которая обучает программированию. Ученики пишут код на реальных языках программирования."
+    intro_2: "Опыт не обязателен!"
+    free_title: "Сколько это стоит?"
+    free_1: "CodeCombat (его базовая часть) бесплатен! В наличии более 70 уровней, которые покрывают каждый концепт."
+    free_2: "Месячная подписка предоставляет доступ к видео-урокам и дополнительным уровням."
+    teacher_subs_title: "Учителя получают бесплатные подписки!"
+    teacher_subs_1: "Пожалуйста, напишите на"
+    teacher_subs_2: "для получения бесплатной месячной подписки."
+    sub_includes_title: "Что включено в подписку?"
+    sub_includes_1: "В дополнение к более чем 70 бесплатным уровням ученики с месячной подпиской получат доступ к дополнительным возможностям:"
+    sub_includes_2: "Более 40 уровней для дополнительной практики"
+    sub_includes_3: "Видео-уроки"
+    sub_includes_4: "Эксклюзивная поддержка по электронной почте"
+    sub_includes_5: "7 новых героев с уникальными возможностями для оттачивания мастерства"
+    sub_includes_6: "3500 бонусных самоцветов каждый месяц"
+    who_for_title: "Для кого предназначен CodeCombat?"
+    who_for_1: "Мы рекомендуем CodeCombat для учеников старше 9 лет. Какой-либо опыт программирования не требуется."
+    who_for_2: "Мы разработали CodeCombat так, чтобы он подходил и мальчикам и девочкам."
+    material_title: "Как много здесь материала?"
+    material_1: "Около 8 часов бесплатного контента и 14 часов дополнительного контента для подписчиков."
+    concepts_title: "О каких концептах мы рассказываем? What concepts are covered?"
+    how_much_title: "How much does a monthly subscription cost?"
+    how_much_1: "Цена"
+    how_much_2: "месячной подписки"
+    how_much_3: "- $9.99. Подписка может быть отменена в любой момент."
+    how_much_4: "Дополнительно мы предоставляем скидки для больших групп:"
+    group_discounts_1: "Мы так же предлагаем скидки при групповых подписках."
     sys_requirements_title: "Системные требования"
-    sys_requirements_1: "Так как CodeCombat - это игра, для плавной работы она более требовательна к компьютерам, чем видео или электронные учебники. Мы оптимизировали CodeCombat для быстрой работы во всех современных браузерах и старых машинах, так что все могут играть. Тем не менее, вот наши предложения, чтобы вы могли получить максимум от CodeCombat:" # {change}
-    sys_requirements_2: "Используйте последние версии Chrome или Firefox." # {change}
+    sys_requirements_1: "Современный веб-браузер. Последние версии Chrome, Firefox или Safari. Internet Explorer 9 или новее."
+    sys_requirements_2: "CodeCombat пока не поддерживает iPad."

   versions:
     save_version_title: "Сохранить новую версию"


Update sk.coffee

Update sk.coffee

Translated: Leaderboard

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -356,19 +356,19 @@
     multiplayer_caption: "Hraj s priateľmi!"
     auth_caption: "Ulož svoj progres."

-#  leaderboard:
-#    leaderboard: "Leaderboard"
-#    view_other_solutions: "View Other Solutions"
-#    scores: "Scores"
-#    top_players: "Top Players by"
-#    day: "Today"
-#    week: "This Week"
-#    all: "All-Time"
-#    time: "Time"
-#    damage_taken: "Damage Taken"
-#    damage_dealt: "Damage Dealt"
-#    difficulty: "Difficulty"
-#    gold_collected: "Gold Collected"
+  leaderboard:
+    leaderboard: "Rebríček"
+    view_other_solutions: "Ukážka iných riešení"
+    scores: "Skóre"
+    top_players: "Najlepší hráči podľa"
+    day: "Dnes"
+    week: "Tento týždeň"
+    all: "Celkovo"
+    time: "Time"
+    damage_taken: "obdržanej újmy"
+    damage_dealt: "spôsobenej újmy"
+    difficulty: "obtiažnosti"
+    gold_collected: "Nazbierané zlato"

   inventory:
     choose_inventory: "Použi veci"
@@ -493,7 +493,7 @@
 #    speed_2: "meters per second."
 #    available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
 #    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
-#    restricted_to_certain_heroes: "Only certain heroes can play this level."
+    restricted_to_certain_heroes: "Iba niektorí hrdinovia môžu hrať túto úroveň."

   skill_docs:
     writable: "zapisovatelné" # Hover over "attack" in Your Skills while playing a level to see most of this


:bug: fix compilation, add before/after

:bug: fix compilation, add before/after

--- root/test/test.coffee
+++ root/test/test.coffee
@@ -3,16 +3,17 @@
 fs     = require 'fs'
 Roots  = require 'roots'

-test_path = path.join(__dirname, '..')
-project = new Roots(test_path)
+project_path = path.join __dirname, '..'
+compile_project = (done) ->
+  h.project.compile(Roots, path.resolve './').then -> done()
+
+before (done) -> h.project.install_dependencies('*',done)
+after -> h.project.remove_folders('**/public')

 describe 'roots.compile', ->
-  before ->
-    h.project.compile(project, __dirname)
-
-  after -> rimraf.sync(path.join(test_path, 'public'))
+  before (done) -> compile_project(done)

   it 'compiles the roots project properly', (done) ->
-    tgt = path.join(test_path, 'public', 'index.html')
+    tgt = path.join(project_path, 'public/index.html')
     fs.existsSync(tgt).should.be.ok
     done()


in layout, only get groups that current user is a member of

--- src/node_modules/layout/layout.coffee
+++ src/node_modules/layout/layout.coffee
@@ -4,6 +4,7 @@
   template: require('./layout-template.html')
   controller: require('./layout-controller.coffee')
   resolve:
-    groups: (GroupsStore) ->
-      GroupsStore.all()
+    groups: (GroupsStore, AuthService) ->
+      user = AuthService.getCurrentUser()
+      GroupsStore.all(memberId: user.id)
 }


configuration system update: support windows

--- configure.coffee
+++ configure.coffee
@@ -1,6 +1,9 @@
 #!/usr/bin/env coffee
 fs = require "fs"
 path = require "path"
+os = require "os"
+
+isWin = !!os.platform().match(/^win/);

 walkDir = (dir, cb) ->
   for fname in fs.readdirSync dir
@@ -31,7 +34,12 @@
   opath = path.join(odir, fname)

   fs.writeSync makefile, "#{opath}: #{ipath}\n"
-  fs.writeSync makefile, "\tln -f #{ipath} #{opath}\n"
+  fs.writeSync makefile,
+    if isWin
+      "\tcopy /Y #{ipath} #{opath}\n"
+    else
+      "\tln -f #{ipath} #{opath}\n"
+
   js_sources.push ipath
   js_outputs.push opath


Autocomplete: Use lodash map

Autocomplete: Use lodash map

Because some kernels return the matches as objects

--- lib/autocomplete-provider.coffee
+++ lib/autocomplete-provider.coffee
@@ -1,3 +1,4 @@
+_ = require 'lodash'
 iconHTML = "<img src='#{__dirname}/../static/logo.svg' style='width: 100%;'>"

 regexes =
@@ -55,7 +56,7 @@
                 kernel.complete prefix, ({matches, cursor_start, cursor_end}) ->
                     replacementPrefix = prefix.slice cursor_start, cursor_end

-                    matches = matches.map (match) ->
+                    matches = _.map matches, (match) ->
                         text: match
                         replacementPrefix: replacementPrefix
                         iconHTML: iconHTML


Better livereload.

Better livereload.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -33,10 +33,14 @@
   gulp.watch(paths.lib.in, ['lib'])
   gulp.watch(paths.sass.in, ['compass'])
   # Livereload
-  livereload.listen()
-  gulp.watch("#{paths.sass.out}#{paths.sass.name}").on('change', livereload.changed)
-  gulp.watch("#{paths.coffee.out}#{paths.coffee.name}").on('change', livereload.changed)
-  gulp.watch("./partials/**/*.html").on('change', livereload.changed)
+  livereload = livereload()
+  gulp.watch([
+    "#{paths.sass.out}#{paths.sass.name}"
+    "#{paths.coffee.out}#{paths.coffee.name}"
+    "./partials/**/*.html"
+  ]).on('change', (file)->
+    livereload.changed(file.path)
+  )
 )

 gulp.task('compile', ['browserify', 'lib', 'compass'])


error on rsync?

error on rsync?

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -111,6 +111,7 @@
     string: 'dest'
     defaults: ""
   opts = if process.argv.length > 1 then minimist process.argv.slice(2), knownOpts else {'dest':null}
+  gutil.log "dest: " + opts.dest
   if opts.dest
     rsync
       ssh: true


Update gulp-webserver

Update gulp-webserver

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -53,5 +53,6 @@
   gulp.src './'
     .pipe webserver
       livereload: true
+      host: '0.0.0.0'

 gulp.task 'development', ['webserver','build','watch']


Build: made sure that we have ./build directory

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -107,6 +107,8 @@

 gulp.task 'html', ->

+  try fs.mkdirSync './build'
+
   fs.writeFileSync './build/index.html', fs.readFileSync './src/index.html'


Fix callback argument name

Fix callback argument name

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -131,7 +131,7 @@
   "app-version": VERSION
 }
 gulp.task 'package:mac', ['browserify', 'html'], (done) ->
-  packager(_.extend({}, packageConfig, platform: "darwin"), (err, path) ->
+  packager(_.extend({}, packageConfig, platform: "darwin"), (err, output) ->
     outputDir = path.join(path.dirname(output[0]), 'out')
     outputArchive = path.join(outputDir, path.basename(output[0]))
     childProcess.execFileSync('mkdir', ['-p', outputDir])


Hide the dinner choices etc... when its not the dinner session

--- lib/client/views/register.coffee
+++ lib/client/views/register.coffee
@@ -1,6 +1,6 @@
 {View} = require 'space-pen'

-{Class, DinnerMenuAssignment, Pupil, Register, RegisterEntry, Session} = require '../../database'
+{Class, Config, DinnerMenuAssignment, Pupil, Register, RegisterEntry, Session} = require '../../database'

 module.exports =
   class RegisterView extends View
@@ -13,7 +13,8 @@
             @tr =>
               @th "Name"
               @th "Present?"
-              @th "Dinner Choice"
+              if '' + register.sessionId == Config.setting('dinnerSession')
+                @th "Dinner Choice"
           @tbody id: 'registerBody', =>
             for pupil in Pupil.where('classId', register.classId)
               @tr =>
@@ -25,11 +26,12 @@
                       @input type: 'checkbox', 'data-pupil': pupil.idPupils, 'data-register': register.idRegister
                       @span class: 'check'
                       @span class: 'choice', "Yes"
-                @td =>
-                  @select 'data-pupil': pupil.idPupils, 'data-register': register.idRegister, =>
-                    @option value: 0, 'None/Packed Lunch'
-                    for choice in DinnerMenuAssignment.forRegister()
-                      @option value: choice.idDinnerChoice, choice.name
+                if '' + register.sessionId == Config.setting('dinnerSession')
+                  @td =>
+                    @select 'data-pupil': pupil.idPupils, 'data-register': register.idRegister, =>
+                      @option value: 0, 'None/Packed Lunch'
+                      for choice in DinnerMenuAssignment.forRegister()
+                        @option value: choice.idDinnerChoice, choice.name
         @button class: 'large info', click: 'recordRegister', "Record Register"

     recordRegister: (event, element) ->
@@ -76,13 +78,13 @@

         input = $('#registerBody tr:nth-child(' + current + ')').children('td').children('div').children('label').children('input')
         input.prop 'checked', false
-
-      $('#mainBody').append('

')
-      $('#notifyContainer').append('

Dinner Choices

')
-      $('#choicesList').append('[0] None/Packed Lunch
')
-      $(document).on 'keydown', null, '0', ->
-        select = $('#registerBody tr:nth-child(' + current + ')').children('td').children('select')
-        select.val(0)
+      if($('thead tr').children().length == 3)
+        $('#mainBody').append('

')
+        $('#notifyContainer').append('

Dinner Choices

')
+        $('#choicesList').append('[0] None/Packed Lunch
')
+        $(document).on 'keydown', null, '0', ->
+          select = $('#registerBody tr:nth-child(' + current + ')').children('td').children('select')
+          select.val(0)

       num = 0
       for choice in DinnerMenuAssignment.forRegister()


Extract out a duplicate function.

Extract out a duplicate function.

--- src/lib/vpn.coffee
+++ src/lib/vpn.coffee
@@ -53,15 +53,13 @@
 			vpnConf
 		])

+prefixData = (data) ->
+	prefix = 'OPENVPN: '
+	console.log((prefix + data).trim().replace(/\n/gm, "\n#{prefix}"))
+
 exports.connect = ->
 	openvpn = spawn('openvpn', [ 'client.conf' ], cwd: '/data')

 	# Prefix and log all OpenVPN output
-	openvpn.stdout.on 'data', (data) ->
-		prefix = 'OPENVPN: '
-		console.log((prefix + data).trim().replace(/\n/gm, "\n#{prefix}"))
-
-	# Prefix and log all OpenVPN output
-	openvpn.stderr.on 'data', (data) ->
-		prefix = 'OPENVPN: '
-		console.log((prefix + data).trim().replace(/\n/gm, "\n#{prefix}"))
+	openvpn.stdout.on('data', prefixData)
+	openvpn.stderr.on('data', prefixData)


fix regex again

fix regex again

--- lib/misc/words.coffee
+++ lib/misc/words.coffee
@@ -1,5 +1,5 @@
 module.exports =
-  wordRegex: /\b[\u00A0-\uFFFF\w_!´\.]*@?[\u00A0-\uFFFF\w_!´]+\b/
+  wordRegex: /[\u00A0-\uFFFF\w_!´\.]*@?[\u00A0-\uFFFF\w_!´]+/

   # takes an editor and gets the current word. If that is nonempty, call function
   # `fn` with arguments `word` and `range`.