Use the modern IP event instead

Use the modern IP event instead

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -22,11 +22,11 @@
         datatype: 'all'
         description: 'NoFlo graph definition to be used with the subgraph component'
         required: true
-        immediate: true
     @outPorts = new noflo.OutPorts

-    @inPorts.on 'graph', 'data', (data) =>
-      @setGraph data
+    @inPorts.graph.on 'ip', (packet) =>
+      return unless packet.type is 'data'
+      @setGraph packet.data

   setGraph: (graph) ->
     @ready = false


Add TODO

Add TODO

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -27,6 +27,7 @@
     @inPorts.graph.on 'ip', (packet) =>
       return unless packet.type is 'data'
       @setGraph packet.data, (err) =>
+        # TODO: Port this part to Process API and use output.error method instead
         return @error err if err

   setGraph: (graph, callback) ->


:art: Inline stuff a little bit

:art: Inline stuff a little bit

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -135,9 +135,8 @@
   updateLineMessages: (shouldRender = false) ->
     return unless @tabs['Line'].visibility
     @lineMessages.clear()
-    activeTextEditor = atom.workspace.getActiveTextEditor()
-    if activeTextEditor
-      currentLine = activeTextEditor.getCursorBufferPosition()?.row
+    currentLine = atom.workspace.getActiveTextEditor()?.getCursorBufferPosition()?.row
+    if currentLine
       @messages.forEach (message) =>
         if message.currentFile and message.range?.intersectsRow currentLine
           @lineMessages.add message


addmanagedmachinemodal: after destroy go back previous router for solo

--- client/app/lib/providers/managed/addmanagedmachinemodal.coffee
+++ client/app/lib/providers/managed/addmanagedmachinemodal.coffee
@@ -178,4 +178,4 @@

     cc = kd.singletons.computeController
     cc.managedKiteChecker.removeListener @bound 'machineFoundCallback'
-    kd.singletons.router.back()
+    kd.singletons.router.back()  unless isTeamReactSide()


:art: Consume BottomTab::visibility in LinterViews::render

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -74,6 +74,9 @@
     @_bottomTabFile.count = counts.file
     @_bottomTabProject.count = counts.project
     @_bottomStatus.count = counts.project
+    hasActiveEditor = typeof atom.workspace.getActiveTextEditor() isnt 'undefined'
+    @_bottomTabFile.visibility = hasActiveEditor
+    @_bottomTabProject.visibility = hasActiveEditor

   # consumed in editor-linter, _renderPanel
   updateBubble: (point) ->


Turning on logging should call Columnist middleware, not Journalist

--- lib/local_server.coffee
+++ lib/local_server.coffee
@@ -54,7 +54,7 @@
     if opts.gzip
       middlewares.push(charge.minimist(opts.gzip))
     if opts.log
-      middlewares.push(charge.journalist(opts.log))
+      middlewares.push(charge.columnist(opts.log))
     if opts.error_page
       middlewares.push(charge.apologist(opts.error_page))


Fix issue raised on minimap activation

Fix issue raised on minimap activation

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -116,6 +116,7 @@
   updateMinimapEditorView: => @miniEditorView.update()

   updateMinimapView: =>
+    return unless @editorView
     # offset minimap
     @offset top: @editorView.offset().top


Allow 0 opacities

Allow 0 opacities

--- lib/mixins/color.coffee
+++ lib/mixins/color.coffee
@@ -92,10 +92,10 @@
        return this

     _doOpacity: (fillOpacity, strokeOpacity) ->
-       return unless fillOpacity or strokeOpacity
-
-       fillOpacity = Math.max 0, Math.min 1, fillOpacity
-       strokeOpacity = Math.max 0, Math.min 1, strokeOpacity
+       return unless fillOpacity? or strokeOpacity?
+
+       fillOpacity = Math.max 0, Math.min 1, fillOpacity      if fillOpacity?
+       strokeOpacity = Math.max 0, Math.min 1, strokeOpacity  if strokeOpacity?
        key = "#{fillOpacity}_#{strokeOpacity}"

        if @_opacityRegistry[key]
@@ -104,8 +104,8 @@
            dictionary =
                Type: 'ExtGState'

-           dictionary.ca = fillOpacity if fillOpacity
-           dictionary.CA = strokeOpacity if strokeOpacity
+           dictionary.ca = fillOpacity if fillOpacity?
+           dictionary.CA = strokeOpacity if strokeOpacity?

            dictionary = @ref dictionary
            id = ++@_opacityCount


:art: further refactoring

:art: further refactoring

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -25,7 +25,13 @@
       @editor.clearSelections()
       mode = 'normal'

-    @deactivate(mode) unless @isMode(mode)
+    # Deactivate old mode
+    if mode isnt @mode
+      switch @mode
+        when 'insert' then @deactivateInsertMode()
+        when 'visual' then @deactivateVisualMode()
+
+    # Activate
     switch mode
       when 'normal' then @activateNormalMode()
       when 'insert' then @activateInsertMode(submode)
@@ -36,14 +42,9 @@
         null # This is just placeholder, nothing to do without updating selector.

     [@mode, @submode] = [mode, submode]
-    @vimState.showCursors() if @isMode('visual')
+    @vimState.showCursors() if @mode is 'visual'
     @updateModeSelector(mode, submode)
     @vimState.statusBarManager.update(mode, submode)
-
-  deactivate: (mode) ->
-    switch @mode
-      when 'insert' then @deactivateInsertMode(@submode)
-      when 'visual' then @deactivateVisualMode(@submode)

   updateModeSelector: (mode, submode) ->
     for _mode in supportedModes
@@ -79,14 +80,14 @@
       @replaceModeSubscriptions.add new Disposable =>
         @replacedCharsBySelection = null

-  deactivateInsertMode: (oldSubmode) ->
+  deactivateInsertMode: ->
     @editor.groupChangesSinceCheckpoint(@insertionCheckpoint)
     changes = getChangesSinceCheckpoint(@editor.buffer, @insertionCheckpoint)
     @insertionCheckpoint = null
     if (item = @vimState.getLastOperation()) and item.isInsert()
       item.confirmChanges(changes)

-    if oldSubmode is 'replace'
+    if @submode is 'replace'
       @replaceModeSubscriptions?.dispose()
       @replaceModeSubscriptions = null

@@ -108,52 +109,40 @@
   # Visual
   # -------------------------
   activateVisualMode: (submode) ->
-    oldSubmode = @submode
-    # [FIXME]
-    # Following operation depend operationStack,
-    # So setting @mode, @submode here is important since operationStack
-    # do special cursor treatment depending on current @mode, @submode.
-    @mode = 'visual'
-    @submode = submode
-    @selectCharacterwise(oldSubmode, 'activate')
+    # Restern to charwise selection then preserve charwise range.
+    if @submode?
+      @restoreCharacterwiseRange {fromMode: @submode}
+    else
+      @editor.selectRight() if @editor.getLastSelection().isEmpty()
+    swrap(s).preserveCharacterwise() for s in @editor.getSelections()
+
+    # Change selection range to final submode.
     switch submode
       when 'linewise'
         swrap(s).expandOverLine() for s in @editor.getSelections()
       when 'blockwise'
         @vimState.operationStack.push new BlockwiseSelect(@vimState)

-  deactivateVisualMode: (oldSubmode) ->
-    @selectCharacterwise(oldSubmode, 'deactivate')
-    # NOTE: Since cursor is serialized and restored in next session.
-    # If we don't reset this propety, first find-and-replace:select-next will
-    # put selection wrong place.
+  deactivateVisualMode: ->
+    @restoreCharacterwiseRange({fromMode: @submode})
     for s in @editor.getSelections()
       swrap(s).resetProperties()
       if (not s.isEmpty()) and (not s.isReversed())
         s.cursor.moveLeft()
       s.clear(autoscroll: false)

-  preserveCharacterwise: ->
-    for s in @editor.getSelections()
-      swrap(s).preserveCharacterwise()
-
-  # FIXME: Eliminate complexity further
-  selectCharacterwise: (oldSubmode, stage) ->
-    switch oldSubmode
+  restoreCharacterwiseRange: ({fromMode}) ->
+    switch fromMode
+      when 'characterwise'
+        null # nothiing to do, but I want to be explicte.
       when 'linewise'
         for s in @editor.getSelections() when not s.isEmpty()
           swrap(s).restoreCharacterwise()
-        return
-      when 'characterwise'
-        null
       when 'blockwise'
-        # Many of VisualBlockwise commands change mode in the middle of processing()
-        # in this case, we want to loose multi-cursor.
+        # Many VisualBlockwise commands change mode in the middle of processing()
+        # in this case, we dont want to loose multi-cursor.
         unless @vimState.operationStack.isProcessing()
           @vimState.operationStack.push new BlockwiseRestoreCharacterwise(@vimState)
-      else
-        @editor.selectRight() if @editor.getLastSelection().isEmpty()
-    @preserveCharacterwise()

 # This uses private APIs and may break if TextBuffer is refactored.
 # Package authors - copy and paste this code at your own risk.


Properly delete socket file in benchmark

--- benchmark/browser-process-startup.coffee
+++ benchmark/browser-process-startup.coffee
@@ -1,6 +1,7 @@
 #!/usr/bin/env coffee

 {spawn, exec} = require 'child_process'
+fs = require 'fs'
 os = require 'os'
 path = require 'path'
 _ = require 'underscore-plus'
@@ -10,24 +11,30 @@
 socketPath = path.join(os.tmpdir(), 'atom.sock')
 numberOfRuns = 10

+deleteSocketFile = ->
+  try
+    fs.unlinkSync(socketPath) if fs.existsSync(socketPath)
+  catch error
+    console.error(error)
+
 launchAtom = (callback) ->
+  deleteSocketFile()
+
   cmd = 'atom'
   args = ['--safe', '--new-window', '--foreground', directoryToOpen]
   atomProcess = spawn(cmd, args)

   output = ''
   startupTimes = []
-
   dataListener = (data) ->
     output += data
     if match = /App load time: (\d+)/.exec(output)
+      startupTime = parseInt(match[1])
       atomProcess.stderr.removeListener 'data', dataListener
       atomProcess.kill()
-      exec 'pkill -9 Atom', ->
-        try
-          fs.unlinkSync(socketPath)
-
-        callback(parseInt(match[1]))
+      exec 'pkill -9 Atom', (error) ->
+        console.error(error) if error?
+        callback(startupTime)

   atomProcess.stderr.on 'data', dataListener

@@ -41,6 +48,7 @@
     minTime = _.min(startupTimes)
     totalTime = startupTimes.reduce (previousValue=0, currentValue) -> previousValue + currentValue
     console.log "Startup Runs: #{startupTimes.length}"
+    console.log "First run time: #{startupTimes[0]}ms"
     console.log "Max time: #{maxTime}ms"
     console.log "Min time: #{minTime}ms"
     console.log "Average time: #{Math.round(totalTime/startupTimes.length)}ms"


tweaks

tweaks

--- scripts/setup/setup-teams.coffee
+++ scripts/setup/setup-teams.coffee
@@ -70,4 +70,7 @@
   else
     mongoose.connection.close()

-async.doWhilst processTeam, hasTeam, last
+async.doWhilst processTeam, hasTeam, (err) ->
+  return last(err) if err
+  console.log 'no more teams to setup'
+  last()


Improve docs for up.motion

Improve docs for up.motion

--- lib/assets/javascripts/up/motion.js.coffee
+++ lib/assets/javascripts/up/motion.js.coffee
@@ -1,26 +1,30 @@
 ###*
-Animation and transitions
-=========================
-
-Any fragment change in Up.js can be animated.
-
-    Show users
-
-Or a dialog open:
+Animation
+=========
+
+Whenever you change a page fragment (through methods like
+[`up.replace`](/up.replace) or UJS attributes like [`up-target`](/up-target))
+you can animate the change.
+
+For instance, when you replace a selector `.list` with a new `.list`
+from the server, you can add an `up-transition="cross-fade"` attribute
+to smoothly fade out the old `.list` while fading in the new `.list`:
+
+    Show users
+
+When we morph between an old an new element, we call it a *transition*.
+In contrast, when we animate a new element without simultaneously removing an
+old element, we call it an *animation*.
+
+An example for an animation is opening a new dialog, which we can animate
+using the `up-animation` attribute:

     Show users

-Up.js ships with a number of predefined animations and transitions,
-and you can easily define your own using Javascript or CSS.
-
-
-\#\#\# Incomplete documentation!
-
-We need to work on this page:
-
-- Explain the difference between transitions and animations
-- Demo the built-in animations and transitions
-- Explain ghosting
+Up.js ships with a number of predefined [animations](/up.animate#named-animation)
+and [transitions](/up.morph#named-animation).
+You can also easily [define your own animations](/up.animation)
+or [transitions](/up.transition) using Javascript or CSS.

 @class up.motion
@@ -265,7 +269,25 @@

   - `move-to-bottom/fade-in`
   - `move-to-left/move-from-top`
-
+
+  \#\#\#\# Implementation details
+
+  During a transition both the old and new element occupy
+  the same position on the screen.
+
+  Since the CSS layout flow will usually not allow two elements to
+  overlay the same space, Up.js:
+
+  - The old and new elements are cloned
+  - The old element is removed from the layout flow using `display: hidden`
+  - The new element is hidden, but still leaves space in the layout flow by setting `visibility: hidden`
+  - The clones are [absolutely positioned](https://developer.mozilla.org/en-US/docs/Web/CSS/position#Absolute_positioning)
+    over the original elements.
+  - The transition is applied to the cloned elements.
+    At no point will the hidden, original elements be animated.
+  - When the transition has finished, the clones are removed from the DOM and the new element is shown.
+    The old element remains hidden in the DOM.
+
   @method up.morph
   @param {Element|jQuery|String} source
   @param {Element|jQuery|String} target
@@ -455,7 +477,8 @@
   Returns a new promise that resolves once all promises in arguments resolve.

   Other then [`$.when` from jQuery](https://api.jquery.com/jquery.when/),
-  the combined promise will have a `resolve` method.
+  the combined promise will have a `resolve` method. This `resolve` method
+  will resolve all the wrapped promises.

   @method up.motion.when
   @param promises...


Fixing EXTREMELY CRITICAL BUG. No idea how it was working before, just God could tell.

--- src/polvo/core/optimizer.coffee
+++ src/polvo/core/optimizer.coffee
@@ -199,7 +199,7 @@
           # the specific dependency and run reorder recursively
           # until everything is beautiful
           else
-            files.splice index, 0, dependency.item
+            files.splice file_index, 0, dependency.item
             files.splice dependency.index + 1, 1
             @reorder files, true
             break


Update to new factory.js style

Update to new factory.js style

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -330,12 +330,7 @@
       info.pages.push(pageDiv)
     res.render('static.html', info)

-  app.get ////plugins/(factory/)?factory.js///, (req, res) ->
-    cb = (e, catalog) ->
-      if e then return res.e e
-      res.write('window.catalog = ' + JSON.stringify(catalog) + ';')
-      fs.createReadStream(argv.c + '/plugins/meta-factory.js').pipe(res)
-
+  app.get ///system/factories.json///, (req, res) ->
     glob argv.c + '/**/factory.json', (e, files) ->
       if e then return cb(e)
       files = files.map (file) ->
@@ -346,7 +341,11 @@

       es.concat.apply(null, files)
         .on('error', res.e)
-        .pipe(es.writeArray(cb))
+        .pipe(es.writeArray((e, body) ->
+          if e then return res.e(e)
+          res.json(body)
+        ))
+

   ###### Json Routes ######
   # Handle fetching local and remote json pages.


[BUGFIX] Made certificate verification on Othentic.Request / Othentic.RequestBuilder optional

--- server/lib/RequestBuilder.coffee
+++ server/lib/RequestBuilder.coffee
@@ -19,7 +19,7 @@
       @serviceConfiguration.port,
       @path,
       signer,
-      @serviceConfgiuration.verifyCertificate == null || @serviceConfgiuration.verifyCertificate
+      @serviceConfiguration.verifyCertificate == null || @serviceConfiguration.verifyCertificate
     )
     return request


Improve GitHubDeploy.githubConfig() error handling

--- server/lib/git_hub_deploy.coffee
+++ server/lib/git_hub_deploy.coffee
@@ -111,9 +111,9 @@
     )

   @githubConfig: ->
-    githubConfig = AppConfig.get('deploy').github
+    githubConfig = AppConfig.get('deploy')?.github

     if githubConfig?
       return githubConfig
     else
-      throw new Error("Unable to find 'github' attribute in config")
+      throw new Error("Unable to find 'deploy.github' attribute in config")


Do the collision/star checking from the centre of the player.

--- src/game/states/game.coffee
+++ src/game/states/game.coffee
@@ -75,6 +75,7 @@
     @levelcomplete = false
     @drawConstellation(@level)
     @player = @add.sprite x, y, 'player'
+    @player.anchor.setTo 0.5, 0.5
     @player.scale.set 0.5,0.5

     #add sounds


Hide message tags covered by cluster heading (#520)

--- client/group.coffee
+++ client/group.coffee
@@ -392,7 +392,7 @@

             </>
           }
-          <MessageShort message={message}/>
+          <MessageShort message={message} clusterBy={clusterBy}/>
         </ErrorBoundary>
       }

@@ -492,7 +492,7 @@

 GroupMembers.displayName = 'GroupMembers'

-export MessageShort = React.memo ({message}) ->
+export MessageShort = React.memo ({message, clusterBy}) ->
   messageLink = useMemo ->
     pathFor 'message',
       group: message.group
@@ -514,6 +514,11 @@
   subscribed = useTracker ->
     subscribedToMessage message
   , [message]
+  tags = useMemo ->
+    t = message.tags
+    if t? and clusterBy
+      t = _.omit t, clusterBy
+    sortTags t

   onSubscribe = (e) ->
     e.preventDefault()
@@ -531,7 +536,8 @@

-        <MessageTags message={message} noLink/>
+        {###<MessageTags message={message} noLink/>###}
+        <TagList tags={tags} group={message.group} noLink/>
         <MessageLabels message={message}/>


don't bind function to variable

don't bind function to variable

--- scripts/feed_checker.coffee
+++ scripts/feed_checker.coffee
@@ -36,9 +36,11 @@
       is_room = usr.room?
       continue if _.contains sent, (if is_room then usr.room else usr.id)

-      func = (if is_room then robot.messageRoom else robot.send)
-      func (if is_room then usr.room else usr), msg
-      sent.push if is_room then usr.room else usr.id
+      if is_room
+        robot.messageRoom usr.room, msg
+      else
+        robot.send usr, msg
+      sent.push (if is_room then usr.room else usr.id)

   robot.brain.on 'loaded', ->
     interval = parseInt process.env.FEED_CHECK_INTERVAL


Call eachBuffer on atom.project instead of atom.workspaceView

--- lib/symbols-view.coffee
+++ lib/symbols-view.coffee
@@ -16,7 +16,7 @@
     super

     @cachedTags = {}
-    atom.workspaceView.eachBuffer (buffer) =>
+    atom.project.eachBuffer (buffer) =>
       @subscribe buffer, 'reloaded saved destroyed path-changed', =>
         delete @cachedTags[buffer.getPath()]
       @subscribe buffer, 'destroyed', =>


Revert "Use Element.scrollIntoView instead"

This reverts commit acbbe10b2947fef1f455e1e7c281683970c5c390.

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -124,6 +124,15 @@
     tab.updateTitle() for tab in @getTabs()
     @updateTabBarVisibility()

+  scrollToTab: (tab) ->
+    tabRightEdge = tab.offsetLeft + tab.clientWidth
+    tabBarRightEdge = this.scrollLeft + this.clientWidth
+
+    if tabRightEdge > tabBarRightEdge
+      this.scrollLeft = tabRightEdge - this.clientWidth
+    else if this.scrollLeft > tab.offsetLeft
+      this.scrollLeft = tab.offsetLeft
+
   updateTabBarVisibility: ->
     if not atom.config.get('tabs.alwaysShowTabBar') and not @shouldAllowDrag()
       @classList.add('hidden')
@@ -143,7 +152,7 @@
     if tabView? and not tabView.classList.contains('active')
       @querySelector('.tab.active')?.classList.remove('active')
       tabView.classList.add('active')
-      tabView.scrollIntoView()
+      @scrollToTab(tabView)

   getActiveTab: ->
     @tabForItem(@pane.getActiveItem())


Copy implementation for local target.

Copy implementation for local target.

--- lib/target_local.coffee
+++ lib/target_local.coffee
@@ -1,3 +1,6 @@
+path = require 'path'
+fs_extra = require 'fs-extra'
+logsmith = require 'logsmith'
 child_process = require 'child_process'

 # ---
@@ -30,6 +33,18 @@

 		callback null, shell_stream if callback

+	copy: (source, dest) ->
+		real_source = path.resolve path.dirname(@manifest.meta.location), source
+		task =
+			desc: "copy #{source} to #{dest}"
+			run: (callback) => fs_extra.copy real_source, dest, (err) ->
+				logsmith.exception err if err
+
+				return callback new Error "copy failed" if err
+				return callback null if callback
+
+		@step task
+
 	exec: (command, failproof_or_handler) ->
 		task =
 			desc: command


keep per-task count of branches and aborts

--- lib/SubtreeResults.coffee
+++ lib/SubtreeResults.coffee
@@ -21,18 +21,28 @@
       continue unless r
       rej += Object.keys(r).length
     rej
-  countAborted: ->
+  countAborted: (branched = false) ->
     aborted = 0
     for a in @aborted
       continue unless a
       for k,v of a
-        continue if v.branched
+        continue unless v.branched is branched
         aborted++
     aborted
   isComplete: ->
-    todo = @tasks + @branches.length - @aborted.length
-    done = @countFulfilled() + @countRejected()
-    @finished = done >= todo
+    return @finished if @finished
+    i = 0
+    complete = true
+    while i < @tasks
+      fulfilled = if @fulfilled[i] then Object.keys(@fulfilled[i]).length else 0
+      rejected = if @rejected[i] then Object.keys(@rejected[i]).length else 0
+      branched = if @branches[i] then Object.keys(@branches[i]).length else 0
+      aborted = if @aborted[i] then Object.keys(@aborted[i]).length else 0
+      #console.log "#{@choice.treeId} #{@choice} #{i} #{fulfilled} #{rejected} #{aborted} #{branched}"
+      if fulfilled + rejected + aborted < branched + 1
+        complete = false
+      i++
+    @finished = complete
     return @finished

   getFulfilled: -> @getResults @fulfilled
@@ -87,18 +97,17 @@
     callback @, value

   registerTree: (idx, tree, onResult) ->
+    tree.branched (c) =>
+      path = if c then c.toString() else ''
+      @branches[idx] = {} unless @branches[idx]
+      @branches[idx][path] = c
     tree.aborted (a) =>
-      return if a.branched
       path = if a.choice then a.choice.toString() else ''
       @aborted[idx] = {} unless @aborted[idx]
       @aborted[idx][path] = a
       return unless @isComplete()
       # Every task aborted
       onResult @, a.value
-    tree.branched (c) =>
-      path = if c.choice then c.toString() else ''
-      @branches[idx] = {} unless @branches[idx]
-      @branches[idx][path] = c

   toJSON: ->
     state =


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -15,7 +15,10 @@
         item.I[attr] = value

   ###*
-  Get the game object closest to the
+  Get the game object matching the given selector that is closest to the given position.
+
+  @param selector
+  @param
   ###
   closest: (selector, position) ->


everything is saving to/from the brain now

--- src/event-announcer.coffee
+++ src/event-announcer.coffee
@@ -17,24 +17,24 @@
 module.exports = (robot) ->

   getSubscription = () ->
-    robot.brain.get('event-announcer:subs') or {}
+    robot.brain.get('eventannouncer:subs') or {}

   pushSubscription = (listener, eventMatch) ->
-    subs = getSubscription
+    subs = getSubscription()
     subs[listener] ||= []
     subs[listener].push eventMatch
-    robot.brain.set 'event-announcer:subs', subs
+    robot.brain.set 'eventannouncer:subs', subs
     robot.brain.save()

   removeSubscription = (listener, eventMatch) ->
-    subs = getSubscription
+    subs = getSubscription()
     matches = subs[listener] || []
     eventIndex = matches.indexOf(eventMatch)
     if eventIndex > -1
       matches.splice(eventIndex)

   publishEvent = (eventName, message) ->
-    subs = getSubscription
+    subs = getSubscription()
     for listener, eventMatches of subs
       for eventMatch in eventMatches
         if eventName.lastIndexOf(eventMatch) != -1
@@ -65,12 +65,12 @@

   robot.respond /show my subs/i, (msg) ->
     listener = getListener msg
-    subs = getSubscription
+    subs = getSubscription()
     if listener of subs and subs[listener].length > 0
       msg.send "#{listener} is listening to: #{subs[listener]}"
     else
       msg.send "no subs for #{listener}"

   robot.respond /show all subs/i, (msg) ->
-    for listener, eventMatches of getSubscription
+    for listener, eventMatches of getSubscription()
       msg.send "#{listener} is listening to: #{eventMatches}"


Drain message

Drain message

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -94,7 +94,7 @@
     @afterSuccessfulHit = (battle, user, target, damage) ->
       amount = Math.ceil(damage * drainPercent)
       user.drain(amount, target)
-      # TODO: Message after drain
+      battle.message "#{target.name} had its energy drained!"
       battle.message "#{user.name} absorbed some HP!"
       oldFunc.call(this, battle, user, target, damage)


- fixed search

- fixed search

--- lib/crystal/search.coffee
+++ lib/crystal/search.coffee
@@ -2,6 +2,8 @@
 request = require 'request'

 search = (opts) ->
+  crystal = this
+
   if typeof opts == 'object'
     if opts._ && opts._[1]
       name = opts._[1]


Refactored select-list-multiple-view.coffee

--- lib/views/select-list-multiple-view.coffee
+++ lib/views/select-list-multiple-view.coffee
@@ -39,7 +39,7 @@
   # be called.
   initialize: ->
     super
-    selectedItems = []
+    @selectedItems = []
     @list.addClass('mark-active')

     @on 'mousedown', ({target}) =>
@@ -92,16 +92,16 @@
       @cancel()

   confirmed: (item, viewItem) ->
-    if item in selectedItems
-      selectedItems = selectedItems.filter (i) -> i isnt item
+    if item in @selectedItems
+      @selectedItems = @selectedItems.filter (i) -> i isnt item
       viewItem.removeClass('active')
     else
-      selectedItems.push item
+      @selectedItems.push item
       viewItem.addClass('active')

   complete: ->
-    if selectedItems.length > 0
-      @completed(selectedItems)
+    if @selectedItems.length > 0
+      @completed(@selectedItems)
     else
       @cancel()

@@ -129,7 +129,7 @@
         item = filteredItems[i].original ? filteredItems[i]
         itemView = $(@viewForItem(item, filteredItems[i].string ? null))
         itemView.data('select-list-item', item)
-        itemView.addClass 'active' if item in selectedItems
+        itemView.addClass 'active' if item in @selectedItems
         @list.append(itemView)

       @selectItemView(@list.find('li:first'))


Fix importers modal display when no importer format is supplied. Fixes #496.

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -539,7 +539,7 @@
       unless this.options.controls.measure.show is false
         this.controls.measure = new L.Control.Measure(this.options.controls.measure)
         this.map.addControl this.controls.measure
-      unless this.options.controls.importers is false
+      unless this.options.controls.importers.gml is false and this.options.controls.importers.geojson is false and this.options.controls.importers.kml is false

         this.controls.importers_ctrl = new L.Control.EasyButton '<i class="leaflet-importer-ctrl"></i>', (btn, map) =>
           args =


Clean code.

Clean code.

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -120,15 +120,11 @@
         catch
           widget.edition.addLayer e.layer

-#        widget._saveUpdates()
-#        widget.element.trigger "mapchange"

         this._refreshControls()
         widget.update()

       this.map.on "draw:edited", (e) ->
-#        widget._saveUpdates()
-#        widget.element.trigger "mapchange"
         widget.update()

@@ -138,8 +134,6 @@
           widget.element.trigger 'mapeditor:feature_delete', layer.feature

         widget.update()
-#        widget._saveUpdates()
-#        widget.element.trigger "mapchange"

       this._resize()
       # console.log "resized"
@@ -337,16 +331,11 @@
               if not feature.properties.name?
                 feature.properties.name = if feature.properties.id? then "#{this.options.defaultEditionFeaturePrefix}#{feature.properties.id}" else this.defaultLabel

-#              label = new L.Label({direction: 'auto', className:'referenceLabel'})
-#              label.setContent(feature.properties.name || feature.properties.id)
-#              label.setLatLng(layer.getBounds().getCenter())
-#              this.map.showLabel(label)
           })
         else
           this.reference = L.GeoJSON.geometryToLayer(this.options.show)

         this.reference.setStyle this.options.showStyle
-#        this.reference = L.GeoJSON.geometryToLayer(this.options.show).setStyle this.options.showStyle
         this.reference.addTo this.map
       this

@@ -358,10 +347,6 @@
           this.ghost = L.geoJson(this.options.ghost, {
             onEachFeature: (feature, layer) =>

-              #required for cap_land_parcel_clusters as names are set later
-#              if not feature.properties.name?
-#                feature.properties.name = if feature.properties.id? then "#{this.options.defaultEditionFeaturePrefix}#{feature.properties.id}" else this.defaultLabel
-#
               label = new L.Label({direction: 'bottom', className:'ghostLabel', offset: [0, -50], opacity: 0.7})
               label.setContent(feature.properties.name || feature.properties.id)
               label.setLatLng(layer.getBounds().getCenter())


Removed bad console.log

Removed bad console.log

--- app/assets/javascripts/models/ban.js.coffee
+++ app/assets/javascripts/models/ban.js.coffee
@@ -40,8 +40,6 @@
     args ||= {}
     args.fetch = if args.fetch then args.fetch else false

-    console.log @
-
     return session.get('users').findOrInitialize { id: @get('enforcer_id') },
       fetch: args.fetch


fixed: sometimes the room does not open -> rocket chat todo

--- app/assets/javascripts/rocketchat.js.coffee
+++ app/assets/javascripts/rocketchat.js.coffee
@@ -147,6 +147,10 @@
   window.iframeEl.frameBorder = "0";
   $('.js-rocketchat-iframe-container').append(iframeEl);
   $("#js-rocketchat-iframe").on 'load', ->
+    document.getElementById('js-rocketchat-iframe').contentWindow.postMessage({
+      event: 'login-with-token',
+      loginToken: token
+    }, 'https://staging.jugend.online');
     rocketChatProxy.iframeLoad = true
     rocketChatProxy.url = url + "/home"


Better comment

Better comment

--- src/jquery.textfade.coffee
+++ src/jquery.textfade.coffee
@@ -7,8 +7,8 @@
 https://github.com/mdesantis/jquery.textfade/LICENSE
 ###

-# Compilation command:  coffee --compile --output lib src
-# Minification command: uglifyjs lib/jquery.textfade.js --mangle --compress --comments '/^!/' --output lib/jquery.textfade.min.js
+# Compilation:  coffee -c -o lib src
+# Minification: uglifyjs lib/jquery.textfade.js -o lib/jquery.textfade.min.js -m -c --comments '/^!/'

 TextFade = (@$element, @action, options) ->


make sure basic class is returning undefined

--- src/kontrol/kontrol.coffee
+++ src/kontrol/kontrol.coffee
@@ -38,7 +38,8 @@
         type      : 'token'
         key       : token

-  createKites: (kiteDescriptors) -> (@createKite k for k in kiteDescriptors)
+  createKites: (kiteDescriptors) ->
+    (@createKite k for k in kiteDescriptors)

   fetchKites: (query = {}, callback) ->
     @kite.tell 'getKites', [query], (err, result) =>
@@ -80,7 +81,9 @@

       callback null, { changes, watcherID }

-      changes.emit 'register', kite for kite in @createKites kiteDescriptors
+      changes.emit 'register', kite  for kite in @createKites kiteDescriptors
+      return
+    return

   cancelWatcher: (id, callback) ->
     @kite.tell 'cancelWatcher', [id], callback
@@ -98,6 +101,7 @@

     eventName = @constructor.actions[action]
     changes.emit eventName, kite
+    return

   @actions      =
     REGISTER    : 'register'


refactor and command for copying the current marker

--- lib/editor/results.coffee
+++ lib/editor/results.coffee
@@ -12,6 +12,7 @@
       'inline-results:clear-current': (e) => @removeCurrent e
       'inline-results:clear-all': => @removeAll()
       'inline-results:toggle': => @toggleCurrent()
+      'inline-results:copy-current': => @copyCurrent()

     @subs.add atom.commands.add '.ink.inline',
       'inline-results:clear': (e) ->
@@ -148,9 +149,16 @@
         done = true
     e.abortKeyBinding() unless done

-  toggleCurrent: ->
+  getCurrentMarkers: ->
     ed = atom.workspace.getActiveTextEditor()
     pos = ed.getCursorBufferPosition()
     ms = ed.findMarkers {containsBufferPosition: pos}
-    m = ms.filter((m)->m.result?).map((m)->m.result)
-    m[0]?.view.firstElementChild?.firstElementChild?.click()
+    ms = ms.filter((m)->m.result?).map((m)->m.result)
+
+  toggleCurrent: ->
+    ms = @getCurrentMarkers()
+    ms.map((m) -> m?.view.firstElementChild?.firstElementChild?.click())
+
+  copyCurrent: ->
+    m = @getCurrentMarkers()[0]
+    atom.clipboard.write(m.view.getAttribute('plain'))


Remove js selector from scopes

Remove js selector from scopes

--- lib/fold-functions.coffee
+++ lib/fold-functions.coffee
@@ -87,8 +87,8 @@
         hasFoldableLines = true

       isFunction = @hasScopeAtBufferRow(editor, row,
-      'meta.function', 'meta.method.js',
-      'storage.type.arrow.js', 'entity.name.function.constructor.js')
+      'meta.function', 'meta.method',
+      'storage.type.arrow', 'entity.name.function.constructor')
       if foldable and isFunction and not isCommented
         if @indentLevel == null
           @indentLevel = thisIndentLevel


Better importer warning

Better importer warning

--- framer/Importer.coffee
+++ framer/Importer.coffee
@@ -137,7 +137,7 @@

 		# Layer names cannot start with a number
 		if startsWithNumber(layerInfo.name)
-			throw new Error("(#{layerInfo.name}) Layer or Artboard names can not start with a number")
+			throw new Error("Layer or Artboard names can not start with a number: '#{layerInfo.name}'")

 		# We can create the layer here
 		layer = new LayerClass(layerInfo)


Comments and minor clean-up

Comments and minor clean-up

--- framer/SVGGroup.coffee
+++ framer/SVGGroup.coffee
@@ -28,7 +28,7 @@
 			else
 				value

-		privateProp = "_" + propertyName
+		privateProp = "_#{propertyName}"

 		return @define propertyName,
 			get: ->
@@ -66,12 +66,16 @@

 	@define "gradient",
 		get: ->
+			# No reduction from child members here; don't think we need it for gradients per se.
 			@_gradient
-		set: (value) -> # Copy semantics!
+		set: (value) ->
 			if Gradient.isGradient(value)
 				@_gradient = new Gradient(value)
 			else if not value and Gradient.isGradientObject(@_gradient)
 				@_gradient = null
+
+			# Below method ends in @fill being set to a url, which is then in turn forwarded
+			# to the group's children:
 			SVG.updateGradientSVG(@)


Maybe...

Maybe...

--- lib/linter-crystal.coffee
+++ lib/linter-crystal.coffee
@@ -20,7 +20,7 @@
   errorStream: 'stderr'

   # A regex pattern used to extract information from the executable's output.
-  regex: ':(?<line>\\d+): (?<message>.*)'
+  regex: ':(?<line>\d+): (?<message>.*)'

   constructor: (editor) ->
     super(editor)


Reduce the space usage of song data.

Reduce the space usage of song data.

--- frontend/script.coffee
+++ frontend/script.coffee
@@ -63,6 +63,11 @@

 simpleNormalizeName = (name) ->
   return name.replace /\s+/g, "-"
+
+stripEmptyItems = (list) ->
+  while not list[list.length - 1]
+    list.length--
+  return list

 class RetryTimeouter
   constructor: (@minTimeout=MINIMUM_RETRY_TIMEOUT, @maxTimeout=MAXIMUM_RETRY_TIMEOUT) ->
@@ -923,7 +928,7 @@
     return song[0]

   title: (song) =>
-    result = song[1]
+    result = song[2]
     if not result
       result = @basename(song).replace /\.[^.]+$/, ""
     if not result
@@ -931,7 +936,7 @@
     return result

   album: (song) =>
-    result = song[2]
+    result = song[3]
     if not result
       parts = @directory(song).split "/"
       result = parts[parts.length - 2]
@@ -940,7 +945,7 @@
     return result

   artist: (song) =>
-    result = song[3]
+    result = song[4]
     if not result
       parts = @directory(song).split "/"
       result = parts[parts.length - 3]
@@ -949,7 +954,7 @@
     return result

   length: (song) =>
-    result = song[4]
+    result = song[1]
     if not result
       return 0
     return result
@@ -976,8 +981,7 @@
   album = fileinfo[fields.album]
   artist = fileinfo[fields.artist]
   length = fileinfo[fields.length]
-  data = [index, title, album, artist, length]
-  return data
+  return stripEmptyItems [index, length, title, album, artist]

 jsonToSong = (json_data) ->


Update de-DE.coffee

Update de-DE.coffee

minimal changes:Whitespace correction, removed trailing spaces.

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -1320,7 +1320,7 @@
     update_account_students_can3: "Sich in Arenen mit Mitschülern messen"
     update_account_students_can4: "Zugriff auf aktuelle Scülerfunktionen erhalten, sobald wir sie veröffentlichen."
     update_account_students_warning: "Warnung: Du wirst keine Möglichkeit mehr haben, zuvor von dir erstellte Klassen zu verwalten oder neue zu erstellen."
-    unsubscribe_warning: "Warnung: Dein Monats-Abonnement wird beendet werden."
+    unsubscribe_warning: "Warnung: Dein Monats-Abonnement wird beendet werden."
     update_account_remain_student: "Schüler bleiben"
     update_account_update_student: "In Schülerkonto umwandeln"
     need_a_class_code: "Du brauchst einen Klassencode für die Klasse, der du beitrittst:"
@@ -2242,6 +2242,6 @@
     spelling_issues: "Achte auf die Schreibweise: meintest du `$1` anstelle von `$2`?"
     capitalization_issues: "Achte auf Groß-/Kleinschreibung: `$1` sollte `$2` heißen."
     py_empty_block: "Leeres $1. Schreibe 4 Leerzeichen vor allen Anweisungen innerhalb des $2."
-    fx_missing_paren: "Wenn du `$1` als Funktion verwenden möchtest, dann brauchst du `()`"
-    unmatched_token: "`$1` ohne Gegenpart.  Jede öffnende `$2` benötigt eine zugehörige schließende `$3` ."
+    fx_missing_paren: "Wenn du `$1` als Funktion verwenden möchtest, dann brauchst du `()`."
+    unmatched_token: "`$1` ohne Gegenpart.  Jede öffnende `$2` benötigt eine zugehörige schließende `$3`."
     unterminated_string: "String ohne Abschluss. Füge ein ein `\"` am Ende deiner Zeichenkette hinzu."


Update de-DE.coffee

Update de-DE.coffee

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -760,7 +760,7 @@
     level_tab_thangs_add: "Thangs hinzufügen"
     add_components: "Kommentar hinzufügen"
     component_configs: "Komponenten Konfiguration"
-   config_thang: "Doppelklicke, um Thang zu konfigurieren"
+    config_thang: "Doppelklicke, um Thang zu konfigurieren"
     delete: "Löschen"
     duplicate: "Duplizieren"
     stop_duplicate: "Stop Duplizieren"


more translations pt-br

more translations pt-br

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -78,7 +78,7 @@
     awaiting_levels_adventurer_prefix: "Nós liberamos cinco níveis por semana."
     awaiting_levels_adventurer: "Cadastre-se como um aventureiro"
     awaiting_levels_adventurer_suffix: "para ser o primeiro a jogar as novas fases."
-#    adjust_volume: "Adjust volume"
+    adjust_volume: "Ajuste o volume"
     choose_your_level: "Escolha seu estágio" # The rest of this section is the old play view at /play-old and isn't very important.
     adventurer_prefix: "Você pode ir para qualquer um dos estágios abaixo, ou discutir sobre eles no "
     adventurer_forum: "Fórum do Aventureiro"
@@ -94,14 +94,14 @@
     campaign_classic_algorithms: "Algoritmos Clássicos"
     campaign_classic_algorithms_description: "...onde você aprende os algoritmos mais conhecidos em Ciência da Computação."

-#  share_progress_modal:
+  share_progress_modal:
 #    blurb: "You’re making great progress! Tell someone how much you've learned with CodeCombat."
-#    email_invalid: "Email address invalid."
+    email_invalid: "Endereço de email invalido."
 #    form_blurb: "Enter their email below and we’ll show them!"
-#    form_label: "Email Address"
-#    placeholder: "email address"
-#    title: "Excellent Work, Apprentice"
-#    tell_friend: "Tell your Friend"
+    form_label: "Endereço de Email"
+    placeholder: "endereço de email"
+    title: "Excelente Trabalho, Aprendiz"
+    tell_friend: "Diga a seu amigo"
 #    tell_parent: "Tell your Parent"

   login:
@@ -142,7 +142,7 @@

   common:
 #    back: "Back" # When used as an action verb, like "Navigate backward"
-#    continue: "Continue" # When used as an action verb, like "Continue forward"
+    continue: "Continue" # When used as an action verb, like "Continue forward"
     loading: "Carregando..."
     saving: "Salvando..."
     sending: "Enviando..."
@@ -169,9 +169,9 @@
     date: "Data"
     body: "Principal"
     version: "Versão"
-#    pending: "Pending"
-#    accepted: "Accepted"
-#    rejected: "Rejected"
+    pending: "Pendente"
+    accepted: "Aceito"
+    rejected: "Rejeitado"
 #    withdrawn: "Withdrawn"
 #    submitter: "Submitter"
     submitted: "Enviado"
@@ -180,10 +180,10 @@
     version_history: "Histórico de Versão"
     version_history_for: "Histórico de Versão para: "
     select_changes: "Selecione duas alterações para ver as diferenças."
-#    undo_prefix: "Undo"
-#    undo_shortcut: "(Ctrl+Z)"
-#    redo_prefix: "Redo"
-#    redo_shortcut: "(Ctrl+Shift+Z)"
+    undo_prefix: "Desfazer"
+    undo_shortcut: "(Ctrl+Z)"
+    redo_prefix: "Refazer"
+    redo_shortcut: "(Ctrl+Shift+Z)"
 #    play_preview: "Play preview of current level"
     result: "Resultado"
     results: "Resultados"
@@ -288,7 +288,7 @@
     loading_ready: "Pronto!"
     loading_start: "Iniciar fase"
     problem_alert_title: "Altere seu Código"
-#    problem_alert_help: "Help"
+    problem_alert_help: "Ajuda"
     time_current: "Agora:"
     time_total: "Máximo:"
     time_goto: "Ir para:"
@@ -335,8 +335,8 @@
 #    tip_source_code: "I want to change the world but they would not give me the source code."
 #    tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
 #    tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
-#    tip_google: "Have a problem you can't solve? Google it!"
-#    tip_adding_evil: "Adding a pinch of evil."
+    tip_google: "Tem um problema que você não pode solucionar? Google!"
+    tip_adding_evil: "Adicionando uma pitada de maldade."
 #    tip_miss_lunch: "Anyone who has lost track of time when using a computer knows the propensity to dream, the urge to make dreams come true and the tendency to miss lunch. - Tim Berners-Lee"
 #    tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"

@@ -345,8 +345,8 @@
     save_load_tab: "Salvar/Carregar"
     options_tab: "Opções"
     guide_tab: "Guia"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
+    guide_video_tutorial: "Vídeo Tutorial"
+    guide_tips: "Dicas"
     multiplayer_tab: "Multijogador"
     auth_tab: "Registrar"
     inventory_caption: "Equipar seu herói"
@@ -374,7 +374,7 @@
   inventory:
     choose_inventory: "Equipar itens"
     equipped_item: "Equipado"
-#    required_purchase_title: "Required"
+    required_purchase_title: "Obrigatório"
     available_item: "Disponível"
     restricted_title: "Restrito"
     should_equip: "(dois cliques para equipar)"
@@ -403,10 +403,10 @@
 #    feature2: "7 powerful new heroes with unique skills!"
 #    feature3: "30+ bonus levels"
 #    feature4: "3500 bonus gems every month!"
-#    feature5: "Video tutorials"
+    feature5: "Vídeo tutorials"
 #    feature6: "Premium email support"
 #    free: "Free"
-#    month: "month"
+    month: "mês"
     subscribe_title: "Inscrever-se"
     unsubscribe: "Desinscrever-se"
 #    confirm_unsubscribe: "Confirm Unsubscribe"
@@ -421,8 +421,8 @@
 #    parent_email_input_invalid: "Email address invalid."
 #    parent_email_input_label: "Parent email address"
 #    parent_email_input_placeholder: "Enter parent email"
-#    parent_email_send: "Send Email"
-#    parent_email_sent: "Email sent!"
+    parent_email_send: "Enviar Email"
+    parent_email_sent: "Email enviado!"
 #    parent_email_title: "What's your parent's email?"
     parents: "Para os pais"
     parents_title: "Seus filhos aprenderam código."
@@ -431,7 +431,7 @@
     parents_blurb3: "Sem risco: 100% devolução do dinheiro garantida, basta um simples clique em desinscrever-se."
     stripe_description: "Inscrição Mensal"
     subscription_required_to_play: "Você precisará se inscrever para jogar este nível."
-#    unlock_help_videos: "Subscribe to unlock all video tutorials."
+    unlock_help_videos: "Inscreva-se para desbloquear todos os vídeos tutoriais."

   choose_hero:
     choose_hero: "Escolha seu Herói"
@@ -534,9 +534,9 @@
     matt_title: "Programador"
     matt_blurb: "O Ciclista"

-#  teachers:
-#    title: "CodeCombat for Teachers"
-#    preparation_title: "Preparation"
+  teachers:
+    title: "CodeCombat para Professores"
+    preparation_title: "Preparação"
 #    preparation_1: "CodeCombat is free to play for the core level progression and does not require students to sign up. We encourage teachers to"
 #    preparation_play_campaign: "play through the campaign"
 #    preparation_2: "to try it out, but the only thing you absolutely need to do to be ready is ensure students have access to a computer."
@@ -590,7 +590,7 @@
     forum_page: "nosso fórum"
     forum_suffix: " ao invés disso."
 #    faq_prefix: "There's also a"
-#    faq: "FAQ"
+    faq: "FAQ"
 #    subscribe_prefix: "If you need help figuring out a level, please"
 #    subscribe: "buy a CodeCombat subscription"
 #    subscribe_suffix: "and we'll be happy to help you with your code."
@@ -773,7 +773,7 @@
 #    achievement_query_goals: "Key achievement off of level goals"
     level_completion: "Conclusão do Nível"
 #    pop_i18n: "Populate I18N"
-#    tasks: "Tasks"
+    tasks: "Tarefas"

   article:
     edit_btn_preview: "Prever"


Update pt-BR.coffee

Update pt-BR.coffee

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -1702,83 +1702,83 @@
 #    quests_complete: "Quests Complete"
     teacher_quest_create_classroom: "Criar turma"
     teacher_quest_add_students: "Adicionar alunos"
-#    teacher_quest_teach_methods: "Help your students learn how to `call methods`."
-#    teacher_quest_teach_methods_step1: "Get 75% of at least one class through the first level, __Dungeons of Kithgard__"
-#    teacher_quest_teach_methods_step2: "Print out the [Student Quick Start Guide](http://files.codecombat.com/docs/resources/StudentQuickStartGuide.pdf) in the Resource Hub."
-#    teacher_quest_teach_strings: "Don't string your students along, teach them `strings`."
-#    teacher_quest_teach_strings_step1: "Get 75% of at least one class through __True Names__"
-#    teacher_quest_teach_strings_step2: "Use the Teacher Level Selector on [Course Guides](/teachers/courses) page to preview __True Names__."
-#    teacher_quest_teach_loops: "Keep your students in the loop about `loops`."
-#    teacher_quest_teach_loops_step1: "Get 75% of at least one class through __Fire Dancing__."
-#    teacher_quest_teach_loops_step2: "Use the __Loops Activity__ in the [CS1 Curriculum guide](/teachers/resources/cs1) to reinforce this concept."
-#    teacher_quest_teach_variables: "Vary it up with `variables`."
-#    teacher_quest_teach_variables_step1: "Get 75% of at least one class through __Known Enemy__."
-#    teacher_quest_teach_variables_step2: "Encourage collaboration by using the [Pair Programming Activity](/teachers/resources/pair-programming)."
-#    teacher_quest_kithgard_gates_100: "Escape the Kithgard Gates with your class."
-#    teacher_quest_kithgard_gates_100_step1: "Get 75% of at least one class through __Kithgard Gates__."
-#    teacher_quest_kithgard_gates_100_step2: "Guide students to think through hard problems using the [Engineering Cycle Worksheet](http://files.codecombat.com/docs/resources/EngineeringCycleWorksheet.pdf)."
-#    teacher_quest_wakka_maul_100: "Prepare to duel in Wakka Maul."
-#    teacher_quest_wakka_maul_100_step1: "Get 75% of at least one class to __Wakka Maul__."
-#    teacher_quest_wakka_maul_100_step2: "See the [Arena Guide](/teachers/resources/arenas) in the [Resource Hub](/teachers/resources) for tips on how to run a successful arena day."
-#    teacher_quest_reach_gamedev: "Explore new worlds!"
-#    teacher_quest_reach_gamedev_step1: "[Get licenses](/teachers/licenses) so that your students can explore new worlds, like Game Development and Web Development!"
-#    teacher_quest_done: "Want your students to learn even more code? Get in touch with our [school specialists](mailto:schools@codecombat.com) today!"
-#    teacher_quest_keep_going: "Keep going! Here's what you can do next:"
-#    teacher_quest_more: "See all quests"
-#    teacher_quest_less: "See fewer quests"
-#    refresh_to_update: "(refresh the page to see updates)"
-#    view_project_gallery: "View Project Gallery"
-#    office_hours: "Teacher Webinars"
-#    office_hours_detail: "Learn how to keep up with with your students as they create games and embark on their coding journey! Come and attend our"
-#    office_hours_link: "teacher webinar"
-#    office_hours_detail_2: "sessions."
+    teacher_quest_teach_methods: "Ajude seus alunos a aprender como 'chamar métodos'."
+    teacher_quest_teach_methods_step1: "Obtenha 75% de pelo menos uma aula no primeiro nível, __Dungeons of Kithgard__"
+    teacher_quest_teach_methods_step2: "Imprima o [Student Quick Start Guide](http://files.codecombat.com/docs/resources/StudentQuickStartGuide.pdf) em Recursos do Professor."
+    teacher_quest_teach_strings: "Não amarre seus alunos, ensine-os 'strings'."
+    teacher_quest_teach_strings_step1: "Obtenha 75% de pelo menos uma aula através de __True Names__"
+    teacher_quest_teach_strings_step2: "Use o seletor de nível de professor na página [Course Guides](/teachers/courses) para prever __True Names__."
+    teacher_quest_teach_loops: "Mantenha seus alunos em loop sobre 'loops'."
+    teacher_quest_teach_loops_step1: "Obtenha 75% de pelo menos uma aula através de __Fire Dancing__."
+    teacher_quest_teach_loops_step2: "Use __Loops Activity__ em [CS1 Curriculum guide](/teachers/resources/cs1) para reforçar este conceito."
+    teacher_quest_teach_variables: "Varie com `variáveis`."
+    teacher_quest_teach_variables_step1: "Obtenha 75% de pelo menos uma aula através de __Known Enemy__."
+    teacher_quest_teach_variables_step2: "Incentive a colaboração usando [Pair Programming Activity](/teachers/resources/pair-programming)."
+    teacher_quest_kithgard_gates_100: "Fuja dos Portões de Kithgard com sua turma."
+    teacher_quest_kithgard_gates_100_step1: "Obtenha 75% de pelo menos uma aula através de __Kithgard Gates__."
+    teacher_quest_kithgard_gates_100_step2: "Oriente os alunos a pensarem sobre problemas difíceis usando [Engineering Cycle Worksheet](http://files.codecombat.com/docs/resources/EngineeringCycleWorksheet.pdf)."
+    teacher_quest_wakka_maul_100: "Prepare-se para duelar Wakka Maul."
+    teacher_quest_wakka_maul_100_step1: "Obtenha 75% de pelo menos uma aula através de __Wakka Maul__."
+    teacher_quest_wakka_maul_100_step2: "Veja [Arena Guide](/teachers/resources/arenas) em [Resource Hub](/teachers/resources) para dicas sobre como administrar um dia de arena de sucesso."
+    teacher_quest_reach_gamedev: "Explore novos mundos!"
+    teacher_quest_reach_gamedev_step1: "[Get licenses](/teachers/licenses) para que seus alunos possam explorar novos mundos, como Desenvolvimento de Jogos e Desenvolvimento Web!"
+    teacher_quest_done: "Quer que seus alunos aprendam ainda mais sobre programação? Entre em contato com nosso [school specialists](mailto:schools@codecombat.com) hoje!"
+    teacher_quest_keep_going: "Continue! Veja o que você pode fazer a seguir:"
+    teacher_quest_more: "Veja todas as missões"
+    teacher_quest_less: "Veja menos missões"
+    refresh_to_update: "(atualize a página para ver as atualizações)"
+    view_project_gallery: "Visualizar galeria de projetos"
+    office_hours: "Webinars de professores"
+    office_hours_detail: "Aprenda a acompanhar seus alunos enquanto eles criam jogos e embarcam em sua jornada de codificação! Venha e participe do nosso"
+    office_hours_link: "Webinars de professores"
+    office_hours_detail_2: "sessões."
     success: "Bem sucedido"
     in_progress: "Em progresso"
     not_started: "Não Iniciado"
     mid_course: "Meio do curso"
     end_course: "Fim do Curso"
-#    none: "None detected yet"
-#    explain_open_ended: "Note: Students are encouraged to solve this level creatively — one possible solution is provided below."
+    none: "Nenhum detectado ainda"
+    explain_open_ended: "Nota: Os alunos são encorajados a resolver este nível criativamente - uma solução possível é fornecida abaixo."
     level_label: "Nível:"
     time_played_label: "Tempo jogado:"
     back_to_resource_hub: "Voltar para Recursos do Professor"
     print_guide: "Imprimir este guia"
-#    combo: "Combo"
-#    combo_explanation: "Students pass Combo challenge levels by using at least one listed concept. Review student code by clicking the progress dot."
+    combo: "Combo"
+    combo_explanation: "Os alunos passam os níveis de desafio Combo usando pelo menos um conceito listado. Analise o código do aluno clicando no ponto de progresso."
     concept: "Conceito"

-#  share_licenses:
-#    share_licenses: "Share Licenses"
-#    shared_by: "Shared By:"
-#    add_teacher_label: "Enter exact teacher email:"
+  share_licenses:
+    share_licenses: "Compartilhar licenças"
+    shared_by: "Compatilhado por:"
+    add_teacher_label: "Insira o e-mail exato do professor:"
     add_teacher_button: "Adicionar professor"
-#    subheader: "You can make your licenses available to other teachers in your organization. Each license can only be used for one student at a time."
-#    teacher_not_found: "Teacher not found. Please make sure this teacher has already created a Teacher Account."
-#    teacher_not_valid: "This is not a valid Teacher Account. Only teacher accounts can share licenses."
-#    already_shared: "You've already shared these licenses with that teacher."
-#    teachers_using_these: "Teachers who can access these licenses:"
-#    footer: "When teachers revoke licenses from students, the licenses will be returned to the shared pool for other teachers in this group to use."
-#    you: "(you)"
-#    one_license_used: "(1 license used)"
-#    licenses_used: "(__licensesUsed__ licenses used)"
-#    more_info: "Mais informações"
-
-#  sharing:
+    subheader: "Você pode disponibilizar suas licenças para outros professores em sua organização. Cada licença só pode ser usada por um aluno de cada vez."
+    teacher_not_found: "Professor não encontrado. Por favor, certifique-se que este professor já criou uma conta de professor."
+    teacher_not_valid: "Esta não é uma conta de professor válida. Apenas contas de professores podem compartilhar licenças."
+    already_shared: "Você já compartilhou essas licenças com esse professor."
+    teachers_using_these: "Professores que podem acessar essas licenças:"
+    footer: "Quando os professores revogam as licenças dos alunos, as licenças serão devolvidas ao espaço compartilhado para uso por outros professores desse grupo."
+    you: "(você)"
+    one_license_used: "(1 license used)"
+    licenses_used: "(__licensesUsed__ licenses used)"
+    more_info: "Mais informações"
+
+  sharing:
     game: "Jogo"
-#    webpage: "Webpage"
-#    your_students_preview: "Your students will click here to see their finished projects! Unavailable in teacher preview."
-#    unavailable: "Link sharing not available in teacher preview."
-#    share_game: "Share This Game"
-#    share_web: "Share This Webpage"
-#    victory_share_prefix: "Share this link to invite your friends & family to"
-#    victory_share_prefix_short: "Invite people to"
-#    victory_share_game: "play your game level"
-#    victory_share_web: "view your webpage"
-#    victory_share_suffix: "."
-#    victory_course_share_prefix: "This link will let your friends & family"
-#    victory_course_share_game: "play the game"
-#    victory_course_share_web: "view the webpage"
-#    victory_course_share_suffix: "you just created."
+    webpage: "Página da web"
+    your_students_preview: "Seus alunos irão clicar aqui para ver seus projetos finalizados! Indisponível na pré-visualização do professor."
+    unavailable: "Compartilhamento de links não disponível na visualização do professor."
+    share_game: "Compartilhe este jogo"
+    share_web: "Compartilhe esta Página da web"
+    victory_share_prefix: "Compartilhe este link para convidar seus amigos e familiares para"
+    victory_share_prefix_short: "Convide pessoas para"
+    victory_share_game: "jogue seu nível de jogo"
+    victory_share_web: "visualizar sua Página da web"
+    victory_share_suffix: "."
+    victory_course_share_prefix: "Este link vai deixar seus amigos & familiares"
+    victory_course_share_game: "jogar o jogo"
+    victory_course_share_web: "visualizar a Página da web"
+    victory_course_share_suffix: "você acabou de criar."
     copy_url: "Copie o URL"
     share_with_teacher_email: "Enviar para seu professor"

@@ -2035,8 +2035,8 @@
     tournament_rules: "Regras do Torneio"
     tournament_blurb: "Escreva códigos, colete ouro, construa exércitos, esmague inimigos, ganhe prêmios e aprimore sua carreira no nosso Torneio da Cobiça de $40,000! Veja os detalhes"
     tournament_blurb_criss_cross: "Ganhe leilões, construa caminhos, despiste oponentes, agarre joias e aprimore sua carreira no nosso Torneio de Cruzadas! Veja os detalhes"
-#    tournament_blurb_zero_sum: "Unleash your coding creativity in both gold gathering and battle tactics in this alpine mirror match between red sorcerer and blue sorcerer. The tournament began on Friday, March 27 and will run until Monday, April 6 at 5PM PDT. Compete for fun and glory! Check out the details"
-#    tournament_blurb_ace_of_coders: "Battle it out in the frozen glacier in this domination-style mirror match! The tournament began on Wednesday, September 16 and will run until Wednesday, October 14 at 5PM PDT. Check out the details"
+    tournament_blurb_zero_sum: "ULibere sua criatividade de codificação em colagens de ouro e táticas de batalha neste jogo de espelho alpino entre o feiticeiro vermelho e o feiticeiro azul. O torneio começou na sexta-feira, 27 de março, e vai até segunda-feira, 6 de abril, às 17h PDT. Venha competir por diversão e glória! Confira os detalhes"
+    tournament_blurb_ace_of_coders: "Batalhe na geleira congelada neste jogo de espelho de estilo de dominação! O torneio começou na quarta-feira, 16 de setembro, e vai até quarta-feira, 14 de outubro, às 17h PDT. Confira os detalhes"
     tournament_blurb_blog: "no nosso blog"
     rules: "Regras"
     winners: "Vencedores"
@@ -2077,9 +2077,9 @@

   account:
     payments: "Pagamentos"
-#    prepaid_codes: "Prepaid Codes"
+    prepaid_codes: "Código prépagos"
     purchased: "Comprado"
-#    subscribe_for_gems: "Subscribe for gems"
+    subscribe_for_gems: "Inscreva-se para gemas"
     subscription: "Assinatura"
     invoices: "Faturas"
     service_apple: "Apple"
@@ -2097,12 +2097,12 @@
     card: "Cartão"
     status_unsubscribed_active: "Você não é um assinante e não será cobrado, mas sua conta ainda está ativa."
     status_unsubscribed: "Ganhe acesso a novos níveis, heróis, itens, e gemas bônus com uma assinatura CodeCombat!"
-#    not_yet_verified: "Not yet verified."
-#    resend_email: "Resend email"
-#    email_sent: "Email sent! Check your inbox."
-#    verifying_email: "Verifying your email address..."
+    not_yet_verified: "Ainda não verificado."
+    resend_email: "re-enviar e-mail"
+    email_sent: "E-mail enviado! Verifique sua caixa de entrada."
+    verifying_email: "Verificando seu endereço de email..."
     successfully_verified: "Seu endereço de email foi verificado com sucesso!"
-#    verify_error: "Something went wrong when verifying your email :("
+    verify_error: "Algo deu errado ao verificar seu e-mail :("

   account_invoices:
     amount: "Valor em dólares americanos"
@@ -2116,39 +2116,39 @@

   account_prepaid:
     purchase_code: "Adquira um código de inscrição"
-#    purchase_code1: "Subscription Codes can be redeemed to add premium subscription time to one or more accounts for the Home version of CodeCombat." #
-#    purchase_code2: "Each CodeCombat account can only redeem a particular Subscription Code once."
-#    purchase_code3: "Subscription Code months will be added to the end of any existing subscription on the account."
-#    purchase_code4: "Subscription Codes are for accounts playing the Home version of CodeCombat, they cannot be used in place of Student Licenses for the Classroom version."
-#    purchase_code5: "For more information on Student Licenses, reach out to"
+    purchase_code1: "Os códigos de assinatura podem ser resgatados para adicionar tempo de assinatura premium a uma ou mais contas para a Home Version do CodeCombat." #
+    purchase_code2: "Cada conta do CodeCombat só pode resgatar um código de assinatura específico."
+    purchase_code3: "Os meses do Código de Assinatura serão adicionados ao final de qualquer assinatura existente na conta."
+    purchase_code4: "Códigos de Assinatura são para contas que jogam a versão Home do CodeCombat, eles não podem ser usados no lugar de Licenças de Estudante para a versão do Sala de Aula."
+    purchase_code5: "Para mais informações sobre Licenças para Estudantes, entre em contato com"
     users: "Usuários"
     months: "Meses"
     purchase_total: "Total"
-#    purchase_button: "Submit Purchase"
+    purchase_button: "Submeter compra"
     your_codes: "Seus códigos"
-#    redeem_codes: "Redeem a Subscription Code"
-#    prepaid_code: "Prepaid Code"
-#    lookup_code: "Lookup prepaid code"
-#    apply_account: "Apply to your account"
-#    copy_link: "You can copy the code's link and send it to someone."
+    redeem_codes: "Resgatar um código de assinatura"
+    prepaid_code: "Código prépago"
+    lookup_code: "Pesquisar código pré-pago"
+    apply_account: "Aplicar na sua conta"
+    copy_link: "Você pode copiar o link do código e enviá-lo para alguém."
     quantity: "Quantidade"
-#    redeemed: "Redeemed"
+    redeemed: "Resgatado"
     no_codes: "Sem códigos ainda!"
     you_can1: "Você pode"
-#    you_can2: "purchase a prepaid code"
-#    you_can3: "that can be applied to your own account or given to others."
+    you_can2: "compre um código pré-pago"
+    you_can3: "que pode ser aplicado à sua conta ou dado a outras pessoas."

   loading_error:
     could_not_load: "Erro ao carregar do servidor"
     connection_failure: "Conexão falhou."
-#    connection_failure_desc: "It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page."
-#    login_required: "Login Required"
-#    login_required_desc: "You need to be logged in to access this page."
+    connection_failure_desc: "Não parece que você esteja conectado à internet! Verifique sua conexão de rede e recarregue esta página."
+    login_required: "Login obrigatório"
+    login_required_desc: "Você precisa estar logado para acessar esta página."
     unauthorized: "Você precisa estar autenticado. Você desativou os cookies?"
     forbidden: "Você não possui permissão."
-#    forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!"
+    forbidden_desc: "Ah não, não há nada que possamos mostrar aqui! Certifique-se de estar conectado à conta correta ou visite um dos links abaixo para voltar à programar!"
     not_found: "Não encontrado."
-#    not_found_desc: "Hm, there’s nothing here. Visit one of the following links to get back to programming!"
+    not_found_desc: "Não há nada aqui. Visite um dos links a seguir para voltar à programar!"
     not_allowed: "Método não permitido."
     timeout: "Tempo de requisição esgotado." # {change}
     conflict: "Conflito de recurso."
@@ -2183,9 +2183,9 @@
     arguments: "Argumentos"
     arithmetic: "Aritmética"
     array_2d: "Array 2D"
-#    array_index: "Array Indexing"
-#    array_iterating: "Iterating Over Arrays"
-#    array_literals: "Array Literals"
+    array_index: "Array de Indexação"
+    array_iterating: "Iterando mais Arrays"
+    array_literals: "Arrays Literais"
     array_searching: "Buscando no Array"
     array_sorting: "Ordenação de Array"
     arrays: "Arrays"
@@ -2196,42 +2196,42 @@
     basic_syntax: "Sintaxe Básica"
     binary: "Binário"
     boolean_and: "Booleano E"
-#    boolean_inequality: "Boolean Inequality"
+    boolean_inequality: "Desigualdade Booleana"
     boolean_equality: "Booleano Igualdade"
     boolean_greater_less: "Booleano Maior que/Menor que"
-#    boolean_logic_shortcircuit: "Boolean Logic Shortcircuiting"
+    boolean_logic_shortcircuit: "Lógica Booleana de Curto-Circuito"
     boolean_not: "Booleano Não"
-#    boolean_operator_precedence: "Boolean Operator Precedence"
+    boolean_operator_precedence: "Precedência do Operador Booleano"
     boolean_or: "Booleano Ou"
-#    boolean_with_xycoordinates: "Coordinate Comparison"
-#    bootstrap: "Bootstrap"
+    boolean_with_xycoordinates: "Comparação de Coordenada"
+    bootstrap: "Bootstrap"
     break_statements: "Comandos Break"
     classes: "Classes"
     continue_statements: "Comandos continue"
-#    dom_events: "DOM Events"
-#    dynamic_styling: "Dynamic Styling"
-#    event_concurrency: "Event Concurrency"
-#    event_data: "Event Data"
-#    event_handlers: "Event Handlers"
-#    event_spawn: "Spawn Event"
+    dom_events: "Eventos DOM"
+    dynamic_styling: "Estilo Dinâmico"
+    event_concurrency: "Concorrência de Eventos"
+    event_data: "Dados de Eventos"
+    event_handlers: "Manipuladores de Eventos"
+    event_spawn: "Gerador de Eventos"
     for_loops: "Laço For"
-#    for_loops_nested: "Nested For Loops"
+    for_loops_nested: "Aninhados por Loops"
 #    for_loops_range: "For Loops Range"
     functions: "Funções"
-#    functions_parameters: "Parameters"
-#    functions_multiple_parameters: "Multiple Parameters"
-#    game_ai: "Game AI"
-#    game_goals: "Game Goals"
+    functions_parameters: "Parâmetros"
+    functions_multiple_parameters: "Múltiplos Parâmetros"
+    game_ai: "IA do Jogo"
+    game_goals: "Objetivos do Jogo"
 #    game_spawn: "Game Spawn"
     graphics: "Gráficos"
     graphs: "Grafos"
-#    heaps: "Heaps"
-#    if_condition: "Conditional If Statements"
-#    if_else_if: "If/Else If Statements"
-#    if_else_statements: "If/Else Statements"
+    heaps: "Pilhas"
+    if_condition: "Declaração Condicional If"
+    if_else_if: "Declarações If/Else If "
+    if_else_statements: "Declarações If/Else"
     if_statements: "Comandos if"
-#    if_statements_nested: "Nested If Statements"
-#    indexing: "Array Indexes"
+    if_statements_nested: "Declarações If aninhadas"
+    indexing: "Índices de Arrays"
     input_handling_flags: "Manipulando Input - Flags"
     input_handling_keyboard: "Manipulando Input - Teclado"
     input_handling_mouse: "Manipulando Input - Mouse"
@@ -2244,16 +2244,16 @@
     jquery_filtering: "jQuery Filtro de Elementos"
     jquery_selectors: "jQuery Seletores"
     length: "Comprimento do Array"
-#    math_coordinates: "Coordinate Math"
+    math_coordinates: "Coordenadas matemáticas"
     math_geometry: "Geometria"
     math_operations: "Operações Matemáticas"
-#    math_proportions: "Proportion Math"
+    math_proportions: "Proporção Matemática"
     math_trigonometry: "Trigonometria"
     object_literals: "Objetos Literais"
     parameters: "Parâmetros"
     property_access: "Acessando Propriedades"
     property_assignment: "Atribuindo Propriedades"
-#    property_coordinate: "Coordinate Property"
+    property_coordinate: "Propriedade de Coordenada"
     queues: "Estruturas de Dados - Filas"
     reading_docs: "Lendo as Documentações"
     recursion: "Recursividade"
@@ -2409,11 +2409,11 @@
     december: "Dezembro"

 #  code_play_create_account_modal:
-#    title: "You did it!" # This section is only needed in US, UK, Mexico, India, and Germany
-#    body: "You are now on your way to becoming a master coder. Sign up to receive an extra <strong>100 Gems</strong> & you will also be entered for a chance to <strong>win $2,500 & other Lenovo Prizes</strong>."
-#    sign_up: "Sign up & keep coding ▶"
-#    victory_sign_up_poke: "Create a free account to save your code & be entered for a chance to win prizes!"
-#    victory_sign_up: "Sign up & be entered to <strong>win $2,500</strong>"
+    title: "Você fez isso!" # This section is only needed in US, UK, Mexico, India, and Germany
+    body: "Você está agora no seu caminho para se tornar um codificador mestre. Inscreva-se para receber um extra <strong>100 Gems</strong> & você também será inscrito para uma chance de <strong>ganhar $2,500 & outros Prêmios da Lenovo</strong>."
+    sign_up: "Inscreva-se & continue programando ▶"
+    victory_sign_up_poke: "Crie uma conta gratuita para salvar seu código e ser inscrito para ter a chance de ganhar prêmios!"
+    victory_sign_up: "Inscreva-se e seja inscrito para <strong>ganhar $2,500</strong>"

   server_error:
     email_taken: "Email já em uso"


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -4,7 +4,7 @@
     no_ie: "O CodeCombat não funciona no Internet Explorer 8 ou anterior. Desculpa!" # Warning that only shows up in IE8 and older
     no_mobile: "O CodeCombat não foi feito para dispositivos móveis e pode não funcionar!" # Warning that shows up on mobile devices
     play: "Jogar" # The big play button that just starts playing a level
-#    try_it: "Try It" # Alternate wording for Play button
+    try_it: "Experimenta" # Alternate wording for Play button
     old_browser: "Ups, o teu navegador é demasiado antigo para que o CodeCombat funcione. Desculpa!" # Warning that shows up on really old Firefox/Chrome/Safari
     old_browser_suffix: "Mesmo assim podes tentar, mas provavelmente não irá funcionar."
     ipad_browser: "Más notícias: o CodeCombat não funciona no navegador do iPad. Boas notícias: a nossa aplicação nativa para iPad está à espera da aprovação da Apple."
@@ -71,9 +71,9 @@
     buy_gems: "Comprar Gemas"
     campaign_forest: "Campanha da Floresta"
     campaign_dungeon: "Campanha da Masmorra"
-#    subscription_required: "Subscription Required"
-#    free: "Free"
-#    subscribed: "Subscribed"
+    subscription_required: "Subscrição Necessária"
+    free: "Gratuita"
+    subscribed: "Subscrito(a)"
     older_campaigns: "Campanhas Mais Antigas"
     anonymous: "Jogador Anónimo"
     level_difficulty: "Dificuldade: "
@@ -296,7 +296,7 @@
     tip_hofstadters_law: "Lei de Hofstadter: Tudo demora sempre mais do que pensas, mesmo quando levas em conta a Lei de Hofstadter."
     tip_premature_optimization: "Uma otimização permatura é a raíz de todo o mal. - Donald Knuth"
     tip_brute_force: "Quando em dúvida, usa a força bruta. - Ken Thompson"
-#    tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
+    tip_extrapolation: "Há apenas dois tipos de pessoas: aquelas que conseguem tirar uma conclusão a partir de dados reduzidos.."
     customize_wizard: "Personalizar Feiticeiro"

   game_menu:
@@ -337,20 +337,20 @@
     prompt_body: "Queres obter mais?"
     prompt_button: "Entra na Loja"

-#  subscribe:
-#    subscribe_title: "Subscribe"
-#    levels: "Unlock 25 levels! With 5 new ones every week!"
-#    heroes: "More powerful heroes!"
-#    gems: "3500 bonus gems every month!"
-#    items: "Over 250 bonus items!"
-#    parents: "For Parents"
-#    parents_title: "Your child will learn to code."
-#    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
-#    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
-#    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
-#    stripe_description: "Monthly Subscription"
-#    subscription_required_to_play: "You'll need a subscription to play this level."
+  subscribe:
+    subscribe_title: "Subscrever"
+    levels: "Desbloqueia 25 níveis! Com mais 5 novos por semana!"
+    heroes: "Heróis mais poderosos!"
+    gems: "3500 gemas de bónus todos os meses!"
+    items: "Mais de 250 itens de bónus!"
+    parents: "Para Educadores"
+    parents_title: "O teu educando vai aprender a programar."
+    parents_blurb1: "Com o CodeCombat, o teu educando aprende ao escrever código real. Começa por aprender comandos simples e progride para tópicos mais avançados."
+    parents_blurb2: "Por $9.99 USD/mês, recebe novos desafios todas as semanas e suporte pessoal, via e-mail, de programadores profissionais."
+    parents_blurb3: "Sem Risco: 100% de garantia de devolução do dinheiro, com anulação fácil de 1 clique."
+    subscribe_button: "Subscrever Agora"
+    stripe_description: "Subscrição Mensal"
+    subscription_required_to_play: "Precisas de uma subscrição para jogares este nível."

   choose_hero:
     choose_hero: "Escolhe o Teu Herói"
@@ -821,9 +821,9 @@
     service: "Serviço"
     price: "Preço"
     gems: "Gemas"
-#    status_subscribed: "You're currently subscribed at $9.99 USD/mo. Thanks for your support!"
-#    status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
-#    status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
+    status_subscribed: "Atualmente, estás subscrito por $9.99 USD/mês. Agradecemos o teu apoio!"
+    status_unsubscribed_active: "Não estás subscrito e não te vamos cobrar, mas a tua conta ainda está ativa, por agora."
+    status_unsubscribed: "Ganha acesso a novos níveis, heróis, itens e gemas de bónus com uma subscrição do CodeCombat!"

   loading_error:
     could_not_load: "Erro ao carregar do servidor"
@@ -908,7 +908,7 @@

   legal:
     page_title: "Legal"
-    opensource_intro: "O CodeCombat é gratuito para jogar e é totalmente open source."
+    opensource_intro: "O CodeCombat é completamente open source."
     opensource_description_prefix: "Confere "
     github_url: "o nosso GitHub"
     opensource_description_center: "e ajuda se quiseres! O CodeCombat é construído tendo por base dezenas de projetos open source, os quais nós amamos. Vê "
@@ -917,7 +917,7 @@
     practices_title: "Melhores Práticas Respeitosas"
     practices_description: "Estas são as nossas promessas para contigo, o jogador, com um pouco menos de politiquices."
     privacy_title: "Privacidade"
-#    privacy_description: "We will not sell any of your personal information."
+    privacy_description: "Nós não vamos vender nenhuma das tuas informações pessoais."
     security_title: "Segurança"
 #    security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
     email_title: "E-mail"


Removed start and stop loading methods that do nothing

--- app/js/jail_iframe/classes/highlight.coffee
+++ app/js/jail_iframe/classes/highlight.coffee
@@ -30,12 +30,10 @@
     $elements.on 'mouseleave', @_onUnhover

   _onClick: =>
-    @show_button.startLoading() # must be called after show
     FactlinkJailRoot.on 'modalOpened', @_onModalOpened, @
     FactlinkJailRoot.openFactlinkModal @id

   _onModalOpened: ->
-    @show_button.stopLoading()
     FactlinkJailRoot.off 'modalOpened', @_onModalOpened, @

   _onHover: =>


added an atom plugin version comment to cform starter

--- snippets.static/stack-start.cson
+++ snippets.static/stack-start.cson
@@ -3,6 +3,7 @@
     'prefix': 'stack-start'
     'body': """
 ---
+# Created using Atom-CForm-YAML v0.5.0-0
 AWSTemplateFormatVersion: "2010-09-09"

 Description: >


Danbooru Module Refactoring

Danbooru Module Refactoring

--- modules/danbooru.coffee
+++ modules/danbooru.coffee
@@ -29,114 +29,80 @@

     @registerCommand 'danbooru', {
       allowDM: true
-      aliases: ['d']
-    }, (msg, tags, d)=>
+      aliases: ['d', 'safebooru', 'safe']
+      includeCommandNameInArgs: true
+    }, (msg, args, d)=>
+      switch args[0]
+        when 'danbooru', 'd'
+          host = 'danbooru.donmai.us'
+          b = danbooru
+        when 'safebooru', 'safe'
+          host = 'safebooru.donmai.us'
+          b = safebooru
+      tags = args[1]
+      # Rate limiting
       if d.danbooruDate
         return msg.reply 'Rate limit excedeed. Wait a few seconds.' if (new Date() - d.danbooruDate) < 3000
       d.danbooruDate = new Date()
-      tags = 'rating:safe ' + tags if not d.data.allowNSFW and msg.channel.name.indexOf('nsfw') < 0
-      qs = {
-        random: true
-        tags
+      # NSFW Filter
+      b = safebooru if not d.data.allowNSFW and msg.channel.name.indexOf('nsfw') < 0
+      try
+        r = await b.get('posts.json', { json: true, qs: { random: true, tags } })
+      catch
+        # the random option of the Danbooru API doesn't work sometimes
+        try r = await b.get('posts.json', { json: true, qs: { tags } })
+        catch error
+          Core.log error, 2
+          msg.reply 'Something went wrong.'
+      return msg.reply 'No results.' if not r.length
+      msg.reply '', false, {
+        title: '[click for sauce]'
+        url: "https://#{host}/posts/#{r[0].id}"
+        image: { url: "https://#{host + r[0].file_url}" }
       }
-      danbooru.get '/posts.json', { json: true, qs }
-      .then (r)=>
-        if r.length
-          url = "https://danbooru.donmai.us#{r[0].file_url}"
-          msg.reply '', false, {
-            title: '[click for sauce]'
-            url: "https://danbooru.donmai.us/posts/#{r[0].id}"
-            image: { url }
-          }
-        else
-          msg.reply 'No results.'
-      .catch (e)=>
-        console.error e
+
+    @registerCommand 'setwaifu', { allowDM: true, aliases: 'sw' }, (msg, args, d)=>
+      return if not d.data.allowWaifus
+      waifu = (args.match(/\S+/) or [''])[0]
+      return msg.reply "Usage: ```#{@prefix}setWaifu <safebooru_tag>```" if not waifu
+      try
+        # Do a dummy search to check if the tag is valid
+        r = await safebooru.get('/posts.json', { json: true, qs: { tags: 'solo ' + waifu } })
+        return msg.reply 'Invalid safebooru tag.' if not r.length
+        # Check the DB for an existent waifu entry for the current user
+        w = (await Waifu.filter({ user: msg.author.id }).run())[0]
+        # If none, create a new entry
+        w = new Waifu { user: msg.author.id } if not w?
+        w.waifu = waifu
+        await w.save()
+        msg.reply 'Success.'
+      catch error
+        Core.log error, 2
         msg.reply 'Something went wrong.'

-    @registerCommand 'safebooru', {
-      allowDM: true
-      aliases: ['safe']
-    }, (msg, tags, d)=>
+    @registerCommand 'waifu', { allowDM: true, aliases: ['w'] }, (msg, args, d)=>
+      return if not d.data.allowWaifus
+      # Rate limiting
       if d.danbooruDate
         return msg.reply 'Rate limit excedeed. Wait a few seconds.' if (new Date() - d.danbooruDate) < 3000
       d.danbooruDate = new Date()
-      qs = {
-        random: true
-        tags
-      }
-      safebooru.get '/posts.json', { json: true, qs }
-      .then (r)=>
-        if r.length
-          url = "https://safebooru.donmai.us#{r[0].file_url}"
-          msg.reply '', false, {
-            title: '[click for sauce]'
-            url: "https://safebooru.donmai.us/posts/#{r[0].id}"
-            image: { url }
-          }
-        else
-          msg.reply 'No results.'
-      .catch (e)=>
-        console.error e
+      try
+        # Get the waifu entry for the current user
+        w = (await Waifu.filter({ user: msg.author.id }).run())[0]
+        return msg.reply "Run the #{@prefix}setWaifu command first." if not w?
+        # Make a safebooru search
+        try
+          r = await safebooru.get('/posts.json', { json: true, qs: { random: true, tags: 'solo ' + w.waifu } })
+        catch
+          r = await safebooru.get('/posts.json', { json: true, qs: { tags: 'solo ' + w.waifu } })
+        return msg.reply 'No results.' if not r.length
+        msg.reply '', false, {
+          title: '[click for sauce]'
+          url: "https://safebooru.donmai.us/posts/#{r[0].id}"
+          image: { url: "https://safebooru.donmai.us#{r[0].file_url}" }
+        }
+      catch error
+        Core.log error, 2
         msg.reply 'Something went wrong.'

-    @registerCommand 'setwaifu', { allowDM: true }, (msg, args, d)=>
-      return if not d.data.allowWaifus
-      waifu = (args.match(/\S+/) or [''])[0]
-      return msg.reply "Usage: ```#{@prefix}setWaifu <safebooru_tag>```" if not waifu
-      # Do a dummy search
-      qs = {
-        tags: 'solo ' + waifu
-      }
-      safebooru.get '/posts.json', { json: true, qs }
-      .then (r)=>
-        return Promise.reject { msg: 'Invalid safebooru tag.' } if not r.length
-        Waifu.filter({ user: msg.author.id }).run()
-      .then (results)=>
-        return results[0] if results[0]?
-        new Waifu { user: msg.author.id }
-      .then (w)=>
-        w.waifu = waifu
-        w.save()
-      .then ()=>
-        msg.reply 'Success.'
-      .catch (e)=>
-        console.error e
-        return msg.reply e.msg if e.msg
-        msg.reply 'Something went wrong.'
-
-    @registerCommand 'waifu', {
-      aliases: ['w'],
-      allowDM: true
-    }, (msg, args, d)=>
-      return if not d.data.allowWaifus
-      if d.danbooruDate
-        return msg.reply 'Rate limit excedeed. Wait a few seconds.' if (new Date() - d.danbooruDate) < 3000
-      d.danbooruDate = new Date()
-      Waifu.filter({ user: msg.author.id }).run()
-      .then (results)=>
-        return Promise.reject { msg: "Run the #{@prefix}setWaifu command first." } if not results[0]?
-        qs = {
-          random: true
-          tags: 'solo ' + results[0].waifu
-        }
-        safebooru.get '/posts.json', { json: true, qs }
-      .then (r)=>
-        if r.length
-          url = "https://safebooru.donmai.us#{r[0].file_url}"
-          msg.reply '', false, {
-            title: '[click for sauce]'
-            url: "https://safebooru.donmai.us/posts/#{r[0].id}"
-            image: { url }
-          }
-        else
-          msg.reply 'No results.'
-      .catch (e)=>
-        console.error e
-        return msg.reply e.msg if e.msg
-        msg.reply 'Something went wrong.'
-
-  getFileName: (url)=> url.split('/').reverse()[0]
-
-
 module.exports = DanbooruModule


Update danbooru.coffee

Update danbooru.coffee

--- modules/danbooru.coffee
+++ modules/danbooru.coffee
@@ -21,7 +21,7 @@
       b = safebooru unless d.data.allowNSFW or msg.channel.name.indexOf('nsfw') >= 0
       # Blacklisted tags (discord community guidelines)
       for tag in tags.split(' ')
-        if tag in ['loli', 'shota', 'lolicon'] then return msg.reply '', false, {
+        if tag in ['loli', 'shota', 'lolicon', 'toddlercon'] then return msg.reply '', false, {
           image:
             url: 'https://cdn.discordapp.com/attachments/244581077610397699/315655455143886850/Screenshot_from_2017-05-20_21-01-03.png'
         }


Fix spec failures on workspace-view-providers branch

Previously, the simple item we attached did not actually have a root
DOM node, which isn’t actually legal and would be entirely useless
outside of specs.

--- spec/background-tips-spec.coffee
+++ spec/background-tips-spec.coffee
@@ -38,7 +38,7 @@

     describe "when the pane is not empty", ->
       it "does not attach the view", ->
-        atom.workspaceView.getActivePaneView().activateItem($("item"))
+        atom.workspaceView.getActivePaneView().activateItem($("
item
"))

         activatePackage ->
           advanceClock BackgroundTipsView.startDelay + 1


enhance: update plan model

enhance: update plan model

--- app/models/plan.coffee
+++ app/models/plan.coffee
@@ -1,7 +1,15 @@
 `import DS from 'ember-data'`

-Plan = DS.Model.extend {
-
-}
+Plan = DS.Model.extend
+  name: DS.attr 'string'
+  description: DS.attr 'string'
+  price: DS.attr 'number'
+  projectsLimit: DS.attr "number"
+  url: DS.attr 'string'
+
+  descriptionItems:(->
+    description = @get "description"
+    description?.split ","
+  ).property "description"

 `export default Plan`


introduced an internal API for accessing localStorage

--- client/prefs.coffee
+++ client/prefs.coffee
@@ -1,5 +1,7 @@
 keymap = require './keymap'
-$d = $pk = null
+
+# Preferences UI
+$d = $pk = null

 ok = ->
   pk = $pk.val()
@@ -35,3 +37,45 @@
   # load current values
   $pk.val keymap.getPrefixKey()
   return
+
+# Preferences API -- localStorage should be accessed only through it
+# Usage:
+#   prefs.foo()                 # getter
+#   prefs.foo(123)              # setter
+#   prefs.foo (newValue) -> ... # add "on change" listener
+#   prefs.foo.toggle()          # convenience function for booleans (numbers 0 and 1)
+#   prefs.foo.getDefault()      # retrieve default value
+prefs = module.exports
+[ # name                    default
+  ['favs',                  [host: '127.0.0.1', port: 4502]]
+  ['floatingWindowInfos',   {}]
+  ['floatNewEditors',       0]
+  ['lineNumbersInDebugger', 0]
+  ['lineNumbersInEditor',   1]
+  ['prefixKey',             '`']
+  ['sessionLineWrapping',   0]
+  ['showLanguageBar',       1]
+  ['theme',                 '']
+  ['windowTitle',           '{WSID} - {HOST}:{PORT} (PID: {PID})']
+]
+.forEach ([k, d]) ->       # k: preference name, d: default value
+  t = typeof d; l = []     # t: type, l: listeners
+  str = if t == 'object' then JSON.stringify else (x) -> '' + x
+  sd = str d               # sd: default value "d" converted to a string
+  prefs[k] = p = (x) ->
+    if typeof x == 'function'
+      l.push f; return
+    else if arguments.length
+      if t == 'number' then x = +x else if t == 'string' then x = '' + x # coerce to type "t"
+      sx = str x # sx: "x" converted to a string; localStorage values can only be strings
+      if sx == sd then delete localStorage[k] else localStorage[k] = sx # avoid recording if it's at its default
+      for f in l then f value # notify listeners
+      x
+    else
+      if !(r = localStorage[k])? then d
+      else if t == 'number' then +r
+      else if t == 'object' then JSON.parse r
+      else r
+  p.getDefault = -> d
+  p.toggle = -> p !p()
+  return


exposed prefs API as D.prefs for easier debugging

--- client/prefs.coffee
+++ client/prefs.coffee
@@ -6,7 +6,7 @@
 #   prefs.foo (newValue) -> ... # add "on change" listener
 #   prefs.foo.toggle()          # convenience function for booleans (numbers 0 and 1)
 #   prefs.foo.getDefault()      # retrieve default value
-prefs = @
+D.prefs = @
 [ # name             default (type is determined from default value; setter enforces type and handles encoding)
   ['autoCloseBrackets', 1] # whether to insert {}[]() in pairs
   ['colourScheme',      'Default'] # name of the active colour scheme
@@ -98,13 +98,13 @@
   t = typeof d; l = []     # t: type, l: listeners
   str = if t == 'object' then JSON.stringify else (x) -> '' + x
   sd = str d               # sd: default value "d" converted to a string
-  prefs[k] = p = (x) ->
+  D.prefs[k] = p = (x) ->
     if typeof x == 'function'
       l.push x; return
     else if arguments.length
       if t == 'number' then x = +x else if t == 'string' then x = '' + x # coerce to type "t"
       sx = str x # sx: "x" converted to a string; localStorage values can only be strings
-      if l.length then old = prefs[k]()
+      if l.length then old = p()
       if sx == sd then delete localStorage[k] else localStorage[k] = sx # avoid recording if it's at its default
       for f in l then f x, old # notify listeners
       x


Ensure template variables passed to login page

--- app/site/routes.coffee
+++ app/site/routes.coffee
@@ -70,7 +70,7 @@

 login = (req, res) ->
 	msg = if req.query.wrong_pw isnt undefined then 'Wrong. Try again' else false
-	res.render "site/login", msg: msg
+	res.render "site/login", getTemplateData('LOGIN')

 loginPost = (req, res) ->
 	if req.body.pw is config.PASSWORD


Update docs

Update docs

--- src/leveldb/iterator.coffee
+++ src/leveldb/iterator.coffee
@@ -223,7 +223,9 @@

       Get the key at the current iterator position.

-      @param {Object} [options] Optional options. See `Handle.get()`
+      @param {Object} [options] Optional options.
+        @param {Boolean} [options.as_buffer=false] If true, data will be
+          returned as a `Buffer`.
       @param {Function} [callback] Optional callback. If not given, returns
         the key synchronously.
         @param {Error} error The error value on error, null otherwise.
@@ -257,7 +259,9 @@

       Get the value at the current iterator position.

-      @param {Object} [options] Optional options. See `Handle.get()`
+      @param {Object} [options] Optional options.
+        @param {Boolean} [options.as_buffer=false] If true, data will be
+          returned as a `Buffer`.
       @param {Function} [callback] Optional callback. If not given, returns
         the value synchronously.
         @param {Error} error The error value on error, null otherwise.
@@ -291,7 +295,9 @@

       Get the key and value at the current iterator position.

-      @param {Object} [options] Optional options. See `Handle.get()`
+      @param {Object} [options] Optional options.
+        @param {Boolean} [options.as_buffer=false] If true, data will be
+          returned as a `Buffer`.
       @param {Function} [callback] Optional callback. If not given, returns
         the key synchronously.
         @param {Error} error The error value on error, null otherwise.


follow up fixes to https://github.com/sagemathinc/smc/pull/1003 new file with no extension...

--- src/smc-webapp/project_new.cjsx
+++ src/smc-webapp/project_new.cjsx
@@ -213,15 +213,15 @@
         actions        : rtypes.object.isRequired

     getInitialState : ->
-        filename : @props.default_filename ? @default_filename()
-        warning  : false
+        filename           : @props.default_filename ? @default_filename()
+        extension_warning  : false

     componentWillReceiveProps: (newProps) ->
         if newProps.default_filename != @props.default_filename
             @setState(filename: newProps.default_filename)

     componentDidUpdate: ->
-        if not @state.warning
+        if not @state.extension_warning
             ReactDOM.findDOMNode(@refs.project_new_filename).focus()

     default_filename : ->
@@ -243,10 +243,10 @@
         e.preventDefault()
         if @state.filename[@state.filename.length - 1] == '/'
             @create_folder()
-        else if @state.filename.includes(".")
+        else if misc.filename_extension(@state.filename)
             @create_file()
         else
-            @setState(warning : true)
+            @setState(extension_warning : true)

     render_header: ->
         if @props.current_path?
@@ -278,14 +278,14 @@
             on_error     : on_error
             switch_over  : true

-    render_alert : ->
+    render_no_extension_alert : ->
         <Alert bsStyle='warning' style={marginTop: '10px', fontWeight : 'bold'}>
-
Warning: Are you sure you want to create a file with no extensions?

-            <ButtonToolbar>
+
Warning: Are you sure you want to create a file with no extension?

+            <ButtonToolbar style={marginTop:'10px'}>
                 <Button onClick={=>@create_file()} bsStyle='success'>
                     Create file
                 </Button>
-                <Button onClick={=>@setState(warning : false)} bsStyle='default'>
+                <Button onClick={=>@setState(extension_warning : false)} bsStyle='default'>
                     Cancel
                 </Button>
             </ButtonToolbar>
@@ -308,12 +308,12 @@
                                 ref         = 'project_new_filename'
                                 value       = @state.filename
                                 type        = 'text'
-                                disabled    = @state.warning
+                                disabled    = @state.extension_warning
                                 placeholder = 'Name your file, folder, or paste in a link...'
                                 onChange    = {=>@setState(filename : ReactDOM.findDOMNode(@refs.project_new_filename).value)} />
                         </FormGroup>
                     </form>
-                    {@render_alert() if @state.warning}
+                    {@render_no_extension_alert() if @state.extension_warning}
                     {if @state.error then @render_error()}

Select the type

                     <FileTypeSelector create_file={@create_file} create_folder={@create_folder}>


Closes #32; Significantly Improved QueryCSS

QueryCSS now takes the full DOM tree into account when collecting styles from reference elements. Previously the query was limited to only the containing element of the canvas and completely missed styles that referenced parents further up the DOM tree. Now we build a full tree, up to (but not including) the body tag and use it query styles.

--- coffee/epoch.coffee
+++ coffee/epoch.coffee
@@ -313,6 +313,10 @@
 # This allows canvas based visualizations to use the same styles as their
 # SVG counterparts.
 class QueryCSS
+  # Handles automatic container id generation
+  containerCount = 0
+  nextContainerId = -> "epoch-container-#{containerCount++}"
+
   # Key-Value cache for computed styles that we found using this class.
   @cache = {}

@@ -336,20 +340,11 @@
   # @param [String] selector Selector from which to derive the styles
   # @param container The containing element for a chart.
   @hash: (selector, container) ->
-    id = container.attr('id')
-    if (klass = container.attr('class'))?
-      klass = klass.split(/\s+/).join('.')
-
-    pre = if id? and klass?
-      "\##{id}.#{klass}"
-    else if id?
-      "\##{id}"
-    else if klass?
-      ".#{klass}"
-    else
-      ""
-
-    return "#{pre}__#{selector}"
+    containerId = $(container).data('epoch-container-id')
+    unless containerId?
+      containerId = nextContainerId()
+      $(container).data('epoch-container-id', containerId)
+    return "#{containerId}__#{selector}"

   # @return The computed styles for the given selector in the given container element.
   # @param [String] selector Selector from which to derive the styles.
@@ -360,34 +355,46 @@
     cache = QueryCSS.cache[cacheKey]
     return cache if cache?

-    # 1) Create Reference SVG
-    clone = $(container).clone().html('<svg></svg>')
-    clone.removeAttr('style')
-
-
-    QueryCSS.getContainer().append(clone)
-    svg = $('svg', clone)
-
-    # 2) Create Reference Element
-    levels = selector.split(/\s+/)
-    parent = root = put(levels.shift())
-    while levels.length
-      el = put(levels.shift())
+    # 1) Build a full reference tree (parents, container, and selector elements)
+    parents = []
+    for element in $(container).parents()
+      break if element.tagName.toLowerCase() == 'body'
+      parents.unshift(element)
+    parents.push $(container).get(0)
+
+    selectorList = []
+    for element in parents
+      sel = element.tagName.toLowerCase()
+      if element.id? and element.id.length > 0
+        sel += '#' + element.id
+      if element.className? and element.className.length > 0
+        sel += '.' + $.trim(element.className).replace(/\s+/g, '.')
+      selectorList.push sel
+
+    selectorList.push('svg')
+
+    for subSelector in $.trim(selector).split(/\s+/)
+      selectorList.push(subSelector)
+
+    parent = root = put(selectorList.shift())
+    while selectorList.length
+      el = put(selectorList.shift())
       parent.appendChild el
       parent = el
-    svg.html(root)
-    ref = $(selector, svg)
-
-    # 3) Collect & Cache Styles
+
+    # 2) Place the reference tree and fetch styles given the selector
+    QueryCSS.getContainer().append(root)
+    ref = $(selector, root)
     styles = {}
     for name in QueryCSS.styleList
       styles[name] = ref.css(name)
     QueryCSS.cache[cacheKey] = styles

-    # 4) Cleanup and return the styles
-    $(clone, QueryCSS.getContainer()).remove()
+    # 3) Cleanup and return the styles
+    QueryCSS.getContainer().html('')
     return styles

+
 Epoch.QueryCSS = QueryCSS

 # "I think, baby, I was born just a little late!" -- Middle Class Rut


Added Cutpurse and Treasury, moved Familiar's priority

--- basicAI.coffee
+++ basicAI.coffee
@@ -208,7 +208,9 @@
     "Caravan"
     "Market"
     "Peddler"
+    "Treasury"
     "Conspirator" if my.inPlay.length >= 2
+    "Familiar"
     "Great Hall"
     "Wishing Well"
     "Lighthouse"
@@ -218,7 +220,6 @@
     "Watchtower" if my.actions > 1 and my.hand.length <= 4
     "Library" if my.actions > 1 and my.hand.length <= 5
     # Sixth priority: card-cycling that might improve the hand.
-    "Familiar" # after other non-terminals in case non-terminal draws KC/TR
     "Pawn"
     "Warehouse"
     "Cellar"
@@ -250,6 +251,7 @@
     "Expand"
     "Remodel"
     "Militia"
+    "Cutpurse"
     "Bridge"
     "Horse Traders"
     "Steward"


Override process.argv as well as process.ARGV for eval'd scripts

--- command_line.coffee
+++ command_line.coffee
@@ -44,7 +44,7 @@
   if separator >= 0
     flags: sources[(separator + 1)...sources.length]
     sources: sources[0...separator]
-  process.ARGV = flags
+  process.ARGV: process.argv: flags
   watch_scripts() if options.watch
   compile_scripts()
   this


Added component context to the diplomat interface (#5091)

--- app/views/i18n/I18NEditComponentView.coffee
+++ app/views/i18n/I18NEditComponentView.coffee
@@ -45,3 +45,10 @@
                 @wrapRow "#{propDoc.name} arg description #{argDoc.name} (#{progLang})", ['description', progLang], description, i18n[lang]?[progLang]?.description, path, 'markdown'
             else if _.isString argDoc.description
               @wrapRow "#{propDoc.name} arg description #{argDoc.name}", ['description'], argDoc.description, i18n[lang]?.description, path, 'markdown'
+
+    # Code context
+    i18n = @model.get("i18n")
+    context = @model.get("context")
+    if i18n and context
+      for key, value of context
+        @wrapRow "Code context value", ['context', key], value, i18n[lang]?.context?[key], []