Removed rivets configuration and init

Removed rivets configuration and init

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -31,10 +31,10 @@
     adapters: {}

     # Default attribute prefix.
-    prefix: 'rv'
+    prefix: 'cb'

     # Default template delimiters.
-    templateDelimiters: ['{', '}']
+    templateDelimiters: [ '{', '}' ]

     # Default sightglass root interface.
     rootInterface: '.'
@@ -43,7 +43,7 @@
     preloadData: true,

     # Execute functions in bindings. Defaultis false since rivets 0.9. Set to true to be backward compatible with rivets 0.8.
-    executeFunctions: false,
+    executeFunctions: true,

     # Alias for index in rv-each binder
     iterationAlias : (modelName) ->
@@ -51,39 +51,10 @@

     # Default event handler.
     handler: (context, ev, binding) ->
-      @call context, ev, binding.view.models
-
-    # Merges an object literal into the corresponding global options.
-    configure: (options = {}) ->
-      for option, value of options
-        if option in ['binders', 'components', 'formatters', 'adapters']
-          for key, descriptor of value
-            Rivets[option][key] = descriptor
-        else
-          Rivets.public[option] = value
-
-      return
+      @call binding.view.models.scope, ev, context

     # Binds some data to a template / element. Returns a Rivets.View instance.
     bind: (el, models = {}, options = {}) ->
       view = new Rivets.View(el, models, options)
       view.bind()
-      view
-
-    # Initializes a new instance of a component on the specified element and
-    # returns a Rivets.View instance.
-    init: (component, el, data = {}) ->
-      el ?= document.createElement 'div'
-      component = Rivets.public.components[component]
-      template = component.template.call @, el
-      if template instanceof HTMLElement
-        while el.firstChild
-          el.removeChild(el.firstChild)
-        el.appendChild(template)
-      else
-        el.innerHTML = template
-      scope = component.initialize.call @, el, data
-
-      view = new Rivets.View(el, scope)
-      view.bind()
-      view
+      view


Fix error in process time entries

Fix error in process time entries

--- src/script.coffee
+++ src/script.coffee
@@ -10,7 +10,8 @@
 #
 # Commands:
 #   hubot toggl login <token> <password> - Login to Toggl
-#   hubot toggl payment <amount> <price> <password> - Close time entries for the amount and price
+#   hubot toggl payment <amount> <price> <password> - Close time entries for
+#     the amount and price
 #
 # Author:
 #   lgaticaq
@@ -31,7 +32,7 @@
     url: "http://indicadoresdeldia.cl/webservice/indicadores.json"
     json: true
     transform: (body) ->
-      parseFloat body.indicador.uf.replace("$", "").replace(".", "").replace(",", ".")
+      parseFloat body.indicador.uf.replace(/[$.]/g, "").replace(",", ".")
   rp options

 process = (timeEntries, amount, price) ->
@@ -40,11 +41,13 @@
       limit = (amount / (uf * price)) * 3600
       teIds = []
       duration = 0
+      difference = ""
       for i in timeEntries
-        if ((duration + i.duration) < limit) and i.tags?
+        if ((duration + i.duration) < limit) and not i.tags?
           duration += i.duration
           teIds.push i.id
-      difference = if limit > duration then ". Diference is #{moment.duration(limit - duration, 'seconds').humanize()}" else ""
+      if limit > duration
+        difference = ". Diference is #{parseInt(limit - duration, 10)} seconds"
       resolve {difference: difference, teIds: teIds}
     .catch reject

@@ -63,7 +66,8 @@
     encryptor = simpleEncryptor secret
     toggl.getUserDataAsync({}).then (userData) ->
       unless robot.brain.data.users[res.message.user.id]?
-        robot.brain.data.users[res.message.user.id] = {name: res.message.user.name}
+        robot.brain.data.users[res.message.user.id] =
+          name: res.message.user.name
         robot.brain.save()
       user = robot.brain.userForName res.message.user.name
       user.toggl =
@@ -88,13 +92,13 @@
     res.send "Processing time entries..."
     tags = ["Pagado"]
     action = "add"
-    end = moment()
-    start = end.subtract 1, "years"
+    end = moment().toISOString()
+    start = moment().subtract(1, "years").toISOString()
     user = robot.brain.userForName res.message.user.name
     encryptor = simpleEncryptor secret
     message = ""
     toggl = getClient encryptor.decrypt user.toggl.api_token
-    toggl.getTimeEntriesAsync(start.toISOString(), end.toISOString()).then (timeEntries) ->
+    toggl.getTimeEntriesAsync(start, end).then (timeEntries) ->
       process timeEntries, amount, price
     .then (data) ->
       message = "Ready#{data.difference}"


Don't sort search results by name

Don't sort search results by name

Closes #118

--- src/search.coffee
+++ src/search.coffee
@@ -43,7 +43,6 @@
       else if response.statusCode is 200
         packages = body.filter (pack) -> pack.releases?.latest?
         packages = packages.map ({readme, metadata, downloads}) -> _.extend({}, metadata, {readme, downloads})
-        packages = _.sortBy(packages, 'name')
         callback(null, packages)
       else
         message = body.message ? body.error ? body


snake.move refactoring

snake.move refactoring

--- coffee/front/snake.coffee
+++ coffee/front/snake.coffee
@@ -3,6 +3,13 @@
   class Head extends GameObject
     color: 'red'
     constructor: (@game) -> @points = []
+
+    set: (point) ->
+      if @points.length > 0
+        tmp = @points[0]
+        do @unset
+        @game.snake.set tmp
+      super point

   color: 'white'

@@ -26,6 +33,33 @@

   move: ->
     do @step
+    next = @get_next()
+
+    @food = no
+    @food = yes if next.obj is @game.food
+    if @food
+      do @game.food.respawn
+      do @game.score.next
+
+    if @is_free(next) or @will_be_tail next
+      do @unset if not @food
+      @head.set next
+
+      if @game.win
+        @game.started = no
+        @game.over = yes
+        @game.msg_bottom.show 'WOOOOOW!!!
 Snake has max size'
+    else
+      @game.started = no
+      @game.over = yes
+      do @game.score.set_high
+
+      unless @game.win
+        @game.msg_bottom.show "GAME OVER! Your score #{@game.score.value}.
+
Press Space/Enter/Esc to restart"
+    return
+
+  get_next: ->
     x = @head.points[0].x
     y = @head.points[0].y

@@ -42,32 +76,7 @@
       when @LEFT
         if x > 0 then x = x-1
         else  x = @game.width-1
-
-    @food = no
-    next = @get x, y
-    @food = yes if next.obj is @game.food
-    if @food
-      do @game.food.respawn
-      do @game.score.next
-
-    if @is_free(next) or @will_be_tail next
-      do @unset if not @food
-      head = @head.points[0]
-      do @head.unset
-      @head.set next
-      @set head
-      if @game.win
-        @game.started = no
-        @game.over = yes
-        @game.msg_bottom.show 'WOOOOOW!!!
 Snake has max size'
-    else
-      @game.started = no
-      @game.over = yes
-      do @game.score.set_high
-      unless @game.win
-        @game.msg_bottom.show "GAME OVER! Your score #{@game.score.value}.
-
Press Space/Enter/Esc to restart"
-    return
+    return @get x, y

   step: ->
     if @stack[0]


improve error names and let them all inherit from a base error

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -37,23 +37,23 @@
     try
       resolution = hinoki.resolveInContainers containers, path, debug
     catch error
-      return Promise.reject new hinoki.errors.ErrorInResolvers path, containers, error
+      return Promise.reject new hinoki.ErrorInResolversError path, containers, error

     # from here on we use the name and container returned by the resolver
     # (resolution.name and resolution.container)
     # which might differ from what was originally searched (path[0]).

     if resolution instanceof Error
-      return Promise.reject new hinoki.errors.ErrorInResolvers path, containers, resolution
+      return Promise.reject new hinoki.ErrorInResolversError path, containers, resolution

     unless resolution?
       # we are out of luck: the factory could not be found
-      return Promise.reject new hinoki.errors.Unresolvable path, containers
+      return Promise.reject new hinoki.UnresolvableError path, containers

     resolutionErrors = hinoki.resolutionErrors resolution

     if resolutionErrors?
-      return Promise.reject new hinoki.errors.InvalidResolution path, resolution, resolutionErrors
+      return Promise.reject new hinoki.InvalidResolutionError path, resolution, resolutionErrors

     unless hinoki.isUndefined resolution.value
       debug? {
@@ -71,7 +71,7 @@
     # TODO how to handle name change here

     if hinoki.arrayOfStringsHasDuplicates path
-      return Promise.reject new hinoki.errors.CircularDependency path, resolution
+      return Promise.reject new hinoki.CircularDependencyError path, resolution

     # no cycle - yeah!

@@ -136,7 +136,7 @@
       .then (value) ->
         # note that a null value is allowed!
         if hinoki.isUndefined value
-          return Promise.reject new hinoki.errors.FactoryReturnedUndefined newPath, resolution.container, resolution.factory
+          return Promise.reject new hinoki.FactoryReturnedUndefinedError newPath, resolution.container, resolution.factory

         # cache
         unless nocache
@@ -162,7 +162,7 @@
     try
       valueOrPromise = factory.apply null, dependencyValues
     catch error
-      return Promise.reject new hinoki.errors.ErrorInFactory path, container, error
+      return Promise.reject new hinoki.ThrowInFactoryError path, container, error

     unless hinoki.isThenable valueOrPromise
       # valueOrPromise is not a promise but an value
@@ -196,7 +196,7 @@
         }
         return value
       .catch (rejection) ->
-        Promise.reject new hinoki.errors.PromiseRejected path, container, rejection
+        Promise.reject new hinoki.PromiseRejectedError path, container, rejection

   ###################################################################################
   # functions that resolve factories
@@ -290,76 +290,80 @@

   # constructors for errors which are catchable with bluebirds `catch`

-  hinoki.errors = {}
-
-  hinoki.errors.ErrorInResolvers = (path, containers, error) ->
+  # the base error for all other hinoki errors
+  # not to be instantiated directly
+  hinoki.BaseError = ->
+  hinoki.BaseError.prototype = new Error
+  hinoki.BaseError.prototype.name = 'BaseError'
+
+  hinoki.ErrorInResolversError = (path, containers, error) ->
     this.message = "error in resolvers for '#{path[0]}' (#{hinoki.pathToString path}). original error: #{error.toString()}"
-    this.type = 'ErrorInResolver'
     this.path = path
     this.containers = containers
     this.error = error
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.ErrorInResolvers.prototype = new Error
-
-  hinoki.errors.Unresolvable = (path, container) ->
+  hinoki.ErrorInResolversError.prototype = new hinoki.BaseError
+  hinoki.ErrorInResolversError.prototype.name = 'ErrorInResolversError'
+
+  hinoki.UnresolvableError = (path, container) ->
     this.message = "unresolvable name '#{path[0]}' (#{hinoki.pathToString path})"
-    this.type = 'Unresolvable'
     this.path = path
     this.container = container
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.Unresolvable.prototype = new Error
-
-  hinoki.errors.InvalidResolution = (path, resolution, errors) ->
+  hinoki.UnresolvableError.prototype = new hinoki.BaseError
+  hinoki.UnresolvableError.prototype.name = 'UnresolvableError'
+
+  hinoki.InvalidResolutionError = (path, resolution, errors) ->
     lines = errors
     lines.unshift "errors in resolution returned by resolvers for '#{path[0]}' (#{hinoki.pathToString path}):"
     this.message = lines.join '\n'
-    this.type = 'InvalidResolution'
     this.path = path
     this.resolution = resolution
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.InvalidResolution.prototype = new Error
-
-  hinoki.errors.CircularDependency = (path, containers) ->
+  hinoki.InvalidResolutionError.prototype = new hinoki.BaseError
+  hinoki.InvalidResolutionError.prototype.name = 'InvalidResolutionError'
+
+  hinoki.CircularDependencyError = (path, containers) ->
     this.message = "circular dependency #{hinoki.pathToString path}"
-    this.type = 'CircularDependency'
     this.path = path
     this.containers = containers
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.CircularDependency.prototype = new Error
-
-  hinoki.errors.ErrorInFactory = (path, container, error) ->
+  hinoki.CircularDependencyError.prototype = new hinoki.BaseError
+  hinoki.CircularDependencyError.prototype.name = 'CircularDependencyError'
+
+  hinoki.ThrowInFactoryError = (path, container, error) ->
     this.message = "error in factory for '#{path[0]}'. original error: #{error.toString()}"
-    this.type = 'ErrorInFactory'
     this.path = path
     this.container = container
     this.error = error
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.ErrorInFactory.prototype = new Error
-
-  hinoki.errors.FactoryReturnedUndefined = (path, container, factory) ->
+  hinoki.ThrowInFactoryError.prototype = new hinoki.BaseError
+  hinoki.ThrowInFactoryError.prototype.name = 'ThrowInFactoryError'
+
+  hinoki.FactoryReturnedUndefinedError = (path, container, factory) ->
     this.message = "factory for '#{path[0]}' returned undefined"
-    this.type = 'FactoryReturnedUndefined'
     this.path = path
     this.container = container
     this.factory = factory
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.FactoryReturnedUndefined.prototype = new Error
-
-  hinoki.errors.PromiseRejected = (path, container, error) ->
+  hinoki.FactoryReturnedUndefinedError.prototype = new hinoki.BaseError
+  hinoki.FactoryReturnedUndefinedError.prototype.name = 'FactoryReturnedUndefinedError'
+
+  hinoki.PromiseRejectedError = (path, container, error) ->
     this.message = "promise returned from factory for '#{path[0]}' was rejected. original error: #{error.toString()}"
-    this.type = 'PromiseRejected'
     this.path = path
     this.container = container
     this.error = error
     if Error.captureStackTrace
       Error.captureStackTrace(this, this.constructor)
-  hinoki.errors.PromiseRejected.prototype = new Error
+  hinoki.PromiseRejectedError.prototype = new hinoki.BaseError
+  hinoki.PromiseRejectedError.prototype.name = 'PromiseRejectedError'

   ###################################################################################
   # path


:lipstick: method order. Add some public method docs.

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -14,12 +14,19 @@
 class Keymap
   Emitter.includeInto(this)

+  # Public:
+  #
+  # options - An {Object} with the following optional keys:
+  #   :defaultTarget - This will be used as the target of events whose target
+  #     is `document.body` to allow for a catch-all element when nothing is
+  #     focused
   constructor: (options) ->
     @defaultTarget = options?.defaultTarget
     @keyBindings = []
     @keystrokes = []
     @watchSubscriptions = {}

+  # Public: Unwatch all watched paths.
   destroy: ->
     for filePath, subscription of @watchSubscriptions
       subscription.off()
@@ -30,7 +37,6 @@
   #   so they can be removed later.
   # bindings - An {Object} whose top-level keys point at sub-objects mapping
   #   keystroke patterns to commands.
-
   addKeyBindings: (source, keyBindingsBySelector) ->
     for selector, keyBindings of keyBindingsBySelector
       @addKeyBindingsForSelector(source, selector, keyBindings)
@@ -49,6 +55,80 @@
       @addKeyBindings(bindingsPath, season.readFileSync(bindingsPath))
       @watchKeyBindings(bindingsPath) if options?.watch

+  # Public: Remove the key bindings added with {::addKeyBindings} or
+  # {::loadKeyBindings}.
+  #
+  # source - A {String} representing the `source` in a previous call to
+  #   {::addKeyBindings} or the path in {::loadKeyBindings}.
+  removeKeyBindings: (source) ->
+    @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source
+
+  # Public: Dispatch a custom event associated with the matching key binding for
+  # the given {KeyboardEvent} if one can be found.
+  #
+  # If a matching binding is found on the event's target or one of its
+  # ancestors, `.preventDefault()` is called on the keyboard event and the
+  # binding's command is emitted as a custom event on the matching element.
+  #
+  # If the matching binding's command is 'native!', the method will terminate
+  # without calling `.preventDefault()` on the keyboard event, allowing the
+  # browser to handle it as normal.
+  #
+  # If the event's target is `document.body`, it will be treated as if its
+  # target is `.defaultTarget` if that property is assigned on the keymap.
+  handleKeyboardEvent: (event) ->
+    @keystrokes.push(@keystrokeForKeyboardEvent(event))
+    keystrokes = @keystrokes.join(' ')
+
+    target = event.target
+    target = @defaultTarget if event.target is document.body and @defaultTarget?
+    while target? and target isnt document
+      candidateBindings = @keyBindingsForKeystrokesAndTarget(keystrokes, target)
+      if candidateBindings.length > 0
+        @keystrokes = []
+        return if @dispatchCommandEvent(event, target, candidateBindings[0].command)
+      target = target.parentElement
+
+  addKeyBindingsForSelector: (source, selector, keyBindings) ->
+    # Verify selector is valid before registering any bindings
+    try
+      document.body.webkitMatchesSelector(selector.replace(/!important/g, ''))
+    catch e
+      console.warn("Encountered an invalid selector adding key bindings from '#{source}': '#{selector}'")
+      return
+
+    for keystroke, command of keyBindings
+      keyBinding = new KeyBinding(source, command, keystroke, selector)
+      @keyBindings.push(keyBinding)
+
+  # Public: Get the key bindings for a given command and optional target.
+  #
+  # params - An {Object} whose keys constrain the binding search:
+  #   :command - A {String} representing the name of a command, such as
+  #     'editor:backspace'
+  #   :target - An optional DOM element constraining the search. If this
+  #     parameter is supplied, the call will only return bindings that can be
+  #     invoked by a KeyboardEvent originating from the target element.
+  findKeyBindings: (params={}) ->
+    {command, target} = params
+
+    bindings = @keyBindings
+
+    if command?
+      bindings = bindings.filter (binding) -> binding.command is command
+
+    if target?
+      candidateBindings = bindings
+      bindings = []
+      element = target
+      while element? and element isnt document
+        matchingBindings = candidateBindings
+          .filter (binding) -> element.webkitMatchesSelector(binding.selector)
+          .sort (a, b) -> a.compare(b)
+        bindings.push(matchingBindings...)
+        element = element.parentElement
+    bindings
+
   watchKeyBindings: (filePath) ->
     unless @watchSubscriptions[filePath]?.cancelled is false
       @watchSubscriptions[filePath] =
@@ -77,38 +157,12 @@
   # For testing purposes
   getOtherPlatforms: -> OtherPlatforms

-  # Public: Remove the key bindings added with {::addKeyBindings} or
-  # {::loadKeyBindings}.
-  #
-  # source - A {String} representing the `source` in a previous call to
-  #   {::addKeyBindings} or the path in {::loadKeyBindings}.
-  removeKeyBindings: (source) ->
-    @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source
-
-  addKeyBindingsForSelector: (source, selector, keyBindings) ->
-    # Verify selector is valid before registering any bindings
-    try
-      document.body.webkitMatchesSelector(selector.replace(/!important/g, ''))
-    catch e
-      console.warn("Encountered an invalid selector adding key bindings from '#{source}': '#{selector}'")
-      return
-
-    for keystroke, command of keyBindings
-      keyBinding = new KeyBinding(source, command, keystroke, selector)
-      @keyBindings.push(keyBinding)
-
-  handleKeyboardEvent: (event) ->
-    @keystrokes.push(@keystrokeForKeyboardEvent(event))
-    keystrokes = @keystrokes.join(' ')
-
-    target = event.target
-    target = @defaultTarget if event.target is document.body and @defaultTarget?
-    while target? and target isnt document
-      candidateBindings = @keyBindingsForKeystrokesAndTarget(keystrokes, target)
-      if candidateBindings.length > 0
-        @keystrokes = []
-        return if @dispatchCommandEvent(event, target, candidateBindings[0].command)
-      target = target.parentElement
+  keyBindingsForKeystrokesAndTarget: (keystrokes, target) ->
+    @keyBindings
+      .filter (binding) ->
+        binding.keystrokes.indexOf(keystrokes) is 0 \
+          and target.webkitMatchesSelector(binding.selector)
+      .sort (a, b) -> a.compare(b)

   dispatchCommandEvent: (keyboardEvent, target, command) ->
     return true if command is 'native!'
@@ -123,41 +177,6 @@
     target.dispatchEvent(commandEvent)
     not commandEvent.keyBindingAborted

-  keyBindingsForKeystrokesAndTarget: (keystrokes, target) ->
-    @keyBindings
-      .filter (binding) ->
-        binding.keystrokes.indexOf(keystrokes) is 0 \
-          and target.webkitMatchesSelector(binding.selector)
-      .sort (a, b) -> a.compare(b)
-
-  # Public: Get the key bindings for a given command and optional target.
-  #
-  # params - An {Object} whose keys constrain the binding search:
-  #   :command - A {String} representing the name of a command, such as
-  #     'editor:backspace'
-  #   :target - An optional DOM element constraining the search. If this
-  #     parameter is supplied, the call will only return bindings that can be
-  #     invoked by a KeyboardEvent originating from the target element.
-  findKeyBindings: (params={}) ->
-    {command, target} = params
-
-    bindings = @keyBindings
-
-    if command?
-      bindings = bindings.filter (binding) -> binding.command is command
-
-    if target?
-      candidateBindings = bindings
-      bindings = []
-      element = target
-      while element? and element isnt document
-        matchingBindings = candidateBindings
-          .filter (binding) -> element.webkitMatchesSelector(binding.selector)
-          .sort (a, b) -> a.compare(b)
-        bindings.push(matchingBindings...)
-        element = element.parentElement
-    bindings
-
   # Public: Translate a keydown event to a keystroke string.
   #
   # event - A {KeyboardEvent} of type 'keydown'


enable inspector

enable inspector

--- src/layout.coffee
+++ src/layout.coffee
@@ -25,7 +25,8 @@
 #     grid
 #       sprite1
 #       sprite2...
-#     <-- state...
+#     inspector
+#       active_sprite.state...

 exports.layout = (game_dict) ->
   level_dict = game_dict[my.key.children][0]
@@ -39,6 +40,6 @@
     controls(level_dict)
     # grid(level_dict)
     # sprites?
-    # inspector?
+    inspector
   ]
   game_dict


Copy report attachments

Copy report attachments

--- website/gulpfile.coffee
+++ website/gulpfile.coffee
@@ -103,12 +103,16 @@
   gulp.src '../report/**/*.{png,jpg,svg}'
     .pipe gulp.dest 'dist/report'

+gulp.task 'report-attachments', ->
+  gulp.src '../report/attachments/*.*'
+    .pipe gulp.dest 'dist/report/attachments'
+
 gulp.task 'report-scripts', ['coffee'], ->
   gulp.src ['.tmp/js/report.js']
     .pipe gulp.dest('dist/js')

-gulp.task 'report', ['report-markdown', 'report-images', 'report-scripts']
+gulp.task 'report', ['report-markdown', 'report-images', 'report-attachments', 'report-scripts']

 #
 # Sample Data


Fixed small bugs on main_loop timeout. Added main_loop.pause.

--- engine/main_loop.coffee
+++ engine/main_loop.coffee
@@ -2,7 +2,7 @@
 {get_last_char_phy, step_world, step_world, phy_to_ob} = require './physics'
 # Logic assumes a frame won't be longer than this
 # Below that point, things go slow motion
-MAX_FRAME_DURATION = 167   # 10 fps
+MAX_FRAME_DURATION = 167  # 6 fps

 class MainLoop

@@ -11,45 +11,51 @@
         @frame_duration = 16
         @last_frame_durations = [16, 16, 16, 16, 16, 16, 16, 16, 16, 16]
         @_fdi = 0
+        @timeout = context.MYOU_PARAMS.timeout
+        @timeout? and console.log 'WARNING: Timeout set: ' + @timeout
+        @pause_time = 0
         @last_time = 0
-        @timeout = null
-        @timeout_timer = null
+        @timeout_timer = @timeout
         @enabled = false
         @context = context
-        @timeout = context.MYOU_PARAMS.timeout
-        @timeout? and console.log 'WARNING: Timeout set: ' + @timeout
+        @_bound_tick = @tick.bind @

     run: ->
-        @_bound_tick = @tick.bind @
+        if @enabled
+            return
         requestAnimationFrame @_bound_tick
         not @enabled and @timeout? and console.log 'Main loop running: ' + Math.floor(performance.now())
         @enabled = true
         @last_time = performance.now()

-
     stop: ->
         cancelAnimationFrame @_bound_tick
         @enabled = false

+    pause: (pause_time)->
+        if not pause_time?
+            console.log 'WARNING: Undefined pause_time.'
+        @pause_time = pause_time
+
     tick: ->
         requestAnimationFrame @_bound_tick
         time = performance.now()
-        @frame_duration = frame_duration = Math.min time - @last_time, MAX_FRAME_DURATION
+        @frame_duration = frame_duration = Math.min(time - @last_time, MAX_FRAME_DURATION)
+        @last_time = time

-
-        if @timeout? and @timeout_timer > @timeout
-            @stop()
-            @timeout_timer = 0
-            console.log 'Main loop paused: ' + Math.floor(time)
-
-        if @enabled == false or @context.loaded_scenes.length == 0
+        if not @enabled or @pause_time > 0
+            @pause_time -= frame_duration
             return

-        @timeout_timer += frame_duration
+        if  @enabled and @timeout_timer <= 0
+            console.log  'Main loop paused: ' + Math.floor(time)
+            @enabled = false
+            return
+
+        @timeout_timer -= frame_duration
         @last_frame_durations[@_fdi] = frame_duration
         @_fdi = (@_fdi+1) % @last_frame_durations.length

-        @last_time = time

         for scene in @context.loaded_scenes
             if not scene.enabled
@@ -90,9 +96,17 @@

         @context.events.reset_frame_events()

+    set_timeout: (timeout, reset=true)=>
+        'WARNING: Timeout set: ' + timeout
+        @timeout = timeout
+        if reset then @reset_timeout()
+
     reset_timeout: =>
-        @timeout_timer = 0
-        @enabled = @enabled or @run()
+        if not @enabled
+            console.log 'Main loop running: ' + Math.floor( performance.now())
+
+        @timeout_timer =  @timeout
+        @enabled = true

 module.exports = {MainLoop}


Minor corrections in the mirrors service

--- library/vending/mirrors.coffee
+++ library/vending/mirrors.coffee
@@ -77,5 +77,5 @@
     # class and its module implementation for more information on it.
     document.describe @prototype.GET, ->
         @follows tools.urlWithHost no, ["api", "doc"]
-        @synopsis "Get all of the APIs in the system"
-        @results "An array of objects, each describes services"
+        @synopsis "Get all of the APIs available in the system"
+        @results "An array of objects, each describes a service"


always run all perftest cases (seems that picking cases was broken)

--- performance/PerformanceTestCases.coffee
+++ performance/PerformanceTestCases.coffee
@@ -99,15 +99,4 @@
       gen.stream().holdWhen(gen.stream().map(false))
 }

-
-_ = require "lodash"
-
-includeCase = (key) ->
-  args = process.argv.slice(2)
-  if args.length
-    _.any args, (arg) ->
-      key.toLowerCase().indexOf(arg.toLowerCase()) >= 0
-  else
-    true
-
-module.exports = _.pick cases, (value, key) -> includeCase(key)
+module.exports = cases


Fixes #998 Ensure we look at the current control, not the entire collection

--- app/assets/javascripts/append_date.js.coffee
+++ app/assets/javascripts/append_date.js.coffee
@@ -8,13 +8,12 @@

   el.datepicker({'format': 'yyyy-mm-dd'})

-  href = el.attr('href')
-
   el.click (e) ->
     e.stopPropagation()
     e.preventDefault()

     originalText = $(this).text()
+    href = $(this).attr('href')
     $(this).text('Confirm without date')

     $(this).bind('click.confirm', (e) ->
@@ -30,6 +29,8 @@

   el.one 'changeDate', ->
     date = $(this).datepicker('getDate')
+    href = $(this).attr('href')
+
     url  = "#{href}&planting[finished_at]=#{date}"

     link = $("")


Fix resetResizeTextarea on other pages load

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -93,6 +93,8 @@

   if window.location.hash
     showAwardStep("step-#{window.location.hash.substr(1)}")
+    # Resize textareas that were previously hidden
+    resetResizeTextarea()
   $(document).on "click", ".js-step-link", (e) ->
     e.preventDefault()
     if !$(this).hasClass("step-current")


Fix issue where loading screen would stay stuck.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -10,3 +10,6 @@
 document.addEventListener 'page:fetch', ->
   html = "

"
   $('body').prepend(html)
+
+document.addEventListener 'page:receive', ->
+  $('#loading').remove()


simplify js

simplify js

--- app/assets/javascripts/eligibility.js.coffee
+++ app/assets/javascripts/eligibility.js.coffee
@@ -7,8 +7,5 @@
         ($ '.eligibility_qae_for_trade_expiery_date').addClass('visuallyhidden')

     toggleOther()
-    ($ '#eligibility_current_holder_of_qae_for_trade_true').on 'change', ->
+    ($ '#eligibility_current_holder_of_qae_for_trade_true, #eligibility_current_holder_of_qae_for_trade_false').on 'change', ->
       toggleOther()
-
-    ($ '#eligibility_current_holder_of_qae_for_trade_false').on 'change', ->
-      toggleOther()


Add separator as a line divider

Add separator as a line divider

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -249,8 +249,11 @@
   renderItem: (data) ->
     html = ""

-    # Separator
+    # Divider
     return "

" if data is "divider"
+
+    # Separator is a full-width divider
+    return "

" if data is "separator"

     # Header
     return "
#{data.header}
" if data.header?


error handling

error handling

--- modules/gui/tools/popup.coffee
+++ modules/gui/tools/popup.coffee
@@ -52,4 +52,5 @@
             fn null, child

     catch err
-        alert err.message
+        alert 'Error: '+err.message
+        console.log err


Assert that params are defined when required by the action

--- src/extras/testing/controller_test_case.coffee
+++ src/extras/testing/controller_test_case.coffee
@@ -15,6 +15,9 @@
       @assert false, "Route doesn't exist for action"
       return

+    if actionRoutes[0].namedArguments.length > 0
+      @assert params.params, 'params are required for #{action}'
+
     for namedRoute in actionRoutes[0].namedArguments
       @assert namedRoute of params.params, 'named argument mismatch'


[Forms | Company Financials] UK Sales: system should ignore commas in calculation

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -212,7 +212,7 @@
         turnover_selector = "[id='form["+turnover_data+"_"+sales_year+"]']"
         exports_selector = "[id='form["+exports_data+"_"+sales_year+"]']"
         sales_selector = ".js-sales-value[data-year='"+sales_year+"']"
-        sales_value = parseInt($(turnover_selector).val()) - parseInt($(exports_selector).val())
+        sales_value = parseInt($(turnover_selector).val().replace(/,/g, '')) - parseInt($(exports_selector).val().replace(/,/g, ''))
         if sales_value
           $(sales_selector).text(sales_value)
         else


Added refreshing page

Added refreshing page

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -15,6 +15,11 @@

   sessionStorage.setItem('previous', window.location.href);
   sessionStorage.setItem('goBack', goBack);
+
+  shouldRefresh = sessionStorage.getItem('shouldRefresh');
+  if shouldRefresh == true
+    sessionStorage.setItem('shouldRefresh', false);
+    window.location.reload();

   $('#job_date_type').change(->
@@ -69,6 +74,7 @@
   ).change();

   $('.back_button').click(->
+    sessionStorage.setItem('shouldRefresh', true);
     history.go(goBack);
   );


fix current year not being changed by the C2 question

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -173,6 +173,9 @@

     fy_latest_changed_input.find("input").attr("disabled", "disabled")
     $(".js-financial-year-changed-dates").attr("data-year", fy_year)
+
+    # We should change the last year date regardless if it's present or not
+    fy_latest_changed_input.find("input.js-fy-year").val(fy_year)

     updateYearEnd()


client: add 30 sec timeout and ping kite after vm closed

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -252,9 +252,16 @@

     kite = @mountedMachine.getBaseKite()

-    if @amIHost
-    then kite.on 'close', @bound 'handleCollaborationEndedForHost'
-    else kite.on 'close', @bound 'handleCollaborationEndedForParticipant'
+    kite.once 'close', =>
+      kite.ping()
+        .timeout(30000)
+        .then =>
+          kite.reconnect()
+          @listenKlientKite()
+        .catch =>
+          if @amIHost
+          then @handleCollaborationEndedForHost()
+          else @handleCollaborationEndedForParticipant()

   setWatchMap: ->


Remove old _createDomainEvent function from the aggregate coffee

--- src/aggregate/aggregate.coffee
+++ src/aggregate/aggregate.coffee
@@ -18,26 +18,15 @@
       @_eventric.log.error err
       throw new Error err

-    domainEvent = @_createDomainEvent domainEventName, DomainEventClass, domainEventPayload
+    aggregate:
+      id: @id
+      name: @_name
+    domainEvent = @_context.createDomainEvent domainEventName, DomainEventClass, domainEventPayload, aggregate
     @_domainEvents.push domainEvent

     @_handleDomainEvent domainEventName, domainEvent
     @_eventric.log.debug "Created and Handled DomainEvent in Aggregate", domainEvent
     # TODO: do a rollback if something goes wrong inside the handle function
-
-
-  _createDomainEvent: (domainEventName, DomainEventClass, domainEventPayload) ->
-    payload = {}
-    DomainEventClass.apply payload, [domainEventPayload]
-
-    new @_eventric.DomainEvent
-      id: @_eventric.generateUid()
-      name: domainEventName
-      aggregate:
-        id: @id
-        name: @_name
-      context: @_context.name
-      payload: payload

   _handleDomainEvent: (domainEventName, domainEvent) ->


js function from main project

js function from main project

--- app/assets/javascripts/s3_form_for.js.coffee
+++ app/assets/javascripts/s3_form_for.js.coffee
@@ -37,21 +37,21 @@
     disable_fields: 'textarea,input[type=text],input[type=checkbox],select'
     file_name_for_upload: '#file_name_for_upload'

-  enable_all_field: ->
-    $(@selectors.s3_form).find(@selectors.disable_fields).removeAttr('disabled')
-
-  reset_s3_uploder: ->
-    $('.upload_uploading').hide()
-    $('.upload_picking').show()
-    $('.progress-bar').removeAttr('style')
-
-  show_submit_button: ->
-    $(@selectors.submit).show()
-    $(@selectors.cancel_upload).addClass('hide')
-
-  show_cancel_button: ->
-    $(@selectors.submit).hide()
-    $(@selectors.cancel_upload).removeClass('hide')
+#  enable_all_field: ->
+#    $(@selectors.s3_form).find(@selectors.disable_fields).removeAttr('disabled')
+#
+#  reset_s3_uploder: ->
+#    $('.upload_uploading').hide()
+#    $('.upload_picking').show()
+#    $('.progress-bar').removeAttr('style')
+#
+#  show_submit_button: ->
+#    $(@selectors.submit).show()
+#    $(@selectors.cancel_upload).addClass('hide')
+#
+#  show_cancel_button: ->
+#    $(@selectors.submit).hide()
+#    $(@selectors.cancel_upload).removeClass('hide')

   $.extend settings, options

@@ -206,20 +206,7 @@
           $uploadForm.find("input[name='key']").val(settings.path + key)
         data

-#    s3_upload_complete: (e, content) =>
-#      console.log('s3_upload_complete')
-#      @enable_all_field()
-#      # Note: on older IE, this is hit even on failure
-#      # if content.url?
-#      $(this).find(".upload_uploading").hide()
-#      $(this).find(".upload_finished").show().removeClass "hidden"
-#      $(@selectors.submit).removeClass("disabled").prop "disabled", false
-#      $("#upload_skip_link").removeClass "hidden"
-#      $("#upload_s3_path").val(content.filepath)
-#      $(@selectors.s3_form).submit()
-
     $uploadForm.on 'show_cancel_button', ->
-      console.log('show_cancel_button')
       $(selectors.submit).hide()
       $(selectors.cancel_upload).removeClass('hide')

@@ -235,10 +222,19 @@
     $uploadForm.on 'show_submit_button', ->
       $(selectors.submit).show()
       $(selectors.cancel_upload).addClass('hide')
-  #
+
     $uploadForm.on 'show_cancel_button', ->
       $(selectors.submit).hide()
       $(selectors.cancel_upload).removeClass('hide')
+
+    $uploadForm.on 's3_upload_complete', (e, content) =>
+      $(selectors.s3_form).find(selectors.disable_fields).removeAttr('disabled')
+      $uploadForm.find(".upload_uploading").hide()
+      $uploadForm.find(".upload_finished").show().removeClass "hidden"
+      $(selectors.submit).removeClass("disabled").prop "disabled", false
+      $("#upload_skip_link").removeClass "hidden"
+      $("#upload_s3_path").val(content.filepath)
+      $(selectors.s3_form).submit()

   build_content_object = ($uploadForm, file, result) ->
     content = {}


Move target line out of viewport if no target set

If no target is set (present_value() or future_value() equals null), no target
line is visible as it is moved out of the viewport. If the target is set to
0, the target line is displayed as expected. Closes #1452.

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -156,9 +156,9 @@
         .transition()
         .attr 'y', (d) =>
           value = if d.get('target_line_position') is '1'
-            if d.safe_present_value() == 0 then 3*@series_height else d.safe_present_value()
+            if d.present_value() == null and d.safe_present_value() == 0 then 3*@series_height else d.safe_present_value()
           else
-            if d.safe_future_value() == 0 then 3*@series_height else d.safe_future_value()
+            if d.future_value() == null and d.safe_future_value() == 0 then 3*@series_height else d.safe_future_value()

           @series_height - @y(value)


continue work on geometric collisions

continue work on geometric collisions

* initial draft of 'mouse' collision routine
* make minor adjustments to set 'top', 'bottom', 'left', and 'right'

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -266,22 +266,22 @@
             _top = @_bnd[0]
             for pt in @_bnd when pt.y > _top.y
                 _top = pt
-            @set 'y', to - _top.get('dist') * Math.sin _top.get('a')
+            @set 'y', to - Math.abs(_top.get('y') - _top.get('org_y')), false
         else if what.match /^bottom$/
             _bottom = @_bnd[0]
             for pt in @_bnd when pt.y < _bottom.y
                 _bottom = pt
-            @set 'y', to + _bottom.get('dist') * Math.sin _bottom.get('a')
+            @set 'y', to + Math.abs(_bottom.get('y') - _bottom.get('org_y')), false
         else if what.match /^right$/
             _right = @_bnd[0]
             for pt in @_bnd when pt.x > _right.x
                 _right = pt
-            @set 'x', to - _right.get('dist') * Math.cos _right.get('a')
+            @set 'x', to - Math.abs(_right.get('x') - _right.get('org_x')), false
         else if what.match /^left$/
             _left = @_bnd[0]
             for pt in @_bnd when pt.x < _left.x
                 _left = pt
-            @set 'x', to + _left.get('dist') * Math.cos _left.get('a')
+            @set 'x', to + Math.abs(_left.get('x') - _left.get('org_x')), false
         else if what.match /^imageFile$/
             if env.IMAGE_PATH.match /\/$/
                 @_dis.image.src = env.IMAGE_PATH.concat to
@@ -425,8 +425,27 @@
     collidesWith: (other) ->
         if other is 'mouse'
             if @_dis.visible
-                if @get('left', false) <= Greenhorn.getMouseX() <= @get('right', false) and
-                @get('bottom', false) <= Greenhorn.getMouseY() <= @get('top', false)
+                if @distanceTo('mouse') <= @get('radius', false)
+                    #declare Line arrays
+                    myLines = []
+                    otherLines = []
+                    mousePos = new Point Greenhorn.getMouseX(), Greenhorn.getMouseY(), 0, 0
+
+                    #create Lines to check for collisions
+                    for pt, i in @_bnd
+                        otherLines.push new Line pt, mousePos
+                        if i is @_bnd.length - 1
+                            myLines.push new Line pt, @_bnd[0]
+                        else
+                            myLines.push new Line pt, @_bnd[i+1]
+
+                    #check for collisions
+                    for myLine in myLines
+                        for otherLine in otherLines
+                            unless myLine.p1 is otherLine.p1 or myLine.p1 is otherLine.p2
+                                unless myLine.p2 is otherLine.p1 or myLine.p2 is otherLine.p2
+                                    if myLine.collidesWith otherLine
+                                        return false
                     return true
             return false
         else
@@ -434,16 +453,16 @@
                 if other._dis.visible
                     if @_dis.level == other._dis.level
                         if @distanceTo(other) <= @get('radius', false) + other.get('radius', false)
-                            #declare arrays
+                            #declare Line arrays
                             myLines = []
                             otherLines = []

-                            #create lines representing boundaries
+                            #create Lines representing boundaries
                             for pt, i in @_bnd
                                 if i is @_bnd.length - 1
-                                    myLines.push new Line pt, @_bnds[0]
+                                    myLines.push new Line pt, @_bnd[0]
                                 else
-                                    myLines.push new Line pt, @_bnds[i+1]
+                                    myLines.push new Line pt, @_bnd[i+1]
                             for pt, i in other._bnd
                                 if i is other._bnd.length - 1
                                     otherLines.push new Line pt, other._bnd[0]


one more typo in spec, tobe -> toBe

one more typo in spec, tobe -> toBe


compressedUpdates are now never inserted with inS3

it is now always added later, and a new update is forced for any
addition to an archived update

--- app/coffee/MongoManager.coffee
+++ app/coffee/MongoManager.coffee
@@ -59,7 +59,6 @@

 	insertCompressedUpdate: (project_id, doc_id, update, temporary, callback = (error) ->) ->
-		inS3 = update.inS3?
 		update = {
 			doc_id: ObjectId(doc_id.toString())
 			project_id: ObjectId(project_id.toString())
@@ -67,8 +66,6 @@
 			meta:   update.meta
 			v:      update.v
 		}
-		if inS3
-			update.inS3 = true

 		if temporary
 			seconds = 1000


Don't call sadd with blank args

Don't call sadd with blank args

--- app/coffee/RedisManager.coffee
+++ app/coffee/RedisManager.coffee
@@ -143,4 +143,7 @@
 	getAndSetProject: (project_id, callback = (error) ->) ->
 		rclient.smembers keys.docsInProject(project_id: project_id), (error, doc_ids) ->
 			return callback(error) if error?
-			rclient.sadd keys.docsInProject(project_id: project_id), doc_ids..., callback
+			if doc_ids.length > 0
+				rclient.sadd keys.docsInProject(project_id: project_id), doc_ids..., callback
+			else
+				callback()


slabs

slabs

--- src/javascript/modules/block_maze/index.coffee
+++ src/javascript/modules/block_maze/index.coffee
@@ -27,6 +27,37 @@
   Systems.camera_follow_system()
 ])

+
+generateSlabComps = () ->
+  compLists = []
+  dark=false
+  back = -2
+  left = -2
+  width = 4
+  length = 4
+  height = 0.5
+  y = -2
+  z = -2
+  lightColor = 0xffffff
+  darkColor = 0x333366
+
+  numXSlabs = 10
+  numZSlabs = 10
+
+  for i in [0...numZSlabs]
+    z = back + i*length
+    for j in [0...numXSlabs]
+      dark = if i % 2 == 0
+        j % 2 == 0
+      else
+        j % 2 != 0
+      x = left + j*width
+      color = if dark then darkColor else lightColor
+      compLists.push mkSlabComps(canVec3(x, y, z), canVec3(width,height,length), color)
+      dark = !dark
+
+  return compLists
+
 mkCubeComps = (pos,color=0xffffff,name='Cube') ->
   [
     C.buildCompForType(T.Name, name: name)
@@ -34,6 +65,17 @@
     C.buildCompForType(T.Physical,
       kind: 'cube'
       data: new C.Physical.Cube(color)
+    )
+  ]
+
+mkSlabComps = (pos,dim,color=0xffffff,name='Slab') ->
+  [
+    C.buildCompForType(T.Name, name: 'Slab')
+    C.buildCompForType(T.Location, position: pos)
+    C.buildCompForType(T.Physical,
+      kind: 'block'
+      bodyType: Cannon.Body.STATIC
+      data: new C.Physical.Block(color, dim)
     )
   ]

@@ -58,36 +100,29 @@
   ])

-  estore.createEntity mkCubeComps(canVec3(-1,0,-1),0x993333)
+  for comps in generateSlabComps()
+    estore.createEntity comps
+
   estore.createEntity mkCubeComps(canVec3(-1,1,-1),0x993333)
   estore.createEntity mkCubeComps(canVec3(-1.1,2,-1),0x993333)
   estore.createEntity mkCubeComps(canVec3(20,0,-1),0x993333)
   estore.createEntity mkCubeComps(canVec3(20,0,10),0x993333)
   estore.createEntity mkCubeComps(canVec3(-1,0,10),0x993333)

-  groundQuat = canQuat()
-  groundQuat.setFromAxisAngle(canVec3(1, 0, 0), -Math.PI / 2)
-  estore.createEntity([
-    C.buildCompForType(T.Name, name: 'Ground')
-    C.buildCompForType(T.Location, position: canVec3(0,-10,0), quaternion: groundQuat)
-    C.buildCompForType(T.Physical,
-      kind: 'plane'
-      data: new C.Physical.Plane(0x9999cc, 100, 100)
-    )
-  ])
-  estore.createEntity([
-    C.buildCompForType(T.Name, name: 'Ground')
-    C.buildCompForType(T.Location, position: canVec3(0,-1.5,0))
-    C.buildCompForType(T.Physical,
-      kind: 'block'
-      bodyType: Cannon.Body.STATIC
-      data: new C.Physical.Block(0x9999cc, canVec3(5,1,10))
-    )
-  ])
+  # groundQuat = canQuat()
+  # groundQuat.setFromAxisAngle(canVec3(1, 0, 0), -Math.PI / 2)
+  # estore.createEntity([
+  #   C.buildCompForType(T.Name, name: 'Ground')
+  #   C.buildCompForType(T.Location, position: canVec3(0,-10,0), quaternion: groundQuat)
+  #   C.buildCompForType(T.Physical,
+  #     kind: 'plane'
+  #     data: new C.Physical.Plane(0x9999cc, 100, 100)
+  #   )
+  # ])
+

   estore.createEntity([
     C.buildCompForType(T.Name, name: 'Follow Camera')
-    # C.buildCompForType(T.Tag, name: 'follow_cam')
     C.buildCompForType(T.FollowCamera, followTag: 'player_piece')
     C.buildCompForType(T.Location, position: canVec3(0,3,5))
   ])


Fix child function depending on parent to compile

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1533,7 +1533,7 @@
       prevSize = ctx.deferred().length
       [expression, dependencyName, useScope, lhs, rhs] = deferred = ctx.deferred().shift()
       if useScope isnt ctx.currentScopeIndex() and (ctx.isDeclared dependencyName) or
-          (ctx.isFinallyDeclared dependencyName)
+          (ctx.isTyped dependencyName)
         compiledPairs.push definitionPairCompile ctx, lhs, rhs
         deferredCount = 0
       else


Fix hoisting inside match and add recursive hoisting

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -583,11 +583,21 @@
     throw new Error "Missing definition of a function"
   # Find invalid (hoistable) wheres and let the function body
   # define them
+  log printAst wheres
+  if (unpairs wheres).length % 2 != 0
+    throw new Error "Missing definition for last assignement inside function"
   [readyWheres, hoistableWheres] = wheresWithHoisting wheres
   addToScopeAllNames readyWheres
   wheresDef = (graphToWheres readyWheres).map(compileDef).join '\n'
   bodyDef = compileImpl body, graphToWheres hoistableWheres
   [wheresDef, bodyDef]
+
+unpairs = (pairs) ->
+  unpaired = []
+  for [a, b] in pairs
+    unpaired.push a if a?
+    unpaired.push b if b?
+  unpaired

 addToScopeAllNames = (graph) ->
   for {names, def: [pattern, def]} in graph
@@ -604,7 +614,7 @@
   ]

 graphToWheres = (graph) ->
-  graph.map ({def}) -> def
+  graph.map ({def: [pattern, def], missing}) -> [pattern, def, missing]

 # Returns two new graphs, one which needs hoisting and one which doesnt
 findHoistableWheres = ([graph, lookupTable]) ->
@@ -878,8 +888,10 @@
   'match': (hoistableWheres, onwhat, cases...) ->
     varNames = []
     if not onwhat
-      throw new Error 'match `onwhat` missing'
+      throw new Error 'match `onwhat:` missing'
     exp = compileImpl onwhat
+    if cases.length % 2 != 0
+      throw new Error 'match missing result for last pattern'
     compiledCases = (for [pattern, result], i in pairs cases
       control = if i is 0 then 'if' else ' else if'
       {precs, assigns} = patternMatch pattern, exp, mainCache
@@ -890,12 +902,12 @@
       else
         varNames.push vars...
       {conds, preassigns} = constructCond precs
-      hoistedWheres = hoistWheres hoistableWheres, assigns
+      [hoistedWheres, furtherHoistable] = hoistWheres hoistableWheres, assigns
       #log "assigns", printAst assigns
       """#{control} (#{conds}) {
            #{preassigns.concat(assigns).map(compileAssign).join '\n'}
-           #{hoistedWheres}
-           return #{compileImpl result};
+           #{hoistedWheres.map(compileDef).join '\n'}
+           return #{compileImpl result, furtherHoistable};
         }"""
       )
     mainCache ?= []
@@ -916,16 +928,14 @@
 hoistWheres = (possible, assigns) ->
   defined = (n for [n, _] in assigns)
   hoisted = []
-  for [pattern, def, names] in possible
-    canHoist = yes
-    for name in names when name not in defined
-      canHoist = no
-    if canHoist
+  notHoisted = []
+  for [pattern, def, missingNames] in possible
+    stillMissingNames = (name for name in missingNames when name not in defined)
+    if stillMissingNames.length == 0
       hoisted.push [pattern, def]
-  if hoisted.length > 0
-    compileWhereImpl hoisted
-  else
-    ''
+    else
+      notHoisted.push [pattern, def, stillMissingNames]
+  [hoisted, notHoisted]

 toJsString = (token) ->
   "'#{token}'"


record system time

record system time

--- services/clsi/app/coffee/LatexRunner.coffee
+++ services/clsi/app/coffee/LatexRunner.coffee
@@ -43,8 +43,10 @@
 			stats["latex-runs-with-errors-#{runs}"] = if failed then 1 else 0
 			# timing information from /usr/bin/time
 			timings = {}
-			timings["cpu-percent"] = output?.stderr?.match(/Percent of CPU this job got: (\d+)/m)?[1] or 0
-			timings["cpu-time"] = output?.stderr?.match(/User time.*: (\d+.\d+)/m)?[1] or 0
+			stderr = output?.stderr
+			timings["cpu-percent"] = stderr?.match(/Percent of CPU this job got: (\d+)/m)?[1] or 0
+			timings["cpu-time"] = stderr?.match(/User time.*: (\d+.\d+)/m)?[1] or 0
+			timings["sys-time"] = stderr?.match(/System time.*: (\d+.\d+)/m)?[1] or 0
 			callback error, output, stats, timings

 	_latexmkBaseCommand: ["/usr/bin/time", "-v", "latexmk", "-cd", "-f", "-jobname=output", "-auxdir=$COMPILE_DIR", "-outdir=$COMPILE_DIR"]


「.gitignore」の生成処理を整理

「.gitignore」の生成処理を整理

--- bin/lamjet.coffee
+++ bin/lamjet.coffee
@@ -45,19 +45,16 @@
     return readTemplate("lambda-config.js")
       .then (result)-> writeArtifact("lambda-config.js", result.body)

+  makeGitIgnore = ->
+    return readTemplate("gitignore")
+      .then (result)-> writeArtifact(".gitignore", result.body)
+
   Promise.resolve()
     .then (result)-> makePackageJson(functionName: defaultFunctionName)
+    .then (result)-> makeLambdaConfigJs()
+    .then (result)-> makeGitIgnore()
     .then (result)->
       console.log(JSON.stringify({then: result}, null, 2))
-    .then (result)-> makeLambdaConfigJs()
-    .then (result)->
-      console.log(JSON.stringify({then: result}, null, 2))
-
-  gitignore     = fs.readFileSync(path.join(templatePath, "gitignore"), encoding: "utf8")
-  gitignorePath = path.join(path.resolve(), ".gitignore")
-  console.log gitignore
-  console.log gitignorePath
-  fs.writeFileSync(gitignorePath, gitignore, encoding: "utf8", flag: "w")

   gulpfileCoffee     = fs.readFileSync(path.join(templatePath, "gulpfile.coffee"), encoding: "utf8")
   gulpfileCoffeePath = path.join(path.resolve(), "gulpfile.coffee")


Remove logging

Remove logging

--- app/controllers/profile.coffee
+++ app/controllers/profile.coffee
@@ -25,7 +25,6 @@
       @pages =
         recents: Math.ceil (User.current.classification_count / @opts.per_page) or 1
         favorites: Math.ceil (User.current.favorites / @opts.per_page) or 1
-      console.log @pages
     User.bind 'sign-in', @refresh
     Quiz.bind 'quiz-user', @render
     Quiz.bind 'quiz-finished', @render


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,5 +1,5 @@
 ###*
-The ClampBounds module bind
+The ClampBounds module bi

 # Health regeneration


feat(funnel): add reviewable link to pull request

notification message

--- scripts/gitter-activity-notifier.coffee
+++ scripts/gitter-activity-notifier.coffee
@@ -83,7 +83,7 @@
             m.shift(1)
             console.log(m)
             #message = vsprintf('@%1$s %2$s a Pull Request to [%3$s](https://github.com/%3$s/): %3$s#%5$s', m)
-            message = primg + vsprintf('%1$s %2$s a Pull Request: %3$s#%5$s', m)
+            message = primg + vsprintf('%1$s %2$s a Pull Request: %3$s#%5$s; [Reviewable %5$s](https://reviewable.io/reviews/%3$s/%5$s)', m)
         else if commit.test text
             m = text.match commit
             m.shift(1)


Added listener ID

Added listener ID

--- src/twitch.coffee
+++ src/twitch.coffee
@@ -33,7 +33,7 @@
     GetTTVData = ->
         robot.brain.data[TWITCH_STORAGE_KEY] or= {}

-    robot.respond /ttv follows/i, (res) ->
+    robot.respond /ttv follows/i, id: "twitch.follows", (res) ->
         user = res.message.user.name.toLowerCase()
         if twitchUser = GetTTVData()[user]
             GetTwitchResult res, "/users/#{twitchUser}/follows/channels", { limit: 10, sortby: "last_broadcast" }, (followsObj) ->
@@ -58,7 +58,7 @@
         else
             res.reply "You have not linked your Twitch account yet."

-    robot.respond /ttv link (.+)/i, (res) ->
+    robot.respond /ttv link (.+)/i, id: "twitch.link", (res) ->
         user = res.message.user.name.toLowerCase()
         twitchUser = res.match[1]

@@ -71,7 +71,7 @@
             robot.brain.save()
             res.reply "Twitch user \"#{twitchUser}\" is now linked to you."

-    robot.respond /ttv featured/i, (res) ->
+    robot.respond /ttv featured/i, id: "twitch.featured", (res) ->
         GetTwitchResult res, '/streams/featured', null, (object) ->
             response = ""
             for feature in object.featured
@@ -79,7 +79,7 @@
                 response += "#{feature.stream.game}: #{channel.display_name} (\"#{channel.status}\") - #{channel.url} [Viewers: #{feature.stream.viewers}]\n"
             res.send response

-    robot.respond /ttv game (.+)/i, (res) ->
+    robot.respond /ttv game (.+)/i, id: "twitch.game", (res) ->
         category = res.match[1]
         GetTwitchResult res, '/streams', { game: category }, (object) ->
             if object._total is 0
@@ -95,7 +95,7 @@
             if object._total > TWITCH_MAX_RESULTS
                 res.reply "There are #{object._total - TWITCH_MAX_RESULTS} other \"#{category}\" live streams."

-    robot.respond /ttv search (.+)/i, (res) ->
+    robot.respond /ttv search (.+)/i, id: "twitch.search", (res) ->
         query = res.match[1]
         GetTwitchResult res, "/search/streams", { q: query }, (object) ->
             if object._total is 0
@@ -111,7 +111,7 @@
             if object._total > TWITCH_MAX_RESULTS
                 res.reply "There are #{object._total - TWITCH_MAX_RESULTS} other live streams matching your search query: \"#{query}\"."

-    robot.respond /ttv stream (.+)/i, (res) ->
+    robot.respond /ttv stream (.+)/i, id: "twitch.stream", (res) ->
         GetTwitchResult res, "/streams/#{res.match[1]}", null, (object) ->
             if object.status is 404
                 res.reply "The stream you have entered (\"#{res.match[1]}\") does not exist."
@@ -127,7 +127,7 @@
             response += "Viewers: #{object.stream.viewers}"
             res.send response

-    robot.respond /ttv top/i, (res) ->
+    robot.respond /ttv top/i, id: "twitch.top", (res) ->
         createGameURL = (game) ->
             "https://www.twitch.tv/directory/game/#{encodeURIComponent(game)}"


Override the destroy() method to immediately trigger the event on nodes

--- src/coffee/cilantro/models/context/nodes/base.coffee
+++ src/coffee/cilantro/models/context/nodes/base.coffee
@@ -45,6 +45,10 @@
             attrs = @identity()
             super
             @set(attrs, silent: true)
+
+        # Removes the node from the collection, no syncing
+        destroy: (options) ->
+            @trigger('destroy', @, @collection, options)

         # Attempts to fetch a node relative to this one. The `ident` is a set
         # of attributes the target node must match in order to be returned.


Updating recent number of projects

Updating recent number of projects

--- app/modules/projects/projects.service.coffee
+++ app/modules/projects/projects.service.coffee
@@ -15,7 +15,7 @@
                 for project in projects
                     project.url = @projectUrl.get(project)

-                @.projects.recents = projects.slice(0, 8)
+                @.projects.recents = projects.slice(0, 10)
                 @.projects.all = projects

                 return @.projects


:bug: Quit the application when all windows are closed

--- src/browser/application.coffee
+++ src/browser/application.coffee
@@ -24,6 +24,9 @@

     @pkgJson = require '../../package.json'
     @windows = []
+
+    app.on 'window-all-closed', ->
+      app.quit() if process.platform in ['win32', 'linux']

     @openWithOptions(options)


:bug: Fix #4300 by preventing 'will-navigate'

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -141,6 +141,8 @@
       switch chosen
         when 0 then @browserWindow.destroy()
         when 1 then @browserWindow.restart()
+
+    @browserWindow.webContents.on 'will-navigate', (event) -> event.preventDefault()

     @setupContextMenu()


more robust scroll preventing during mousewheel zoom

--- src/coffee/tools.coffee
+++ src/coffee/tools.coffee
@@ -147,17 +147,32 @@
         eventSink.trigger("active_tool", toolName)
         @button_activated = true)

+    no_scroll = (el) ->
+      el.setAttribute("old_overflow", el.style.overflow)
+      el.style.overflow = "hidden"
+      if el == document.body
+        return
+      else
+        no_scroll(el.parentNode)
+    restore_scroll = (el) ->
+      el.style.overflow = el.getAttribute("old_overflow")
+      if el == document.body
+        return
+      else
+        restore_scroll(el.parentNode)
+
     eventSink.on("#{toolName}:deactivated", =>
       @tool_active=false;
       @button_activated = false;
       @$tool_button.removeClass('active')
+      restore_scroll(@plotview.$el[0])
       document.body.style.overflow = @old_overflow)

     eventSink.on("#{toolName}:activated", =>
       @tool_active=true;
       @$tool_button.addClass('active')
-      @old_overflow = document.body.style.overflow
-      document.body.style.overflow = "hidden")
+      no_scroll(@plotview.$el[0]))
+
     return eventSink

 class ToolView extends Bokeh.PlotWidget


Replace listener on key_distr_sorted to key_distr

--- admin/static/coffee/namespaces/shards.coffee
+++ admin/static/coffee/namespaces/shards.coffee
@@ -322,7 +322,7 @@

             @name_view = new NamespaceView.ShardName()

-            @namespace.on 'change:key_distr_sorted', @render_num_keys
+            @namespace.on 'change:key_distr', @render_num_keys
             @namespace.on 'change:shards', @render_num_keys

         render: =>
@@ -336,7 +336,7 @@
             @shard.shard_stats.rows_approx =  @namespace.compute_shard_rows_approximation(@shard.shard)
             if @shard.shard_stats.rows_approx?
                 @.$('.name').html @name_view.render(@shard).$el
-            else
+            #else
                 #setTimeout @render_num_keys 1000 #TODO Magic number (should be greater than the polling time of /ajax/distribution)


Fixed #315 rendering acitons columns

Fixed #315 rendering acitons columns

--- wzk/ui/grid/Grid.coffee
+++ wzk/ui/grid/Grid.coffee
@@ -62,7 +62,7 @@
     @param {Element} table
   ###
   decorate: (@table) ->
-    if @dom.cls wzk.ui.grid.Grid.CLS.ACTIONS, @table
+    unless @dom.cls wzk.ui.grid.Grid.CLS.ACTIONS, @table
       @showActions = false

     @removeBody()


Delete atom.coffee

Delete atom.coffee

--- atom.coffee
+++ atom.coffee
@@ -1 +0,0 @@
-# Atom customization goes here.


Save a few bytes

Save a few bytes


refactor: coffeescript syntax

refactor: coffeescript syntax


xq de la vida

xq de la vida

--- app/assets/javascripts/autocomplete.js.coffee
+++ app/assets/javascripts/autocomplete.js.coffee
@@ -32,13 +32,13 @@

     $(".cantidad").on "input", ->
       $this = $(this)
-      calcular_total_producto($this)
-      calcular_valores_factura()
+      window.Helpers.AutocompleteHelper.calcular_total_producto($this)
+      window.Helpers.AutocompleteHelper.calcular_valores_factura()

     $(".eliminar_item").on "click", ->
       $this = $(this)
       $this.closest(".fields").remove()
-      calcular_valores_factura()
+      window.Helpers.AutocompleteHelper.calcular_valores_factura()

   calcular_total_producto: (componente) ->
     cantidad = componente.closest(".fields").find("td:nth-child(2)").find(".cantidad").val()


don't activate iscroll if `body` element has class `test`

--- app/assets/javascripts/bsat/sidebar.js.coffee
+++ app/assets/javascripts/bsat/sidebar.js.coffee
@@ -36,12 +36,13 @@
   # Use IScroll for scrolling the sidebar
   #
   selector = '#sidebar'
-  new IScroll(selector, {
-    scrollbars: true,
-    fadeScrollbars: true,
-    mouseWheel: true,
-    click: true
-  }) if $(selector)[0]
+  if !$('body').hasClass 'test'
+    new IScroll(selector, {
+      scrollbars: true,
+      fadeScrollbars: true,
+      mouseWheel: true,
+      click: true
+    }) if $(selector)[0]

   #
   # Handle Subnavigation


Update pie_chart remove stroke

Update pie_chart remove stroke

--- app/assets/javascripts/chart/pie_chart.coffee
+++ app/assets/javascripts/chart/pie_chart.coffee
@@ -3,13 +3,11 @@
     @data = @formatValue(data)

     @options =
-      egmentShowStroke : true
-      segmentStrokeColor : "#fff"
-      segmentStrokeWidth : 1
-      animationSteps : 100
-      animationEasing : "easeOutBounce"
-      animateRotate : true
-      animateScale : false
+      segmentShowStroke: false
+      animationSteps: 100
+      animationEasing: "easeOutBounce"
+      animateRotate: true
+      animateScale: false
       maintainAspectRatio: true
       tooltipTemplate: "<%if (label){%><%=label%><%}%>"
       showTooltips: true


Fix application.js.coffee

Fix application.js.coffee

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -11,4 +11,4 @@
 $(document).on 'page:update', ->
   $('[data-behaviors~=autosize]').autosize()

-  $("time[data-behaviors~=timeago]").timeago();
+  $("time[data-behaviors~=timeago]").timeago()


Fix default tooltip placement

Fix default tooltip placement

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -140,8 +140,8 @@
         # Place the logo tooltip on the right when collapsed, bottom when expanded
         $el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
       else
-        # Otherwise use the data-placement attribute like normal
-        $el.data('placement')
+        # Otherwise use the data-placement attribute, or 'bottom' if undefined
+        $el.data('placement') or 'bottom'
   })

   # Form submitter


Increase debounce time

Increase debounce time

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -293,14 +293,15 @@
     #TODO: indicators, error handlers?

   triggerAutosave = (e) ->
-    window.autosave_timer ||= setTimeout( autosave, 5000 )
-
-  $(document).debounce "change", ".js-trigger-autosave", triggerAutosave, 50
-  $(document).debounce "keyup", "input[type='text'].js-trigger-autosave", triggerAutosave, 50
-  $(document).debounce "keyup", "input[type='number'].js-trigger-autosave", triggerAutosave, 50
-  $(document).debounce "keyup", "input[type='url'].js-trigger-autosave", triggerAutosave, 50
-  $(document).debounce "keyup", "input[type='tel'].js-trigger-autosave", triggerAutosave, 50
-  $(document).debounce "keyup", "textarea.js-trigger-autosave", triggerAutosave, 50
+    window.autosave_timer ||= setTimeout( autosave, 1000 )
+
+  debounceTime = 5000
+  $(document).debounce "change", ".js-trigger-autosave", triggerAutosave, debounceTime
+  $(document).debounce "keyup", "input[type='text'].js-trigger-autosave", triggerAutosave, debounceTime
+  $(document).debounce "keyup", "input[type='number'].js-trigger-autosave", triggerAutosave, debounceTime
+  $(document).debounce "keyup", "input[type='url'].js-trigger-autosave", triggerAutosave, debounceTime
+  $(document).debounce "keyup", "input[type='tel'].js-trigger-autosave", triggerAutosave, debounceTime
+  $(document).debounce "keyup", "textarea.js-trigger-autosave", triggerAutosave, debounceTime

   updateUploadListVisiblity = (list, button, max) ->
     list_elements = list.find("li")


cleanup sse switch

cleanup sse switch

--- app/assets/javascripts/connections.js.coffee
+++ app/assets/javascripts/connections.js.coffee
@@ -8,7 +8,7 @@
     callerId

-app.takeIncoming = (call, name) ->
+app.takeIncomingCall = (call, name) ->
   app.dialog(
     "You have an incoming call from
#{name}",
     'question', 'Take call', 'I\'m busy'
@@ -37,7 +37,7 @@
     if call.incoming
       env.callDialogActive = true
       name = app.getAgentFrom(call.visibleNameCaller)
-      app.takeIncoming(call, name)
+      app.takeIncomingCall(call, name)

   phone.notifyRemoveCall = (call) ->
     console.log('SIP remove call:', call) if env.debug
@@ -79,14 +79,18 @@
     data = JSON.parse(event.data)
     console.log('SSE message:', data) if env.debug

-    if data.user
-      app.updateUserFrom(data)
-    else if data.call
-      app.updateCallFrom(data)
-    else if data.chat_message
-      app.createMessageFrom(data)
-    else if data.servertime
-      app.resetServerTimer()
-
+    app.parseIncomingData(data)
     if env.railsEnv == 'test' && !data.servertime
       env.messages.push(data)
+
+
+app.parseIncomingData = (data) ->
+
+  if data.user
+    app.updateUserFrom(data)
+  else if data.call
+    app.updateCallFrom(data)
+  else if data.chat_message
+    app.createMessageFrom(data)
+  else if data.servertime
+    app.resetServerTimer()


stacked bar: increased axis ticks and margin. See #1071

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -94,7 +94,7 @@
       # draw a nice axis
       @y_axis = d3.svg.axis()
         .scale(@inverted_y)
-        .ticks(4)
+        .ticks(5)
         .tickSize(-420, 10, 0)
         .orient("right")
         .tickFormat((x) => Metric.autoscale_value x, @model.get('unit'))
@@ -129,10 +129,10 @@
         _.sum(@model.values_future()),
         _.sum(@model.values_present()),
         _.max(@model.values_targets()) || 0
-      )
+      ) * 1.05
       # update the scales as needed
-      @y = @y.domain([0, tallest])
-      @inverted_y = @inverted_y.domain([0, tallest])
+      @y = @y.domain([0, tallest]).nice()
+      @inverted_y = @inverted_y.domain([0, tallest]).nice()

       # animate the y-axis
       @svg.selectAll(".y_axis").transition().call(@y_axis.scale(@inverted_y))


Fixed the way the 2FA key is being handled.

--- server/lib/passport_configurator.coffee
+++ server/lib/passport_configurator.coffee
@@ -43,11 +43,10 @@
     # hotp strategy
     # we need a key (stored string) and a counter
     passport.use new HotpStrategy { codeField: "authcode" }, (user, done) ->
-        done null, user.encryptedOtpKey, user.hotpCounter
+        done null, new Buffer(user.encryptedOtpKey, 'hex'), user.hotpCounter
     , (user, key, counter, delta, done) ->
         if counter > user.hotpCounter
             User.updateAttributes user._id,
-                encryptedOtpKey: key
                 hotpCounter: counter
             , (err) ->
                 done err


Removed linebreaks from astronomy corpus.

--- WordGenerator.coffee
+++ WordGenerator.coffee
@@ -5,7 +5,7 @@
 # the "custom" user-input corpus.
 corpora = [
 	"Astronomy"
-	"Sun\nMercury\nVenus\nEarth\nMars\nJupiter\nSaturn\nUranus\nNeptune\nPluto\nCeres\nPallas\nVesta\nHygiea\nInteramnia\nEuropa\nDavida\nSylvia\nCybele\nEunomia\nJuno\nEuphrosyne\nHektor\nThisbe\nBamberga\nPatientia\nHerculina\nDoris\nUrsula\nCamilla\nEugenia\nIris\nAmphitrite"
+	"Sun Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto Ceres Pallas Vesta Hygiea Interamnia Europa Davida Sylvia Cybele Eunomia Juno Euphrosyne Hektor Thisbe Bamberga Patientia Herculina Doris Ursula Camilla Eugenia Iris Amphitrite"

 	"Shakespeare"
 	"Two households, both alike in dignity,\nIn fair Verona, where we lay our scene,\nFrom ancient grudge break to new mutiny,\nWhere civil blood makes civil hands unclean.\nFrom forth the fatal loins of these two foes\nA pair of star-cross'd lovers take their life;\nWhose misadventured piteous overthrows\nDo with their death bury their parents' strife.\nThe fearful passage of their death-mark'd love,\nAnd the continuance of their parents' rage,\nWhich, but their children's end, nought could remove,\nIs now the two hours' traffic of our stage;\nThe which if you with patient ears attend,\nWhat here shall miss, our toil shall strive to mend."


Add relative difference filter.

Add relative difference filter.

--- app/javascripts/filters.coffee
+++ app/javascripts/filters.coffee
@@ -7,7 +7,7 @@

     filters.filter 'capitalize', ->
       (s) -> _s.capitalize(s)
-
+

     filters.filter 'romanize', ['StringHelper', (StringHelper) ->
       # @param value the input number or string
@@ -29,6 +29,34 @@
       (value) ->
         if value? then value else 'Not Specified'

+
+    filters.filter 'relativeDifference', ->
+      (objects, index, property=null) ->
+        evaluate = (object, property) ->
+          apply = (object, property) ->
+            if property then object[property] else object
+
+          props = property.split('.')
+          _.reduce(props, apply, object)
+
+        current = objects[index]
+        if index
+          previous = objects[index - 1]
+          prevValue = evaluate(previous, property)
+          currValue = evaluate(current, property)
+          (currValue - prevValue) / prevValue
+
+
+    filters.filter 'percent', ->
+      (value) -> value * 100
+
+    filters.filter 'imageContainerTitle', ->
+      # If the image container name is
+      (value) ->
+        if value.startsWith('reg')
+          'Registration ' + value
+        else
+          _s.capitalize(value) + ' scan'

     # Returns the string representation of the given input value as follows:
     # * true => 'True'
@@ -148,4 +176,3 @@
       (tnm) ->
         TNM.stage(tnm)
     ]
-


Fixed bug where profile saves were nullifying admin and isNew properties.

--- client/profile/profile.coffee
+++ client/profile/profile.coffee
@@ -9,6 +9,8 @@
     email: template.find('#email').value
     photoFilename: template.find('#photoFilename').value
     gender: template.find('input:radio[name=gender]:checked').value
+    isNew: Meteor.user().profile.isNew
+    admin: Meteor.user().profile.admin

 getVolunteerFormValues = (template) ->
   return values =
@@ -21,6 +23,24 @@
     suburb: template.find('#suburb').value
     city: template.find('#city').value
     postalCode: template.find('#postalCode').value
+
+showSuccess = (msg) ->
+  Template.userMessages.showMessage
+    type: 'info',
+    title: 'Success!',
+    message: msg
+
+showError = (msg, err) ->
+  Template.userMessages.showMessage
+    type: 'error',
+    title: 'Uh oh!',
+    message: msg + ' Reason: ' + err.reason
+
+showResultOfProfileUpdate = (err) ->
+  if err
+    showError 'Your profile details were not saved.', err
+  else
+    showSuccess 'Your profile details were saved successfully.'

@@ -80,29 +100,11 @@
     userOptions = getUserFormValues template
     # Todo: add exception handling for these 2 calls
     Meteor.call 'updateUser', userOptions, (err) ->
-      if err
-        Template.userMessages.showMessage
-          type: 'error',
-          title: 'Error. ',
-          message: 'Your profile details were not saved. Reason: ' + err.reason
-      else
-        Template.userMessages.showMessage
-          type: 'info',
-          title: 'Success!',
-          message: 'Your profile details were saved successfully.'
+      showResultOfProfileUpdate err
     if Volunteers.findOne Meteor.userId()
       volunteerOptions = getVolunteerFormValues template
       Meteor.call 'updateVolunteer', volunteerOptions, (err) ->
-        if err
-          Template.userMessages.showMessage
-            type: 'error',
-            title: 'Error.',
-            message: 'Your profile details were not saved. Reason: '+err.reason
-        else
-          Template.userMessages.showMessage
-            type: 'info',
-            title: 'Success!',
-            message: 'Your profile details were saved successfully.'
+        showResultOfProfileUpdate err
     Router.go 'profileDetails'

   'click #pickPhoto': (evnt, template) ->


Fixed url for backbone cloud model.

Fixed url for backbone cloud model.

--- app/assets/javascripts/models/cloud.js.coffee
+++ app/assets/javascripts/models/cloud.js.coffee
@@ -1,6 +1,6 @@
 class Cloudsdale.Models.Cloud extends Backbone.Model

-  url: -> "v1/clouds/#{@id}.json"
+  url: -> "/v1/clouds/#{@id}.json"

   initialize: ->
     @type = 'cloud'


Fix TVA calculation bug and remove useless code

--- app/assets/javascripts/backend/purchase_invoices.js.coffee
+++ app/assets/javascripts/backend/purchase_invoices.js.coffee
@@ -12,46 +12,12 @@
         vatRate = $(item).find('.total-column label.vat-rate').text().split("%")[0]

         totalAmountExcludingTaxes += parseFloat(amountExcludingTaxes)
-        totalVatRate += parseFloat(vatRate)
-        totalAmountIncludingTaxes += parseInt(amountExcludingTaxes * (1 + (vatRate / 100)))
+        totalAmountIncludingTaxes += parseInt(amountExcludingTaxes * (1 + (parseFloat(vatRate) / 100)))
+        totalVatRate += parseFloat(parseFloat(totalAmountIncludingTaxes - totalAmountExcludingTaxes).toFixed(2))
         $('.nested-item-form').each (index, item) ->

       $('.invoice-totals .total-except-tax .total-value').text(totalAmountExcludingTaxes)
       $('.invoice-totals .vat-total .total-value').text(totalVatRate)
       $('.invoice-totals .invoice-total .total-value').text(totalAmountIncludingTaxes)

-    $(document).on 'selector:change', '.invoice-variant.selector-search', (event) ->
-      E.PurchaseInvoices.fillStocksCounters(event)
-
-    $(document).on 'change', '.nested-fields .form-field .purchase_invoice_items_quantity .invoice-quantity', (event) ->
-      E.PurchaseInvoices.fillStocksCounters(event)
-
-    $(document).on 'keyup', '.nested-fields .form-field .purchase_invoice_items_quantity .invoice-quantity', (event) ->
-      E.PurchaseInvoices.fillStocksCounters(event)
-
-
-  E.PurchaseInvoices =
-    fillStocksCounters: (event) ->
-      currentForm = $(event.target).closest('.nested-item-form')
-      variantId = $(currentForm).find('.purchase_invoice_items_variant .selector-value').val()
-
-      if variantId == ""
-        return
-
-      $.ajax
-        url: "/backend/product_nature_variants/#{variantId}/detail",
-        success: (data, status, request) ->
-          $(currentForm).find('.merchandise-current-stock .stock-value').text(data.stock)
-          $(currentForm).find('.merchandise-current-stock .stock-unit').text(data.unit.name)
-
-          quantity = 0
-          quantityElement = $(currentForm).find('.purchase_invoice_items_quantity .invoice-quantity')
-
-          if ($(quantityElement).val() != "")
-            quantity = $(quantityElement).val()
-
-          newStock = parseFloat(data.stock) - parseFloat(quantity)
-          $(currentForm).find('.merchandise-stock-after-invoice .stock-value').text(newStock)
-          $(currentForm).find('.merchandise-stock-after-invoice .stock-unit').text(data.unit.name)
-
 ) ekylibre, jQuery


implement multiple condition code for any and all condition checking

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -24,22 +24,43 @@
         # deal with any condition, once we get a hide_questions = false then we dont need to run through the rules
         hideQuestions = self.evaluateCondition(condition.operator, condition.answer, self.getValue($triggerElement))
         ancestorId = rule.question_id
+        # bind conditions based on element type
         # text, textarea, select
         if $triggerElement.length < 2
-          self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
           self.bindConditions(rule.question_id, $triggerElement, condition.operator, condition.answer)
         # radio buttons
         else
           if $triggerElement.is(":checkbox")
             # limit binding of each checkbox if data-label-value and answer are the same-kdh
             $triggerElement = $triggerContainer.find(".form-control[data-label-value=" + condition.answer + "]")
-            self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
             self.bindConditions(rule.question_id, $triggerElement, condition.operator, condition.answer)
           else
             for element in $triggerElement
               do (element) ->
-                self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
                 self.bindConditions(rule.question_id, $(element), condition.operator, condition.answer)
+        # hide show questions
+        #self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
+        if rule.conditions.length > 1
+          conditionMetTracker = []
+          _.each rule.conditions, (condition) ->
+            $conditionElement = $("[data-question-id=" + condition.question_id + "]").find('.form-control')
+            hideQuestions = self.evaluateCondition(condition.operator, condition.answer, self.getValue($conditionElement))
+            conditionMet = !hideQuestions
+            conditionMetTracker.push conditionMet
+          # match type any (or)
+          if matchType == "any"
+            if _.indexOf(conditionMetTracker, true) > 0
+              self.hideShowQuestions(false, ancestorId, $ruleElement)
+            else
+              self.hideShowQuestions(true, ancestorId, $ruleElement)
+          # match type all
+          if matchType == "all"
+            if _.indexOf(conditionMetTracker, false) == -1
+              self.hideShowQuestions(false, ancestorId, $ruleElement)
+            else
+              self.hideShowQuestions(true, ancestorId, $ruleElement)
+        else
+          self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement)
     return

   #bind conditions to question


Fixed broken test

Fixed broken test

--- test/changes.spec.coffee
+++ test/changes.spec.coffee
@@ -29,7 +29,7 @@
   it 'should be able to query for changes by build', ->
     change = new Change locator: 'build:(id:4252)', client
     change.query()
-    expect(client).to.haveCalled 'get', '/changes?locator=build:(id:4252)'
+    expect(client).to.haveCalled 'get', '/changes', change.locator

   it 'should be able to query for changes by id', ->
     change = new Change 14800, client


[nav] more mobile nav improvements

[nav] more mobile nav improvements

--- app/assets/javascripts/states/main/views/nav_bar.js.coffee
+++ app/assets/javascripts/states/main/views/nav_bar.js.coffee
@@ -11,13 +11,13 @@
       ".tab.inventory a": "inventory"

       # monitoring
-      ".tab.monitoring.primary a":         "monitoring_overview"
+      ".tab.monitoring.primary a:first":   "monitoring_overview"
       ".tab.monitoring a.overview":        "monitoring_overview"
       ".tab.monitoring a.check_templates": "mon_check_templates"
       ".tab.monitoring a.schedules":       "mon_oncalls"

       # runbooks
-      ".tab.runbooks.primary a":            "runbooks"
+      ".tab.runbooks.primary a:first":      "runbooks"
       ".tab.runbooks a.run":                "runbooks"
       ".tab.runbooks a.scheduled_commands": "scheduled_commands"
       ".tab.runbooks a.logs":               "runbooks_logs"
@@ -49,9 +49,8 @@

       "click a": (e) ->
         # hide the navbar-toggle on click (only visible on xs or sm screens)
-        e = $(e.target)
-        if !(e.hasClass("dropdown-toggle") or e.parent().hasClass("dropdown-toggle") or e.hasClass("navbar-brand"))
-          @$("button.navbar-toggle:visible").click()
+        if @$(".navbar-collapse.in").length > 0
+          @close_nav()

       "click a#logout": (e) ->
         $.ajax "/logout",
@@ -93,9 +92,8 @@

     # Close nav menu if open
     close_nav: ->
-      if @$(".navbar-collapse.in").length > 0 || @$("li.open").length > 0
-        @$(".navbar-collapse").collapse("hide")
-        @$("li.open").removeClass("open")
+      @$(".navbar-collapse.in").length > 0 && @$(".navbar-collapse").collapse("hide")
+      @$("li.open").length > 0 && @$("li.open").removeClass("open")

     impersonate: (user_id) ->
       return if !@true_user.can("impersonate_users")


adding dev url for form handler for testing

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -530,8 +530,8 @@
           $("textarea[id='cartObject']").val(JSON.stringify(products))
           formData = $(form).serialize()

-          prd_url = "https://script.google.com/macros/s/AKfycbx8LH0V-1gd_JSCbQItjtGlTQCNhNpWwFVd7IkW0E_uzmQj1pWP/exec"
-          url = prd_url
+          dev_url = "https://script.google.com/macros/s/AKfycbyPEqIjIfyWR09vPhd5HcP7jB9KIjng0YzEwo2tjWOk8aEscM4/exec"
+          url = dev_url

           $.ajax
             type: 'POST'


add legacy product update in render cart

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -226,20 +226,18 @@
         db = get_product_database()

         for k,v of LocalStorage.dict()
+          if v.order?
+            # legacy - update
+            v['product'] = updateLegacyProductIds_(v.order)
+            delete v.order
+            LocalStorage.set(k,JSON.stringify(v))

           title_product = db[v.product]['title_product']
-
-          # temporary workaround
-          if not title_product
-            LocalStorage.clear()
-            $(".Cart-container").hide()
-            $("#Cart-empty").show()
-
           cart_spec_html = ""

           if title_product is "Pupil Headset"
             sub_products = db[v.product]['sub_products']
-            for sbu_product_key,sub_product_data of sub_products
+            for sub_product_key,sub_product_data of sub_products
               cart_spec_html += "
#{ sub_product_data['title_cart'] }

#{ sub_product_data['description_cart'] }
"
           else


Remove paintbox

Remove paintbox

--- assets/javascripts/paint_box.js.coffee
+++ assets/javascripts/paint_box.js.coffee
@@ -1,23 +0,0 @@
-class window.MogoPaintBox
-  nextColor: 0
-  colors: [
-    "F57DBA"
-    "829DE7"
-    "77B546"
-    "FFCC20"
-    "A79D95"
-    "809DAA"
-    "9013FE"
-    "637AB2"
-    "BBAD7C"
-    "C831DD"
-    "71CCD3"
-    "417505"
-  ]
-
-  getColor: ->
-    color = @colors[@nextColor]
-    @nextColor = @nextColor + 1
-    if @nextColor >= @colors.length
-      @nextColor = 0
-    "##{color}"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -32,4 +32,5 @@

+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -63,7 +63,7 @@

   player.update()

-  equals player.I.y, 19
+  equals player.I.y, 21

   player.update()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -32,7 +32,7 @@

   player.update()

-  equals player.I.velocity.x, 11
+  equals player.I.velocity.x, 3

   player.update()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -92,6 +92,8 @@

   window.keydown.down = true

-  player.u
+  player.update()
+
+  e

 module()


Properly clean up regions and reassign  on show/close

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -9,22 +9,27 @@
       @el = el

     show: (view) ->
-      @close()
+      @clean()
       @views = null
       @append(view)

     append: (view) ->
-      @$el = @$el or @parent.$el.find(@el)
+      @$el = @parent.$el.find(@el)
       view.parent = @parent
       @views ?= []
       @views.push(view)
       view.setElement($('<div>').appendTo(@$el)).render()

-    close: () ->
+    clean: () ->
       _.each @views, (view) ->
         view.close()

       @$el?.empty()
+      @$el = null
+
+    close: () ->
+      @clean()
+      delete @[key] for key of @

   class Regions
     constructor: (regions = {}, $context) ->
@@ -35,10 +40,9 @@
     initialize: () ->
       @_popovers = []
       @regions = new Regions(@regions, @)
-      @listenTo(@model, 'change', @render) if @model

     _renderDom: (data) ->
-      @$el.html(@template?(data) or @template)
+      @$el?.html(@template?(data) or @template)

     _render: () ->
       data = @model?.toJSON() or {}


Adds artwork ids check

Adds artwork ids check

--- api/apps/articles/model/save.coffee
+++ api/apps/articles/model/save.coffee
@@ -92,8 +92,11 @@
     cb(null, article)

 @generateArtworks = (input, article, accessToken, cb) ->
+  before = _.flatten _.pluck _.where(article.sections, type: 'artworks'), 'ids'
+  after = _.flatten _.pluck _.where(input.sections, type: 'artworks'), 'ids'
+  intersection = _.intersection(before, after)
   article.sections = input.sections
-  return cb(null, article) unless _.some input.sections, type: 'artworks'
+  return cb(null, article) if intersection.length is before.length and intersection.length is after.length
   # Try to fetch and denormalize the artworks from Gravity asynchonously
   artworkIds = _.pluck (_.where input.sections, type: 'artworks' ), 'ids'
   Q.allSettled( for artworkId in _.flatten artworkIds


fix: 第30回大会試合詳細の審査員判定が入力できなかったのを修正

fix: 第30回大会試合詳細の審査員判定が入力できなかったのを修正

変数文字列脱字でした。

--- app/assets/javascripts/games.coffee
+++ app/assets/javascripts/games.coffee
@@ -55,7 +55,7 @@
               '_opponent_progress').val('')
     when 30
       $('form').on 'change', (event) ->
-        if /^game_game_detail30ths_attributes_\d+_jur_votes$/.test(event.target.id)
+        if /^game_game_detail30ths_attributes_\d+_jury_votes$/.test(event.target.id)
           i = event.target.id.match(/_\d+_/)[0].replace(/_/g, '')
           if $('#game_game_detail30ths_attributes_' + i + '_jury_votes')[0].checked
             $('.jury_votes_' + i).toggleClass('hidden')


intersaction observer fix attempt

intersaction observer fix attempt

--- app/assets/javascripts/works.coffee
+++ app/assets/javascripts/works.coffee
@@ -13,13 +13,13 @@
   if supportsIntersectionObserver
     intersectionObsOptions =
       root_margin: "100px"
-      treshold: [0.0, 0.1, 1.0]
+      treshold: [0.0,  0.5, 1.0]
     intersectionObserver = new IntersectionObserver((entries)->
-      # unless entries[0].intersectionRatio <= 0
-      target = entries[0].target
-      noscript_tag = target.querySelector("noscript")
-      if noscript_tag
-        lazy_load_image_in_noscript_wrapper noscript_tag
+      entries.forEach((entry)->
+        noscript_tag = entry.target.querySelector("noscript")
+        if noscript_tag
+          lazy_load_image_in_noscript_wrapper noscript_tag
+      )
     , intersectionObsOptions)
     noscript_wrapped_images.forEach((e)->intersectionObserver.observe(e.parentElement))
   else


Add missing translated ship (DE).

Add missing translated ship (DE).

fixes #382

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -1765,6 +1765,7 @@
             text: '''Upsilon-class Shuttle only.%LINEBREAK%At the end of the Combat phase, choose an unstressed enemy ship at Range 1-2.  Its owner must assign a stress token to it or assign a stress token to another ship at Range 1-2 of you that that player controls.'''
         '''Pivot Wing''':
             name: "Schwenkflügel"
+            ship: 'U-Flügler'
             text: '''Nur U-Flügler. %DUALCARD%%LINEBREAK%Angriffsposition: Erhöhe deinen Wendigkeitswert um 1.%LINEBREAK%Nachdem du ein Manöver ausgeführt hast, darfst du diese Karte umdrehen.%LINEBREAK%Landeposition: Sobald du ein (0 %STOP%)-Manöver aufdeckst, darfst du dein Schiff um 180° drehen.%LINEBREAK%Nachdem du ein Manöver ausgeführt hast, darfst du diese Karte umdrehen.'''
         '''Adaptive Ailerons''':
             name: "Adaptive Querruder"


Remove dupe Bomb Loadout; add ship restriction. #190

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -468,7 +468,6 @@
         "Commander Kenkirk":
             ship: "VT-49 Diezmador"
             text: """Si no te quedan escudos y tienes asignada al menos 1 carta de Daño, tu Agilidad aumenta en 1."""
-            ship: "VT-49 Diezmador"
         "Captain Oicunn":
             name: "Capitán Oicunn"
             text: """Después de ejecutar un maniobra, toda nave enemiga con la que estés en contacto sufre 1 daño."""
@@ -859,6 +858,7 @@
         "Bomb Loadout":
             name: "Compartimento de Bombas"
             text: """Solo ala-Y.

Tu barra de mejoras gana el icono %BOMB%."""
+            ship: "Ala-Y"
         "Bodyguard":
             name: "Guardaespaldas"
             text: """%SCUMONLY%

Al principio de la fase de Combate, puedes gastar 1 ficha de Concentración para elegir 1 nave aliada situada a alcance 1 cuyo piloto tenga una Habilidad más alta que la tuya. Hasta el final de la ronda, la puntuación de Agilidad de esa nave se incrementa en 1."""
@@ -897,9 +897,6 @@
         "Salvaged Astromech":
             name: "Droide Astromecánico Remendado"
             text: """Cuando recibas una carta de Daño con el atributo Nave, puedes descartarla de inmediato (antes de resolver sus efectos).

Luego descarta esta carta de Mejora."""
-        "Bomb Loadout":
-            name: "Compartimento de Bombas"
-            text: """Solo ala-Y.

Tu barra de mejoras gana el icono %BOMB%."""
         '"Genius"':
             name: '"Genio"'
             text: """Si estás equipado con una bomba que puede soltarse antes de revelar tu selector de maniobras, puedes elegir soltar la bomba después de ejecutar tu maniobra."""
@@ -913,7 +910,7 @@
             text: """Cuando ataques, si tienes al defensor fijado como blanco, puedes gastar la ficha de Blanco Fijado para elegir cualquier o todos sus dados de defensa. El defensor debe volver a tirar los dados elegidos."""
         "Autoblaster Turret":
             name: "Torreta de Bláster Automático"
-            text: """Ataque: Ataca a 1 nave (aunque esté fuera de tu arco de fuego).

Tus resultados %HIT% no pueden ser anulados por los dados de defensa.

El defensor puede anular tus resultados %CRIT% antes que los %HIT%."""
+            text: """Ataque: Ataca a 1 nave (aunque esté fuera de tu arco de fuego).

Tus resultados %HIT% no pueden ser anulados por los dados de defensa.

El defensor puede anular tus resultados %CRIT% antes que los %HIT%."""
         "Advanced Targeting Computer":
             text: """TIE Advanced only.%LINEBREAK%When attacking with your primary weapon, if you have a target lock on the defender, you may add 1 %CRIT% result to your roll.  If you do, you cannot spend target locks during this attack."""
         "Ion Cannon Battery":


Move logout and backup buttons to user picture.

--- app/client/js/react/pages/me.coffee
+++ app/client/js/react/pages/me.coffee
@@ -16,28 +16,26 @@
     @component = React.createClass

       render: ->
+        publishedSongs = userStore.songs.filter (song) -> song.isPublished()
         user = userStore.user
-        publishedSongs = userStore.songs
-          .filter (song) -> song.isPublished()
-          .map (song) ->
-            href = routes.song.url song
-            li key: song.id,
-              a href: href, location.host + href

         div className: 'page',
           p {}, @getWelcomeMessage user.displayName
           img className: 'user-picture', src: user.thirdPartyUserData.picture.data.url
-          if publishedSongs.length
-            div {},
-              p {}, Me.MSG_PUBLISHED_SONGS
-              ul {}, publishedSongs
           nav {},
+            login.component {}
             button
               className: 'btn btn-default'
               onPointerUp: @onBackupButtonPointerUp
               type: 'button'
             , Me.MSG_BACKUP
-            login.component {}
+          if publishedSongs.length
+            div {},
+              p {}, Me.MSG_PUBLISHED_SONGS
+              ul {}, publishedSongs.map (song) ->
+                href = routes.song.url song
+                li key: song.id,
+                  a href: href, location.host + href

       getWelcomeMessage: (displayName) ->
         Me.MSG_WELCOME_MESSAGE = goog.getMsg 'Hi, {$displayName}.',
@@ -46,9 +44,10 @@
       onBackupButtonPointerUp: ->
         data = JSON.stringify userStore.songs
         anchor = document.createElement 'a'
-        anchor.setAttribute 'href', 'data:text/plain;charset=utf-8,' + encodeURIComponent data
+        href = 'data:text/plain;charset=utf-8,' + encodeURIComponent data
+        anchor.setAttribute 'href', href
         anchor.setAttribute 'download', 'songs.json'
         anchor.click()

-  @MSG_PUBLISHED_SONGS: goog.getMsg 'Published songs:'
-  @MSG_BACKUP: goog.getMsg 'Backup'
+  @MSG_PUBLISHED_SONGS: goog.getMsg 'Your published songs:'
+  @MSG_BACKUP: goog.getMsg 'Download Your Songs'


Template for remote select2 data

Template for remote select2 data

--- app/assets/javascripts/user_profile.js.coffee
+++ app/assets/javascripts/user_profile.js.coffee
@@ -1,3 +1,14 @@
 $ ->
-  $('#languages').select2(tags: ["svenska", "tyska", "danska", "engelska", "grekiska", "farsi"] )
-  $('#skills').select2( )
+  $('#skills').select2()
+
+  $('#languages').select2
+    ajax:
+      url: "/skills/search",
+      dataType: 'jsonp',
+      data: (term) ->
+        return {
+          q: term
+          page_limit: 10
+        }
+      results: (data) ->
+        return {results: data.movies}


Sim: Enforce name length of 2 rather than 6.

--- app.coffee
+++ app.coffee
@@ -174,8 +174,8 @@
     {username, email, password} = params
     email = email.toLowerCase()
     errors = []
-    if !username || username.length < 6
-      errors.push 'Your username must be at least 6 characters.'
+    if !username || username.length < 2
+      errors.push 'Your username must be at least 2 characters.'
     if username && /[^a-zA-Z0-9\-_]/.test(username)
       errors.push 'Your username cannot include non-alphanumeric characters.'
     if !email || !/\@/.test(email)


give it a go with all files and parsers

give it a go with all files and parsers

--- app.coffee
+++ app.coffee
@@ -33,16 +33,16 @@
       failures = _.uniq failures
       if failures.length then process.exit(1) else process.exit(0)

-  #parsers: [frontMatterParser, markdownParser, fileParser, linkParser]
-  parsers: [linkParser, fileParser]
+  parsers: [frontMatterParser, markdownParser, fileParser, linkParser]
+  #parsers: [linkParser, fileParser]

   # Uncomment for production files
-  #directoryFilters: ['!node_modules', '!Release Notes']
-  #fileFilters: '*.md'
+  directoryFilters: ['!node_modules', '!Release Notes']
+  fileFilters: '*.md'

   # Uncomment for test files
-  directoryFilters: ['Accounts & Users']
-  fileFilters: '*.md'
+  #directoryFilters: ['Accounts & Users']
+  #fileFilters: '*.md'

   readOpts: ->
     root: path.join(__dirname, '..')


Edited app.coffee

Edited app.coffee

--- app.coffee
+++ app.coffee
@@ -1,5 +1,3 @@
-# Generated by CoffeeScript 1.6.3
-#Module dependencies.#
 express = require("express")
 routes = require("./routes")
 user = require("./routes/user")


use correct port for heroku

use correct port for heroku

--- app.coffee
+++ app.coffee
@@ -46,4 +46,4 @@
       console.log err
       resp.send reply.toString(), { 'Content-Type': 'text/css' }, 200

-app.listen process.env.VMC_APP_PORT or 3000, -> console.log 'Listening...'
+app.listen process.env.PORT or 3000, -> console.log 'Listening...'


Added comment

Added comment

--- app.coffee
+++ app.coffee
@@ -75,6 +75,9 @@
   if options.httpsPort and not _.isArray options.httpsPort
     options.httpsPort = [options.httpsPort]

+  #this will ensure that it goes through the project routes FIRST, before personal, then defaults (stepped through by
+  #insertion order). `_.defaults` was used so personal don't overwrite project routes and default routes don't overwrite
+  #project or personal routes.
   options.routes = _.extend {}, project.routes
   _.defaults options.routes, personal.routes
   _.defaults options.routes, defaults.routes


Adjust compilation error

Adjust compilation error

--- src/spec/validator.spec.coffee
+++ src/spec/validator.spec.coffee
@@ -178,7 +178,7 @@
         """
       @validator.parse csv
       .then (parsed) =>
-        #@validator.valProduct parsed.data
+        @validator.valProduct parsed.data
       .fail (e) -> done(e)

   describe '#validateOffline', ->