:art: more readable.

:art: more readable.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -20,13 +20,15 @@
   recodable: true
   linewiseAlias: false

+  isSameOperatorRepeated: ->
+    if @linewiseAlias and @vimState.isOperatorPendingMode()
+      @vimState.operationStack.peekTop().constructor is @constructor
+    else
+      false
+
   constructor: ->
     super
     #  To support, `dd`, `cc`, `yy` `>>`, `<<`, `==`
-    if @linewiseAlias and @vimState.isOperatorPendingMode() and
-      @vimState.operationStack.peekTop().constructor is @constructor
-        @vimState.operationStack.push @new('MoveToRelativeLine')
-        @abort()

   # target - TextObject or Motion to operate on.
   compose: (target) ->


fix lint error

fix lint error

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -599,7 +599,7 @@
     else
       @pasteCharacterwise(selection, text)

-  pasteCharacterwise: (selection, text)->
+  pasteCharacterwise: (selection, text) ->
     {cursor} = selection
     if selection.isEmpty() and @location is 'after' and not cursorIsAtEmptyRow(cursor)
       cursor.moveRight()


add api to allow other file extensions as output than json, js and coffee

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -3,6 +3,21 @@
 { extname } = require 'path'
 { jsonify } = require './traverse'
 link = require './linker'
+
+extensions =  # defaults
+    json: (tree) ->
+        JSON.stringify(tree)
+    coffee: (tree) ->
+        """link = require 'dt-compiler/linker'
+        tree = #{JSON.stringify tree}
+        module.exports = (rawtemplate) -> link rawtemplate, tree
+        """
+    js: (tree) ->
+        """var link = require('dt-compiler/linker'),
+        tree = #{JSON.stringify tree};
+        module.exports = function (rawtemplate) {
+            return link(rawtemplate, tree);
+        };"""

 class HTMLCompiler
@@ -13,8 +28,14 @@
         # values
         @loaded = no
         @loading = no
+        @extensions = {}
+        for name, ext of HTMLCompiler.extensions
+            @extensions[name] = ext
         #aliases
         @loadSync = @open
+
+    register: (name, ext) ->
+        @extensions[name] = ext

     read: (filename, callback) ->
         fs.readFile filename, (err, data) =>
@@ -92,17 +113,10 @@
                 done()
             else
                 # save json dom in an own file
-                ext = extname(opts.dest).toLowerCase()
-                if ext is '.json'
-                    source = JSON.stringify(elem.data)
-                else if ext is '.js'
-                    source = """var link = require('dt-compiler/linker'),
-                    tree = #{JSON.stringify elem.data};
-                    module.exports = function (rawtemplate) {
-                        return link(rawtemplate, tree);
-                    };"""
-                else
-                    throw new Error "file extendsion of #{opts.dest} not supported."
+                ext = extname(opts.dest).toLowerCase().substr(1) # without dot
+                unless @extensions[ext]?
+                    throw new Error "file extension of #{opts.dest} not supported."
+                source = @extensions[ext](elem.data)
                 fs.writeFile(opts.dest, source, done)

         # do an auto load if not loaded so an extra load call is not needed
@@ -133,5 +147,6 @@

 # exports

+HTMLCompiler.extensions = extensions
 module.exports = HTMLCompiler


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -15,7 +15,7 @@
     unless I.cooldowns[name]
       I.cooldowns[name] = {
         options.target
-        options.
+        options.appro
       }


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -9,7 +9,7 @@
       I[name] = I[name].approach(target, approachBy)

   cooldown: (name, options={}) ->
-    {target}
+    {target, approach}
     options.target ||= 0
     options.approachBy = 1 unless options.approachBy?


Update ::terminatePendingState comment

Update ::terminatePendingState comment

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -232,10 +232,10 @@
     @pendingPartialMatches = null

   # This is called by {::handleKeyboardEvent} when no matching bindings are
-  # found for the currently queued keystrokes. It disables the longest of the
-  # pending partially matching bindings, then replays the queued keyboard
-  # events to allow any bindings with shorter keystroke sequences to be matched
-  # unambiguously.
+  # found for the currently queued keystrokes or by the pending state timeout.
+  # It disables the longest of the pending partially matching bindings, then
+  # replays the queued keyboard events to allow any bindings with shorter
+  # keystroke sequences to be matched unambiguously.
   terminatePendingState: ->
     return unless @pendingPartialMatches? and @queuedKeyboardEvents.length > 0


only use a regex when looking for roles, and quit early

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -101,9 +101,9 @@
   #
   #
   has_role: (role) ->
-    role = role.toLowerCase()
-    regex = new RegExp("^urn:lti:role:ims/lis/" + role + "$")
-    @body.roles && @body.roles.filter((r) -> (r = r.toLowerCase()) and (r is role or regex.test(r))).length >= 1
+    role = role.replace /([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'
+    regex = new RegExp "(^|^urn:lti:role:ims/lis/)#{role}$", 'i'
+    @body.roles && @body.roles.some (r) -> regex.test(r)


Added all existing divination cards.

Added all existing divination cards.

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -403,12 +403,170 @@
   ]

   divcards: [
-    #Just a start, I'll add more when I have time
-    #I had no idea these had a base type
+    { snippet: '"Abandoned Wealth"' },
+    { snippet: '"The Aesthete"' },
+    { snippet: '"Anarchy\'s Price"' },
+    { snippet: '"The Arena Champion"' },
+    { snippet: '"The Artist"' },
+    { snippet: '"Assassin\'s Favour"' },
+    { snippet: 'Audacity' },
+    { snippet: '"The Avenger"' },
+    { snippet: '"The Battle Born"' },
+    { snippet: '"The Betrayal"' },
+    { snippet: '"Birth of the Three"' },
+    { snippet: '"Blind Venture"' },
+    { snippet: '"The Body"' },
+    { snippet: '"Boundless Realms"' },
+    { snippet: '"Bowyer\'s Dream"' },
+    { snippet: '"The Brittle Emperor"' },
+    { snippet: '"The Calling"' },
+    { snippet: '"The Carrion Crow"' },
+    { snippet: '"The Cartographer"' },
+    { snippet: '"Cartographer\'s Delight"' },
+    { snippet: '"The Cataclysm"' },
+    { snippet: '"The Catalyst"' },
+    { snippet: '"The Celestial Justicar"' },
+    { snippet: '"The Chains that Bind"' },
+    { snippet: '"Chaotic Disposition"' },
+    { snippet: '"The Conduit"' },
+    { snippet: '"Coveted Possession"' },
+    { snippet: '"The Cursed King"' },
+    { snippet: '"The Dapper Prodigy"' },
+    { snippet: '"The Dark Mage"' },
+    { snippet: 'Death' },
+    { snippet: '"The Demoness"' },
+    { snippet: '"Destined to Crumble"' },
+    { snippet: '"The Devastator"' },
+    { snippet: '"Dialla\'s Subjugation"' },
+    { snippet: '"The Doctor"' },
+    { snippet: '"Doedre\'s Madness"' },
+    { snippet: '"The Doppelganger"' },
+    { snippet: '"The Dragon"' },
+    { snippet: '"The Dragon\'s Heart"' },
+    { snippet: '"The Drunken Aristocrat"' },
+    { snippet: '"Dying Anguish"' },
+    { snippet: '"Earth Drinker"' },
+    { snippet: '"Emperor of Purity"' },
+    { snippet: '"Emperor\'s Luck"' },
+    { snippet: '"The Encroaching Darkness"' },
+    { snippet: '"The Endurance"' },
+    { snippet: '"The Enlightened"' },
+    { snippet: '"The Ethereal"' },
+    { snippet: '"The Explorer"' },
+    { snippet: '"The Feast"' },
+    { snippet: '"The Fiend"' },
+    { snippet: '"The Fletcher"' },
+    { snippet: '"The Flora\'s Gift"' },
+    { snippet: '"The Formless Sea"' },
+    { snippet: '"The Fox"' },
+    { snippet: '"The Gambler"' },
+    { snippet: '"The Gemcutter"' },
+    { snippet: '"Gemcutter\'s Promise"' },
+    { snippet: '"The Gentleman"' },
+    { snippet: '"Gift of the Gemling Queen"' },
+    { snippet: '"The Gladiator"' },
+    { snippet: '"Glimmer of Hope"' },
+    { snippet: '"Grave Knowledge"' },
+    { snippet: '"The Harvester"' },
+    { snippet: '"Her Mask"' },
+    { snippet: '"The Hermit"' },
+    { snippet: '"Heterochromia"' },
+    { snippet: '"The Hoarder"' },
+    { snippet: 'Hope' },
+    { snippet: '"House of Mirrors"' },
+    { snippet: 'Hubris' },
+    { snippet: 'Humility' },
+    { snippet: '"The Hunger"' },
+    { snippet: '"Hunter\'s Resolve"' },
+    { snippet: '"Hunter\'s Reward"' },
+    { snippet: '"The Immortal"' },
+    { snippet: '"The Incantation"' },
+    { snippet: '"The Inoculated"' },
+    { snippet: '"The Inventor"' },
+    { snippet: '"Jack in the Box"' },
+    { snippet: '"The Jester"' },
+    { snippet: '"The King\'s Blade"' },
+    { snippet: '"The King\'s Heart"' },
+    { snippet: '"Lantador\'s Lost Love"' },
+    { snippet: '"Last Hope"' },
+    { snippet: '"The Last One Standing"' },
+    { snippet: '"The Lich"' },
+    { snippet: '"Light and Truth"' },
+    { snippet: '"The Lion"' },
+    { snippet: '"The Lord in Black"' },
+    { snippet: '"Lost Worlds"' },
+    { snippet: '"The Lover"' },
+    { snippet: 'Loyalty' },
+    { snippet: '"Lucky Connections"' },
+    { snippet: '"Lucky Deck"' },
+    { snippet: '"The Lunaris Priestess"' },
+    { snippet: '"Lysah\'s Respite"' },
+    { snippet: '"Mawr Blaidd"' },
+    { snippet: '"The Mercenary"' },
+    { snippet: '"Merciless Armament"' },
+    { snippet: '"The Metalsmith\'s Gift"' },
+    { snippet: '"A Mother\'s Parting Gift"' },
     { snippet: '"The Oath"' },
+    { snippet: '"The Offering"' },
+    { snippet: '"The One With All"' },
+    { snippet: '"The Pack Leader"' },
+    { snippet: '"The Pact"' },
+    { snippet: '"The Penitent"' },
+    { snippet: '"The Poet"' },
+    { snippet: '"Pride Before the Fall"' },
+    { snippet: 'Prosperity' },
+    { snippet: '"The Queen"' },
+    { snippet: '"The Rabid Rhoa"' },
+    { snippet: '"Rain of Chaos"' },
+    { snippet: '"Rain Tempter"' },
+    { snippet: 'Rats' },
+    { snippet: '"The Risk"' },
+    { snippet: '"The Road to Power"' },
+    { snippet: '"The Scarred Meadow"' },
     { snippet: '"The Scavenger"' },
+    { snippet: '"The Scholar"' },
+    { snippet: '"Scholar of the Seas"' },
+    { snippet: '"The Sephirot"' },
+    { snippet: '"Shard of Fate"' },
+    { snippet: '"The Sigil"' },
+    { snippet: '"The Siren"' },
+    { snippet: '"The Soul"' },
+    { snippet: '"The Spoiled Prince"' },
+    { snippet: '"The Stormcaller"' },
+    { snippet: '"The Summoner"' },
+    { snippet: '"The Sun"' },
+    { snippet: '"The Surgeon"' },
+    { snippet: '"The Surveyor"' },
+    { snippet: '"The Survivalist"' },
+    { snippet: '"The Thaumaturgist"' },
+    { snippet: '"Three Faces in the Dark"' },
+    { snippet: '"The Throne"' },
+    { snippet: '"Thunderous Skies"' },
+    { snippet: '"Time-Lost Relic"' },
+    { snippet: '"The Tower"' },
+    { snippet: '"The Traitor"' },
+    { snippet: 'Tranquillity' },
+    { snippet: '"Treasure Hunter"' },
+    { snippet: '"The Trial"' },
+    { snippet: '"Turn the Other Cheek"' },
+    { snippet: '"The Twins"' },
+    { snippet: '"The Tyrant"' },
+    { snippet: '"The Union"' },
+    { snippet: '"The Valkyrie"' },
+    { snippet: '"The Vast"' },
+    { snippet: '"Vinia\'s Token"' },
+    { snippet: '"The Visionary"' },
     { snippet: '"The Void"' },
-    { snippet: '"Mawr Blaidd"' },
+    { snippet: '"Volatile Power"' },
+    { snippet: '"The Warden"' },
+    { snippet: '"The Warlord"' },
+    { snippet: '"The Watcher"' },
+    { snippet: '"Wealth and Power"' },
+    { snippet: '"The Web"' },
+    { snippet: '"The Wind"' },
+    { snippet: '"The Wolf"' },
+    { snippet: '"The Wolf\'s Shadow"' },
+    { snippet: '"The Wrath"' },
   ]

   skills: [


using atom.syntax.selectGrammar to check for a grammar with a scope name of source.litcoffee

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -21,13 +21,13 @@
   roaster text, options, (error, html) =>
     return callback(error) if error

-    defaultLanguage = if path.extname(filePath) is '.litcoffee'
-      'coffeescript'
+    scopeName = atom.syntax.selectGrammar(filePath, text)?.scopeName
+    defaultCodeLanguage = if scopeName is 'source.litcoffee' then 'coffeescript'
     else 'text'

     html = sanitize(html)
     html = resolveImagePaths(html, filePath)
-    html = tokenizeCodeBlocks(html,defaultLanguage)
+    html = tokenizeCodeBlocks(html,defaultCodeLanguage)
     callback(null, html.html().trim())

 exports.toText = (text, filePath, callback) ->


Run populateOptios before spawning a proc

--- lib/surveyor.coffee
+++ lib/surveyor.coffee
@@ -94,9 +94,11 @@
       if repoData.required?
         numProcs += repoData.required.length
         for slave in repoData.required
-          do (slave) ->
-            cavalry.spawn slave, repoData.opts, (err, res) ->
-              checkDone slave, err, res
+          do (slave) =>
+            @populateOptions slave, repoData.opts, (err, opts) ->
+              return checkDone slave, err, null if err?
+              cavalry.spawn slave, repoData.opts, (err, res) ->
+                checkDone slave, err, res
       else if repoData.delta > 0
         numProcs += repoData.delta
         while repoData.delta > 0


Don't check create view model

Don't check create view model

--- lib/template.coffee
+++ lib/template.coffee
@@ -40,7 +40,6 @@

 Blaze.Template.prototype.createViewModel = (context) ->
   template = this
-  ViewModel.check 'T#createViewModel', context, template
   initial = ViewModel.getInitialObject template.viewmodelInitial, context
   viewmodel = new ViewModel(initial)
   viewmodel


:art:

:art:

Signed-off-by: Nathan Sobo <2e8aa918660411855c6d44d5bb2da677aa033255@github.com>

--- src/marker.coffee
+++ src/marker.coffee
@@ -4,7 +4,7 @@

 module.exports =
 class Marker
-  constructor: (@id, @manager, range, @properties) ->
+  constructor: (@id, @store, range, @properties) ->
     @emitter = new Emitter
     @valid = true

@@ -17,23 +17,23 @@
     @invalidationStrategy = @properties.invalidate ? 'overlap'
     delete @properties.invalidate

-    @manager.setMarkerHasTail(@id, @tailed)
+    @store.setMarkerHasTail(@id, @tailed)
     @previousEventState = @getEventState(range)

   getRange: ->
-    @manager.getMarkerRange(@id)
+    @store.getMarkerRange(@id)

   getHeadPosition: ->
     if @reversed
-      @manager.getMarkerStartPosition(@id)
+      @store.getMarkerStartPosition(@id)
     else
-      @manager.getMarkerEndPosition(@id)
+      @store.getMarkerEndPosition(@id)

   getTailPosition: ->
     if @reversed
-      @manager.getMarkerEndPosition(@id)
+      @store.getMarkerEndPosition(@id)
     else
-      @manager.getMarkerStartPosition(@id)
+      @store.getMarkerStartPosition(@id)

   setRange: (range, properties) ->
     if properties?.reversed?
@@ -119,9 +119,9 @@
       changed = true

     unless newRange.isEqual(oldRange)
-      @manager.setMarkerRange(@id, newRange)
+      @store.setMarkerRange(@id, newRange)
     unless @tailed is wasTailed
-      @manager.setMarkerHasTail(@id, @tailed)
+      @store.setMarkerHasTail(@id, @tailed)
     @emitChangeEvent(newRange, textChanged, propertiesChanged)
     changed

@@ -162,13 +162,13 @@
   hasTail: -> @tailed

   destroy: ->
-    @manager.destroyMarker(@id)
+    @store.destroyMarker(@id)
     @emitter.emit("did-destroy")

   copy: (options) ->
     properties = clone(@properties)
     properties[key] = value for key, value of options
-    @manager.markRange(@getRange(), options)
+    @store.markRange(@getRange(), options)

   ###
   Section: Event Subscription


more production mongo tweaks

more production mongo tweaks

--- models/mongo.coffee
+++ models/mongo.coffee
@@ -11,8 +11,8 @@
     @server: new MongoServer @host, @port
     @db: new MongoDB @dbname, @server
     if @user?
-      @db.authenticate @user, @password, =>
-        @db.open -> # no callback
+      @db.open =>
+        @db.authenticate @user, @password, -> # no callback
     else @db.open -> # no callback

   parseUrl: (urlString)->


aaaaaaaaaaaaa

aaaaaaaaaaaaa

--- modules/eval.coffee
+++ modules/eval.coffee
@@ -28,7 +28,7 @@
       eval args
     @registerCommand 'sudo', { adminOnly: true, argSeparator: ' ' }, (msg, args)=>
       return if not msg.mentions[0]
-      name = msg.mentions[0].memberOf(msg.guild).nick or msg.mentions[0].memberOf(msg.guild).nick
+      name = msg.mentions[0].memberOf(msg.guild).nick or msg.mentions[0].memberOf(msg.guild).username
       @webHooks.getForChannel(msg.channel, true)
       .then (hook)=>
         hook.execSlack {


print value

print value

--- src/player.coffee
+++ src/player.coffee
@@ -27,6 +27,7 @@
     communityCards = gameState.community_cards
     hand = analyzeHand myCards, communityCards
     value = valueHand hand
+    console.log "VALUE #{value}"

     if commitment <= 20
       if seriousness > 2 and value < 0.5


fix uniform output

fix uniform output

--- src/plugin.coffee
+++ src/plugin.coffee
@@ -110,7 +110,10 @@
       """

     # FIXME pegjs should throw an exception if startRule is not defined
-    parser._parse = parser.parse
+    {SyntaxError, parse} = parser
+    parser = parse
+    parser._parse = parse
+    parser.SyntaxError = SyntaxError

     parser.parse = (input, options = {}) ->
       _.defaults options, {


Improve documentation

Improve documentation

--- client/import.iced
+++ client/import.iced
@@ -332,11 +332,12 @@
   formatLabel = (label) ->
     "``#{label.replace /^fig:/, ''}''"

-  ## Extract sections.
+  ## Extract sections = top-level threads.
   for filename, content of zip.files
     if filename[-4..] == '.tex'
       tex = content.asText()
-      ## Remove comments (to ignore \sections inside comments).
+      ## Remove comments (to ignore \sections inside comments),
+      ## and remove figures (already processed).
       tex = tex
       .replace /%.*$\n?/mg, ''
       .replace /\\begin\s*{(wrap)?figure}[^]*?\\end\s*{(wrap)?figure}\s*/g, ''


Sort parties by time

Sort parties by time

--- client/main.coffee
+++ client/main.coffee
@@ -34,7 +34,9 @@
 # Parties

 Template.parties.list = ->
-  Parties.find()
+  Parties.find {},
+    sort:
+      created: -1

 Template.parties.isLocal = ->
   Session.get 'isLocal'


fix of frequently used emojis

fix of frequently used emojis

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -137,9 +137,9 @@

       for emoji in frequently_used_emojis
         do (emoji) ->
-          $(".emoji-menu-content [data-emoji='" + emoji + "']").closest("li").clone().appendTo(ul)
+          $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)

-      $(".emoji-menu-content").prepend(ul).prepend($("<h4>").text("Frequently used"))
+      $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))

   setupSearch: ->
     $("input.emoji-search").keyup (ev) =>
@@ -149,7 +149,7 @@
       $("ul.emoji-search,h5.emoji-search").remove()

       if term
-        # Generate search result block
+        # Generate a search result block
         h5 = $("<h5>").text("Search results").addClass("emoji-search")
         found_emojis = @searchEmojis(term).show()
         ul = $("<ul>").addClass("emoji-search").append(found_emojis)


Uses new edge "source-target" attribute

Uses new edge "source-target" attribute

Fixes #151

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -94,7 +94,7 @@
                 return d.id is c.id or alchemy.edges.some (e) ->
                     return ((e.source.id is c.id and e.target.id is d.id) or
                             (e.source.id is d.id and e.target.id is c.id)) and
-                            d3.select(".edge[id*='#{d.id}']").classed("active"))
+                            d3.select(".edge[source-target*='#{d.id}']").classed("active"))

         if typeof alchemy.conf.nodeClick == 'function'
             alchemy.conf.nodeClick(c)


Set limit of body parser for json 500MB

Set limit of body parser for json 500MB

--- src/server.coffee
+++ src/server.coffee
@@ -31,9 +31,11 @@
     @app.set 'views', path.join(__dirname, '../views')
     @app.set 'view engine', 'jade'

-    @app.use(bodyParser.urlencoded extended: true)
-    @app.use(bodyParser.json(),
-      limit: 1024 * 1024 * 500)
+    @app.use bodyParser.urlencoded
+      extended: true
+    @app.use bodyParser.json
+      limit: 1024 * 1024 * 500
+
     @app.use require("connect-assets")()
     @app.use '/bower_components',
              express.static __dirname + '/../bower_components'


Increase responsiveness of timer from 30ms to 15ms

--- src/server.coffee
+++ src/server.coffee
@@ -69,7 +69,7 @@
           @buffer = ''
           @_write_pipe()
         )
-    ), 30
+    ), 15

   #
   # convert halo rgba string to a UART instruction


z: fix infinite dirty loop

z: fix infinite dirty loop

--- src/z.coffee
+++ src/z.coffee
@@ -138,7 +138,8 @@

       child.__disposables = _.map childrenStreams, (dirtyStream) ->
         dirtyStream.subscribe (isDirty) ->
-          child.__dirtySubject.onNext isDirty
+          if isDirty
+            child.__dirtySubject.onNext isDirty

       child.__dirtySubject.onNext false


bug fix: last line after list being removed on save

--- src/core/document.coffee
+++ src/core/document.coffee
@@ -81,10 +81,10 @@
             type: 'list',
             data: [line.getJSON()]
           }
-      else if prevLine == 'LI' and list
-        objects.push(list)
-        list = false
-      else
+      else
+        if prevLine == 'LI' and list
+          objects.push(list)
+          list = false
         objects.push(line.getJSON())
       prevLine = line.node.nodeName
       line = line.next


planned out how the heroku deployment would work

--- src/deploy/heroku.coffee
+++ src/deploy/heroku.coffee
@@ -5,5 +5,15 @@
     msg.reply "In order to deploy to heroku, a valid API token must be supplied"
   else
     console.log "I GOT HERE", organization, repo, branch
-    # use github.repo.getBranch to see if the branch exists
-    # if so, use the sha from the resp to deploy to heroku
+
+    # use github.getarchivelink
+      # GET /repos/:owner/:repo/:archive_format/:ref
+      # e.g. https://github.com/User/repo/tarball/master
+
+    # github.repos.getArchiveLink user, repo, ref=heads/branch, archive_format
+
+    # create app
+    # POST /apps -> name, region="eu", stack="cedar"
+
+    # Create build
+    # POST /apps/{app id or name}/builds -> source_blob_url, source_blob_version


Correct some small behavioral bugs with dragging and resizing.

--- src/grid/GridView.coffee
+++ src/grid/GridView.coffee
@@ -89,6 +89,17 @@

 ###*
+# Handles the drag-end event of a GridItemView.
+#
+# @function onDragEnd
+# @this stout-ui/grid/GridItemView#
+###
+onDragEnd = (e) ->
+  shifted = onDrag.call @, e
+  positionItems.call @parent, shifted
+
+
+###*
 # Handles a resize event of a GridItemView.
 #
 # @param {stout-core/event/Event} e - Resize event.
@@ -98,8 +109,8 @@
 ###
 onResize = (e) ->
   {width, height} = e.data
-  top  = parseInt(@root.style.top)
-  left = parseInt(@root.style.left)
+  top  = convertToPixels(@root.style.top, @parent.gridWidth)
+  left = convertToPixels(@root.style.left, @parent.gridWidth)

   [row, col] = @parent.toGridCoords(left, top)
   [height, width] = @parent.toGridCoords(width, height)
@@ -132,6 +143,25 @@
 ###
 isPercentage = (n) ->
   n.indexOf and n.indexOf('%') isnt -1
+
+
+###*
+# Converts the passed `v` param to pixels, if it is a percentage.
+#
+# @param {string|number} v - The value to convert to pixels.
+#
+# @param {number} max - The container size, or maximum value of-which the
+# percentage is a portion of.
+#
+# @returns {number} Pixel dimension.
+#
+# @function convertToPixels
+###
+convertToPixels = (v, max) ->
+  if isPercentage(v)
+    max * (parseFloat(v) / 100)
+  else
+    parseFloat(v)

 ###*
@@ -288,10 +318,14 @@
   ###
   _addItem: (itemView) ->
     {height, width, id} = itemView
-    [row, col] = @_packer.insert height, width, id
-
-    itemView.row = row
-    itemView.column = col
+
+    if not itemView.row or not itemView.column
+      [row, col] = @_packer.insert height, width, id
+      itemView.row = row
+      itemView.column = col
+    else
+      {row, column} = itemView
+      @_packer.insertAt height, width, row, column, id

     [top, left] = @fromGridCoords row, col
     [height, width] = @fromGridCoords height, width
@@ -299,12 +333,15 @@
     for prop, v of {top, left, height, width}
       itemView.root.style[prop] = v

+    itemView.resizeContainer = @root
+    itemView.dragContainer = @root
     itemView.parent = @
     itemView.parentEl = @root

     throttleOptions = {trailing: true, leading: false}
     itemView.on 'drag', throttle(onDrag, 300, throttleOptions)
     itemView.on 'resize', throttle(onResize, 100, throttleOptions)
+    itemView.on 'dragstop', onDragEnd
     itemView.on 'resizeend', onResizeEnd

     itemView.render()
@@ -322,4 +359,5 @@
   ###
   _removeItem: (itemView) ->
     @_packer.remove itemView.id
-    itemView.unrender()
+    if itemView.rendered then itemView.unrender()
+    setGridSize.call @


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


adjustments

adjustments

--- src/player.coffee
+++ src/player.coffee
@@ -28,7 +28,7 @@
     hand = analyzeHand myCards, communityCards
     value = valueHand hand

-    if commitment == 0
+    if commitment <= 20
       if seriousness > 2 and value < 0.5
         console.log 'too serious'
         return sendBet 0
@@ -36,15 +36,16 @@
         console.log 'too serious'
         return sendBet 0

+    if value >= 0.5
+      bet = if gameState.players[0]?.status == 'active'
+        700
+      else
+        200
+      bet = maxBet unless bet < maxBet
+      bet = minBet unless bet > minBet
+      return sendBet maxBet
+
     if communityCards.length == 0
-      if value >= 0.5
-        bet = if gameState.players[0]?.status == 'active'
-          700
-        else
-          200
-        bet = maxBet unless bet < maxBet
-        bet = minBet unless bet > minBet
-        return sendBet maxBet
       unless value < 0.1
         return sendBet minBet


do not overwrite `node-integration` option

If `node-integration` option pass to window.open, do not overwrite by current window's one

--- atom/renderer/lib/override.coffee
+++ atom/renderer/lib/override.coffee
@@ -61,7 +61,7 @@
   (options[name] = parseInt(options[name], 10) if options[name]?) for name in ints

   # Inherit the node-integration option of current window.
-  unless options['node-integration']
+  unless options['node-integration']?
     for arg in process.argv when arg.indexOf('--node-integration=') is 0
       options['node-integration'] = arg.substr(-4) is 'true'
       break


upload to rerun Travis CI

upload to rerun Travis CI


render: allow for components as parts of full page tree

--- src/render.coffee
+++ src/render.coffee
@@ -6,13 +6,13 @@
 flattenTree = require './flatten_tree'

 parseFullTree = (tree) ->
-  unless tree?.tagName is 'HTML'
+  unless tree?.tagName is 'HTML' and tree.children.length is 2
     throw new Error 'Invalid HTML base element'

-  $head = tree.children[0]
-  $body = tree.children[1]
-  $title = $head?.children[0]
-  appTree = $body?.children[0]
+  $head = flattenTree tree.children[0]
+  $body = flattenTree tree.children[1]
+  $title = flattenTree $head.children[0]
+  appTree = flattenTree $body.children[0]

   unless $head?.tagName is 'HEAD' and $title?.tagName is 'TITLE'
     throw new Error 'Invalid HEAD base element'


Fix nitpicky comments

Fix nitpicky comments

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -386,7 +386,7 @@
         slack_api_token: @slack_api_token()
         slack_notify_prefs: @slack_notify_prefs()
         hall_room_api_token: @hall_room_api_token()
-        shall_notify_prefs: @hall_notify_prefs()
+        hall_notify_prefs: @hall_notify_prefs()
         campfire_room: @campfire_room()
         campfire_token: @campfire_token()
         campfire_subdomain: @campfire_subdomain()


Added one optional argument named unit to setWidth method of KDView to set width with percentage values.

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -336,10 +336,10 @@
   getWidth:()->
     w = @getDomElement().width()

-  setWidth:(w)->
-    @getElement().style.width = "#{w}px"
+  setWidth:(w, unit = "px")->
+    @getElement().style.width = "#{w}#{unit}"
     # @getDomElement().width w
-    @emit "ViewResized", newWidth : w
+    @emit "ViewResized", {newWidth : w, unit}

   getHeight:()->
     # @getDomElement()[0].clientHeight


bugfix : app init was broken

bugfix : app init was broken

--- client/app/libs/panel_router.coffee
+++ client/app/libs/panel_router.coffee
@@ -64,6 +64,9 @@

             [firstPanelInfo, secondPanelInfo] = @_processSubRouting name, args

+            @previous = @current
+            @current = firstPanel: firstPanelInfo, secondPanel: secondPanelInfo
+
             firstAction = @fluxActionFactory firstPanelInfo
             if firstAction?
                 firstAction firstPanelInfo, 'first'
@@ -72,8 +75,6 @@
             if secondAction?
                 secondAction secondPanelInfo, 'second'

-            @previous = @current
-            @current = firstPanel: firstPanelInfo, secondPanel: secondPanelInfo
             @trigger 'fluxRoute', @current


Added findMistake link to problem

Added findMistake link to problem

--- client/components/SubmitList.coffee
+++ client/components/SubmitList.coffee
@@ -7,6 +7,8 @@
 import Button from 'react-bootstrap/lib/Button'
 import Tabs from 'react-bootstrap/lib/Tabs'
 import Tab from 'react-bootstrap/lib/Tab'
+
+import { Link } from 'react-router-dom'

 import ConnectedComponent from '../lib/ConnectedComponent'
 import withMyUser from '../lib/withMyUser'
@@ -64,6 +66,13 @@
         if blockedByTestSystem
             return blockedByTestSystem

+            {if @props.noBestSubmits || not @props.findMistake?.pagesCount
+                null
+            else if @props.result?.solved != 0 || @props.me.admin
+
<Link to="/findMistakeProblem/#{@props.material._id}">Найди ошибку</Link>

+            else if @props.result?.solved == 0
+
Найди ошибку <FontAwesome name="question-circle-o"/>

+            }
             {if @props.noBestSubmits
                 null
             else if @props.bestSubmits?.length
@@ -104,6 +113,8 @@
             result = {}
             if not props?.noBestSubmits
                 result.bestSubmits = "bestSubmits/#{props.material._id}"
+                if props.myUser?._id
+                    result.findMistake = "findMistakeProblemPages/#{props.myUser._id}/#{props.material._id}"
             return result
         return {}


Finsihed brackets

Finsihed brackets

--- client/src/Screen/GoalScreen.coffee
+++ client/src/Screen/GoalScreen.coffee
@@ -66,6 +66,7 @@
         if(@leftBracketIndex == 0 && @numberInGoal > 1)
           html = "

"
           @numberDots++
+          @leftBracketIndex++
           $(html).prependTo("#added-goal").bind 'click', (event) ->
             thisReference.dotListener(this)
     else if(@bracketClicks == 1)
@@ -100,7 +101,7 @@
       # If it's the first dice we just added, then we need to add the leftmost brackets dot
       if(@numberInGoal == 0)
         @numberDots++
-        html = "

"
+        html = "

"
         $(html).appendTo("#added-goal").bind 'click', (event) ->
           thisReference.dotListener(this)

@@ -123,31 +124,59 @@
   removeFromGoal: (index) ->
     # Remove the diceface to the dom and add a click listener that adds it when its clicked
     @numberInGoal--
-    @numberDots--
     diceFace = DiceFace.toHtml(@resources[index])
     html = "
#{diceFace}
"
     thisReference = this
+    $('#added-goal li.dice[data-index='+index+']').remove()
+    @cleanUpBrackets()
+    $(html).appendTo("#notadded-goal").bind 'click', (event) ->
+      thisReference.addToGoal($(this).data('index'));

-    previousDot = $('li.dice[data-index='+index+']').prev()
+
+  cleanUpBrackets: () ->
+    thisReference = this
+    for d in $('#added-goal li')
+      twoDotsCase = ($(d).prev().attr('data-bracket') is 'none') and ($(d).attr('data-bracket') is 'none')
+      leftBrkDotCase = (($(d).prev().attr('data-bracket') is 'left') and ($(d).attr('data-bracket') is 'none'))
+      dotRightBrkCase = (($(d).prev().attr('data-bracket') is 'none') and ($(d).attr('data-bracket') is 'right'))
+      leftBkrRightBrkCase = (($(d).prev().attr('data-bracket') is 'left') and ($(d).attr('data-bracket') is 'right'))
+
+      if (twoDotsCase || leftBrkDotCase || dotRightBrkCase || leftBkrRightBrkCase)
+        # Adjust the left dot accordingly (since we always delete the right ... see below)
+        if(leftBkrRightBrkCase)
+          $(d).prev().attr('data-bracket', 'none')
+        else if(leftBrkDotCase)
+          $(d).prev().attr('data-bracket', 'left')
+        else if(dotRightBrkCase)
+          $(d).prev().attr('data-bracket', 'right')
+
+        # Special case for when there is just one dot left
+        if(@numberDots == 2)
+          $(d).prev().remove()
+          @numberDots--
+
+        # Always remove the right dot/bracket
+        @numberDots--
+        $(d).remove()
+
+        # Now try and clean up again
+        @cleanUpBrackets()
+
+
+
+
+    ### previousDot = $('li.dice[data-index='+index+']').prev()
     nextDot     = $('li.dice[data-index='+index+']').next()
-    if($(previousDot).data('bracket') is 'left' and $(nextDot).data('bracket') is 'right')
+    $(nextDot).remove()###
+    # Remove the dice from the goal
+    ###if($(previousDot).data('bracket') is 'left' and $(nextDot).data('bracket') is 'right')
       $(previousDot).attr('data-bracket', 'none')
       $(previousDot).remove()
       @numberDots--
     else
-      $(previousDot).attr('data-bracket', nextDot.data('bracket'))
-
-    $(nextDot).remove()
-    # Remove the dice from the goal
-    $('#added-goal li.dice[data-index='+index+']').remove()
-
-    if(@numberInGoal == 0)
+      $(previousDot).attr('data-bracket', nextDot.data('bracket'))###
+    ###if(@numberInGoal == 0)
       $('#added-goal li.dot.first').remove()
-      @numberDots--
-
-    $(html).appendTo("#notadded-goal").bind 'click', (event) ->
-      thisReference.addToGoal($(this).data('index'));
-
-
+      @numberDots--###


fix for issue #10

fix for issue #10

--- src/server.coffee
+++ src/server.coffee
@@ -7,7 +7,7 @@
 class HoardD extends EventEmitter

   constructor: (@conf, @cli) ->
-    @sPath = @conf.scriptPath
+    @sPath = Path.resolve @conf.scriptPath
     @fqdn = @conf.fqdn.split('.').join('_')
     @namespace = @conf.namespace or 'hoard'
     @samplesRun = 0


Fix minor bug in URI/path comparison.

Fix minor bug in URI/path comparison.

If `uri` is `/subdir/` and the `item.url` is `/subdir/index.html` then
`index.html` needs to be added to `uri` and not `item.url`.

--- src/server.coffee
+++ src/server.coffee
@@ -1,4 +1,3 @@
-
 util = require 'util'
 async = require 'async'
 fs = require 'fs'
@@ -43,7 +42,7 @@
         # render if uri matches
         {contents, templates} = result
         async.detect ContentTree.flatten(contents), (item, callback) ->
-          callback (uri is item.url or (item.url[item.url.length - 1] is '/' and uri is (item.url + 'index.html')))
+          callback (uri is item.url or (uri[uri.length - 1] is '/' and (uri + 'index.html') is item.url))
         , (result) ->
           if result
             result.render options.locals, contents, templates, (error, res) ->


Fix background of transparent photos

Fix background of transparent photos

--- client/app/views/photo.coffee
+++ client/app/views/photo.coffee
@@ -32,9 +32,10 @@
         helpers.rotate @model.get('orientation'), @image
         @link.addClass 'server' unless @model.isNew()
         if @image.get(0).complete
-            @onImageLoaded
+            @onImageLoaded()
         else
-            @image.on 'load', @onImageLoaded
+            @image.on 'load', =>
+                @onImageLoaded()

     setProgress: (percent) ->
         @progressbar.css 'width', percent + '%'
@@ -88,4 +89,4 @@
                     @remove()

     onImageLoaded: ->
-        @.classList.add 'loaded'
+        @image.addClass 'loaded'


Don't attempt to reload unfound plugins

Don't attempt to reload unfound plugins

--- client/source/mediator.coffee
+++ client/source/mediator.coffee
@@ -3,6 +3,9 @@
 module.exports = mediator = Chaplin.mediator

 mediator.loadPlugin = (name, force=no) ->
+
+  return if @plugins[name] is false
+
   promise = new $.Deferred

   return promise.resolve() if @plugins?[name] and !force
@@ -11,5 +14,8 @@
     load: "/#{@options.adminSegment}/plugins/#{name}.js"
     complete: ->
       promise.resolve()
+    fail: =>
+      @plugins[name] = false
+      promise.reject()

   promise


Rest of Wave X

Rest of Wave X

-Added and translated the Expansion Packs and Ship names for Upsilon, Quadjumper and Sabine's TIE.
-Added and translated all the generic pilots for the Upsilon and Quadjumper.
-Translated all the new cards of the three expansions, adding the "ship" tag wherever it was needed due changing the ship's name.
-Translated Commander Kenkirk rank, something it was never done.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -81,6 +81,9 @@
         "ARC-170 Expansion Pack": "Pack de Expansión ARC-170"
         "U-Wing Expansion Pack": "Pack de Expansión Ala-U"
         "TIE Striker Expansion Pack": "Pack de Expansión Fustigador TIE"
+        "Upsilon-class Shuttle Expansion Pack": "Pack de Expansión Lanzadera clase Ípsilon"
+        "Sabine's TIE Fighter Expansion Pack": "Pack de Expansión Caza TIE de Sabine"
+        "Quadjumper Expansion Pack": "Pack de Expansión Saltador Quad"
     ui:
         shipSelectorPlaceholder: "Selecciona una nave"
         pilotSelectorPlaceholder: "Selecciona un piloto"
@@ -193,6 +196,7 @@
     exportObj.renameShip 'TIE/fo Fighter', 'Caza TIE/fo'
     exportObj.renameShip 'TIE/sf Fighter', 'Caza TIE/sf'
     exportObj.renameShip 'TIE Striker', 'Fustigador TIE'
+    exportObj.renameShip 'Upsilon-class Shuttle', 'Lanzadera clase Ípsilon'
     exportObj.renameShip 'A-Wing', 'Ala-A'
     exportObj.renameShip 'B-Wing', 'Ala-B'
     exportObj.renameShip 'E-Wing', 'Ala-E'
@@ -214,6 +218,7 @@
     exportObj.renameShip 'JumpMaster 5000', 'Saltador Maestro 5000'
     exportObj.renameShip 'Protectorate Starfighter', 'Caza Estelar del Protectorado'
     exportObj.renameShip 'Lancer-class Pursuit Craft', 'Nave de persecución clase Lancero'
+    exportObj.renameShip 'Quadjumper', 'Saltador Quad'

     pilot_translations =
         "Wedge Antilles":
@@ -517,6 +522,7 @@
             ship: "VT-49 Diezmador"
         "Commander Kenkirk":
             ship: "VT-49 Diezmador"
+            ship: "Comandante Kenkirk"
             text: """Si no te quedan escudos y tienes asignada al menos 1 carta de Daño, tu Agilidad aumenta en 1."""
         "Captain Oicunn":
             name: "Capitán Oicunn"
@@ -884,14 +890,16 @@
             ship: "Nave de persecución clase Lancero"
         'Sabine Wren (TIE Fighter)':
             ship: "Caza TIE"
-            text: '''Immediately before you reveal your maneuver, you may perform a free boost or barrel roll action.'''
+            text: '''Inmediatametne antes de revelar tu maniobra, puedes realizar una acción gratuita de impulso o tonel volado.'''
         '"Zeb" Orrelios (TIE Fighter)':
             ship: "Caza TIE"
-            text: '''When defending, you may cancel %CRIT% results before %HIT% results.'''
+            text: '''Cuando te defiendas, puedes anular resultados %CRIT% antes de anular resultados %HIT%.'''
         'Kylo Ren':
-            text: '''The first time you are hit by an attack each round, deal the "I'll Show You the Dark Side" Condition card to the attacker.'''
+            ship: "Lanzadera clase Ípsilon"
+            text: '''La primera vez que seas impactado por un ataque en cada ronda, asigna la carta de Estad "Yo te mostraré el Lado Oscuro" al atacante.'''
         'Unkar Plutt':
-            text: '''At the end of the Activation phase, you must assign a tractor beam token to each ship you are touching.'''
+            ship: "Saltador Quad"
+            text: '''Al final de la fase de Activación, debes asignar una ficha de Campo de tracción a toda nave con la que estés en contacto.'''
         'Cassian Andor':
             ship: "Ala-U"
             text: '''Al comienzo de la fase de Activación, puedes quitar 1 ficha de Tensión de 1 otra nave aliada que tengas a alcance 1-2.'''
@@ -924,18 +932,24 @@
             text: '''Cuando ataques o te defiendas, puedes volver a tirar 1 de tus dados por cada otra nave aliada que tengas a Alcance 1.'''
         'Ahsoka Tano':
             ship: "Caza TIE"
-            text: '''At the start of the Combat phase, you may spend 1 focus token to choose a friendly ship at Range 1.  It may perform 1 free action.'''
+            text: '''Al comienzo de la fase de Combate, puedes gastar 1 ficha de Concentración para elegir una nave aliada que tengas a alcance 1. Esa nave puede realizar 1 acción gratuita.'''
         'Captain Rex':
             ship: "Caza TIE"
-            text: '''After you perform an attack, assign the "Suppressive Fire" Condition card to the defender.'''
+            name: "Capitán Rex"
+            text: '''Después de que efectúes un ataque, asigna la carta de Estado "Fuego de supresión" al defensor.'''
         'Major Stridan':
-            text: '''For the purpose of your actions and Upgrade cards, you may treat friendly ships at Range 2-3 as being at Range 1.'''
+            ship: "Lanzadera clase Ípsilon"
+            text: '''A efectos de tus acciones y cartas de Mejora, puedes considerar las naves aliadas que tengas a alcance 2-3 como si estuvieran a alcance 1.'''
         'Lieutenant Dormitz':
-            text: '''During setup, friendly ships may placed anywhere in the play area at Range 1-2 of you.'''
+            ship: "Lanzadera clase Ípsilon"
+            text: '''Durante la preparación de la partida, las naves aliadas pueden ser colocadas en cualquier lugar de la zona de juego que esté situado a alcance 1-2 de ti.'''
         'Constable Zuvio':
-            text: '''When you reveal a reverse maneuver, you may drop a bomb using your front guides (including a bomb with the "Action:" header).'''
+            ship: "Saltador Quad"
+            name: "Alguacil Zuvio"
+            text: '''Cuando reveles una maniobra de retroceso, puedes soltar una bomba usando los salientes de la parte frontal de tu peana (incluso una bomba con el encabezado "Acción:").'''
         'Sarco Plank':
-            text: '''When defending, instead of using your agility value, you may roll a number of defense dice equal to the speed of the maneuver you executed this round.'''
+            ship: "Saltador Quad"
+            text: '''Cuando te defiendas, en vez de usar tu valor de Agilidad, puedes tirar tantos dados de defensa como la velocidad de la maniobra que has ejecutado en esta ronda.'''
         "Blue Squadron Pathfinder":
             name: "Infiltrador del Escuadrón Azul"
             ship: "Ala-U"
@@ -948,6 +962,12 @@
         "Imperial Trainee":
             name: "Cadete Imperial"
             ship: "Fustigador TIE"
+        "Starkiller Base Pilot":
+            ship: "Lanzadera clase Ípsilon"
+            name: "Piloto de la base Starkiller"
+        "Jakku Gunrunner":
+            ship: "Saltador Quad"
+            name: "Traficante de armas de  Jakku"

     upgrade_translations =
         "Ion Cannon Turret":
@@ -1494,11 +1514,12 @@
             text: '''Acción: Elige una nave enemiga bajo tensión que tengas a alcance 1-2 y tira 1 dado de ataque. Si sacas %HIT% o %CRIT%, quítale 1 ficha de Tensión e inflíngele 1 carta de Daño boca abajo.'''
         # Wave X
         'Kylo Ren':
-            text: '''%IMPERIALONLY%%LINEBREAK%Action: Assign the "I'll Show You the Dark Side" Condition card to an enemy ship at Range 1-3.'''
+            text: '''%IMPERIALONLY%%LINEBREAK%Acción: Asigna la carta de Estado "Yo re mostraré el Lado Oscuro" a una nave enemiga que tengas a alcance 1-3.'''
         'Unkar Plutt':
-            text: '''%SCUMONLY%%LINEBREAK%After executing a maneuver that causes you to overlap an enemy ship, you may suffer 1 damage to perform 1 free action.'''
+            text: '''%SCUMONLY%%LINEBREAK%Después de que ejecutes una maniobra que te haga solaparte con una nave enemiga, puedes sufrir 1 de daño para realizar 1 acción gratuita.'''
         'A Score to Settle':
-            text: '''During setup, before the "Place Forces" step, choose 1 enemy ship and deal the "A Debt to Pay" Condition card to it.%LINEBREAK%Wehn attacking a ship that has the "A Debt to Pay" Condition card, you may change 1 %FOCUS% result to a %CRIT% result.'''
+            name: "Una cuenta pendiente"
+            text: '''Durante la preparación de la partida, antes del paso "Desplegar fuerzas", elige 1 nave enemiga y asígnale la carta de Estado "Una deuda por saldar"%LINEBREAK%Cuando ataques a una nave que tiene asignada la carta de Estado "Una deuda por saldar", puedes cambair 1 resultado %FOCUS% por un resultado %CRIT%.'''
         'Jyn Erso':
             text: '''%REBELONLY%%LINEBREAK%Acción: Elige 1 nave aliada que tengas a alcance 1-2. Asigna 1 ficha de Concentración a esa nave por cada nave enemiga que tengas dentro de tu arco de fuego a alcance 1-3. No puedes asignar más de 3 fichas de esta forma.'''
         'Cassian Andor':
@@ -1522,17 +1543,22 @@
         'M9-G8':
             text: '''%REBELONLY%%LINEBREAK%Cuando una nave que tengas fijada como blanco esté atacando, puedes elegir 1 dado de ataque. El atacante debe volver a tirar ese dado.%LINEBREAK%Puedes fijar como blanco otras naves aliadas.'''
         'EMP Device':
-            text: '''During the Combat phase, instead of performing any attacks, you may discard this card to assign 2 ion tokens to each ship at Range 1.'''
+            name: "Dispositivo de pulso electromagnético"
+            text: '''Durante la fase de Combate, en vez de efecturas ningún ataque, puedes descartar esta carta para asignar 2 fichas de Iones a toda nave que tengas a alcance 1.'''
         'Captain Rex':
-            text: '''%REBELONLY%%LINEBREAK%After you perform an attack that does not hit, you may assign 1 focus token to your ship.'''
+            name: "Capitán Rex"
+            text: '''%REBELONLY%%LINEBREAK%Después de que efectúes un ataque no impacte, puedes asignar 1 ficha de Concentración a tu nave.'''
         'General Hux':
-            text: '''%IMPERIALONLY%%LINEBREAK%Action: Choose up to 3 friendly ships at Range 1-2.  Assign 1 focus token to each and assign the "Fanatical Devotion" Condition card to 1 of them.  Then receive 1 stress token.'''
+            text: '''%IMPERIALONLY%%LINEBREAK%Acción: Elige hasta 3 naves aliadas que tengas a alcance 1-2. Asigna 1 ficha de Concentración a cada una de esas naves y asigna la carta de Estado "Lealtad fanática" a 1 de ellas. Luego recibes 1 ficha de Tensión.'''
         'Operations Specialist':
-            text: '''%LIMITED%%LINEBREAK%After a friendly ship at Range 1-2 performs an attack that does not hit, you may assign 1 focus token to a friendly ship at Range 1-3 of the attacker.'''
+            name: "Especialista en operaciones"
+            text: '''%LIMITED%%LINEBREAK%Después de que una nave aliada que tengas a alcance 1-2 efectúe un ataque que no impacte, puedes asignar 1 ficha de Concentración a una nave aliada situada a alcance 1-3 del atacante.'''
         'Targeting Synchronizer':
-            text: '''When a friendly ship at Range 1-2 is attacking a ship you have locked, the friendly ship treats the "Attack (target lock): header as "Attack:."  If a game effect instructs you to spend a target lock, it may spend your target lock instead.'''
+            name: "Sincronizador de disparos"
+            text: '''Cuando una nave aliada que tengas a alcance 1-2 ataque a una nave que tienes fijada como blanco, esa nave aliada considera el encabezado "Ataque (Blanco fijado): como si fuera "Ataque:." Si un efecto de juego indica a esa nave que gaste una ficha de Blanco fijada, en vez de eso puede gastar tu ficha de Blanco fijado.'''
         'Hyperwave Comm Scanner':
-            text: '''At the start of the "Place Forces" step, you may choose to treat your pilot skill value as "0," "6," or "12" until the end of the step.%LINEBREAK%During setup, after another friendly ship is placed at Range 1-2, you may assign 1 focus or evade token to it.'''
+            name: "Escáner de frecuencias hiperlumínicas"
+            text: '''Al comienzo del paso "Desplegar fuerzas", puedes elegir considerar la Habilidad de tu piloto como si fuera 0, 6 o 12 hasta el final del paso. Durante la preparación de la partida, después de que otra nave aliada sea colocada a alcance 1-2 de ti, puedes asignar 1 ficha de Concentración o Evasión a esa nave.'''
         'Trick Shot':
             name: "Disparo inverosímil"
             text: '''Cuando ataques, si el ataque se considera obstruido, puedes tirar 1 dado de ataque adicional.'''
@@ -1540,7 +1566,8 @@
             name: "Copiloto extraordinario"
             text: '''Cuando ataques con un armamento princnipal, el defensor debe gastar 1 ficha de Concentración si le es posible.%LINEBREAK%Cuando te defiendas, el atacante debe gastar 1 ficha de Concentración si le es posible.'''
         '''Scavenger Crane''':
-            text: '''After a ship at Range 1-2 is destroyed, you may choose a discarded %TORPEDO%, %MISSILE%, %BOMB%, %CANNON%, %TURRET%, or Modification Upgrade card that was equipped to your ship and flip it faceup.  Then roll 1 attack die.  On a blank result, discard Scavenger Crane.'''
+            name: "Grúa de salvamento"
+            text: '''Después de que una nave que tengas a alcance 1-2 sea destruida, puedes elegir una carta de Mejora %TORPEDO%, %MISSILE%, %BOMB%, %CANNON%, %TURRET%, o Modificación que estuviera equipada en tu nave y ponerla boca arriba. Luego tira 1 dado de ataque. Si sacas una cara vacía, descarta tu "Grúa de salvamento".'''
         'Bodhi Rook':
             text: '''%REBELONLY%%LINEBREAK%Cuando fijes un blanco, puedes fijarlo sobre una nave enemiga que esté situada a alcance 1-3 de cualquier nave aliada.'''
         'Baze Malbus':
@@ -1557,7 +1584,7 @@
             name: "Maestría"
             text: '''Cuando ataques, si no estás bajo tensión, puedes cambair todos tus resultados %FOCUS% por resultados %HIT%.'''
         'BoShek':
-            text: '''When a ship you are touching activates, you may look at its chosen maneuver.  If you do, its owner must rotate the dial to an adjacent maneuver.  The ship can reveal and execute that maneuver even while stressed.'''
+            text: '''Cuando una nave con la que estás en contacto se activa, puedes mirar la maniobra que ha elegido. Si lo haces, el jugador que controla esa nave debe elegir en el selector una maniobra adyacente. La nave puede revelar y efectuar esa maniobra incluso aunque esté bajo tensión.'''
         # C-ROC
         'Heavy Laser Turret':
             text: '''C-ROC Cruiser only.%LINEBREAK%Attack (energy): Spend 2 energy from this card to perform this attack against 1 ship (even a ship outside of your firing arc).'''
@@ -1653,9 +1680,12 @@
             text: '''Sólo Nave de persecución clase Lancero.%LINEBREAK%Al final de la fase de Combate, si en esta ronda ejecutaste una maniobra de velocidad 3, 4 o 5, puedes reorientar tu arco de fuego móvil.'''
         'Captured TIE':
             ship: "Caza TIE"
-            text: '''TIE Fighter only.%REBELONLY%%LINEBREAK%Enemy ships with a pilot skill value lower than yours cannot declare you as the target of an attack.  After you perform an attack or when you are the only remaining friendly ship, discard this card.'''
+            name: "TIE capturado"
+            text: '''Sólo Caza TIE.%REBELONLY%%LINEBREAK%Las naves enemigas cuyos pilotos tengan una Habilidad más baja que la tuya no pueden declararte como blanco de un ataque. Después de que efectúes un ataque o cuando seas la única nave aliada que queda en juego, descarta esta carta.'''
         'Spacetug Tractor Array':
-            text: '''Quadjumper only.%LINEBREAK%Action: Choose a ship inside your firing arc at Range 1 and assign a tractor beam token to it.  If it is a friendly ship, resolve the effect of the tractor beam token as though it were an enemy ship.'''
+            name: "Campos de tracción de remolque"
+            ship: "Saltador Quad"
+            text: '''Sólo Saltador Quad.%LINEBREAK%Acción: Elige una nave que tengas dentro de tu arco de fuego a distancia 1 y asígnale una ficha de Campo de tracción. Si es una nave aliada, resuelve el efecto de la ficha de Campo de tracción como si se tratara de una nave enemiga.'''
         'Lightweight Frame':
             name: "Fuselaje ultraligero"
             text: '''Sólo TIE.%LINEBREAK%Cuando te defiendas, tras tirar los dados de defensa, si hay más dados de ataque que dados de defensa, tira 1 dado de defensa adicional.%LINEBREAK%Esta mejora no puede equiparse en naves con puntuación de Agilidad 3 o superior.'''
@@ -1811,9 +1841,12 @@
         # Wave X
         '''Sabine's Masterpiece''':
             ship: "Caza TIE"
-            text: '''TIE Fighter only.%REBELONLY%%LINEBREAK%Your upgrade bar gains the %CREW% and %ILLICIT% upgrade icons.'''
+            name: "Obra maestra de Sabine"
+            text: '''Sólo Caza TIE.%REBELONLY%%LINEBREAK%Tu barra de mejoras gana los iconos %CREW% y %ILLICIT%.'''
         '''Kylo Ren's Shuttle''':
-            text: '''Upsilon-class Shuttle only.%LINEBREAK%At the end of the Combat phase, choose an unstressed enemy ship at Range 1-2.  Its owner must assign a stress token to it or assign a stress token to another ship at Range 1-2 of you that that player controls.'''
+            ship: "Lanzadera clase Ípsilon"
+            name: "Lanzadera de Kylo Ren"
+            text: '''Sólo Lanzadera clase Ípsilon.%LINEBREAK%Al final de la fase de Combate, elige una nave enemiga que tengas a alcance 1-2 y no esté bajo tensión. El jugador que la controla debe asignarle una ficha de Tensión o asignar una ficha de Tensión a otra d esus naves que esté situada a alcance 1-2 de ti.'''
         '''Pivot Wing''':
             name: "Ala pivotante"
             ship: "Ala-U"
@@ -1830,12 +1863,16 @@

     condition_translations =
         '''I'll Show You the Dark Side''':
-            text: '''When this card is assigned, if it is not already in play, the player who dealt it searches the Damage deck for 1 Damage card with the Pilot trait and may place it faceup on this card. Then shuffle the damage deck.%LINEBREAK%When you suffer critical damage from an attack, you are instead dealt the chosen faceup Damage card.%LINEBREAK%When there is no Damage card on this card, remove it.'''
+            name: "Yo te mostraré el Lado Oscuro"
+            text: '''Cuando esta carta es asignada, si no está ya en juego, el jugador que la ha asignado busca en el mazo de Daño 1 carta de Daño que tenga el atributo Piloto y puede colocarla boca arriba sobre esta carta de Estado. Luego vuelve a barajar el mazo de Daño.%LINEBREAKCuando sufras daño crítico durante un ataque, en vez de eso recibes la carta de Daño boca arriba que has elegido.%LINEBREAK%Cuando no haya ninguna carta de Daño sobre esta carta de Estado, retírala.'''
         'Suppressive Fire':
-            text: '''When attacking a ship other than "Captain Rex," roll 1 fewer attack die.%LINEBREAK% When you declare an attack targeting "Captain Rex" or when "Captain Rex" is destroyed, remove this card.%LINEBREAK%At the end of the Combat phase, if "Captain Rex" did not perform an attack this phase, remove this card.'''
+            name: "Fuego de supresión"
+            text: '''Cuando ataques a una nave que no sea el "Capitán Rex", tira 1 dado de ataque menos.%LINEBREAK% Cuando declares un ataque que tenga como blanco al "Capitán Rex" o cuando el "Capitán Rex" sea destruido, retira esta carta.%LINEBREAK%Al final de la fase de Combate, si el "Capitán Rex" no ha efectuado un ataque en esta fase, retira esta carta.'''
         'Fanatical Devotion':
-            text: '''When defending, you cannot spend focus tokens.%LINEBREAK%When attacking, if you spend a focus token to change all %FOCUS% results to %HIT% results, set aside the first %FOCUS% result that you change. The set-aside %HIT% result cannot be canceled by defense dice, but the defender may cancel %CRIT% results before it.%LINEBREAK%During the End phase, remove this card.'''
+            name: "Lealtad Fanática"
+            text: '''Cuando te defiendas, no pudes gastar fichas de Concentración.%LINEBREAK%Cuando ataques, si gastas una ficha de Concentración parar cambiar todos los resultados %FOCUS% por resultados %HIT%, deja aparte el primer resultado %FOCUS% que has cambiado. El resultado %HIT% que has dejado aparte no puede ser anulado por dados de defensa, pero el defensor puede anular resultados %CRIT% antes que él.%LINEBREAK%Durante la fase Final, retira esta carta.'''
         'A Debt to Pay':
-            text: '''When attacking a ship that has the "A Score to Settle" Upgrade card equipped, you may change 1 %FOCUS% result to a %CRIT% result.'''
+            name: "Una deuda por saldar"
+            text: '''Cuando ataques a una nave que tiene la carta de Mejora "Una cuenta pendiente", puedes cambiar 1 resultado %FOCUS% por un resultado %CRIT%.'''

     exportObj.setupCardData basic_cards, pilot_translations, upgrade_translations, modification_translations, title_translations, condition_translations


OX-55 Bug fix, repair node clicking on skeleton view

--- app/assets/javascripts/controller.coffee
+++ app/assets/javascripts/controller.coffee
@@ -277,12 +277,12 @@
       # intersects[0].object.material.color.setHex(Math.random() * 0xffffff)
       vertex = intersects[0].object.geometry.vertices[intersects[0].vertex]
       # set the active Node to the one that has the ID stored in the vertex
-      @model.Route.setActiveNode(vertex.id)
+      @model.Route.setActiveNode(vertex.nodeId)
       @flycam.setGlobalPos(@model.Route.getActiveNodePos())
       @gui.updateNodeAndTreeIds()
       @sceneController.skeleton.setActiveNode()

-      #@gui.setActiveNode(vertex.id)
+      #@gui.setActiveNode(vertex.nodeId)
       console.log intersects
       # jump to the nodes position
       #@flycam.setGlobalPos [vertex.x, vertex.y, vertex.z]


OX-613 fixed keyboard callback triggering while an input field is focussed

--- app/assets/javascripts/libs/input.coffee
+++ app/assets/javascripts/libs/input.coffee
@@ -83,11 +83,9 @@
         # When control key is pressed, everything is ignored, because
         # if there is any browser action attached to this (as with Ctrl + S)
         # KeyboardJS does not receive the up event.
-
-        if not @keyCallbackMap[key]
-          callback(true)

         unless @keyCallbackMap[key]? or $(":focus").length
+          callback(true)
           @keyPressedCount++
           callback._delayed    = true
           @keyCallbackMap[key] = callback


Adjust Rotation for WWT

Adjust Rotation for WWT

--- app/classifier/world_wide_telescope.cjsx
+++ app/classifier/world_wide_telescope.cjsx
@@ -135,8 +135,8 @@
     "http://imgproc.zooniverse.org/crop/#{@starChart.width}/#{@starChart.height}/#{@starChart.x}/#{@starChart.y}?u=#{url}"

   computeRotation: ->
-    if @starChart.xAxis == Axis.RA || @starChart.xAxis == Axis.GLAT
-    then 0
+    if @starChart.xAxis.unit == Axis.RA || @starChart.xAxis.unit == Axis.GLAT
+    then 180
     else 90

   computeName: ->
@@ -259,11 +259,11 @@
         plates.push(new Plate(chart, subjImage, @props.user_name))

-
View Your Classification(s) in the WorldWide Telescope!

       {plates.map (plate, idx) ->

+
View Your Classification in the WorldWide Telescope!

-        World Wide Telescope
+        World Wide Telescope

       }


fix: Prevent making another estimate request after chart.data has estimated series

--- app/static/script/directive/chart.coffee
+++ app/static/script/directive/chart.coffee
@@ -183,15 +183,16 @@
         scope.render(scope.chart)

       scope.$watchCollection 'chart.data', (val, old) ->
-        return if not val
-        # any = R.reduce(
-        #   (a, b) ->
-        #     return a or b
-        #   false
-        # )
-        # estimated = if any(R.map(R.match(/estimated/), scope.chart.series)) then ' estimated' else ''
-        # $log.info "Chart data#{estimated}:", val
-        scope.estimate(scope.fn)
+        return if not val or val is old
+        any = R.reduce(
+          (a, b) ->
+            return a or b
+          false
+        )
+        estimated = if any(R.map(R.match(/estimated/), scope.chart.series)) then ' estimated' else ''
+        $log.info "Chart data#{estimated}"
+        if not estimated
+          scope.estimate(scope.fn)
         scope.render(scope.chart)

       scope.$watch 'fn', (val, old) ->


Updated DiplomatView.coffee.

Updated DiplomatView.coffee.

--- app/views/contribute/DiplomatView.coffee
+++ app/views/contribute/DiplomatView.coffee
@@ -104,7 +104,7 @@
     'zh-HANT': ['Adam23', 'gintau', 'shuwn']      # 繁體中文, Chinese (Traditional)
     'zh-WUU-HANS': []  # 吴语, Wuu (Simplified)
     'zh-WUU-HANT': ['benojan']  # 吳語, Wuu (Traditional)
-    fr: ['Anon', 'Armaldio', 'ChrisLightman', 'Elfisen', 'Feugy', 'MartinDelille', 'Oaugereau', 'Xeonarno', 'dc55028', 'jaybi', 'pstweb', 'veritable', 'xavismeh']             # français, French
+    fr: ['Anon', 'Armaldio', 'ChrisLightman', 'Elfisen', 'Feugy', 'MartinDelille', 'Oaugereau', 'Xeonarno', 'dc55028', 'jaybi', 'pstweb', 'veritable', 'xavismeh', 'CatSkald']             # français, French
     ja: ['Coderaulic', 'g1itch', 'kengos', 'treby']             # 日本語, Japanese
     ar: ['5y', 'ahmed80dz']             # العربية, Arabic
     'pt-BR': ['Bia41', 'Gutenberg Barros', 'Kieizroe', 'Matthew Burt', 'brunoporto', 'cassiocardoso', 'jklemm', 'Arkhad']        # português do Brasil, Portuguese (Brazil)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/distance.coffee
+++ src/distance.coffee
@@ -1,14 +1,2 @@
 Distance = (I={}, self) ->
-  originalAlpha = I.alpha
-
-  self.bind 'update', ->
-    I.flickerDuration = I.flickerDuration.approach(0, 1)
-
-    if I.flickerDuration > 0
-      if I.age.mod(I.flickerFrequency) is 0
-        if I.alpha is I.flickerAlpha
-          I.alpha = originalAlpha
-        else
-          I.alpha = I.flickerAlpha
-    else
-      I.alpha = originalAlpha+  distance:


'Updating for version 0.12.6rc2'

'Updating for version 0.12.6rc2'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.6rc1'
+export version = '0.12.6rc2'


[MPS/LT] [106225716] Added tests for css class mods from #activate and #deactivate.

--- test/spec/pinball_wizard_spec.coffee
+++ test/spec/pinball_wizard_spec.coffee
@@ -2,6 +2,7 @@

   beforeEach ->
     pinball.reset()
+    document.documentElement.className = ''

   describe 'initialize', ->
     it 'is defined', ->
@@ -87,6 +88,12 @@

       expect(pinball.get('a')).toEqual('disabled')

+    it 'adds use-feature class to css', ->
+      pinball.add
+        feature_a: 'inactive'
+      pinball.activate 'feature_a'
+      expect(document.documentElement.className).toEqual(' use-feature-a')
+
   describe '#deactivate', ->
     it 'makes an active feature inactive', ->
       pinball.add
@@ -94,6 +101,12 @@
       pinball.deactivate 'a'

       expect(pinball.get('a')).toEqual('inactive')
+
+    it 'adds without-feature class to css', ->
+      pinball.add
+        feature_a: 'active'
+      pinball.activate 'feature_a'
+      expect(document.documentElement.className).toEqual(' without-feature-a')

   describe '#isActive', ->
     beforeEach ->


Allow more spaces

Allow more spaces

--- src/highfive.coffee
+++ src/highfive.coffee
@@ -115,7 +115,7 @@
         sheet.stats msg

     # The main responder
-    robot.respond /highfive (.+?)(?: \$(\S+))? (?:for )?(.*)/, (msg) ->
+    robot.respond /highfive (.+?)(?: +\$(\S+))? +(?:for )?(.*)/, (msg) ->
         robot.logger.debug msg.match[1], msg.match[2], msg.match[3]
         from_user = msg.message.user.name
         to_user = msg.match[1][1..]


insights: Fix --app option, target the default appName attribute

And fix LIMIT clause being munged up

--- src/insights.coffee
+++ src/insights.coffee
@@ -25,8 +25,11 @@
   end = end.toISOString()

   limit = 999
-  query = "SELECT #{fields} FROM #{event} SINCE '#{start}' UNTIL '#{end}' LIMIT #{limit}"
-  query += "WHERE app = '#{app}'" if app
+  query = "SELECT #{fields} FROM #{event}"
+  query += " WHERE appName = '#{app}'" if app
+  query += " SINCE '#{start}' UNTIL '#{end}'"
+  query += " LIMIT #{limit}"
+  debug 'query', query
   insights.query query, (err, body) ->
     return callback err if err
     return callback new Error "#{body.error}" if body.error


Added a debug function to turn debug mode on/off

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -2,8 +2,21 @@
 {join} = require 'path'
 fs = require 'fs'

-log = console.log.bind console, '[INFO] %s'
-error = console.error.bind console, '[ERROR] %s'
+doNothing = ->
+log = doNothing
+error = doNothing
+
+debugMode = false
+debug = (b = true, logFunc, errFunc) ->
+  debugMode = b
+  if debugMode
+    log = logFunc
+    log ?= console.log.bind console
+    error = errFunc
+    error ?= console.error.bind console
+    log 'Debugging Flash Loader'
+  else
+    log = error = doNothing

 PLATFORM = process.platform
 FILENAME = switch PLATFORM
@@ -120,6 +133,7 @@
   exports.addSource = addSource
   exports.load = load

+exports.debug = debug
 exports.FLASH_PLAYER_FILENAME = FILENAME
 exports.getFlashVersion = getFlashVersion
 exports.getAllVersions = getAllVersions


Fixes `with-minimap` class not removed on toggle off

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -82,7 +82,9 @@
     @paneView.addClass('with-minimap')
     @paneView.append(this)

-  detachFromPaneView: -> @detach()
+  detachFromPaneView: ->
+    @paneView.removeClass('with-minimap')
+    @detach()

   minimapIsAttached: -> @paneView.find('.minimap').length is 1


Require _ from underscore-plus

Require _ from underscore-plus

--- lib/project/util.coffee
+++ lib/project/util.coffee
@@ -1,4 +1,4 @@
-{_} = require 'atom'
+_ = require 'underscore-plus'

 module.exports =
   escapeHtml: (str) ->


improved support for :access

improved support for :access

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -121,7 +121,8 @@
               when 'in', 'ineach'            then ok = la.t == 'for'
               when 'case', 'caselist'        then ok = la.t in ['select', 'trap']
               when 'leave', 'continue'       then ok = la.t in ['for', 'while', 'continue']
-              when 'access', 'base', 'field', 'goto', 'include', 'return', 'using' then ok = 1
+              when 'access'                  then ok = la.t in ['class', '∇']; stream.match /(?:\s+\w+)+/; ok = 1
+              when 'base', 'field', 'goto', 'include', 'return', 'using' then ok = 1
               when 'implements'
                 if x = stream.match(/\s+(\w+)/)?[1]
                   x = x.toLowerCase()
@@ -139,15 +140,18 @@
           else 'apl-err'

   # when the user enters one of these, a re-indent is triggered
-  electricInput: /(?::end|:else|:andif|:orif|:case|:until|\}|∇)$/
+  electricInput: /(?::end|:else|:andif|:orif|:case|:until|:access|\}|∇)$/

   indent: (h, s) -> # h:state, s:textAfter
-    {a} = h
-    if /^\s*∇/.test s
+    {a} = h; la = last a
+    if dfnDepth a
+      if /^\s*\}/.test s then la.oi else la.ii
+    else if /^\s*∇/.test s
       i = a.length - 1; while i && a[i].t != '∇' then i--
-      if i then a[i].oi else last(a).ii
+      if i then a[i].oi else la.ii
+    else if /^\s*:access/i.test s
+      if la.t == 'class' then la.oi else la.ii
     else
-      re = dfnDepth(a) && /^\s*\}/ || /^\s*:(?:end|else|andif|orif|case|until)/i
-      if re.test s then last(a).oi else last(a).ii
+      if /^\s*:(?:end|else|andif|orif|case|until)/i.test s then la.oi else la.ii

   fold: 'indent'


Dplclick on table cell leads not to algoprog instead of informatics

--- client/components/TableRow.coffee
+++ client/components/TableRow.coffee
@@ -9,7 +9,7 @@

 ProblemResult = (props) ->
     r = props.result
-    text =
+    text =
         if r.ignored < 0  # DQ
             ""
         else if r.solved > 0 or r.ok > 0
@@ -18,7 +18,7 @@
             "-" + r.attempts
         else
             " "
-    className =
+    className =
         if r.solved > 0
             "ac"
         else if r.ignored > 0
@@ -31,38 +31,36 @@
             "wa"
         else
             undefined
-
+
     if className
         className = globalStyles[className]
-
+
     realProblem = r.table.substr(1)
     runId = r.lastSubmitId
-    runSuff = ''
     if runId
         if runId.indexOf("p") > 0
             runId = runId.substr(0, runId.indexOf("p"))  # strip problem suffix
-        runSuff = '&run_id=' + runId
-    dbClickUrl = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + realProblem + runSuff
+    dbClickUrl = "/material/#{r.table}"

     ctrlDbClickUrl = 'http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=' + realProblem + '&submit&user_id=' + props.user._id
-
+
     dbClickHandler = (event) ->
-        if event.ctrlKey
-            window.open(ctrlDbClickUrl, '_blank')
-        else
+        if event.ctrlKey
+            window.open(ctrlDbClickUrl, '_blank')
+        else
             window.open(dbClickUrl, '_blank')

         {text}

-
+
 totalResultClass = (result) ->
     if not result.problemName
         return undefined
     if result.total == result.solved
         return "full"
-    else
+    else
         needed = result.total
         if result.problemName.slice(-1) == "В"
             needed = Math.ceil(needed / 2)
@@ -74,7 +72,7 @@
             return "started"
         else
             return "none"
-
+
 TotalResult = (props) ->
     style = globalStyles.mainTable_td
     title = ""
@@ -84,17 +82,17 @@
             style += " " + styles[s]
         if props.result.problemName
             title = props.user.name + ": " + props.result.problemName
-    return
+    return

             {if props.header
                 "="
             else
-                ("" + props.result.solved +
+                ("" + props.result.solved +
                  (if props.result.ok then " + " + props.result.ok else "") +
                 " / " + props.result.total)
             }

-
+
 Result = (props) ->
     if props.result.total > 1
         `<TotalResult {...props}/>`
@@ -126,7 +124,7 @@

                     else
                         for result in subtable.results
-                            a <Result header={props.header} result={result} user={props.user} key={result._id + "::" + subtable._id}/>
+                            a <Result header={props.header} result={result} user={props.user} key={result._id + "::" + subtable._id}/>
                             subTotal = addTotal(subTotal, result)
                 a
                 if props.results.length > 1


kite: update for new token format

kite: update for new token format

--- client/Main/kite/kontrol.coffee
+++ client/Main/kite/kontrol.coffee
@@ -48,13 +48,7 @@

     onEvent = (options)=>
       e = options.withArgs[0]
-      kite =
-        kite   : e.kite
-        token  :
-          key  : e.token?.key
-          ttl  : e.token?.ttl
-
-      callback null, {action: e.action, kite: @_createKite kite}
+      callback null, {action: e.action, kite: @_createKite e}

     @kite.tell "getKites", [query, onEvent], (err, kites)=>
       return callback err, null  if err
@@ -65,7 +59,7 @@
   # Returns a new NewKite instance from Kite data structure coming from
   # getKites() and watchKites() methods.
   _createKite: (k)->
-    new NewKite k.kite, {type: "token", key: k.token.key}
+    new NewKite k.kite, {type: "token", key: k.token}

   _sanitizeQuery: (query) ->
     query.username    = "#{KD.nick()}"              unless query.username


payment: fix reference error

payment: fix reference error

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -144,7 +144,6 @@
       @form
       @existingCreditCardMessage
       @securityNote
-      @totalPrice
       @yearPriceMessage
     ].forEach (view) -> view.destroy()


builder: pass assetsPath to stylus

builder: pass assetsPath to stylus

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -381,6 +381,8 @@
         use       : nib()
         compress  : if opts.minifyCss then yes else no
         import    : includes
+        define    :
+          assetsPath: "#{opts.baseurl}/#{ASSETS_OUTDIR}"
         sourcemap :
           inline: if opts.debugCss then yes else no


Refactoring.

Refactoring.

--- client/templates/item/item.coffee
+++ client/templates/item/item.coffee
@@ -1,4 +1,4 @@
 Template.item.events
   'click .edit': (e) ->
     e.preventDefault()
-    Session.set 'itemEdited', this._id
+    start_editing(this)


AppController: Make participant editable/readonly when permission changed.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1024,7 +1024,19 @@
         @handleWatchMapChange event

       else if map is @permissions
-        @chat.settingsPane.emit 'PermissionChanged', event
+        @handlePermissionMapChange event
+
+
+  handlePermissionMapChange: (event) ->
+
+    @chat.settingsPane.emit 'PermissionChanged', event
+
+    {property, newValue} = event
+
+    return  unless property is KD.nick()
+
+    if      newValue is 'edit' then @makeEditable()
+    else if newValue is 'read' then @makeReadOnly()

   handleWatchMapChange: (event) ->


AppController: Code refactor.

AppController: Code refactor.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -883,7 +883,9 @@

     return if not context or not origin or origin is KD.nick()

-    if not myWatchMap or myWatchMap.keys().length is 0 or origin in myWatchMap.keys()
+    amIWatchingChangeOwner = not myWatchMap or myWatchMap.keys().length is 0 or origin in myWatchMap.keys()
+
+    if amIWatchingChangeOwner
       @getPaneByChange(change)?.handleChange? change, @rtm, @realTimeDoc


hotfix: non-canvas layers don't show up in autocomplete

--- client/src/app/util/pins.coffee
+++ client/src/app/util/pins.coffee
@@ -23,9 +23,9 @@
     # Fill in mouse pins
     for pin in ["down", "position", "cursor", "justDown", "justUp"] then keys.push("io.mouse.#{pin}")

-    # Fill in canvas pins
+    # Fill in layer pins
     layers = currentGame.version.circuits[circuits.currentCircuitMeta.type].io.layers
-    for layer in layers
+    for layer in layers when layer.type is "canvas"
       keys.push("io.canvas.#{layer.name}")

     # Fill in sound pins


Improve the notification message

Improve the notification message

--- src/js/background.coffee
+++ src/js/background.coffee
@@ -19,7 +19,7 @@
           items.push {title: this.title, message: ''}
       if items.length > 5
         items = items.slice(1, 5)
-        items.push {title: '', message: 'and more...'}
+        items.push {title: ' and more...', message: ''}

       chrome.notifications.create 'github-checker', {
         type: "list",


kubernetes: initial version

kubernetes: initial version

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -424,6 +424,74 @@
       fi
     }

+    function k8s () {
+      params=(start stop)
+      param=$1
+      case "${params[@]}" in  *"$param"*)
+        ;;
+      *)
+        echo "Error: Command not found: $param"
+        echo "Usage: ./run k8s COMMAND"
+        echo ""
+        echo "Commands:  "
+        echo "  start : install the services in Kubernetes pods"
+        echo "  stop  : stop the currently running Kubernetes cluster"
+
+        echo ""
+        exit 1
+      ;;
+      esac
+
+      if [ "$param" == "start" ]; then
+        # Dependency on a hypervisor will be dropped off in the future
+        command -v VirtualBox       >/dev/null 2>&1 || { echo >&2 "I require VirtualBox but it's not installed.  Aborting."; exit 1; }
+        command -v minikube         >/dev/null 2>&1 || { echo >&2 "I require a Kubernetes cluster. To install minikube: \
+                (curl -Lo minikube curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.20.0/minikube-$(uname | awk '{print tolower($0)}')-amd64 && chmod +x minikube && mv minikube /usr/local/bin/)"; exit 1;}
+        command -v kubectl          >/dev/null 2>&1 || { echo >&2 "I require kubectl. To install kubectl: \
+                (curl -L -O https://storage.googleapis.com/kubernetes-release/release/v1.6.4/bin/$(uname | awk '{print tolower($0)}')/amd64/kubectl && chmod +x kubectl && mv kubectl /usr/local/bin/)"; exit 1;}
+        command -v helm             >/dev/null 2>&1 || { echo >&2 "I require helm but it's not installed. To install helm: \
+                (curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh && chmod 700 get_helm.sh && ./get_helm.sh)"; exit 1; }
+
+        minikube start
+        helm init
+
+        export TILLER_POD_NAME=$(kubectl get pods -n kube-system -l app=helm  -o jsonpath="{.items[0].metadata.name}")
+        k8s_health_check $TILLER_POD_NAME kube-system 5 120
+
+      else
+        # TODO: stop the cluster, maybe even have an option to kill the cluster?
+        minikube stop
+      fi
+    }
+
+    function k8s_health_check () {
+      # args: $1: POD NAME, $2: NAMESPACE, $3: health check interval (recommended 5 seconds), $4: timeout duration
+      declare interval=$3
+      declare timeout=$4
+      declare duration=0
+
+      sleep $interval
+      declare response_code=$(kubectl exec -n $2 $1 -- echo 'i am alive')
+
+      echo -n 'health-check: '
+
+      until [[ $response_code != *"error"* ]]; do
+        if [ $duration -eq $timeout ]; then
+          echo ' timed out!'
+          exit 255
+        fi
+
+        echo -n '.'
+
+        sleep $interval
+        duration=$((duration + interval))
+
+        declare response_code=$(kubectl exec -n $2 $1 -- echo 'i am alive')
+      done
+
+      echo ' succeeded!'
+    }
+
     function switch_client_version () {
       if [ "$1" == "default" ]; then
         rm $KONFIG_PROJECTROOT/CLIENTVERSION
@@ -659,6 +727,9 @@
     elif [ "$1" == "switchclient" ]; then
       switch_client_version $2

+    elif [ "$1" == "k8s" ]; then
+      k8s $2
+
     else
       echo "Unknown command: $1"
       printHelp


Add HathiTrust AJAX calls and parsing (fixes #1)

--- src/js/ogl-browse.coffee
+++ src/js/ogl-browse.coffee
@@ -8,17 +8,30 @@
   repo_li = $("##{repo_li_id}")
   loader = ($('<div>').attr('class','ui active mini loader'))
   repo_li.append(loader)
-  $.ajax "http://catalog.hathitrust.org/api/volumes/htid/#{identifier}.json",
+  $.ajax "http://catalog.hathitrust.org/api/volumes/full/htid/#{identifier}.json",
     type: 'GET'
     dataType: 'json'
     crossDomain: 'true'
     error: (jqXHR, textStatus, errorThrown) ->
       console.log('AJAX error')
+      console.log(jqXHR)
+      console.log(textStatus)
+      console.log(errorThrown)
       loader.remove()
     success: (data, textStatus, jqXHR) ->
-      console.log('hathitrust success for ' + identifier)
-      console.log(data)
-      console.log(data.records.length)
+      # console.log('hathitrust success for ' + identifier)
+      # console.log(data)
+      # console.log(data.records)
+      for record_key,record of data.records
+        # console.log(record)
+        repo_li.append($('<p>').append($('<a>').attr('href',record.recordURL).attr('target','_blank').text(identifier + ' on HathiTrust')))
+        xml = $.parseXML(record['marc-xml'])
+        # console.log(xml)
+        for key in ['245','100','243','260'] # 500 504 700
+          record_text = $(xml).find("datafield[tag=#{key}]").children('subfield').text()
+          record_text = record_text.replace(/,([^ ])/g, ', \$1')
+          if record_text
+            repo_li.append($('<p>').text(record_text))
       loader.remove()

 add_archive_repo = (repo_li_id, identifier) ->
@@ -60,8 +73,8 @@
       repo_li.append(repo_link)
       repo_list.append(repo_li)
       if ocr_identifier.match(/\./) # hathitrust
-        # disable calls to hathitrust until CORS is enabled
-        # add_hathitrust_repo(repo_li_id, ocr_identifier)
+        ocr_identifier = ocr_identifier.replace(/\.ark-/,'.ark:').replace(/-/g,'/')
+        add_hathitrust_repo(repo_li_id, ocr_identifier)
       else # archive.org
         add_archive_repo(repo_li_id, ocr_identifier)


Update lua.coffee

Update lua.coffee

Tabletop Simulator Lua added

--- src/languages/lua.coffee
+++ src/languages/lua.coffee
@@ -15,6 +15,7 @@
   ###
   extensions: [
     'lua'
+    'ttslua'
   ]

   defaultBeautifier: "Lua beautifier"


- Return if already in the same state

- Return if already in the same state

--- components/inputs/KDRySwitch.coffee
+++ components/inputs/KDRySwitch.coffee
@@ -35,6 +35,7 @@
     , 0

   _setOff:(wCallback = yes)->
+    return unless @getValue()
     @inputDefaultValue = off
     @getDomElement().find("input").eq(0).attr "checked",no
     @$('a.on').removeClass('active')
@@ -42,6 +43,7 @@
     @switchStateChanged() if wCallback

   _setOn:(wCallback = yes)->
+    return if @getValue()
     @inputDefaultValue = on
     @getDomElement().find("input").eq(0).attr "checked",yes
     @$('a.off').removeClass('active')


Changed default method to cubic

Changed default method to cubic

--- Smooth.coffee
+++ Smooth.coffee
@@ -9,8 +9,8 @@
 Enum =
 	###Interpolation methods###
 	METHOD_NEAREST: 0 #Rounds to nearest whole index
-	METHOD_LINEAR: 1 # Default: linear interpolation
-	METHOD_CUBIC: 2
+	METHOD_LINEAR: 1
+	METHOD_CUBIC: 2 # Default: cubic interpolation

 	###Input clipping types###
 	CLIP_CLAMP: 0 # Default: clamp to [0, arr.length-1]
@@ -20,7 +20,7 @@

 defaultConfig =
-	method: Enum.METHOD_LINEAR
+	method: Enum.METHOD_CUBIC
 	clip: Enum.CLIP_CLAMP


Issue #18: form params should be encoded

--- lib/assets/javascripts/form.js.coffee
+++ lib/assets/javascripts/form.js.coffee
@@ -21,9 +21,9 @@

     serialized = []
     for key of params
-      serialized.push("#{key}=#{params[key]}")
-
-    serialized = serialized.join('&').replace(/%|!/g, '')
+      serialized.push("#{key}=#{encodeURIComponent(params[key])}")
+
+    serialized = serialized.join('&')

     url = @$form.attr("action")
     url += "?#{serialized}" if serialized.length > 0


Target .element

Target .element

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -283,7 +283,7 @@
       false

   onDragLeave: (event) ->
-    tab.style.maxWidth = '' for tab in @getTabs()
+    tab.element.style.maxWidth = '' for tab in @getTabs()
     @removePlaceholder()

   onDragEnd: (event) ->


Improve readability

Improve readability

--- vendor/assets/javascripts/dashing/dashing-src.coffee
+++ vendor/assets/javascripts/dashing/dashing-src.coffee
@@ -32,20 +32,25 @@

     super

-    @observe 'node', (newValue, oldValue) ->
-      if !oldValue && !@_registeredAtDashing
-        @_registeredAtDashing = true
+    # In newer Batman versions, the node is only set *after*
+    # the view is initialized, so we have to observe the view
+    # in order to register it into Dashing when the node is set
+    @observe 'node', (node) =>
+      @_registerWidget() if node

-        @mixin($(@node).data())
-        Dashing.widgets[@id] ||= []
-        Dashing.widgets[@id].push(@)
+  _registerWidget: ->
+    @mixin($(@node).data())

-        # in case the events from the server came
-        # before the widget was rendered
-        @mixin(Dashing.lastEvents[@id])
+    Dashing.widgets[@id] ||= []
+    Dashing.widgets[@id].push(@)

-        type = Batman.Filters.dashize(@constructor.name)
-        $(@node).addClass("widget widget-#{type} #{@id}")
+    # in case the events from the server came
+    # before the widget was rendered
+    lastEvent = Dashing.lastEvents[@id]
+    @receiveData(lastEvent)
+
+    type = Batman.Filters.dashize(@constructor.name)
+    $(@node).addClass("widget widget-#{type} #{@id}")

   @accessor 'updatedAtMessage', ->
     if updatedAt = @get('updatedAt')


Remove remaining references to window global

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -574,8 +574,8 @@
     return if @inSpecMode()

     workspaceElement = @views.getView(@workspace)
-    backgroundColor = window.getComputedStyle(workspaceElement)['background-color']
-    window.localStorage.setItem('atom:window-background-color', backgroundColor)
+    backgroundColor = @window.getComputedStyle(workspaceElement)['background-color']
+    @window.localStorage.setItem('atom:window-background-color', backgroundColor)

   # Call this method when establishing a real application window.
   startEditorWindow: ->


uplink config

uplink config

--- src/notice/client.coffee
+++ src/notice/client.coffee
@@ -15,10 +15,10 @@
                 if error?

                     console.log error
-
                     return callback error

+
+
                 console.log 'connected', socket.id
-
                 callback null, notice: 'CLIENT'


Value inspector updates nodes. Nodes aren't accumulators by default  [#95934234]

https://www.pivotaltracker.com/story/show/95934234

--- src/code/models/node.coffee
+++ src/code/models/node.coffee
@@ -10,7 +10,7 @@
     if key
       @key = key
     @links = []
-    {@x, @y, @title, @image, @initialValue, @isAccumulator, @color} = nodeSpec
+    {@x, @y, @title, @image, @initialValue, @isAccumulator} = nodeSpec
     @initialValue  ?= 50
     @isAccumulator ?= false
     @color ?= Colors[0].value


Cleanup

Cleanup

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -23,6 +23,7 @@
     width: (world) ->  world.up.get('width') - 2*my.margin
   }

+# TODO: Add ICON authority for smaller command display
 PROGRAM_AUTHORITY = $.extend {}, BUTTON_AUTHORITY # shallow copy
 PROGRAM_AUTHORITY['selected'] = (world) ->
   return false unless world.up.get('selected')
@@ -30,7 +31,6 @@

 display_commands = (name, programs) ->
   signals = programs.get('signals')
-  my.assert signals, "no current signals"
   children = Object.keys(signals).map (command) ->
     {
       _LABEL: command, name: command
@@ -40,7 +40,6 @@
     }
   {_LABEL: name, _AUTHORITY: BUTTON_AUTHORITY, _CHILDREN: children}

-# TODO: Add ICON authority for smaller command display
 display_program = (name, children) ->
   children = children.all() unless _.isArray(children)
   children.unshift {name: name, fill: "white", stroke: "white"}


Not yet!

Not yet!

--- lib/about-view.coffee
+++ lib/about-view.coffee
@@ -56,12 +56,6 @@
                 @span class: 'about-updates-label icon icon-squirrel', 'New update'
                 @span class: 'about-updates-version', '1.5.0'
                 @span class: 'about-updates-label', 'downloaded'
-                @a class: 'about-updates-release-notes', 'Release Notes'
-
-              @div class: 'about-updates-item', outlet: 'updateAvailable', =>
-                @span class: 'about-updates-label icon icon-squirrel', 'New update'
-                @span class: 'about-updates-version', '1.5.0'
-                @span class: 'about-updates-label', 'available'
                 @a class: 'about-updates-release-notes', 'Release Notes'

             @button class: 'btn', 'Check for updates'


Print line number of wait timeout in presenter spec

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -91,7 +91,9 @@
     expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)

     waitsForStateToUpdate = (presenter, fn) ->
-      waitsFor "presenter state to update", 1000, (done) ->
+      line = new Error().stack.split('\n')[2].split(':')[1]
+
+      waitsFor "presenter state to update at line #{line}", 1000, (done) ->
         disposable = presenter.onDidUpdateState ->
           disposable.dispose()
           process.nextTick(done)


Store and reset cursor position after isort runes on whole file.

--- lib/atom-isort.coffee
+++ lib/atom-isort.coffee
@@ -113,7 +113,9 @@
       self.updateStatusbarText '⧗', true
       if response['new_contents'].length > 0
         if insert_type == 'set'
+          pos = editor.getCursorScreenPosition()
           editor.setText response['new_contents']
+          editor.setCursorScreenPosition(pos)
         else if insert_type == 'insert'
           editor.insertText response['new_contents']
         self.updateStatusbarText '√', true


Refactor postprocessor to be more context aware.

--- src/postprocessor.coffee
+++ src/postprocessor.coffee
@@ -11,6 +11,8 @@
 # @class Postprocessor
 #
 class Postprocessor
+    @supportedRules = ['definition', 'enumeration', 'index', 'attribute', 'relation', 'link']
+
     #
     # @param [Object] parser
     #
@@ -38,22 +40,32 @@
     # Converts enumeration into JSON Schema object definition.
     #
     # @param [Object] enumeration raw definition of enumeration
+    # @param [Object] ctx context for enumeration
     # @return [Object] JSON Schema definition
     #
-    processEnumeration: (enumeration) =>
-        @processClass(enumeration)
+    processEnumeration: (enumeration, ctx={}) =>
+        @processDefinition(enumeration, ctx)

     #
-    # Converts class into JSON Schema object definition.
+    # Converts class rule into JSON Schema object definition.
     #
     # @param [Object] klass raw definition of class
-    # @return [Object] JSON Schema definition
+    # @param [Object] ctx context for class
+    # @return [Object] context with class JSON Schema definition
     #
-    processClass: (klass) =>
+    processDefinition: (klass, ctx={}) =>
+        # Process class rules
         if klass.rules? and klass.rules.length > 0
             @processRules(klass.rules, klass)

-        _.omit(klass, ['rules', 'rule'])
+        # Strip verbose fields
+        klass = _.omit(klass, ['rules', 'rule'])
+
+        # Add class to context
+        utils.addAsObjectMember(ctx, 'definitions', klass.title, klass)
+
+        # Return context
+        ctx

     #
     # Converts rules for context into JSON Schema object definition.
@@ -64,21 +76,10 @@
     #
     processRules: (rules, ctx={}) =>
         rules.forEach (rule) =>
-            switch rule.rule
-                when 'definition'
-                    utils.addAsObjectMember(ctx, 'definitions', rule.title, @processClass(rule))
-                when 'enumeration'
-                    utils.addAsObjectMember(ctx, 'definitions', rule.title, @processEnumeration(rule))
-                when 'index'
-                    @processIndex(rule, ctx)
-                when 'attribute'
-                    utils.addAsObjectMember(ctx, 'properties', rule.attribute.name, @processProperty(rule.attribute))
-                when 'relation'
-                    utils.addAsObjectMember(ctx, 'relations', rule.attribute.name, @processRelation(rule))
-                when 'link'
-                    utils.addAsObjectMember(ctx, 'links', rule.attribute.name, @processLink(rule))
-                else
-                    throw new Error("Unrecognized Grypher rule #{rule.rule}.")
+            if (rule.rule in Postprocessor.supportedRules)
+                method = "process#{utils.capitalizeFirst(rule.rule)}"
+                @[method](rule, ctx)
+            else throw new Error("Unrecognized Grypher rule #{rule.rule}.")
         ctx

     #
@@ -96,7 +97,8 @@
         index.key = index.key.map (field) =>
             # Save keys as properties if they specified in extended format
             if field.type?
-                utils.addAsObjectMember(ctx, 'properties', field.name, @processProperty(field))
+                # Note that field should be wrapped with attribute rule
+                @processAttribute(attribute: field, ctx)
             # We need only attribute names here
             field.name

@@ -110,25 +112,37 @@
     # Converts property definition to JSON Schema entry.
     #
     # @param [Object] property raw definition of property
-    # @return [Object] JSON Schema definition
+    # @param [Object] ctx context for property
+    # @return [Object] property context JSON Schema definition
     #
-    processProperty: (property) -> property
+    processAttribute: (property, ctx={}) ->
+        # Add property to context and return context
+        utils.addAsObjectMember(ctx, 'properties', property.attribute.name, property.attribute)
+        ctx

     #
     # Converts relation definition to JSON Schema entry.
     #
     # @param [Object] relation raw definition of relation
-    # @return [Object] JSON Schema definition
+    # @param [Object] ctx context for relation
+    # @return [Object] relation context JSON Schema definition
     #
-    processRelation: (relation) -> relation
+    processRelation: (relation, ctx={}) ->
+        # Add relation to context and return context
+        utils.addAsObjectMember(ctx, 'relations', relation.attribute.name, relation)
+        ctx

     #
     # Converts link definition to JSON Schema entry.
     #
     # @param [Object] link raw definition of link
-    # @return [Object] JSON Schema definition
+    # @param [Object] ctx context for link
+    # @return [Object] link context JSON Schema definition
     #
-    processLink: (link) -> link
+    processLink: (link, ctx={}) ->
+        # Add link to context and return context
+        utils.addAsObjectMember(ctx, 'links', link.attribute.name, link)
+        ctx

 module.exports = Postprocessor


Use webp for image

Use webp for image

--- lib/client/jpeg_streamer.coffee
+++ lib/client/jpeg_streamer.coffee
@@ -106,7 +106,7 @@
     @_canvas.width = width
     @_canvas.height = height
     @_ctx.drawImage(@_videoEl, 0, 0, width, height)
-    data = @_canvas.toDataURL("image/jpeg", @_quality.get())
+    data = @_canvas.toDataURL("image/webp", @_quality.get())
     dataBytesLength = byteLength(data)
     @_localJpegByteLength.set dataBytesLength
     @_localJpegDataUrl.set data
@@ -115,7 +115,9 @@
       if @_lastUpdatedTime?
         timeDiff = now - @_lastUpdatedTime
         if timeDiff > 1000
-          @_localJpegBytesPerSecond.set 1000 / timeDiff * @_sumOfBytesSinceLastTime
+          @_localJpegBytesPerSecond.set(
+            1000 / timeDiff * @_sumOfBytesSinceLastTime
+          )
           @_lastUpdatedTime = now
           @_sumOfBytesSinceLastTime = 0
         else


Use span

Use span

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -80,5 +80,5 @@
                 if self.getPackageName(stack)
                   @span self.getPackageName(stack) + " package"
                 else
-                  "atom core"
+                  @span "atom core"
                 @pre class: 'stack-trace', self.formatStack(stack)


Handle client closing the connection when a login failure is reported.

--- lib/connection.coffee
+++ lib/connection.coffee
@@ -87,6 +87,8 @@
       enter: ->
         @cleanupConnection()
       events:
+        loginFailed: ->
+          # Do nothing. The connection was probably closed by the client code.
         connectTimeout: ->
           # Do nothing, as the timer should be cleaned up.


Emit 'execution' when getting network:status

Comes as a response of a network:getstatus

--- src/runtimes/base.coffee
+++ src/runtimes/base.coffee
@@ -79,6 +79,10 @@
         @emit 'execution',
           running: false
           label: 'stopped'
+      when 'status'
+        @emit 'execution',
+          running: payload.running
+          label: if payload.running then 'running' else 'stopped'
       when 'icon'
         @emit 'icon', payload
       else


Improve best/worst lists

Improve best/worst lists

--- src/scripts/karma.coffee
+++ src/scripts/karma.coffee
@@ -2,8 +2,10 @@
 #
 # <thing>++ - give thing some karma
 # <thing>-- - take away some of thing's karma
-# karma <thing> - check thing's karma, if <thing> is omitted get top and bottom 3
+# karma <thing> - check thing's karma (if <thing> is omitted, show the top 5)
 # karma empty <thing> - empty a thing's karma
+# karma best - show the top 5
+# karma worst - show the bottom 5
 class Karma

   constructor: (@robot) ->
@@ -45,16 +47,20 @@
     k = if @cache[thing] then @cache[thing] else 0
     return k

-  summary: ->
+  sort: ->
     s = []
-    for key,val of @cache
-      s.push({name: key, karma: val})
-    s.sort (a,b) -> b.karma - a.karma
-    if s.length >= 6
-      return [s[0], s[1], s[2], s[s.length-3], s[s.length-2], s[s.length-1]]
-    else
-      return s
-
+    for key, val of @cache
+      s.push({ name: key, karma: val })
+    s.sort (a, b) -> b.karma - a.karma
+
+  top: (n = 5) ->
+    sorted = @sort()
+    sorted.slice(0, n)
+
+  bottom: (n = 5) ->
+    sorted = @sort()
+    sorted.slice(-n).reverse()
+
 module.exports = (robot) ->
   karma = new Karma robot
   robot.hear /(\S+[^+\s])\+\+(\s|$)/, (msg) ->
@@ -72,18 +78,20 @@
     karma.kill subject
     msg.send "#{subject} has had its karma scattered to the winds."

-  robot.respond /karma ?(\S+[^-\s])?$/i, (msg) ->
-    if msg.match[1]
-      match = msg.match[1].toLowerCase()
+  robot.respond /karma( best)?$/i, (msg) ->
+    verbiage = ["The Best"]
+    for item, rank in karma.top()
+      verbiage.push "#{rank + 1}. #{item.name} - #{item.karma}"
+    msg.send verbiage.join("\n")
+
+  robot.respond /karma worst$/i, (msg) ->
+    verbiage = ["The Worst"]
+    for item, rank in karma.bottom()
+      verbiage.push "#{rank + 1}. #{item.name} - #{item.karma}"
+    msg.send verbiage.join("\n")
+
+  robot.respond /karma (\S+[^-\s])$/i, (msg) ->
+    match = msg.match[1].toLowerCase()
+    if match != "best" && match != "worst"
       msg.send "\"#{match}\" has #{karma.get(match)} karma."
-    else
-      s = karma.summary()
-      if s.length >= 3
-        msg.send "Highest karma: \"#{s[0].name}\" (#{s[0].karma}), " +
-               "\"#{s[1].name}\" (#{s[1].karma}), and \"#{s[2].name}\" " +
-               "(#{s[2].karma}). Lowest karma: \"#{s[s.length-1].name}\" " +
-               "(#{s[s.length-1].karma}), \"#{s[s.length-2].name}\" " +
-               "(#{s[s.length-2].karma}), and \"#{s[s.length-3].name}\" " +
-               "(#{s[s.length-3].karma})."
-      else
-        msg.send "There aren't enough items with karma to give a top and bottom 3"+