Add property changed callbacks

Add property changed callbacks

--- src/element.coffee
+++ src/element.coffee
@@ -8,6 +8,7 @@
       return unless Utils.connectedToDOM(@) and not @__initialized
       @_initializeProps()
       @__releaseCallbacks = []
+      @__propertyChangedCallbacks = []
       props = Utils.propValues(@props)
       try
         if Utils.isConstructor(ComponentType)
@@ -21,6 +22,7 @@
       # prevent premature releasing when element is only temporarely removed from DOM
       await Promise.resolve()
       return if Utils.connectedToDOM(@)
+      @__propertyChangedCallbacks.length = 0
       callback(@) while callback = @__releaseCallbacks.pop()
       @__released = true

@@ -56,6 +58,8 @@

     addReleaseCallback: (fn) -> @__releaseCallbacks.push(fn)

+    addPropertyChangedCallback: (fn) -> @__propertyChangedCallbacks.push(fn)
+
     _initializeProps: ->
       @props = Utils.cloneProps(propDefinition)
       @__updating = {}
@@ -72,6 +76,6 @@
               @props[key].value = val[..]
             else @props[key].value = val
             Utils.reflect(@, prop.attribute, @props[key].value)
-            @onPropertyChangedCallback?(key, val)
+            callback(key, val) for callback in @__propertyChangedCallbacks
         }
       return


Clearer CoffeeScript: migrate helper mixins, etc.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -43,8 +43,8 @@
       @animation

     fadeTo: (gameObject, duration, alpha, init = yes) ->
-      if init then gameObject.alpha = (if alpha is 0 then 1 else 0)
-      tween = @add.tween(gameObject).to { alpha }, duration, 'Cubic', yes
+      gameObject.alpha = (if alpha is 0 then 1 else 0) if init
+      tween = @add.tween(gameObject).to({ alpha }, duration, 'Cubic', yes)

   # CameraMixin
   # -----------
@@ -81,32 +81,32 @@

       toggle = @gui.add(@, 'debugging')
       toggle.listen()
-      toggle.onFinishChange => @debugTextItems = {}
-
-      @gui.add @, 'tracing'
+      toggle.onFinishChange( => @debugTextItems = {}; return )
+
+      @gui.add(@, 'tracing')

       yes

     debug: (label, value, details) ->
       return unless @debugging

-      value = parseFloat value.toFixed(2) if _.isNumber(value)
-      value = @_prettyHash @_prettyPoint(value) if value instanceof Point
+      value = parseFloat(value.toFixed(2)) if _.isNumber(value)
+      value = @_prettyHash(@_prettyPoint(value)) if value instanceof Point

       if details?.position and details.position instanceof Point
-        details.position = @_prettyPoint details.position
+        details.position = @_prettyPoint(details.position)

       if @tracing
         label = "#{@debugNamespace}:#{label}"
-        if details? then console.trace label, value, details
-        else console.trace label, value
+        if details? then console.trace(label, value, details)
+        else console.trace(label, value)
       else
         if (_.isArray(value) and
           (_.isArray(value[0]) or _.isPlainObject(value[0]))
         )
           label = "#{@debugNamespace}:#{label}"
-          console.groupCollapsed label
-          console.table? value
+          console.groupCollapsed(label)
+          console.table?(value)
           console.groupEnd()
         else
           details = if details? then @_prettyHash(details) else ''
@@ -114,15 +114,15 @@
       return

     _prettyPoint: (point) ->
-      _.chain point
-        .pick 'x', 'y'
-        .mapObject (n) -> parseFloat n.toFixed(2)
+      _.chain(point)
+        .pick('x', 'y')
+        .mapObject( (n) -> parseFloat(n.toFixed(2)) )
         .value()

     _prettyHash: (hash) ->
-      JSON.stringify hash
-        .replace RegExps.PrettyHashRemove,''
-        .replace RegExps.PrettyHashPad, '$& '
+      JSON.stringify(hash)
+        .replace(RegExps.PrettyHashRemove, '')
+        .replace(RegExps.PrettyHashPad, '$& ')

   # Define Phaser-specific constants. Fragile.
   kPhaserLayoutX = -8
@@ -152,11 +152,11 @@

     renderDebugDisplayItems: (items, lines) ->
       if _.isFunction(items) and lines?
-        items @_layoutX, @_layoutY
+        items(@_layoutX, @_layoutY)
         @_layoutY += lines * @_debugLine

       else for own label, text of items
-        @debugDisplay.text text, @_layoutX, @_layoutY, null, @debugDisplay.font
+        @debugDisplay.text(text, @_layoutX, @_layoutY, null, @debugDisplay.font)
         @_layoutY += @_debugLine
       return

@@ -165,7 +165,7 @@
   # Basic dat.GUI extensions to require less code.

   addOpenFolder = ->
-    folder = @addFolder.apply @, arguments
+    folder = @addFolder.apply(@, arguments)
     folder.open()
     folder

@@ -173,13 +173,13 @@
     value = obj[prop]
     [min, max] = [value / 2, 2 * value]

-    if value < 0 then gui = @.add obj, prop, max, min
-    else if value > 0 then gui = @.add obj, prop, min, max
-    else gui = @.add obj, prop
+    if value < 0 then gui = @.add(obj, prop, max, min)
+    else if value > 0 then gui = @.add(obj, prop, min, max)
+    else gui = @.add(obj, prop)

     if chain then @ else gui

-  _.extend dat.GUI::, { addOpenFolder, addRange }
+  _.extend(dat.GUI::, { addOpenFolder, addRange })

   # Flixel Tilemap AUTO Layout Shim
   # -------------------------------
@@ -187,7 +187,7 @@
   # implementation.

   autoSetTiles = (tiles) ->
-    result = (_.clone row for row in tiles)
+    result = (_.clone(row) for row in tiles)

     for row, r in result
       for col, c in row when col is 1
@@ -225,10 +225,10 @@
   # Basic Phaser class extension for less code.

   PointMixin =
-    addPoint: (point) -> @add point.x, point.y
-    dividePoint: (point) -> @divide point.x, point.y
-    multiplyPoint: (point) -> @multiply point.x, point.y
-    subtractPoint: (point) -> @subtract point.x, point.y
+    addPoint: (point) -> @add(point.x, point.y)
+    dividePoint: (point) -> @divide(point.x, point.y)
+    multiplyPoint: (point) -> @multiply(point.x, point.y)
+    subtractPoint: (point) -> @subtract(point.x, point.y)

   # StateManagerMixin
   # -----------------
@@ -258,10 +258,10 @@
       texture = new RenderTexture(
         @game, @game.width, @game.height, "transition-to-#{stateName}"
       )
-      texture.renderXY @game.world, -@game.camera.x, -@game.camera.y
+      texture.renderXY(@game.world, -@game.camera.x, -@game.camera.y)
       @game.paused = no

-      interstitial = new Sprite @game, 0, 0, texture
+      interstitial = new Sprite(@game, 0, 0, texture)
       interstitial.fixedToCamera = on

       destroy = ->
@@ -274,21 +274,21 @@
         return

       state.init = =>
-        init?.apply state, arguments
-        @game.add.existing interstitial
+        init?.apply(state, arguments)
+        @game.add.existing(interstitial)
         return

       state.create = =>
-        create?.apply state, arguments
+        create?.apply(state, arguments)
         interstitial.bringToTop()
-        @game.add.tween interstitial
-          .to properties, duration, easing, yes
-          .onComplete.addOnce destroy
+        @game.add.tween(interstitial)
+          .to(properties, duration, easing, yes)
+          .onComplete.addOnce(destroy)
         return

-      _.delay destroy, 2 * duration # In case of failure.
-
-      @start.apply @, startArgs
+      _.delay(destroy, 2 * duration) # In case of failure.
+
+      @start.apply(@, startArgs)
       return

   # TextMixin
@@ -299,9 +299,9 @@
   TextMixin =

     addCenteredText: (text, layout, style, group) ->
-      _.defaults style, { boundsAlignH: 'center', boundsAlignV: 'middle' }
-      text = @add.text @world.centerX, layout.y, text, style, group
-      text.anchor.setTo 0.5
+      _.defaults(style, { boundsAlignH: 'center', boundsAlignV: 'middle' })
+      text = @add.text(@world.centerX, layout.y, text, style, group)
+      text.anchor.setTo(0.5)
       layout.y += text.height + layout.baseline
       text

@@ -310,7 +310,7 @@

   isPlainObject = (arg) -> _.isObject(arg) and not _.isFunction(arg)

-  _.mixin { isPlainObject }
+  _.mixin({ isPlainObject })

   # Export.


Correct dependency ordering

Correct dependency ordering

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -4,8 +4,8 @@
 gulp       = require('gulp')
 header     = require('./header')
 mocha      = require('gulp-mocha')
+pkg        = require('./package.json')
 plumber    = require('gulp-plumber')
-pkg        = require('./package.json')
 rename     = require('gulp-rename')
 uglify     = require('gulp-uglify')


Moved to ff 32.

Moved to ff 32.

--- app/assets/javascripts/app/lib/app_post/browser.coffee
+++ app/assets/javascripts/app/lib/app_post/browser.coffee
@@ -30,7 +30,7 @@
     # define min. required browser version
     map =
       Chrome2: 37
-      Firefox: 28
+      Firefox: 32
       Explorer: 10
       Safari: 6
       Opera: 22


fixing error with con label

fixing error with con label

Former-commit-id: 5ed93ea40642db32e7dbca7655cb4a45cd09371d [formerly 34219c0a4e5565091ee372e0638ec0ae5af30fab] [formerly 3a95ca6e9b30fd64681dc24b2e0fafbbebd3bf50 [formerly 3a95ca6e9b30fd64681dc24b2e0fafbbebd3bf50 [formerly debc91f6d2caabb4a28a6922d77232da7fe0d1ba]]]
Former-commit-id: eb237f88547f565df02c061b393c0ef78809a414 [formerly e885d885c0f754222d995921b187d98b8a97cf6e]
Former-commit-id: 977ce1c53b7802529fc1b06f04e595f32849ee4c
Former-commit-id: ddcd457125a5543ef424d39f765dd210dc1216a3

--- app/assets/javascripts/backbone/entities/tenant.coffee
+++ app/assets/javascripts/backbone/entities/tenant.coffee
@@ -60,7 +60,7 @@

     _getLabel : (is_pro, capitalize, plural) ->
       label = if is_pro then @get('pro_label') else @get('con_label')
-      if plural && label == 'pro' || label == 'con' #TODO: better solution
+      if plural && (label == 'pro' || label == 'con') #TODO: better solution
         label = label + 's'
       if capitalize
         label = label.charAt(0).toUpperCase() + label.slice(1)


Preserve date instance for initialMonth on course date calendar - #294

--- app/assets/javascripts/components/common/calendar.cjsx
+++ app/assets/javascripts/components/common/calendar.cjsx
@@ -6,6 +6,12 @@

 Calendar = React.createClass(
   displayName: 'Calendar'
+  getInitialState: ->
+    return initialMonth: moment(@props.course.start).toDate()
+  componentWillReceiveProps: (nextProps) ->
+    if nextProps.course.start != moment(@state.initialMonth).format('YYYY-MM-DD')
+      @setState
+        initialMonth: moment(nextProps.course.start).toDate()
   selectDay: (e, day) ->
     return unless @inrange(day)
     course = @props.course
@@ -91,7 +97,7 @@
             modifiers={modifiers}
             onDayClick={if @props.editable then @selectDay else null}
             onWeekdayClick={if @props.editable then @selectWeekday else null}
-            initialMonth={moment.max(moment(@props.course.start), moment()).toDate()}
+            initialMonth={@state.initialMonth}
           />
           <div className='course-dates__calendar-key'>
             <ul>


kill error messages

kill error messages

--- app/assets/javascripts/lib/utils/error_messages.coffee
+++ app/assets/javascripts/lib/utils/error_messages.coffee
@@ -1,11 +0,0 @@
-define ->
-
-  class errorMessages
-
-    systemError : ->
-      error = '
' +
-                '' +
-                '
' +
-                  '
Oops… something broke. Please try again.
' +
-                '
' +
-              '
'


change test to 19 not 20 as a stub in mongo is left behind


fix issues with headers not getting passed

--- app/assets/javascripts/monster_mithril/core/api.coffee
+++ app/assets/javascripts/monster_mithril/core/api.coffee
@@ -26,27 +26,30 @@
     fd.append parameter_name(root), value
   fd

+# AB - the headers isn't even getting passed through,
+# this solution will not do
 request_wrap = (method)=>
   #success and error callbacks can be set via returned promise
   (url, data, iso_path)=>
-    ApiBase._request iso_path, method, url, data, undefined, undefined
+    ApiBase._request iso_path, method, url, undefined, data, undefined, undefined

 class ApiBase
   @preload = (typeof _isomorphic != 'undefined')
-  @custom_headers = {}

-  get: request_wrap('GET')
-  post: request_wrap('POST')
-  put: request_wrap('PUT')
-  delete: request_wrap('DELETE')
+  get    : request_wrap 'GET'
+  post   : request_wrap 'POST'
+  put    : request_wrap 'PUT'
+  delete : request_wrap 'DELETE'

-  @_request: (iso_path, method, url, data, success, error) =>
+  @_request: (iso_path, method, url, headers, data, success, error) =>
     deferred = m.deferred()
     iso_pathless = (iso_path == undefined)

     config = (xhr)=>
-      for k, v of @custom_headers
-        xhr.setRequestHeader k, v
+      for k, v of headers
+        vv = v
+        vv = v() if typeof v is 'function'
+        xhr.setRequestHeader k, vv
       xhr

     ev_success = (data)->
@@ -97,26 +100,19 @@
       only = {index: false, new: false, create: false, show: false, edit: false, update: false, destroy: false}
       only[o] = true for o in options.split(' ')
     @[tn] = {}
-    if only.index
-      @[tn].index = (params,success,error)=> ApiBase._request "#{tn}/index", 'GET', @path([tn], ns), params, success,error
-    if only.new
-      @[tn].new = (params,success,error)=> ApiBase._request "#{tn}/new", 'GET', @path([tn,'new'], ns), params, success,error
-    if only.create
-      @[tn].create = (params,success,error)=> ApiBase._request "#{tn}/create", 'POST', @path([tn], ns), params, success,error
-    if only.show
-      @[tn].show = (model,params,success,error)=> ApiBase._request "#{tn}/show", 'GET', @path([tn,@_extract_id(model)], ns), params, success,error
-    if only.edit
-      @[tn].edit = (model,params,success,error)=> ApiBase._request "#{tn}/edit", 'GET', @path([tn,@_extract_id(model),'edit'], ns), params, success,error
-    if only.update
-      @[tn].update = (model,params,success,error)=> ApiBase._request "#{tn}/update", 'PUT', @path([tn,@_extract_id(model)], ns), params, success,error
-    if only.destroy
-      @[tn].destroy = (model,params,success,error)=> ApiBase._request "#{tn}/destroy", 'DELETE', @path([tn,@_extract_id(model)], ns), params, success,error
+    @[tn].index   = (pms,suc,err)=>       ApiBase._request "#{tn}/index"  , 'GET'   , @path([tn], ns)                           , @headers, pms, suc,err if only.index
+    @[tn].new     = (pms,suc,err)=>       ApiBase._request "#{tn}/new"    , 'GET'   , @path([tn,'new'], ns)                     , @headers, pms, suc,err if only.new
+    @[tn].create  = (pms,suc,err)=>       ApiBase._request "#{tn}/create" , 'POST'  , @path([tn], ns)                           , @headers, pms, suc,err if only.create
+    @[tn].show    = (model,pms,suc,err)=> ApiBase._request "#{tn}/show"   , 'GET'   , @path([tn,@_extract_id(model)], ns)       , @headers, pms, suc,err if only.show
+    @[tn].edit    = (model,pms,suc,err)=> ApiBase._request "#{tn}/edit"   , 'GET'   , @path([tn,@_extract_id(model),'edit'], ns), @headers, pms, suc,err if only.edit
+    @[tn].update  = (model,pms,suc,err)=> ApiBase._request "#{tn}/update" , 'PUT'   , @path([tn,@_extract_id(model)], ns)       , @headers, pms, suc,err if only.update
+    @[tn].destroy = (model,pms,suc,err)=> ApiBase._request "#{tn}/destroy", 'DELETE', @path([tn,@_extract_id(model)], ns)       , @headers, pms, suc,err if only.destroy
     @_collection tn,action,method,ns for action,method of options.collection
     @_member     tn,action,method,ns for action,method of options.member
   _collection:(tn,a,method,ns)=>
-    @[tn][a] = (params,success,error)=> ApiBase._request "#{tn}/#{a}", method.toUpperCase(), @path([tn, a], ns), params, success, error
+    @[tn][a] = (params,success,error)=> ApiBase._request "#{tn}/#{a}", method.toUpperCase(), @path([tn, a], ns), @headers, params, success, error
   _member:(tn,a,method,ns)=>
-    @[tn][a] = (model,params,success,error)=> ApiBase._request "#{tn}/#{a}", method.toUpperCase(), @path([tn, model.id, a], ns), params, success, error
+    @[tn][a] = (model,params,success,error)=> ApiBase._request "#{tn}/#{a}", method.toUpperCase(), @path([tn, model.id, a], ns), @headers, params, success, error
   constructor:()->
     @_resource table_name, options for table_name,options of @resources
 window.ApiBase = ApiBase


Updated Cart.shippingRates to reload order data

--- app/assets/javascripts/sprangular/services/cart.coffee
+++ app/assets/javascripts/sprangular/services/cart.coffee
@@ -88,8 +88,10 @@
         country_id: options.countryId
         state_id: options.stateId
         zipcode: options.zipcode
+        use_billing: @current.shipToBillAddress

-      $http.get('/api/shipping_rates', {params: params, ignoreLoadingIndicator: true, class: Sprangular.ShippingRate})
+      $http.get('/api/shipping_rates', {params: params, ignoreLoadingIndicator: true})
+        .success(@load)

     clear:                   -> @current.clear()
     totalQuantity:           -> @current.totalQuantity()


remove alternate binding

remove alternate binding

--- app/assets/javascripts/ultimate/backbone/app.js.coffee
+++ app/assets/javascripts/ultimate/backbone/app.js.coffee
@@ -7,7 +7,6 @@
   warnOnMultibind: true
   preventMultibind: false
   performanceReport: true
-  alternateBinding: false

   Models: {}
   Collections: {}
@@ -33,7 +32,7 @@
       else
         cout 'warn', 'performance.now() isnt available'
         @performanceReport = null
-    bindedViewsCount = (if @alternateBinding then @___bindViews() else @bindViews()).length
+    bindedViewsCount = @bindViews().length
     if @performanceReport
       performanceViews = performance.now()
       cout 'info', "Binded #{bindedViewsCount} view#{if bindedViewsCount is 1 then '' else 's'} in #{Math.round((performanceViews - performanceStart) * 1000)}\u00B5s"
@@ -51,19 +50,6 @@
         if @canBind(el, viewClass)
           view = new viewClass(el: el)
           cout 'info', "Binded view #{viewName}:", view
-          @viewInstances.push view
-          bindedViews.push view
-    bindedViews
-
-  ___bindViews: (jRoot = $('html')) ->
-    bindedViews = []
-    allSelectors = (viewClass::el for viewName, viewClass of @Views when viewClass::el).join(', ')
-    jAllElements = jRoot.find(allSelectors)
-    for viewName, viewClass of @Views when viewClass::el
-      jAllElements.filter(viewClass::el).each (index, el) =>
-        if @canBind(el, viewClass)
-          view = new viewClass(el: el)
-          cout 'info', "Alternate! Binded view #{viewName}:", view
           @viewInstances.push view
           bindedViews.push view
     bindedViews


Add specs for handling multiple coordinate sets

--- spec/path-parser-spec.coffee
+++ spec/path-parser-spec.coffee
@@ -232,6 +232,60 @@
     expect(getXY(subject.nodes[3].handleIn, 'x', 'y')).toEqual x: -30, y: -70
     expect(subject.nodes[3].handleOut).toBeUndefined()

+  it 'parses S and s commands when multiple coordinate sets specified', ->
+    path = 'M10 80 C 40 10, 65 10, 100 100 s 50 50, 80-20 55-70, 85 0S300,400 350,320 500,100 600,320'
+    parsedPath = PathParser.parsePath(path)
+    expect(parsedPath.subpaths.length).toEqual 1
+
+    subject = parsedPath.subpaths[0]
+    expect(subject.closed).toEqual false
+    expect(subject.nodes.length).toEqual 6
+
+    expect(getXY(subject.nodes[0].point, 'x', 'y')).toEqual x: 10, y: 80
+    expect(subject.nodes[0].handleIn).toBeUndefined()
+    expect(getXY(subject.nodes[0].handleOut, 'x', 'y')).toEqual x: 30, y: -70
+
+    expect(getXY(subject.nodes[1].point, 'x', 'y')).toEqual x: 100, y: 100
+    expect(getXY(subject.nodes[1].handleIn, 'x', 'y')).toEqual x: -35, y: -90
+    expect(getXY(subject.nodes[1].handleOut, 'x', 'y')).toEqual x: 35, y: 90
+
+    expect(getXY(subject.nodes[2].point, 'x', 'y')).toEqual x: 180, y: 80
+    expect(getXY(subject.nodes[2].handleIn, 'x', 'y')).toEqual x: -30, y: 70
+    expect(getXY(subject.nodes[2].handleOut, 'x', 'y')).toEqual x: 30, y: -70
+
+    expect(getXY(subject.nodes[3].point, 'x', 'y')).toEqual x: 265, y: 80
+    expect(getXY(subject.nodes[3].handleIn, 'x', 'y')).toEqual x: -30, y: -70
+    expect(getXY(subject.nodes[3].handleOut, 'x', 'y')).toEqual x: 30, y: 70
+
+    expect(getXY(subject.nodes[4].point, 'x', 'y')).toEqual x: 350, y: 320
+    expect(getXY(subject.nodes[4].handleIn, 'x', 'y')).toEqual x: -50, y: 80
+    expect(getXY(subject.nodes[4].handleOut, 'x', 'y')).toEqual x: 50, y: -80
+
+    expect(getXY(subject.nodes[5].point, 'x', 'y')).toEqual x: 600, y: 320
+    expect(getXY(subject.nodes[5].handleIn, 'x', 'y')).toEqual x: -100, y: -220
+    expect(subject.nodes[5].handleOut).toBeUndefined()
+
+  it 'parses C and c commands when multiple coordinate sets specified', ->
+    path = 'M10 80 C 40 10, 65 10, 100 100 45 15, 70 15, 105 105 '
+    parsedPath = PathParser.parsePath(path)
+    expect(parsedPath.subpaths.length).toEqual 1
+
+    subject = parsedPath.subpaths[0]
+    expect(subject.closed).toEqual false
+    expect(subject.nodes.length).toEqual 3
+
+    expect(getXY(subject.nodes[0].point, 'x', 'y')).toEqual x: 10, y: 80
+    expect(subject.nodes[0].handleIn).toBeUndefined()
+    expect(getXY(subject.nodes[0].handleOut, 'x', 'y')).toEqual x: 30, y: -70
+
+    expect(getXY(subject.nodes[1].point, 'x', 'y')).toEqual x: 100, y: 100
+    expect(getXY(subject.nodes[1].handleIn, 'x', 'y')).toEqual x: -35, y: -90
+    expect(getXY(subject.nodes[1].handleOut, 'x', 'y')).toEqual x: -55, y: -85
+
+    expect(getXY(subject.nodes[2].point, 'x', 'y')).toEqual x: 105, y: 105
+    expect(getXY(subject.nodes[2].handleIn, 'x', 'y')).toEqual x: -35, y: -90
+    expect(subject.nodes[2].handleOut).toBeUndefined()
+
   it 'parses Q and T commands', ->
     path = 'M10 80 Q 52.5 10, 95 80 T 180 80'
     parsedPath = PathParser.parsePath(path)


New Google values that fix login

New Google values that fix login

--- accounts/providers.coffee
+++ accounts/providers.coffee
@@ -1,2 +1,2 @@
-Meteor.accounts.google.config "592591295899-6pclrnrc9p8tig26c3l7l4qu7nj81knp.apps.googleusercontent.com", "http://localhost:3000"
-Meteor.accounts.facebook.config "153991541402175", "http://localhost:3000"
+Meteor.accounts.google.config "592591295899-bf7snrv07q0ee1l7abp8rs2s1trul0ip.apps.googleusercontent.com", "http://meta.socrench.us:3000"
+Meteor.accounts.facebook.config "237173219733778", "http://meta.socrench.us:3000"


Fix re-editing of reviews not showing newly created embeds

--- resources/assets/coffee/react/beatmap-discussions/post.coffee
+++ resources/assets/coffee/react/beatmap-discussions/post.coffee
@@ -115,7 +115,7 @@
                 el Editor,
                   beatmapset: @props.beatmapset
                   beatmaps: beatmaps
-                  document: @state.message
+                  document: @props.post.message
                   discussion: @props.discussion
                   discussions: discussions
                   editMode: true


oops fixed string axes

oops fixed string axes

--- src/viz/types/helpers/graph/includes/plot.coffee
+++ src/viz/types/helpers/graph/includes/plot.coffee
@@ -114,7 +114,7 @@
     "font-size":   vars.y.ticks.font.size+"px"
     "font-family": vars.y.ticks.font.family.value
     "font-weight": vars.y.ticks.font.weight
-  yValues = vars.x.ticks.visible
+  yValues = vars.y.ticks.visible
   if vars.y.scale.value is "log"
     yText = yValues.map (d) -> formatPower d
   else if vars.y.scale.value is "share"


cleanurls selective

cleanurls selective

--- docpad.coffee
+++ docpad.coffee
@@ -41,13 +41,17 @@
   collections:
     posts: ->
       @getCollection("html").findAllLive({write:true}).on 'add', (model) ->
-        model.setMetaDefaults({layout:"default"})
+        model.setMetaDefaults({layout:"default", cleanurls: true})
+    cleanurls: ->
+      @getCollection("html").findAllLive({cleanurls: $ne: false})
+
   plugins:
     ghpages:
       deployRemote:"origin"
       deployBranch:"master"
     cleanurls:
       trailingSlashes: true
+      collectionName: "cleanurls"
     marked:
       markedOptions:
         pedantic: false


do not force to production

do not force to production

--- docpad.coffee
+++ docpad.coffee
@@ -10,7 +10,7 @@

 	# Change the port DocPad uses from the default 9778 to 8080
 	port: 8080
-	env: "production"
+	#env: "production"

 	templateData:


Reverse sorting of blog posts.

Reverse sorting of blog posts.

--- docpad.coffee
+++ docpad.coffee
@@ -126,7 +126,7 @@
   collections:

     blog: (database) ->
-      database.findAllLive({layout:$has:'blog-post'}, [date:-1]).on 'add', (document) ->
+      database.findAllLive({layout:$has:'blog-post'}, [date:1]).on 'add', (document) ->
         a = document.attributes
         contentPreview = a.content.substring(0,150)
         contentPreview = contentPreview + " ..."


Fix oversight with package casing.

Fix oversight with package casing.

--- indexer.coffee
+++ indexer.coffee
@@ -17,7 +17,7 @@
 		conda_output = child_process.execSync ' conda search ".*" --names-only '
 		names = _.without(conda_output.toString().split('\n').slice(1), '')  # skip first line of output
 		(result[name] = {
-			name: name,
+			name: name.trim(),
 			title: name
 			description: null,
 			provider:
@@ -54,8 +54,9 @@
 						return callback err, null
 					packages = {}
 					for p in results
-						packages[p.name] =
-							name: p.name.toLowerCase()  # Because pip is case-insensitive, coerce to lowercase
+						name = p.name.toLowerCase().trim()
+						packages[name] =
+							name: name # Because pip is case-insensitive, coerce to lowercase
 							title: p.name
 							description: p.details.info.description or null
 							provider:
@@ -85,16 +86,15 @@
 			console.log ">> both: #{pip_and_conda.length}"

 			python_packages = {}
+			for name in pip_only
+				python_packages[name] = pip_packages[name]
 			for name in conda_only
 				python_packages[name] = conda_packages[name]
-
 			for name in pip_and_conda
 				p = _.extend({}, pip_packages[name])
 				p.provider.source = 'conda'
 				python_packages[name] = p

-			for name in pip_only
-				python_packages[name] = pip_packages[name]

 			callback(null, python_packages)


Remove unnecessary path recalculations

Remove unnecessary path recalculations

--- src/js/Game.coffee
+++ src/js/Game.coffee
@@ -210,13 +210,11 @@
     # Removing all dragged blocks as they may be drawn elsewhere
     # FIXME: This is only necessary if the position or state of the dragged
     #        blocks actually changed
-    changed = no
+    didRemoveBlocks = no
     @map.blocksEach (block) =>
       if block.dragged
         @map.removeBlock block.getCoordinates()..., yes
-        changed = yes
-
-    @map.forceUpdate() if changed
+        didRemoveBlocks = yes

     mouseX = event.pageX - @mainCanvas.offset().left
     mouseY = event.pageY - @mainCanvas.offset().top
@@ -254,6 +252,7 @@

     # otherwise, display the dragged canvas and move it to the mouse position
     else
+      @map.forceUpdate() if didRemoveBlocks
       @showDraggedCanvas event

   # Starts a drag operation using with a given event.


Add some inline documentation

Add some inline documentation

--- src/js/main.coffee
+++ src/js/main.coffee
@@ -11,10 +11,16 @@
   Fixtures = require './view/fixtures'

-  class TestWrapper
+  class TestRunner
+    ###
+    The main Sugarspoon testrunner
+
+    Provides a public API for running a series of Mocha unit test suites. Actual
+    implementation is delegated to either a straight-up Mocha test runner or a
+    Blanket coverage runner.
+    ###

     views: {}
-    data: {}

     constructor: (options = {}) ->
       @config = do ->


add support for controllers destroy event

--- src/manager.coffee
+++ src/manager.coffee
@@ -12,14 +12,14 @@
 #
 #  users.active();
 #  assert( users.isActive() );
-#  assert( users.el.hasClass("active") );
-#  assert( ! groups.el.hasClass("active") );
+#  assert( users.el.hasClass('active') );
+#  assert( ! groups.el.hasClass('active') );
 #
 #  groups.active();
-#  assert( groups.el.hasClass("active") );
-#  assert( ! users.el.hasClass("active") );
+#  assert( groups.el.hasClass('active') );
+#  assert( ! users.el.hasClass('active') );

-Spine ?= require("spine")
+Spine ?= require('spine')
 $      = Spine.$

 class Spine.Manager extends Spine.Module
@@ -28,19 +28,21 @@
   constructor: ->
     @controllers = []
     @add(arguments...)
-    @bind "change", @change
+    @bind 'change', @change

   add: (controllers...) ->
     @addOne(cont) for cont in controllers

   addOne: (controller) ->
-    controller.active (args...) =>
-      @trigger("change", controller, args)
+    controller.bind 'active', (args...) =>
+      @trigger('change', controller, args)
+    controller.bind 'destroy', =>
+      @controllers.splice(@controllers.indexOf(controller), 1)

     @controllers.push(controller)

   deactivate: ->
-    @trigger("change", false, arguments)
+    @trigger('change', false, arguments)

   # Private

@@ -53,22 +55,22 @@

 Spine.Controller.include
   active: (args...) ->
-    if typeof args[0] is "function"
-      @bind("active", args[0])
+    if typeof args[0] is 'function'
+      @bind('active', args[0])
     else
-      args.unshift("active")
+      args.unshift('active')
       @trigger(args...)
     @

   isActive: ->
-    @el.hasClass("active")
+    @el.hasClass('active')

   activate: ->
-    @el.addClass("active")
+    @el.addClass('active')
     @

   deactivate: ->
-    @el.removeClass("active");
+    @el.removeClass('active');
     @

 module?.exports = Spine.Manager


Tweak to the local disable refresh setting

--- SingularityUI/app/lib/router.coffee
+++ SingularityUI/app/lib/router.coffee
@@ -37,7 +37,7 @@

 $(window).on 'focus', ->
     windowBlurred = false
-    refresh()
+    refresh() unless env.disablePageRefresh is true

 nav = ->
     if not app.views.navigationView?
@@ -48,11 +48,11 @@
 globalRefresh = ->
     clearTimeout(window.globalRefreshTimeout) if window.globalRefreshTimeout
     window.globalRefreshTimeout = setInterval ->
-        refresh()
+        refresh() unless env.disablePageRefresh is true
     , 20 * 1000

 refresh = ->
-    if not $('body > .vex').length and not windowBlurred and env.disablePageRefresh isnt true
+    if not $('body > .vex').length and not windowBlurred
         app.views.current?.refresh?()

 class Router extends Backbone.Router


Properly handle errors

Properly handle errors

--- demo_server/src/test-error.coffee
+++ demo_server/src/test-error.coffee
@@ -53,7 +53,6 @@
         try
             cursor_server = eval(@queries[@index])
         catch err
-            debugger
             @callback_server err.toString()
             return ''

@@ -78,6 +77,12 @@
                 @result_server[0].generated_keys = [ '...' ]

         if found_error_in_generated_keys is false
+            # We need to overwrite prototype or underscore will unmatch the objects if they are errors.
+            for result in @result_demo
+                result?.__proto__ = Object.prototype
+            for result in @result_server
+                result?.__proto__ = Object.prototype
+
             if _.isEqual(@result_demo, @result_server)
                 @display
                     query: @queries[@index]
@@ -120,7 +125,7 @@
 $(document).ready ->
     # Some tests are commented because the real server doesn't implement (yet) object comparison.
     queries = [
-        'r.db("test").table("table_that_does_not_exist").run()',
+        'r.db("testsasdasaad").table("table_that_does_not_exist").run()',
         'r.expr(true).run()',
         'r.expr(false).run()',
         'r.expr(132).run()',


Fix validateMoneyByYears multiple conditionals

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -157,8 +157,13 @@
         @addErrorClass($(".question-block[data-answer='overseas_sales-total-overseas-sales']"))

   validateMoneyByYears: (question) ->
-    for subquestion in question.find(".js-conditional-question.show-question input")
-      if $(subquestion).closest(".js-conditional-question").hasClass("show-question")
+    for subquestion in question.find("input")
+      shown_question = true
+      for conditional in $(subquestion).parents('.js-conditional-question')
+        if !$(conditional).hasClass('show-question')
+          shown_question = false
+
+      if shown_question
         subq = $(subquestion)

         if not subq.val() and question.hasClass("question-required")


Whoops. Lowercase unigrams.

Whoops. Lowercase unigrams.

--- app/TokenBinStream.coffee
+++ app/TokenBinStream.coffee
@@ -17,7 +17,7 @@
     @tokenBin = new TokenBin([])

   addDocumentText: (text) ->
-    tokens = tokenize(text)
+    tokens = tokenize(text.toLowerCase())
     @tokenBin.addTokens(tokens)

   getTokensByFrequency: ->


Fix omission from 3768b54aa1ac12cdd809e7108a6a9fd629a1164f.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -381,6 +381,7 @@
   onConnect: (sender, port) ->
     [tabId, frameId] = [sender.tab.id, sender.frameId]
     # We always add frameId 0, the top frame, automatically, and never unregister it.
+    frameIdsForTab[tabId]?
     frameIdsForTab[tabId] ?= [0]
     frameIdsForTab[tabId].push frameId unless frameId == 0
     port.postMessage name: "registerFrameId", chromeFrameId: frameId
@@ -391,14 +392,14 @@
       # then we'll tidy up in the chrome.tabs.onRemoved listener, below.  This approach avoids any dependency
       # on the order in which register and unregister events happens (on navigation, a new top frame
       # registering before the old one is deregistered).
-      if tabId of frameIdsForTab and tabId != 0
+      if tabId of frameIdsForTab and frameId != 0
         frameIdsForTab[tabId] = frameIdsForTab[tabId].filter (fId) -> fId != frameId
       port.onDisconnect.removeListener listener

 handleFrameFocused = (request, sender) ->
   [tabId, frameId] = [sender.tab.id, sender.frameId]
   # This might be the first time we've heard from this tab.
-  frameIdsForTab[tabId] ?= []
+  frameIdsForTab[tabId] ?= [0]
   # Cycle frameIdsForTab to the focused frame.
   frameIdsForTab[tabId] = cycleToFrame frameIdsForTab[tabId], frameId
   # Inform all frames that a frame has received the focus.


bubbles: moving inner functions out to the top level

--- app/charts/bubbles.coffee
+++ app/charts/bubbles.coffee
@@ -307,7 +307,6 @@

-    idToNode = {}

     provideNodesWithTotals(data.flows, data.nodes, conf)

@@ -382,20 +381,28 @@

     #nodesWithoutLocationX = 0

-    nodes = nodesWithFlows.map (d) ->
-      xy = projectNode(d)
-
-      maxin = d3.max(d.totals.inbound ? [0])
-      maxout = d3.max(d.totals.outbound ? [0])
-
-      idToNode[d[conf.nodeIdAttr]] =
-        data : d
-        max : Math.max(maxin, maxout)
-        name: d[conf.nodeLabelAttr]
-        code: d[conf.nodeIdAttr]
-        x: xy?[0]
-        y: xy?[1]
-        gravity: {x: xy?[0], y: xy?[1]}
+    nodes = do ->
+      idToNode = {}
+      nodesArr = nodesWithFlows.map (d) ->
+        xy = projectNode(d)
+
+        maxin = d3.max(d.totals.inbound ? [0])
+        maxout = d3.max(d.totals.outbound ? [0])
+
+        n =
+          data : d
+          max : Math.max(maxin, maxout)
+          name: d[conf.nodeLabelAttr]
+          code: d[conf.nodeIdAttr]
+          x: xy?[0]
+          y: xy?[1]
+          gravity: {x: xy?[0], y: xy?[1]}
+
+        idToNode[d[conf.nodeIdAttr]] = n
+        return n
+
+      nodesArr.nodeById = (id) -> idToNode[id]
+      nodesArr

@@ -403,11 +410,11 @@
     placeNodesWithoutCoords(nodes)

-    nodeById = (id) -> idToNode[id]
+

     for f in data.flows
-      src = nodeById(f[conf.flowOriginAttr])
-      target = nodeById(f[conf.flowDestAttr])
+      src = nodes.nodeById(f[conf.flowOriginAttr])
+      target = nodes.nodeById(f[conf.flowDestAttr])
       if src? and target?
         link =
           source: src
@@ -436,119 +443,6 @@

     selectedNode = null
-
-    showFlowsOf = (bbl) ->
-      d = d3.select(bbl).data()?[0]
-      ###
-      ffwscale = d3.scale.linear()
-        .range([0, rscale.range()[1]/2])
-        .domain([0, Math.max(d3.max(max.inbound), d3.max(max.outbound))])
-      ###
-
-      if (d.outlinks?)
-        flows.selectAll("line.out")
-            .data(d.outlinks) #.filter (d) -> flowLineValue(d) > 0)
-          .enter().append("svg:line")
-            .attr("class", "out")
-            #.attr("stroke", outboundColor)
-            .attr("opacity", 0)
-            .transition()
-              .duration(300)
-                .attr("opacity", 1)
-
-
-      if (d.inlinks?)
-        flows.selectAll("line.in")
-            .data(d.inlinks) #.filter (d) -> flowLineValue(d) > 0)
-          .enter().append("svg:line")
-            .attr("class", "in")
-            #.attr("stroke", inboundColor)
-            #.attr("opacity",0.5)
-            .attr("opacity", 0)
-            .transition()
-              .duration(300)
-                .attr("opacity", 1)
-
-      createFlowTimeSeries = (flow, d, i) ->
-
-        dir = (if d3.select(flow).classed("in") then "in" else "out")
-
-        flowData = (for attr in state.magnAttrs() when d.data[attr]?
-          date: dateFromMagnAttr(attr)
-          value: +d.data[attr]
-        )
-
-        data = if dir is "in"
-          inbound : flowData
-          outbound : []
-        else
-          inbound : []
-          outbound : flowData
-
-
-        if not tseriesPanel.contains("flow" + i)
-          tseries = tseriesPanel.addNew("flow" + i, "tseries")
-          recipient = d.target.data[conf.nodeLabelAttr]
-          donor = d.source.data[conf.nodeLabelAttr]
-
-          flowLabel = shortenLabel(donor, 20) + " -> " + shortenLabel(recipient, 20)
-
-          createTimeSeries(tseries, data, flowLabel, dir)
-
-
-
-      flows.selectAll("line")
-        .attr("x1", (d) -> d.source.x)
-        .attr("y1", (d) -> d.source.y)
-        .attr("x2", (d) -> d.target.x)
-        .attr("y2", (d) -> d.target.y)
-        .attr("stroke-width", (d) -> fwscale(flowLineValue(d)))
-        .attr("visibility", (d) -> if flowLineValue(d) > 0 then "visible" else "hidden")
-        .on "mouseover", (d, i) ->
-          if this.parentNode?
-            this.parentNode.appendChild(this)
-
-          $(this).tipsy("show")
-          # $("#tseriesPanel").add('

')
-
-          createFlowTimeSeries(this, d, i)
-
-          $(tipsy.$tip)
-              .css('top', d3.event.pageY-($(tipsy.$tip).outerHeight()/2))
-              .css('left', d3.event.pageX + 10)
-
-
-        .on "mouseout", (d, i) ->
-          $(this).tipsy("hide")
-          tseriesPanel.remove("flow" + i)
-
-
-      $('line').tipsy
-        gravity: 'w'
-        opacity: 0.9
-        html: true
-        trigger: "manual"
-        title: ->
-          flowTooltip(d3.select(this).data()[0])
-
-
-
-    createNodeTimeSeries = (node, d, i) ->
-      data =
-        inbound : state.magnAttrs().map (attr, i) ->
-          date: dateFromMagnAttr(attr)
-          value: d.data.totals?.inbound?[i] ? 0
-
-        outbound : state.magnAttrs().map (attr, i) ->
-          date: dateFromMagnAttr(attr)
-          value: d.data.totals?.outbound?[i] ? 0
-
-      if not tseriesPanel.contains("node" + i)
-        tseries = tseriesPanel.addNew("node" + i, "tseries")
-        nodeLabel = d3.select(node).data()[0][conf.nodeLabelAttr]
-        #parent = d3.select(tseries)
-        #parent.setSelDateTo = setSelDateTo
-        createTimeSeries(tseries, data, shortenLabel(nodeLabel, 40))

     bubble = svg.selectAll("g.bubble")
@@ -702,6 +596,124 @@
           utils.date.yearToDate(state.magnAttrs(old)))
       #$("#yearSlider").slider('value', state.selAttrIndex)
     chart
+
+
+
+
+
+  showFlowsOf = (bbl) ->
+    flows = svg.selectAll("g.flows")
+    d = d3.select(bbl).datum() #data()?[0]
+    ###
+    ffwscale = d3.scale.linear()
+      .range([0, rscale.range()[1]/2])
+      .domain([0, Math.max(d3.max(max.inbound), d3.max(max.outbound))])
+    ###
+
+    if (d.outlinks?)
+      flows.selectAll("line.out")
+          .data(d.outlinks) #.filter (d) -> flowLineValue(d) > 0)
+        .enter().append("svg:line")
+          .attr("class", "out")
+          #.attr("stroke", outboundColor)
+          .attr("opacity", 0)
+          .transition()
+            .duration(300)
+              .attr("opacity", 1)
+
+
+    if (d.inlinks?)
+      flows.selectAll("line.in")
+          .data(d.inlinks) #.filter (d) -> flowLineValue(d) > 0)
+        .enter().append("svg:line")
+          .attr("class", "in")
+          #.attr("stroke", inboundColor)
+          #.attr("opacity",0.5)
+          .attr("opacity", 0)
+          .transition()
+            .duration(300)
+              .attr("opacity", 1)
+
+
+    flows.selectAll("line")
+      .attr("x1", (d) -> d.source.x)
+      .attr("y1", (d) -> d.source.y)
+      .attr("x2", (d) -> d.target.x)
+      .attr("y2", (d) -> d.target.y)
+      .attr("stroke-width", (d) -> fwscale(flowLineValue(d)))
+      .attr("visibility", (d) -> if flowLineValue(d) > 0 then "visible" else "hidden")
+      .on "mouseover", (d, i) ->
+        if this.parentNode?
+          this.parentNode.appendChild(this)
+
+        $(this).tipsy("show")
+        # $("#tseriesPanel").add('

')
+
+        createFlowTimeSeries(this, d, i)
+
+        $(tipsy.$tip)
+            .css('top', d3.event.pageY-($(tipsy.$tip).outerHeight()/2))
+            .css('left', d3.event.pageX + 10)
+
+
+      .on "mouseout", (d, i) ->
+        $(this).tipsy("hide")
+        tseriesPanel.remove("flow" + i)
+
+
+    $('line').tipsy
+      gravity: 'w'
+      opacity: 0.9
+      html: true
+      trigger: "manual"
+      title: -> flowTooltip(d3.select(this).data()[0])
+
+
+
+  createNodeTimeSeries = (node, d, i) ->
+    data =
+      inbound : state.magnAttrs().map (attr, i) ->
+        date: dateFromMagnAttr(attr)
+        value: d.data.totals?.inbound?[i] ? 0
+
+      outbound : state.magnAttrs().map (attr, i) ->
+        date: dateFromMagnAttr(attr)
+        value: d.data.totals?.outbound?[i] ? 0
+
+    if not tseriesPanel.contains("node" + i)
+      tseries = tseriesPanel.addNew("node" + i, "tseries")
+      nodeLabel = d3.select(node).data()[0][conf.nodeLabelAttr]
+      #parent = d3.select(tseries)
+      #parent.setSelDateTo = setSelDateTo
+      createTimeSeries(tseries, data, shortenLabel(nodeLabel, 40))
+
+
+  createFlowTimeSeries = (flow, d, i) ->
+
+    dir = (if d3.select(flow).classed("in") then "in" else "out")
+
+    flowData = (for attr in state.magnAttrs() when d.data[attr]?
+      date: dateFromMagnAttr(attr)
+      value: +d.data[attr]
+    )
+
+    data = if dir is "in"
+      inbound : flowData
+      outbound : []
+    else
+      inbound : []
+      outbound : flowData
+
+
+    if not tseriesPanel.contains("flow" + i)
+      tseries = tseriesPanel.addNew("flow" + i, "tseries")
+      recipient = d.target.data[conf.nodeLabelAttr]
+      donor = d.source.data[conf.nodeLabelAttr]
+
+      flowLabel = shortenLabel(donor, 20) + " -> " + shortenLabel(recipient, 20)
+
+      createTimeSeries(tseries, data, flowLabel, dir)
+

   updateNodeSizes = ->
     for n in nodes


Got initial login test working

Got initial login test working

--- tests/cucumber/features/step_definitions/steps.coffee
+++ tests/cucumber/features/step_definitions/steps.coffee
@@ -17,6 +17,7 @@
     helper = @

     linkXpath = (text) -> "//a[contains(text(),\"#{text}\")]"
+    buttonXpath = (text) -> "//button[contains(text(),\"#{text}\")]"

     @.Given /^I'm on the home page$/, (next) =>
         @.world.browser.url helper.world.cucumber.mirror.rootUrl
@@ -25,19 +26,14 @@
     @.Given /^I'm logged in as a[n]? "([^"]*)"/, (role, next) =>
         @.world.browser
             .executeAsync (done) -> Meteor.logout(done)
-            .waitForVisible linkXpath("sign in")
-            .click linkXpath "sign in"
+            .waitForVisible linkXpath("Sign in")
+            .click linkXpath "Sign in"
             .setValue "#login-username", Data[role].username
             .setValue "#login-password", Data[role].password
-            .waitForVisible linkXpath Data[role].name, 1000, false, next
+            .click '//button[@id="login-buttons-password"]'
+            .waitForExist linkXpath Data[role].name
             .call next

     @.Given /the "([^"]*)" link should be visible./, (text, next) =>
         @.world.browser.waitForVisible linkXpath(text)
-            .call next
-        #getHTML linkXpath(text), (error, actualText) ->
-        #    console.log error, actualText
-        #    if not actualText.match text
-        #        next.fail "#{actualText} did not match #{text}"
-        #    else
-        #        next()+            .call next


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -136,7 +136,7 @@
     position - An x, y object representing the position of the meter
     radius - Border radius of the meter
     showName - Boolean to toggle whether or not to show the attribute associated with the meter
-    text - A String to display over the meter. Overrides
+    text - A String to display over the meter. Overrides default nam
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -165,7 +165,7 @@
     I.meters[name] = options

   ###*
-  Configures a meter to be drawn each overlay event.
+  Shows

   player = GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -125,7 +125,7 @@
   @param {String} font Size and style of the meter's font
   @param {Number} height Height of the meter
   @param {Object} position An x, y object representing the position of the meter
-  @param {String} radius - Border radius of the meter
+  @param {Number} radius - Border radius of the meter
     showName - Boolean to toggle whether or not to show the attribute associated with the meter
     text - A String to display over the meter. Overrides default name attribute
     width - How wide the meter is


fixing imports, not sure how this worked before...

--- src/methods.coffee
+++ src/methods.coffee
@@ -9,7 +9,9 @@
 types = require './types'
 natives = require './natives'
 runtime = require './runtime'
-{log,vtrace,trace,debug,error,debug_vars} = require './logging'
+logging = require './logging'
+{vtrace,trace,debug_vars} = logging
+{java_throw} = require './exceptions'
 {opcode_annotators} = disassembler
 {str2type,carr2type,c2t} = types
 {native_methods,trapped_methods} = natives
@@ -110,7 +112,7 @@
     while true
       pc = cf.pc
       op = code[pc]
-      unless RELEASE? or util.log_level < util.STRACE
+      unless RELEASE? or logging.log_level < logging.STRACE
         throw "#{@name}:#{pc} => (null)" unless op
         vtrace "#{padding}stack: [#{debug_vars cf.stack}], local: [#{debug_vars cf.locals}]"
         annotation =
@@ -147,7 +149,7 @@
         # method on the most specific type
         obj = caller_stack[caller_stack.length-@param_bytes]
         unless caller_stack.length-@param_bytes >= 0 and obj?
-          util.java_throw runtime_state, 'java/lang/NullPointerException',
+          java_throw runtime_state, 'java/lang/NullPointerException',
             "null 'this' in virtual lookup for #{sig}"
         return runtime_state.method_lookup({
             class: obj.type.toClassString(),
@@ -174,11 +176,11 @@
         trace "#{padding}entering native method #{sig}"
         return @run_manually native_methods[sig], runtime_state
       try
-        util.java_throw runtime_state, 'java/lang/Error', "native method NYI: #{sig}"
+        java_throw runtime_state, 'java/lang/Error', "native method NYI: #{sig}"
       finally
         runtime_state.meta_stack().pop()
     if @access_flags.abstract
-      util.java_throw runtime_state, 'java/lang/Error', "called abstract method: #{sig}"
+      java_throw runtime_state, 'java/lang/Error', "called abstract method: #{sig}"

     # Finally, the normal case: running a Java method
     trace "#{padding}entering method #{sig}"


forgot to rename this one, whoops.

forgot to rename this one, whoops.

--- core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
@@ -83,7 +83,7 @@
   activate:   -> @$el.addClass 'active'
   deActivate: -> @$el.removeClass 'active'

-  resetWheel: ->
+  reset: ->
     @setQuery ''
     @wheel.reset()
     @wheel_region.currentView.render()


Less fat arrows in Utils

Less fat arrows in Utils

--- vendor/assets/javascripts/brainstem/utils.coffee
+++ vendor/assets/javascripts/brainstem/utils.coffee
@@ -4,10 +4,10 @@
   @warn: (args...) ->
     console?.log "Error:", args...

-  @throwError: (message) =>
+  @throwError: (message) ->
     throw new Error("#{Backbone.history.getFragment()}: #{message}")

-  @matches: (obj1, obj2) =>
+  @matches: (obj1, obj2) ->
     if @empty(obj1) && @empty(obj2)
       true
     else if obj1 instanceof Array && obj2 instanceof Array
@@ -19,7 +19,7 @@
     else
       String(obj1) == String(obj2)

-  @empty: (thing) =>
+  @empty: (thing) ->
     if thing == null || thing == undefined || thing == ""
       true
     if thing instanceof Array
@@ -29,12 +29,12 @@
     else
       false

-  @extractArray: (option, options) =>
+  @extractArray: (option, options) ->
     result = options[option]
     result = [result] unless result instanceof Array
     _.compact(result)

-  @wrapObjects: (array) =>
+  @wrapObjects: (array) ->
     output = []
     _(array).each (elem) =>
       if elem.constructor == Object


we do actually look there now, I think

we do actually look there now, I think

--- src/natives.coffee
+++ src/natives.coffee
@@ -95,8 +95,7 @@
   'line.separator':'\n', 'file.separator':'/', 'path.separator':':',
   'user.dir':'.','user.home':'.','user.name':'DoppioUser',
   'os.name':'Doppio',
-  # we don't actually load from this file, but it has to look like a valid filename
-  'sun.boot.class.path': '/System/Library/Frameworks/JavaVM.framework/Classes/classes.jar',
+  'sun.boot.class.path': 'third_party/java_home/lib/rt.jar',
   # this doesn't actually exist in our classes.jar, but no other GraphicsEnvironment does either
   'java.awt.graphicsenv': 'sun.awt.X11GraphicsEnvironment',
   'useJavaUtilZip': 'true'  # hack for sun6javac, avoid ZipFileIndex shenanigans


fix-copy-i18n-after-revert (#5680)

fix-copy-i18n-after-revert (#5680)

* fix-copy-i18n-after-revert

* fix


Run copy-i18n-tags (#5345)

Run copy-i18n-tags (#5345)

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -2849,6 +2849,7 @@
 #    teacher_email: "Teacher's email address"

   school_administrator:
+#    title: "School Administrator Dashboard"
     my_teachers: "我的老师"
     last_login: "上次登录"
     licenses_used: "已使用许可证"


Update zh-HANT.coffee

Update zh-HANT.coffee

--- app/locale/zh-HANT.coffee
+++ app/locale/zh-HANT.coffee
@@ -749,7 +749,7 @@
     level_component_edit_title: "編輯組件"
     level_component_config_schema: "配置模式"
     level_component_settings: "設定"
-    level_system_edit_title: "編輯系統"
+    level_system _edit_title: "編輯系統"
     create_system_title: "Create New System"
     new_component_title: "建立新系統"
     new_component_field_system: "系統"
@@ -778,34 +778,34 @@
     edit_article_title: "編輯文章"

   contribute:
-#    page_title: "Contributing"
-#    intro_blurb: "CodeCombat is 100% open source! Hundreds of dedicated players have helped us build the game into what it is today. Join us and write the next chapter in CodeCombat's quest to teach the world to code!"
-#    alert_account_message_intro: "Hey there!"
-#    alert_account_message: "To subscribe for class emails, you'll need to be logged in first."
-#    archmage_introduction: "One of the best parts about building games is they synthesize so many different things. Graphics, sound, real-time networking, social networking, and of course many of the more common aspects of programming, from low-level database management, and server administration to user facing design and interface building. There's a lot to do, and if you're an experienced programmer with a hankering to really dive into the nitty-gritty of CodeCombat, this class might be for you. We would love to have your help building the best programming game ever."
-#    class_attributes: "Class Attributes"
-#    archmage_attribute_1_pref: "Knowledge in "
-#    archmage_attribute_1_suf: ", or a desire to learn. Most of our code is in this language. If you're a fan of Ruby or Python, you'll feel right at home. It's JavaScript, but with a nicer syntax."
-#    archmage_attribute_2: "Some experience in programming and personal initiative. We'll help you get oriented, but we can't spend much time training you."
-#    how_to_join: "How To Join"
-#    join_desc_1: "Anyone can help out! Just check out our "
-#    join_desc_2: "to get started, and check the box below to mark yourself as a brave Archmage and get the latest news by email. Want to chat about what to do or how to get more deeply involved? "
-#    join_desc_3: ", or find us in our "
-#    join_desc_4: "and we'll go from there!"
-#    join_url_email: "Email us"
-#    join_url_hipchat: "public HipChat room"
-#    archmage_subscribe_desc: "Get emails on new coding opportunities and announcements."
-#    artisan_introduction_pref: "We must construct additional levels! People be clamoring for more content, and we can only build so many ourselves. Right now your workstation is level one; our level editor is barely usable even by its creators, so be wary. If you have visions of campaigns spanning for-loops to"
-#    artisan_introduction_suf: ", then this class might be for you."
-#    artisan_attribute_1: "Any experience in building content like this would be nice, such as using Blizzard's level editors. But not required!"
+    page_title: "貢獻"
+    intro_blurb: "CodeCombat是100%開源! 上百位專注的玩家已經幫助我們建造到今天的程度. 加入我們並完成下一章CodeCombat的冒險給全世界參與!"
+    alert_account_message_intro: "您好!"
+    alert_account_message: "為了訂閱課程信件, 你將必須先登入."
+    archmage_introduction: "在建造遊戲中，其中一個最美好的事情是需要整合許多不同東西. 圖像, 聲音, 及時網路, 社群網路。以及許多不同層面的編程, 從底層數據庫管理, 到用戶界面的設計和實現. 這裡有許多事情需要完成, 如果你是個有經驗的程序員並且熱衷于深入處理CodeCombat的本質, 那麼這職業就是為你打造的. 我們將很高興擁有你的幫助來打造有史以來最優質的編程遊戲."
+    class_attributes: "職業說明"
+    archmage_attribute_1_pref: "熟悉于"
+    archmage_attribute_1_suf: ", 或者渴望去學習. 我們大部份的程式以這個語言寫成. 如果你是個Ruby或Python粉絲, 你將感覺到歸屬感. 它是個JavaScript, 但是擁有更佳的文法."
+    archmage_attribute_2: "一些程式經驗和個人的衝勁. 我們將幫助你找到方向, 但是我們不會花費太多時間訓練你."
+    how_to_join: "如何加入"
+    join_desc_1: "任何人都可加入我們! 只需確認我們的"
+    join_desc_2: "來開始, 並且勾選底下的條件來宣告你成為勇敢的大法師和借由郵件獲得我們最新的消息. 想要討論可做的事或者更加深入地參與? "
+    join_desc_3: ", 或者找到我們在"
+    join_desc_4: "讓我們從這開始!"
+    join_url_email: "發信給我們"
+    join_url_hipchat: "公共的HipChat房間"
+    archmage_subscribe_desc: "取得郵件關於新的編程機會和公告."
+    artisan_introduction_pref: "我們必須建造更多的關卡! 大家為了更多的內容在高聲吶喊, 但只靠我們只能建造這麼多. 現在你的工作場所就是一關; 我們的關卡編輯器是勉強可用的, 所以請小心. 只要你有新的靈感，不論從簡單的for-loops到"
+    artisan_introduction_suf: ", 那個這職業會適合你."
+    artisan_attribute_1: "任何的創建內容經驗都是加分的, 例如使用過Blizzard的關卡編輯器. 但不是必須的!"
 #    artisan_attribute_2: "A hankering to do a whole lot of testing and iteration. To make good levels, you need to take it to others and watch them play it, and be prepared to find a lot of things to fix."
 #    artisan_attribute_3: "For the time being, endurance en par with an Adventurer. Our Level Editor is super preliminary and frustrating to use. You have been warned!"
 #    artisan_join_desc: "Use the Level Editor in these steps, give or take:"
-#    artisan_join_step1: "Read the documentation."
-#    artisan_join_step2: "Create a new level and explore existing levels."
-#    artisan_join_step3: "Find us in our public HipChat room for help."
-#    artisan_join_step4: "Post your levels on the forum for feedback."
-#    artisan_subscribe_desc: "Get emails on level editor updates and announcements."
+    artisan_join_step1: "閱讀文擋."
+    artisan_join_step2: "創建新的關卡並且探索已存在的關卡."
+    artisan_join_step3: "在公共的HipChat房間裡找到我們尋求幫助."
+    artisan_join_step4: "公佈你的關卡到論壇中尋求回饋."
+    artisan_subscribe_desc: "取得郵件關於關卡編輯器更新和公告."
 #    adventurer_introduction: "Let's be clear about your role: you are the tank. You're going to take heavy damage. We need people to try out brand-new levels and help identify how to make things better. The pain will be enormous; making good games is a long process and no one gets it right the first time. If you can endure and have a high constitution score, then this class might be for you."
 #    adventurer_attribute_1: "A thirst for learning. You want to learn how to code and we want to teach you how to code. You'll probably be doing most of the teaching in this case, though."
 #    adventurer_attribute_2: "Charismatic. Be gentle but articulate about what needs improving, and offer suggestions on how to improve."
@@ -817,7 +817,7 @@
 #    scribe_introduction_url_mozilla: "Mozilla Developer Network"
 #    scribe_introduction_suf: " has built. If your idea of fun is articulating the concepts of programming in Markdown form, then this class might be for you."
 #    scribe_attribute_1: "Skill in words is pretty much all you need. Not only grammar and spelling, but able to convey complicated ideas to others."
-#    contact_us_url: "Contact us"
+    contact_us_url: "聯繫我們"
 #    scribe_join_description: "tell us a little about yourself, your experience with programming and what sort of things you'd like to write about. We'll go from there!"
 #    scribe_subscribe_desc: "Get emails about article writing announcements."
 #    diplomat_introduction_pref: "So, if there's one thing we learned from the "


Fixed subscribe().

Fixed subscribe().

--- app/assets/javascripts/app/models/_application_model.js.coffee
+++ app/assets/javascripts/app/models/_application_model.js.coffee
@@ -175,7 +175,8 @@
         callback(collection)
       @fetch( {}, { clear: true } )

-    return key
+    # return key
+    key

   subscribe: (callback) ->

@@ -187,19 +188,22 @@
       App[ @constructor.className ].bind(
         'refresh change'
         (item) =>
+          console.log('BIND', item)
           for key, callback of App[ @constructor.className ]['SUBSCRIPTION_ITEM'][ item.id ]
             item = App[ @constructor.className ]._fillUp( item )
             callback(item)
       )

       # subscribe and render data after server change
-      events = "#{@constructor.className}:created #{@constructor.className}:updated #{@constructor.className}:destroy"
+      events = "#{@constructor.className}:create #{@constructor.className}:update #{@constructor.className}:destroy"
       App.Event.bind(
         events
-        (record) =>
-          if @id.toString() is record.id.toString()
-            for key, callback of App[ @constructor.className ]['SUBSCRIPTION_ITEM'][ @id ]
-              App[ @constructor.className ].retrieve( @id, callback, true )
+        (item) =>
+          console.log('SERVER BIND try', item)
+          if App[ @constructor.className ]['SUBSCRIPTION_ITEM'] && App[ @constructor.className ]['SUBSCRIPTION_ITEM'][ item.id ]
+            console.log('SERVER BIND', item)
+            for key, callback of App[ @constructor.className ]['SUBSCRIPTION_ITEM'][ item.id ]
+              App[ @constructor.className ].retrieve( item.id, callback, true )
         'Item::Subscribe::' + @constructor.className
       )

@@ -208,7 +212,9 @@
       App[ @constructor.className ]['SUBSCRIPTION_ITEM'][ @id ] = {}
     key = @constructor.className + '-' + Math.floor( Math.random() * 99999 )
     App[ @constructor.className ]['SUBSCRIPTION_ITEM'][ @id ][key] = callback
-    return key
+
+    # return key
+    key

   @unsubscribe: (data) ->
     if @SUBSCRIPTION_ITEM


Add speaker emoji icons to sound playing and stopping.

--- app/assets/javascripts/backbone/plugins/music_player.js.coffee
+++ app/assets/javascripts/backbone/plugins/music_player.js.coffee
@@ -12,9 +12,9 @@
     attention: 'threetone-alert.wav'
   }

-  @playTemplate:   _.template('play <a target="_blank" href="<%- soundUrl %>"><%- url %>')
-  @stopTemplate:   _.template('stop the music.')
-  @resumeTemplate: _.template('resume the music.')
+  @playTemplate:   _.template(':sound: play <a target="_blank" href="<%- soundUrl %>"><%- url %>')
+  @stopTemplate:   _.template(':mute: stop the music.')
+  @resumeTemplate: _.template(':sound: resume the music.')
   @songTemplate:   _.template('
<%= song.split("/").pop() %>
')


fix order of tutorials

fix order of tutorials

--- docpad.coffee
+++ docpad.coffee
@@ -50,7 +50,7 @@

         getTutorialsVersions: (version) ->
           obj = @getCollection('html')
-            .findAll({relativeOutDirPath:  /tutorials(\/|\\).*/, keywords: "tutorial"},[{order: 1}])
+            .findAll({relativeOutDirPath:  /tutorials(\/|\\).*/, keywords: "tutorial"},[{order: -1}])
             .toJSON().reduce((acc, val) =>
               versionsUrls = val.knotxVersions.map((v) -> {version: v, url: val.url})


dashboard/billing: make selectboxes non clearable

--- client/home/lib/billing/components/creditcard/view.coffee
+++ client/home/lib/billing/components/creditcard/view.coffee
@@ -74,6 +74,7 @@
   return \
     <div className={"HomeAppView-selectBoxWrapper expiration-#{type}"}>
       <SelectBox
+        clearable={no}
         options={options[type]}
         placeholder={placeholders[type]}
         onChange={(e) -> onChange?(e.value)}


Open search dropdown on re-focus if value

--- app/assets/rui/Search.coffee
+++ app/assets/rui/Search.coffee
@@ -89,8 +89,13 @@
           ref: 'input'
           placeholder: if !@state.value? then @props.placeholder
           onKeyDown: @onKeyDown
-          onFocus: (e) => @setState focused: true
           onBlur: (e) => @setState focused: false
+          onFocus: (e) =>
+            @setState focused: true
+            if e.target.value.length > 0
+              @setState value: null
+              @props.query(e.target.value).then (items) =>
+                @setState items: items, current: 0
           onChange: (e) =>
             if e.target.value.length == 0
               @setState items: [], current: 0


resolved paritipants issue

resolved paritipants issue

--- client/views/dashboard/participants/dbparticipants.coffee
+++ client/views/dashboard/participants/dbparticipants.coffee
@@ -24,8 +24,10 @@
       return Participants.find(hackathon: mydata._id).fetch()
     else
       console.log 'error' + Participants.find(hackathon: mydata._id).fetch()
+
+Template.dashboardAddParticipant.helpers
   hackExist: ->
     if Hackathons.findOne({owner: Meteor.userId()})
       return true
     else
-      return no
+      return false


:bug: Fix errors on inconsistent columns count

Fixes #48 and fixes #49

--- lib/csv-editor-form-element.coffee
+++ lib/csv-editor-form-element.coffee
@@ -297,6 +297,7 @@
       quoted: @querySelector('[id^="quoted"]').checked
       skip_empty_lines: @querySelector('[id^="skip-empty-lines"]').checked
       fileEncoding: @encodingSelect.value
+      relax_column_count: true

     trim = @querySelector('[name="trim"]:checked')?.value
     comment = @querySelector('[name="comment"]:checked')?.value


Fix bug where template and layout styles weren't applied to each other

--- emails.coffee
+++ emails.coffee
@@ -76,9 +76,17 @@

       if layout.css
         layoutContent = addCSS Utils.readFile(layout.css), layoutContent
+        content = addCSS Utils.readFile(layout.css), content

       if layout.scss
         layoutContent = addCSS Utils.toCSS(layout.scss), layoutContent
+        content = addCSS Utils.toCSS(layout.scss), content
+
+      if template.css
+        layoutContent = addCSS Utils.readFile(template.css), layoutContent
+
+      if template.scss
+        layoutContent = addCSS Utils.toCSS(template.scss), layoutContent

       SSR.compileTemplate(layout.name, layoutContent)
       addHelpers layout


Sanitize getParameterValues task before saving

--- api.coffee
+++ api.coffee
@@ -58,10 +58,36 @@
   , 500)

-sanitizeTask = (device, task) ->
-  task.device = device
+expandParam = (param) ->
+  params = [param]
+  for a,aa of config.ALIASES
+    if a == param or common.startsWith(a, "#{param}.")
+      for p in aa
+        params.push(p) if p[p.lastIndexOf('.') + 1] != '_'
+
+  return params
+
+
+sanitizeTask = (deviceId, task, callback) ->
+  task.device = deviceId
   task.timestamp = new Date()
-  return task
+  switch task.name
+    when 'getParameterValues'
+      projection = {}
+      for p in task.parameterNames
+        for pp in expandParam(p)
+          projection[pp] = 1
+      db.devicesCollection.findOne({_id : deviceId}, projection, (err, device) ->
+        parameterNames = []
+        for k of projection
+          if common.getParamValueFromPath(device, k)?
+            parameterNames.push(k)
+        task.parameterNames = parameterNames
+        callback(task)
+      )
+    else
+      # TODO implement setParameterValues
+      callback(task)

 addAliases = (device) ->
@@ -179,36 +205,35 @@
           deviceId = querystring.unescape(DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1])
           if body
             # queue given task
-            task = sanitizeTask(deviceId, JSON.parse(body))
-
-            if task.uniqueKey?
-              db.tasksCollection.remove({device : deviceId, uniqueKey : task.uniqueKey}, (err, removed) ->
+            sanitizeTask(deviceId, JSON.parse(body), (task) ->
+              if task.uniqueKey?
+                db.tasksCollection.remove({device : deviceId, uniqueKey : task.uniqueKey}, (err, removed) ->
+                )
+              db.tasksCollection.save(task, (err) ->
+                if err
+                  response.writeHead(500)
+                  response.end(err)
+                  return
+
+                if urlParts.query.connection_request?
+                  connectionRequest(deviceId, (err) ->
+                    if err
+                      response.writeHead(202)
+                      response.end()
+                    else
+                      watchTask(task._id, config.DEVICE_ONLINE_THRESHOLD, (err) ->
+                        if err
+                          response.writeHead(202)
+                          response.end()
+                          return
+                        response.writeHead(200)
+                        response.end()
+                      )
+                  )
+                else
+                  response.writeHead(202)
+                  response.end()
               )
-
-            db.tasksCollection.save(task, (err) ->
-              if err
-                response.writeHead(500)
-                response.end(err)
-                return
-
-              if urlParts.query.connection_request?
-                connectionRequest(deviceId, (err) ->
-                  if err
-                    response.writeHead(202)
-                    response.end()
-                  else
-                    watchTask(task._id, config.DEVICE_ONLINE_THRESHOLD, (err) ->
-                      if err
-                        response.writeHead(202)
-                        response.end()
-                        return
-                      response.writeHead(200)
-                      response.end()
-                    )
-                )
-              else
-                response.writeHead(202)
-                response.end()
             )
           else if urlParts.query.connection_request?
             # no task, send connection request only


meryl test

meryl test

--- app.coffee
+++ app.coffee
@@ -2,22 +2,34 @@
 http = require('http')
 fs = require('fs')
 _ = require('underscore')
+meryl = require('meryl')
+# actions = []

-actions = []
+# http.createServer (req, res) ->
+#   res.writeHead(200, {'Content-Type': 'text/html'})
+#   #Add a listener to the request for the request body data
+#   klop = "KLOP is coming!!"
+#   req.addListener('data', (data) ->
+#     klop = data.toString()
+#   ).addListener('end', () ->
+#       res.end("
#{klop}
")
+#     )
+# .listen(8962)

+# sys.puts "Server running at http://127.0.0.1:8962/"
+#
+#
+meryl.h('GET /{param}?',(req, res) ->
+  res.writeHead(200, {'Content-Type': 'text/html'})
+  if req.params.param == undefined
+    klop = "KLOP"
+  else
+    klop = req.params.param
+  res.end("
#{klop} is coming
")
+)

-http.createServer (req, res) ->
-  res.writeHead(200, {'Content-Type': 'text/html'})
-  #Add a listener to the request for the request body data
-  klop = "KLOP is coming!!"
-  req.addListener('data', (data) ->
-    klop = data.toString()
-  ).addListener('end', () ->
-      res.end("
#{klop}
")
-    )
-.listen(8962)
-
-sys.puts "Server running at http://127.0.0.1:8962/"
+meryl.run()

+


Remove debug code

Remove debug code

--- app.coffee
+++ app.coffee
@@ -422,7 +422,6 @@
 				# write <excludedpackages>
 				if version.excludedpackages?.excludedpackage?.length
 					writer.startElement 'excludedpackages'
-					console.log version.excludedpackages
 					for excludedPackage in version.excludedpackages.excludedpackage
 						writer.startElement 'excludedpackage'
 						writer.writeAttribute 'version', excludedPackage.$.version if excludedPackage.$?.version?


add basic auth

add basic auth

--- app.coffee
+++ app.coffee
@@ -25,6 +25,10 @@
 app.use(express.bodyParser())
 app.use(express.static("#{__dirname}/public"))

+[user, password] = [process.env.USER, process.env.PASSWORD]
+if user && password
+  app.use(express.basicAuth(process.env.USER, process.env.PASSWORD))
+
 app.use(require('./api'))

 app.get "*", (req, res) ->


coffeenize part of app.coffee

coffeenize part of app.coffee

--- app.coffee
+++ app.coffee
@@ -1,16 +1,15 @@
+express = require 'express'
+path = require 'path'
+favicon = require 'serve-favicon'
+logger = require 'morgan'
+cookieParser = require 'cookie-parser'
+bodyParser = require 'body-parser'
+
+routes = require './routes/index'
+users = require './routes/users'
+
+app = express()
 `
-var express = require('express');
-var path = require('path');
-var favicon = require('serve-favicon');
-var logger = require('morgan');
-var cookieParser = require('cookie-parser');
-var bodyParser = require('body-parser');
-
-var routes = require('./routes/index');
-var users = require('./routes/users');
-
-var app = express();
-
 // view engine setup
 app.set('views', path.join(__dirname, 'views'));
 app.set('view engine', 'jade');


Remove asar patch

Remove asar patch

--- app.coffee
+++ app.coffee
@@ -5,10 +5,10 @@
 fs = require 'fs-extra'

 # Patch fs for current Electron
-fs.accessSync = (filePath, mode) ->
-  if fs.existsSync(filePath)
-    return true
-  throw new Error("ENOENT: no such file or directory, access '#{filePath}'")
+#fs.accessSync = (filePath, mode) ->
+#  if fs.existsSync(filePath)
+#    return true
+#  throw new Error("ENOENT: no such file or directory, access '#{filePath}'")

 # Environment
 global.POI_VERSION = app.getVersion()


fix model initialization

fix model initialization

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -180,7 +180,7 @@

 	lazyInitialize : (callback) ->
 		unless @initialized
-			if @waitingForInitializing
+			if @waitingForInitializing?
 				@waitingForInitializing.push callback
 			else
 				@initialize callback
@@ -202,6 +202,7 @@
 			callback err
 			return
 		else
+			@initialized = true
 			return callback

 _.extend Model.Route, Model.LazyInitializable


Fix curly-brace bug

Fix curly-brace bug

--- src/prefixr.coffee
+++ src/prefixr.coffee
@@ -53,7 +53,7 @@

 module.exports = (css) =>
   vendoredProperties.forEach (prop) ->
-    regex = new RegExp "(?:;*)([ \s\t]*)?(#{prop})[\s]*:([^\n;]+);?", 'ig'
+    regex = new RegExp "(?:;*)([ \s\t]*)?(#{prop})[\s]*:([^\n;}]+);?", 'ig'
     css = css.replace regex, (match, spaces, prop, value) ->
       spaces = spaces or ''
       result = ''


:fire: ;

:fire: ;

--- src/project.coffee
+++ src/project.coffee
@@ -53,7 +53,7 @@
     # to either a {Repository} or null. Ideally, the {Directory} would be used
     # as the key; however, there can be multiple {Directory} objects created for
     # the same real path, so it is not a good key.
-    @repositoryPromisesByPath = new Map();
+    @repositoryPromisesByPath = new Map()

     # Note that the GitRepositoryProvider is registered synchronously so that
     # it is available immediately on startup.


Remove error example

Remove error example

--- examples/src/error.coffee
+++ examples/src/error.coffee
@@ -1 +0,0 @@
-some..invalid..coffee..script


Use log helpers in Rebuild command

Use log helpers in Rebuild command

--- src/rebuild.coffee
+++ src/rebuild.coffee
@@ -41,10 +41,10 @@
         env = _.extend({}, process.env, HOME: @atomNodeDirectory)
         env.USERPROFILE = env.HOME if config.isWin32()

-        @fork @atomNpmPath, rebuildArgs, {env}, (code, stderr='') ->
+        @fork @atomNpmPath, rebuildArgs, {env}, (code, stderr='') =>
           if code is 0
-            process.stdout.write '\u2713\n'.green
+            @logSuccess()
             callback()
           else
-            process.stdout.write '\u2717\n'.red
+            @logFailure()
             callback(stderr)


entropy function for regex patterns + entropy tweak for date patterns

--- src/scoring.coffee
+++ src/scoring.coffee
@@ -138,13 +138,12 @@
   calc_entropy: (match) ->
     return match.entropy if match.entropy? # a match's entropy doesn't change. cache it.
     entropy_func = switch match.pattern
+      when 'dictionary' then @dictionary_entropy
+      when 'spatial'    then @spatial_entropy
       when 'repeat'     then @repeat_entropy
       when 'sequence'   then @sequence_entropy
-      when 'digits'     then @digits_entropy
-      when 'year'       then @year_entropy
+      when 'regex'      then @regex_entropy
       when 'date'       then @date_entropy
-      when 'spatial'    then @spatial_entropy
-      when 'dictionary' then @dictionary_entropy
     match.entropy = entropy_func.call this, match

   repeat_entropy: (match) ->
@@ -166,21 +165,29 @@
       base_entropy += 1 # extra bit for descending instead of ascending
     base_entropy + @lg match.token.length

-  digits_entropy: (match) -> @lg Math.pow(10, match.token.length)
-
-  NUM_YEARS: 119 # years match against 1900 - 2019
-  NUM_MONTHS: 12
-  NUM_DAYS: 31
-
-  year_entropy: (match) -> @lg @NUM_YEARS
+  regex_entropy: (match) ->
+    char_class_bases =
+      alpha_lower:  26
+      alpha_upper:  26
+      alpha:        52
+      alphanumeric: 62
+      digits:       10
+      symbols:      33
+    if match.regex_name of char_class_bases
+      @lg Math.pow(char_class_bases[match.regex_name], match.token.length)
+    else switch match.regex_name
+      when 'recent_year'
+        # conservative estimate of year space: num years from 2000
+        year_space = parseInt(match.rx_match) - 2000
+        @lg Math.abs year_space

   date_entropy: (match) ->
-    if match.year < 100
-      entropy = @lg @NUM_DAYS * @NUM_MONTHS * 100 # two-digit year
-    else
-      entropy = @lg @NUM_DAYS * @NUM_MONTHS * @NUM_YEARS # four-digit year
-    if match.separator
-      entropy += 2 # add two bits for separator selection [/,-,.,etc]
+    # base entropy: lg of year distance from 2000
+    entropy = @lg Math.abs(match.year - 2000)
+    # add one bit for four-digit years
+    entropy += 1 if match.has_full_year
+    # add two bits for separator selection (one of ~4 choices)
+    entropy += 2 if match.separator
     entropy

   spatial_entropy: (match) ->


add discussion group list

add discussion group list

--- src/service.coffee
+++ src/service.coffee
@@ -165,7 +165,21 @@
                     @emit 'error', 'online failed'
             )

-    # discussionGroupList: ->
+    discussionGroupList: (cb)->
+        params =
+            clientid: @authInfo.clientid
+            psessionid: @authInfo.psessionid
+            vfwebqq: @authInfo.vfwebqq
+            t: new Date().getTime()
+
+        client = HttpClient.create('http://s.web2.qq.com')
+            .path('api/get_group_name_list_mask2')
+            .header('Cookie', @cookie)
+            .header('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
+            .post(QueryString.stringify(params))((err, resp, body) =>
+                console.log body
+                cb body
+        )

     # discussionGroupMember: ->

@@ -180,6 +194,9 @@
             @groupList((data)->
                 console.log data
             )
+            @discussionGroupList((data)->
+                console.log data
+            )

         @online()


Remove code handling transition state on session connect

--- src/session.coffee
+++ src/session.coffee
@@ -112,14 +112,10 @@

     _onConnectSessionSuccess: (session) =>
       if session
-        try
-          session = JSON.parse(session)
+        session = JSON.parse(session)

-          @analytics_session = session.session
-          @cookie_policy = session.cookie_policy
-        catch # TODO remove me after successfully transition to new mechanism
-          @analytics_session = session
-          @cookie_policy = 'full'
+        @analytics_session = session.session
+        @cookie_policy = session.cookie_policy

         @_createSessionCacheCookie(@analytics_session)


let there be light

let there be light

--- src/startup.coffee
+++ src/startup.coffee
@@ -26,8 +26,8 @@
 atom.settings = new Settings

 # Ignore extensions for now
-#atom.extensions = {}
-#atom.extensionManager = new ExtensionManager
+atom.extensions = {}
+atom.extensionManager = new ExtensionManager

 atom.app = new App


Renamed timbit_id to esi_id in remote rendering block.

--- src/timbits.coffee
+++ src/timbits.coffee
@@ -178,11 +178,11 @@

 		if context.remote is 'true'
 			output = """
-				context_#{context.timbit_id} = #{JSON.stringify(context)};
+				context_#{context.esi_id} = #{JSON.stringify(context)};
 				var views = views || {};
 				if (views['#{context.view}'] || false) {
-					render = CoffeeKup.render(views['#{context.view}'], context_#{context.timbit_id});
-					#{if context.timbit_id? then "$('##{context.timbit_id}').html(render)" else "$('body').append(render)"};
+					render = CoffeeKup.render(views['#{context.view}'], context_#{context.esi_id});
+					#{if context.esi_id? then "$('##{context.esi_id}').html(render)" else "$('body').append(render)"};
 				}
 				else {
 					$.ajax({
@@ -192,8 +192,8 @@
 						jsonpCallback: "#{context.view.replace(/\//,'_')}",
 						success: function(data, textStatus, jqXHR){
 							views['#{context.view}'] = data; //save view for reuse
-							render = CoffeeKup.render(views['#{context.view}'], context_#{context.timbit_id});
-							#{if context.timbit_id? then "$('##{context.timbit_id}').html(render)" else "$('body').append(render)"};
+							render = CoffeeKup.render(views['#{context.view}'], context_#{context.esi_id});
+							#{if context.esi_id? then "$('##{context.esi_id}').html(render)" else "$('body').append(render)"};
 						}
 					});
 				}


Track progress quartiles only when current progress matches

--- src/tracker.coffee
+++ src/tracker.coffee
@@ -22,6 +22,12 @@
             @trackingEvents[eventName] = events.slice(0)
         if creative instanceof VASTCreativeLinear
             @assetDuration = creative.duration
+            # beware of key names, theses are also used as event names
+            @quartiles =
+                'firstQuartile' : Math.round(25 * @assetDuration) / 100
+                'midpoint'      : Math.round(50 * @assetDuration) / 100
+                'thirdQuartile' : Math.round(75 * @assetDuration) / 100
+
             @skipDelay = creative.skipDelay
             @linear = yes
             @clickThroughURLTemplate = creative.videoClickThroughURLTemplate
@@ -53,9 +59,8 @@
                 percent = Math.round(progress / @assetDuration * 100)
                 events.push "progress-#{percent}%"

-                events.push "firstQuartile" if percent >= 25
-                events.push "midpoint" if percent >= 50
-                events.push "thirdQuartile" if percent >= 75
+                for quartile, time of @quartiles
+                    events.push quartile if time <= progress <= (time + 1)

             for eventName in events
                 @track eventName, yes


showList in blocks

showList in blocks

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -424,7 +424,9 @@
   logmsg = "#{modulename}: #{msg.envelope.user.name} requested: show #{list_name}"
   robot.logger.info logmsg

-  arr = []
+  epb = 20 # entries per block
+  arr = [] # temp container
+  output = [] # output blocks
   arr.push sprintf displayfmt, 'Type', 'Value', 'Expiration', 'Creator'
   for obj in fwdata[list_name]
     expires = moment(obj.expires)
@@ -434,9 +436,17 @@
       continue
     if expires.isBefore() # now
       continue
-    arr.push sprintf displayfmt, obj.type, obj.val, expires.fromNow(), obj.creator
-
-  msg.reply "#{list_name} items and expirations\n```\n"+ arr.join("\n") + "\n```"
+    if arr.length >= epb
+      output.push arr.join("\n")
+      arr = []
+    else
+      arr.push sprintf displayfmt, obj.type, obj.val, expires.fromNow(), obj.creator
+
+  if arr.length > 0
+    output.push arr.join("\n")
+
+  msg.send "Here is the current #{list_name}:\n"
+  msg.send "```#{ob}\n```" for ob in output

   logmsg = "#{modulename}: robot responded to #{msg.envelope.user.name}: " +
     "displayed #{list_name} items and expirations"


SharedMachine: Notify owner account when a user is kicked from shared machine.

--- workers/social/lib/social/models/sharedmachine.coffee
+++ workers/social/lib/social/models/sharedmachine.coffee
@@ -25,10 +25,8 @@
     setUsers client, uid, options, (err) ->
       return callback err  if err

-      { nickname } = client.connection.delegate.profile
-
       notifyByUsernames options.target, 'SharedMachineInvitation', { uid }
-      notifyByUsernames [ nickname ], 'MachineShareListUpdated', options
+      notifyOwner client, options
       callback()

@@ -36,7 +34,17 @@

     asUser  = no
     options = { target, asUser }
-    setUsers client, uid, options, callback
+    setUsers client, uid, options, (err) ->
+      return callback err  if err
+
+      notifyOwner client, options
+      callback()
+
+
+  notifyOwner = (client, options) ->
+
+    { nickname } = client.connection.delegate.profile
+    notifyByUsernames [ nickname ], 'MachineShareListUpdated', options

   setUsers = (client, uid, options, callback) ->


filter prompt

filter prompt

--- app/assets/javascripts/china_city/jquery.china_city.js.coffee
+++ app/assets/javascripts/china_city/jquery.china_city.js.coffee
@@ -6,7 +6,7 @@
         $this = $(@)
         next_selects = selects.slice(selects.index(@) + 1) # empty all children city
         $("option:gt(0)", next_selects).remove()
-        if next_selects.first()[0] and $this.val() # init next child
+        if next_selects.first()[0] and $this.val() and !$this.val().match(/--.*--/) # init next child
           $.get "/china_city/#{$(@).val()}", (data) ->
             next_selects.first()[0].options.add(new Option(option[0], option[1])) for option in data
             # init value after data completed.


Bring back conditional check on home_wiki in Assign Button

--- app/assets/javascripts/components/students/assign_button.cjsx
+++ app/assets/javascripts/components/students/assign_button.cjsx
@@ -21,10 +21,17 @@
     tag = if @props.role == 0 then 'assign_' else 'review_'
     tag + @props.student.id
   getInitialState: ->
+    if @props.course.home_wiki
+      language = @props.course.home_wiki.language
+      project = @props.course.home_wiki.project
+    else
+      language = "en"
+      project = "wikipedia"
+
     return {
       showOptions: false
-      language: @props.course.home_wiki.language
-      project: @props.course.home_wiki.project
+      language: language
+      project: project
     }
   shouldComponentUpdate: (nextProps, nextState) ->
     shallowCompare(this, nextProps, nextState)


NavigationMachineItem: Fixed reference errors.

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -64,13 +64,16 @@
     @progress  = new KDProgressBarView
       cssClass : 'hidden'

-    if @machine.isMine() or @machine.isApproved()
+    isMine     = @machine.isMine()
+    isApproved = @machine.isApproved()
+
+    if isMine or isApproved
       if @settingsEnabled()
         @settingsIcon = new KDCustomHTMLView
           tagName     : 'span'
           click       : (e) =>
             if isMine then @handleMachineSettingsClick e
-            else if isPermanent then @showSidebarSharePopup()
+            else if isApproved then @showSidebarSharePopup()
     else
       @settingsIcon = new KDCustomHTMLView cssClass: 'hidden'


getTimedOutCallback: returns a new callback which calls the failcallback if first callback not finish its job in given timeout

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -90,9 +90,9 @@
   fetchAppsFromFs:(callback)->

     path = "/Users/#{KD.whoami().profile.nickname}/Applications"
-    KD.utils.wait 6000, callback
-
-    @kiteController.run "ls #{escapeFilePath path} -lpva", (err, response)=>
+
+    @kiteController.run "ls #{escapeFilePath path} -lpva", \
+    KD.utils.getTimedOutCallback (err, response)=>
       if err
         @putAppsToAppStorage {}
         warn err
@@ -124,6 +124,9 @@
                 console.warn "Manifest file is broken", e
           @putAppsToAppStorage manifests
           callback? null, manifests
+    , 5000, ->
+      log "Timeout reached for kite request"
+      callback()

   fetchAppsFromDb:(callback)->


Updated angular-slider dependencies.

Updated angular-slider dependencies.

--- app/javascripts/image.coffee
+++ app/javascripts/image.coffee
@@ -1,5 +1,5 @@
-define ['angular', 'xtk', 'file', 'slider'], (ng) ->
-  image = ng.module 'qiprofile.image', ['qiprofile.file']
+define ['angular', 'xtk', 'file', 'slider', 'touch'], (ng) ->
+  image = ng.module 'qiprofile.image', ['qiprofile.file', 'vr.directives.slider']

   image.factory 'Image', ['$rootScope', '$q', 'File', ($rootScope, $q, File) ->
     # The root scope {parent id: [Image objects]} cache.
@@ -136,7 +136,7 @@
           #labelmapGui.open()

         # Adjust the camera position.
-        renderer.camera.position = [120, 20, 20]
+        renderer.camera.position = [0, 0, 240]

         # Render the image.
         renderer.render()


:bug: Fix amd_tamer task

:bug: Fix amd_tamer task

--- grunt/amd_tamer.coffee
+++ grunt/amd_tamer.coffee
@@ -7,4 +7,4 @@
       footer: '\n//# sourceMappingURL=<%= package.name %>.js.map'
       processName: (name) -> name.toLowerCase()
     files:
-      'dist/<%= package.name %>.all.js': 'dist/**/*.js'+      'dist/<%= package.name %>.js': 'dist/**/*.js'


Increase name label background opacity as per Robin's suggestion

--- app/lib/surface/Label.coffee
+++ app/lib/surface/Label.coffee
@@ -83,7 +83,7 @@
       o.fontColor = '#c66'
     else if @style is 'name' and @sprite?.thang?.team is 'ogres'
       o.fontColor = '#66c'
-    o.backgroundFillColor = {D: 'white', S: 'rgba(0,0,0,0.4)', N: 'rgba(0,0,0,0.5)'}[st]
+    o.backgroundFillColor = {D: 'white', S: 'rgba(0,0,0,0.4)', N: 'rgba(0,0,0,0.7)'}[st]
     o.backgroundStrokeColor = {D: 'black', S: 'rgba(0,0,0,0.6)', N: 'rgba(0,0,0,0)'}[st]
     o.backgroundStrokeStyle = {D: 2, S: 1, N: 1}[st]
     o.backgroundBorderRadius = {D: 10, S: 3, N: 3}[st]


pagination changes

pagination changes

--- public/views/pagination_view.coffee
+++ public/views/pagination_view.coffee
@@ -1,36 +1,77 @@
 class PaginationView extends Backbone.View
+
+  events:
+    'click a.previous_page' : 'previous'
+    'click a.next_page'     : 'next'
+    'click a.set_page'      : 'goto_page'

   initialize: ->
     @listenTo @collection, "click reset", @render

-  events:
-    "click": "onClick"

   template: _.template """

-
<

-      <% _.map(_.range(1,pages + 1), function(num) { %>
-        <li class="<% if(current_page == num){ %><%= 'active' %><% }%>" ><%= num %>

+
<

+      <% _.map(selected_pages, function(num) { %>
+        <li class="<% if(current_page == num){ %><%= 'active' %><% }%>" ><a class="<% if(!isNaN(num)){ %><%= 'set_page' %><% }%>" href="#"><%= num %>

       <% }); %>
-
>

+
>

   """

   render: ->
     if @collection.pages > 1
-      @$el.html @template({pages: @collection.pages, current_page: @collection.page})
+      @$el.html @template({pages: @collection.pages, current_page: @collection.page, selected_pages: @windowed_page() })
     this

-  onClick: (event) ->
+  previous: ->
+    @page = @collection.page - 1
+    if @page == 0 then @page = 1
+    @change_page()
+
+  next: ->
+    @page = @collection.page + 1
+    if @page > @collection.pages then @page = @collection.pages
+    @change_page()
+
+  goto_page: (event) ->
     event.preventDefault()
+    @page = event.target.text || null
+    @change_page()

-    @page = event.target.text || null
-    if @page == "<"
-      @page = 1
-    if @page == ">"
-      @page = @collection.pages
+  windowed_page: ->
+    total_pages = parseInt(@collection.pages)
+    inner_window = 2
+    outer_window = 1
+    window_from = parseInt(@collection.page) - inner_window
+    window_to = parseInt(@collection.page) + inner_window
+    if window_to > total_pages
+      window_from -= window_to - total_pages
+      window_to = total_pages
+    if window_from < 1
+      window_to  += 1 - window_from
+      window_from = 1
+      window_to = total_pages if window_to > total_pages

+    middle = [window_from..window_to]
+
+    if outer_window + 3 < middle[0]
+      left = [1..(outer_window + 1)]
+      left.push "..."
+    else
+      left = [1...middle[0]]
+
+    if (total_pages - outer_window - 2) > middle[middle.length - 1]
+      right = [(total_pages - outer_window)..total_pages]
+      right.unshift "..."
+    else
+      right_start = Math.min(middle[middle.length - 1] + 1, total_pages)
+      right = [right_start..total_pages]
+      right = [] if right_start is total_pages
+
+    left.concat(middle.concat(right))
+
+  change_page: ->
     $(".epages-shop-pagination ul li").removeClass("active")
     $(".epages-shop-pagination ul li:contains(#{this.page})").addClass("active")
-    if @page
-      @trigger "change:page"+    if @page then @trigger "change:page"


Remove Field Guide from Project Homepage (#3329)

* Remove Field Guide from Project Homepage

* Remove prop and change conditional

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -311,7 +311,8 @@
       {unless @props.project.launch_approved or @props.project.beta_approved
         <Translate component="p" className="project-disclaimer" content="project.disclaimer" />}

-      <PotentialFieldGuide project={@props.project} />
+      {unless @props.location.pathname is projectPath
+        <PotentialFieldGuide project={@props.project} />}
     </div>


Fix dongle certification checking

Fix dongle certification checking

--- app/src/dongle/dongle.coffee
+++ app/src/dongle/dongle.coffee
@@ -20,14 +20,18 @@
   V_LW_1_0_0: 0x20010000010f

 # Ledger OS pubKey, used for pairing.
-ProductionAttestation = "04c370d4013107a98dfef01d6db5bb3419deb9299535f0be47f05939a78b314a3c29b51fcaa9b3d46fa382c995456af50cd57fb017c0ce05e4a31864a79b8fbfd6"
-BetaAttestation = "04c370d4013107a98dfef01d6db5bb3419deb9299535f0be47f05939a78b314a3c29b51fcaa9b3d46fa382c995456af50cd57fb017c0ce05e4a31864a79b8fbfd6"
-
 Attestation =
-  String: ProductionAttestation
+  String: "04c370d4013107a98dfef01d6db5bb3419deb9299535f0be47f05939a78b314a3c29b51fcaa9b3d46fa382c995456af50cd57fb017c0ce05e4a31864a79b8fbfd6"
 Attestation.Bytes = parseInt(hex, 16) for hex in Attestation.String.match(/\w\w/g)
 Attestation.xPoint = Attestation.String.substr(2,64)
-Attestation.yPoint = Attestation.String.substr(64)
+Attestation.yPoint = Attestation.String.substr(66)
+
+BetaAttestation =
+  String: "04e69fd3c044865200e66f124b5ea237c918503931bee070edfcab79a00a25d6b5a09afbee902b4b763ecf1f9c25f82d6b0cf72bce3faf98523a1066948f1a395f"
+BetaAttestation.Bytes = parseInt(hex, 16) for hex in BetaAttestation.String.match(/\w\w/g)
+BetaAttestation.xPoint = BetaAttestation.String.substr(2,64)
+BetaAttestation.yPoint = BetaAttestation.String.substr(66)
+

 # This path do not need a verified PIN to sign messages.
 BitIdRootPath = "0'/0/0xb11e"
@@ -40,6 +44,7 @@
   States: States
   Firmware: Firmware
   Attestation: Attestation
+  BetaAttestation: BetaAttestation
   BitIdRootPath: BitIdRootPath

 ###
@@ -154,7 +159,12 @@
   # Verify that dongle firmware is "official".
   # @param [Function] callback Optional argument
   # @return [CompletionClosure]
-  isCertified: (callback=undefined) ->
+  # isCertified: (callback=undefined) -> @_checkDongleCertification(Attestation, callback)
+  isCertified: (callback=undefined) -> @_checkDongleCertification(BetaAttestation, callback)
+
+  isBetaCertified: (callback=undefined) -> @_checkDongleCertification(BetaAttestation, callback)
+
+  _checkDongleCertification: (Attestation, callback) ->
     completion = new CompletionClosure(callback)
     randomValues = new Uint32Array(2)
     crypto.getRandomValues(randomValues)


Client: add unimplemented log

Client: add unimplemented log

--- client/Main/CommonViews/SkillTagAutoCompleteController.coffee
+++ client/Main/CommonViews/SkillTagAutoCompleteController.coffee
@@ -5,15 +5,6 @@
     super options, data

   putDefaultValues: (stringTags) ->
-    KD.remote.api.JTag.fetchSkillTags
-      title     :
-        $in     : stringTags
-    ,
-      sort      :
-        title   : 1
-    , (err, tags) =>
-        unless err and not tags
-        then @setDefaultValue tags
-        else warn "There was a problem fetching default tags!", err, tags
+    return console.error "not implemented feature"

   getCollectionPath: -> 'skillTags'


it's fs.remove not fs.delete

it's fs.remove not fs.delete

--- client/landing/app/MainApp/filetree/itemviews/fileitem.coffee
+++ client/landing/app/MainApp/filetree/itemviews/fileitem.coffee
@@ -1,6 +1,6 @@
 class NFileItemView extends KDCustomHTMLView

-  loaderRequiredEvents = ['job', 'delete', 'save', 'saveAs']
+  loaderRequiredEvents = ['job', 'remove', 'save', 'saveAs']

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


Can now create blueprint items with belongs_to relationship.

--- app/controllers/items/create.coffee
+++ app/controllers/items/create.coffee
@@ -24,14 +24,13 @@
             item.set field, result
           promises.push promise

-      RSVP.allSettled(promises).then ->
-        console.log item
-        # item.save().then =>
-        #   #TODO For whatever reason, this does not load the model hook in the
-        #   # route, but I can't pass a string because there's two dynamic
-        #   # segments.
-        #   @transitionToRoute 'items',
-        #     extension: extension.id
-        #     blueprint_slug: blueprint.get 'slug'
+      Ember.RSVP.allSettled(promises).then ->
+        item.save().then =>
+          #TODO For whatever reason, this does not load the model hook in the
+          # route, but I can't pass a string because there's two dynamic
+          # segments.
+          @transitionToRoute 'items',
+            extension: extension.id
+            blueprint_slug: blueprint.get 'slug'

 `export default ItemsCreateController`


payment: use KDModal.confirm to confirm product deletion

this has the advantage of being able to show a view of the product that is being deleted

--- client/app/Applications/Dashboard.kdapplication/AppController.coffee
+++ client/app/Applications/Dashboard.kdapplication/AppController.coffee
@@ -136,24 +136,21 @@

   productViewAdded: do ->

-    confirmDelete = (callback) ->
-      deleteModal = new KDModalView
-        title        : "Warning"
-        content      : "
Are you sure you want to delete this item?
"
-        height       : "auto"
-        overlay      : yes
-        buttons      :
-          Remove     :
-            loader   :
-              color  : "#ffffff"
-              diameter : 16
-            style    : "modal-clean-red"
-            callback : ->
-              deleteModal.destroy()
-              callback()
-          cancel     :
-            style    : "modal-cancel"
-            callback : -> deleteModal.destroy()
+    confirmDelete = (data, callback) ->
+
+      productViewOptions =
+        tagName   : 'div'
+        cssClass  : 'modalformline'
+
+      modal = KDModalView.confirm
+        title       : "Warning"
+        description : "Are you sure you want to delete this item?"
+        subView     : new GroupProductView productViewOptions, data
+        ok          :
+          title     : "Remove"
+          callback  : ->
+            modal.destroy()
+            callback()

     showCreateModal = (options, data, callback) ->
       productType = options.productType ? 'product'
@@ -178,19 +175,21 @@
       switch category

         when 'Product'
-          productType     : 'product'
-          isRecurOptional : yes
-          showOverage     : yes
-          showSoldAlone   : yes
+          productType         : 'product'
+          isRecurOptional     : yes
+          showOverage         : yes
+          showSoldAlone       : yes
+          showPriceIsVolatile : yes

         when 'Plan'
-          productType     : 'plan'
-          isRecurOptional : no
-          showOverage     : no
-          showSoldAlone   : no
-          placeholders    :
-            title         : 'e.g. "Gold Plan"'
-            description   : 'e.g. "2 VMs, and a tee shirt"'
+          productType         : 'plan'
+          isRecurOptional     : no
+          showOverage         : no
+          showSoldAlone       : no
+          showPriceIsVolatile : no
+          placeholders        :
+            title             : 'e.g. "Gold Plan"'
+            description       : 'e.g. "2 VMs, and a tee shirt"'

     getConstructor = (category) ->
       KD.remote.api["JPayment#{ category.capitalize() }"]
@@ -208,10 +207,9 @@

         reload()

-      # view.on "#{category}CreateRequested", (productData) ->
-
-      view.on "#{category}DeleteRequested", (code) ->
-        confirmDelete -> konstructor.removeByCode code, handleResponse
+      view.on "#{category}DeleteRequested", (data) ->
+        confirmDelete data, ->
+          konstructor.removeByCode data.planCode, handleResponse

       view.on "#{category}EditRequested", (data) ->
         options = getProductFormOptions category