more ctrl keys

more ctrl keys

--- urb/zod/main/pub/sole/src/main.coffee
+++ urb/zod/main/pub/sole/src/main.coffee
@@ -38,6 +38,7 @@
     $el.css {background}
     if background
       setTimeout (-> flash $el,''), 50
+  bell = -> flash ($ 'body'), 'black'

   matr = rend (Matr
     rows:[]
@@ -59,13 +60,13 @@
     switch Object.keys(ruh)[0]
       when 'txt' then update rows: [mapr.rows..., ruh.txt]
       when 'tan' then ruh.tan.split("\n").reverse().map (txt)-> peer {txt}
-      when 'hop' then update cursor: buffer.transpose ruh.hop #; peer act:'bel'
+      when 'hop' then update cursor: ruh.hop; bell() # XX buffer.transpose?
       when 'pro' then update prompt: ruh.pro.cad
       when 'blk' then console.log "Stub #{str ruh}"
       when 'det' then buffer.receive ruh.det; sync ruh.det.ted
       when 'act' then switch ruh.act
         when 'clr' then update rows:[]
-        when 'bel' then flash ($ 'body'), 'black'
+        when 'bel' then bell()
         when 'nex' then update
           input: ""
           cursor: 0
@@ -103,38 +104,9 @@
     sync ted
     sendAction {det}

-
-  Mousetrap.handleKey = (char, mod, e)->
-    norm = {
-      capslock:  'caps'
-      pageup:    'pgup'
-      pagedown:  'pgdn'
-      backspace: 'baxp'
-      enter:     'entr'
-    }
-
-    key = switch
-      when char.length is 1
-        if e.type is 'keypress'
-          chac = char.charCodeAt(0)
-          if chac < 32          # normalize ctrl keys
-            char = String.fromCharCode chac | 96
-          str: char
-      when e.type is 'keydown'
-        if char isnt 'space'
-          act: norm[char] ? char
-      when e.type is 'keyup' and norm[key] is 'caps'
-        act: 'uncap'
-    if !key then return
-    if key.act and key.act in mod
-      return
-    e.preventDefault()
+  yank = ''
+  eatKyev= (mod, key)->
     mapr = matr.props
-    #[fore, aft] = (
-    #  [sli,cur] = [mapr.input.slice, mapr.cursor]
-    #  [sli(0, cur), sli(cur)]
-    #)
-
     switch mod.sort().join '-'
       when '', 'shift'
         if key.str
@@ -168,20 +140,79 @@
       when 'ctrl' then switch key.str || key.act
         when 'a','left'  then update cursor: 0
         when 'e','right' then update cursor: mapr.input.length
+        when 'l' then update rows: []
         when 'entr' then peer act: 'bel'
+        when 'w' then eatKyev ['alt'], act:'baxp'
+        when 'p' then eatKyev [], act: 'up'
+        when 'n' then eatKyev [], act: 'down'
+        when 'b' then eatKyev [], act: 'left'
+        when 'f' then eatKyev [], act: 'right'
+        when 'g' then bell()
+        when 't'
+          if mapr.cursor is 0 or mapr.input.length < 2
+            return bell()
+          cursor = mapr.cursor
+          if cursor < mapr.input.length
+            cursor++
+          doEdit [{del:cursor-1},ins:{at:cursor-2,cha:mapr.input[cursor-1]}]
+          update {cursor}
+        when 'u'
+          yank = mapr.input.slice(0,mapr.cursor)
+          doEdit (del:mapr.cursor - n for n in [1..mapr.cursor])
+        when 'k'
+          yank = mapr.input.slice(mapr.cursor)
+          doEdit (del:mapr.cursor for _ in [mapr.cursor...mapr.input.length])
+        when 'y'
+          doEdit (ins: {cha, at: mapr.cursor + n} for cha,n in yank)
         else console.log mod, str key
       when 'alt' then switch key.str || key.act
         when 'f','right'
           rest = mapr.input.slice(mapr.cursor)
-          rest = rest.match(/[^a-z0-9]*[a-z0-9]*/)[0]
+          rest = rest.match(/\W*\w*/)[0] # XX unicode
           update cursor: mapr.cursor + rest.length
         when 'b','left'
           prev = mapr.input.slice(0,mapr.cursor)
           prev = prev.split('').reverse().join('')  # XX
-          prev = prev.match(/[^a-z0-9]*[a-z0-9]*/)[0]
+          prev = prev.match(/\W*\w*/)[0] # XX unicode
           update cursor: mapr.cursor - prev.length
+        when 'baxp'
+          prev = mapr.input.slice(0,mapr.cursor)
+          prev = prev.split('').reverse().join('')  # XX
+          prev = prev.match(/\W*\w*/)[0] # XX unicode
+          yank = prev
+          doEdit (del: mapr.cursor-1 - n for _,n in prev)
       else console.log mod, str key

+  Mousetrap.handleKey = (char, mod, e)->
+    norm = {
+      capslock:  'caps'
+      pageup:    'pgup'
+      pagedown:  'pgdn'
+      backspace: 'baxp'
+      enter:     'entr'
+    }
+
+    key = switch
+      when char.length is 1
+        if e.type is 'keypress'
+          chac = char.charCodeAt(0)
+          if chac < 32          # normalize ctrl keys
+            char = String.fromCharCode chac | 96
+          str: char
+      when e.type is 'keydown'
+        if char isnt 'space'
+          act: norm[char] ? char
+      when e.type is 'keyup' and norm[key] is 'caps'
+        act: 'uncap'
+    if !key then return
+    if key.act and key.act in mod
+      return
+    e.preventDefault()
+    #[fore, aft] = (
+    #  [sli,cur] = [mapr.input.slice, mapr.cursor]
+    #  [sli(0, cur), sli(cur)]
+    #)
+    eatKyev mod, key

     #amod = (arr)->
     #  for i in arr
@@ -218,3 +249,4 @@
     #   )
     #   document.title = "Matri"  # XX  debug
     # later {mod, key}
+


date picker change month/year registers as a selection.

--- app/assets/javascripts/people.js.coffee
+++ app/assets/javascripts/people.js.coffee
@@ -10,6 +10,13 @@
     changeMonth: true,
     dateFormat: "mm/dd/yy",
     yearRange: '-80:+1'
+    onChangeMonthYear: (year, month, inst) ->
+      selectedDate = $(this).datepicker("getDate")
+      if(selectedDate == null)
+        return
+      selectedDate.setMonth month - 1 # 0-11
+      selectedDate.setFullYear year
+      $(this).datepicker "setDate", selectedDate

   $('.input-group-btn button').click ->
     $('.date_picker').datepicker("show")


trakttv: Code refactor

trakttv: Code refactor

--- src/js/trakttv.coffee
+++ src/js/trakttv.coffee
@@ -8,131 +8,109 @@

 events = new Emitter()
 log = require('loglevel')
-
-merge = (objects...) ->
-  res = {}
-  for obj in objects
-    for key, value of obj
-       res[key] = value
-  res
-
-uniqBy = (arr, key) ->
-    seen = {}
-    arr.filter (item)->
-      k = key(item)
-      if seen.hasOwnProperty(k)
-        false
-      else
-        seen[k] = true
-        true
-
-trakttv = {}
-
-trakttv.BASE_URL = 'https://api-v2launch.trakt.tv'
-
-trakttv.on = (args...) -> events.on(args...)
-
-trakttv.request = (opt, callback) ->
-  log.debug "trakttv.request: opt: #{JSON.stringify opt}"
-  if typeof opt == 'string'
-    opt = if opt.indexOf('http') == 0
-      url: opt
-    else
-      action: opt
-
-  opt.method ?= 'GET'
-
-  if opt.action[0] == '/'
-    opt.action = opt.action[1..]
-
-  opt.url ?= "#{trakttv.BASE_URL}/#{opt.action}"
-
-  accessToken = Settings.option 'accessToken'
-  unless accessToken?
-    events.emit 'authorizationRequired', message: 'Missing access token'
-
-  ajax
-    url: opt.url
-    type: 'json'
-    headers:
-      'trakt-api-version': 2
-      'trakt-api-key': config.TRAKT_CLIENT_ID
-      Authorization: "Bearer #{accessToken}"
-    method: opt.method
-    data: opt.data
-    (response, status, req) =>
-      callback null, response
-    (response, status, req) ->
-      log.error "Request failure (#{status} #{opt.method} #{opt.url})"
-      if status == 401
-        log.error "Server says that authorization is required"
-        events.emit 'authorizationRequired', message: 'Authorization required from server'
-        return
-
-      if status == null
-        err = new Error("Unable to connect to the server.")
-      else
-        err = new Error("Communication error (#{status}).")
-        err.status = status
-
-      callback err
-
-trakttv.getPopular = (callback) ->
-  @request 'shows/popular', callback
-
-trakttv.getWatched = (callback) ->
-  trakttv.request '/sync/watched/shows', callback
-
-trakttv.getWatchList = (callback) ->
-  trakttv.request '/sync/watchlist/shows', callback
-
-
-trakttv.fetchToWatchList = (callback) ->
-  async.parallel(
-    watched: trakttv.getWatched
-    watchlist: trakttv.getWatchList
-    (err, result) =>
-      return callback(err) if err?
-
-      shows = uniqBy(
-        Array::concat(result.watchlist, result.watched)
-        (elem) -> elem.show.ids.trakt
-      )
-
-      async.each(
-        shows,
-        (item, doneItem) ->
-          showID = item.show.ids.trakt
-          trakttv.fetchShowProgress showID,
-            (err, showProgress) ->
-              return doneItem() if err?
-
-              item = i for i in shows when i.show.ids.trakt == showID
-
-              item.next_episode = showProgress.next_episode
-              item.seasons = showProgress.seasons
-
-              events.emit 'update', 'show', show: item
-
-              doneItem()
-            (status) ->
-              doneItem()
-        (err) ->
-          events.emit 'update', 'shows', shows: shows
-          callback err, shows
-      )
-  )
-
-trakttv.getCalendar = (fromDate, daysWindow, callback) ->
-  trakttv.request "/calendars/shows/#{fromDate}/#{daysWindow}",
-    (err, response) =>
-      return callback(err) if err?
-
-      events.emit 'update', 'calendar', calendar: response
-      callback null, response
-
-trakttv.fetchShowProgress = (showID, callback) ->
-  trakttv.request "/shows/#{showID}/progress/watched", callback
+misc = require('misc')
+
+class Trakttv
+  @BASE_URL: 'https://api-v2launch.trakt.tv'
+  @on: (args...) => events.on(args...)
+
+  @request: (opt, callback) =>
+    log.debug "trakttv.request: opt: #{JSON.stringify opt}"
+    if typeof opt == 'string'
+      opt = if opt.indexOf('http') == 0
+        url: opt
+      else
+        action: opt
+
+    opt.method ?= 'GET'
+
+    if opt.action[0] == '/'
+      opt.action = opt.action[1..]
+
+    opt.url ?= "#{@BASE_URL}/#{opt.action}"
+
+    accessToken = Settings.option 'accessToken'
+    unless accessToken?
+      events.emit 'authorizationRequired', message: 'Missing access token'
+
+    ajax
+      url: opt.url
+      type: 'json'
+      headers:
+        'trakt-api-version': 2
+        'trakt-api-key': config.TRAKT_CLIENT_ID
+        Authorization: "Bearer #{accessToken}"
+      method: opt.method
+      data: opt.data
+      (response, status, req) =>
+        callback null, response
+      (response, status, req) =>
+        log.error "Request failure (#{status} #{opt.method} #{opt.url})"
+        if status == 401
+          log.error "Server says that authorization is required"
+          events.emit 'authorizationRequired', message: 'Authorization required from server'
+          return
+
+        if status == null
+          err = new Error("Unable to connect to the server.")
+        else
+          err = new Error("Communication error (#{status}).")
+          err.status = status
+
+        callback err
+
+  @getPopular: (callback) => @request 'shows/popular', callback
+
+  @getWatched: (callback) => @request '/sync/watched/shows', callback
+
+  @getWatchList: (callback) => @request '/sync/watchlist/shows', callback
+
+  @fetchToWatchList: (callback) =>
+    async.parallel(
+      watched: @getWatched
+      watchlist: @getWatchList
+      (err, result) =>
+        return callback(err) if err
+
+        shows = misc.uniqBy(
+          Array::concat(result.watchlist, result.watched)
+          (elem) => elem.show.ids.trakt
+        )
+
+        async.each(
+          shows,
+          (item, doneItem) =>
+            showID = item.show.ids.trakt
+            @fetchShowProgress showID,
+              (err, showProgress) =>
+                return doneItem() if err
+
+                item = i for i in shows when i.show.ids.trakt == showID
+
+                item.next_episode = showProgress.next_episode
+                item.seasons = showProgress.seasons
+
+                events.emit 'update', 'show', show: item
+
+                doneItem()
+              (status) =>
+                doneItem()
+          (err) =>
+            events.emit 'update', 'shows', shows: shows
+            callback err, shows
+        )
+    )
+
+  @getCalendar: (fromDate, daysWindow, callback) =>
+    @request "/calendars/shows/#{fromDate}/#{daysWindow}",
+      (err, response) =>
+        return callback(err) if err
+
+        events.emit 'update', 'calendar', calendar: response
+        callback null, response
+
+  @fetchShowProgress: (showID, callback) =>
+    @request "/shows/#{showID}/progress/watched", callback

 # {
 #   season: 1,
@@ -153,42 +131,42 @@
 #     showID: 1388
 #   }
 # }
-trakttv.getEpisodeData = (showID, seasonNumber, episodeNumber, callback) ->
-  result = {}
-# {
-#   "season":1,
-#   "number":1,
-#   "title":"Pilot",
-#   "ids":{
-#     "trakt":73482,
-#     "tvdb":349232,
-#     "imdb":"tt0959621",
-#     "tmdb":62085,
-#     "tvrage":637041
-#   }
-# }
-  trakttv.request "/shows/#{showID}/seasons/#{seasonNumber}/episodes/#{episodeNumber}",
-    (err, response) =>
-      return callback(err) if err?
-
-      result.title = response.title
-      result.episodeID = episodeID = response.ids.trakt
-      result.season = response.season
-      result.number = response.number
-
-      trakttv.searchEpisode episodeID, (err, response) ->
-        return callback(err) if err?
-
-        result.overview = response.episode.overview
-        result.images = response.episode.images
-
-        result.show =
-          title: response.show.title
-          showID: response.show.ids.trakt
-
-        events.emit 'update', 'episode', episode: result
-
-        callback null, result
+  @getEpisodeData: (showID, seasonNumber, episodeNumber, callback) =>
+    result = {}
+    # {
+    #   "season":1,
+    #   "number":1,
+    #   "title":"Pilot",
+    #   "ids":{
+    #     "trakt":73482,
+    #     "tvdb":349232,
+    #     "imdb":"tt0959621",
+    #     "tmdb":62085,
+    #     "tvrage":637041
+    #   }
+    # }
+    @request "/shows/#{showID}/seasons/#{seasonNumber}/episodes/#{episodeNumber}",
+      (err, response) =>
+        return callback(err) if err
+
+        result.title = response.title
+        result.episodeID = episodeID = response.ids.trakt
+        result.season = response.season
+        result.number = response.number
+
+        @searchEpisode episodeID, (err, response) =>
+          return callback(err) if err
+
+          result.overview = response.episode.overview
+          result.images = response.episode.images
+
+          result.show =
+            title: response.show.title
+            showID: response.show.ids.trakt
+
+          events.emit 'update', 'episode', episode: result
+
+          callback null, result

 #   {
 #     "type":"episode",
@@ -234,94 +212,94 @@
 #       }
 #     }
 #   }
-trakttv.searchEpisode = (episodeID, callback) ->
-  trakttv.request "/search?id_type=trakt-episode&id=#{episodeID}",
-    (err, response) =>
-      return callback(err) if err?
-
-      callback null, response[0]
-
-trakttv.markEpisode = (episodeObj, seen, watched_at, callback) ->
-  episode =
-    if (typeof episodeObj) == 'number'
-      {
-        ids:
-          trakt: episodeObj
-      }
-    else
-      episodeObj
-
-  # HACK: clone obj
-  episode = JSON.parse(JSON.stringify(episode))
-  episode.watched_at = watched_at
-
-  action =
-    if seen
-      '/sync/history'
-    else
-      '/sync/history/remove'
-
-  body =
-    episodes: [episode]
-
-  @request
-    action: action
-    method: 'POST'
-    data: body
-    callback
-
-trakttv.checkInEpisode = (episodeObj, callback) ->
-  episode =
-    if (typeof episodeObj) == 'number'
-      {
-        ids:
-          trakt: episodeObj
-      }
-    else
-      episodeObj
-
-  # HACK: clone obj
-  episode = JSON.parse(JSON.stringify(episode))
-
-  @request
-    action: '/checkin'
-    method: 'POST'
-    data:
-      episode: episode
-      app_version: appinfo.versionLabel
-    callback
-
-trakttv.modifyEpisodeCheckState = (showID, seasonNumber, episodeNumber, state, callback) ->
-  request =
-    shows: [
-      ids: trakt: showID
-      seasons: [{
-        number: seasonNumber
-        episodes: [{
-          number: episodeNumber
+  @searchEpisode: (episodeID, callback) =>
+    @request "/search?id_type=trakt-episode&id=#{episodeID}",
+      (err, response) =>
+        return callback(err) if err
+
+        callback null, response[0]
+
+  @markEpisode: (episodeObj, seen, watched_at, callback) =>
+    episode =
+      if (typeof episodeObj) == 'number'
+        {
+          ids:
+            trakt: episodeObj
+        }
+      else
+        episodeObj
+
+    # HACK: clone obj
+    episode = JSON.parse(JSON.stringify(episode))
+    episode.watched_at = watched_at
+
+    action =
+      if seen
+        '/sync/history'
+      else
+        '/sync/history/remove'
+
+    body =
+      episodes: [episode]
+
+    @request
+      action: action
+      method: 'POST'
+      data: body
+      callback
+
+  @checkInEpisode: (episodeObj, callback) =>
+    episode =
+      if (typeof episodeObj) == 'number'
+        {
+          ids:
+            trakt: episodeObj
+        }
+      else
+        episodeObj
+
+    # HACK: clone obj
+    episode = JSON.parse(JSON.stringify(episode))
+
+    @request
+      action: '/checkin'
+      method: 'POST'
+      data:
+        episode: episode
+        app_version: appinfo.versionLabel
+      callback
+
+  @modifyEpisodeCheckState: (showID, seasonNumber, episodeNumber, state, callback) =>
+    request =
+      shows: [
+        ids: trakt: showID
+        seasons: [{
+          number: seasonNumber
+          episodes: [{
+            number: episodeNumber
+          }]
         }]
-      }]
-    ]
-
-  action =
-    if state == 'check'
-      '/sync/history'
-    else
-      '/sync/history/remove'
-
-  trakttv.request
-    action: action
-    method: 'POST'
-    data: request
-    (err, response) =>
-      return callback(err) if err?
-
-      callback null
-
-trakttv.checkEpisode = (showID, seasonNumber, episodeNumber, callback) ->
-  trakttv.modifyEpisodeCheckState showID, seasonNumber, episodeNumber, 'check', callback
-
-trakttv.uncheckEpisode = (showID, seasonNumber, episodeNumber, callback) ->
-  trakttv.modifyEpisodeCheckState showID, seasonNumber, episodeNumber, 'uncheck', callback
-
-module.exports = trakttv
+      ]
+
+    action =
+      if state == 'check'
+        '/sync/history'
+      else
+        '/sync/history/remove'
+
+    @request
+      action: action
+      method: 'POST'
+      data: request
+      (err, response) =>
+        return callback(err) if err
+
+        callback null
+
+  @checkEpisode: (showID, seasonNumber, episodeNumber, callback) =>
+    @modifyEpisodeCheckState showID, seasonNumber, episodeNumber, 'check', callback
+
+  @uncheckEpisode: (showID, seasonNumber, episodeNumber, callback) =>
+    @modifyEpisodeCheckState showID, seasonNumber, episodeNumber, 'uncheck', callback
+
+module.exports = Trakttv


Minor non-breaking `protected` hook optimisation

--- files.coffee
+++ files.coffee
@@ -700,18 +700,15 @@

     @_checkAccess = (http) ->
       if self.protected
-        user = false
-        userFuncs = self._getUser http
-        {user, userId} = userFuncs
-        user = user()
+        {user, userId} = self._getUser http

         if _.isFunction self.protected
           if http?.params?._id
             fileRef = self.collection.findOne http.params._id

-          result = if http then self.protected.call(_.extend(http, userFuncs), (fileRef or null)) else self.protected.call userFuncs, (fileRef or null)
+          result = if http then self.protected.call(_.extend(http, {user, userId}), (fileRef or null)) else self.protected.call {user, userId}, (fileRef or null)
         else
-          result = !!user
+          result = !!userId

         if (http and result is true) or not http
           return true
@@ -1137,7 +1134,7 @@
         if mtok
           userId = Meteor.server.sessions?[mtok]?.userId
           if userId
-            result.user   = -> return Meteor.users.findOne userId
+            result.user   = -> Meteor.users.findOne userId
             result.userId = userId
     else
       if Meteor.userId?()


Update metalsmith task

Update metalsmith task

--- grunt/metalsmith.coffee
+++ grunt/metalsmith.coffee
@@ -5,7 +5,12 @@
         title: 'flockn'
         description: 'Lightweight game engine'
       plugins:
-        'metalsmith-markdown': {}
+        'metalsmith-markdown':
+          gfm: true
+          breaks: true
+          smartypants: true
+          tables: true
+        'metalsmith-navigation': {}
         'metalsmith-stylus':
           nib: true
         'metalsmith-templates':


add display interface

add display interface

--- src/coffee/services/registration.coffee
+++ src/coffee/services/registration.coffee
@@ -7,13 +7,18 @@
 ##

 angular.module('CoolFormServices').
-  factory('registrationService', (validationService, eventService, networkService) ->
+  factory('registrationService', (
+    validationService,
+    eventService,
+    networkService,
+    displayService) ->

     return ()->
       form = null
       events = eventService()
       validation = validationService(events)
       values = {}
+      display = displayService(events, values)

       setFields = (pages) ->
         p = 0
@@ -49,6 +54,7 @@
         networkService().sendForm(params, values)

       services =
+        display: display
         registerController: registerController
         registerField: registerField
         validateField: (fieldName) -> validation.validateField(fieldName, values)


Add fixme to loader module.

Add fixme to loader module.

--- app/coffee/modules/common/loader.coffee
+++ app/coffee/modules/common/loader.coffee
@@ -21,6 +21,8 @@
 # File: modules/common/loader.coffee
 ###

+# FIXME: this code not follows any style and any good practices on coffeescript
+# and it should be rewritten in coffeescript style classes.

 taiga = @.taiga
 sizeFormat = @.taiga.sizeFormat


refactor gulp start task

refactor gulp start task

--- gulp/tasks/start.coffee
+++ gulp/tasks/start.coffee
@@ -1,25 +1,23 @@
-# starts up the application with nodemon
-# and loads environment variables from .env
+# start up app with nodemon

 path = require 'path'
 gulp = require 'gulp'
 nodemon = require 'gulp-nodemon'
 sync = require 'browser-sync'

-log = require '../helpers/log'
 config = require '../config'

 gulp.task 'start', ->
-  log.info 'Starting application server'
   nodemon(
     script: config.main
-    ext: 'coffee'
+    ext: 'coffee js'
     env: process.env
+    stdout: false
     nodeArgs: ['--nodejs', "--debug=#{process.env.DEBUG_PORT or 5858}"]
     ignore: config.appIgnoreDirs
+  ).on('readable', ->
+    @stdout.pipe process.stdout
+    @stderr.pipe process.stderr
   ).on('restart', (files) ->
-    log.info 'Application restarted'
     sync.reload(stream:false)
-  ).on('quit', ->
-    log.info 'Application closed'
   )


Rework Player turning logic and refactor.

The problem is start animations never playing, and stop logic never
running. The whole turning logic wasn't right. Turning and restarting x
movement should wait for stopping to finish.

--- src/player.coffee
+++ src/player.coffee
@@ -121,7 +121,7 @@
         y: 1500 # Escape velocity.

       @_jumpTimer = @sprite.game.time.create() # This also acts like a flag.
-      @_justTurned = no # Because velocity won't be 0 when just turning.
+      @_isTurning = no # Because velocity won't be 0 when turning while running.

     _changeAnimation: (nameOrFrame, interrupt = yes) ->
       return if interrupt is no and @animation?.isPlaying
@@ -180,23 +180,24 @@
       @acceleration.x = @maxVelocity.x * factor * direction

     _turn: (direction) ->
-      return if @isRunning(direction)
-
-      if @_justTurned
+      return if !@_isTurning and @isRunning(direction)
+
+      if @_isTurning and @animation?.isFinished
         @nextAction = 'start'
-        @_justTurned = no
-      else if @velocity.x isnt 0 and @nextAction isnt 'stop'
+        @_isTurning = no
+        # Visualize the turn.
+        @sprite.scale.x = direction
+        @physics.offset = new Phaser.Point @_xOffset(direction), @_yOffset
+
+      else unless (@_isTurning or @nextAction is 'stop' or @velocity.x is 0)
         @nextAction = 'stop'
-        @_justTurned = yes
+        @_isTurning = yes
+        @direction = direction
         @debug 'turn', @velocity.x
-
-      @direction = direction
-      @sprite.scale.x = direction
-      @physics.offset = new Phaser.Point @_xOffset(direction), @_yOffset

     _updateAnimations: ->
       unless @isInMidAir() or @nextAction is 'none'
-        @_changeAnimation @nextAction
+        @_changeAnimation @nextAction, @animation?.loop
         return

       switch @nextState


Fix .css.map file deploy path.

Fix .css.map file deploy path.

--- h2o-web/gulpfile.coffee
+++ h2o-web/gulpfile.coffee
@@ -33,6 +33,8 @@
     css: [
       'lib/fontawesome/css/font-awesome.css'
       'lib/bootstrap/dist/css/bootstrap.css'
+    ]
+    cssmap: [
       'lib/bootstrap/dist/css/bootstrap.css.map'
     ]
     fonts: [
@@ -80,6 +82,9 @@
     .pipe concat 'lib.css'
     .pipe gulp.dest config.dir.deploy + 'css/'

+  gulp.src config.lib.cssmap
+    .pipe gulp.dest config.dir.deploy + 'css/'
+
 gulp.task 'watch', ->
   gulp.watch 'src/main/steam/scripts/*.coffee', [ 'build-browser-script' ]
   gulp.watch 'src/main/steam/templates/*.jade', [ 'build-templates' ]


add check result

add check result

--- index.cjsx
+++ index.cjsx
@@ -90,6 +90,7 @@
   sortieCnt = enemyCnt = 0
   sortieDmg = enemyDmg = 0.0
   sortieTot = enemyTot = 0.0
+  console.log sortieHp, enemyHp, combinedHp
   for i in [0..5]
     if sortieHp.now[i] + sortieHp.dmg[i] > 0
       sortieCnt += 1
@@ -115,6 +116,8 @@
         sortieDmg += (enemyHp.now[i] - leastHp)
         enemyDrop += 1
         enemyHp.now[i] = 0
+  console.log sortieHp, enemyHp, combinedHp
+  console.log enemyDmg / sortieTot, sortieDmg / enemyTot
   result = 'D'
   if enemyDrop == enemyCnt
     result = 'S'
@@ -124,6 +127,7 @@
     result = 'B'
   else if sortieDmg != 0 && (sortieDmg * sortieTot >= 1.0 * enemyDmg * enemyTot)
     result = 'C'
+  console.log result
   result

 checkRepair = (shipId) ->


module clean-up: - remove commented out lines of code - remove redundant code - always return a promise so async code can be tested

--- src/radbus.coffee
+++ src/radbus.coffee
@@ -106,7 +106,7 @@
   userData

 ensureAuthToken = (robot, msg, userData, logPrefix) ->
-  if userData.authValue #and moment().isBefore(userData.authExpires)
+  if userData.authValue
     Q.resolve()
   else
     console.log "#{logPrefix} Calling RadBus API to get the RadBus API's Google API client ID & secret..."
@@ -118,7 +118,6 @@
         console.log "#{logPrefix} Done. Refresh token expires in #{authTokenInfo.expires_in} seconds."

         userData.authValue = "#{authTokenInfo.token_type} #{authTokenInfo.access_token}"
-        #userData.authExpires = moment().add(authTokenInfo.expires_in, 'seconds').format()
         robot.brain.save()

         Q.resolve()
@@ -170,7 +169,7 @@
       logPrefix = "#{LOG_PREFIX}(@#{userName}):"

       userData = getRadBusUserData robot, userName
-      if !userData or !userData.refreshToken
+      if !userData.refreshToken
         msg.send "Sorry #{userName}, I can't get at your bus schedule until you give me your RadBus API application token.\n" +
                  "Do this:\n" +
                  "1. Go to https://www.radbus.io/app-token.html to obtain a token.\n" +
@@ -206,7 +205,7 @@

                 formattedDeparture =
                   time: departureTime.format 'LT'
-                  wait: wait + " minute" + (if wait isnt 1 then "s")
+                  wait: wait + " minute" + (if wait is 1 then "" else "s")
                   route: departure.route.id + (if departure.route.terminal then "-#{departure.route.terminal}" else '')
                   departure: departure.stop.description
                   locationLink: ''
@@ -231,3 +230,4 @@

         chain.fail (err) ->
           console.error "#{logPrefix} Something got borked: #{err.stack || util.inspect(err, depth: null)}"
+          Q.reject err


SocialWorker: add accountId unique index

--- workers/social/lib/social/models/combinedappstorage.coffee
+++ workers/social/lib/social/models/combinedappstorage.coffee
@@ -7,6 +7,8 @@
   @share()

   @set
+    indexes         :
+      accountId     : 'unique'
     sharedEvents    :
       static        : []
       instance      : []


removed meshblu config from server

removed meshblu config from server

--- src/server.coffee
+++ src/server.coffee
@@ -5,14 +5,12 @@
 errorHandler       = require 'errorhandler'
 meshbluHealthcheck = require 'express-meshblu-healthcheck'
 meshbluAuth        = require 'express-meshblu-auth'
-MeshbluConfig      = require 'meshblu-config'
 debug              = require('debug')('meshblu-responder-service:server')
 Router             = require './router'
 MeshbluRespondService = require './services/meshblu-respond-service'

 class Server
   constructor: ({@disableLogging, @port}, {@meshbluConfig, @uuid, @Meshblu})->
-    @meshbluConfig ?= new MeshbluConfig().toJSON()

   address: =>
     @server.address()


server: added getReq() method

server: added getReq() method

--- src/server.coffee
+++ src/server.coffee
@@ -35,6 +35,7 @@
     @animationRequestId = null
     @$root = null
     @status = null # server only
+    @req = null # server only

     # coffeelint: disable=missing_fat_arrows
     @Redirect = ({path}) ->
@@ -65,7 +66,15 @@
         if @currentPath
           setTimeout @go

-  setStatus: (@status) => null
+  setStatus: (@status) =>
+    if window?
+      throw new Error 'z.server.setStatus() called client-side'
+    null
+
+  getReq: =>
+    if window?
+      throw new Error 'z.server.getReq() called client-side'
+    @req

   factoryToMiddleware: (factory) =>
     handleRenderError = (err, req, res, next) =>
@@ -81,6 +90,7 @@
     (req, res, next) =>
       # Reset state between requests
       @setStatus 200
+      @req = req
       cookies._reset()
       StateFactory.reset()

@@ -157,7 +167,7 @@

   go: (path) =>
     unless window?
-      throw new Error 'z.server.go() called from node'
+      throw new Error 'z.server.go() called server-side'

     path ?= getCurrentPath(@mode)
     hasRouted = not Boolean @currentPath
@@ -186,7 +196,7 @@

   on: (name, fn) =>
     unless window?
-      throw new Error 'z.server.on() called from node'
+      throw new Error 'z.server.on() called server-side'

     (@events[name] = @events[name] or []).push(fn)

@@ -197,7 +207,7 @@

   off: (name, fn) =>
     unless window?
-      throw new Error 'z.server.off() called from node'
+      throw new Error 'z.server.off() called server-side'

     @events[name] = _.without(@events[name], fn)


fix console.log error

fix console.log error

--- client/home/lib/stacks/index.coffee
+++ client/home/lib/stacks/index.coffee
@@ -93,7 +93,7 @@
           .get(0)

         VirtualMachinesSelectedMachineFlux.actions.setSelectedMachine identifier
-        EnvironmentFlux.actions.loadMachineSharedUsers machine.get '_id'
+        EnvironmentFlux.actions.loadMachineSharedUsers machine.get '_id'  if machine
         onboarding.run 'VMsViewed', yes
       break


set roster item lastmsgAt with default timestamp Date(0)

--- api/models/Roster.coffee
+++ api/models/Roster.coffee
@@ -31,6 +31,7 @@
 			defaultsTo:	0
 		lastmsgAt:
 			type:		'datetime'
+			defaultsTo:	new Date(0)
 		createdBy:
 			model:		'user'
 			required:	true


Remove unneeded assignment variables

Remove unneeded assignment variables

--- src/bitbucket-pr.coffee
+++ src/bitbucket-pr.coffee
@@ -57,7 +57,7 @@
     super branch#{@reason}"

   getMessage: ->
-    msg = switch
+    switch
       # PR created
       when @type is 'pullrequest:created' && 'created' in ANNOUNCE_OPTIONS
         @robot.logger.debug "Pull request created"
@@ -192,13 +192,13 @@
       ]

   pullRequestDeclined: ->
-    content = @branchAction('Declined', @RED)
+    @branchAction('Declined', @RED)

   pullRequestMerged: ->
-    content = @branchAction('Merged', @GREEN)
+    @branchAction('Merged', @GREEN)

   pullRequestUpdated: ->
-    content = @branchAction('Updated', @PURPLE)
+    @branchAction('Updated', @PURPLE)

   pullRequestApproved: ->
     content =


EDSC-615 remove custom metric reporting in favor of events

--- app/assets/javascripts/util/metrics.js.coffee
+++ app/assets/javascripts/util/metrics.js.coffee
@@ -94,6 +94,4 @@

       distanceSum = Math.round(distance.reduce (t, v) -> t + v)

-      ga('set', 'metric1', distanceSum)
       ga('send', 'event', 'Spatial Edit', type, '', distanceSum)
-      ga('set', 'metric1', null)


Update only changed data

Update only changed data

--- demo/client/index.coffee
+++ demo/client/index.coffee
@@ -119,16 +119,16 @@
 Template.main.events
   'click button#deleteNode': (e, template) ->
     e.preventDefault()
-    if template.nodeFrom.get()
+    _n = template.nodeFrom.get()
+    if _n
       e.currentTarget.textContent = 'Removing...'
       e.currentTarget.disabled = true
-      id = template.nodeFrom.get().id
-      Meteor.call 'deleteNode', id, (error) ->
+      Meteor.call 'deleteNode', _n.id, (error) ->
         if error
           throw new Meteor.Error error
         else
-          template.nodesDS.remove id
-          delete template._nodes[id]
+          template.nodesDS.remove _n.id
+          delete template._nodes[_n.id]
           template.resetNodes()
           template.resetNodes('edge')
     false
@@ -137,14 +137,15 @@
     e.preventDefault()
     template.$(e.currentTarget).find(':submit').text('Creating...').prop('disabled', true)
     form =
-      name: e.target.name.value
-      label: e.target.label.value
-      description: e.target.description.value
-    if e.target.name.value.length > 0 and e.target.label.value.length > 0
+      name: "#{e.target.name.value}"
+      label: "#{e.target.label.value}"
+      description: "#{e.target.description.value}"
+    if form.name.length > 0 and form.label.length > 0
       Meteor.call 'createNode', form, (error, node) ->
         if error
           throw new Meteor.Error error
         else
+          console.log node
           template.nodesDS.add node
           template._nodes[node.id] = node
           template.$(e.currentTarget).find(':submit').text('Create Node').prop('disabled', false)
@@ -153,13 +154,14 @@

   'submit form#editNode': (e, template) ->
     e.preventDefault()
-    template.$(e.currentTarget).find(':submit').text('Saving...').prop('disabled', true)
+    _n = template.nodeFrom.get()
     form =
-      name: e.target.name.value
-      label: e.target.label.value
-      description: e.target.description.value
-      id: template.nodeFrom.get().id
-    if e.target.name.value.length > 0 and e.target.label.value.length > 0
+      name: "#{e.target.name.value}"
+      label: "#{e.target.label.value}"
+      description: "#{e.target.description.value}"
+      id: _n.id
+    if form.name.length > 0 and form.label.length > 0 and (_n.name isnt form.name or _n.description isnt form.description or _n.group isnt form.label)
+      template.$(e.currentTarget).find(':submit').text('Saving...').prop('disabled', true)
       Meteor.call 'updateNode', form, (error, node) ->
         if error
           throw new Meteor.Error error
@@ -211,7 +213,9 @@

   lastTimestamp = 0
   fetchData = =>
-    Meteor.call 'graph', lastTimestamp - 250, (error, data) =>
+    Meteor.call 'graph', lastTimestamp, (error, result) =>
+      data = result.data
+      lastTimestamp = result.updatedAt
       if error
         throw new Meteor.Error error
       else
@@ -222,7 +226,19 @@
               delete @_nodes[node.id]
           else
             if @_nodes?[node.id]
-              @nodesDS.update node
+              unless EJSON.equals node, @_nodes[node.id]
+                @nodesDS.update node
+                @nodeFrom.set node if @nodeFrom.get() and @nodeFrom.get().id is node.id
+                @nodeTo.set node if @nodeTo.get() and @nodeTo.get().id is node.id
+                ###
+                If user currently inspecting / editing relationship,
+                which somehow includes one of updated nodes - we update inspector state
+                ###
+                r = @relationship.get()
+                if r and (r.from is node.id or r.to is node.id)
+                  _r = _.clone @relationship.get()
+                  _r.updatedAt = lastTimestamp
+                  @relationship.set _r
             else
               @nodesDS.add [node]
             @_nodes[node.id] = node
@@ -232,9 +248,20 @@
             if @_edges?[edge.id]
               @edgesDS.remove edge.id
               delete @_edges[node.id]
+              ###
+              If user currently inspecting / editing relationship, which just was removed
+              We close inspector
+              ###
+              @relationship.set null if @relationship.get() and @relationship.get().id is edge.id
           else
             if @_edges?[edge.id]
-              @edgesDS.update edge
+              unless EJSON.equals edge, @_edges[edge.id]
+                @edgesDS.update edge
+                ###
+                If user currently inspecting / editing relationship, which just was updated
+                We update inspector
+                ###
+                @relationship.set edge if @relationship.get().id is edge.id
             else
               @edgesDS.add [edge]
             @_edges[edge.id] = edge
@@ -243,7 +270,6 @@
       Set up long-polling
       ###
       Meteor.setTimeout fetchData, 1500
-    lastTimestamp = +new Date

   fetchData()


Clean up duplicate bone hand in util

Clean up duplicate bone hand in util

--- utils/bone-hand/leap.bone-hand.coffee
+++ utils/bone-hand/leap.bone-hand.coffee
@@ -1,262 +0,0 @@
-scope = null
-
-initScene = (targetEl)->
-  scope.scene = new THREE.Scene()
-  scope.renderer = renderer = new THREE.WebGLRenderer({
-    alpha: true
-  })
-
-  width = window.innerWidth
-  height = window.innerHeight
-
-  renderer.setClearColor(0x000000, 0)
-  renderer.setSize(width, height)
-
-  renderer.domElement.className = "leap-boneHand"
-
-  targetEl.appendChild(renderer.domElement)
-
-  directionalLight = directionalLight = new THREE.DirectionalLight( 0xffffff, 1 )
-  directionalLight.position.set( 0, 0.5, 1 )
-  scope.scene.add(directionalLight)
-
-  directionalLight = directionalLight = new THREE.DirectionalLight( 0xffffff, 1 )
-  directionalLight.position.set( 0.5, -0.5, -1 )
-  scope.scene.add(directionalLight)
-
-  directionalLight = directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 )
-  directionalLight.position.set( -0.5, 0, -0.2 )
-  scope.scene.add(directionalLight)
-
-  scope.camera = camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000)
-
-  camera.position.fromArray([0, 300, 500]);
-  camera.lookAt(new THREE.Vector3(0, 160, 0));
-
-
-  scope.scene.add(camera)
-
-  renderer.render(scope.scene, camera)
-
-
-  window.addEventListener 'resize', ->
-    width = window.innerWidth
-    height = window.innerHeight
-
-    camera.aspect = width / height
-    camera.updateProjectionMatrix()
-
-    renderer.setSize( width, height )
-
-    renderer.render(scope.scene, camera)
-  , false
-
-  render = ->
-    renderer.render(scope.scene, camera);
-    window.requestAnimationFrame(render);
-
-  render()
-
-
-
-# bone hand methods copied from leapdev
-
-baseBoneRotation = (new THREE.Quaternion).setFromEuler(
-  new THREE.Euler(Math.PI / 2, 0, 0)
-);
-
-
-jointColor = (new THREE.Color).setHex(0x5daa00)
-boneColor = (new THREE.Color).setHex(0xffffff)
-
-boneScale  = 1 / 6
-jointScale = 1 / 5
-
-boneRadius = null
-jointRadius = null
-
-material = null
-
-boneHand = (hand) ->
-  return if !scope.scene
-
-
-  hand.fingers.forEach (finger) ->
-
-    # the handFound listener doesn't actually fire if in live mode with hand-in-screen
-    # we manually check for finger meshes and initialize if necessary
-    boneMeshes = finger.data("boneMeshes")
-    jointMeshes = finger.data("jointMeshes")
-
-    unless boneMeshes
-      boneMeshes = []
-      jointMeshes = []
-
-      material = if !isNaN(scope.opacity)
-        new THREE.MeshPhongMaterial(transparent: true, opacity: scope.opacity)
-      else
-        new THREE.MeshPhongMaterial()
-
-
-      boneRadius  = hand.middleFinger.proximal.length * boneScale
-      jointRadius = hand.middleFinger.proximal.length * jointScale
-
-      unless finger.bones
-        console.warn("error, no bones on", hand.id)
-        return
-
-      finger.bones.forEach (bone) ->
-
-        # create joints
-
-
-        # CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
-        boneMesh = new THREE.Mesh(
-          new THREE.CylinderGeometry(boneRadius, boneRadius, bone.length, 32),
-          material.clone()
-        )
-        boneMesh.material.color.copy(boneColor)
-        scope.scene.add boneMesh
-        boneMeshes.push boneMesh
-
-        jointMesh = new THREE.Mesh(
-          new THREE.SphereGeometry(jointRadius, 32, 32),
-          material.clone()
-        )
-        jointMesh.material.color.copy(jointColor)
-        scope.scene.add jointMesh
-        jointMeshes.push jointMesh
-
-      jointMesh = new THREE.Mesh(
-        new THREE.SphereGeometry(jointRadius, 32, 32),
-        material.clone()
-      )
-      jointMesh.material.color.copy(jointColor)
-      scope.scene.add jointMesh
-      jointMeshes.push jointMesh
-
-      finger.data "boneMeshes", boneMeshes
-      finger.data "jointMeshes", jointMeshes
-
-    boneMeshes.forEach (mesh, i) ->
-      bone = finger.bones[i]
-      mesh.position.fromArray bone.center()
-      mesh.setRotationFromMatrix (new THREE.Matrix4).fromArray(bone.matrix())
-      mesh.quaternion.multiply baseBoneRotation
-
-    jointMeshes.forEach (mesh, i) ->
-      bone = finger.bones[i]
-      if bone
-        mesh.position.fromArray bone.prevJoint
-      else
-        bone = finger.bones[i-1]
-        mesh.position.fromArray bone.nextJoint
-
-
-  if scope.arm
-    armMesh = hand.data('armMesh')
-
-    unless armMesh
-      armMesh = new THREE.Object3D;
-      scope.scene.add(armMesh);
-      hand.data('armMesh', armMesh);
-
-      boneXOffset = (hand.arm.width / 2) - (boneRadius / 2)
-      halfArmLength = hand.arm.length / 2
-
-      armBones = []
-      for i in [0..3]
-        armBones.push(new THREE.Mesh(
-          # CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
-          new THREE.CylinderGeometry(boneRadius, boneRadius,
-            ( if  i < 2 then hand.arm.length else hand.arm.width )
-          , 32),
-          material.clone()
-        ))
-        armBones[i].material.color.copy(boneColor)
-        armMesh.add(armBones[i])
-
-      # CW from Top center
-      armBones[0].position.setX(boneXOffset) # radius
-      armBones[1].position.setX(-boneXOffset) # ulna
-      armBones[2].position.setY(halfArmLength)
-      armBones[3].position.setY(-halfArmLength)
-      armTopAndBottomRotation = (new THREE.Quaternion).setFromEuler(
-        new THREE.Euler(0, 0, Math.PI / 2)
-      );
-
-      armBones[2].quaternion.multiply(armTopAndBottomRotation)
-      armBones[3].quaternion.multiply(armTopAndBottomRotation)
-
-      armBones = []
-      for i in [0..3]
-        armBones.push(new THREE.Mesh(
-          new THREE.SphereGeometry(jointRadius, 32, 32),
-          material.clone()
-        ))
-        armBones[i].material.color.copy(jointColor)
-        armMesh.add(armBones[i])
-
-      # CW from TL
-      armBones[0].position.set( - boneXOffset,   halfArmLength, 0)
-      armBones[1].position.set(   boneXOffset,   halfArmLength, 0)
-      armBones[2].position.set(   boneXOffset, - halfArmLength, 0)
-      armBones[3].position.set( - boneXOffset, - halfArmLength, 0)
-
-
-    armMesh.position.fromArray(hand.arm.center());
-
-    armMesh.setRotationFromMatrix(
-      (new THREE.Matrix4).fromArray( hand.arm.matrix() ) # does this get transformed?
-    );
-    armMesh.quaternion.multiply(baseBoneRotation);
-
-
-
-
-boneHandLost = (hand) ->
-  hand.fingers.forEach (finger) ->
-    boneMeshes = finger.data("boneMeshes")
-    jointMeshes = finger.data("jointMeshes")
-
-
-    return unless boneMeshes
-
-    boneMeshes.forEach (mesh) ->
-      scope.scene.remove mesh
-
-    jointMeshes.forEach (mesh) ->
-      scope.scene.remove mesh
-
-    finger.data(boneMeshes: null)
-    finger.data(jointMeshes: null)
-
-  if scope.arm
-    armMesh = hand.data('armMesh');
-    scope.scene.remove(armMesh);
-    hand.data('armMesh', null);
-
-
-Leap.plugin 'boneHand', (options = {}) ->
-  # make sure scope is globally available
-  scope = options
-
-  scope.boneScale  && boneScale  = scope.boneScale
-  scope.jointScale && jointScale = scope.jointScale
-
-  scope.boneColor  && boneColor  = scope.boneColor
-  scope.jointColor && jointColor = scope.jointColor
-
-  @use('handEntry')
-  @use('handHold')
-
-  # this allows a null scene to be passed in for delayed-initialization.
-  if scope.scene == undefined
-    console.assert(scope.targetEl)
-    initScene(scope.targetEl)
-
-  @on 'handLost', boneHandLost
-
-  {
-    hand: boneHand
-  }


Fix fall-back to dev resource path in main

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -133,7 +133,7 @@
   if args['resource-path']
     devMode = true
     resourcePath = args['resource-path']
-    resourcePath ?= devResourcePath if devMode
+  resourcePath ?= devResourcePath if devMode

   unless fs.statSyncNoException(resourcePath)
     resourcePath = path.dirname(path.dirname(__dirname))


Transition to correct talk on delete discussion

--- app/talk/discussion.cjsx
+++ app/talk/discussion.cjsx
@@ -135,7 +135,8 @@
       @discussionsRequest().delete()
         .then (deleted) =>
           @setComments(@props.query.page)
-          @transitionTo('talk')
+          {owner, name} = @props.params
+          if (owner and name) then @transitionTo('project-talk', {owner, name}) else @transitionTo('talk')

   commentValidations: (commentBody) ->
     # TODO: return true if any additional validations fail


fix announcement/discussion form in IE8. fixes #11493

$.serializeForm's use of _.toArray breaks in IE8 b/c IE8
doesn't accept a NodeList as an argument for
Array.prototype.slice.

this works as a NodeList, so the fixes leaves it there.

test plan:
  * in IE8, attempt to create a new discussion or announcement
    and verify that it posts and saves correctly.

Change-Id: Ie2b24f496efc133dff51da0b448c1a777f931f18
Reviewed-on: https://gerrit.instructure.com/14949
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Mark Ericksen <776ab05fc6b68e82d2b2c63873bf56edde131809@instructure.com>

--- app/coffeescripts/jquery/serializeForm.coffee
+++ app/coffeescripts/jquery/serializeForm.coffee
@@ -36,7 +36,6 @@

   $.fn.serializeForm = ->
     _.chain(this[0].elements)
-      .toArray()
       .filter(isInput)
       .map(getValue)
       .value()


Overwrite raking log chart line

Overwrite raking log chart line

--- public/assets/coffee/adkeyword.coffee
+++ public/assets/coffee/adkeyword.coffee
@@ -27,11 +27,11 @@
         labels.unshift log.create_date
         rank = parseInt(log.rank)
         if rank is 0 then ranks.unshift 'Unknown' else ranks.unshift rank
-        tobe.push ranking.tobe
+        tobe.unshift log.tobe

       data = {
         labels: labels
-        series: [ranks, tobe]
+        series: [ranks, tobe, ranks]
       }

       options = {


fix toLowerCase fails with numeric user id, ie: in telegram adapter

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

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

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

         GetTwitchResult res, "/users/#{twitchUser}", null, (object) ->
@@ -149,4 +149,4 @@
                 res.reply "An error occurred while attempting to process your request."
                 return res.robot.logger.error "hubot-twitch: #{err}"

-            handler JSON.parse(body)+            handler JSON.parse(body)


check the words in batches, to avoid blocking the event loop

--- app/coffee/ASpell.coffee
+++ app/coffee/ASpell.coffee
@@ -44,9 +44,10 @@
 			if new Date() - start > ASpell.ASPELL_TIMEOUT
 				@close(true)
 			else if i < words.length
-				word = words[i]
-				@sendWord(word)
-				i++
+				# batch up the words to check for efficiency
+				batch = words.slice(i, i + ASpell.ASPELL_BATCH_SIZE)
+				@sendWords(batch)
+				i += ASpell.ASPELL_BATCH_SIZE
 				setTimeout tick, 0
 			else
 				@close()
@@ -80,6 +81,13 @@
 	sendWord: (word) ->
 		@sendCommand("^" + word)

+	sendWords: (words) ->
+		# Aspell accepts multiple words to check on the same line
+		# ^word1 word2 word3 ...
+		# See aspell.info, writing programs to use Aspell Through A Pipe
+		@sendCommand("^" + words.join(" "))
+
+
 	sendCommand: (command) ->
 		@aspell.stdin.write(command + "\n")

@@ -96,5 +104,4 @@
 			callback("process killed")
 		setTimeout forceClose, @ASPELL_TIMEOUT
 	ASPELL_TIMEOUT : 4000
-
-
+	ASPELL_BATCH_SIZE : 100


Fix check so I don’t respond to myself

Fix check so I don’t respond to myself

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -11,12 +11,6 @@
 mmWSSPort = process.env.MATTERMOST_WSS_PORT or '443'

 class Matteruser extends Adapter
-
-    send: (envelope, strings...) ->
-        @robot.logger.info "Should send: " + strings
-
-    reply: (envelope, strings...) ->
-        @robot.logger.info "Should send reply: " + strings

     run: ->
         @client = new MatterMostClient mmHost, mmGroup, mmUser, mmPassword, {wssPort: mmWSSPort}
@@ -53,8 +47,18 @@
         @robot.logger.info 'Brain loaded'
         return true

+    send: (envelope, strings...) ->
+        @client.postMessage(str, envelope.room) for str in strings
+
+    reply: (envelope, strings...) ->
+        @robot.logger.info "Reply"
+        strings = strings.map (s) -> "@#{envelope.user.name} #{s}"
+        @send envelope, strings...
+
+
     message: (msg) =>
-        return if msg.user_id == @self.user_id
+        return if msg.user_id == @self.id
+        @robot.logger.debug 'From: ' + msg.user_id + ', To: ' + @self.id

         mmChannel = @client.getChannelByID msg.channel_id if msg.channel_id
         mmUser = @client.getUserByID(msg.user_id)


Fix type of clothing log

Fix type of clothing log

--- app/assets/javascripts/clothing.coffee
+++ app/assets/javascripts/clothing.coffee
@@ -1,6 +1,6 @@
 logClothingInBackground = (element) ->
   element.style.backgroundColor = '#CCCCCC'
-  $.ajax(url: element.href, method: 'POST').done ->
+  $.ajax(url: element.href, type: 'POST').done ->
     element.style.backgroundColor = '#CCFFCC'
   return false


Add a blank option when selecting the location option

--- app/assets/javascripts/items.js.coffee
+++ app/assets/javascripts/items.js.coffee
@@ -23,6 +23,6 @@
     espaced_department = department.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1')
     options = $(locations).filter("optgroup[label='#{espaced_department}']").html()
     if options
-      $('#item_location_id').html(options)
+      $('#item_location_id').html(options).prepend("<option value='' selected='selected'></option>")
     else
       $('#item_location_id').empty()


external link are now without turbolinks

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -51,12 +51,12 @@

 # select all links to external sites and add them attribute rel="external"
-$ ->
+$(document).ready ->
   $("a[href*='http://']:not([href*='" + location.hostname + "']),[href*='https://']:not([href*='" + location.hostname + "'])")
-    .attr("rel","external")
+    .attr("rel","external").attr("data-no-turbolink", "true")

 # open external links in new tab
-$ ->
+$(document).ready ->
   $('a[rel="external"]').click ->
     window.open($(this).attr('href'))
     return false


bump page size for collection searching (#4879)

allow more than 20 similarly named collection items to be searched for

--- app/collections/collection-search.cjsx
+++ app/collections/collection-search.cjsx
@@ -25,7 +25,7 @@

   searchCollections: (value) ->
     query =
-      page_size: 20
+      page_size: 100
       favorite: false
       current_user_roles: 'owner,collaborator,contributor'
     query.search = "#{value}" unless value is ''


hover effects

hover effects

--- app/coffee/game.coffee
+++ app/coffee/game.coffee
@@ -152,6 +152,7 @@
     objState = stateFromSgfObject sgfObject, moves: @state.viewingMove
     {boardState, lastPlayed, prisoners} = objState
     size = parseInt(sgfObject.gameTrees[0].nodes[0].SZ, 10) or 19
+    nextColor = null

     if (tesuji_charm.onTurn and \
         (@state.viewingMove == null or \
@@ -176,6 +177,7 @@
     div {},
         [(React.createElement BoardDom,
                               boardState: boardState
+                              hoverColor: nextColor
                               placeStoneCallback: onPlaceStone
                               proposedMove: @state.proposedMove
                               lastPlayed: lastPlayed
@@ -201,6 +203,9 @@
     blackStone = div {className: "stone black"}
     whiteStone = div {className: "stone white"}
     lastPlayedMarker = div {className: "last-played"}
+    hover = if @props.hoverColor \
+            then div {className: "placement #{@props.hoverColor}"} \
+            else div {}

     boardDivsForPos = (i, j) ->
       result = []
@@ -222,6 +227,7 @@
       result = switch color
         when 'black' then [blackStone]
         when 'white' then [whiteStone]
+        when 'empty' then [hover]
         else []
       if i == _lastPlayed.x and j == _lastPlayed.y
         result.push lastPlayedMarker


allow browser sync notify to be quickly toggler

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -47,6 +47,7 @@
     proxy: scrt.bsProxy
     injectchanges: true
     open: false
+    # notify: false
     # tunnel: true
   }


single quoted routes

single quoted routes

--- packages/tower-generator/server/generators/tower/controller/controllerGenerator.coffee
+++ packages/tower-generator/server/generators/tower/controller/controllerGenerator.coffee
@@ -6,7 +6,7 @@
     @template "controller.coffee", "app/controllers/#{@controller.directory}/#{@controller.name}.coffee"
     @template "client/controller.coffee", "app/client/controllers/#{@controller.directory}/#{@controller.name}.coffee".replace(/\/+/g, "/")
     #@template "client/objectController.coffee", "app/client/controllers/#{@model.name}Controller.coffee".replace(/\/+/g, "/")
-    @route '@resources "' + @model.paramNamePlural + '"'
+    @route "@resources '" + @model.paramNamePlural + "'"
     @navigation @model.namePlural, "urlFor(#{@app.namespace}.#{@model.className})"
     @locale /links: */, """\n    #{@model.namePlural}: "#{@model.humanNamePlural}"
 """


Fix redirect from /hoc to /play/hoc-2018 (#5030)

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -141,7 +141,7 @@

     'github/*path': 'routeToServer'

-    'hoc': -> @navigate "/play?hour_of_code=true", {trigger: true, replace: true}
+    'hoc': -> @navigate "/play/hoc-2018", {trigger: true, replace: true}
     'home': go('HomeView')

     'i18n': go('i18n/I18NHomeView')


fix for windows tests

fix for windows tests

--- spec/console-spec.coffee
+++ spec/console-spec.coffee
@@ -14,6 +14,7 @@
       model = new Console initialInput: false
       view = atom.views.getView(model)
       view.style.height = '500px'
+      view.style.width = '800px'
     waitsForPromise ->
       atom.workspace.open().then (ed) ->
         editor = ed


remove useless logging in jasmine tests

remove useless logging in jasmine tests

--- www/test/src/model/user.coffee
+++ www/test/src/model/user.coffee
@@ -25,7 +25,6 @@
             beforeEach -> createModel()

             it "all realms", ->
-                console.log model.histogramPoints()
                 expect(
                     _.isEqual(
                         model.histogramPoints()
@@ -34,7 +33,6 @@
                 ).toEqual true

             it "single realm", ->
-                console.log model.histogramPoints()
                 expect(
                     _.isEqual(
                         model.histogramPoints("asia")


:green_heart: Change tests to account for case-sensitivity of the filesystems

--- spec/fs-plus-spec.coffee
+++ spec/fs-plus-spec.coffee
@@ -511,7 +511,8 @@

       waitsForPromise -> fs.move(directoryPath, newDirectoryPath)

-      expect(fs.existsSync(directoryPath)).toBe true
+      # If the filesystem is case-insensitive, the old directory should still exist.
+      expect(fs.existsSync(directoryPath)).toBe fs.isCaseInsensitive()
       expect(fs.existsSync(newDirectoryPath)).toBe true

     it 'renames to a target with an existent parent directory', ->
@@ -574,7 +575,8 @@

       fs.moveSync(directoryPath, newDirectoryPath)

-      expect(fs.existsSync(directoryPath)).toBe true
+      # If the filesystem is case-insensitive, the old directory should still exist.
+      expect(fs.existsSync(directoryPath)).toBe fs.isCaseInsensitive()
       expect(fs.existsSync(newDirectoryPath)).toBe true

     it 'renames to a target with an existent parent directory', ->


Add missing Sync suffix to describe

Add missing Sync suffix to describe

--- spec/fs-plus-spec.coffee
+++ spec/fs-plus-spec.coffee
@@ -16,7 +16,7 @@
       expect(fs.isFileSync(path.join(fixturesDir, 'non-existent'))).toBe false
       expect(fs.isFileSync(null)).toBe false

-  describe ".isSymbolicLink(path)", ->
+  describe ".isSymbolicLinkSync(path)", ->
     it "returns true with a symbolic link path", ->
       expect(fs.isSymbolicLinkSync(path.join(fixturesDir,  'link-to-sample.js'))).toBe true


removing console.log

removing console.log

--- shared/base/view.coffee
+++ shared/base/view.coffee
@@ -77,9 +77,6 @@
         if !_.isObject(value)
           attributes["data-#{key}"] = value

-    if @name is 'reviews_view'
-      console.log 'getAttributes>>>>>', @collection, @options.collection
-
     attributes

   # Turn template into HTML, minus the wrapper element.


warning sytax

warning sytax

--- app/js/controllers/transactions.coffee
+++ app/js/controllers/transactions.coffee
@@ -20,7 +20,7 @@
         $scope.account_transactions = Wallet.transactions[$scope.name]
         $scope.warning = ""
         if !$scope.account_transactions || $scope.account_transactions.length == 0
-            $scope.warning = if $scope.pending_only then "There is no pending trasactions!" else "There is no trasanctions!"
+            $scope.warning = if $scope.pending_only then "There are no pending trasactions!" else "There are no trasanctions!"
         else if $scope.pending_only
             have_pending = false
             for a in $scope.account_transactions
@@ -28,7 +28,7 @@
                     have_pending = true
                     break
             if !have_pending
-                $scope.warning = "There is no pending trasactions!"
+                $scope.warning = "There are no pending trasactions!"

     $scope.$watch watch_for, on_update, true


one backtick to igrone coffee syntax

one backtick to igrone coffee syntax

--- src/backside/filesystem_chizelin.coffee
+++ src/backside/filesystem_chizelin.coffee
@@ -1,3 +1,5 @@
+
+`
 """
 	Test
 """
@@ -12,3 +14,4 @@
 );

 cmd.run('dir /s | FINDSTR OneDrive');
+`


DOB will not save unless the correct format and age, addresses #453

--- app/assets/javascripts/cleantheme/surveyAnimation.js.coffee
+++ app/assets/javascripts/cleantheme/surveyAnimation.js.coffee
@@ -251,9 +251,14 @@
   # Attach change event handler to everything but radio button inputs. Radio button inputs are changed by JS, so each time
   # the :checked property is changed, handleChangedValue has to be called.
   $(".survey-container input").not(":radio").change (event) ->
-    console.log "non radio clicked"
-    target = event.target or event.srcElement
-    handleChangedValue($(target))
+    if $(this).hasClass "survey-custom-date"
+      dateStr = $(this).val()
+      if validateDate(dateStr) == "" and validateOver18(dateStr) == ""
+        target = event.target or event.srcElement
+        handleChangedValue($(target))
+    else
+      target = event.target or event.srcElement
+      handleChangedValue($(target))

   $(".survey-container select").change (event) ->
     target = event.target or event.srcElement


remove the callback on every headers event

--- updatesVia.coffee
+++ updatesVia.coffee
@@ -56,8 +56,9 @@
             @parent.onUpdate message[1], @_decode(message[2])

     onError: (e) =>
-        console.log [this, 'onError', arguments]
-
+#        console.log [this, 'onError', arguments]
+        throw 'onError' + e
+
     subscribe: (uri) =>
         if @connected
             @_subscribe uri
@@ -68,7 +69,9 @@
     constructor: (@fetcher) ->
         @graph = {}
         @via = {}
-        @fetcher.addCallback 'headers', @onHeaders
+
+        # Don't add the callback -- we can find the headers by looking at stored
+        #        @fetcher.addCallback 'headers', @onHeaders

     register: (via, uri) =>
         unless @via[via]?


(fix) Only push results if the exercise is active.

--- app/scripts/pages/editor/editor.coffee
+++ app/scripts/pages/editor/editor.coffee
@@ -71,7 +71,8 @@
       @group = group
     .then -> api.get.exercise params.id
     .then (exercise) =>
-      api.post.solution exercise.id
+      if(Date.parse(exercise.dueDate) >= @serverTime)
+        api.post.solution exercise.id
       return exercise
     .then (exercise) =>
       @theExercise = exercise


Disable broken part

Disable broken part

--- app/scripts/scenes/GameplayDemo.coffee
+++ app/scripts/scenes/GameplayDemo.coffee
@@ -27,29 +27,29 @@
         'p1,!p2:John:Entering the tunnel!'
         'p2:Jim:Prepare for darkness!!!'
       ]
-      @level.data.needToKill = 10
+      #@level.data.needToKill = 10

-      counter = Crafty.e('2D, DOM, Text, HUD')
-        .text("Enemies Left: #{@level.data.needToKill}")
-        .textColor('#FF0000')
-        .css('textAlign', 'center')
-        .textFont({
-          size: '20px',
-          weight: 'bold',
-          family: 'Courier new'
-        })
-        .attr(w: 640)
-        .positionHud(x: 0, y: 110, z: 2)
-      evt = Crafty.bind 'EnemyDestroyed', =>
-        @level.data.needToKill -= 1
-        counter.text("Enemies Left: #{@level.data.needToKill}")
-        if @level.data.needToKill <= 0
-          counter.destroy()
-          Crafty.unbind evt
-          @level.data.needToKill = 0
+      #counter = Crafty.e('2D, DOM, Text, HUD')
+        #.text("Enemies Left: #{@level.data.needToKill}")
+        #.textColor('#FF0000')
+        #.css('textAlign', 'center')
+        #.textFont({
+          #size: '20px',
+          #weight: 'bold',
+          #family: 'Courier new'
+        #})
+        #.attr(w: 640)
+        #.positionHud(x: 0, y: 110, z: 2)
+      #evt = Crafty.bind 'EnemyDestroyed', =>
+        #@level.data.needToKill -= 1
+        #counter.text("Enemies Left: #{@level.data.needToKill}")
+        #if @level.data.needToKill <= 0
+          #counter.destroy()
+          #Crafty.unbind evt
+          #@level.data.needToKill = 0

   level.addBlock('GameplayDemo.TunnelStart', only: ['cleared'])
-  level.generateBlocks until: -> @data.needToKill is 0
+  #level.generateBlocks until: -> @data.needToKill is 0
   level.generateBlocks(stopBefore: 'GameplayDemo.Tunnel')

   level.addBlock 'GameplayDemo.Lasers',


Move school admin tooltip so it can't clip (#5852)

--- app/views/courses/EnrollmentsView.coco.coffee
+++ app/views/courses/EnrollmentsView.coco.coffee
@@ -88,7 +88,7 @@

   afterRender: ->
     super()
-    @$('[data-toggle="tooltip"]').tooltip(placement: 'left', html: true, animation: false, container: '#site-content-area')
+    @$('[data-toggle="tooltip"]').tooltip(placement: 'top', html: true, animation: false, container: '#site-content-area')

   getStarterLicenseCourseList: ->
     return if !@courses.loaded


Add additional formatting tags to Tome popup

Helps fix this #2945

--- app/views/play/level/tome/DocFormatter.coffee
+++ app/views/play/level/tome/DocFormatter.coffee
@@ -154,7 +154,10 @@
     content = popoverTemplate doc: @doc, docName: docName, language: @options.language, value: @formatValue(), marked: marked, argumentExamples: argumentExamples, writable: @options.writable, selectedMethod: @options.selectedMethod, cooldowns: @inferCooldowns(), item: @options.item
     owner = if @doc.owner is 'this' then @options.thang else window[@doc.owner]
     content = @replaceSpriteName content
-    content.replace /\#\{(.*?)\}/g, (s, properties) => @formatValue downTheChain(owner, properties.split('.'))
+    content = content.replace /\#\{(.*?)\}/g, (s, properties) => @formatValue downTheChain(owner, properties.split('.'))
+    content = content.replace /{([a-z]+)}([^]*?){\/\1}/g, (s, language, text) =>
+      if language is @options.language then return text
+      return ''

   replaceSpriteName: (s) ->
     # Prefer type, and excluded the quotes we'd get with @formatValue


Request 4-Bit data only for color layers

--- app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
+++ app/assets/javascripts/oxalis/model/binary/pullqueue.coffee
@@ -55,7 +55,7 @@
       zoomStep = bucket[3]
       transmitBuffer.push(
         zoomStep
-        if @fourBit and zoomStep == 0 then 1 else 0
+        if @shouldRequestFourBit(zoomStep) then 1 else 0
         bucket[0] << (zoomStep + @cube.BUCKET_SIZE_P)
         bucket[1] << (zoomStep + @cube.BUCKET_SIZE_P)
         bucket[2] << (zoomStep + @cube.BUCKET_SIZE_P)
@@ -126,6 +126,11 @@
   setFourBit : (@fourBit) ->

+  shouldRequestFourBit : (zoomStep) ->
+
+    return @fourBit and zoomStep == 0 and @layer.category == "color"
+
+
   getLoadSocket : ->

     if @socket? then @socket else @socket = new ArrayBufferSocket(


Finished translations on tips

Finished translations on tips

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -317,14 +317,14 @@
     tip_brute_force: "Cuando tengas duda, usa la fuerza bruta. - Ken Thompson"
     tip_extrapolation: "Solo hay dos tipos de personas: Esas que pueden extrapolar desde información incompleta..."
     tip_superpower: "Programar es lo más cercano que tenemos a superpoderes."
-#    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
-#    tip_no_code: "No code is faster than no code."
-#    tip_code_never_lies: "Code never lies, comments sometimes do. — Ron Jeffries"
-#    tip_reusable_software: "Before software can be reusable it first has to be usable."
-#    tip_optimization_operator: "Every language has an optimization operator. In most languages that operator is ‘//’"
-#    tip_lines_of_code: "Measuring programming progress by lines of code is like measuring aircraft building progress by weight. — Bill Gates"
-#    tip_source_code: "I want to change the world but they would not give me the source code."
-#    tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
+    tip_control_destiny: "En el código abierto real, tú tienes el derecho de controlar tu propio destino. - Linus Torvalds"
+    tip_no_code: "Ningún código es más rápido que nada de código."
+    tip_code_never_lies: "El código nunca miente, los comentarios a veces sí. — Ron Jeffries"
+    tip_reusable_software: "Antes de que el software sea reusable, primero tiene que ser usable."
+    tip_optimization_operator: "Cada lenguaje tiene un operador de optimización. En la mayoría de ellos, ese operador es ‘//’"
+    tip_lines_of_code: "Medir el progreso en la programación en líneas de código es como medir el progreso de construcción de una aeronave por su peso. — Bill Gates"
+    tip_source_code: "Quisiera cambiar el mundo, pero no me dan el código fuente."
+    tip_javascript_java: "Java es a Javascript lo mismo que Comer es a Comercial. - Chris Heilmann"

   game_menu:
     inventory_tab: "Inventario"