Fix asset drag position parsing

Fix asset drag position parsing

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -220,7 +220,7 @@
         @segment.u = pos + @segment.length
         @segment.l = pos
         if event.type != 'mousemove'
-          @data.position = pos
+          @data.position = Math.floor(pos)
           $scope.form.edit.$setDirty()
         return


decrease delay when packing

decrease delay when packing

--- app/coffee/PackManager.coffee
+++ app/coffee/PackManager.coffee
@@ -409,7 +409,7 @@
 					}, {upsert:true}, () ->
 						callback null, null

-	DB_WRITE_DELAY: 2000
+	DB_WRITE_DELAY: 100

 	savePacks: (packs, callback) ->
 		async.eachSeries packs, PackManager.safeInsert, (err, result) ->


fixed coffeelint errors on workers/social/lib/social/models/notificationmailtoken.coffee

--- workers/social/lib/social/models/notificationmailtoken.coffee
+++ workers/social/lib/social/models/notificationmailtoken.coffee
@@ -1,4 +1,4 @@
-{Model, ObjectId, signature} = require 'bongo'
+{ Model, ObjectId, signature } = require 'bongo'
 KodingError = require '../error'

 module.exports = class JNotificationMailToken extends Model
@@ -29,20 +29,20 @@
     groupRequest   : 'group request'
     groupApproved  : 'group approval'

-  @unsubscribeWithId = (unsubscribeId, email, opt, callback)->
-    JNotificationMailToken.one {unsubscribeId}, (err, token)->
+  @unsubscribeWithId = (unsubscribeId, email, opt, callback) ->
+    JNotificationMailToken.one { unsubscribeId }, (err, token) ->
       return callback err  if err
       return callback new KodingError 'Invalid unsubscription id'  unless token

       JUser = require './user'
-      JUser.one {_id: token.recipient}, (err, user)->
+      JUser.one { _id: token.recipient }, (err, user) ->
         return callback err  if err
         return callback new KodingError 'User not found'  unless user

         if user.email isnt email
-            return callback new KodingError 'Unsubscription token does not match given email.'
+          return callback new KodingError 'Unsubscription token does not match given email.'

-        user.fetchOwnAccount (err, account)->
+        user.fetchOwnAccount (err, account) ->
           return callback err  if err
           return callback new KodingError 'Account not found'  unless account

@@ -52,11 +52,13 @@
           switch opt
             when 'all'
               prefs.global = false
-              definition = ""
+              definition = ''
             when 'daily' then prefs.daily = false
             else prefs[token.notificationType] = false

-          account.setEmailPreferences user, prefs, (err)->
+          account.setEmailPreferences user, prefs, (err) ->
             return callback err  if err

-            callback null, "You will no longer get #{definition} notification e-mails"+            callback null, "You will no longer get #{definition} notification e-mails"
+
+


Get also api builds when clicking on 'show more'

--- app/controllers/builds.coffee
+++ app/controllers/builds.coffee
@@ -52,6 +52,8 @@
     options = { repository_id: id, after_number: number }
     if type?
       options.event_type = type.replace(/s$/, '') # poor man's singularize
+      if options.event_type == 'push'
+        options.event_type = ['push', 'api']

     @store.query('build', options)


Add expand to docblockr keymap

Add expand to docblockr keymap

--- keymaps/docblockr.cson
+++ keymaps/docblockr.cson
@@ -9,3 +9,6 @@
 # https://atom.io/docs/latest/advanced/keymaps
 '.workspace':
   'ctrl-alt-o': 'docblockr:toggle'
+
+'.editor':
+    'tab': 'docblockr:expand'


Clean up visual

Clean up visual

--- app/core/mixins/Visual.coffee
+++ app/core/mixins/Visual.coffee
@@ -1,10 +1,9 @@
 module.exports =

-
-  _initVisual: ->
+  # Call this in the construcotr before you call super
+  _visual_setUp: (pathToAtlas) ->
+    # set up sane defaults
     @visual = true
-
-    @loadMethods.push @_loadVisual

     @spriteState =
       moving: false
@@ -12,9 +11,8 @@
       animationRate: 100
       normal: 1

-    # Must be set in constructor
     @tileSet =
-      image: 'path to atlas'
+      image: pathToAtlas
       tilesX: 0
       tilesY: 0
       tileheight: 0
@@ -24,14 +22,14 @@
         y: 0

-  _loadVisual: ->
-    tileSet = @tileSet
+  # Call this in the constructor after you call super
+  _visual_init: () ->
+    @loadMethods.push @_visual_load

+  # Will be called when Entity.load is called
+  _visual_load: ->
     img = new Image()
-    ###
-      TODO: change tileSet.image in Yeti.json
-    ###
-    img.src = tileSet.image
+    img.src = @tileSet.image

     @atlas = img


Ensure that invalid paragraph buttons cannot be constructed

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -77,6 +77,9 @@
   content: '
+
'

   constructor: (paragraphElement) ->
+    @$paragraph = $(paragraphElement)
+    return unless @_valid()
+
     @frame = new FactlinkJailRoot.ControlIframe @content
     $el = $(@frame.frameBody.firstChild)

@@ -92,9 +95,8 @@
       mouseleave: => @frame.removeClass 'hovered'; @_attentionSpan.loseAttention()
     $el.on 'click', @_onClick

-    @$paragraph = $(paragraphElement)
     FactlinkJailRoot.on 'updateIconButtons', @_update
-    @_update()
+    @_updatePosition()

     if FactlinkJailRoot.isTouchDevice()
       @frame.fadeIn()


JAccount::modify: validate to-be-updated fields

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -900,7 +900,28 @@
       @fetchActivities selector, options, callback

   modify: secure (client, fields, callback) ->
-    if @equals(client.connection.delegate) and 'globalFlags' not in Object.keys(fields)
+
+    allowedFields = [
+      "preferredKDProxyDomain"
+      "profile.about"
+      "profile.description"
+      "profile.ircNickname"
+      "profile.firstName"
+      "profile.lastName"
+      "profile.avatar"
+      "profile.experience"
+      "profile.experiencePoints"
+      "skillTags"
+      "locationTags"
+    ]
+
+    objKeys = Object.keys(fields)
+
+    for objKey in objKeys
+      if objKey not in allowedFields
+        return callback new KodingError "Modify fields is not valid"
+
+    if @equals(client.connection.delegate)
       @update $set: fields, callback

   oldFetchMounts = @::fetchMounts


Consistent treatment of tutorial and training sims rearding mask value - still seem to be getting mask read err (val=0) though in local app. Not present in beta2 site.

--- app/lib/tutorial_steps.coffee
+++ app/lib/tutorial_steps.coffee
@@ -105,7 +105,7 @@
     next:
       'mouseup .annotation': (e, tutorial, step) ->
         mask = getMaskValue(e)
-        return if mask is 254 then 'good_job' else false
+        return if mask is 255 then 'good_job' else false

   training: new Step
     number: 7


Remove logs in account class

Remove logs in account class

--- app/src/models/account.coffee
+++ app/src/models/account.coffee
@@ -11,7 +11,8 @@
       else
         -1

-  @index 'index'
+  @index 'index', sync: yes
+  @sync 'number'

   @fromWalletAccount: (hdAccount) ->
     return null unless hdAccount?


remformated and fixed some of the logic

remformated and fixed some of the logic

--- translations.coffee
+++ translations.coffee
@@ -2,29 +2,31 @@
 _ = require("underscore")

-module.exports = setup: (options) ->
+module.exports =

-	subdomainLang = options.subdomainLang
+	setup: (options) ->

-	i18n.init
-		resGetPath: __dirname + "/locales/__lng__.json"
-		saveMissing: true
-		resSetPath: __dirname + "/locales/missing-__lng__.json"
-		sendMissingTo: "fallback"
-		fallbackLng: options?.defaultLng || "en-US"
-		detectLngFromHeaders: false
-		useCookie: false
-		preload: _.values(subdomainLang)
+		subdomainLang = options?.subdomainLang || {}

-	setLangBasedOnDomainMiddlewear = (req, res, next) ->
+		i18n.init
+			resGetPath: __dirname + "/locales/__lng__.json"
+			saveMissing: true
+			resSetPath: __dirname + "/locales/missing-__lng__.json"
+			sendMissingTo: "fallback"
+			fallbackLng: options?.defaultLng || "en-US"
+			detectLngFromHeaders: false
+			useCookie: false
+			preload: _.values(subdomainLang)

-		host = req.headers.host
-		subdomain = host.slice(0, host.indexOf("."))
-		if req.originalUrl.indexOf("setLng") is -1 and ((if typeof subdomainLang isnt "undefined" and subdomainLang isnt null then subdomainLang[subdomain] else undefined))
-			lang = subdomainLang[subdomain]
-			req.i18n.setLng lang
-		next()
+		setLangBasedOnDomainMiddlewear = (req, res, next) ->

-	expressMiddlewear: i18n.handle
-	setLangBasedOnDomainMiddlewear: setLangBasedOnDomainMiddlewear
-	i18n: i18n+			host = req.headers.host
+			subdomain = host.slice(0, host.indexOf("."))
+			lang = options?.subdomainLang?[subdomain]
+			if req.originalUrl.indexOf("setLng") == -1 and lang?
+				req.i18n.setLng lang
+			next()
+
+		expressMiddlewear: i18n.handle
+		setLangBasedOnDomainMiddlewear: setLangBasedOnDomainMiddlewear
+		i18n: i18n


Fix placeholder bug

Fix placeholder bug

--- app/assets/javascripts/orders.coffee
+++ app/assets/javascripts/orders.coffee
@@ -19,6 +19,9 @@
 populateQuantityAvailable = (selected, element) ->
   totalAvailableQuantity = selected.data("available-quantity")
   element.text(totalAvailableQuantity)
+
+updatePlaceholder = (element, text) ->
+  element.attr("placeholder", text)

 addOrderRow = (orderDetails) ->
   $("#order-table tbody").append tmpl("orders-new-order-template", {})
@@ -95,6 +98,8 @@
 $(document).on "change", ".order-row .category", ->
   item_element = $(@).parents(".order-row").find ".item"
   populateItems $(@).val(), item_element
+  quantity_element = $(@).parents(".order-row").find ".quantity"
+  updatePlaceholder quantity_element, "Select an Item..."

 $(document).on "change", ".order-row .item", ->
   quantity_element = $(@).parents(".order-row").find ".quantity"
@@ -102,3 +107,4 @@
   selected = $(@).find('option:selected')
   populateQuantity selected, quantity_element
   populateQuantityAvailable selected, quantity_available_element
+  updatePlaceholder quantity_element, "Enter Quantity"


work in progress to stub in useful functions

--- src/smc-hub/kucalc/compute-client.coffee
+++ src/smc-hub/kucalc/compute-client.coffee
@@ -14,6 +14,9 @@
 - Provide the project secret token to the hub

 ###
+
+LOCAL_HUB_PORT = 6000
+RAW_PORT       = 6001

 {EventEmitter} = require('events')

@@ -41,6 +44,7 @@

 class Project extends Dbg
     constructor: (@compute_server, @project_id, @logger) ->
+        @host = "project-{@project_id}"

     free: () =>

@@ -51,21 +55,32 @@
             cb     : required     # cb(err, {state:?, time:?, error:?})
         dbg = @dbg("state")
         dbg()
-        opts.cb?("state -- not implemented")
+        opts.cb(undefined, {state:'running', time:new Date()})

     status: (opts) =>
         opts = defaults opts,
             cb     : required
         dbg = @dbg("status")
         dbg()
-        opts.cb?("status -- not implemented")
+        status =
+            "sage_server.pid"     : false
+            "secret_token"        : 'secret'  # TODO
+            "local_hub.port"      : LOCAL_HUB_PORT
+            "raw.port"            : RAW_PORT
+            "sage_server.port"    : false
+            "local_hub.pid"       : 5     # TODO
+            "console_server.pid"  : false
+            "console_server.port" : false
+        status.quotas = {}
+        status.host = status.ssh = 'host'
+        opts.cb(undefined, status)

     open: (opts) =>
         opts = defaults opts,
             cb   : required
         dbg = @dbg("open")
         dbg()
-        opts.cb?("open -- not implemented")
+        opts.cb()

     start: (opts) =>
         opts = defaults opts,
@@ -73,7 +88,7 @@
             cb         : required
         dbg = @dbg("start")
         dbg()
-        opts.cb?("start -- not implemented")
+        opts.cb()

     restart: (opts) =>
         opts = defaults opts,
@@ -81,14 +96,14 @@
             cb         : required
         dbg = @dbg("restart")
         dbg()
-        opts.cb?("restart -- not implemented")
+        opts.cb()

     ensure_running: (opts) =>
         opts = defaults opts,
             cb : undefined
         dbg = @dbg("ensure_running")
         dbg()
-        opts.cb?("ensure_running -- not implemented")
+        opts.cb()

     ensure_closed: (opts) =>
         opts = defaults opts,
@@ -109,7 +124,7 @@
             cb     : required
         dbg = @dbg("stop")
         dbg()
-        opts.cb?("stop -- not implemented")
+        opts.cb()

     save: (opts) =>
         opts = defaults opts,
@@ -117,14 +132,18 @@
             cb            : undefined
         dbg = @dbg("save(min_interval:#{opts.min_interval})")
         dbg()
-        opts.cb?("save -- not implemented")
+        opts.cb()

     address: (opts) =>
         opts = defaults opts,
             cb : required
         dbg = @dbg("address")
         dbg()
-        opts.cb?("address -- not implemented")
+        address =
+            host         : @host
+            port         : LOCAL_HUB_PORT
+            secret_token : 'secret'   # TODO
+        opts.cb(undefined, address)

     copy_path: (opts) =>
         opts = defaults opts,
@@ -185,22 +204,22 @@
         opts.member_host =  opts.member_host > 0
         dbg = @dbg("set_member_host(member_host=#{opts.member_host})")
         dbg()
-        opts.cb?('set_member_host -- not implemented')
+        opts.cb() # TODO

     set_quotas: (opts) =>
         opts = misc.copy_with(opts, ['disk_quota', 'cores', 'memory', 'cpu_shares', 'network',
                                      'mintime', 'member_host', 'cb'])
         dbg = @dbg("set_quotas")
         dbg()
-        opts.cb?('set_quotas -- not implemented')
+        opts.cb() # TODO

     set_all_quotas: (opts) =>
         opts = defaults opts,
             cb : required
         dbg = @dbg("set_all_quotas")
         dbg()
-        opts.cb?('set_all_quotas -- not implemented')
-
-
-
-
+        opts.cb() # TODO
+
+
+
+


address something seen in hub log...

address something seen in hub log...

--- src/smc-hub/postgres-user-queries.coffee
+++ src/smc-hub/postgres-user-queries.coffee
@@ -1016,7 +1016,12 @@
                                     subs[value] = user_query.project_id
                                     cb()
                                 else
-                                    cb("you do not have read access to this project")
+                                    f = ->
+                                        cb("you do not have read access to this project")
+                                    # A basic anti-DOS measure, e.g,. against clients that have
+                                    # signed out and are still sitting their querying, or whatever.
+                                    # Obviously, this is inspired by something in the logs.
+                                    setTimeout(f, 1000)
                 when 'project_id-public'
                     if not user_query.project_id?
                         cb("must specify project_id")


Make ScrollView Extended

Make ScrollView Extended

--- src/scroll-view.coffee
+++ src/scroll-view.coffee
@@ -1,6 +1,6 @@
 {View} = require './space-pen-extensions'

-# Public: Represents a view that scrolls.
+# Extended: Represents a view that scrolls.
 #
 # Handles several core events to update scroll position:
 #


participant: Remove hardcoded prefetch

participant: Remove hardcoded prefetch

--- src/participant.coffee
+++ src/participant.coffee
@@ -59,7 +59,6 @@

   start: (callback) ->
     @messaging.connect (err) =>
-      @messaging.channel?.prefetch 1 # FIXME: move out to consumer

       debug 'connected', err
       return callback err if err


participant: Fix discovery callback only firing once

--- src/participant.coffee
+++ src/participant.coffee
@@ -76,7 +76,7 @@
         @register (err) =>
           return callback err if err
           period = @options.discoveryPeriod*1000/2.2 # try to send 2 messages before deadline
-          setTimeout () =>
+          setInterval () =>
             @register (err) ->
               console.log 'Could not send discovery message', err if err
           , period


Make Glyph.defaults chain up so it gets name and tags

--- bokehjs/src/coffee/renderer/glyph/glyph.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph.coffee
@@ -309,7 +309,7 @@
   }

   defaults: ->
-    return _.extend {
+    return _.extend {}, super(), {
       visible: true
     }


Limit arrays' items to 100 by default (configurable)

--- bokehjs/src/coffee/util/object_explorer.coffee
+++ bokehjs/src/coffee/util/object_explorer.coffee
@@ -11,6 +11,7 @@
       super(options)
       @onEvent = _.debounce(@onEvent, options.debounce or 200)
       @showToolbar = options.showToolbar or false
+      @arrayLimit = options.arrayLimit or 100
       @render()

     base: () ->
@@ -58,8 +59,10 @@
         value = null
         color = null
       else if _.isArray(obj)
-        children = (@descend(index, value, visited) for value, index in obj)
-        type = "Array[#{obj.length}]"
+        truncate = obj.length > @arrayLimit
+        arrayLimit = @arrayLimit or obj.length
+        children = (@descend(index, value, visited) for value, index in obj[..@arrayLimit])
+        type = "Array[#{obj.length}]" + (if truncate then " (showing first #{@arrayLimit} items)" else "")
         value = null
         color = null
       else if _.isObject(obj)


data: top-commitments query added

data: top-commitments query added

--- app/routes-data.coffee
+++ app/routes-data.coffee
@@ -441,7 +441,6 @@

-
   @get '/aiddata-nodes.csv': ->
     pg.sql "select donor as Name,donorcode as Code from totals_for_donor_ma
         UNION
@@ -540,6 +539,68 @@

+  @get '/top-commitments.json': ->
+    cond = ""
+    if @query.node
+      node = @query.node
+
+      re = /^[A-Za-z\-0-9\,\.\s\(\)]{2,64}$/
+      unless re.test node
+        @send { err: "Bad node" }
+        return
+      else
+        cond += " AND (recipientcode='#{node}' OR recipient='#{node}' OR donor='#{node}' OR donorcode='#{node}')"
+
+    if @query.purpose
+      purpose = @query.purpose
+      re = /^[0-9]{0,10}\*?$/
+
+      unless re.test purpose
+        @send { err: "Bad purpose" }
+        return
+      else
+        if purpose.indexOf("*") >= 0
+          purpose = purpose.replace("*", "%")
+          cond += " AND (aiddata_purpose_code like '#{purpose}')"
+        else
+          cond += " AND (aiddata_purpose_code='#{purpose}')"
+
+
+    pageSize = 10
+    page = 0
+
+
+    if @query.page
+      unless /^[0-9]{0,15}$/.test @query.page
+        @send { err: "Bad page" }
+        return
+      else
+        offset = @query.page
+
+
+    pg.sql "
+      select
+        donor, recipient,
+        short_description,
+        short_description_original_language,
+        long_description,
+        long_description_original_language, additional_info, additional_info_original_language,
+        other_involved_institutions,
+        to_char(commitment_amount_usd_constant, 'FM99999999999999999999') as amount_constant,
+        COALESCE(aiddata2.aiddata_purpose_code, aiddata2.crs_purpose_code, '99000') AS purpose_code,
+        aiddata_purpose_name AS purpose_name
+       from aiddata2
+      WHERE
+        commitment_amount_usd_constant is not null
+        #{cond}
+      order by commitment_amount_usd_constant desc
+      limit #{pageSize} OFFSET #{pageSize * page}
+    ",
+      (err, data) =>
+        unless err?
+          @send data.rows
+        else
+          @next(err)


expiration redirection only for candiature urls fix

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -162,7 +162,7 @@
                   # candidature expired
                   if (response.data.candidature == "expired")
                     # user candidate
-                    if($state.$urlRouter!=undefined && $state.$urlRouter.location.indexOf("candidature/")!=-1)
+                    if($state.$current!=undefined && $state.$current.name.indexOf("candidature.")!=-1)
                       # go to expiration page
                       $state.go("candidature.expired")
                 return $q.reject(response);


Replace .style with setStyle

Replace .style with setStyle

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -106,8 +106,7 @@
     thumb = item.thumbnail
     addClass thumb, class_selected

-    offset = thumb.offsetLeft + thumb.offsetWidth / 2
-    offset = env.w / 2 - offset
+    offset = env.w / 2 - thumb.offsetLeft - thumb.offsetWidth / 2

     offset = squeeze offset, 0, env.w - @dimensions.thumbWidth

@@ -179,8 +178,9 @@
   updateSides: (item) ->
     if not item.size
       item.size = item.slide.querySelector(".#{choco}-slide-container").offsetWidth
-    @leftside.style.width = (env.w - item.size) / 2 + 'px'
-    @rightside.style.width = (env.w - item.size) / 2 + 'px'
+    s = "#{(env.w - item.size) / 2}px"
+    setStyle @leftside, width: s
+    setStyle @rightside, width: s


tidy function declaration

tidy function declaration

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -253,11 +253,7 @@
 #*------------------------------------*\
 #     $RSYNC
 #*------------------------------------*/
-doRsync = (type, opts, rsyncOpts = {}) ->
-  opts =
-    isDry: opts.isDry || false
-    isToRemote: opts.isToRemote || true
-
+doRsync = (type, opts = {isDry: false, isToRemote: true}, rsyncOpts = {}) ->
   if opts.isToRemote
     dest = "#{scrt.username}@#{scrt.domain}:#{conf.rsync[type].src}"
     src = conf.rsync[type].src


:memo: Do not mention persistent option for mark...Range methods

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -1702,8 +1702,6 @@
   #     operations, but uses more time and memory. (default: false)
   #   * `reversed` (optional) {Boolean} Creates the marker in a reversed
   #     orientation. (default: false)
-  #   * `persistent` (optional) {Boolean} Whether to include this marker when
-  #     serializing the buffer. (default: true)
   #   * `invalidate` (optional) {String} Determines the rules by which changes
   #     to the buffer *invalidate* the marker. (default: 'overlap') It can be
   #     any of the following strategies, in order of fragility:
@@ -1737,8 +1735,6 @@
   #     operations, but uses more time and memory. (default: false)
   #   * `reversed` (optional) {Boolean} Creates the marker in a reversed
   #     orientation. (default: false)
-  #   * `persistent` (optional) {Boolean} Whether to include this marker when
-  #     serializing the buffer. (default: true)
   #   * `invalidate` (optional) {String} Determines the rules by which changes
   #     to the buffer *invalidate* the marker. (default: 'overlap') It can be
   #     any of the following strategies, in order of fragility:


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -7,6 +7,7 @@
   self = GameState(I).extend
     centerText: (canvas, options={}) ->
       font = options.font || I.font
+      si
       canvas.font "#{size}px #{font}"

       canvas.centerText


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -1,6 +1,6 @@
 GameOver = (I={}) ->
   Object.reverseMerge I,
-    font: 'level1'
+    font: '24px Helvetica'

   self = GameState(I).extend
     drawTitleText: (canvas, titleText, subtitleText) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -25,7 +25,7 @@

     @name centerText
     @methodOf TextScreen#
-    @param {Pixie} canvas The object you want to follow
+    @param {PixieCanvas} canvas The cavans
     ###
     centerText: (canvas, text, options={}) ->
       font = options.font || I.font


javascript handle popup panning

javascript handle popup panning

replace panTo() by autoPan with padding
popup closes social box when opened

--- app/assets/javascripts/map.js.coffee
+++ app/assets/javascripts/map.js.coffee
@@ -268,7 +268,7 @@
       # "new L.Icon.extend({})" raises "TypeError: object is not a function"

       @marker = new L.Marker new L.LatLng(@model.get('lat'), @model.get('lng')), icon: new icon
-      @marker._popup = new L.Popup offset: offset, closeButton: false, @marker
+      @marker._popup = new L.Popup offset: offset, autoPan: true, autoPanPaddingTopLeft: [50,100], autoPanPaddingBottomRight: [70,40], closeButton: false, @marker
       @marker._popup.setContent @template @model.toJSON()
       @marker._popup._initLayout()

@@ -300,7 +300,7 @@
       # Refresh Twitter button.
       twttr.widgets.load() if twttr.widgets
       # Pan to popup.
-      Map.panTo @marker.getLatLng()
+      $('#social .navbar').slideUp()

   # Don't navigate to the last known state if opening another popup.
   Map.on 'popupclose', (event) ->


remove the prev. heatmap before adding new one

--- app/assets/javascripts/map.js.coffee
+++ app/assets/javascripts/map.js.coffee
@@ -18,48 +18,26 @@
         addHeatmapLayer(json)
     })
   )
-
-  heatMapData = [
-    {location: new google.maps.LatLng(37.782, -122.447), weight: 0.5},
-    new google.maps.LatLng(37.782, -122.445),
-    {location: new google.maps.LatLng(37.782, -122.443), weight: 2},
-    {location: new google.maps.LatLng(37.782, -122.441), weight: 3},
-    {location: new google.maps.LatLng(37.782, -122.439), weight: 2},
-    new google.maps.LatLng(37.782, -122.437),
-    {location: new google.maps.LatLng(37.782, -122.435), weight: 0.5},
-
-    {location: new google.maps.LatLng(37.785, -122.447), weight: 3},
-    {location: new google.maps.LatLng(37.785, -122.445), weight: 2},
-    new google.maps.LatLng(37.785, -122.443),
-    {location: new google.maps.LatLng(37.785, -122.441), weight: 0.5},
-    new google.maps.LatLng(37.785, -122.439),
-    {location: new google.maps.LatLng(37.785, -122.437), weight: 2},
-    {location: new google.maps.LatLng(37.785, -122.435), weight: 3}
-  ]
-  console.log(heatMapData)
-
+
   sanFrancisco = new google.maps.LatLng(37.774546, -122.433523)

-
   root.map = new google.maps.Map($('#map-canvas')[0], {
       center: sanFrancisco,
       zoom: 13,
       mapTypeId: google.maps.MapTypeId.SATELLITE
   })

-  heatmap = new google.maps.visualization.HeatmapLayer({
-      data: heatMapData
-  })
-
-  heatmap.setMap(root.map)

 addHeatmapLayer = (json) ->
   for key,val of json
     console.log(val.db)
   heatMapData = ({location: new google.maps.LatLng(value.lat, value.lon), weight: value.db} for key, value of json)
   console.log(JSON.stringify heatMapData)
-  heatmap = new google.maps.visualization.HeatmapLayer({
+  if root.heatmap
+    root.heatmap.setMap null
+
+  root.heatmap = new google.maps.visualization.HeatmapLayer({
         data: heatMapData
   })
-  heatmap.setMap(root.map)
-  root.map.panTo(heatMapData[3].location)
+  root.heatmap.setMap(root.map)
+  root.map.panTo(heatMapData[0].location)


Actual timing.

Actual timing.

--- apps/algorithms/client.coffee
+++ apps/algorithms/client.coffee
@@ -36,7 +36,7 @@
     $('textarea').fadeIn 400, ->
       setTimeout ->
         dance _.without(genes, gene), _.rest(durations)
-      , duration * 10
+      , duration * 1000

 getMaxGeneCount = (genes) ->
   max_gene = _.max genes, (gene) -> gene.artworks_count + gene.artists_count


Update FSPersistorManager.coffee

Update FSPersistorManager.coffee

Using code 'ENOENT' for error checking instead of errno 34.

--- app/coffee/FSPersistorManager.coffee
+++ app/coffee/FSPersistorManager.coffee
@@ -60,7 +60,7 @@
     filteredName = filterName name
     fs.rmdir "#{location}/#{filteredName}", (err) ->
       logger.err err:err, location:location, name:filteredName, "Error on rmdir."
-      if err and err.errno != 34
+      if err and err.code != 'ENOENT'
         callback err
       else
         callback()


Fixed plain text get rendering.

Fixed plain text get rendering.

--- app/controllers/device_bridge.coffee
+++ app/controllers/device_bridge.coffee
@@ -39,7 +39,7 @@
         if err?
           res.send "", 404
         else
-          res.send value
+          res.send "" + data.getValue()

   app.put /^\/topics\/(.+)$/, (req, res) ->


Remove log messages

Remove log messages

--- test/spec/test_reactive.coffee
+++ test/spec/test_reactive.coffee
@@ -65,8 +65,6 @@
     it 'should have the given child contents', ->
       cont = elt.contents()
       child = cont.last()
-      console.log(child)
-      console.log(child.is)
       expect(cont.length).toBe(2)
       expect(cont[0]).toEqual(jasmine.any(Text))
       expect(cont[0].textContent).toBe('hello world')
@@ -96,15 +94,12 @@

     it 'should have the right tag', ->
       expect(elt).toBeDefined()
-      console.log(elt.is)
       expect(elt.is('rect')).toBe(true)
     it 'should have the set attributes', ->
       expect(elt.attr('x')).toBe('10')
     it 'should have the given child contents', ->
       cont = elt.contents()
       child = cont.last()
-      console.log(child)
-      console.log(child.is)
       expect(cont.length).toBe(1)
       expect(cont.last().is('animatetransform')).toBe(true)


remove extraneous comments

remove extraneous comments

--- test/test-chain-builder.coffee
+++ test/test-chain-builder.coffee
@@ -1,5 +1,5 @@
 assert = require 'assert'
-builder = require '../index'
+buildChain = require '../index'

 noop = /function\s?\(\)\s?{\n\s*return had\.success\(\);\n\s*}/

@@ -24,8 +24,6 @@
   assertValues result, exists, equals

 describe 'test building chains/pipelines', ->
-  #before ->
-  #beforeEach '', ->

   describe 'test passing bad values to chain/pipeline', ->


sloan_singleband: Correct a closing quote and correct the examples count.

The icon name didn't have a closing '. Instead it was at the end of the
line. When corrected, the examples count was also wrong, based on
the files that actually exist in public/images/examples/.

--- app/lib/sloan_singleband_tree.coffee
+++ app/lib/sloan_singleband_tree.coffee
@@ -27,7 +27,7 @@
     @answer 'No bulge', icon: 'bulge_none', examples: 2
     @answer 'Just noticeable', icon: 'bulge_noticeable', examples: 2
     @answer 'Obvious', icon: 'bulge_obvious', examples: 2
-    @answer 'Dominant', icon: 'bulge_dominant, examples: 2'
+    @answer 'Dominant', icon: 'bulge_dominant', examples: 0

   @question 'Odd', 'Is there anything odd?', ->
     @help "We're looking for signs that the galaxy is merging, is disturbed, or has other unusual features. Click to see larger images."


--- app/assets/javascripts/angular/controllers/GradeCtrl.js.coffee
+++ app/assets/javascripts/angular/controllers/GradeCtrl.js.coffee
@@ -1,10 +1,11 @@
-@gradecraft.controller 'GradeCtrl', ['$rootScope', '$scope', 'GradePrototype', '$window', '$http', 'debounce', ($rootScope, $scope, GradePrototype, $window, $http, debounce) ->
+@gradecraft.controller 'GradeCtrl', ['$rootScope', '$scope', 'GradePrototype', '$window', '$http' , ($rootScope, $scope, GradePrototype, $window, $http) ->

   $scope.init = (params)->
     $scope.header = "waffles"
     gradeParams = params["grade"]
     $scope.grade = new GradePrototype(gradeParams)
+    $scope.grade.addWatchers()
     gradeId = gradeParams.id

     $scope.froalaOptions = {
@@ -29,22 +30,27 @@
     this.raw_score = grade["raw_score"]
     this.feedback = grade["feedback"]

-    # manage object state
-    this.hasChanges = false
-
-  GradePrototype.prototype =
-    change: ()->
-      this.hasChanges = true
+  GradePrototype.prototype = {
+    snakes: ()->
+      alert("snakes")

     update: ()->
-      if this.hasChanges
-        self = this
-        $http.put("/grades/#{self.id}/async_update", self).success(
-          (data,status)->
-            self.resetChanges()
-        )
-        .error((err)->
-        )
+      self = this
+      $http.put("/grades/#{self.id}/async_update", self).success(
+        (data,status)->
+          self.resetChanges()
+      )
+      .error((err)->
+      )
+
+    addWatchers: ()->
+      $scope.$watch('grade.feedback', (()->
+        $scope.grade.update()
+        ), true)
+
+      $scope.$watch('grade.raw_score', (()->
+        $scope.grade.update()
+        ), true)

     debouncedUpdate: ()->
       debounce((->
@@ -76,4 +82,9 @@

     resetChanges: ()->
       this.hasChanges = false
+<<<<<<< HEAD
+=======
+  }
+
+>>>>>>> implement watchers to manage updates in grade edit controller using angular-debounce, working around fcsa-number to override internal blur actions in directive
 ]


Correction in comment.

Correction in comment.

--- app/assets/javascripts/ng-services/ng-LazyLoadingSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-LazyLoadingSvc.js.coffee
@@ -14,7 +14,7 @@
     .on 'load', ->
       if !img.hasClass 'load-failed'
         img.addClass('loaded').removeClass('hidden')
-        # center and add display-block to images if wider than 30% of the entries div
+        # center and add display-block to images if wider than 40% of the entries div
         img_width = 100 * img.width() / $('#feed-entries').width()
         img.addClass 'center-block' if img_width > 40
     data_src = img.attr 'data-src'


Fix issue with then() returning nil on successful fetch

--- app/assets/javascripts/sprangular/static_content/routes.coffee
+++ app/assets/javascripts/sprangular/static_content/routes.coffee
@@ -17,5 +17,5 @@
         page: (StaticContent, $route, $window)->
           slug = $route.current.params.slug

-          StaticContent.find(slug).then (->), ->
+          StaticContent.find(slug).then ((data) -> data), ->
             $window.location.href = "/not-found"


Fix event callbacks being skipped

Fix event callbacks being skipped

When a callback calls @removeListener, like most events dialog events do, it changes the array the @fire method iterates over, thus callbacks might not be called.

Now it stores the callbacks in a temp array and iterates over this one.

--- app/core/Entity.coffee
+++ app/core/Entity.coffee
@@ -75,13 +75,17 @@

     if @_listeners[event.type] instanceof Array
       listeners = @_listeners[event.type]
+      callbacks = []

       # call all listeners in the context of this object
       # and remove them if they return true
       for listener in listeners
-        event.listener = listener
-        listener.call @, event
-        delete event.listener
+        callbacks.push listener
+
+      for callback in callbacks
+        event.listener = callback
+        callback.call @, event
+        delete event.callback

   removeListener: (type, listener) =>


Revert "redirect /careers to /about (#4938)"

This reverts commit f335a8dff33d7e4a6af108511008d2c2f5098a1e.

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -81,8 +81,8 @@
     'artisans/sandbox': go('artisans/SandboxView')
     'artisans/bulk-level-editor/:campaign': go('artisans/BulkLevelEditView')

-    'careers': redirect('/about')
-    'Careers': redirect('/about')
+    'careers': => window.location.href = 'https://jobs.lever.co/codecombat'
+    'Careers': => window.location.href = 'https://jobs.lever.co/codecombat'

     'cla': go('CLAView')


[master] Unit tests simplify

[master] Unit tests simplify

--- test/as-country-map.coffee
+++ test/as-country-map.coffee
@@ -4,16 +4,14 @@
 m = require '../as-country-map/process'

 describe 'AS of Country Map', ->
+  parsed = m.parseFile path.join(__dirname, 'as-country-map-example.input')
   describe '#parseFile()', ->
     it 'should correctly parse example file', ->
-      m.parseFile path.join(__dirname, 'as-country-map-example.input')
-      .should.deep.
+      parsed.should.deep.
       equal JSON.parse(fs.readFileSync(path.join(__dirname, 'as-country-map-example.expected')))
   describe '#getASForCountry()', ->
     it 'should count correctly for China', ->
-      parsed = m.parseFile path.join(__dirname, 'as-country-map-example.input')
       m.getASForCountry(parsed, 'CN').should.have.length 745
     it 'should find the AS for Netherlands Antilles', ->
-      parsed = m.parseFile path.join(__dirname, 'as-country-map-example.input')
       m.getASForCountry(parsed, 'AN').should.deep.equal ['61473']


Fixed a broken config

Fixed a broken config

Don't know how this has been passing tests for the past 2 years, but Travis just picked it up.  Way to go Travis!

--- test/config/default.coffee
+++ test/config/default.coffee
@@ -1,4 +1,5 @@
 module.exports =
-  AnotherModule: parm3: "value3"
+  AnotherModule:
+    parm3: "value3"
   Customers:
     dbName: "from_default_coffee"


Defer loading of video until easter egg is triggered

--- apps/about2/client/easter_egg.coffee
+++ apps/about2/client/easter_egg.coffee
@@ -1,38 +1,38 @@
 Konami = require 'konami-js'

 module.exports = ->
-  $section = $ """
-

-

-        Carter Cleveland
-

-

-

-
The legend himself

-
Carter Cleveland, a computer science major with art-loving parents, \
-             scoured the Internet for something beautiful to hang on his wall at \
-             Princeton University, but came up empty. He resolved to build a one-stop \
-             art shop himself.
-

-             [...]
-

-             "In the art world, people are afraid to say they don't get it," he said. \
-             "What's up with that? Why should there be fear associated with this form \
-             of human expression?"
-

-

-
-              Source
-

+  init = ->
+    $section = $ """
+

+

+          Carter Cleveland
+

+

+

+
The legend himself

+
Carter Cleveland, a computer science major with art-loving parents, \
+               scoured the Internet for something beautiful to hang on his wall at \
+               Princeton University, but came up empty. He resolved to build a one-stop \
+               art shop himself.
+

+               [...]
+

+               "In the art world, people are afraid to say they don't get it," he said. \
+               "What's up with that? Why should there be fear associated with this form \
+               of human expression?"
+

+

+
+                Source
+

+

+          <video id='carter-video' \
+            style="position: fixed; top: 0; left: 0; opacity: 0; transition: opacity 0.3s; z-index: -1">
+            <source src="#{location.origin}/video/carter.mp4" type='video/mp4' />
+          </video>

-        <video id='carter-video' \
-          style="position: fixed; top: 0; left: 0; opacity: 0; transition: opacity 0.3s; z-index: -1">
-          <source src="#{location.origin}/video/carter.mp4" type='video/mp4' />
-        </video>

-

-  """
-  init = ->
+    """
     $('.about-2-foreground').append $section
     video = $('#carter-video')[0]
     $('#carter-section-container .about2-pull-blurb').waypoint
@@ -55,4 +55,4 @@
         pos = $(window).scrollTop()
         progress = (start - pos) / (start - end)
         video.currentTime = video.duration * progress
-  new Konami init+  new Konami init


fix test

fix test

--- apps/artwork/test/client/view.coffee
+++ apps/artwork/test/client/view.coffee
@@ -49,7 +49,7 @@
         ['detailTemplate', 'auctionPlaceholderTemplate']
       )
       @ArtworkView.__set__ 'analytics', { abTest: sinon.stub(), delta: sinon.stub(), track: { click: sinon.stub() } }
-      @ArtworkView.__set__ 'ShareView', (@shareViewStub = sinon.stub())
+      @ArtworkView.__set__ 'ShareModal', (@shareViewStub = sinon.stub())
       @ArtworkView.__set__ 'acquireArtwork', (@acquireArtworkStub = sinon.stub())

       stubChildClasses mod, @, ['BlurbView'], []
@@ -186,7 +186,7 @@
     describe '#openShare', ->
       it 'opens the share view when the share button is clicked', ->
         @view.$('.circle-icon-button-share').click()
-        @shareViewStub.args[0][0].artwork.id.should.equal @artwork.id
+        @shareViewStub.args[0][0].description.should.include @artwork.toAltText()
         @shareViewStub.args[0][0].width.should.equal '350px'

     describe '#route', ->


Include FreeTrialModalControler in main.coffee

--- services/web/public/coffee/main.coffee
+++ services/web/public/coffee/main.coffee
@@ -2,6 +2,7 @@
 	"main/project-list/index"
 	"main/user-details"
 	"main/account-settings"
+	"main/account-upgrade"
 	"main/templates"
 	"main/plans"
 	"main/group-members"


Add toJSON() function for datetime.coffee

This makes the dates come out as ISO strings when serializing results to JSON.

--- Src/coffeescript/cql-execution/src/datatypes/datetime.coffee
+++ Src/coffeescript/cql-execution/src/datatypes/datetime.coffee
@@ -232,6 +232,9 @@
     else
       new Date(y, mo, d, h, mi, s, ms)

+  toJSON: () ->
+    @toString()
+
   _pad: (num) ->
     String("0" + num).slice(-2);


Refactored and responsive activity graph

--- static/js/plots.coffee
+++ static/js/plots.coffee
@@ -80,133 +80,145 @@
     return getter

 class @MultiresStackedGraph
-    constructor: (el, res_data) ->
-        if not (res_data instanceof Function)
-            res_data = default_multires_smoother res_data
+    constructor: (el, @res_data) ->
+        if not (@res_data instanceof Function)
+            @res_data = default_multires_smoother res_data

         # TODO: Change on resize!
         # TODO: Refactor this mess!
         # TODO: Swap to histogram when the
         #       bin size gets too low
-        $el = $(el)
-        elwidth = $el.width()
-        elheight = $el.height()
-
-        margin =
+        @$el = $(el)
+
+        @margin =
             top: 0
             bottom: 30
             left: 30
             right: 0
-        margin.x = margin.left + margin.right
-        margin.y = margin.top + margin.bottom
-
-        width = elwidth - margin.x
-        height = elheight - margin.y
-
-        root = d3.select(el).append("svg")
+        @margin.x = @margin.left + @margin.right
+        @margin.y = @margin.top + @margin.bottom
+
+        @root = d3.select(el).append("svg")
         .attr("class", "axisplot")
-        .attr("width", elwidth)
-        .attr("height", elheight)
-
-        plot = root.append("g")
-        .attr("transform", "translate(#{margin.left}, #{margin.top})")
-
-        x = d3.time.scale()
-        .range([0, width])
-        x.domain(res_data.extent)
-
-        # TODO: The axis seems to steal 4 pixels!
-        y = d3.scale.linear()
-        .range([height, 0])
-
-
-        area = d3.svg.area()
-        .x(([d, i]) -> x d.x[i])
-        .y0(([d, i]) -> y d.y0[i])
-        .y1(([d, i]) -> y (d.y0[i] + d.y[i]))
-
-        xAxis = d3.svg.axis()
-            .scale(x)
-            .orient("bottom")
-            plot.append("g")
-            .attr("class", "x axis")
-            .attr("transform", "translate(0, #{height})")
-
-        yAxis = d3.svg.axis()
-            .scale(y)
-            .orient("left")
-            .ticks(4)
-            plot.append("g")
-            .attr("class", "y axis")
-
-        zoom = d3.behavior.zoom()
-        .x(x)
-
-        plot.append("clipPath")
+
+        @x = x = d3.time.scale()
+        .domain(@res_data.extent)
+
+        @y = y = d3.scale.linear()
+
+        @area = d3.svg.area()
+            .x(([d, i]) -> x d.x[i])
+            .y0(([d, i]) -> y d.y0[i])
+            .y1(([d, i]) -> y (d.y0[i] + d.y[i]))
+
+        @zoom = d3.behavior.zoom()
+
+        color = d3.scale.category20()
+        .domain(@res_data.keys)
+
+        @plot = @root.append("g")
+        .attr("transform", "translate(#{@margin.left}, #{@margin.top})")
+
+        @xAxis = d3.svg.axis()
+        .scale(x)
+        .orient("bottom")
+        .ticks(5)
+
+        @xAxisEl = @plot.append("g")
+        .attr("class", "x axis")
+
+        @yAxis = d3.svg.axis()
+        .scale(y)
+        .orient("left")
+        .ticks(4)
+        @plot.append("g")
+        .attr("class", "y axis")
+
+        @plot.append("clipPath")
         .attr("id", "plotclip")
         .append("use")
         .attr("xlink:href", "#plotrect")
-
-
-        color = d3.scale.category20()
-        color.domain(res_data.keys)
-
-        areas = plot.selectAll(".group")
-            .data(res_data.keys)
-            .enter().append('g')
-            .attr("class", "group")
-            .attr("style", "clip-path: url(#plotclip);")
+
+
+        areas = @plot.selectAll(".group")
+        .data(@res_data.keys)
+        .enter().append('g')
+        .attr("class", "group")
+        .attr("style", "clip-path: url(#plotclip);")
+
+        @paths = areas.append("path")
+        .attr("class", "area")
+        .style("fill", (d) -> color(d))

         # NOTE: The rect has to after the plots
         # so that it has a higher "z-index" and gets
         # all mouse events.
-        plot.append("rect")
+        @plotrect = @plot.append("rect")
         .attr("id", "plotrect")
         .attr("class", "pane")
+
+        @zoom.on("zoom", @_draw)
+
+        $(window).on "resize", =>
+            @_render_base()
+            @_draw()
+
+        @_render_base()
+        @_draw()
+
+    _render_base: =>
+        elwidth = @$el.width()
+        elheight = @$el.height()
+
+        @root
+        .attr("width", elwidth)
+        .attr("height", elheight)
+
+        width = elwidth - @margin.x
+        height = elheight - @margin.y
+
+        @x.range([0, width])
+        # TODO: The axis seems to steal 4 pixels!
+        @y.range([height, 0])
+
+        @xAxisEl
+        .attr("transform", "translate(0, #{height})")
+
+
+        @plotrect
         .attr("width", width)
         .attr("height", height)
-        .call(zoom)
-
-
-        paths = areas.append("path")
-            .attr("class", "area")
-            .style("fill", (d) -> color(d))
-
-
-
-        #stack = d3.layout.stack()
-        #.values((d) -> d.values)
-        draw = -> res_data(x.domain()).done (data, opts) ->
-            opts ?= {}
-            area.interpolate opts.interpolate ? "linear"
-
-            d.y0 = [] for d in data
-            for ix in [0...data[0].y.length]
-                y0 = 0
-                for d in data
-                    d.y0.push y0
-                    y0 += d.y[ix]
-
-
-            last = data[data.length-1]
-            lasty = (last.y[i] + last.y0[i] for i in [0...last.y0.length])
-            y.domain d3.extent lasty
+
+        @zoom.x(@x)
+        @plotrect.call(@zoom)
+
+    _draw: =>
+        @res_data(@x.domain())
+        .done @_do_draw
+
+    _do_draw: (data, opts) =>
+        opts ?= {}
+        @area.interpolate opts.interpolate ? "linear"
+
+        d.y0 = [] for d in data
+        for ix in [0...data[0].y.length]
+            y0 = 0
+            for d in data
+                d.y0.push y0
+                y0 += d.y[ix]
+

-
-            d_and_i = (d) -> ([d, i] for i in [0...d.x.length])
-
-            paths.data(data)
-            .attr("d", (d) -> area d_and_i d)
-
-
-            # TODO: Limit the extent somehow!
-            #plot.selectAll(".group .area").attr("d", (d) -> area d_and_i d)
-            plot.select(".axis.x").call(xAxis)
-            #plot.select(".axis.y").call(yAxis)
-
-
-        zoom.on("zoom", draw)
-        draw()
-        #stack color.domain().map (name) ->
-        #    {name: name, values: data[name]}
-
+        last = data[data.length-1]
+        lasty = (last.y[i] + last.y0[i] for i in [0...last.y0.length])
+        @y.domain d3.extent lasty
+
+        d_and_i = (d) -> ([d, i] for i in [0...d.x.length])
+
+        @paths.data(data)
+        .attr("d", (d) => @area d_and_i d)
+
+        # TODO: Limit the extent somehow!
+        #plot.selectAll(".group .area").attr("d", (d) -> area d_and_i d)
+        @plot.select(".axis.x").call(@xAxis)
+        #plot.select(".axis.y").call(yAxis)
+


Finally...

Finally...


Camera: Remove debug draw accidentally left on.

--- engine/camera.coffee
+++ engine/camera.coffee
@@ -186,7 +186,6 @@
         normal = vec3.create()
         q = quat.create()
         i = 0
-        dbg = @context.scenes.Scene.get_debug_draw()
         for axis in [0...3]
             for side in [-1,1]
                 # We calculate 3 points in the untransformed plane,


waterfall charts: fixed series colors. Closes #824

--- app/assets/javascripts/lib/views/waterfall_chart_view.coffee
+++ app/assets/javascripts/lib/views/waterfall_chart_view.coffee
@@ -37,7 +37,7 @@

   chart_opts: =>
     out =
-      seriesVColors: @colors()
+      seriesColors: @colors()
       highlighter: @defaults.highlighter
       grid: @defaults.grid
       seriesDefaults:


A little more work on fledgling finger tree code.

--- lib/finger_tree.coffee
+++ lib/finger_tree.coffee
@@ -5,22 +5,22 @@
 # --------------------------------------------------------------------

-reducel: (op) -> (z, x) -> x.reducel(z, op)
-reducer: (op) -> (x, z) -> x.reducer(op, z)
+reduceLeft  = (op) -> (z, x) -> x.reduceLeft(z, op)
+reduceRight = (op) -> (x, z) -> x.reduceRight(op, z)

 # A node.
 class Node2
   constructor: -> [@a, @b] = arguments
-  reducel: (z, op) -> op(op(z, @a), @b)
-  reducer: (op, z) -> op(@a, op(@b, z))
+  reduceLeft:  (z, op) -> op(op(z, @a), @b)
+  reduceRight: (op, z) -> op(@a, op(@b, z))

 class Node3
   constructor: -> [@a, @b, @c] = arguments
-  reducel: (z, op) -> op(op(op(z, @a), @b), @c)
-  reducer: (op, z) -> op(@a, op(@b, op(@c, z)))
+  reduceLeft:  (z, op) -> op(op(op(z, @a), @b), @c)
+  reduceRight: (op, z) -> op(@a, op(@b, op(@c, z)))

-make_node = (args...) ->
+makeNode = (args...) ->
   type = switch args.length
     when 2 then Digit2
     when 3 then Digit3
@@ -33,25 +33,25 @@
 # A digit in a finger tree.
 class Digit1
   constructor: -> [@a] = arguments
-  reducel: (z, op) -> op(z, @a)
-  reducer: (op, z) -> op(@a, z)
+  reduceLeft:  (z, op) -> op(z, @a)
+  reduceRight: (op, z) -> op(@a, z)

 class Digit2
   constructor: -> [@a, @b] = arguments
-  reducel: (z, op) -> op(op(z, @a), @b)
-  reducer: (op, z) -> op(@a, op(@b, z))
+  reduceLeft:  (z, op) -> op(op(z, @a), @b)
+  reduceRight: (op, z) -> op(@a, op(@b, z))

 class Digit3
   constructor: -> [@a, @b, @c] = arguments
-  reducel: (z, op) -> op(op(op(z, @a), @b), @c)
-  reducer: (op, z) -> op(@a, op(@b, op(@c, z)))
+  reduceLeft:  (z, op) -> op(op(op(z, @a), @b), @c)
+  reduceRight: (op, z) -> op(@a, op(@b, op(@c, z)))

 class Digit4
   constructor: -> [@a, @b, @c, @d] = arguments
-  reducel: (z, op) -> op(op(op(op(z, @a), @b), @c), @d)
-  reducer: (op, z) -> op(@a, op(@b, op(@c, op(@d, z))))
+  reduceLeft:  (z, op) -> op(op(op(op(z, @a), @b), @c), @d)
+  reduceRight: (op, z) -> op(@a, op(@b, op(@c, op(@d, z))))

-make_digit = (args...) ->
+makeDigit = (args...) ->
   type = switch args.length
     when 1 then Digit1
     when 2 then Digit2
@@ -65,8 +65,8 @@

 # An empty finger tree.
 Empty = {
-  reducel: (z, op) -> z
-  reducer: (op, z) -> z
+  reduceLeft:  (z, op) -> z
+  reduceRight: (op, z) -> z
 }

@@ -74,8 +74,8 @@
 class Single
   constructor: (a) -> @a = a

-  reducel: (z, op) -> op z, @a
-  reducer: (op, z) -> op @a, z
+  reduceLeft:  (z, op) -> op z, @a
+  reduceRight: (op, z) -> op @a, z

 # A deep finger tree.
@@ -85,12 +85,26 @@
     @m = middle
     @r = right

-  reducel: (z, op0) ->
-    op1 = reducel op0
-    op2 = reducel op1
+  reduceLeft: (z, op0) ->
+    op1 = reduceLeft op0
+    op2 = reduceLeft op1
     op1(op2(op1(z, @l), @m), @r)

-  reducer: (op0, z) ->
-    op1 = reducer op0
-    op2 = reducer op1
+  reduceRight: (op0, z) ->
+    op1 = reduceRight op0
+    op2 = reduceRight op1
     op1(@l, op2(@m, op1(@r, z)))
+
+
+# --------------------------------------------------------------------
+# Exporting.
+# --------------------------------------------------------------------
+
+exports ?= this.pazy ?= {}
+exports.reduceLeft  = reduceLeft
+exports.reduceRight = reduceRight
+exports.makeDigit   = makeDigit
+exports.makeNode    = makeNode
+exports.Empty  = Empty
+exports.Single = Single
+exports.Deep   = Deep


KDView: getBounds minor styling

KDView: getBounds minor styling

--- src/core/view.coffee
+++ src/core/view.coffee
@@ -317,14 +317,13 @@

   hasClass:(cssClass)-> @getElement().classList.contains cssClass

-  getBounds:->
-    #return false unless @viewDidAppend
-    bounds =
-      x : @getX()
-      y : @getY()
-      w : @getWidth()
-      h : @getHeight()
-      n : @constructor.name
+  getBounds: ->
+
+    x : @getX()
+    y : @getY()
+    w : @getWidth()
+    h : @getHeight()
+    n : @constructor.name

   setRandomBG:->@getDomElement().css "background-color", KD.utils.getRandomRGB()

@@ -358,8 +357,8 @@

   setX:(x)-> @$().css left : x
   setY:(y)-> @$().css top : y
-  getX:-> @$().offset().left
-  getY:-> @$().offset().top
+  getX:-> @getElement().getBoundingClientRect().left
+  getY:-> @getElement().getBoundingClientRect().top
   getRelativeX:-> @$().position().left
   getRelativeY:-> @$().position().top


don't warn on pusher errors for now

don't warn on pusher errors for now

--- assets/javascripts/app/pusher.coffee
+++ assets/javascripts/app/pusher.coffee
@@ -42,8 +42,8 @@
         { worker: data }

   warn: (type, warning) ->
-    console.warn(warning) unless @ignoreWarning(warning)
+    # console.warn(warning) unless @ignoreWarning(warning)

-  ignoreWarning: (warning) ->
-    if message = warning.data?.message
-      message.indexOf('Existing subscription') == 0 || message.indexOf('No current subscription') == 0
+  # ignoreWarning: (warning) ->
+  #   if message = warning.data?.message
+  #     message.indexOf('Existing subscription') == 0 || message.indexOf('No current subscription') == 0


Increase xhr timeout when downloading docs

--- assets/javascripts/models/doc.coffee
+++ assets/javascripts/models/doc.coffee
@@ -107,6 +107,7 @@
       url: @dbUrl()
       success: success
       error: error
+      timeout: 3600
     return

   uninstall: (onSuccess, onError) ->


Don't auto-tail files if task has stopped @tpetr

--- SingularityUI/app/views/tailer.coffee
+++ SingularityUI/app/views/tailer.coffee
@@ -1,5 +1,6 @@
 View = require './view'

+TaskHistory = require '../models/TaskHistory'
 LogLines = require '../collections/LogLines'

 class TailerView extends Backbone.View
@@ -22,6 +23,8 @@
         @lines = LogLines.getInstance @options

         @$container = @$('.tail-container')
+
+        @taskHistory = new TaskHistory {}, taskId: @options.taskId

         @lines.on 'sort', =>
             @handleEmpty()
@@ -109,6 +112,10 @@
         @nextPromise

     startTailing: ->
+        if @taskHistory.get('task').isStopped
+            @stopTailing()
+            return
+
         @parent.$el.addClass('tailing')

         if @tailing is null
@@ -153,10 +160,11 @@
     render: ->
         @$el.addClass 'loading'

-        # seek to the end of the file
-        @lines.fetchEndOffset().then (offset) =>
-            @handleEmpty(offset)
-            @tail Math.max(0, offset - @readLength)
+        @taskHistory.fetch().done =>
+            # seek to the end of the file
+            @lines.fetchEndOffset().then (offset) =>
+                @handleEmpty(offset)
+                @tail Math.max(0, offset - @readLength)

         @


Display better error when invalid class code is entered

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -160,7 +160,7 @@
     if jqxhr.status is 422
       @errorMessage = 'Please enter a code.'
     else if jqxhr.status is 404
-      @errorMessage = 'Code not found.'
+      @errorMessage = 'No class exists with this Class Code. Check your spelling or ask your teacher for help.'
     else
       @errorMessage = "#{jqxhr.responseText}"
     @renderSelectors '#join-class-form'


Autofill class code from URL in signup modal

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -69,7 +69,7 @@
     application.tracker?.trackEvent 'Started Student Login', category: 'Courses'

   openSignUpModal: ->
-    modal = new CreateAccountModal()
+    modal = new CreateAccountModal({ initialValues: { classCode: utils.getQueryVariable('_cc', "") } })
     @openModalView(modal)
     application.tracker?.trackEvent 'Started Student Signup', category: 'Courses'


Use translated ClassCode error message

Use translated ClassCode error message

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -162,7 +162,7 @@
     if jqxhr.status is 422
       @errorMessage = 'Please enter a code.'
     else if jqxhr.status is 404
-      @errorMessage = 'No class exists with this Class Code. Check your spelling or ask your teacher for help.'
+      @errorMessage = $.t('signup.classroom_not_found')
     else
       @errorMessage = "#{jqxhr.responseText}"
     @renderSelectors '#join-class-form'


Clean Markdown Preview View specs

Clean Markdown Preview View specs

--- spec/markdown-preview-view-spec.coffee
+++ spec/markdown-preview-view-spec.coffee
@@ -1,11 +1,9 @@
 MarkdownPreviewView = require '../lib/markdown-preview-view'
-{$, $$$} = require 'atom'

 describe "MarkdownPreviewView", ->
   [buffer, preview] = []

   beforeEach ->
-    atom.project.setPath(atom.project.resolve('markdown'))
     buffer = atom.project.bufferForPathSync('file.markdown')
     atom.packages.activatePackage('language-ruby', sync: true)
     preview = new MarkdownPreviewView(buffer.getPath())
@@ -16,7 +14,7 @@
   afterEach ->
     buffer.release()

-  describe "on construction", ->
+  describe "::constructor", ->
     it "shows a loading spinner and renders the markdown", ->
       preview.setLoading()
       expect(preview.find('.markdown-spinner')).toExist()
@@ -25,12 +23,12 @@
       preview.renderMarkdown()
       expect(preview.find(".emoji")).toExist()

-    it "shows an error message on error", ->
+    it "shows an error message when there is an error", ->
       preview.setErrorHtml("Not a real file")
       expect(preview.text()).toContain "Failed"

   describe "serialization", ->
-    it "reassociates with the same buffer when deserialized", ->
+    it "reassociates with the same buffer when serialized/deserialized", ->
       newPreview = atom.deserializers.deserialize(preview.serialize())
       waitsFor ->
         newPreview.buffer


:fire: Debug statement

:fire: Debug statement

--- spec/markdown-preview-view-spec.coffee
+++ spec/markdown-preview-view-spec.coffee
@@ -357,7 +357,6 @@
         selection.removeAllRanges()
         range = document.createRange()
         range.setStart(document.querySelector('atom-text-editor'), 0)
-        console.dir document.querySelector('p').firstChild
         range.setEnd(document.querySelector('p').firstChild, 3)
         selection.addRange(range)


Add Task.tag()

Add Task.tag()

--- app/models/task.coffee
+++ app/models/task.coffee
@@ -41,4 +41,10 @@
     @select (item) ->
       item.name?.toLowerCase().indexOf(query) > -1

+  @tag: (tag) ->
+    return [] unless tag
+    tag = tag.toLowerCase()
+    @select (item) ->
+      item.name?.toLowerCase().indexOf(tag) > -1
+
 module.exports = Task


Increase user throttle time to 10 seconds


Map the rest of the lower case registers

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -44,6 +44,22 @@
   '" h': 'vim-mode:register-prefix'
   '" i': 'vim-mode:register-prefix'
   '" j': 'vim-mode:register-prefix'
+  '" k': 'vim-mode:register-prefix'
+  '" l': 'vim-mode:register-prefix'
+  '" m': 'vim-mode:register-prefix'
+  '" n': 'vim-mode:register-prefix'
+  '" o': 'vim-mode:register-prefix'
+  '" p': 'vim-mode:register-prefix'
+  '" q': 'vim-mode:register-prefix'
+  '" r': 'vim-mode:register-prefix'
+  '" s': 'vim-mode:register-prefix'
+  '" t': 'vim-mode:register-prefix'
+  '" u': 'vim-mode:register-prefix'
+  '" v': 'vim-mode:register-prefix'
+  '" w': 'vim-mode:register-prefix'
+  '" x': 'vim-mode:register-prefix'
+  '" y': 'vim-mode:register-prefix'
+  '" z': 'vim-mode:register-prefix'
   '" *': 'vim-mode:register-prefix'
   '" +': 'vim-mode:register-prefix'
   '" %': 'vim-mode:register-prefix'


App data functions to use with server API


forgot to remove a line I used for debugging

--- lib/idris-model.coffee
+++ lib/idris-model.coffee
@@ -12,7 +12,6 @@
   oldCompilerOptions: {}

   ideMode: (compilerOptions) ->
-    a = !JS.objectEqual(@oldCompilerOptions, compilerOptions)
     if !@ideModeRef || !JS.objectEqual(@oldCompilerOptions, compilerOptions)
       @ideModeRef = new IdrisIdeMode
       @ideModeRef.on 'message', @handleCommand


Document top-level folders.

Document top-level folders.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -137,6 +137,7 @@
       @bookmarks = @traverseBookmarks(bookmarks).filter((bookmark) -> bookmark.url?)
       @onBookmarksLoaded()

+  # If these names occur as top-level bookmark names, then they are not included in the names of bookmark folders.
   ignoreTopLevel:
     'Other Bookmarks': true
     'Mobile Bookmarks': true


Remove returns provided by js to coffee conversion, I dont think they are actually needed

--- assets/scripts/mandrill_merge.coffee
+++ assets/scripts/mandrill_merge.coffee
@@ -14,12 +14,9 @@
         callback = $(this).attr('ajax-callback')
         $.post(action, params).done((response) ->
           window['MandrillMerge'].app[callback] jQuery.parseJSON(response)
-          return
         ).fail (response) ->
           alert 'something went wrong'
-          return
         e.preventDefault()
-        return

   initialize_foundation: ->
     $(document).foundation()


fix #6

fix #6

* remove `--all`, flow should be configured by the `.flowconfig` file
* remove `--module`, at least flow 0.11.0 does not know this option
* flow returns 0 after successfully starting the server, so check for that instead of 2

--- lib/linter-flow.coffee
+++ lib/linter-flow.coffee
@@ -111,8 +111,8 @@

     @flowPath = path.join(@flowPath, 'flow')

-    flowServer = spawn(@flowPath, ['start', '--all', '--module', 'node', path.resolve(atom.project.getPaths()[0])])
-    flowServer.on 'close', (code) => @flowEnabled &= (code is 2)
+    flowServer = spawn(@flowPath, ['start', path.resolve(atom.project.getPaths()[0])])
+    flowServer.on 'close', (code) => @flowEnabled &= (code is 0)

   destroy: ->
     if @flowEnabled


:bug: Respect lintOnFly config on startup

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -7,7 +7,7 @@
 class Linter
   constructor: ->
     @subscriptions = new CompositeDisposable
-    @lintOnFly = true
+    @lintOnFly = atom.config.get('linter-plus.lintOnFly')

     @emitter = new Emitter
     @views = new LinterViews this
@@ -18,7 +18,7 @@
     @linters = []

     @subscriptions.add atom.config.onDidChange 'linter-plus.lintOnFly', (value) =>
-      @lintOnFly = true
+      @lintOnFly = value
     @subscriptions.add atom.workspace.onDidChangeActivePaneItem (editor) =>
       @activeEditor = editor
       # Exceptions thrown here prevent switching tabs


Fix for path.join killing //'s in query strings

--- lib/load-config.coffee
+++ lib/load-config.coffee
@@ -10,6 +10,14 @@
       matched = item
       true
   matched
+
+joinURLs = (before, after)->
+  if before[before.length-1] == '/' and after[0] == '/'
+    before + after.substring(1)
+  else if before[before.length-1] == '/' || after[0] == '/'
+    before + after
+  else
+    before + '/' + after

 module.exports.loadFile = (configPath)->

@@ -80,9 +88,9 @@
               if @target.nopath
                 @target.pathname
               else if @target.nostrippath
-                path.join targetPathBase, reqUrl
+                joinURLs targetPathBase, reqUrl
               else
-                path.join targetPathBase, reqUrl.substring(@_path.length)
+                joinURLs targetPathBase, reqUrl.substring(@_path.length)
       else
         match =
           target: host.default
@@ -96,9 +104,9 @@
             if @target.nopath
               targetPathBase
             else if @target.nostrippath
-              path.join targetPathBase, reqUrl
+              joinURLs targetPathBase, reqUrl
             else
-              path.join targetPathBase, reqUrl.substring(@_path.length)
+              joinURLs targetPathBase, reqUrl.substring(@_path.length)

       match


修复登录条件判断

修复登录条件判断

--- lib/login/login.coffee
+++ lib/login/login.coffee
@@ -67,7 +67,7 @@
             @closeView()
             util.getPanes().destroyItem(util.getThatPane("atom://ChameleonSettings"))
             @settings.activate()
-          when 'unactive'
+          when 'unactivation'
             alert "登录失败：用户未激活"
           when 'forbidden'
             alert "登录失败：用户被禁用"


[nj] fix typo in variable name

[nj] fix typo in variable name

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -18,7 +18,7 @@
   markdown     = require('nunjucks-markdown')

   locales    = grunt.template.process('<%= data.site.locales %>').split(',')
-  baseLocale = grunt.template.process('<%= data.site.baseLocales %>')
+  baseLocale = grunt.template.process('<%= data.site.baseLocale %>')

   buildDir   = grunt.template.process('<%= path.build.root %>')
   layoutsDir = grunt.template.process('<%= path.source.layouts %>')


[grunt] use proper arrow function in i18n task

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -57,14 +57,12 @@

   # Build task
   Task = ->
-    self = @
-
     # Define targets, with unique options and files, for each locale
-    locales.forEach (locale, index, array) ->
+    locales.forEach (locale, index, array) =>
       localeDir    = resolveLocaleDir(locale)

-      self[locale]         = {}
-      self[locale].options = {
+      @[locale]         = {}
+      @[locale].options = {
         paths: '<%= path.source.layouts %>/'
         autoescape: false
         data:  '<%= data %>'
@@ -234,7 +232,7 @@
       }

-      self[locale].files =  [
+      @[locale].files =  [
         expand: true
         cwd: '<%= path.source.layouts %>/'
         src: ['{,**/}*.{nj,html}', '!{,**/}_*.{nj,html}']


Errors thrown when running benchmarks are logged to the console

--- benchmark/benchmark-bootstrap.coffee
+++ benchmark/benchmark-bootstrap.coffee
@@ -1,4 +1,4 @@
 {runSpecSuite} = require 'jasmine-helper'

 document.title = "Benchmark Suite"
-runSpecSuite("benchmark-suite", false)
+runSpecSuite("benchmark-suite", true)


Changed the flow so that running the options doesn't run the program. It instead empties the panel and says its configuring options.

To run the script, there is a run button which will save the options, close the option dialog, and run the program.

For some reason I can't get it to appear without the script window already being open

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -21,7 +21,7 @@
   initialize: (serializeState) ->
     # Bind commands
     atom.workspaceView.command "script:run", => @start(configure='hide')
-    atom.workspaceView.command "script:run-options", => @start(configure='show')
+    atom.workspaceView.command "script:run-options", => @runOptions()
     atom.workspaceView.command "script:toggle-options", => @toggleConfigureOptions()
     atom.workspaceView.command "script:save-options", => @saveOptions()
     atom.workspaceView.command "script:close-view", => @close()
@@ -47,6 +47,10 @@
     @toggleConfigureOptions(command=configure)
     info = @setup(editor)
     if info then @run(info.command, info.args)
+
+  runOptions: ->
+    @resetView(title='Configure Options')
+    @toggleConfigureOptions(command='show')

   toggleConfigureOptions: (command) ->
@@ -63,7 +67,7 @@
     @options.cmd_args = (item for item in @customOptionView.inputCommandArgs.val().split(' ') when item != '')
     @options.script_args = (item for item in @customOptionView.inputScriptArgs.val().split(' ') when item != '')

-  resetView: ->
+  resetView: (title='Loading...') ->
     # Display window and load message

     # First run, create view
@@ -73,7 +77,7 @@
     # Close any existing process and start a new one
     @stop()

-    @headerView.title.text("Loading...")
+    @headerView.title.text(title)
     @headerView.setStatus("start")

     # Get script view ready


Add routes for requests list and triage views

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -24,6 +24,9 @@
         controller: 'WaterpointCreateCtrl'
       .when '/requests',
         templateUrl: 'views/requests.html'
+        controller: 'RequestListCtrl'
+      .when '/requests/:id',
+        templateUrl: 'views/triage.html'
         controller: 'RequestTriageCtrl'
       .when '/requests/new',
         templateUrl: 'views/edit.html'


tidying nested conditionals

tidying nested conditionals

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -72,12 +72,11 @@
     $rootScope.$broadcast('admin-mode-toggle', $rootScope.admin)

   $rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams)->
-    unless $state.is('demo')
-      unless toState == 'demo'
-        if _.isEmpty(User.getCurrentUser()) or !User.getCurrentUser()?
-          event.preventDefault()
-          $timeout ()->
-            event.currentScope.$apply ()->
-              $state.go("home")
-           , 100
+    unless $state.is('demo') && toState == 'demo'
+      if _.isEmpty(User.getCurrentUser()) or !User.getCurrentUser()?
+        event.preventDefault()
+        $timeout ()->
+          event.currentScope.$apply ()->
+            $state.go("home")
+         , 100
 ])


resurrection.account: uncomment linked accounts controller class mapping

--- client/Account/AppView.coffee
+++ client/Account/AppView.coffee
@@ -18,7 +18,7 @@
     username                   : AccountEditUsername
     security                   : AccountEditSecurity
     emailNotifications         : AccountEmailNotifications
-    # linkedAccountsController   : AccountLinkedAccountsListController
+    linkedAccountsController   : AccountLinkedAccountsListController
     linkedAccounts             : AccountLinkedAccountsList
     referralSystemController   : AccountReferralSystemListController
     referralSystem             : AccountReferralSystemList


payment: remove unnecessary appStorage

payment: remove unnecessary appStorage

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -3,13 +3,10 @@
   JView.mixin @prototype

   constructor:(options = {}, data) ->
+
     options.cssClass = KD.utils.curry "content-page pricing", options.cssClass
+
     super options, data
-
-    # CtF I know this does not belongs here, but the problem was for the partial registration
-    # there is an async situation of app storage operations caused by guest users/registered
-    # members conversion
-    @appStorage = KD.getSingleton('appStorageController').storage 'Login', '1.0'

     @initViews()
     @initEvents()


Client: Activity: Added setTooltipPosition method. It calculates tooltip position.

--- client/activity/lib/components/chatlistitem/messagetime.coffee
+++ client/activity/lib/components/chatlistitem/messagetime.coffee
@@ -1,7 +1,9 @@
+$              = require 'jquery'
 kd             = require 'kd'
 React          = require 'kd-react'
 dateFormat     = require 'dateformat'
 Tooltip        = require 'app/components/tooltip'
+Portal         = require 'react-portal'

 module.exports = class MessageTime extends React.Component

@@ -16,6 +18,15 @@

     dateFormat date, timeFormat

+
+  setTooltipPosition: (e) ->
+
+    MessageDateDOMNode = @refs.MessageDate.getDOMNode()
+    offset = $(MessageDateDOMNode).offset()
+
+    @setState
+      tooltipY      : offset.top
+      tooltipX      : offset.left + MessageDateDOMNode.offsetWidth / 2

   getTooltipTitle: (data, timeFormat) ->


code readability fix

code readability fix

--- client/app/Applications/Ace.kdapplication/AppController.coffee
+++ client/app/Applications/Ace.kdapplication/AppController.coffee
@@ -27,8 +27,8 @@
       { title     : "Find and Replace",    eventName : "findAndReplace" }
       { title     : "Goto line",           eventName : "gotoLine" }
       { type      : "separator" }
-      { title     : "Compile and Run",     eventName : "compileAndRun", condition: compileCondition}
-      { type      : "separator",                                        condition: compileCondition}
+      { title     : "Compile and Run",     eventName : "compileAndRun", condition: canCompile}
+      { type      : "separator",                                        condition: canCompile}
       { title     : "Preview",             eventName : "preview" }
       { type      : "separator" }
       { title     : "Advanced Settings",   id        : "advancedSettings" }


client: update kd install url

client: update kd install url

Closes #8514.

--- client/app/lib/providers/managed/addmanagedmachinemodal.coffee
+++ client/app/lib/providers/managed/addmanagedmachinemodal.coffee
@@ -87,7 +87,7 @@
       then "export KONTROLURL=#{KodingKontrol.getKontrolUrl()} CHANNEL=devmanaged; "
       else ''

-    cmd = "#{kontrolUrl}curl -sL https://kodi.ng/s | bash -s #{token}"
+    cmd = "#{kontrolUrl}curl -sL https://kodi.ng/c/p/kd | bash -s #{token}"

     @loader.destroy()


landing: handle oauth process in Login app

--- client/landing/site.landing/coffee/core/oauthcontroller.coffee
+++ client/landing/site.landing/coffee/core/oauthcontroller.coffee
@@ -83,13 +83,13 @@
   handleNewUser: (userInfo)->

     utils.storeLastUsedProvider userInfo.provider
-    kd.singletons.router.handleRoute '/'
+    kd.singletons.router.handleRoute '/Register'

-    kd.singletons.router.requireApp 'Home', (homeController)->
-      homeView = homeController.getView()
-      { signUpForm } = homeView
+    kd.singletons.router.requireApp 'Login', (loginController)->
+      loginView = loginController.getView()
+      { registerForm } = loginView

-      signUpForm.handleOauthData userInfo
+      registerForm.handleOauthData userInfo

   notify = (err)->


tidy up home

tidy up home

--- client/views/home/home.coffee
+++ client/views/home/home.coffee
@@ -1,5 +1,7 @@
 Template.home.rendered = () ->
 	w = new WOW().init()
+
+	# TODO: End after home destroyed

 	# make sure div stays full width/height on resize
 	# global vars
@@ -16,13 +18,8 @@
 	    width: $(window).width()
 	    height: $(window).height()

-
-	# s = new skrollr.init({smoothScrolling: true})
-	# s.refresh()
-
-	if  (/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone/i).test(navigator.userAgent or navigator.vendor or window.opera)
-		$('#intro').addClass('mobile')
-	else
+	#Skroll doesn't work so well on mobile imo
+	unless Utils.isMobile
 		options =
 			forceHeight: false
 			smoothScrolling: false


Indent correctly

Indent correctly

--- src/databound.coffee
+++ src/databound.coffee
@@ -105,11 +105,11 @@
     # backend responds with:
     #
     # ```javascript
-    # {
-    #   success: true,
-    #   id: record.id,
-    #   scoped_records: []
-    # }
+    #   {
+    #     success: true,
+    #     id: record.id,
+    #     scoped_records: []
+    #   }
     # ```
     @request(action, params).then (resp) ->
       throw new Error 'Error in the backend' unless resp?.success


Update the test to what I actually expect:

1. You have to re-require the car to get a real car
2. This car should still blow up, but because brake is not a real thing

--- test/src/replace/index-test.coffee
+++ test/src/replace/index-test.coffee
@@ -189,4 +189,5 @@

     describe 'post-reset usage', ->
       Given -> td.reset()
-      Then -> @car.honk.toString() == "throw 'honk'"
+      When -> try require('../../fixtures/car') catch e then @error = e
+      Then -> @error.message == "Cannot find module './brake'"


Remove Google Url Shortener support

Remove Google Url Shortener support

--- lib/utils/index.coffee
+++ lib/utils/index.coffee
@@ -443,23 +443,6 @@
     chance = Math.floor(Math.random() * 100)
     chance <= percent

-  shortenUrl: (url, key, callback)->
-    throw Error('Missing Google API Key') unless typeof key is 'string' and key?
-
-    request = $.ajax "https://www.googleapis.com/urlshortener/v1/url?key=#{key}",
-      type        : "POST"
-      contentType : "application/json"
-      data        : JSON.stringify {longUrl: url}
-      timeout     : 4000
-      dataType    : "json"
-
-    request.done (data)=>
-      callback data?.id or url, data
-
-    request.fail ({status, statusText, responseText})->
-      debug "could not shorten url #{url}", status, statusText, responseText
-      callback url
-
   formatBytesToHumanReadable: (bytes, fixedAmout = 2) ->
     minus = ''
     if bytes < 0


License overrides.

License overrides.

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -123,3 +123,206 @@
     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     """
+  'tweetnacl@0.13.2':
+    license: 'CC0-1.0'
+    source: 'https://github.com/dchest/tweetnacl-js/blob/2f328394f74d83564634fb89ea2798caa3a4edb9/README.md says public domain.'
+  'json-schema@0.2.2':
+    license: 'AFLv2.1'
+    source: 'README links to https://github.com/dojo/dojo/blob/8b6a5e4c42f9cf777dd39eaae8b188e0ebb59a4c/LICENSE'
+    sourceText: """
+    Dojo is available under *either* the terms of the modified BSD license *or* the
+    Academic Free License version 2.1. As a recipient of Dojo, you may choose which
+    license to receive this code under (except as noted in per-module LICENSE
+    files). Some modules may not be the copyright of the Dojo Foundation. These
+    modules contain explicit declarations of copyright in both the LICENSE files in
+    the directories in which they reside and in the code itself. No external
+    contributions are allowed under licenses which are fundamentally incompatible
+    with the AFL or BSD licenses that Dojo is distributed under.
+
+    The text of the AFL and BSD licenses is reproduced below.
+
+    -------------------------------------------------------------------------------
+    The "New" BSD License:
+    **********************
+
+    Copyright (c) 2005-2015, The Dojo Foundation
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are met:
+
+      * Redistributions of source code must retain the above copyright notice, this
+        list of conditions and the following disclaimer.
+      * Redistributions in binary form must reproduce the above copyright notice,
+        this list of conditions and the following disclaimer in the documentation
+        and/or other materials provided with the distribution.
+      * Neither the name of the Dojo Foundation nor the names of its contributors
+        may be used to endorse or promote products derived from this software
+        without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+    -------------------------------------------------------------------------------
+    The Academic Free License, v. 2.1:
+    **********************************
+
+    This Academic Free License (the "License") applies to any original work of
+    authorship (the "Original Work") whose owner (the "Licensor") has placed the
+    following notice immediately following the copyright notice for the Original
+    Work:
+
+    Licensed under the Academic Free License version 2.1
+
+    1) Grant of Copyright License. Licensor hereby grants You a world-wide,
+    royalty-free, non-exclusive, perpetual, sublicenseable license to do the
+    following:
+
+    a) to reproduce the Original Work in copies;
+
+    b) to prepare derivative works ("Derivative Works") based upon the Original
+    Work;
+
+    c) to distribute copies of the Original Work and Derivative Works to the
+    public;
+
+    d) to perform the Original Work publicly; and
+
+    e) to display the Original Work publicly.
+
+    2) Grant of Patent License. Licensor hereby grants You a world-wide,
+    royalty-free, non-exclusive, perpetual, sublicenseable license, under patent
+    claims owned or controlled by the Licensor that are embodied in the Original
+    Work as furnished by the Licensor, to make, use, sell and offer for sale the
+    Original Work and Derivative Works.
+
+    3) Grant of Source Code License. The term "Source Code" means the preferred
+    form of the Original Work for making modifications to it and all available
+    documentation describing how to modify the Original Work. Licensor hereby
+    agrees to provide a machine-readable copy of the Source Code of the Original
+    Work along with each copy of the Original Work that Licensor distributes.
+    Licensor reserves the right to satisfy this obligation by placing a
+    machine-readable copy of the Source Code in an information repository
+    reasonably calculated to permit inexpensive and convenient access by You for as
+    long as Licensor continues to distribute the Original Work, and by publishing
+    the address of that information repository in a notice immediately following
+    the copyright notice that applies to the Original Work.
+
+    4) Exclusions From License Grant. Neither the names of Licensor, nor the names
+    of any contributors to the Original Work, nor any of their trademarks or
+    service marks, may be used to endorse or promote products derived from this
+    Original Work without express prior written permission of the Licensor. Nothing
+    in this License shall be deemed to grant any rights to trademarks, copyrights,
+    patents, trade secrets or any other intellectual property of Licensor except as
+    expressly stated herein. No patent license is granted to make, use, sell or
+    offer to sell embodiments of any patent claims other than the licensed claims
+    defined in Section 2. No right is granted to the trademarks of Licensor even if
+    such marks are included in the Original Work. Nothing in this License shall be
+    interpreted to prohibit Licensor from licensing under different terms from this
+    License any Original Work that Licensor otherwise would have a right to
+    license.
+
+    5) This section intentionally omitted.
+
+    6) Attribution Rights. You must retain, in the Source Code of any Derivative
+    Works that You create, all copyright, patent or trademark notices from the
+    Source Code of the Original Work, as well as any notices of licensing and any
+    descriptive text identified therein as an "Attribution Notice." You must cause
+    the Source Code for any Derivative Works that You create to carry a prominent
+    Attribution Notice reasonably calculated to inform recipients that You have
+    modified the Original Work.
+
+    7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that
+    the copyright in and to the Original Work and the patent rights granted herein
+    by Licensor are owned by the Licensor or are sublicensed to You under the terms
+    of this License with the permission of the contributor(s) of those copyrights
+    and patent rights. Except as expressly stated in the immediately proceeding
+    sentence, the Original Work is provided under this License on an "AS IS" BASIS
+    and WITHOUT WARRANTY, either express or implied, including, without limitation,
+    the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR
+    PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU.
+    This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No
+    license to Original Work is granted hereunder except under this disclaimer.
+
+    8) Limitation of Liability. Under no circumstances and under no legal theory,
+    whether in tort (including negligence), contract, or otherwise, shall the
+    Licensor be liable to any person for any direct, indirect, special, incidental,
+    or consequential damages of any character arising as a result of this License
+    or the use of the Original Work including, without limitation, damages for loss
+    of goodwill, work stoppage, computer failure or malfunction, or any and all
+    other commercial damages or losses. This limitation of liability shall not
+    apply to liability for death or personal injury resulting from Licensor's
+    negligence to the extent applicable law prohibits such limitation. Some
+    jurisdictions do not allow the exclusion or limitation of incidental or
+    consequential damages, so this exclusion and limitation may not apply to You.
+
+    9) Acceptance and Termination. If You distribute copies of the Original Work or
+    a Derivative Work, You must make a reasonable effort under the circumstances to
+    obtain the express assent of recipients to the terms of this License. Nothing
+    else but this License (or another written agreement between Licensor and You)
+    grants You permission to create Derivative Works based upon the Original Work
+    or to exercise any of the rights granted in Section 1 herein, and any attempt
+    to do so except under the terms of this License (or another written agreement
+    between Licensor and You) is expressly prohibited by U.S. copyright law, the
+    equivalent laws of other countries, and by international treaty. Therefore, by
+    exercising any of the rights granted to You in Section 1 herein, You indicate
+    Your acceptance of this License and all of its terms and conditions.
+
+    10) Termination for Patent Action. This License shall terminate automatically
+    and You may no longer exercise any of the rights granted to You by this License
+    as of the date You commence an action, including a cross-claim or counterclaim,
+    against Licensor or any licensee alleging that the Original Work infringes a
+    patent. This termination provision shall not apply for an action alleging
+    patent infringement by combinations of the Original Work with other software or
+    hardware.
+
+    11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this
+    License may be brought only in the courts of a jurisdiction wherein the
+    Licensor resides or in which Licensor conducts its primary business, and under
+    the laws of that jurisdiction excluding its conflict-of-law provisions. The
+    application of the United Nations Convention on Contracts for the International
+    Sale of Goods is expressly excluded. Any use of the Original Work outside the
+    scope of this License or after its termination shall be subject to the
+    requirements and penalties of the U.S. Copyright Act, 17 U.S.C. Â§ 101 et
+    seq., the equivalent laws of other countries, and international treaty. This
+    section shall survive the termination of this License.
+
+    12) Attorneys Fees. In any action to enforce the terms of this License or
+    seeking damages relating thereto, the prevailing party shall be entitled to
+    recover its costs and expenses, including, without limitation, reasonable
+    attorneys' fees and costs incurred in connection with such action, including
+    any appeal of such action. This section shall survive the termination of this
+    License.
+
+    13) Miscellaneous. This License represents the complete agreement concerning
+    the subject matter hereof. If any provision of this License is held to be
+    unenforceable, such provision shall be reformed only to the extent necessary to
+    make it enforceable.
+
+    14) Definition of "You" in This License. "You" throughout this License, whether
+    in upper or lower case, means an individual or a legal entity exercising rights
+    under, and complying with all of the terms of, this License. For legal
+    entities, "You" includes any entity that controls, is controlled by, or is
+    under common control with you. For purposes of this definition, "control" means
+    (i) the power, direct or indirect, to cause the direction or management of such
+    entity, whether by contract or otherwise, or (ii) ownership of fifty percent
+    (50%) or more of the outstanding shares, or (iii) beneficial ownership of such
+    entity.
+
+    15) Right to Use. You may use the Original Work in all ways not otherwise
+    restricted or conditioned by this License or by law, and Licensor promises not
+    to interfere with or be responsible for such uses by You.
+
+    This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved.
+    Permission is hereby granted to copy and distribute this license without
+    modification. This license may not be modified without the express written
+    permission of its copyright owner.
+    """


fixed buffered pipe test

fixed buffered pipe test

--- src/test/buffer.coffee
+++ src/test/buffer.coffee
@@ -43,16 +43,15 @@

     pipe: (æ) ->
-        buffer = new BufferStream size:'flexible', split:'\n'
-        buffer.on 'data', (data) -> æ.equal data.toString(), readme.shift()
+        buffer = new BufferStream size:'flexible'
+        buffer.on 'data', (data) -> throw 'up'
         buffer.on 'end', ->
-            æ.equal buffer.length, 0
-            æ.equal buffer.toString(), ""
-            æ.deepEqual readme, [ "END" ]
+            æ.equal buffer.enabled, yes
+            æ.equal "#{buffer.toString()}END", readme
             æ.done()

-        filename = path.join(__dirname,"..","..","..","README.md")
-        readme = "#{readFileSync(filename)}END".split('\n')
+        filename = path.join(__dirname,"..","README.md")
+        readme = "#{readFileSync(filename)}END"
         stream = createReadStream filename

         stream.pipe buffer


remove logging

remove logging

--- tests/04_message_operations.coffee
+++ tests/04_message_operations.coffee
@@ -120,7 +120,6 @@
         """

         req = client.post "/message", null, (err, res, body) =>
-            console.log "FROM TEST", err?.stack
             should.not.exist err
             res.statusCode.should.equal 200
             body.should.have.property 'id'


@markijbema prefers 5 underscores

@markijbema prefers 5 underscores

--- core/app/backbone/views/evidence/add_evidence_form_view.coffee
+++ core/app/backbone/views/evidence/add_evidence_form_view.coffee
@@ -28,7 +28,7 @@
       new Backbone.Collection [@collection.fact.clone()]

   _collectionUtils: ->
-    @__collectionUtils ?= new CollectionUtils this
+    @_____collectionUtils ?= new CollectionUtils this

   searchView: ->
     searchView = new AutoCompleteFactRelationsView


Replace 'groupable' -> 'open for grouping'

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -821,9 +821,10 @@
   # commit, but a single call to abort will cancel all nested transactions.
   #
   # * `groupingInterval` (optional) The {Number} of milliseconds for which this
-  #   transaction should be considered 'groupable' after it begins. If a transaction
-  #   with a positive `groupingInterval` is committed while the previous transaction is
-  #   still 'groupable', the two transactions are merged with respect to undo and redo.
+  #   transaction should be considered 'open for grouping' after it begins. If a
+  #   transaction with a positive `groupingInterval` is committed while the previous
+  #   transaction is still open for grouping, the two transactions are merged with
+  #   respect to undo and redo.
   beginTransaction: (groupingInterval) -> @history.beginTransaction(groupingInterval)

   # Public: Commit an open-ended transaction started with {::beginTransaction}


Clear cached text when buffer is destroyed

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -1657,6 +1657,7 @@
       for id, markerLayer of @markerLayers
         markerLayer.destroy()
       @buffer.reset('')
+      @cachedText = null
       @history.clear()

   isAlive: -> not @destroyed


Remove log lines

Remove log lines

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -717,6 +717,3 @@
         else
           bufferRow += numberLines
           bufferColumn = computeBufferColumn(change.value)
-
-        # console.log 'after', bufferRow, bufferColumn, change
-        # console.log @getText()


Publicize checkpoint methods and deprecate open-ended transactions

On TextEditor

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -1098,41 +1098,30 @@
   # abort the transaction, call {::abortTransaction} to terminate the function's
   # execution and revert any changes performed up to the abortion.
   #
-  # * `groupingInterval` (optional) This is the sames as the `groupingInterval`
-  #    parameter in {::beginTransaction}
-  # * `fn` A {Function} to call inside the transaction.
-  transact: (groupingInterval, fn) -> @buffer.transact(groupingInterval, fn)
-
-  # Extended: Start an open-ended transaction.
-  #
-  # Call {::commitTransaction} or {::abortTransaction} to terminate the
-  # transaction. If you nest calls to transactions, only the outermost
-  # transaction is considered. You must match every begin with a matching
-  # commit, but a single call to abort will cancel all nested transactions.
-  #
   # * `groupingInterval` (optional) The {Number} of milliseconds for which this
   #   transaction should be considered 'groupable' after it begins. If a transaction
   #   with a positive `groupingInterval` is committed while the previous transaction is
   #   still 'groupable', the two transactions are merged with respect to undo and redo.
+  # * `fn` A {Function} to call inside the transaction.
+  transact: (groupingInterval, fn) -> @buffer.transact(groupingInterval, fn)
+
+  # Deprecated: Start an open-ended transaction.
   beginTransaction: (groupingInterval) -> @buffer.beginTransaction(groupingInterval)

-  # Extended: Commit an open-ended transaction started with {::beginTransaction}
-  # and push it to the undo stack.
-  #
-  # If transactions are nested, only the outermost commit takes effect.
+  # Deprecated: Commit an open-ended transaction started with {::beginTransaction}.
   commitTransaction: -> @buffer.commitTransaction()

   # Extended: Abort an open transaction, undoing any operations performed so far
   # within the transaction.
   abortTransaction: -> @buffer.abortTransaction()

-  # Experimental: Create a pointer to the current state of the buffer for use
+  # Extended: Create a pointer to the current state of the buffer for use
   # with {::revertToCheckpoint} and {::groupChangesSinceCheckpoint}.
   #
   # Returns a checkpoint value.
   createCheckpoint: -> @buffer.createCheckpoint()

-  # Experimental: Revert the buffer to the state it was in when the given
+  # Extended: Revert the buffer to the state it was in when the given
   # checkpoint was created.
   #
   # The redo stack will be empty following this operation, so changes since the
@@ -1143,7 +1132,7 @@
   # Returns a {Boolean} indicating whether the operation succeeded.
   revertToCheckpoint: (checkpoint) -> @buffer.revertToCheckpoint(checkpoint)

-  # Experimental: Group all changes since the given checkpoint into a single
+  # Extended: Group all changes since the given checkpoint into a single
   # transaction for purposes of undo/redo.
   #
   # If the given checkpoint is no longer present in the undo history, no


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -7,7 +7,7 @@
   self = GameState(I).extend
     centerText: (canvas, options={}) ->
       font =
-      canvas.font ""
+      canvas.font "#{}"

       canvas.centerText
         y: App.height / 2 - 20


changed missed identifier for Deutsch

changed missed identifier for Deutsch

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -3,7 +3,7 @@

 exportObj.translations ?= {}
 # This is here mostly as a template for other languages.
-exportObj.translations.CapsLock =
+exportObj.translations.Deutsch =
     action:
         "Barrel Roll": "Fassrolle"
         "Boost": "Schub"
@@ -1087,8 +1087,7 @@
             slot: "Elite"
             sources: [ "TIE Advanced Expansion Pack", ]
             points: 2
-            text: """Aktion: Wähle ein Schiff in Reichweite 1-2 mit einem geringeren Pilotenwert als du.
-Das gewählte Schiff darf sofort 1 freie Aktion durhführen."""
+            text: """Aktion: Wähle ein Schiff in Reichweite 1-2 mit einem geringeren Pilotenwert als du.

Das gewählte Schiff darf sofort 1 freie Aktion durhführen."""
         "Flugkunst":
             name: "Flugkunst"
             id: 11


I'M STUPID

I'M STUPID

So yeah, that. Groarg.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1148,7 +1148,8 @@
             name: "Mayor Vermeil"
             ship: "Segador TIE"
             text: '''Cuando ataques, si el defensor no tiene asignada ninguna ficha de Concentración ni de Evasión, puedes cambiar 1 de tus resultados %FOCUS% o de cara vacía por un resultado %HIT%.'''
-        '"Visir"':
+        '"Vizier"':
+            name: '"Visir"'
             text: '''Después de que una nave aliada ejecute una maniobra con una velocidad de 1, si esa nave está situada a alcance 1 de ti y no se ha solapado con ninguna nave, puedes asignarle 1 de tus fichas de Concentración o Evasión.'''
             ship: "Segador TIE"
         'Captain Feroph':


Fixed Rainier Wolfcastle's name.

Fixed Rainier Wolfcastle's name.

--- src/thesimpsons.coffee
+++ src/thesimpsons.coffee
@@ -56,7 +56,7 @@
    "I can't believe you don't shut up! -Apu Nahasapeemapetilon",
    "Ah, alcohol and night-swimming. It's a winning combination. –Lenny",
    "I would kill everyone in this room for a drop of sweet beer. -Homer Simpson",
-   "My eyes! The goggles do nothing! -Rainer Wolfcastle",
+   "My eyes! The goggles do nothing! -Rainier Wolfcastle",
    "Priceless like a mother's love, or the good kind of priceless? -Bart Simpson",
    "Science. What's science ever done for us? TV off. -Moe the Bartender",
    "Chocolate microscopes? -Ralph Wiggum",